fwupd/plugins/synaptics-prometheus/fu-synaprom-config.c
2021-08-24 11:18:40 -05:00

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);
}