mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-11 10:05:30 +00:00

Devices may want to support more than one protocol, and for some devices (e.g. Unifying peripherals stuck in bootloader mode) you might not even be able to query for the correct protocol anyway.
295 lines
9.4 KiB
C
295 lines
9.4 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_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");
|
|
}
|
|
|
|
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_open (FuDevice *device, GError **error)
|
|
{
|
|
FuDevice *parent = fu_device_get_parent (device);
|
|
return fu_device_open (parent, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaprom_config_close (FuDevice *device, GError **error)
|
|
{
|
|
FuDevice *parent = fu_device_get_parent (device);
|
|
return fu_device_close (parent, error);
|
|
}
|
|
|
|
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->open = fu_synaprom_config_open;
|
|
klass_device->close = fu_synaprom_config_close;
|
|
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);
|
|
}
|