mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-05 23:19:50 +00:00
plugins: add new plugin for Synaptics CAPE devices (#3746)
CAPE family is Audio DSP for a board range of applications in IOT, PC and mobile can be interfaced via I2C, UART or USB interface. This patch is only for CX31993 and CX31988 chips, there is not immediate plans is to add support to other CAPE devices. CX31993 have two separate firmware .hid file for for each partition. It need to convert two .hid files into a .fw file for fwupd tool to consume. Currently, this patch is only support for EPOS headsets with basic firmware update feature. Either new code singing or manifest.xml are unsupported yet. The code has been tested with CX31993 EVK board. A test firmware file is put at 'src/fuzzing/firmware/synaptics-cape.fw' synaptics-cape: Port to new FuProgress API and style fixups synaptics-cape: Fix compile errors and add missing test fw file Signed-off-by: Simon Ho <simon.ho@synaptics.com> synaptics-cape: Fix fuzzer test Signed-off-by: Simon Ho <simon.ho@synaptics.com> synaptics-cape: Fix progress bar number Signed-off-by: Simon Ho <simon.ho@synaptics.com> synaptics-cape: Mark the fuzzing target trivial: Use a stable GLib branch for fuzzing synaptics-cape: Fix progress bar number Signed-off-by: Simon Ho <simon.ho@synaptics.com> synaptics-cape: Fix readme synaptics-cape: Style fixups synaptics-cape: Fix progress bar percentage synaptics-cape: Style fixups
This commit is contained in:
parent
6817648c6b
commit
5e67108ab8
@ -214,7 +214,9 @@ class Fuzzer:
|
||||
def _build(bld: Builder) -> None:
|
||||
|
||||
# GLib
|
||||
src = bld.checkout_source("glib", url="https://gitlab.gnome.org/GNOME/glib.git")
|
||||
src = bld.checkout_source(
|
||||
"glib", url="https://gitlab.gnome.org/GNOME/glib.git", commit="glib-2-68"
|
||||
)
|
||||
bld.build_meson_project(
|
||||
src,
|
||||
[
|
||||
@ -321,6 +323,7 @@ def _build(bld: Builder) -> None:
|
||||
Fuzzer("redfish-smbios", srcdir="redfish", pattern="redfish-smbios"),
|
||||
Fuzzer("solokey"),
|
||||
Fuzzer("synaprom", srcdir="synaptics-prometheus"),
|
||||
Fuzzer("synaptics-cape"),
|
||||
Fuzzer("synaptics-mst"),
|
||||
Fuzzer("synaptics-rmi"),
|
||||
Fuzzer("wacom-usb", pattern="wac-firmware", globstr="wacom*"),
|
||||
|
@ -461,6 +461,7 @@ done
|
||||
%if 0%{?have_dell}
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_mst.so
|
||||
%endif
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_cape.so
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_cxaudio.so
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_prometheus.so
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_rmi.so
|
||||
|
@ -54,6 +54,7 @@ subdir('rts54hub')
|
||||
subdir('solokey')
|
||||
subdir('steelseries')
|
||||
subdir('superio')
|
||||
subdir('synaptics-cape')
|
||||
subdir('synaptics-cxaudio')
|
||||
subdir('synaptics-mst')
|
||||
subdir('synaptics-prometheus')
|
||||
|
41
plugins/synaptics-cape/README.md
Normal file
41
plugins/synaptics-cape/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Synaptics CAPE devices
|
||||
|
||||
## Introduction
|
||||
|
||||
This plugin is used to update Synaptics CAPE based audio devices.
|
||||
|
||||
## Firmware Format
|
||||
|
||||
The daemon will decompress the cabinet archive and extract a firmware blob.
|
||||
|
||||
This plugin supports the following protocol ID:
|
||||
|
||||
* com.synaptics.cape
|
||||
|
||||
## GUID Generation
|
||||
|
||||
These devices use the standard USB DeviceInstanceId values, e.g.
|
||||
|
||||
* `USB\VID_1395&PID_0293`
|
||||
|
||||
These devices also use custom GUID values, e.g.
|
||||
|
||||
* `SYNAPTICS_CAPE\CX31993`
|
||||
* `SYNAPTICS_CAPE\CX31988`
|
||||
|
||||
## Update Behavior
|
||||
|
||||
The firmware is deployed when the device is in normal runtime mode, and the
|
||||
device will reset when the new firmware has been written.
|
||||
|
||||
## Vendor ID Security
|
||||
|
||||
The vendor ID is set from the USB vendor, in this instance set to `USB:0x1395`
|
||||
|
||||
## Quirk Use
|
||||
|
||||
This plugin uses no plugin-specific quirks.
|
||||
|
||||
## External Interface Access
|
||||
|
||||
This plugin requires read/write access to `/dev/bus/usb`.
|
20
plugins/synaptics-cape/fu-plugin-synaptics-cape.c
Normal file
20
plugins/synaptics-cape/fu-plugin-synaptics-cape.c
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Synaptics Incorporated <simon.ho@synaptics.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include "fu-synaptics-cape-device.h"
|
||||
#include "fu-synaptics-cape-firmware.h"
|
||||
|
||||
void
|
||||
fu_plugin_init(FuPlugin *plugin)
|
||||
{
|
||||
fu_plugin_set_build_hash(plugin, FU_BUILD_HASH);
|
||||
fu_plugin_add_device_gtype(plugin, FU_TYPE_SYNAPTICS_CAPE_DEVICE);
|
||||
fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPTICS_CAPE_FIRMWARE);
|
||||
}
|
673
plugins/synaptics-cape/fu-synaptics-cape-device.c
Normal file
673
plugins/synaptics-cape/fu-synaptics-cape-device.c
Normal file
@ -0,0 +1,673 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Synaptics Incorporated <simon.ho@synaptics.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "fu-synaptics-cape-device.h"
|
||||
#include "fu-synaptics-cape-firmware.h"
|
||||
|
||||
/* defines timings */
|
||||
#define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_WRITE_TIMEOUT 20000 /* us */
|
||||
#define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_READ_TIMEOUT 30000 /* us */
|
||||
#define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_INTERVAL 10 /* ms */
|
||||
#define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_TIMEOUT 300 /* ms */
|
||||
#define FU_SYNAPTICS_CAPE_DEVICE_USB_RESET_DELAY_MS 3000 /* ms */
|
||||
|
||||
/* define CAPE command constant values and macro */
|
||||
#define FU_SYNAPTICS_CAPE_DEVICE_GOLEM_REPORT_ID 1 /* HID report id */
|
||||
|
||||
#define FU_SYNAPTICS_CAPE_CMD_MAX_DATA_LEN 13 /* number of guint32 */
|
||||
#define FU_SYNAPTICS_CAPE_CMD_WRITE_DATAL_LEN 8 /* number of guint32 */
|
||||
#define FU_SYNAPTICS_CAPE_WORD_IN_BYTES 4 /* bytes */
|
||||
|
||||
#define FU_SYNAPTICS_CAPE_CMD_APP_ID(a, b, c, d) \
|
||||
((((a)-0x20) << 8) | (((b)-0x20) << 14) | (((c)-0x20) << 20) | (((d)-0x20) << 26))
|
||||
|
||||
/* CAPE command return codes */
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_GENERIC_FAILURE (-1025)
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_ALREADY_EXISTS (-1026)
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_NULL_APP_POINTER (-1027)
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_NULL_MODULE_POINTER (-1028)
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_NULL_STREAM_POINTER (-1029)
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_NULL_POINTER (-1030)
|
||||
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_BAD_APP_ID (-1031)
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_MODULE_TYPE_HAS_NO_API (-1034)
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_BAD_MAGIC_NUMBER (-1052)
|
||||
#define FU_SYNAPTICS_CAPE_MODULE_RC_CMD_MODE_UNSUPPORTED (-1056)
|
||||
|
||||
#define FU_SYNAPTICS_CMD_GET_FLAG 0x100 /* GET flag */
|
||||
|
||||
/* CAPE message structure, Little endian */
|
||||
typedef struct __attribute__((packed)) {
|
||||
gint16 data_len : 16; /* data length in dwords */
|
||||
guint16 cmd_id : 15; /* Command id */
|
||||
guint16 reply : 1; /* Host want a reply from device, 1 = true */
|
||||
guint32 module_id; /* Module id */
|
||||
guint32 data[FU_SYNAPTICS_CAPE_CMD_MAX_DATA_LEN]; /* Command data */
|
||||
} FuCapCmd;
|
||||
|
||||
/* CAPE HID report structure */
|
||||
typedef struct __attribute__((packed)) {
|
||||
guint16 report_id; /* two bytes of report id, this should be 1 */
|
||||
FuCapCmd cmd;
|
||||
} FuCapCmdHidReport;
|
||||
|
||||
/* CAPE Commands */
|
||||
typedef enum {
|
||||
FU_SYNAPTICS_CMD_FW_UPDATE_START = 0xC8, /* notifies firmware update started */
|
||||
FU_SYNAPTICS_CMD_FW_UPDATE_WRITE = 0xC9, /* updates firmware data */
|
||||
FU_SYNAPTICS_CMD_FW_UPDATE_END = 0xCA, /* notifies firmware update finished */
|
||||
FU_SYNAPTICS_CMD_MCU_SOFT_RESET = 0xAF, /* reset device*/
|
||||
FU_SYNAPTICS_CMD_FW_GET_ACTIVE_PARTITION = 0x1CF, /* gets cur active partition number */
|
||||
FU_SYNAPTICS_CMD_GET_VERSION = 0x103, /* gets cur firmware version */
|
||||
} FuCommand;
|
||||
|
||||
/* CAPE Fuupd device structure */
|
||||
struct _FuSynapticsCapeDevice {
|
||||
FuHidDevice parent_instance;
|
||||
guint32 ActivePartition; /* active partition, either 1 or 2 */
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(FuSynapticsCapeDevice, fu_synaptics_cape_device, FU_TYPE_HID_DEVICE)
|
||||
|
||||
/* Sends SET_REPORT to device */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_set_report(FuSynapticsCapeDevice *self,
|
||||
const FuCapCmdHidReport *data,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(data != NULL, FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
if (g_getenv("FWUPD_SYNAPTICS_CAPE_HID_REPORT_VERBOSE") != NULL)
|
||||
fu_common_dump_raw(G_LOG_DOMAIN, "SetReport", (guint8 *)data, sizeof(*data));
|
||||
|
||||
return fu_hid_device_set_report(FU_HID_DEVICE(self),
|
||||
FU_SYNAPTICS_CAPE_DEVICE_GOLEM_REPORT_ID,
|
||||
(guint8 *)data,
|
||||
sizeof(*data),
|
||||
FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_WRITE_TIMEOUT,
|
||||
FU_HID_DEVICE_FLAG_NONE,
|
||||
error);
|
||||
}
|
||||
|
||||
/* Gets data from device via GET_REPORT */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_get_report(FuSynapticsCapeDevice *self,
|
||||
FuCapCmdHidReport *data,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(data != NULL, FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
if (!fu_hid_device_get_report(FU_HID_DEVICE(self),
|
||||
FU_SYNAPTICS_CAPE_DEVICE_GOLEM_REPORT_ID,
|
||||
(guint8 *)data,
|
||||
sizeof(*data),
|
||||
FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_READ_TIMEOUT,
|
||||
FU_HID_DEVICE_FLAG_NONE,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
if (g_getenv("FWUPD_SYNAPTICS_CAPE_HID_REPORT_VERBOSE") != NULL)
|
||||
fu_common_dump_raw(G_LOG_DOMAIN, "GetReport", (guint8 *)data, sizeof(*data));
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* dump CAPE command error if any */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_rc_set_error(const FuCapCmd *rsp, GError **error)
|
||||
{
|
||||
g_return_val_if_fail(rsp != NULL, FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
if (rsp->data_len >= 0)
|
||||
return TRUE;
|
||||
|
||||
switch (rsp->data_len) {
|
||||
case FU_SYNAPTICS_CAPE_MODULE_RC_GENERIC_FAILURE:
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: generic failure");
|
||||
break;
|
||||
case FU_SYNAPTICS_CAPE_MODULE_RC_ALREADY_EXISTS:
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: already exists");
|
||||
break;
|
||||
case FU_SYNAPTICS_CAPE_MODULE_RC_NULL_APP_POINTER:
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: null app pointer");
|
||||
break;
|
||||
case FU_SYNAPTICS_CAPE_MODULE_RC_NULL_MODULE_POINTER:
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: null module pointer");
|
||||
break;
|
||||
case FU_SYNAPTICS_CAPE_MODULE_RC_NULL_POINTER:
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: null pointer");
|
||||
break;
|
||||
case FU_SYNAPTICS_CAPE_MODULE_RC_BAD_APP_ID:
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: bad app id");
|
||||
break;
|
||||
case FU_SYNAPTICS_CAPE_MODULE_RC_MODULE_TYPE_HAS_NO_API:
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: has no api");
|
||||
break;
|
||||
case FU_SYNAPTICS_CAPE_MODULE_RC_BAD_MAGIC_NUMBER:
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: bad magic number");
|
||||
break;
|
||||
case FU_SYNAPTICS_CAPE_MODULE_RC_CMD_MODE_UNSUPPORTED:
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: mode unsupported");
|
||||
break;
|
||||
default:
|
||||
g_set_error(error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_BUSY,
|
||||
"CMD ERROR: unknown error: %d",
|
||||
rsp->data_len);
|
||||
}
|
||||
|
||||
/* success */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* sends a FuCapCmd structure command to device to get the response in the same structure */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_sendcmd_ex(FuSynapticsCapeDevice *self,
|
||||
FuCapCmd *req,
|
||||
gulong delay_us,
|
||||
GError **error)
|
||||
{
|
||||
FuCapCmdHidReport report;
|
||||
guint elapsed_ms = 0;
|
||||
gboolean is_get = FALSE;
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(req != NULL, FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
/* first two bytes are report id */
|
||||
report.report_id = GINT16_TO_LE(FU_SYNAPTICS_CAPE_DEVICE_GOLEM_REPORT_ID);
|
||||
|
||||
if (!fu_memcpy_safe((guint8 *)&report.cmd,
|
||||
sizeof(report.cmd),
|
||||
0, /* dst */
|
||||
(const guint8 *)req,
|
||||
sizeof(*req),
|
||||
0, /* src */
|
||||
sizeof(*req),
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* sets data length to MAX for any GET commands */
|
||||
if (FU_SYNAPTICS_CMD_GET_FLAG & report.cmd.cmd_id) {
|
||||
is_get = TRUE;
|
||||
report.cmd.data_len = GINT16_TO_LE(FU_SYNAPTICS_CAPE_CMD_MAX_DATA_LEN);
|
||||
} else {
|
||||
report.cmd.data_len = GINT16_TO_LE(report.cmd.data_len);
|
||||
}
|
||||
|
||||
report.cmd.cmd_id = GUINT32_TO_LE(report.cmd.cmd_id);
|
||||
report.cmd.module_id = GUINT32_TO_LE(report.cmd.module_id);
|
||||
|
||||
if (!fu_synaptics_cape_device_set_report(self, &report, error)) {
|
||||
g_prefix_error(error, "failed to send: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (delay_us > 0)
|
||||
g_usleep(delay_us);
|
||||
|
||||
/* wait for the command to complete */
|
||||
for (; elapsed_ms < FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_TIMEOUT;
|
||||
elapsed_ms += FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_INTERVAL) {
|
||||
if (!fu_synaptics_cape_device_get_report(self, &report, error))
|
||||
return FALSE;
|
||||
if (report.cmd.reply)
|
||||
break;
|
||||
g_usleep(FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_INTERVAL * 1000);
|
||||
}
|
||||
|
||||
if (!report.cmd.reply) {
|
||||
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "send command time out");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* copy returned data if it is GET command */
|
||||
if (is_get) {
|
||||
req->data_len =
|
||||
(gint16)fu_common_read_uint16((guint8 *)&report.cmd, G_LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < FU_SYNAPTICS_CAPE_CMD_MAX_DATA_LEN; i++)
|
||||
req->data[i] = GUINT32_FROM_LE(report.cmd.data[i]);
|
||||
}
|
||||
|
||||
return fu_synaptics_cape_device_rc_set_error(&report.cmd, error);
|
||||
}
|
||||
|
||||
/* a simple version of sendcmd_ex without returned data */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_sendcmd(FuSynapticsCapeDevice *self,
|
||||
const guint32 module_id,
|
||||
const guint32 cmd_id,
|
||||
const guint32 *data,
|
||||
const guint32 data_len,
|
||||
const gulong delay_us,
|
||||
GError **error)
|
||||
{
|
||||
FuCapCmd cmd = {0};
|
||||
const guint32 dataszbyte = data_len * FU_SYNAPTICS_CAPE_WORD_IN_BYTES;
|
||||
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
cmd.cmd_id = cmd_id;
|
||||
cmd.module_id = module_id;
|
||||
|
||||
if (data_len != 0 && data != NULL) {
|
||||
cmd.data_len = data_len;
|
||||
if (!fu_memcpy_safe((guint8 *)cmd.data,
|
||||
sizeof(cmd.data),
|
||||
0, /* dst */
|
||||
(const guint8 *)data,
|
||||
dataszbyte,
|
||||
0, /* src */
|
||||
dataszbyte,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
return fu_synaptics_cape_device_sendcmd_ex(self, &cmd, delay_us, error);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_synaptics_cape_device_to_string(FuDevice *device, guint idt, GString *str)
|
||||
{
|
||||
FuSynapticsCapeDevice *self = FU_SYNAPTICS_CAPE_DEVICE(device);
|
||||
|
||||
g_return_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self));
|
||||
|
||||
fu_common_string_append_ku(str, idt, "active_partition", self->ActivePartition);
|
||||
}
|
||||
|
||||
/* reset device */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_reset(FuSynapticsCapeDevice *self, GError **error)
|
||||
{
|
||||
g_autoptr(GTimer) timer = g_timer_new();
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
if (!fu_synaptics_cape_device_sendcmd(self,
|
||||
FU_SYNAPTICS_CAPE_CMD_APP_ID('C', 'T', 'R', 'L'),
|
||||
FU_SYNAPTICS_CMD_MCU_SOFT_RESET,
|
||||
NULL,
|
||||
0,
|
||||
0,
|
||||
error)) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"reset command is not supported");
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_usleep(1000 * FU_SYNAPTICS_CAPE_DEVICE_USB_RESET_DELAY_MS);
|
||||
|
||||
g_debug("reset took %.2lfms", g_timer_elapsed(timer, NULL) * 1000);
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_synaptics_cape_device_get_active_partition:
|
||||
* @self: a #FuSynapticsCapeDevice
|
||||
* @error: return location for an error
|
||||
*
|
||||
* Updates active partition information to FuSynapticsCapeDevice::ActivePartition
|
||||
*
|
||||
* Returns: returns TRUE if operation is successful, otherwise, return FALSE if
|
||||
* unsuccessful.
|
||||
*
|
||||
**/
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_setup_active_partition(FuSynapticsCapeDevice *self, GError **error)
|
||||
{
|
||||
FuCapCmd cmd = {0};
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
cmd.cmd_id = FU_SYNAPTICS_CMD_FW_GET_ACTIVE_PARTITION;
|
||||
cmd.module_id = FU_SYNAPTICS_CAPE_CMD_APP_ID('C', 'T', 'R', 'L');
|
||||
|
||||
if (!fu_synaptics_cape_device_sendcmd_ex(self, &cmd, 0, error))
|
||||
return FALSE;
|
||||
|
||||
self->ActivePartition = GUINT32_FROM_LE(cmd.data[0]);
|
||||
|
||||
if (self->ActivePartition != 1 && self->ActivePartition != 2) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"partition number out of range, returned partition number is %u",
|
||||
self->ActivePartition);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* gets version number from device and saves to FU_DEVICE */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_setup_version(FuSynapticsCapeDevice *self, GError **error)
|
||||
{
|
||||
guint32 version_raw;
|
||||
FuCapCmd cmd = {0};
|
||||
g_autofree gchar *version_str = NULL;
|
||||
|
||||
cmd.cmd_id = GUINT32_TO_LE(FU_SYNAPTICS_CMD_GET_VERSION);
|
||||
cmd.module_id = FU_SYNAPTICS_CAPE_CMD_APP_ID('C', 'T', 'R', 'L');
|
||||
cmd.data_len = 4;
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
/* gets version number from device */
|
||||
fu_synaptics_cape_device_sendcmd_ex(self, &cmd, 0, error);
|
||||
|
||||
/* The version number are stored in lowest byte of a sequence of returned data */
|
||||
version_raw =
|
||||
(GUINT32_FROM_LE(cmd.data[0]) << 24) | ((GUINT32_FROM_LE(cmd.data[1]) & 0xFF) << 16) |
|
||||
((GUINT32_FROM_LE(cmd.data[2]) & 0xFF) << 8) | (GUINT32_FROM_LE(cmd.data[3]) & 0xFF);
|
||||
|
||||
version_str = fu_common_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_QUAD);
|
||||
fu_device_set_version(FU_DEVICE(self), version_str);
|
||||
fu_device_set_version_raw(FU_DEVICE(self), version_raw);
|
||||
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_setup(FuDevice *device, GError **error)
|
||||
{
|
||||
FuSynapticsCapeDevice *self = FU_SYNAPTICS_CAPE_DEVICE(device);
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
/* FuUsbDevice->setup */
|
||||
if (!FU_DEVICE_CLASS(fu_synaptics_cape_device_parent_class)->setup(device, error))
|
||||
return FALSE;
|
||||
|
||||
if (!fu_synaptics_cape_device_setup_version(self, error)) {
|
||||
g_prefix_error(error, "failed to get firmware version info: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!fu_synaptics_cape_device_setup_active_partition(self, error)) {
|
||||
g_prefix_error(error, "failed to get active partition info: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static FuFirmware *
|
||||
fu_synaptics_cape_device_prepare_firmware(FuDevice *device,
|
||||
GBytes *fw,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuSynapticsCapeDevice *self = FU_SYNAPTICS_CAPE_DEVICE(device);
|
||||
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
||||
g_autoptr(FuFirmware) firmware = fu_synaptics_cape_firmware_new();
|
||||
gsize offset = 0;
|
||||
g_autoptr(GBytes) new_fw = NULL;
|
||||
|
||||
/* the "fw" includes two firmware data for each partition, we need to divide 'fw' into
|
||||
* two equal parts.
|
||||
*/
|
||||
gsize bufsz = g_bytes_get_size(fw);
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), NULL);
|
||||
g_return_val_if_fail(usb_device != NULL, NULL);
|
||||
g_return_val_if_fail(fw != NULL, NULL);
|
||||
g_return_val_if_fail(firmware != NULL, NULL);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
||||
|
||||
if ((guint32)bufsz % 4 != 0) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"data not aligned to 32 bits");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* check file size */
|
||||
if (bufsz < FW_CAPE_HID_HEADER_SIZE * 2) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"file size is too small");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* use second partition if active partition is 1 */
|
||||
if (self->ActivePartition == 1)
|
||||
offset = bufsz / 2;
|
||||
|
||||
new_fw = g_bytes_new_from_bytes(fw, offset, bufsz / 2);
|
||||
|
||||
if (!fu_firmware_parse(firmware, new_fw, flags, error))
|
||||
return NULL;
|
||||
|
||||
/* verify if correct device */
|
||||
if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0) {
|
||||
const guint16 vid =
|
||||
fu_synaptics_cape_firmware_get_vid(FU_SYNAPTICS_CAPE_FIRMWARE(firmware));
|
||||
const guint16 pid =
|
||||
fu_synaptics_cape_firmware_get_pid(FU_SYNAPTICS_CAPE_FIRMWARE(firmware));
|
||||
if (vid != 0x0 && pid != 0x0 &&
|
||||
(g_usb_device_get_vid(usb_device) != vid ||
|
||||
g_usb_device_get_pid(usb_device) != pid)) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"USB vendor or product incorrect, "
|
||||
"got: %04X:%04X expected %04X:%04X",
|
||||
vid,
|
||||
pid,
|
||||
g_usb_device_get_vid(usb_device),
|
||||
g_usb_device_get_pid(usb_device));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* success */
|
||||
return g_steal_pointer(&firmware);
|
||||
}
|
||||
|
||||
/* sends firmware header to device */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_write_firmware_header(FuSynapticsCapeDevice *self,
|
||||
GBytes *fw,
|
||||
GError **error)
|
||||
{
|
||||
const guint8 *buf = NULL;
|
||||
gsize bufsz = 0;
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(fw != NULL, FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
buf = g_bytes_get_data(fw, &bufsz);
|
||||
|
||||
/* check size */
|
||||
if (bufsz != 20) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"firmware header is not 20 bytes");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return fu_synaptics_cape_device_sendcmd(self,
|
||||
FU_SYNAPTICS_CAPE_CMD_APP_ID('C', 'T', 'R', 'L'),
|
||||
FU_SYNAPTICS_CMD_FW_UPDATE_START,
|
||||
(const guint32 *)buf,
|
||||
bufsz / FU_SYNAPTICS_CAPE_WORD_IN_BYTES,
|
||||
0,
|
||||
error);
|
||||
}
|
||||
|
||||
/* sends firmware image to device */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_write_firmware_image(FuSynapticsCapeDevice *self,
|
||||
GBytes *fw,
|
||||
FuProgress *progress,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GPtrArray) chunks = NULL;
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(fw != NULL, FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
chunks = fu_chunk_array_new_from_bytes(fw,
|
||||
0x00,
|
||||
0x00,
|
||||
FU_SYNAPTICS_CAPE_WORD_IN_BYTES *
|
||||
FU_SYNAPTICS_CAPE_CMD_WRITE_DATAL_LEN);
|
||||
|
||||
fu_progress_set_id(progress, G_STRLOC);
|
||||
fu_progress_set_steps(progress, chunks->len);
|
||||
for (guint i = 0; i < chunks->len; i++) {
|
||||
FuChunk *chk = g_ptr_array_index(chunks, i);
|
||||
if (!fu_synaptics_cape_device_sendcmd(
|
||||
self,
|
||||
FU_SYNAPTICS_CAPE_CMD_APP_ID('C', 'T', 'R', 'L'),
|
||||
FU_SYNAPTICS_CMD_FW_UPDATE_WRITE,
|
||||
(const guint32 *)fu_chunk_get_data(chk),
|
||||
fu_chunk_get_data_sz(chk) / FU_SYNAPTICS_CAPE_WORD_IN_BYTES,
|
||||
0,
|
||||
error)) {
|
||||
g_prefix_error(error, "failed send on chk %u: ", i);
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* performs firmware update */
|
||||
static gboolean
|
||||
fu_synaptics_cape_device_write_firmware(FuDevice *device,
|
||||
FuFirmware *firmware,
|
||||
FuProgress *progress,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuSynapticsCapeDevice *self = FU_SYNAPTICS_CAPE_DEVICE(device);
|
||||
g_autoptr(GBytes) fw = NULL;
|
||||
g_autoptr(GBytes) fw_header = NULL;
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE);
|
||||
g_return_val_if_fail(firmware != NULL, FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
/* progress */
|
||||
fu_progress_set_id(progress, G_STRLOC);
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 2); /* header */
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 69);
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 0);
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 29);
|
||||
|
||||
fw_header = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_HEADER, error);
|
||||
if (fw_header == NULL)
|
||||
return FALSE;
|
||||
if (!fu_synaptics_cape_device_write_firmware_header(self, fw_header, error)) {
|
||||
g_prefix_error(error, "update header failed: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
/* perform the actual write */
|
||||
fw = fu_firmware_get_bytes(firmware, error);
|
||||
if (fw == NULL)
|
||||
return FALSE;
|
||||
if (!fu_synaptics_cape_device_write_firmware_image(self,
|
||||
fw,
|
||||
fu_progress_get_child(progress),
|
||||
error)) {
|
||||
g_prefix_error(error, "update image failed: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
/* verify the firmware image */
|
||||
if (!fu_synaptics_cape_device_sendcmd(self,
|
||||
FU_SYNAPTICS_CAPE_CMD_APP_ID('C', 'T', 'R', 'L'),
|
||||
FU_SYNAPTICS_CMD_FW_UPDATE_END,
|
||||
NULL,
|
||||
0,
|
||||
0,
|
||||
error)) {
|
||||
g_prefix_error(error, "failed to verify firmware: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
/* send software reset to run available flash code */
|
||||
if (!fu_synaptics_cape_device_reset(self, error))
|
||||
return FALSE;
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
/* success */
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_synaptics_cape_device_set_progress(FuDevice *self, FuProgress *progress)
|
||||
{
|
||||
fu_progress_set_id(progress, G_STRLOC);
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2); /* detach */
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94); /* write */
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2); /* attach */
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2); /* reload */
|
||||
}
|
||||
|
||||
static void
|
||||
fu_synaptics_cape_device_init(FuSynapticsCapeDevice *self)
|
||||
{
|
||||
fu_device_add_icon(FU_DEVICE(self), "audio-card");
|
||||
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD);
|
||||
fu_device_set_install_duration(FU_DEVICE(self), 3); /* seconds */
|
||||
fu_device_add_protocol(FU_DEVICE(self), "com.synaptics.cape");
|
||||
fu_device_retry_set_delay(FU_DEVICE(self), 100); /* ms */
|
||||
fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_synaptics_cape_device_class_init(FuSynapticsCapeDeviceClass *klass)
|
||||
{
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
||||
klass_device->to_string = fu_synaptics_cape_device_to_string;
|
||||
klass_device->setup = fu_synaptics_cape_device_setup;
|
||||
klass_device->write_firmware = fu_synaptics_cape_device_write_firmware;
|
||||
klass_device->prepare_firmware = fu_synaptics_cape_device_prepare_firmware;
|
||||
klass_device->set_progress = fu_synaptics_cape_device_set_progress;
|
||||
}
|
20
plugins/synaptics-cape/fu-synaptics-cape-device.h
Normal file
20
plugins/synaptics-cape/fu-synaptics-cape-device.h
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Synaptics Incorporated <simon.ho@synaptics.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#define FU_TYPE_SYNAPTICS_CAPE_DEVICE (fu_synaptics_cape_device_get_type())
|
||||
G_DECLARE_FINAL_TYPE(FuSynapticsCapeDevice,
|
||||
fu_synaptics_cape_device,
|
||||
FU,
|
||||
SYNAPTICS_CAPE_DEVICE,
|
||||
FuHidDevice)
|
||||
|
||||
struct _FuSynapticsCapeDeviceClass {
|
||||
FuHidDeviceClass parent_class;
|
||||
};
|
216
plugins/synaptics-cape/fu-synaptics-cape-firmware.c
Normal file
216
plugins/synaptics-cape/fu-synaptics-cape-firmware.c
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Synaptics Incorporated <simon.ho@synaptics.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "fu-synaptics-cape-firmware.h"
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
guint32 data[8];
|
||||
} FuCapeHidFwCmdUpdateWritePar;
|
||||
|
||||
struct _FuSynapticsCapeFirmware {
|
||||
FuFirmware parent_instance;
|
||||
guint16 vid;
|
||||
guint16 pid;
|
||||
};
|
||||
|
||||
/* Firmware update command structure, little endian */
|
||||
typedef struct __attribute__((packed)) {
|
||||
guint32 vid; /* USB vendor id */
|
||||
guint32 pid; /* USB product id */
|
||||
guint32 fw_update_type; /* firmware update type */
|
||||
guint32 fw_signature; /* firmware identifier */
|
||||
guint32 crc_value; /* used to detect accidental changes to fw data */
|
||||
} FuCapeHidFwCmdUpdateStartPar;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
FuCapeHidFwCmdUpdateStartPar par;
|
||||
guint16 version_w; /* firmware version is four parts number "z.y.x.w", this is last part */
|
||||
guint16 version_x; /* firmware version, third part */
|
||||
guint16 version_y; /* firmware version, second part */
|
||||
guint16 version_z; /* firmware version, first part */
|
||||
guint32 reserved3;
|
||||
} FuCapeHidFileHeader;
|
||||
|
||||
G_DEFINE_TYPE(FuSynapticsCapeFirmware, fu_synaptics_cape_firmware, FU_TYPE_FIRMWARE)
|
||||
|
||||
guint16
|
||||
fu_synaptics_cape_firmware_get_vid(FuSynapticsCapeFirmware *self)
|
||||
{
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self), 0);
|
||||
return self->vid;
|
||||
}
|
||||
|
||||
guint16
|
||||
fu_synaptics_cape_firmware_get_pid(FuSynapticsCapeFirmware *self)
|
||||
{
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self), 0);
|
||||
return self->pid;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_synaptics_cape_firmware_export(FuFirmware *firmware,
|
||||
FuFirmwareExportFlags flags,
|
||||
XbBuilderNode *bn)
|
||||
{
|
||||
FuSynapticsCapeFirmware *self = FU_SYNAPTICS_CAPE_FIRMWARE(firmware);
|
||||
fu_xmlb_builder_insert_kx(bn, "vid", self->vid);
|
||||
fu_xmlb_builder_insert_kx(bn, "pid", self->pid);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_synaptics_cape_firmware_parse_header(FuSynapticsCapeFirmware *self,
|
||||
FuFirmware *firmware,
|
||||
GBytes *fw,
|
||||
GError **error)
|
||||
{
|
||||
gsize bufsz = 0x0;
|
||||
guint16 version_w = 0;
|
||||
guint16 version_x = 0;
|
||||
guint16 version_y = 0;
|
||||
guint16 version_z = 0;
|
||||
const guint8 *buf = g_bytes_get_data(fw, &bufsz);
|
||||
g_autofree gchar *version_str = NULL;
|
||||
g_autoptr(FuFirmware) img_hdr = fu_firmware_new();
|
||||
g_autoptr(GBytes) fw_hdr = NULL;
|
||||
|
||||
g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self), FALSE);
|
||||
g_return_val_if_fail(fw != NULL, FALSE);
|
||||
g_return_val_if_fail(firmware != NULL, FALSE);
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
|
||||
/* the input fw image size should be the same as header size */
|
||||
if (bufsz < sizeof(FuCapeHidFileHeader)) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"not enough data to parse header");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!fu_common_read_uint16_safe(buf,
|
||||
bufsz,
|
||||
FW_CAPE_HID_HEADER_OFFSET_VID,
|
||||
&self->vid,
|
||||
G_LITTLE_ENDIAN,
|
||||
error))
|
||||
return FALSE;
|
||||
if (!fu_common_read_uint16_safe(buf,
|
||||
bufsz,
|
||||
FW_CAPE_HID_HEADER_OFFSET_PID,
|
||||
&self->pid,
|
||||
G_LITTLE_ENDIAN,
|
||||
error))
|
||||
return FALSE;
|
||||
if (!fu_common_read_uint16_safe(buf,
|
||||
bufsz,
|
||||
FW_CAPE_HID_HEADER_OFFSET_VER_W,
|
||||
&version_w,
|
||||
G_LITTLE_ENDIAN,
|
||||
error))
|
||||
return FALSE;
|
||||
if (!fu_common_read_uint16_safe(buf,
|
||||
bufsz,
|
||||
FW_CAPE_HID_HEADER_OFFSET_VER_X,
|
||||
&version_x,
|
||||
G_LITTLE_ENDIAN,
|
||||
error))
|
||||
return FALSE;
|
||||
if (!fu_common_read_uint16_safe(buf,
|
||||
bufsz,
|
||||
FW_CAPE_HID_HEADER_OFFSET_VER_Y,
|
||||
&version_y,
|
||||
G_LITTLE_ENDIAN,
|
||||
error))
|
||||
return FALSE;
|
||||
if (!fu_common_read_uint16_safe(buf,
|
||||
bufsz,
|
||||
FW_CAPE_HID_HEADER_OFFSET_VER_Z,
|
||||
&version_z,
|
||||
G_LITTLE_ENDIAN,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
version_str = g_strdup_printf("%u.%u.%u.%u", version_z, version_y, version_x, version_w);
|
||||
fu_firmware_set_version(FU_FIRMWARE(self), version_str);
|
||||
|
||||
fw_hdr = fu_common_bytes_new_offset(fw, 0, sizeof(FuCapeHidFwCmdUpdateStartPar), error);
|
||||
if (fw_hdr == NULL)
|
||||
return FALSE;
|
||||
|
||||
fu_firmware_set_id(img_hdr, FU_FIRMWARE_ID_HEADER);
|
||||
fu_firmware_set_bytes(img_hdr, fw_hdr);
|
||||
fu_firmware_add_image(firmware, img_hdr);
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_synaptics_cape_firmware_parse(FuFirmware *firmware,
|
||||
GBytes *fw,
|
||||
guint64 addr_start,
|
||||
guint64 addr_end,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuSynapticsCapeFirmware *self = FU_SYNAPTICS_CAPE_FIRMWARE(firmware);
|
||||
const gsize bufsz = g_bytes_get_size(fw);
|
||||
const gsize headsz = sizeof(FuCapeHidFileHeader);
|
||||
g_autoptr(GBytes) fw_header = NULL;
|
||||
g_autoptr(GBytes) fw_body = NULL;
|
||||
|
||||
/* check minimum size */
|
||||
if (bufsz < sizeof(FuCapeHidFileHeader)) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"not enough data to parse header, size ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ((guint32)bufsz % 4 != 0) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"data not aligned to 32 bits");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fw_header = g_bytes_new_from_bytes(fw, 0x0, headsz);
|
||||
if (!fu_synaptics_cape_firmware_parse_header(self, firmware, fw_header, error))
|
||||
return FALSE;
|
||||
|
||||
fw_body = g_bytes_new_from_bytes(fw, headsz, bufsz - headsz);
|
||||
fu_firmware_set_id(firmware, FU_FIRMWARE_ID_PAYLOAD);
|
||||
fu_firmware_set_bytes(firmware, fw_body);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_synaptics_cape_firmware_init(FuSynapticsCapeFirmware *self)
|
||||
{
|
||||
fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_synaptics_cape_firmware_class_init(FuSynapticsCapeFirmwareClass *klass)
|
||||
{
|
||||
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
|
||||
klass_firmware->parse = fu_synaptics_cape_firmware_parse;
|
||||
klass_firmware->export = fu_synaptics_cape_firmware_export;
|
||||
}
|
||||
|
||||
FuFirmware *
|
||||
fu_synaptics_cape_firmware_new(void)
|
||||
{
|
||||
return FU_FIRMWARE(g_object_new(FU_TYPE_SYNAPTICS_CAPE_FIRMWARE, NULL));
|
||||
}
|
38
plugins/synaptics-cape/fu-synaptics-cape-firmware.h
Normal file
38
plugins/synaptics-cape/fu-synaptics-cape-firmware.h
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Synaptics Incorporated <simon.ho@synaptics.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#define FU_TYPE_SYNAPTICS_CAPE_FIRMWARE (fu_synaptics_cape_firmware_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE(FuSynapticsCapeFirmware,
|
||||
fu_synaptics_cape_firmware,
|
||||
FU,
|
||||
SYNAPTICS_CAPE_FIRMWARE,
|
||||
FuSrecFirmware)
|
||||
|
||||
FuFirmware *
|
||||
fu_synaptics_cape_firmware_new(void);
|
||||
|
||||
guint16
|
||||
fu_synaptics_cape_firmware_get_vid(FuSynapticsCapeFirmware *self);
|
||||
|
||||
guint16
|
||||
fu_synaptics_cape_firmware_get_pid(FuSynapticsCapeFirmware *self);
|
||||
|
||||
#define FW_CAPE_HID_HEADER_OFFSET_VID 0x0
|
||||
#define FW_CAPE_HID_HEADER_OFFSET_PID 0x4
|
||||
#define FW_CAPE_HID_HEADER_OFFSET_UPDATE_TYPE 0x8
|
||||
#define FW_CAPE_HID_HEADER_OFFSET_SIGNATURE 0xc
|
||||
#define FW_CAPE_HID_HEADER_OFFSET_CRC 0x10
|
||||
#define FW_CAPE_HID_HEADER_OFFSET_VER_W 0x14
|
||||
#define FW_CAPE_HID_HEADER_OFFSET_VER_X 0x16
|
||||
#define FW_CAPE_HID_HEADER_OFFSET_VER_Y 0x18
|
||||
#define FW_CAPE_HID_HEADER_OFFSET_VER_Z 0x1A
|
||||
|
||||
#define FW_CAPE_HID_HEADER_SIZE 32 /* =sizeof(FuCapeHidFileHeader) */
|
31
plugins/synaptics-cape/meson.build
Normal file
31
plugins/synaptics-cape/meson.build
Normal file
@ -0,0 +1,31 @@
|
||||
if get_option('gusb')
|
||||
cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsCape"']
|
||||
|
||||
install_data(['synaptics-cape.quirk'],
|
||||
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
|
||||
)
|
||||
|
||||
shared_module('fu_plugin_synaptics_cape',
|
||||
fu_hash,
|
||||
sources : [
|
||||
'fu-plugin-synaptics-cape.c',
|
||||
'fu-synaptics-cape-device.c',
|
||||
'fu-synaptics-cape-firmware.c', # fuzzing
|
||||
],
|
||||
include_directories : [
|
||||
root_incdir,
|
||||
fwupd_incdir,
|
||||
fwupdplugin_incdir,
|
||||
],
|
||||
install : true,
|
||||
install_dir: plugin_dir,
|
||||
link_with : [
|
||||
fwupd,
|
||||
fwupdplugin,
|
||||
],
|
||||
c_args : cargs,
|
||||
dependencies : [
|
||||
plugin_deps,
|
||||
],
|
||||
)
|
||||
endif
|
78
plugins/synaptics-cape/synaptics-cape.quirk
Normal file
78
plugins/synaptics-cape/synaptics-cape.quirk
Normal file
@ -0,0 +1,78 @@
|
||||
# EPOS Raw Plus
|
||||
[USB\VID_1395&PID_0280]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0281]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
# RAW Teams
|
||||
[USB\VID_1395&PID_0294]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0295]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0296]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0297]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0298]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0299]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0400]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
|
||||
# EPOS Morgan-T
|
||||
[USB\VID_1395&PID_0200]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0288]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0289]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_028A]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_028B]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_028C]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_028D]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_028E]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_028F]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
# EPOS Morgan-V
|
||||
[USB\VID_1395&PID_0290]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0291]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0292]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
[USB\VID_1395&PID_0293]
|
||||
Guid = SYNAPTICS_CAPE\CX31993
|
||||
|
||||
# USB audio codec Dongle
|
||||
[SYNAPTICS_CAPE\CX31993]
|
||||
Plugin = synaptics_cape
|
||||
|
||||
# USB audio codec Hifi
|
||||
[SYNAPTICS_CAPE\CX31988]
|
||||
Plugin = synaptics_cape
|
BIN
src/fuzzing/firmware/synaptics-cape.fw
Executable file
BIN
src/fuzzing/firmware/synaptics-cape.fw
Executable file
Binary file not shown.
@ -38,6 +38,7 @@ if __name__ == "__main__":
|
||||
("srec-addr32.builder.xml", "srec-addr32.srec"),
|
||||
("srec.builder.xml", "srec.srec"),
|
||||
("synaprom.builder.xml", "synaprom.bin"),
|
||||
("synaptics-cape.builder.xml", "synaptics-cape.fw"),
|
||||
("synaptics-mst.builder.xml", "synaptics-mst.dat"),
|
||||
("wacom.builder.xml", "wacom.wac"),
|
||||
]:
|
||||
|
11
src/fuzzing/synaptics-cape.builder.xml
Normal file
11
src/fuzzing/synaptics-cape.builder.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<firmware gtype="FuSynapticsCapeFirmware">
|
||||
<flags>has-vid-pid</flags>
|
||||
<id>payload</id>
|
||||
<version>8.41.24.0</version>
|
||||
<data size="0x1c000">EFSCh...</data>
|
||||
<firmware>
|
||||
<id>header</id>
|
||||
<data size="0x14">
|
||||
</data>
|
||||
</firmware>
|
||||
</firmware>
|
Loading…
Reference in New Issue
Block a user