mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-02 20:38:12 +00:00
296 lines
9.1 KiB
C
296 lines
9.1 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-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);
|
|
}
|