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:
Simon Ho 2021-09-15 03:42:07 +08:00 committed by GitHub
parent 6817648c6b
commit 5e67108ab8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1135 additions and 1 deletions

View File

@ -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*"),

View File

@ -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

View File

@ -54,6 +54,7 @@ subdir('rts54hub')
subdir('solokey')
subdir('steelseries')
subdir('superio')
subdir('synaptics-cape')
subdir('synaptics-cxaudio')
subdir('synaptics-mst')
subdir('synaptics-prometheus')

View 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`.

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

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

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

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

View 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) */

View 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

View 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

Binary file not shown.

View File

@ -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"),
]:

View 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>