mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-11 06:35:28 +00:00

Before this change calling FuUsbDevice->open() opened the device, and also unconditionally added various GUIDs and InstanceIDs which we normally do in setup. Then fu_device_setup() would call the FuSubclass->setup() vfunc which would have no way of either opting out of the FuUsbDevice->setup()-like behaviour, or controlling if the parent class ->setup is run before or after the subclass setup. Split up FuUsbDevice->open() into clear ->open() and ->setup() phases and add the parent class calls where appropriate. This means that ->setup() now behaves the same as all the other vfuncs.
480 lines
14 KiB
C
480 lines
14 KiB
C
/*
|
|
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
|
|
* Copyright (C) 2019 Synaptics Inc
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-synaprom-common.h"
|
|
#include "fu-synaprom-config.h"
|
|
#include "fu-synaprom-device.h"
|
|
#include "fu-synaprom-firmware.h"
|
|
|
|
struct _FuSynapromDevice {
|
|
FuUsbDevice parent_instance;
|
|
guint8 vmajor;
|
|
guint8 vminor;
|
|
};
|
|
|
|
/* vendor-specific USB control requets to write DFT word (Hayes) */
|
|
#define FU_SYNAPROM_USB_CTRLREQUEST_VENDOR_WRITEDFT 21
|
|
|
|
/* endpoint addresses for command and fingerprint data */
|
|
#define FU_SYNAPROM_USB_REQUEST_EP 0x01
|
|
#define FU_SYNAPROM_USB_REPLY_EP 0x81
|
|
#define FU_SYNAPROM_USB_FINGERPRINT_EP 0x82
|
|
#define FU_SYNAPROM_USB_INTERRUPT_EP 0x83
|
|
|
|
/* le */
|
|
typedef struct __attribute__((packed)) {
|
|
guint16 status;
|
|
} FuSynapromReplyGeneric;
|
|
|
|
/* le */
|
|
typedef struct __attribute__((packed)) {
|
|
guint16 status;
|
|
guint32 buildtime; /* Unix-style build time */
|
|
guint32 buildnum; /* build number */
|
|
guint8 vmajor; /* major version */
|
|
guint8 vminor; /* minor version */
|
|
guint8 target; /* target, e.g. VCSFW_TARGET_ROM */
|
|
guint8 product; /* product, e.g. VCSFW_PRODUCT_FALCON */
|
|
guint8 siliconrev; /* silicon revision */
|
|
guint8 formalrel; /* boolean: non-zero -> formal release */
|
|
guint8 platform; /* Platform (PCB) revision */
|
|
guint8 patch; /* patch level */
|
|
guint8 serial_number[6]; /* 48-bit Serial Number */
|
|
guint8 security[2]; /* bytes 0 and 1 of OTP */
|
|
guint32 patchsig; /* opaque patch signature */
|
|
guint8 iface; /* interface type, see below */
|
|
guint8 otpsig[3]; /* OTP Patch Signature */
|
|
guint16 otpspare1; /* spare space */
|
|
guint8 reserved; /* reserved byte */
|
|
guint8 device_type; /* device type */
|
|
} FuSynapromReplyGetVersion;
|
|
|
|
/* the following bits describe security options in
|
|
** FuSynapromReplyGetVersion::security[1] bit-field */
|
|
#define FU_SYNAPROM_SECURITY1_PROD_SENSOR (1 << 5)
|
|
|
|
G_DEFINE_TYPE (FuSynapromDevice, fu_synaprom_device, FU_TYPE_USB_DEVICE)
|
|
|
|
static gboolean
|
|
fu_synaprom_device_open (FuDevice *device, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
|
|
/* FuUsbDevice->open */
|
|
if (!FU_DEVICE_CLASS (fu_synaprom_device_parent_class)->open (device, error))
|
|
return FALSE;
|
|
|
|
if (!g_usb_device_claim_interface (usb_device, 0x0,
|
|
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_synaprom_device_cmd_send (FuSynapromDevice *device,
|
|
GByteArray *request,
|
|
GByteArray *reply,
|
|
guint timeout_ms,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
gboolean ret;
|
|
gsize actual_len = 0;
|
|
|
|
if (g_getenv ("FWUPD_SYNAPROM_VERBOSE") != NULL) {
|
|
fu_common_dump_full (G_LOG_DOMAIN, "REQST",
|
|
request->data, request->len, 16,
|
|
FU_DUMP_FLAGS_SHOW_ADDRESSES);
|
|
}
|
|
ret = g_usb_device_bulk_transfer (usb_device,
|
|
FU_SYNAPROM_USB_REQUEST_EP,
|
|
request->data,
|
|
request->len,
|
|
&actual_len,
|
|
timeout_ms, NULL, error);
|
|
if (!ret) {
|
|
g_prefix_error (error, "failed to request: ");
|
|
return FALSE;
|
|
}
|
|
if (actual_len < request->len) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"only sent 0x%04x of 0x%04x",
|
|
(guint) actual_len, request->len);
|
|
return FALSE;
|
|
}
|
|
|
|
ret = g_usb_device_bulk_transfer (usb_device,
|
|
FU_SYNAPROM_USB_REPLY_EP,
|
|
reply->data,
|
|
reply->len,
|
|
NULL, /* allowed to return short read */
|
|
timeout_ms, NULL, error);
|
|
if (!ret) {
|
|
g_prefix_error (error, "failed to reply: ");
|
|
return FALSE;
|
|
}
|
|
if (g_getenv ("FWUPD_SYNAPROM_VERBOSE") != NULL) {
|
|
fu_common_dump_full (G_LOG_DOMAIN, "REPLY",
|
|
reply->data, actual_len, 16,
|
|
FU_DUMP_FLAGS_SHOW_ADDRESSES);
|
|
}
|
|
|
|
/* parse as FuSynapromReplyGeneric */
|
|
if (reply->len >= sizeof(FuSynapromReplyGeneric)) {
|
|
FuSynapromReplyGeneric *hdr = (FuSynapromReplyGeneric *) reply->data;
|
|
return fu_synaprom_error_from_status (GUINT16_FROM_LE(hdr->status), error);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
fu_synaprom_device_set_version (FuSynapromDevice *self,
|
|
guint8 vmajor, guint8 vminor, guint32 buildnum)
|
|
{
|
|
g_autofree gchar *str = NULL;
|
|
|
|
/* We decide to skip 10.02.xxxxxx firmware, so we force the minor version from 0x02
|
|
** to 0x01 to make the devices with 0x02 minor version firmware allow to be updated
|
|
** back to minor version 0x01. */
|
|
if (vmajor == 0x0a && vminor == 0x02) {
|
|
g_debug ("quirking vminor from %02x to 01", vminor);
|
|
vminor = 0x01;
|
|
}
|
|
|
|
/* set display version */
|
|
str = g_strdup_printf ("%02u.%02u.%u", vmajor, vminor, buildnum);
|
|
fu_device_set_version (FU_DEVICE (self), str);
|
|
|
|
/* we need this for checking the firmware compatibility later */
|
|
self->vmajor = vmajor;
|
|
self->vminor = vminor;
|
|
}
|
|
|
|
static void
|
|
fu_synaprom_device_set_serial_number (FuSynapromDevice *self, guint64 serial_number)
|
|
{
|
|
g_autofree gchar *str = NULL;
|
|
str = g_strdup_printf ("%" G_GUINT64_FORMAT, serial_number);
|
|
fu_device_set_serial (FU_DEVICE (self), str);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaprom_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuSynapromDevice *self = FU_SYNAPROM_DEVICE (device);
|
|
FuSynapromReplyGetVersion pkt;
|
|
guint32 product;
|
|
guint64 serial_number = 0;
|
|
g_autoptr(GByteArray) request = NULL;
|
|
g_autoptr(GByteArray) reply = NULL;
|
|
|
|
/* FuUsbDevice->setup */
|
|
if (!FU_DEVICE_CLASS (fu_synaprom_device_parent_class)->setup (device, error))
|
|
return FALSE;
|
|
|
|
/* get version */
|
|
request = fu_synaprom_request_new (FU_SYNAPROM_CMD_GET_VERSION, NULL, 0);
|
|
reply = fu_synaprom_reply_new (sizeof(FuSynapromReplyGetVersion));
|
|
if (!fu_synaprom_device_cmd_send (self, request, reply, 250, error)) {
|
|
g_prefix_error (error, "failed to get version: ");
|
|
return FALSE;
|
|
}
|
|
memcpy (&pkt, reply->data, sizeof(pkt));
|
|
product = GUINT32_FROM_LE(pkt.product);
|
|
g_debug ("product ID is %u, version=%u.%u, buildnum=%u prod=%i",
|
|
product, pkt.vmajor, pkt.vminor, GUINT32_FROM_LE(pkt.buildnum),
|
|
pkt.security[1] & FU_SYNAPROM_SECURITY1_PROD_SENSOR);
|
|
fu_synaprom_device_set_version (self, pkt.vmajor, pkt.vminor,
|
|
GUINT32_FROM_LE(pkt.buildnum));
|
|
|
|
/* get serial number */
|
|
memcpy (&serial_number, pkt.serial_number, sizeof(pkt.serial_number));
|
|
fu_synaprom_device_set_serial_number (self, serial_number);
|
|
|
|
/* check device type */
|
|
if (product == FU_SYNAPROM_PRODUCT_PROMETHEUS) {
|
|
fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
|
} else if (product == FU_SYNAPROM_PRODUCT_PROMETHEUSPBL ||
|
|
product == FU_SYNAPROM_PRODUCT_PROMETHEUSMSBL) {
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
|
} else {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"device %u is not supported by this plugin",
|
|
product);
|
|
return FALSE;
|
|
}
|
|
|
|
/* add updatable config child, if this is a production sensor */
|
|
if (fu_device_get_children (device)->len == 0 &&
|
|
!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER) &&
|
|
pkt.security[1] & FU_SYNAPROM_SECURITY1_PROD_SENSOR) {
|
|
g_autoptr(FuSynapromConfig) cfg = fu_synaprom_config_new (self);
|
|
fu_device_add_child (FU_DEVICE (device), FU_DEVICE (cfg));
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaprom_device_cmd_download_chunk (FuSynapromDevice *device,
|
|
const GByteArray *chunk,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GByteArray) request = NULL;
|
|
g_autoptr(GByteArray) reply = NULL;
|
|
request = fu_synaprom_request_new (FU_SYNAPROM_CMD_BOOTLDR_PATCH,
|
|
chunk->data, chunk->len);
|
|
reply = fu_synaprom_reply_new (sizeof(FuSynapromReplyGeneric));
|
|
return fu_synaprom_device_cmd_send (device, request, reply, 20000, error);
|
|
}
|
|
|
|
FuFirmware *
|
|
fu_synaprom_device_prepare_fw (FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuSynapromFirmwareMfwHeader hdr;
|
|
guint32 product;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new ();
|
|
|
|
/* parse the firmware */
|
|
if (!fu_firmware_parse (firmware, fw, flags, error))
|
|
return NULL;
|
|
|
|
/* check the update header product and version */
|
|
blob = fu_firmware_get_image_by_id_bytes (firmware, "mfw-update-header", error);
|
|
if (blob == NULL)
|
|
return NULL;
|
|
if (g_bytes_get_size (blob) != sizeof(hdr)) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"MFW metadata is invalid");
|
|
return NULL;
|
|
}
|
|
memcpy (&hdr, g_bytes_get_data (blob, NULL), sizeof(hdr));
|
|
product = GUINT32_FROM_LE(hdr.product);
|
|
if (product != FU_SYNAPROM_PRODUCT_PROMETHEUS) {
|
|
if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) {
|
|
g_warning ("MFW metadata not compatible, "
|
|
"got 0x%02x expected 0x%02x",
|
|
product, (guint) FU_SYNAPROM_PRODUCT_PROMETHEUS);
|
|
} else {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"MFW metadata not compatible, "
|
|
"got 0x%02x expected 0x%02x",
|
|
product, (guint) FU_SYNAPROM_PRODUCT_PROMETHEUS);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return g_steal_pointer (&firmware);
|
|
}
|
|
|
|
gboolean
|
|
fu_synaprom_device_write_fw (FuSynapromDevice *self, GBytes *fw, GError **error)
|
|
{
|
|
const guint8 *buf;
|
|
gsize sz = 0;
|
|
|
|
/* write chunks */
|
|
fu_device_set_progress (FU_DEVICE (self), 10);
|
|
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
|
|
buf = g_bytes_get_data (fw, &sz);
|
|
while (sz != 0) {
|
|
guint32 chunksz;
|
|
g_autoptr(GByteArray) chunk = g_byte_array_new ();
|
|
|
|
/* get chunk size */
|
|
if (sz < sizeof(guint32)) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"No enough data for patch len");
|
|
return FALSE;
|
|
}
|
|
memcpy (&chunksz, buf, sizeof(guint32));
|
|
buf += sizeof(guint32);
|
|
sz -= sizeof(guint32);
|
|
if (sz < chunksz) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"No enough data for patch chunk");
|
|
return FALSE;
|
|
}
|
|
|
|
/* download chunk */
|
|
g_byte_array_append (chunk, buf, chunksz);
|
|
if (!fu_synaprom_device_cmd_download_chunk (self, chunk, error))
|
|
return FALSE;
|
|
|
|
/* next chunk */
|
|
buf += chunksz;
|
|
sz -= chunksz;
|
|
}
|
|
|
|
/* success! */
|
|
fu_device_set_progress (FU_DEVICE (self), 100);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaprom_device_write_firmware (FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuSynapromDevice *self = FU_SYNAPROM_DEVICE (device);
|
|
g_autoptr(GBytes) fw = NULL;
|
|
|
|
/* get default image */
|
|
fw = fu_firmware_get_image_by_id_bytes (firmware, "mfw-update-payload", error);
|
|
if (fw == NULL)
|
|
return FALSE;
|
|
|
|
return fu_synaprom_device_write_fw (self, fw, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaprom_device_attach (FuDevice *device, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
gboolean ret;
|
|
gsize actual_len = 0;
|
|
guint8 data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
/* sanity check */
|
|
if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
|
g_debug ("already in runtime mode, skipping");
|
|
return TRUE;
|
|
}
|
|
|
|
ret = g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
FU_SYNAPROM_USB_CTRLREQUEST_VENDOR_WRITEDFT,
|
|
0x0000, 0x0000,
|
|
data, sizeof(data), &actual_len,
|
|
2000, NULL, error);
|
|
if (!ret)
|
|
return FALSE;
|
|
if (actual_len != sizeof(data)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"only sent 0x%04x of 0x%04x",
|
|
(guint) actual_len, (guint) sizeof(data));
|
|
return FALSE;
|
|
}
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
|
|
if (!g_usb_device_reset (usb_device, error)) {
|
|
g_prefix_error (error, "failed to force-reset device: ");
|
|
return FALSE;
|
|
}
|
|
fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaprom_device_detach (FuDevice *device, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
gboolean ret;
|
|
gsize actual_len = 0;
|
|
guint8 data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 };
|
|
|
|
/* sanity check */
|
|
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
|
g_debug ("already in bootloader mode, skipping");
|
|
return TRUE;
|
|
}
|
|
|
|
ret = g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
FU_SYNAPROM_USB_CTRLREQUEST_VENDOR_WRITEDFT,
|
|
0x0000, 0x0000,
|
|
data, sizeof(data), &actual_len,
|
|
2000, NULL, error);
|
|
if (!ret)
|
|
return FALSE;
|
|
if (actual_len != sizeof(data)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"only sent 0x%04x of 0x%04x",
|
|
(guint) actual_len, (guint) sizeof(data));
|
|
return FALSE;
|
|
}
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
|
|
if (!g_usb_device_reset (usb_device, error)) {
|
|
g_prefix_error (error, "failed to force-reset device: ");
|
|
return FALSE;
|
|
}
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_synaprom_device_init (FuSynapromDevice *self)
|
|
{
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY);
|
|
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN);
|
|
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_TRIPLET);
|
|
fu_device_add_protocol (FU_DEVICE (self), "com.synaptics.prometheus");
|
|
fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
|
|
fu_device_set_name (FU_DEVICE (self), "Prometheus");
|
|
fu_device_set_summary (FU_DEVICE (self), "Fingerprint reader");
|
|
fu_device_set_vendor (FU_DEVICE (self), "Synaptics");
|
|
fu_device_add_icon (FU_DEVICE (self), "touchpad-disabled");
|
|
}
|
|
|
|
static void
|
|
fu_synaprom_device_class_init (FuSynapromDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
klass_device->write_firmware = fu_synaprom_device_write_firmware;
|
|
klass_device->prepare_firmware = fu_synaprom_device_prepare_fw;
|
|
klass_device->setup = fu_synaprom_device_setup;
|
|
klass_device->reload = fu_synaprom_device_setup;
|
|
klass_device->attach = fu_synaprom_device_attach;
|
|
klass_device->detach = fu_synaprom_device_detach;
|
|
klass_device->open = fu_synaprom_device_open;
|
|
}
|
|
|
|
FuSynapromDevice *
|
|
fu_synaprom_device_new (FuUsbDevice *device)
|
|
{
|
|
FuSynapromDevice *self;
|
|
self = g_object_new (FU_TYPE_SYNAPROM_DEVICE, NULL);
|
|
if (device != NULL)
|
|
fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device));
|
|
return FU_SYNAPROM_DEVICE (self);
|
|
}
|