steelseries: Add support for Aerox 3 Wireless

This commit is contained in:
Gaël PORTAY 2022-05-06 11:55:37 +02:00 committed by Gaël PORTAY
parent 2db224627c
commit 10f6f249b0
10 changed files with 1100 additions and 0 deletions

View File

@ -7,6 +7,7 @@ This plugin allows to update firmware on SteelSeries gamepads:
* Stratus Duo * Stratus Duo
* Stratus Duo USB wireless adapter * Stratus Duo USB wireless adapter
* Stratus+ * Stratus+
* Aerox 3 Wireless
* Rival 3 Wireless * Rival 3 Wireless
SteelSeries Rival 100 gaming mice support is limited by getting the correct SteelSeries Rival 100 gaming mice support is limited by getting the correct
@ -15,6 +16,7 @@ available from the vendor.
This plugin supports the following protocol IDs: This plugin supports the following protocol IDs:
* com.steelseries.fizz
* com.steelseries.gamepad * com.steelseries.gamepad
* com.steelseries.sonic * com.steelseries.sonic
@ -40,9 +42,15 @@ The device is not upgradable and thus requires no vendor ID set.
### Wireless Mice ### Wireless Mice
### Rival 3 Wireless
The mouse switch button underneath must be set to 2.4G, and its 2.4G USB The mouse switch button underneath must be set to 2.4G, and its 2.4G USB
Wireless adapter must be connected to host. Wireless adapter must be connected to host.
### Aerox 3 Wireless
The mouse must be connected to host via the USB-A to USB-C cable.
## Vendor ID Security ## Vendor ID Security
The vendor ID is set from the USB vendor, in this instance set to `USB:0x1038` The vendor ID is set from the USB vendor, in this instance set to `USB:0x1038`

View File

@ -9,6 +9,7 @@
#include <fwupdplugin.h> #include <fwupdplugin.h>
#include "fu-steelseries-device.h" #include "fu-steelseries-device.h"
#include "fu-steelseries-fizz.h"
#include "fu-steelseries-gamepad.h" #include "fu-steelseries-gamepad.h"
#include "fu-steelseries-sonic.h" #include "fu-steelseries-sonic.h"
@ -16,6 +17,7 @@ static void
fu_plugin_steelseries_init(FuPlugin *plugin) fu_plugin_steelseries_init(FuPlugin *plugin)
{ {
fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_DEVICE);
fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_FIZZ);
fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_GAMEPAD); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_GAMEPAD);
fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_SONIC); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_SONIC);
} }

View File

@ -10,6 +10,10 @@
FuSteelseriesDeviceKind FuSteelseriesDeviceKind
fu_steelseries_device_type_from_string(const gchar *name) fu_steelseries_device_type_from_string(const gchar *name)
{ {
if (g_strcmp0(name, "fizz") == 0)
return FU_STEELSERIES_DEVICE_FIZZ;
if (g_strcmp0(name, "fizz-dongle") == 0)
return FU_STEELSERIES_DEVICE_FIZZ_DONGLE;
if (g_strcmp0(name, "gamepad") == 0) if (g_strcmp0(name, "gamepad") == 0)
return FU_STEELSERIES_DEVICE_GAMEPAD; return FU_STEELSERIES_DEVICE_GAMEPAD;
if (g_strcmp0(name, "gamepad-dongle") == 0) if (g_strcmp0(name, "gamepad-dongle") == 0)
@ -22,6 +26,10 @@ fu_steelseries_device_type_from_string(const gchar *name)
const gchar * const gchar *
fu_steelseries_device_type_to_string(FuSteelseriesDeviceKind type) fu_steelseries_device_type_to_string(FuSteelseriesDeviceKind type)
{ {
if (type == FU_STEELSERIES_DEVICE_FIZZ)
return "fizz";
if (type == FU_STEELSERIES_DEVICE_FIZZ_DONGLE)
return "fizz-dongle";
if (type == FU_STEELSERIES_DEVICE_GAMEPAD) if (type == FU_STEELSERIES_DEVICE_GAMEPAD)
return "gamepad"; return "gamepad";
if (type == FU_STEELSERIES_DEVICE_GAMEPAD_DONGLE) if (type == FU_STEELSERIES_DEVICE_GAMEPAD_DONGLE)

View File

@ -14,6 +14,8 @@ typedef enum {
FU_STEELSERIES_DEVICE_GAMEPAD, FU_STEELSERIES_DEVICE_GAMEPAD,
FU_STEELSERIES_DEVICE_GAMEPAD_DONGLE, FU_STEELSERIES_DEVICE_GAMEPAD_DONGLE,
FU_STEELSERIES_DEVICE_SONIC, FU_STEELSERIES_DEVICE_SONIC,
FU_STEELSERIES_DEVICE_FIZZ,
FU_STEELSERIES_DEVICE_FIZZ_DONGLE,
} FuSteelseriesDeviceKind; } FuSteelseriesDeviceKind;
FuSteelseriesDeviceKind FuSteelseriesDeviceKind

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2022 Gaël PORTAY <gael.portay@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-steelseries-common.h"
#include "fu-steelseries-firmware.h"
struct _FuSteelseriesFirmware {
FuFirmwareClass parent_instance;
guint32 checksum;
};
G_DEFINE_TYPE(FuSteelseriesFirmware, fu_steelseries_firmware, FU_TYPE_FIRMWARE)
static gboolean
fu_steelseries_firmware_parse(FuFirmware *firmware,
GBytes *fw,
guint64 addr_start,
guint64 addr_end,
FwupdInstallFlags flags,
GError **error)
{
FuSteelseriesFirmware *self = FU_STEELSERIES_FIRMWARE(firmware);
guint32 checksum_tmp;
guint32 checksum;
if (!fu_common_read_uint32_safe(g_bytes_get_data(fw, NULL),
g_bytes_get_size(fw),
g_bytes_get_size(fw) - sizeof(checksum),
&checksum,
G_LITTLE_ENDIAN,
error))
return FALSE;
checksum_tmp = fu_common_crc32(g_bytes_get_data(fw, NULL),
g_bytes_get_size(fw) - sizeof(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, got 0x%08x, expected 0x%08x",
checksum_tmp,
checksum);
return FALSE;
}
g_debug("ignoring checksum mismatch, got 0x%08x, expected 0x%08x",
checksum_tmp,
checksum);
}
self->checksum = checksum;
fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
fu_firmware_set_bytes(firmware, fw);
/* success */
return TRUE;
}
static void
fu_steelseries_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
{
FuSteelseriesFirmware *self = FU_STEELSERIES_FIRMWARE(firmware);
fu_xmlb_builder_insert_kx(bn, "checksum", self->checksum);
}
static void
fu_steelseries_firmware_init(FuSteelseriesFirmware *self)
{
}
static void
fu_steelseries_firmware_class_init(FuSteelseriesFirmwareClass *klass)
{
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
klass_firmware->parse = fu_steelseries_firmware_parse;
klass_firmware->export = fu_steelseries_firmware_export;
}
FuFirmware *
fu_steelseries_firmware_new(void)
{
return FU_FIRMWARE(g_object_new(FU_TYPE_STEELSERIES_FIRMWARE, NULL));
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2022 Gaël PORTAY <gael.portay@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#define FU_TYPE_STEELSERIES_FIRMWARE (fu_steelseries_firmware_get_type())
G_DECLARE_FINAL_TYPE(FuSteelseriesFirmware,
fu_steelseries_firmware,
FU,
STEELSERIES_FIRMWARE,
FuFirmware)
FuFirmware *
fu_steelseries_firmware_new(void);

View File

@ -0,0 +1,935 @@
/*
* Copyright (C) 2022 Gaël PORTAY <gael.portay@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-steelseries-common.h"
#include "fu-steelseries-firmware.h"
#include "fu-steelseries-fizz.h"
#define STEELSERIES_TRANSACTION_TIMEOUT 5000
#define STEELSERIES_BUFFER_CONTROL_SIZE 64
#define STEELSERIES_BUFFER_TRANSFER_SIZE 52
#define STEELSERIES_FIZZ_FILESYSTEM_DONGLE 0x01U
#define STEELSERIES_FIZZ_FILESYSTEM_MOUSE 0x02U
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_MAIN_BOOT_ID 0x01U
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_FSDATA_FILE_ID 0x02U
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_FACTORY_SETTINGS_ID 0x03U
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_MAIN_APP_ID 0x04U
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_BACKUP_APP_ID 0x05U
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_PROFILES_MOUSE_ID 0x06U
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_PROFILES_LIGHTING_ID 0x0fU
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_PROFILES_DEVICE_ID 0x10U
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_PROFILES_RESERVED_ID 0x11U
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_RECOVERY_ID 0x0dU
#define STEELSERIES_FIZZ_DONGLE_FILESYSTEM_FREE_SPACE_ID 0xf1U
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_SOFT_DEVICE_ID 0x00U
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_PROFILES_MOUSE_ID 0x06U
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_MAIN_APP_ID 0x07U
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID 0x08U
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_MSB_DATA_ID 0x09U
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_FACTORY_SETTINGS_ID 0x0aU
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_FSDATA_FILE_ID 0x0bU
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_MAIN_BOOT_ID 0x0cU
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_RECOVERY_ID 0x0eU
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_PROFILES_LIGHTING_ID 0x0fU
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_PROFILES_DEVICE_ID 0x10U
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_FDS_PAGES_ID 0x12U
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_PROFILES_BLUETOOTH_ID 0x13U
#define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_FREE_SPACE_ID 0xf0U
#define STEELSERIES_FIZZ_COMMAND_ERROR_SUCCESS 0
#define STEELSERIES_FIZZ_COMMAND_ERROR_FILE_NOT_FOUND 1
#define STEELSERIES_FIZZ_COMMAND_ERROR_FILE_TOO_SHORT 2
#define STEELSERIES_FIZZ_COMMAND_ERROR_FLASH_FAILED 3
#define STEELSERIES_FIZZ_COMMAND_ERROR_PERMISSION_DENIED 4
#define STEELSERIES_FIZZ_COMMAND_ERROR_OPERATION_NO_SUPPORTED 5
#define STEELSERIES_FIZZ_COMMAND_OFFSET 0x00U
#define STEELSERIES_FIZZ_ERROR_OFFSET 0x01U
#define STEELSERIES_FIZZ_VERSION_COMMAND 0x90U
#define STEELSERIES_FIZZ_VERSION_COMMAND_OFFSET 0x00U
#define STEELSERIES_FIZZ_VERSION_MODE_OFFSET 0x01U
#define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_COMMAND_OFFSET 0x00U
#define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_FILESYSTEM_OFFSET 0x01U
#define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_ID_OFFSET 0x02U
#define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_SIZE_OFFSET 0x03U
#define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_OFFSET_OFFSET 0x05U
#define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_DATA_OFFSET 0x09U
#define STEELSERIES_FIZZ_READ_ACCESS_FILE_COMMAND_OFFSET 0x00U
#define STEELSERIES_FIZZ_READ_ACCESS_FILE_FILESYSTEM_OFFSET 0x01U
#define STEELSERIES_FIZZ_READ_ACCESS_FILE_ID_OFFSET 0x02U
#define STEELSERIES_FIZZ_READ_ACCESS_FILE_SIZE_OFFSET 0x03U
#define STEELSERIES_FIZZ_READ_ACCESS_FILE_OFFSET_OFFSET 0x05U
#define STEELSERIES_FIZZ_READ_ACCESS_FILE_DATA_OFFSET 0x02U
#define STEELSERIES_FIZZ_ERASE_FILE_COMMAND_OFFSET 0x0U
#define STEELSERIES_FIZZ_ERASE_FILE_FILESYSTEM_OFFSET 0x1U
#define STEELSERIES_FIZZ_ERASE_FILE_ID_OFFSET 0x2U
#define STEELSERIES_FIZZ_RESET_COMMAND_OFFSET 0x0U
#define STEELSERIES_FIZZ_RESET_MODE_OFFSET 0x1U
struct _FuSteelseriesFizz {
FuUsbDevice parent_instance;
FuSteelseriesDeviceKind device_kind;
guint8 iface_idx;
guint8 ep;
gsize in_size;
};
G_DEFINE_TYPE(FuSteelseriesFizz, fu_steelseries_fizz, FU_TYPE_USB_DEVICE)
static gboolean
fu_steelseries_fizz_command_error_to_error(guint8 cmd, guint8 err, GError **error)
{
/* success */
if (err == STEELSERIES_FIZZ_COMMAND_ERROR_SUCCESS)
return TRUE;
if (err == STEELSERIES_FIZZ_COMMAND_ERROR_FILE_NOT_FOUND) {
g_set_error(error,
G_IO_ERROR,
G_FILE_ERROR_NOENT,
"command 0x%02x returned error 0x%02x",
cmd,
err);
return FALSE;
}
/* targeted offset is past the file end */
if (err == STEELSERIES_FIZZ_COMMAND_ERROR_FILE_TOO_SHORT) {
g_set_error(error,
G_IO_ERROR,
G_FILE_ERROR_NOSPC,
"command 0x%02x returned error 0x%02x",
cmd,
err);
return FALSE;
}
/* when internal flash returns error */
if (err == STEELSERIES_FIZZ_COMMAND_ERROR_FLASH_FAILED) {
g_set_error(error,
G_IO_ERROR,
G_FILE_ERROR_IO,
"command 0x%02x returned error 0x%02x",
cmd,
err);
return FALSE;
}
/* USB API doesn't have permission to access this file */
if (err == STEELSERIES_FIZZ_COMMAND_ERROR_PERMISSION_DENIED) {
g_set_error(error,
G_IO_ERROR,
G_FILE_ERROR_ACCES,
"command 0x%02x returned error 0x%02x",
cmd,
err);
return FALSE;
}
/* USB API doesn't have permission to access this file */
if (err == STEELSERIES_FIZZ_COMMAND_ERROR_OPERATION_NO_SUPPORTED) {
g_set_error(error,
G_IO_ERROR,
G_FILE_ERROR_PERM,
"command 0x%02x returned error 0x%02x",
cmd,
err);
return FALSE;
}
/* fallback */
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"command 0x%02x returned error 0x%02x",
cmd,
err);
return FALSE;
}
static gboolean
fu_steelseries_fizz_command(FuDevice *device, guint8 *data, gboolean answer, GError **error)
{
FuSteelseriesFizz *self = FU_STEELSERIES_FIZZ(device);
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
gsize actual_len = 0;
gboolean ret;
ret = g_usb_device_control_transfer(usb_device,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_CLASS,
G_USB_DEVICE_RECIPIENT_INTERFACE,
0x09,
0x0200,
self->iface_idx,
data,
STEELSERIES_BUFFER_CONTROL_SIZE,
&actual_len,
STEELSERIES_TRANSACTION_TIMEOUT,
NULL,
error);
if (!ret) {
g_prefix_error(error, "failed to do control transfer: ");
return FALSE;
}
if (actual_len != STEELSERIES_BUFFER_CONTROL_SIZE) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"only wrote %" G_GSIZE_FORMAT "bytes",
actual_len);
return FALSE;
}
/* cleanup the buffer before receiving any data */
memset(data, 0x00, STEELSERIES_BUFFER_CONTROL_SIZE);
/* do not expect the answer from device */
if (answer != TRUE)
return TRUE;
ret = g_usb_device_interrupt_transfer(usb_device,
self->ep,
data,
self->in_size,
&actual_len,
STEELSERIES_TRANSACTION_TIMEOUT,
NULL,
error);
if (!ret) {
g_prefix_error(error, "failed to do EP transfer: ");
return FALSE;
}
if (actual_len != self->in_size) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"only read %" G_GSIZE_FORMAT "bytes",
actual_len);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_fizz_command_and_check_error(FuDevice *device, guint8 *data, GError **error)
{
FuSteelseriesFizz *self = FU_STEELSERIES_FIZZ(device);
gint gerr = G_FILE_ERROR_FAILED;
const guint8 command = data[0];
guint8 err;
guint8 cmd;
if (!fu_steelseries_fizz_command(device, data, TRUE, error))
return FALSE;
if (!fu_common_read_uint8_safe(data,
self->in_size,
STEELSERIES_FIZZ_COMMAND_OFFSET,
&cmd,
error))
return FALSE;
if (cmd != command) {
g_set_error(error,
G_IO_ERROR,
gerr,
"command invalid, got 0x%02x, expected 0x%02x",
cmd,
command);
return FALSE;
}
if (!fu_common_read_uint8_safe(data,
self->in_size,
STEELSERIES_FIZZ_ERROR_OFFSET,
&err,
error))
return FALSE;
return fu_steelseries_fizz_command_error_to_error(cmd, err, error);
}
static gchar *
fu_steelseries_fizz_version(FuDevice *device, GError **error)
{
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
const guint16 cmd = 0x90U;
const guint8 mode = 0U; /* string */
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_VERSION_COMMAND_OFFSET,
cmd,
error))
return NULL;
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_VERSION_MODE_OFFSET,
mode,
error))
return NULL;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "Version", data, sizeof(data));
if (!fu_steelseries_fizz_command(device, data, TRUE, error))
return NULL;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "Version", data, sizeof(data));
/* success */
return g_strndup((const gchar *)data, sizeof(data));
}
static gboolean
fu_steelseries_fizz_write_access_file(FuDevice *device,
guint8 fs,
guint8 id,
const guint8 *buf,
gsize bufsz,
FuProgress *progress,
GError **error)
{
const guint16 cmd = 0x03U;
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
g_autoptr(GPtrArray) chunks = NULL;
chunks = fu_chunk_array_new(buf, bufsz, 0x0, 0x0, STEELSERIES_BUFFER_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 size = fu_chunk_get_data_sz(chk);
const guint32 offset = fu_chunk_get_address(chk);
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_WRITE_ACCESS_FILE_COMMAND_OFFSET,
cmd,
error))
return FALSE;
if (!fu_common_write_uint8_safe(
data,
sizeof(data),
STEELSERIES_FIZZ_WRITE_ACCESS_FILE_FILESYSTEM_OFFSET,
fs,
error))
return FALSE;
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_WRITE_ACCESS_FILE_ID_OFFSET,
id,
error))
return FALSE;
if (!fu_common_write_uint16_safe(data,
sizeof(data),
STEELSERIES_FIZZ_WRITE_ACCESS_FILE_SIZE_OFFSET,
size,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_common_write_uint32_safe(data,
sizeof(data),
STEELSERIES_FIZZ_WRITE_ACCESS_FILE_OFFSET_OFFSET,
offset,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memcpy_safe(data,
sizeof(data),
STEELSERIES_FIZZ_WRITE_ACCESS_FILE_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_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "AccessFile", data, sizeof(data));
if (!fu_steelseries_fizz_command_and_check_error(device, data, error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "AccessFile", data, sizeof(data));
fu_progress_step_done(progress);
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_fizz_erase_file(FuDevice *device, guint8 fs, guint8 id, GError **error)
{
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
const guint16 cmd = 0x02U;
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_ERASE_FILE_COMMAND_OFFSET,
cmd,
error))
return FALSE;
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_ERASE_FILE_FILESYSTEM_OFFSET,
fs,
error))
return FALSE;
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_ERASE_FILE_ID_OFFSET,
id,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "EraseFile", data, sizeof(data));
if (!fu_steelseries_fizz_command_and_check_error(device, data, error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "EraseFile", data, sizeof(data));
/* success */
return TRUE;
}
static gboolean
fu_steelseries_fizz_reset(FuDevice *device, guint8 mode, GError **error)
{
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
const guint16 cmd = 0x01U;
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_RESET_COMMAND_OFFSET,
cmd,
error))
return FALSE;
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_RESET_MODE_OFFSET,
mode,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "Reset", data, sizeof(data));
if (!fu_steelseries_fizz_command(device, data, FALSE, error))
return FALSE;
/* success */
return TRUE;
}
static gboolean
fu_steelseries_fizz_file_crc32(FuDevice *device,
guint8 fs,
guint8 id,
guint32 *calculated_crc,
guint32 *stored_crc,
GError **error)
{
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
const guint16 cmd = 0x84U;
if (!fu_common_write_uint8_safe(data, sizeof(data), 0x00U, cmd, error))
return FALSE;
if (!fu_common_write_uint8_safe(data, sizeof(data), 0x01U, fs, error))
return FALSE;
if (!fu_common_write_uint8_safe(data, sizeof(data), 0x02U, id, error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "FileCRC32", data, sizeof(data));
if (!fu_steelseries_fizz_command_and_check_error(device, data, error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "FileCRC32", data, sizeof(data));
if (!fu_common_read_uint32_safe(data,
sizeof(data),
0x02U,
calculated_crc,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_common_read_uint32_safe(data,
sizeof(data),
0x06U,
stored_crc,
G_LITTLE_ENDIAN,
error))
return FALSE;
/* success */
return TRUE;
}
static gboolean
fu_steelseries_fizz_read_access_file(FuDevice *device,
guint8 fs,
guint8 id,
guint8 *buf,
gsize bufsz,
FuProgress *progress,
GError **error)
{
const guint16 cmd = 0x83U;
guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0};
g_autoptr(GPtrArray) chunks = NULL;
chunks = fu_chunk_array_mutable_new(buf, bufsz, 0x0, 0x0, STEELSERIES_BUFFER_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 size = fu_chunk_get_data_sz(chk);
const guint32 offset = fu_chunk_get_address(chk);
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_READ_ACCESS_FILE_COMMAND_OFFSET,
cmd,
error))
return FALSE;
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_READ_ACCESS_FILE_FILESYSTEM_OFFSET,
fs,
error))
return FALSE;
if (!fu_common_write_uint8_safe(data,
sizeof(data),
STEELSERIES_FIZZ_READ_ACCESS_FILE_ID_OFFSET,
id,
error))
return FALSE;
if (!fu_common_write_uint16_safe(data,
sizeof(data),
STEELSERIES_FIZZ_READ_ACCESS_FILE_SIZE_OFFSET,
size,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_common_write_uint32_safe(data,
sizeof(data),
STEELSERIES_FIZZ_READ_ACCESS_FILE_OFFSET_OFFSET,
offset,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "AccessFile", data, sizeof(data));
if (!fu_steelseries_fizz_command_and_check_error(device, data, error))
return FALSE;
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "AccessFile", data, sizeof(data));
if (!fu_memcpy_safe(fu_chunk_get_data_out(chk),
fu_chunk_get_data_sz(chk),
0x00,
data,
sizeof(data),
STEELSERIES_FIZZ_READ_ACCESS_FILE_DATA_OFFSET,
fu_chunk_get_data_sz(chk),
error))
return FALSE;
fu_progress_step_done(progress);
}
/* success */
return TRUE;
}
static gboolean
fu_steelseries_fizz_probe(FuDevice *device, GError **error)
{
#if G_USB_CHECK_VERSION(0, 3, 3)
FuSteelseriesFizz *self = FU_STEELSERIES_FIZZ(device);
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
GUsbInterface *iface = NULL;
GUsbEndpoint *ep = NULL;
guint8 iface_id = 3;
guint8 ep_id;
guint16 packet_size;
g_autoptr(GPtrArray) ifaces = NULL;
g_autoptr(GPtrArray) endpoints = NULL;
/* FuUsbDevice->probe */
if (!FU_DEVICE_CLASS(fu_steelseries_fizz_parent_class)->probe(device, error))
return FALSE;
ifaces = g_usb_device_get_interfaces(usb_device, error);
if (ifaces == NULL || ifaces->len < iface_id) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"update interface not found");
return FALSE;
}
/* use the fourth interface for interrupt transfer */
iface = g_ptr_array_index(ifaces, iface_id);
endpoints = g_usb_interface_get_endpoints(iface);
/* expecting to have only one endpoint for communication */
if (endpoints == NULL || endpoints->len != 1) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"endpoint not found");
return FALSE;
}
ep = g_ptr_array_index(endpoints, 0);
ep_id = g_usb_endpoint_get_address(ep);
packet_size = g_usb_endpoint_get_maximum_packet_size(ep);
self->iface_idx = iface_id;
self->ep = ep_id;
self->in_size = packet_size;
fu_usb_device_add_interface(FU_USB_DEVICE(self), iface_id);
/* success */
return TRUE;
#else
/* failed */
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"this version of GUsb is not supported");
return FALSE;
#endif
}
static gboolean
fu_steelseries_fizz_attach(FuDevice *device, FuProgress *progress, GError **error)
{
g_autoptr(GError) error_local = NULL;
guint8 mode = 0x00U; /* normal */
if (!fu_steelseries_fizz_reset(device, mode, &error_local))
g_warning("failed to reset: %s", error_local->message);
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
/* success */
return TRUE;
}
static gboolean
fu_steelseries_fizz_setup(FuDevice *device, GError **error)
{
FuSteelseriesFizz *self = FU_STEELSERIES_FIZZ(device);
guint32 calculated_crc;
guint32 stored_crc;
guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE;
guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID;
g_autofree gchar *version = NULL;
/* FuUsbDevice->setup */
if (!FU_DEVICE_CLASS(fu_steelseries_fizz_parent_class)->setup(device, error))
return FALSE;
version = fu_steelseries_fizz_version(device, error);
if (version == NULL) {
g_prefix_error(error, "failed to get version: ");
return FALSE;
}
fu_device_set_version(device, version);
/* it is a dongle */
if (self->device_kind == FU_STEELSERIES_DEVICE_FIZZ_DONGLE) {
fs = STEELSERIES_FIZZ_FILESYSTEM_DONGLE;
id = STEELSERIES_FIZZ_DONGLE_FILESYSTEM_BACKUP_APP_ID;
}
if (!fu_steelseries_fizz_file_crc32(device, fs, id, &calculated_crc, &stored_crc, error)) {
g_prefix_error(error,
"failed to get file CRC32 from FS 0x%02x ID 0x%02x: ",
fs,
id);
return FALSE;
}
if (calculated_crc != stored_crc)
g_warning("%s: checksum mismatch, got 0x%08x, expected 0x%08x",
fu_device_get_name(device),
calculated_crc,
stored_crc);
/* success */
return TRUE;
}
static gboolean
fu_steelseries_fizz_write_file(FuDevice *device,
guint8 fs,
guint8 id,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
guint32 calculated_crc;
guint32 stored_crc;
const guint8 *buf;
gsize bufsz;
g_autoptr(GBytes) blob = NULL;
g_autoptr(GPtrArray) chunks = NULL;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 38);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 60);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 2);
blob = fu_firmware_get_bytes(firmware, 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_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "File", buf, bufsz);
if (!fu_steelseries_fizz_erase_file(device, fs, id, error)) {
g_prefix_error(error, "failed to erase file 0x%02x:0x%02x: ", fs, id);
return FALSE;
}
fu_progress_step_done(progress);
if (!fu_steelseries_fizz_write_access_file(device,
fs,
id,
buf,
bufsz,
fu_progress_get_child(progress),
error)) {
g_prefix_error(error, "failed to write file 0x%02x:0x%02x: ", fs, id);
return FALSE;
}
fu_progress_step_done(progress);
if (!fu_steelseries_fizz_file_crc32(device, fs, id, &calculated_crc, &stored_crc, error)) {
g_prefix_error(error,
"failed to get file CRC32 from FS 0x%02x ID 0x%02x: ",
fs,
id);
return FALSE;
}
if (calculated_crc != stored_crc)
g_warning("%s: checksum mismatch, got 0x%08x, expected 0x%08x",
fu_device_get_name(device),
calculated_crc,
stored_crc);
fu_progress_step_done(progress);
/* success */
return TRUE;
}
static gboolean
fu_steelseries_fizz_write_firmware(FuDevice *device,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
FuSteelseriesFizz *self = FU_STEELSERIES_FIZZ(device);
guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE;
guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID;
/* it is a dongle */
if (self->device_kind == FU_STEELSERIES_DEVICE_FIZZ_DONGLE) {
fs = STEELSERIES_FIZZ_FILESYSTEM_DONGLE;
id = STEELSERIES_FIZZ_DONGLE_FILESYSTEM_BACKUP_APP_ID;
}
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100);
if (!fu_steelseries_fizz_write_file(device,
fs,
id,
firmware,
fu_progress_get_child(progress),
flags,
error))
return FALSE;
fu_progress_step_done(progress);
/* success */
return TRUE;
}
static FuFirmware *
fu_steelseries_fizz_read_firmware(FuDevice *device, FuProgress *progress, GError **error)
{
FuSteelseriesFizz *self = FU_STEELSERIES_FIZZ(device);
guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE;
guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID;
gsize bufsz = 0x27000;
g_autoptr(FuFirmware) firmware = fu_steelseries_firmware_new();
g_autoptr(GBytes) blob = NULL;
g_autofree guint8 *buf = NULL;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 100);
/* it is a dongle */
if (self->device_kind == FU_STEELSERIES_DEVICE_FIZZ_DONGLE) {
fs = STEELSERIES_FIZZ_FILESYSTEM_DONGLE;
id = STEELSERIES_FIZZ_DONGLE_FILESYSTEM_BACKUP_APP_ID;
bufsz = 0x23000;
}
buf = g_malloc0(bufsz);
if (!fu_steelseries_fizz_read_access_file(device,
fs,
id,
buf,
bufsz,
fu_progress_get_child(progress),
error))
return NULL;
fu_progress_step_done(progress);
if (g_getenv("FWUPD_STEELSERIES_FIZZ_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "Firmware", buf, bufsz);
blob = g_bytes_new_take(g_steal_pointer(&buf), bufsz);
if (!fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NONE, error))
return NULL;
/* success */
return g_steal_pointer(&firmware);
}
static FuFirmware *
fu_steelseries_fizz_prepare_firmware(FuDevice *device,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
g_autoptr(FuFirmware) firmware = fu_steelseries_firmware_new();
if (!fu_firmware_parse(FU_FIRMWARE(firmware), fw, flags, error))
return NULL;
/* success */
return g_steal_pointer(&firmware);
}
static gboolean
fu_steelseries_fizz_set_quirk_kv(FuDevice *device,
const gchar *key,
const gchar *value,
GError **error)
{
FuSteelseriesFizz *self = FU_STEELSERIES_FIZZ(device);
if (g_strcmp0(key, "SteelSeriesDeviceKind") == 0) {
self->device_kind = fu_steelseries_device_type_from_string(value);
if (self->device_kind != FU_STEELSERIES_DEVICE_UNKNOWN)
return TRUE;
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"unsupported SteelSeriesDeviceKind quirk format");
return FALSE;
}
/* failed */
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported");
return FALSE;
}
static void
fu_steelseries_fizz_to_string(FuDevice *device, guint idt, GString *str)
{
FuSteelseriesFizz *self = FU_STEELSERIES_FIZZ(device);
FU_DEVICE_CLASS(fu_steelseries_fizz_parent_class)->to_string(device, idt, str);
fu_common_string_append_kv(str,
idt,
"DeviceKind",
fu_steelseries_device_type_to_string(self->device_kind));
fu_common_string_append_kx(str, idt, "Interface", self->iface_idx);
fu_common_string_append_kx(str, idt, "Endpoint", self->ep);
}
static void
fu_steelseries_fizz_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, 82); /* write */
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* attach */
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 18); /* reload */
}
static void
fu_steelseries_fizz_class_init(FuSteelseriesFizzClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->probe = fu_steelseries_fizz_probe;
klass_device->attach = fu_steelseries_fizz_attach;
klass_device->setup = fu_steelseries_fizz_setup;
klass_device->write_firmware = fu_steelseries_fizz_write_firmware;
klass_device->read_firmware = fu_steelseries_fizz_read_firmware;
klass_device->prepare_firmware = fu_steelseries_fizz_prepare_firmware;
klass_device->set_quirk_kv = fu_steelseries_fizz_set_quirk_kv;
klass_device->to_string = fu_steelseries_fizz_to_string;
klass_device->set_progress = fu_steelseries_fizz_set_progress;
}
static void
fu_steelseries_fizz_init(FuSteelseriesFizz *self)
{
self->device_kind = FU_STEELSERIES_DEVICE_FIZZ;
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE);
fu_device_add_protocol(FU_DEVICE(self), "com.steelseries.fizz");
fu_device_set_install_duration(FU_DEVICE(self), 13); /* 13 s */
fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG);
}

View File

@ -0,0 +1,16 @@
/*
* Copyright (C) 2022 Gaël PORTAY <gael.portay@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#define FU_TYPE_STEELSERIES_FIZZ (fu_steelseries_fizz_get_type())
G_DECLARE_FINAL_TYPE(FuSteelseriesFizz, fu_steelseries_fizz, FU, STEELSERIES_FIZZ, FuUsbDevice)
struct _FuSteelseriesFizzClass {
FuUsbDeviceClass parent_class;
};

View File

@ -11,6 +11,8 @@ shared_module('fu_plugin_steelseries',
'fu-plugin-steelseries.c', 'fu-plugin-steelseries.c',
'fu-steelseries-common.c', 'fu-steelseries-common.c',
'fu-steelseries-device.c', 'fu-steelseries-device.c',
'fu-steelseries-firmware.c',
'fu-steelseries-fizz.c',
'fu-steelseries-gamepad.c', 'fu-steelseries-gamepad.c',
'fu-steelseries-sonic.c', 'fu-steelseries-sonic.c',
], ],

View File

@ -11,6 +11,24 @@ Plugin = steelseries
GType = FuSteelseriesSonic GType = FuSteelseriesSonic
Icon = input-mouse Icon = input-mouse
# Aerox 3 Wireless
[USB\VID_1038&PID_1838]
Plugin = steelseries
GType = FuSteelseriesFizz
Name = Aerox 3 Wireless Dongle
Icon = input-mouse
CounterpartGuid = USB\VID_1038&PID_1839
FirmwareSize = 0x23000
SteelSeriesDeviceKind = fizz-dongle
[USB\VID_1038&PID_183A]
Plugin = steelseries
GType = FuSteelseriesFizz
Name = Aerox 3 Wireless Mouse
Icon = input-mouse
CounterpartGuid = USB\VID_1038&PID_183B
FirmwareSize = 0x27000
# Stratus Duo RX # Stratus Duo RX
[USB\VID_1038&PID_1430] [USB\VID_1038&PID_1430]
Plugin = steelseries Plugin = steelseries