fwupd/plugins/steelseries/fu-steelseries-sonic.c
Richard Hughes 090a7c8b9a Add API to wait for a device
This allows us to ignore all the delays when the device is emulated, with the
idea being to do dozens of device emulations in the CI tests.

Also, do not call fu_progress_sleep() when the device is emulated.
2023-02-23 13:04:11 -06:00

1093 lines
33 KiB
C

/*
* Copyright (C) 2022 Gaël PORTAY <gael.portay@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-steelseries-sonic.h"
#define STEELSERIES_BUFFER_FLASH_TRANSFER_SIZE 128
#define STEELSERIES_BUFFER_RAM_TRANSFER_SIZE 48
#define STEELSERIES_SONIC_WIRELESS_STATUS_OPCODE_OFFSET 0x0U
#define STEELSERIES_SONIC_WIRELESS_STATUS_VALUE_OFFSET 0x0U
#define STEELSERIES_SONIC_BATTERY_OPCODE_OFFSET 0x0U
#define STEELSERIES_SONIC_BATTERY_BAT_MODE_OFFSET 0x1U
#define STEELSERIES_SONIC_BATTERY_VALUE_OFFSET 0x0U
#define STEELSERIES_SONIC_READ_FROM_RAM_OPCODE_OFFSET 0x0U
#define STEELSERIES_SONIC_READ_FROM_RAM_OFFSET_OFFSET 0x2U
#define STEELSERIES_SONIC_READ_FROM_RAM_SIZE_OFFSET 0x4U
#define STEELSERIES_SONIC_READ_FROM_RAM_DATA_OFFSET 0x0U
#define STEELSERIES_SONIC_READ_FROM_FLASH_OPCODE_OFFSET 0x0U
#define STEELSERIES_SONIC_READ_FROM_FLASH_CHIPID_OFFSET 0x2U
#define STEELSERIES_SONIC_READ_FROM_FLASH_OFFSET_OFFSET 0x4U
#define STEELSERIES_SONIC_READ_FROM_FLASH_SIZE_OFFSET 0x8U
#define STEELSERIES_SONIC_WRITE_TO_RAM_OPCODE_OFFSET 0x0U
#define STEELSERIES_SONIC_WRITE_TO_RAM_OFFSET_OFFSET 0x2U
#define STEELSERIES_SONIC_WRITE_TO_RAM_SIZE_OFFSET 0x4U
#define STEELSERIES_SONIC_WRITE_TO_RAM_DATA_OFFSET 0x6U
#define STEELSERIES_SONIC_WRITE_TO_FLASH_OPCODE_OFFSET 0x0U
#define STEELSERIES_SONIC_WRITE_TO_FLASH_CHIPID_OFFSET 0x2U
#define STEELSERIES_SONIC_WRITE_TO_FLASH_OFFSET_OFFSET 0x4U
#define STEELSERIES_SONIC_WRITE_TO_FLASH_SIZE_OFFSET 0x8U
#define STEELSERIES_SONIC_ERASE_OPCODE_OFFSET 0x0U
#define STEELSERIES_SONIC_ERASE_CHIPID_OFFSET 0x2U
#define STEELSERIES_SONIC_RESTART_CHIPID_OFFSET 0x0U
typedef enum {
STEELSERIES_SONIC_CHIP_NORDIC = 0,
STEELSERIES_SONIC_CHIP_HOLTEK,
STEELSERIES_SONIC_CHIP_MOUSE,
/*< private >*/
STEELSERIES_SONIC_CHIP_LAST,
} SteelseriesSonicChip;
typedef enum {
STEELSERIES_SONIC_WIRELESS_STATE_OFF, /* WDS not initiated, radio is off */
STEELSERIES_SONIC_WIRELESS_STATE_IDLE, /* WDS initiated, USB receiver is transmitting beacon
* (mouse will not have this state) */
STEELSERIES_SONIC_WIRELESS_STATE_SEARCH, /* WDS initiated, mouse is trying to synchronize to
* receiver (receiver will not have this state) */
STEELSERIES_SONIC_WIRELESS_STATE_LOCKED, /* USB receiver and mouse are synchronized, but not
* necessarily connected. */
STEELSERIES_SONIC_WIRELESS_STATE_CONNECTED, /* USB receiver and mouse are connected. */
STEELSERIES_SONIC_WIRELESS_STATE_TERMINATED, /* Mouse has been disconnected from the USB
* receiver.
*/
} SteelseriesSonicWirelessStatus;
const guint16 STEELSERIES_SONIC_READ_FROM_RAM_OPCODE[] = {0x00c3U, 0x00c3U, 0x0083U};
const guint16 STEELSERIES_SONIC_READ_FROM_FLASH_OPCODE[] = {0x00c5U, 0x00c5U, 0x0085U};
const guint16 STEELSERIES_SONIC_WRITE_TO_RAM_OPCODE[] = {0x0043U, 0x0043U, 0x0003U};
const guint16 STEELSERIES_SONIC_WRITE_TO_FLASH_OPCODE[] = {0x0045U, 0x0045U, 0x0005U};
const guint16 STEELSERIES_SONIC_ERASE_OPCODE[] = {0x0048U, 0x0048U, 0x0008U};
const guint16 STEELSERIES_SONIC_RESTART_OPCODE[] = {0x0041U, 0x0041U, 0x0001U};
const guint16 STEELSERIES_SONIC_CHIP[] = {0x0002U, 0x0003U, 0x0002U};
const guint32 STEELSERIES_SONIC_FIRMWARE_SIZE[] = {0x9000U, 0x4000U, 0x12000U};
const gchar *STEELSERIES_SONIC_FIRMWARE_ID[] = {"app-nordic.bin",
"app-holtek.bin",
"mouse-app.bin"};
const guint STEELSERIES_SONIC_WRITE_PROGRESS_STEP_VALUE[][2] = {{5, 95}, {11, 89}, {3, 97}};
struct _FuSteelseriesSonic {
FuSteelseriesDevice parent_instance;
};
G_DEFINE_TYPE(FuSteelseriesSonic, fu_steelseries_sonic, FU_TYPE_STEELSERIES_DEVICE)
static gboolean
fu_steelseries_sonic_wireless_status(FuDevice *device,
SteelseriesSonicWirelessStatus *status,
GError **error)
{
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
const guint16 opcode = 0xE8U; /* USB receiver */
guint8 value;
if (!fu_memwrite_uint8_safe(data,
sizeof(data),
STEELSERIES_SONIC_WIRELESS_STATUS_OPCODE_OFFSET,
opcode,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "WirelessStatus", data, sizeof(data));
if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device),
data,
sizeof(data),
TRUE,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "WirelessStatus", data, sizeof(data));
if (!fu_memread_uint8_safe(data,
sizeof(data),
STEELSERIES_SONIC_WIRELESS_STATUS_VALUE_OFFSET,
&value,
error))
return FALSE;
*status = value;
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_battery_state(FuDevice *device, guint16 *value, GError **error)
{
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
const guint16 opcode = 0xAAU;
const guint8 bat_mode = 0x01U; /* percentage */
if (!fu_memwrite_uint8_safe(data,
sizeof(data),
STEELSERIES_SONIC_BATTERY_OPCODE_OFFSET,
opcode,
error))
return FALSE;
if (!fu_memwrite_uint8_safe(data,
sizeof(data),
STEELSERIES_SONIC_BATTERY_BAT_MODE_OFFSET,
bat_mode,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "BatteryState", data, sizeof(data));
if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device),
data,
sizeof(data),
TRUE,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "BatteryState", data, sizeof(data));
if (!fu_memread_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_BATTERY_VALUE_OFFSET,
value,
G_LITTLE_ENDIAN,
error))
return FALSE;
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_read_from_ram(FuDevice *device,
SteelseriesSonicChip chip,
guint32 address,
guint8 *buf,
guint16 bufsz,
FuProgress *progress,
GError **error)
{
const guint16 opcode = STEELSERIES_SONIC_READ_FROM_RAM_OPCODE[chip];
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
g_autoptr(GPtrArray) chunks = NULL;
chunks =
fu_chunk_array_mutable_new(buf, bufsz, 0x0, 0x0, STEELSERIES_BUFFER_RAM_TRANSFER_SIZE);
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);
const guint16 offset = fu_chunk_get_address(chk);
const guint16 size = fu_chunk_get_data_sz(chk);
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_READ_FROM_RAM_OPCODE_OFFSET,
opcode,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_READ_FROM_RAM_OFFSET_OFFSET,
offset,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_READ_FROM_RAM_SIZE_OFFSET,
size,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device),
data,
sizeof(data),
TRUE,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "ReadFromRAM", data, sizeof(data));
if (!fu_memcpy_safe(fu_chunk_get_data_out(chk),
fu_chunk_get_data_sz(chk),
0x0,
data,
sizeof(data),
STEELSERIES_SONIC_READ_FROM_RAM_DATA_OFFSET,
fu_chunk_get_data_sz(chk),
error))
return FALSE;
fu_progress_step_done(progress);
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_read_from_flash(FuDevice *device,
SteelseriesSonicChip chip,
guint32 address,
guint8 *buf,
guint32 bufsz,
FuProgress *progress,
GError **error)
{
const guint16 opcode = STEELSERIES_SONIC_READ_FROM_FLASH_OPCODE[chip];
const guint16 chipid = STEELSERIES_SONIC_CHIP[chip];
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
g_autoptr(GPtrArray) chunks = NULL;
chunks = fu_chunk_array_mutable_new(buf,
bufsz,
address,
0x0,
STEELSERIES_BUFFER_FLASH_TRANSFER_SIZE);
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);
const guint32 offset = fu_chunk_get_address(chk);
const guint16 size = fu_chunk_get_data_sz(chk);
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_READ_FROM_FLASH_OPCODE_OFFSET,
opcode,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_READ_FROM_FLASH_CHIPID_OFFSET,
chipid,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint32_safe(data,
sizeof(data),
STEELSERIES_SONIC_READ_FROM_FLASH_OFFSET_OFFSET,
offset,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_READ_FROM_FLASH_SIZE_OFFSET,
size,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device),
data,
sizeof(data),
FALSE,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "ReadFromFlash", data, sizeof(data));
/* timeout to give some time to read from flash to ram */
fu_device_sleep(device, 15); /* ms */
if (!fu_steelseries_sonic_read_from_ram(device,
chip,
offset,
fu_chunk_get_data_out(chk),
size,
fu_progress_get_child(progress),
error))
return FALSE;
fu_progress_step_done(progress);
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_write_to_ram(FuDevice *device,
SteelseriesSonicChip chip,
guint16 address,
const guint8 *buf,
guint16 bufsz,
FuProgress *progress,
GError **error)
{
const guint16 opcode = STEELSERIES_SONIC_WRITE_TO_RAM_OPCODE[chip];
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
g_autoptr(GPtrArray) chunks = NULL;
chunks = fu_chunk_array_new(buf, bufsz, 0x0, 0x0, STEELSERIES_BUFFER_RAM_TRANSFER_SIZE);
fu_progress_set_id(progress, G_STRLOC);
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE);
fu_progress_set_steps(progress, chunks->len);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index(chunks, i);
const guint16 offset = fu_chunk_get_address(chk);
const guint16 size = fu_chunk_get_data_sz(chk);
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_WRITE_TO_RAM_OPCODE_OFFSET,
opcode,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_WRITE_TO_RAM_OFFSET_OFFSET,
offset,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_WRITE_TO_RAM_SIZE_OFFSET,
size,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memcpy_safe(data,
sizeof(data),
STEELSERIES_SONIC_WRITE_TO_RAM_DATA_OFFSET,
fu_chunk_get_data(chk),
fu_chunk_get_data_sz(chk),
0x0,
fu_chunk_get_data_sz(chk),
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "WriteToRAM", data, sizeof(data));
if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device),
data,
sizeof(data),
FALSE,
error))
return FALSE;
/* timeout to give some time to write to ram */
fu_device_sleep(device, 15); /* ms */
fu_progress_step_done(progress);
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_write_to_flash(FuDevice *device,
SteelseriesSonicChip chip,
guint32 address,
const guint8 *buf,
guint32 bufsz,
FuProgress *progress,
GError **error)
{
const guint16 opcode = STEELSERIES_SONIC_WRITE_TO_FLASH_OPCODE[chip];
const guint16 chipid = STEELSERIES_SONIC_CHIP[chip];
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
g_autoptr(GPtrArray) chunks = NULL;
chunks = fu_chunk_array_new(buf, bufsz, 0x0, 0x0, STEELSERIES_BUFFER_FLASH_TRANSFER_SIZE);
fu_progress_set_id(progress, G_STRLOC);
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE);
fu_progress_set_steps(progress, chunks->len);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index(chunks, i);
const guint32 offset = fu_chunk_get_address(chk);
const guint16 size = fu_chunk_get_data_sz(chk);
if (!fu_steelseries_sonic_write_to_ram(device,
chip,
offset,
fu_chunk_get_data(chk),
size,
fu_progress_get_child(progress),
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_WRITE_TO_FLASH_OPCODE_OFFSET,
opcode,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_WRITE_TO_FLASH_CHIPID_OFFSET,
chipid,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint32_safe(data,
sizeof(data),
STEELSERIES_SONIC_WRITE_TO_FLASH_OFFSET_OFFSET,
offset,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_WRITE_TO_FLASH_SIZE_OFFSET,
size,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "WriteToFlash", data, sizeof(data));
if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device),
data,
sizeof(data),
FALSE,
error))
return FALSE;
/* timeout to give some time to write from ram to flash */
fu_device_sleep(device, 15); /* ms */
fu_progress_step_done(progress);
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_erase(FuDevice *device,
SteelseriesSonicChip chip,
FuProgress *progress,
GError **error)
{
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
const guint16 opcode = STEELSERIES_SONIC_ERASE_OPCODE[chip];
const guint16 chipid = STEELSERIES_SONIC_CHIP[chip];
fu_progress_set_id(progress, G_STRLOC);
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_ERASE);
fu_progress_set_steps(progress, 1);
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_ERASE_OPCODE_OFFSET,
opcode,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_ERASE_CHIPID_OFFSET,
chipid,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "Erase", data, sizeof(data));
if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device),
data,
sizeof(data),
FALSE,
error))
return FALSE;
/* timeout to give some time to erase flash */
fu_device_sleep_full(device, 1000, fu_progress_get_child(progress)); /* ms */
fu_progress_step_done(progress);
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_restart(FuDevice *device,
SteelseriesSonicChip chip,
FuProgress *progress,
GError **error)
{
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
const guint16 opcode = STEELSERIES_SONIC_RESTART_OPCODE[chip];
fu_progress_set_id(progress, G_STRLOC);
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_RESTART);
fu_progress_set_steps(progress, 1);
if (!fu_memwrite_uint16_safe(data,
sizeof(data),
STEELSERIES_SONIC_RESTART_CHIPID_OFFSET,
opcode,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "Restart", data, sizeof(data));
if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device),
data,
sizeof(data),
FALSE,
error))
return FALSE;
/* timeout to give some time to restart chip */
fu_device_sleep_full(device, 3000, progress); /* ms */
fu_progress_step_done(progress);
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_wait_for_connect_cb(FuDevice *device, gpointer user_data, GError **error)
{
SteelseriesSonicWirelessStatus *wl_status = (SteelseriesSonicWirelessStatus *)user_data;
if (!fu_steelseries_sonic_wireless_status(device, wl_status, error)) {
g_prefix_error(error, "failed to get wireless status: ");
return FALSE;
}
g_debug("WirelessStatus: %u", *wl_status);
if (*wl_status != STEELSERIES_SONIC_WIRELESS_STATE_CONNECTED) {
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device is unreachable");
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_wait_for_connect(FuDevice *device, guint delay, GError **error)
{
SteelseriesSonicWirelessStatus wl_status;
g_autoptr(FwupdRequest) request = NULL;
g_autoptr(GError) error_local = NULL;
g_autofree gchar *msg = NULL;
if (!fu_steelseries_sonic_wireless_status(device, &wl_status, error)) {
g_prefix_error(error, "failed to get wireless status: ");
return FALSE;
}
g_debug("WirelessStatus: %u", wl_status);
if (wl_status == STEELSERIES_SONIC_WIRELESS_STATE_CONNECTED) {
/* success */
return TRUE;
}
/* the user has to do something */
msg = g_strdup_printf("%s needs to be connected to start the update. "
"Please put the switch button underneath to 2.4G, or "
"click on any button to reconnect it.",
fu_device_get_name(device));
request = fwupd_request_new();
fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE);
fwupd_request_set_id(request, FWUPD_REQUEST_ID_PRESS_UNLOCK);
fwupd_request_set_message(request, msg);
fu_device_emit_request(device, request);
if (!fu_device_retry_full(device,
fu_steelseries_sonic_wait_for_connect_cb,
delay / 1000,
1000,
&wl_status,
&error_local))
g_debug("%s", error_local->message);
if (wl_status != STEELSERIES_SONIC_WIRELESS_STATE_CONNECTED) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION, msg);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_attach(FuDevice *device, FuProgress *progress, GError **error)
{
SteelseriesSonicChip chip;
g_autoptr(FwupdRequest) request = NULL;
g_autofree gchar *msg = NULL;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50, "mouse");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50, "holtek");
/* mouse */
chip = STEELSERIES_SONIC_CHIP_MOUSE;
if (!fu_steelseries_sonic_restart(device, chip, fu_progress_get_child(progress), error)) {
g_prefix_error(error, "failed to restart chip %u: ", chip);
return FALSE;
}
fu_progress_step_done(progress);
/* USB receiver (nordic, holtek; same command) */
chip = STEELSERIES_SONIC_CHIP_HOLTEK;
if (!fu_steelseries_sonic_restart(device, chip, fu_progress_get_child(progress), error)) {
g_prefix_error(error, "failed to restart chip %u: ", chip);
return FALSE;
}
fu_progress_step_done(progress);
/* the user has to do something */
msg = g_strdup_printf("%s needs to be manually restarted to complete the update. "
"Please unplug the 2.4G USB Wireless adapter and then re-plug it.",
fu_device_get_name(device));
request = fwupd_request_new();
fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE);
fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG);
fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE);
fwupd_request_set_message(request, msg);
fu_device_emit_request(device, request);
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_prepare(FuDevice *device,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
guint16 bat_state;
if (!fu_steelseries_sonic_wait_for_connect(device,
fu_device_get_remove_delay(device),
error))
return FALSE;
if (!fu_steelseries_sonic_battery_state(device, &bat_state, error)) {
g_prefix_error(error, "failed to get battery state: ");
return FALSE;
}
g_debug("BatteryState: %u%%", bat_state);
fu_device_set_battery_level(device, bat_state);
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_write_chip(FuDevice *device,
SteelseriesSonicChip chip,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
const guint8 *buf;
gsize bufsz;
g_autoptr(FuFirmware) fw = NULL;
g_autoptr(GBytes) blob = NULL;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress,
FWUPD_STATUS_DEVICE_ERASE,
STEELSERIES_SONIC_WRITE_PROGRESS_STEP_VALUE[chip][0],
NULL);
fu_progress_add_step(progress,
FWUPD_STATUS_DEVICE_WRITE,
STEELSERIES_SONIC_WRITE_PROGRESS_STEP_VALUE[chip][1],
NULL);
fw = fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error);
if (fw == NULL)
return FALSE;
blob = fu_firmware_get_bytes(fw, error);
if (blob == NULL)
return FALSE;
buf = fu_bytes_get_data_safe(blob, &bufsz, error);
if (buf == NULL)
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, STEELSERIES_SONIC_FIRMWARE_ID[chip], buf, bufsz);
if (!fu_steelseries_sonic_erase(device, chip, fu_progress_get_child(progress), error)) {
g_prefix_error(error, "failed to erase chip %u: ", chip);
return FALSE;
}
fu_progress_step_done(progress);
if (!fu_steelseries_sonic_write_to_flash(device,
chip,
0x0,
buf,
bufsz,
fu_progress_get_child(progress),
error)) {
g_prefix_error(error, "failed to write to flash chip %u: ", chip);
return FALSE;
}
fu_progress_step_done(progress);
/* success */
return TRUE;
}
static FuFirmware *
fu_steelseries_sonic_read_chip(FuDevice *device,
SteelseriesSonicChip chip,
FuProgress *progress,
GError **error)
{
guint32 bufsz;
g_autoptr(GBytes) blob = NULL;
g_autofree guint8 *buf = NULL;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_set_steps(progress, 1);
bufsz = STEELSERIES_SONIC_FIRMWARE_SIZE[chip];
buf = g_malloc0(bufsz);
if (!fu_steelseries_sonic_read_from_flash(device,
chip,
0x0,
buf,
bufsz,
fu_progress_get_child(progress),
error)) {
g_prefix_error(error, "failed to read from flash chip %u: ", chip);
return NULL;
}
fu_progress_step_done(progress);
blob = g_bytes_new_take(g_steal_pointer(&buf), bufsz);
return fu_firmware_new_from_bytes(blob);
}
static gboolean
fu_steelseries_sonic_verify_chip(FuDevice *device,
SteelseriesSonicChip chip,
FuFirmware *firmware,
FuProgress *progress,
GError **error)
{
g_autoptr(FuFirmware) fw_tmp = NULL;
g_autoptr(FuFirmware) fw = NULL;
g_autoptr(GBytes) blob_tmp = NULL;
g_autoptr(GBytes) blob = NULL;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 100, NULL);
fw = fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error);
if (fw == NULL)
return FALSE;
blob = fu_firmware_get_bytes(fw, error);
if (blob == NULL)
return FALSE;
fw_tmp =
fu_steelseries_sonic_read_chip(device, chip, fu_progress_get_child(progress), error);
if (fw_tmp == NULL) {
g_prefix_error(error, "failed to read from flash chip %u: ", chip);
return FALSE;
}
blob_tmp = fu_firmware_get_bytes(fw_tmp, error);
if (blob_tmp == NULL)
return FALSE;
if (!fu_bytes_compare(blob_tmp, blob, error)) {
if (g_getenv("FWUPD_STEELSERIES_SONIC_VERBOSE") != NULL) {
fu_dump_raw(G_LOG_DOMAIN,
"Verify",
g_bytes_get_data(blob_tmp, NULL),
g_bytes_get_size(blob_tmp));
}
return FALSE;
}
fu_progress_step_done(progress);
/* success */
return TRUE;
}
static FuFirmware *
fu_steelseries_sonic_read_firmware(FuDevice *device, FuProgress *progress, GError **error)
{
SteelseriesSonicChip chip;
g_autoptr(FuFirmware) firmware = fu_archive_firmware_new();
g_autoptr(FuFirmware) firmware_nordic = NULL;
g_autoptr(FuFirmware) firmware_holtek = NULL;
g_autoptr(FuFirmware) firmware_mouse = NULL;
if (!fu_steelseries_sonic_wait_for_connect(device,
fu_device_get_remove_delay(device),
error))
return NULL;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 18, "nordic");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 8, "holtek");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 73, "mouse");
fu_archive_firmware_set_format(FU_ARCHIVE_FIRMWARE(firmware), FU_ARCHIVE_FORMAT_ZIP);
fu_archive_firmware_set_compression(FU_ARCHIVE_FIRMWARE(firmware),
FU_ARCHIVE_COMPRESSION_NONE);
/* nordic */
chip = STEELSERIES_SONIC_CHIP_NORDIC;
firmware_nordic =
fu_steelseries_sonic_read_chip(device, chip, fu_progress_get_child(progress), error);
if (firmware_nordic == NULL)
return NULL;
fu_firmware_set_id(firmware_nordic, STEELSERIES_SONIC_FIRMWARE_ID[chip]);
fu_firmware_add_image(firmware, firmware_nordic);
fu_progress_step_done(progress);
/* holtek */
chip = STEELSERIES_SONIC_CHIP_HOLTEK;
firmware_holtek =
fu_steelseries_sonic_read_chip(device, chip, fu_progress_get_child(progress), error);
if (firmware_holtek == NULL)
return NULL;
fu_firmware_set_id(firmware_holtek, STEELSERIES_SONIC_FIRMWARE_ID[chip]);
fu_firmware_add_image(firmware, firmware_holtek);
fu_progress_step_done(progress);
/* mouse */
chip = STEELSERIES_SONIC_CHIP_MOUSE;
firmware_mouse =
fu_steelseries_sonic_read_chip(device, chip, fu_progress_get_child(progress), error);
if (firmware_mouse == NULL)
return NULL;
fu_firmware_set_id(firmware_mouse, STEELSERIES_SONIC_FIRMWARE_ID[chip]);
fu_firmware_add_image(firmware, firmware_mouse);
fu_progress_step_done(progress);
/* success */
fu_firmware_set_id(firmware, FU_FIRMWARE_ID_PAYLOAD);
return g_steal_pointer(&firmware);
}
static gboolean
fu_steelseries_sonic_write_firmware(FuDevice *device,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
SteelseriesSonicChip chip;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 34, "device-write-mouse");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 30, "device-verify-mouse");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 17, "device-write-nordic");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 7, "device-verify-nordic");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 8, "device-write-holtek");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 3, "device-verify-holtek");
/* mouse */
chip = STEELSERIES_SONIC_CHIP_MOUSE;
if (!fu_steelseries_sonic_write_chip(device,
chip,
firmware,
fu_progress_get_child(progress),
flags,
error))
return FALSE;
fu_progress_step_done(progress);
if (!fu_steelseries_sonic_verify_chip(device,
chip,
firmware,
fu_progress_get_child(progress),
error))
return FALSE;
fu_progress_step_done(progress);
/* nordic */
chip = STEELSERIES_SONIC_CHIP_NORDIC;
if (!fu_steelseries_sonic_write_chip(device,
chip,
firmware,
fu_progress_get_child(progress),
flags,
error))
return FALSE;
fu_progress_step_done(progress);
if (!fu_steelseries_sonic_verify_chip(device,
chip,
firmware,
fu_progress_get_child(progress),
error))
return FALSE;
fu_progress_step_done(progress);
/* holtek */
chip = STEELSERIES_SONIC_CHIP_HOLTEK;
if (!fu_steelseries_sonic_write_chip(device,
STEELSERIES_SONIC_CHIP_HOLTEK,
firmware,
fu_progress_get_child(progress),
flags,
error))
return FALSE;
fu_progress_step_done(progress);
if (!fu_steelseries_sonic_verify_chip(device,
chip,
firmware,
fu_progress_get_child(progress),
error))
return FALSE;
fu_progress_step_done(progress);
/* success */
return TRUE;
}
static gboolean
fu_steelseries_sonic_parse_firmware(FuFirmware *firmware, FwupdInstallFlags flags, GError **error)
{
guint32 checksum_tmp;
guint32 checksum;
g_autoptr(GBytes) blob = NULL;
blob = fu_firmware_get_bytes(firmware, error);
if (blob == NULL)
return FALSE;
if (!fu_memread_uint32_safe(g_bytes_get_data(blob, NULL),
g_bytes_get_size(blob),
g_bytes_get_size(blob) - sizeof(checksum),
&checksum,
G_LITTLE_ENDIAN,
error))
return FALSE;
checksum_tmp =
fu_crc32(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob) - sizeof(checksum_tmp));
checksum_tmp = ~checksum_tmp;
if (checksum_tmp != checksum) {
if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"checksum mismatch for %s, got 0x%08x, expected 0x%08x",
fu_firmware_get_id(firmware),
checksum_tmp,
checksum);
return FALSE;
}
g_debug("ignoring checksum mismatch, got 0x%08x, expected 0x%08x",
checksum_tmp,
checksum);
}
fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_HAS_CHECKSUM);
/* success */
return TRUE;
}
static FuFirmware *
fu_steelseries_sonic_prepare_firmware(FuDevice *device,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
SteelseriesSonicChip chip;
g_autoptr(FuFirmware) firmware_nordic = NULL;
g_autoptr(FuFirmware) firmware_holtek = NULL;
g_autoptr(FuFirmware) firmware_mouse = NULL;
g_autoptr(FuFirmware) firmware = NULL;
firmware = fu_archive_firmware_new();
if (!fu_firmware_parse(firmware, fw, flags, error))
return NULL;
/* mouse */
chip = STEELSERIES_SONIC_CHIP_MOUSE;
firmware_mouse =
fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error);
if (firmware_mouse == NULL)
return NULL;
if (!fu_steelseries_sonic_parse_firmware(firmware_mouse, flags, error))
return NULL;
/* nordic */
chip = STEELSERIES_SONIC_CHIP_NORDIC;
firmware_nordic =
fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error);
if (firmware_nordic == NULL)
return NULL;
if (!fu_steelseries_sonic_parse_firmware(firmware_nordic, flags, error))
return NULL;
/* holtek */
chip = STEELSERIES_SONIC_CHIP_HOLTEK;
firmware_holtek =
fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error);
if (firmware_holtek == NULL)
return NULL;
if (!fu_steelseries_sonic_parse_firmware(firmware_holtek, flags, error))
return NULL;
/* success */
return g_steal_pointer(&firmware);
}
static void
fu_steelseries_sonic_set_progress(FuDevice *self, FuProgress *progress)
{
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 92, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "attach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 3, "reload");
}
static void
fu_steelseries_sonic_class_init(FuSteelseriesSonicClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->attach = fu_steelseries_sonic_attach;
klass_device->prepare = fu_steelseries_sonic_prepare;
klass_device->read_firmware = fu_steelseries_sonic_read_firmware;
klass_device->write_firmware = fu_steelseries_sonic_write_firmware;
klass_device->prepare_firmware = fu_steelseries_sonic_prepare_firmware;
klass_device->set_progress = fu_steelseries_sonic_set_progress;
}
static void
fu_steelseries_sonic_init(FuSteelseriesSonic *self)
{
fu_steelseries_device_set_iface_idx_offset(FU_STEELSERIES_DEVICE(self), -1);
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_BCD);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE);
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID);
fu_device_add_protocol(FU_DEVICE(self), "com.steelseries.sonic");
fu_device_set_install_duration(FU_DEVICE(self), 120); /* 2 min */
fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); /* 40 s */
fu_device_set_battery_level(FU_DEVICE(self), FWUPD_BATTERY_LEVEL_INVALID);
fu_device_set_battery_threshold(FU_DEVICE(self), 20);
}