mirror of
https://git.proxmox.com/git/fwupd
synced 2026-03-27 11:23:57 +00:00
Add new flag can receive HID report over Interrupt In endpoint instead of control endpoint. This commit also fixes a false error within software reset function.
766 lines
24 KiB
C
766 lines
24 KiB
C
/*
|
|
* 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 */
|
|
|
|
/* defines 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_CTRL 0xb32d2300u
|
|
|
|
/* 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 */
|
|
|
|
#define FU_SYNAPTICS_CAPE_FM3_HID_INTR_IN_EP 0x83
|
|
|
|
/* 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 fwupd device structure */
|
|
struct _FuSynapticsCapeDevice {
|
|
FuHidDevice parent_instance;
|
|
guint32 active_partition; /* active partition, either 1 or 2 */
|
|
};
|
|
|
|
/**
|
|
* FU_SYNAPTICS_CAPE_DEVICE_FLAG_USE_IN_REPORT_INTERRUPT:
|
|
*
|
|
* gets HID REPORT via Interrupt instead of Control endpoint.
|
|
*/
|
|
#define FU_SYNAPTICS_CAPE_DEVICE_FLAG_USE_IN_REPORT_INTERRUPT (1 << 0)
|
|
|
|
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_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 HID report over control ep */
|
|
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_VERBOSE") != NULL)
|
|
fu_common_dump_raw(G_LOG_DOMAIN, "GetReport", (guint8 *)data, sizeof(*data));
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/* gets HID report over interrupt ep */
|
|
static gboolean
|
|
fu_synaptics_cape_device_get_report_intr(FuSynapticsCapeDevice *self,
|
|
FuCapCmdHidReport *data,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(FU_HID_DEVICE(self)));
|
|
gsize actual_len = 0;
|
|
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_usb_device_interrupt_transfer(usb_device,
|
|
FU_SYNAPTICS_CAPE_FM3_HID_INTR_IN_EP,
|
|
(guint8 *)data,
|
|
sizeof(*data),
|
|
&actual_len,
|
|
FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_TIMEOUT * 1000,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error, "failed to get report over interrupt ep: ");
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_getenv("FWUPD_SYNAPTICS_CAPE_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 = {0};
|
|
guint elapsed_ms = 0;
|
|
gboolean is_get = FALSE;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
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);
|
|
|
|
/* waits for the command to complete. There are two appraoches to get status from device:
|
|
* 1. gets IN_REPORT over interrupt endpoint. device won't reply until a command operation
|
|
* has completed. This works only on devices support interrupt endpoint.
|
|
* 2. polls GET_REPORT over control endpoint. device will return 'reply==0' before a
|
|
* command operation has completed.
|
|
*/
|
|
if (fu_device_has_private_flag(FU_DEVICE(self),
|
|
FU_SYNAPTICS_CAPE_DEVICE_FLAG_USE_IN_REPORT_INTERRUPT)) {
|
|
if (!fu_synaptics_cape_device_get_report_intr(self, &report, &error_local)) {
|
|
/* ignoring io error for software reset command */
|
|
if ((req->cmd_id == FU_SYNAPTICS_CMD_MCU_SOFT_RESET) &&
|
|
(req->module_id == FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL) &&
|
|
(g_error_matches(error_local,
|
|
G_USB_DEVICE_ERROR,
|
|
G_USB_DEVICE_ERROR_NO_DEVICE) ||
|
|
g_error_matches(error_local,
|
|
G_USB_DEVICE_ERROR,
|
|
G_USB_DEVICE_ERROR_FAILED))) {
|
|
g_debug("ignoring: %s", error_local->message);
|
|
return TRUE;
|
|
}
|
|
g_propagate_prefixed_error(error,
|
|
g_steal_pointer(&error_local),
|
|
"failed to get IN_REPORT: ");
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
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_local)) {
|
|
/* ignoring io error for software reset command */
|
|
if ((req->cmd_id == FU_SYNAPTICS_CMD_MCU_SOFT_RESET) &&
|
|
(req->module_id == FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL) &&
|
|
(g_error_matches(error_local,
|
|
G_USB_DEVICE_ERROR,
|
|
G_USB_DEVICE_ERROR_NO_DEVICE) ||
|
|
g_error_matches(error_local,
|
|
G_USB_DEVICE_ERROR,
|
|
G_USB_DEVICE_ERROR_FAILED))) {
|
|
g_debug("ignoring: %s", error_local->message);
|
|
return TRUE;
|
|
}
|
|
g_propagate_prefixed_error(error,
|
|
g_steal_pointer(&error_local),
|
|
"failed to get GET_REPORT: ");
|
|
return FALSE;
|
|
}
|
|
if (report.cmd.reply)
|
|
break;
|
|
g_usleep(FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_INTERVAL * 1000);
|
|
}
|
|
}
|
|
|
|
if (!report.cmd.reply) {
|
|
if (error != NULL)
|
|
g_prefix_error(error, "send command time out:: ");
|
|
else
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"firmware don't respond to command");
|
|
return FALSE;
|
|
}
|
|
|
|
/* copies 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, "ActivePartition", self->active_partition);
|
|
}
|
|
|
|
/* resets 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_CTRL,
|
|
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::active_partition
|
|
*
|
|
* 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_CTRL;
|
|
|
|
if (!fu_synaptics_cape_device_sendcmd_ex(self, &cmd, 0, error))
|
|
return FALSE;
|
|
|
|
self->active_partition = GUINT32_FROM_LE(cmd.data[0]);
|
|
|
|
if (self->active_partition != 1 && self->active_partition != 2) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"partition number out of range, returned partition number is %u",
|
|
self->active_partition);
|
|
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_CTRL;
|
|
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;
|
|
|
|
/* a "fw" includes two firmware data for each partition, we need to divide a '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;
|
|
}
|
|
|
|
/* checks 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;
|
|
}
|
|
|
|
/* uses second partition if active partition is 1 */
|
|
if (self->active_partition == 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);
|
|
|
|
/* checks 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_CTRL,
|
|
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_CTRL,
|
|
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);
|
|
|
|
/* performs 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_CTRL,
|
|
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);
|
|
|
|
/* sends software reset to boot into the newly flashed firmware */
|
|
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);
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_SYNAPTICS_CAPE_DEVICE_FLAG_USE_IN_REPORT_INTERRUPT,
|
|
"use-in-report-interrupt");
|
|
}
|
|
|
|
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;
|
|
}
|