/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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; /* 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); if (!fu_device_setup (FU_DEVICE (cfg), error)) { g_prefix_error (error, "failed to get config version: "); return FALSE; } 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); }