fwupd/plugins/synaptics-prometheus/fu-synaprom-config.c
Richard Hughes 0cc06f032c Add a flag to open and close with the parent device
This is a common pattern that is used in lots of plugins, so move the
logic into common code.
2021-07-11 16:37:47 +01:00

282 lines
9.2 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_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);
}