mirror of
https://git.proxmox.com/git/fwupd
synced 2025-07-24 12:42:15 +00:00
1097 lines
33 KiB
C
1097 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 */
|
|
g_usleep(15000); /* 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 */
|
|
g_usleep(15000); /* 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 */
|
|
g_usleep(15000); /* 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_progress_sleep(fu_progress_get_child(progress), 1000); /* 1 s */
|
|
|
|
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_progress_sleep(progress, 3000); /* 3 s */
|
|
|
|
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_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);
|
|
}
|