/* * 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_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE); 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); }