/* * 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-firmware.h" struct _FuSynapromConfig { FuDevice parent_instance; guint32 configid1; /* config ID1 */ guint32 configid2; /* config ID2 */ }; /* Iotas can exceed the size of available RAM in the part. * In order to allow the host to read them the IOTA_FIND command supports * transferring iotas with multiple commands */ typedef struct __attribute__((packed)) { guint16 itype; /* type of iotas to find */ guint16 flags; /* flags, see below */ guint8 maxniotas; /* maximum number of iotas to return, 0 = unlimited */ guint8 firstidx; /* first index of iotas to return */ guint8 dummy[2]; guint32 offset; /* byte offset of data to return */ guint32 nbytes; /* maximum number of bytes to return */ } FuSynapromCmdIotaFind; /* this is followed by a chain of iotas, as follows */ typedef struct __attribute__((packed)) { guint16 status; guint32 fullsize; guint16 nbytes; guint16 itype; } FuSynapromReplyIotaFindHdr; /* this iota contains the configuration id and version */ typedef struct __attribute__((packed)) { guint32 config_id1; /* YYMMDD */ guint32 config_id2; /* HHMMSS */ guint16 version; guint16 unused[3]; } FuSynapromIotaConfigVersion; /* le */ typedef struct __attribute__((packed)) { guint32 product; guint32 id1; /* verification ID */ guint32 id2; /* verification ID */ guint16 version; /* config version */ guint8 unused[2]; } FuSynapromFirmwareCfgHeader; #define FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_ALLIOTAS 0x0001 /* itype ignored*/ #define FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_READMAX 0x0002 /* nbytes ignored */ #define FU_SYNAPROM_MAX_IOTA_READ_SIZE (64 * 1024) /* max size of iota data returned */ #define FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION 0x0009 /* Configuration id and version */ G_DEFINE_TYPE (FuSynapromConfig, fu_synaprom_config, FU_TYPE_DEVICE) static gboolean fu_synaprom_config_setup (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); FuSynapromConfig *self = FU_SYNAPROM_CONFIG (device); FuSynapromCmdIotaFind cmd = { 0x0 }; FuSynapromIotaConfigVersion cfg; FuSynapromReplyIotaFindHdr hdr; g_autofree gchar *version = NULL; g_autoptr(GByteArray) reply = NULL; g_autoptr(GByteArray) request = NULL; g_autofree gchar *devid = NULL; /* get IOTA */ cmd.itype = GUINT16_TO_LE((guint16)FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION); cmd.flags = GUINT16_TO_LE((guint16)FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_READMAX); request = fu_synaprom_request_new (FU_SYNAPROM_CMD_IOTA_FIND, &cmd, sizeof(cmd)); reply = fu_synaprom_reply_new (sizeof(FuSynapromReplyIotaFindHdr) + FU_SYNAPROM_MAX_IOTA_READ_SIZE); if (!fu_synaprom_device_cmd_send (FU_SYNAPROM_DEVICE (parent), request, reply, 5000, error)) return FALSE; if (reply->len < sizeof(hdr) + sizeof(cfg)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG return data invalid size: 0x%04x", reply->len); return FALSE; } memcpy (&hdr, reply->data, sizeof(hdr)); if (GUINT32_FROM_LE(hdr.itype) != FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG iota had invalid itype: 0x%04x", GUINT32_FROM_LE(hdr.itype)); return FALSE; } if (!fu_memcpy_safe ((guint8 *) &cfg, sizeof(cfg), 0x0, /* dst */ reply->data, reply->len, sizeof(hdr), /* src */ sizeof(cfg), error)) return FALSE; self->configid1 = GUINT32_FROM_LE(cfg.config_id1); self->configid2 = GUINT32_FROM_LE(cfg.config_id2); g_debug ("id1=%u, id2=%u, ver=%u", self->configid1, self->configid2, GUINT16_FROM_LE(cfg.version)); /* append the configid to the generated GUID */ devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X&CFG1_%u&CFG2_%u", fu_usb_device_get_vid (FU_USB_DEVICE (parent)), fu_usb_device_get_pid (FU_USB_DEVICE (parent)), self->configid1, self->configid2); fu_device_add_instance_id (FU_DEVICE (self), devid); /* no downgrades are allowed */ version = g_strdup_printf ("%04u", GUINT16_FROM_LE(cfg.version)); fu_device_set_version (FU_DEVICE (self), version); fu_device_set_version_lowest (FU_DEVICE (self), version); return TRUE; } static FuFirmware * fu_synaprom_config_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (device); FuSynapromFirmwareCfgHeader hdr; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new (); guint32 product; guint32 id1; guint32 id2; /* 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, "cfg-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, "CFG 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 ("CFG 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, "CFG metadata not compatible, " "got 0x%02x expected 0x%02x", product, (guint) FU_SYNAPROM_PRODUCT_PROMETHEUS); return NULL; } } id1 = GUINT32_FROM_LE(hdr.id1); id2 = GUINT32_FROM_LE(hdr.id2); if (id1 != self->configid1 || id2 != self->configid2) { if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) { g_warning ("CFG version not compatible, " "got %u:%u expected %u:%u", id1, id2, self->configid1, self->configid2); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG version not compatible, " "got %u:%u expected %u:%u", id1, id2, self->configid1, self->configid2); return NULL; } } /* success */ return g_steal_pointer (&firmware); } static gboolean fu_synaprom_config_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent (device); g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_image_by_id_bytes (firmware, "cfg-update-payload", error); if (fw == NULL) return FALSE; /* I assume the CFG/MFW difference is detected in the device...*/ return fu_synaprom_device_write_fw (FU_SYNAPROM_DEVICE (parent), fw, error); } static void fu_synaprom_config_init (FuSynapromConfig *self) { fu_device_add_protocol (FU_DEVICE (self), "com.synaptics.prometheus.config"); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_logical_id (FU_DEVICE (self), "cfg"); fu_device_set_name (FU_DEVICE (self), "Prometheus IOTA Config"); fu_device_set_summary (FU_DEVICE (self), "Fingerprint reader config"); fu_device_add_icon (FU_DEVICE (self), "touchpad-disabled"); } static void fu_synaprom_config_constructed (GObject *obj) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG (obj); FuDevice *parent = fu_device_get_parent (FU_DEVICE (self)); g_autofree gchar *devid = NULL; /* append the firmware kind to the generated GUID */ devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X-cfg", fu_usb_device_get_vid (FU_USB_DEVICE (parent)), fu_usb_device_get_pid (FU_USB_DEVICE (parent))); fu_device_add_instance_id (FU_DEVICE (self), devid); G_OBJECT_CLASS (fu_synaprom_config_parent_class)->constructed (obj); } static gboolean fu_synaprom_config_attach (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); return fu_device_attach (parent, error); } static gboolean fu_synaprom_config_detach (FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent (device); return fu_device_detach (parent, error); } static void fu_synaprom_config_class_init (FuSynapromConfigClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = fu_synaprom_config_constructed; klass_device->write_firmware = fu_synaprom_config_write_firmware; klass_device->prepare_firmware = fu_synaprom_config_prepare_firmware; klass_device->setup = fu_synaprom_config_setup; klass_device->reload = fu_synaprom_config_setup; klass_device->attach = fu_synaprom_config_attach; klass_device->detach = fu_synaprom_config_detach; } FuSynapromConfig * fu_synaprom_config_new (FuSynapromDevice *device) { FuSynapromConfig *self; self = g_object_new (FU_TYPE_SYNAPROM_CONFIG, "parent", device, NULL); return FU_SYNAPROM_CONFIG (self); }