mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-14 13:42:23 +00:00
corsair: add support for new devices
* KATAR PRO XT * SABRE PRO * KATAR PRO WIRELESS
This commit is contained in:
parent
67c77d7ec1
commit
41f5b7b563
@ -6,9 +6,55 @@ This plugin allows to update firmware on Corsair mice and receivers:
|
||||
|
||||
* SABRE RGB PRO WIRELESS
|
||||
* SLIPSTREAM WIRELESS USB Receiver
|
||||
* KATAR PRO WIRELESS
|
||||
* KATAR PRO XT Gaming Mouse
|
||||
* SABRE PRO Gaming Mouse
|
||||
|
||||
## Update Behavior
|
||||
## Code structure
|
||||
|
||||
All devices handled by one object (FuCorsairDevice). Receivers with wireless-only
|
||||
devices will be shown as two entities: parent device as a receiver and wireless
|
||||
device as a child. Difference in behavior is handled by private flags.
|
||||
|
||||
FuCorsairBp contains low-level protocol related routines. Device objects should
|
||||
call correct versions of these routines in order to update firmware. Correct
|
||||
routines chosen by device quirsks and private flags.
|
||||
|
||||
## Wired mice update behavior
|
||||
|
||||
Mice and/or it's wireless adapter must be connected to host via USB cable
|
||||
to apply an update. The device is switched to bootloader mode to flash
|
||||
updates, and is reset automatically to new firmware after flashing.
|
||||
|
||||
## Wireless mice update behavior
|
||||
|
||||
The receiver should be connected to host and the mouse should be turned on
|
||||
and not sleeping.
|
||||
|
||||
## Quirk Use
|
||||
|
||||
This plugin uses the following plugin-specific quirks:
|
||||
|
||||
### CorsairVendorInterfaceId
|
||||
|
||||
Some devices have non-standard USB interface for protocol communication.
|
||||
This quirk should be set if protocol interface is not 1.
|
||||
|
||||
Since: 1.8.0
|
||||
|
||||
### CorsairSubdeviceId
|
||||
|
||||
Specifies ID of any wireless child device which can be updated. Polling will
|
||||
be turned on if a subdevice is not connected when parent is being probed.
|
||||
|
||||
### Flags:legacy-attach
|
||||
|
||||
This flag is used if legacy attach command should be used
|
||||
|
||||
### Flags:no-version-in-bl
|
||||
|
||||
This flag handles cases if device reports incorrect firmware version in bootloader mode.
|
||||
|
||||
### Flags:is-subdevice
|
||||
|
||||
This flag tells device that it is a child device. All subdevice behavior tweaks will be applied.
|
||||
|
@ -13,3 +13,35 @@ GType = FuCorsairDevice
|
||||
Name = SLIPSTREAM WIRELESS USB Receiver
|
||||
Icon = usb-receiver
|
||||
CorsairDeviceKind = receiver
|
||||
|
||||
# KATAR PRO WIRELESS receiver
|
||||
[USB\VID_1B1C&PID_1B94]
|
||||
Plugin = corsair
|
||||
GType = FuCorsairDevice
|
||||
Name = KATAR PRO WIRELESS receiver
|
||||
CorsairDeviceKind = mouse
|
||||
Flags = legacy-attach
|
||||
CorsairSubdeviceId = USB\VID_1B1C&PID_1B94&WIRELESS
|
||||
|
||||
# KATAR PRO WIRELESS mouse
|
||||
[USB\VID_1B1C&PID_1B94&WIRELESS]
|
||||
Plugin = corsair
|
||||
GType = FuCorsairDevice
|
||||
Name = KATAR PRO WIRELESS mouse
|
||||
CorsairDeviceKind = mouse
|
||||
BatteryThreshold = 30
|
||||
Flags = is-subdevice,legacy-attach,no-version-in-bl
|
||||
|
||||
[USB\VID_1B1C&PID_1BAC]
|
||||
Plugin = corsair
|
||||
GType = FuCorsairDevice
|
||||
Name = KATAR PRO XT Gaming Mouse
|
||||
CorsairDeviceKind = mouse
|
||||
Flags = legacy-attach,no-version-in-bl
|
||||
|
||||
[USB\VID_1B1C&PID_1B7A]
|
||||
Plugin = corsair
|
||||
GType = FuCorsairDevice
|
||||
Name = SABRE PRO Gaming Mouse
|
||||
CorsairDeviceKind = mouse
|
||||
Flags = legacy-attach,no-version-in-bl
|
||||
|
447
plugins/corsair/fu-corsair-bp.c
Normal file
447
plugins/corsair/fu-corsair-bp.c
Normal file
@ -0,0 +1,447 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Andrii Dushko <andrii.dushko@developex.net>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "fu-corsair-bp.h"
|
||||
#include "fu-corsair-common.h"
|
||||
|
||||
#define CORSAIR_DEFAULT_VENDOR_INTERFACE_ID 1
|
||||
#define CORSAIR_ACTIVATION_TIMEOUT 30000
|
||||
#define CORSAIR_MODE_BOOTLOADER 3
|
||||
#define CORSAIR_FIRST_CHUNK_HEADER_SIZE 7
|
||||
#define CORSAIR_NEXT_CHUNKS_HEADER_SIZE 3
|
||||
#define CORSAIR_TRANSACTION_TIMEOUT 10000
|
||||
#define CORSAIR_DEFAULT_CMD_SIZE 64
|
||||
|
||||
#define CORSAIR_OFFSET_CMD_PROPERTY_ID 0x02
|
||||
#define CORSAIR_OFFSET_CMD_PROPERTY_VALUE 0x03
|
||||
#define CORSAIR_OFFSET_CMD_VERSION 0x03
|
||||
#define CORSAIR_OFFSET_CMD_CRC 0x08
|
||||
#define CORSAIR_OFFSET_CMD_MODE 0x03
|
||||
#define CORSAIR_OFFSET_CMD_STATUS 0x02
|
||||
#define CORSAIR_OFFSET_CMD_FIRMWARE_SIZE 0x03
|
||||
#define CORSAIR_OFFSET_CMD_SET_MODE 0x04
|
||||
#define CORSAIR_OFFSET_CMD_DESTINATION 0x00
|
||||
|
||||
typedef enum {
|
||||
FU_CORSAIR_BP_DESTINATION_SELF = 0x08,
|
||||
FU_CORSAIR_BP_DESTINATION_SUBDEVICE = 0x09
|
||||
} FuCorsairBpDestination;
|
||||
|
||||
struct _FuCorsairBp {
|
||||
FuUsbDevice parent_instance;
|
||||
guint8 destination;
|
||||
guint8 epin;
|
||||
guint8 epout;
|
||||
guint16 cmd_write_size;
|
||||
guint16 cmd_read_size;
|
||||
gboolean is_legacy_attach;
|
||||
};
|
||||
G_DEFINE_TYPE(FuCorsairBp, fu_corsair_bp, FU_TYPE_USB_DEVICE)
|
||||
|
||||
static gboolean
|
||||
fu_corsair_bp_command(FuCorsairBp *self,
|
||||
guint8 *data,
|
||||
guint timeout,
|
||||
gboolean need_reply,
|
||||
GError **error)
|
||||
{
|
||||
gsize actual_len = 0;
|
||||
gboolean ret;
|
||||
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
||||
|
||||
data[CORSAIR_OFFSET_CMD_DESTINATION] = self->destination;
|
||||
|
||||
fu_common_dump_raw(G_LOG_DOMAIN, "corsair: command", data, self->cmd_write_size);
|
||||
|
||||
ret = g_usb_device_interrupt_transfer(usb_device,
|
||||
self->epout,
|
||||
data,
|
||||
self->cmd_write_size,
|
||||
&actual_len,
|
||||
timeout,
|
||||
NULL,
|
||||
error);
|
||||
if (!ret) {
|
||||
g_prefix_error(error, "failed to write command: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (actual_len != self->cmd_write_size) {
|
||||
g_set_error(error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"wrong size written: %" G_GSIZE_FORMAT,
|
||||
actual_len);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!need_reply)
|
||||
return TRUE;
|
||||
|
||||
memset(data, 0, FU_CORSAIR_MAX_CMD_SIZE);
|
||||
|
||||
ret = g_usb_device_interrupt_transfer(usb_device,
|
||||
self->epin,
|
||||
data,
|
||||
self->cmd_read_size,
|
||||
&actual_len,
|
||||
timeout,
|
||||
NULL,
|
||||
error);
|
||||
if (!ret) {
|
||||
g_prefix_error(error, "failed to get command response: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (actual_len != self->cmd_read_size) {
|
||||
g_set_error(error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"wrong size read: %" G_GSIZE_FORMAT,
|
||||
actual_len);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fu_common_dump_raw(G_LOG_DOMAIN, "corsair: response", data, self->cmd_write_size);
|
||||
|
||||
if (data[CORSAIR_OFFSET_CMD_STATUS] != 0) {
|
||||
g_set_error(error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"device replied with error: %" G_GSIZE_FORMAT,
|
||||
data[CORSAIR_OFFSET_CMD_STATUS]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_bp_write_first_chunk(FuCorsairBp *self,
|
||||
FuChunk *chunk,
|
||||
guint32 firmware_size,
|
||||
GError **error)
|
||||
{
|
||||
guint8 init_cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x0d, 0x00, 0x03};
|
||||
guint8 write_cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x06, 0x00};
|
||||
if (!fu_corsair_bp_command(self, init_cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) {
|
||||
g_prefix_error(error, "firmware init fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!fu_common_write_uint32_safe(write_cmd,
|
||||
sizeof(write_cmd),
|
||||
CORSAIR_OFFSET_CMD_FIRMWARE_SIZE,
|
||||
firmware_size,
|
||||
G_LITTLE_ENDIAN,
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot serialize firmware size: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (!fu_memcpy_safe(write_cmd,
|
||||
sizeof(write_cmd),
|
||||
CORSAIR_FIRST_CHUNK_HEADER_SIZE,
|
||||
fu_chunk_get_data(chunk),
|
||||
fu_chunk_get_data_sz(chunk),
|
||||
0,
|
||||
fu_chunk_get_data_sz(chunk),
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot set data: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (!fu_corsair_bp_command(self, write_cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) {
|
||||
g_prefix_error(error, "write command fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_bp_write_chunk(FuCorsairBp *self, FuChunk *chunk, GError **error)
|
||||
{
|
||||
guint8 cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x07};
|
||||
if (!fu_memcpy_safe(cmd,
|
||||
sizeof(cmd),
|
||||
CORSAIR_NEXT_CHUNKS_HEADER_SIZE,
|
||||
fu_chunk_get_data(chunk),
|
||||
fu_chunk_get_data_sz(chunk),
|
||||
0,
|
||||
fu_chunk_get_data_sz(chunk),
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot set data: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (!fu_corsair_bp_command(self, cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) {
|
||||
g_prefix_error(error, "write command fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_corsair_bp_incorporate(FuDevice *self, FuDevice *donor)
|
||||
{
|
||||
FuCorsairBp *bp_self = FU_CORSAIR_BP(self);
|
||||
FuCorsairBp *bp_donor = FU_CORSAIR_BP(donor);
|
||||
|
||||
bp_self->epin = bp_donor->epin;
|
||||
bp_self->epout = bp_donor->epout;
|
||||
bp_self->cmd_write_size = bp_donor->cmd_write_size;
|
||||
bp_self->cmd_read_size = bp_donor->cmd_read_size;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_corsair_bp_init(FuCorsairBp *self)
|
||||
{
|
||||
self->cmd_read_size = CORSAIR_DEFAULT_CMD_SIZE;
|
||||
self->cmd_write_size = CORSAIR_DEFAULT_CMD_SIZE;
|
||||
self->destination = FU_CORSAIR_BP_DESTINATION_SELF;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_corsair_bp_get_property(FuCorsairBp *self,
|
||||
FuCorsairBpProperty property,
|
||||
guint32 *value,
|
||||
GError **error)
|
||||
{
|
||||
guint8 data[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x02};
|
||||
|
||||
fu_common_write_uint16(&data[CORSAIR_OFFSET_CMD_PROPERTY_ID],
|
||||
(guint16)property,
|
||||
G_LITTLE_ENDIAN);
|
||||
|
||||
if (!fu_corsair_bp_command(self, data, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error))
|
||||
return FALSE;
|
||||
|
||||
*value = fu_common_read_uint32(&data[CORSAIR_OFFSET_CMD_PROPERTY_VALUE], G_LITTLE_ENDIAN);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_bp_set_mode(FuCorsairBp *self, FuCorsairDeviceMode mode, GError **error)
|
||||
{
|
||||
guint8 cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x01, 0x03};
|
||||
|
||||
cmd[CORSAIR_OFFSET_CMD_SET_MODE] = mode;
|
||||
|
||||
if (!fu_corsair_bp_command(self, cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) {
|
||||
g_prefix_error(error, "set mode command fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_bp_write_firmware_chunks(FuCorsairBp *self,
|
||||
FuChunk *first_chunk,
|
||||
GPtrArray *chunks,
|
||||
FuProgress *progress,
|
||||
guint32 firmware_size,
|
||||
GError **error)
|
||||
{
|
||||
fu_progress_set_id(progress, G_STRLOC);
|
||||
fu_progress_set_steps(progress, chunks->len + 1);
|
||||
|
||||
if (!fu_corsair_bp_write_first_chunk(self, first_chunk, firmware_size, error)) {
|
||||
g_prefix_error(error, "cannot write first chunk: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
for (guint id = 0; id < chunks->len; id++) {
|
||||
FuChunk *chunk = g_ptr_array_index(chunks, id);
|
||||
if (!fu_corsair_bp_write_chunk(self, chunk, error)) {
|
||||
g_prefix_error(error, "cannot write chunk %u", id);
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_bp_commit_firmware(FuCorsairBp *self, GError **error)
|
||||
{
|
||||
guint8 commit_cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x05, 0x01, 0x00};
|
||||
if (!fu_corsair_bp_command(self, commit_cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) {
|
||||
g_prefix_error(error, "firmware commit fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_bp_write_firmware(FuDevice *device,
|
||||
FuFirmware *firmware,
|
||||
FuProgress *progress,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
const guint8 *firmware_raw;
|
||||
gsize firmware_size;
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
g_autoptr(GPtrArray) chunks = NULL;
|
||||
g_autoptr(FuChunk) firstChunk = NULL;
|
||||
g_autoptr(GBytes) rest_of_firmware = NULL;
|
||||
FuCorsairBp *self = FU_CORSAIR_BP(device);
|
||||
guint32 first_chunk_size = self->cmd_write_size - CORSAIR_FIRST_CHUNK_HEADER_SIZE;
|
||||
|
||||
blob = fu_firmware_get_bytes(firmware, error);
|
||||
if (blob == NULL) {
|
||||
g_prefix_error(error, "cannot get firmware data");
|
||||
return FALSE;
|
||||
}
|
||||
firmware_raw = fu_bytes_get_data_safe(blob, &firmware_size, error);
|
||||
if (firmware_raw == NULL) {
|
||||
g_prefix_error(error, "cannot get firmware data: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* the firmware size should be greater than 1 chunk */
|
||||
if (firmware_size <= first_chunk_size) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"update file should be bigger");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
firstChunk = fu_chunk_new(0, 0, 0, g_bytes_get_data(blob, NULL), first_chunk_size);
|
||||
rest_of_firmware = fu_common_bytes_new_offset(blob,
|
||||
first_chunk_size,
|
||||
firmware_size - first_chunk_size,
|
||||
error);
|
||||
if (rest_of_firmware == NULL) {
|
||||
g_prefix_error(error, "cannot get firmware past first chunk: ");
|
||||
return FALSE;
|
||||
}
|
||||
chunks =
|
||||
fu_chunk_array_new_from_bytes(rest_of_firmware,
|
||||
first_chunk_size,
|
||||
0,
|
||||
self->cmd_write_size - CORSAIR_NEXT_CHUNKS_HEADER_SIZE);
|
||||
|
||||
if (!fu_corsair_bp_write_firmware_chunks(self,
|
||||
firstChunk,
|
||||
chunks,
|
||||
progress,
|
||||
g_bytes_get_size(blob),
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
if (!fu_corsair_bp_commit_firmware(self, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_corsair_bp_activate_firmware(FuCorsairBp *self, FuFirmware *firmware, GError **error)
|
||||
{
|
||||
guint32 crc;
|
||||
gsize firmware_size;
|
||||
const guint8 *firmware_raw;
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
guint8 cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x16, 0x00, 0x01, 0x03, 0x00, 0x01, 0x01};
|
||||
|
||||
blob = fu_firmware_get_bytes(firmware, error);
|
||||
if (blob == NULL) {
|
||||
g_prefix_error(error, "cannot get firmware bytes");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
firmware_raw = fu_bytes_get_data_safe(blob, &firmware_size, error);
|
||||
if (firmware_raw == NULL) {
|
||||
g_prefix_error(error, "cannot get firmware data: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
crc = fu_corsair_calculate_crc(firmware_raw, firmware_size);
|
||||
fu_common_write_uint32(&cmd[CORSAIR_OFFSET_CMD_CRC], crc, G_LITTLE_ENDIAN);
|
||||
|
||||
return fu_corsair_bp_command(self, cmd, CORSAIR_ACTIVATION_TIMEOUT, TRUE, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_bp_attach(FuDevice *device, FuProgress *progress, GError **error)
|
||||
{
|
||||
FuCorsairBp *self = FU_CORSAIR_BP(device);
|
||||
if (self->is_legacy_attach) {
|
||||
guint8 cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x10, 0x01, 0x00, 0x03, 0x00, 0x01};
|
||||
return fu_corsair_bp_command(self, cmd, CORSAIR_TRANSACTION_TIMEOUT, FALSE, error);
|
||||
} else {
|
||||
return fu_corsair_bp_set_mode(self, FU_CORSAIR_DEVICE_MODE_APPLICATION, error);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_bp_detach(FuDevice *device, FuProgress *progress, GError **error)
|
||||
{
|
||||
FuCorsairBp *self = FU_CORSAIR_BP(device);
|
||||
return fu_corsair_bp_set_mode(self, FU_CORSAIR_DEVICE_MODE_BOOTLOADER, error);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_corsair_bp_to_string(FuDevice *device, guint idt, GString *str)
|
||||
{
|
||||
FuCorsairBp *self = FU_CORSAIR_BP(device);
|
||||
|
||||
FU_DEVICE_CLASS(fu_corsair_bp_parent_class)->to_string(device, idt, str);
|
||||
|
||||
fu_common_string_append_kx(str, idt, "InEndpoint", self->epin);
|
||||
fu_common_string_append_kx(str, idt, "OutEndpoint", self->epout);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_corsair_bp_class_init(FuCorsairBpClass *klass)
|
||||
{
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
||||
|
||||
klass_device->incorporate = fu_corsair_bp_incorporate;
|
||||
klass_device->write_firmware = fu_corsair_bp_write_firmware;
|
||||
klass_device->attach = fu_corsair_bp_attach;
|
||||
klass_device->detach = fu_corsair_bp_detach;
|
||||
klass_device->to_string = fu_corsair_bp_to_string;
|
||||
}
|
||||
|
||||
FuCorsairBp *
|
||||
fu_corsair_bp_new(GUsbDevice *usb_device, gboolean is_subdevice)
|
||||
{
|
||||
FuCorsairBp *self = g_object_new(FU_TYPE_CORSAIR_BP, "usb_device", usb_device, NULL);
|
||||
|
||||
if (is_subdevice) {
|
||||
self->destination = FU_CORSAIR_BP_DESTINATION_SUBDEVICE;
|
||||
} else {
|
||||
self->destination = FU_CORSAIR_BP_DESTINATION_SELF;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
fu_corsair_bp_set_cmd_size(FuCorsairBp *self, guint16 write_size, guint16 read_size)
|
||||
{
|
||||
self->cmd_write_size = write_size;
|
||||
self->cmd_read_size = read_size;
|
||||
}
|
||||
|
||||
void
|
||||
fu_corsair_bp_set_endpoints(FuCorsairBp *self, guint8 epin, guint8 epout)
|
||||
{
|
||||
self->epin = epin;
|
||||
self->epout = epout;
|
||||
}
|
||||
|
||||
void
|
||||
fu_corsair_bp_set_legacy_attach(FuCorsairBp *self, gboolean is_legacy_attach)
|
||||
{
|
||||
self->is_legacy_attach = is_legacy_attach;
|
||||
}
|
37
plugins/corsair/fu-corsair-bp.h
Normal file
37
plugins/corsair/fu-corsair-bp.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Andrii Dushko <andrii.dushko@developex.net>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include "fu-corsair-common.h"
|
||||
|
||||
#define FU_TYPE_CORSAIR_BP (fu_corsair_bp_get_type())
|
||||
G_DECLARE_FINAL_TYPE(FuCorsairBp, fu_corsair_bp, FU, CORSAIR_BP, FuUsbDevice)
|
||||
|
||||
struct _FuCorsairBpClass {
|
||||
FuUsbDeviceClass parent_class;
|
||||
};
|
||||
|
||||
gboolean
|
||||
fu_corsair_bp_get_property(FuCorsairBp *self,
|
||||
FuCorsairBpProperty property,
|
||||
guint32 *value,
|
||||
GError **error);
|
||||
|
||||
gboolean
|
||||
fu_corsair_bp_activate_firmware(FuCorsairBp *self, FuFirmware *firmware, GError **error);
|
||||
|
||||
void
|
||||
fu_corsair_bp_set_cmd_size(FuCorsairBp *self, guint16 write_size, guint16 read_size);
|
||||
void
|
||||
fu_corsair_bp_set_endpoints(FuCorsairBp *self, guint8 epin, guint8 epout);
|
||||
void
|
||||
fu_corsair_bp_set_legacy_attach(FuCorsairBp *self, gboolean is_legacy_attach);
|
||||
|
||||
FuCorsairBp *
|
||||
fu_corsair_bp_new(GUsbDevice *usb_device, gboolean is_subdevice);
|
@ -6,7 +6,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#define FU_CORSAIR_DEVICE_FLAG_LEGACY_ATTACH (1 << 0)
|
||||
#define FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE (1 << 1)
|
||||
#define FU_CORSAIR_DEVICE_FLAG_NO_VERSION_IN_BOOTLOADER (1 << 2)
|
||||
|
||||
#define FU_CORSAIR_MAX_CMD_SIZE 1024
|
||||
|
||||
typedef enum {
|
||||
FU_CORSAIR_DEVICE_UNKNOWN = 0,
|
||||
@ -14,6 +20,19 @@ typedef enum {
|
||||
FU_CORSAIR_DEVICE_RECEIVER
|
||||
} FuCorsairDeviceKind;
|
||||
|
||||
typedef enum {
|
||||
FU_CORSAIR_BP_PROPERTY_MODE = 3,
|
||||
FU_CORSAIR_BP_PROPERTY_BATTERY_LEVEL = 15,
|
||||
FU_CORSAIR_BP_PROPERTY_VERSION = 19,
|
||||
FU_CORSAIR_BP_PROPERTY_BOOTLOADER_VERSION = 20,
|
||||
FU_CORSAIR_BP_PROPERTY_SUBDEVICES = 54,
|
||||
} FuCorsairBpProperty;
|
||||
|
||||
typedef enum {
|
||||
FU_CORSAIR_DEVICE_MODE_APPLICATION = 1,
|
||||
FU_CORSAIR_DEVICE_MODE_BOOTLOADER = 3
|
||||
} FuCorsairDeviceMode;
|
||||
|
||||
FuCorsairDeviceKind
|
||||
fu_corsair_device_type_from_string(const gchar *kind);
|
||||
|
||||
|
@ -8,104 +8,27 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "fu-corsair-bp.h"
|
||||
#include "fu-corsair-common.h"
|
||||
#include "fu-corsair-device.h"
|
||||
|
||||
#define CORSAIR_DEFAULT_VENDOR_INTERFACE_ID 1
|
||||
#define CORSAIR_ACTIVATION_TIMEOUT 30000
|
||||
#define CORSAIR_MODE_BOOTLOADER 3
|
||||
#define CORSAIR_FIRST_CHUNK_HEADER_SIZE 7
|
||||
#define CORSAIR_NEXT_CHUNKS_HEADER_SIZE 3
|
||||
#define CORSAIR_MAX_CMD_SIZE 1024
|
||||
#define CORSAIR_TRANSACTION_TIMEOUT 2000
|
||||
|
||||
#define CORSAIR_OFFSET_CMD_VERSION 0x03
|
||||
#define CORSAIR_OFFSET_CMD_CRC 0x08
|
||||
#define CORSAIR_OFFSET_CMD_MODE 0x03
|
||||
#define CORSAIR_OFFSET_CMD_STATUS 0x02
|
||||
#define CORSAIR_OFFSET_CMD_FIRMWARE_SIZE 0x03
|
||||
#define CORSAIR_OFFSET_CMD_SET_MODE 0x04
|
||||
#define CORSAIR_TRANSACTION_TIMEOUT 4000
|
||||
#define CORSAIR_SUBDEVICE_POLL_PERIOD 30000
|
||||
#define CORSAIR_SUBDEVICE_REBOOT_DELAY (4 * G_USEC_PER_SEC)
|
||||
#define CORSAIR_SUBDEVICE_RECONNECT_RETRIES 30
|
||||
#define CORSAIR_SUBDEVICE_RECONNECT_PERIOD 1000
|
||||
|
||||
struct _FuCorsairDevice {
|
||||
FuUsbDevice parent_instance;
|
||||
FuCorsairDeviceKind device_kind;
|
||||
guint8 vendor_interface;
|
||||
guint8 epin;
|
||||
guint8 epout;
|
||||
guint16 cmd_write_size;
|
||||
guint16 cmd_read_size;
|
||||
gchar *subdevice_id;
|
||||
FuCorsairBp *bp;
|
||||
};
|
||||
G_DEFINE_TYPE(FuCorsairDevice, fu_corsair_device, FU_TYPE_USB_DEVICE)
|
||||
|
||||
typedef enum {
|
||||
FU_CORSAIR_DEVICE_MODE_APPLICATION = 1,
|
||||
FU_CORSAIR_DEVICE_MODE_BOOTLOADER = 3
|
||||
} FuCorsairDeviceMode;
|
||||
|
||||
static gboolean
|
||||
fu_corsair_device_command(FuDevice *device, guint8 *data, guint timeout, GError **error)
|
||||
{
|
||||
FuCorsairDevice *self = FU_CORSAIR_DEVICE(device);
|
||||
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
||||
gsize actual_len = 0;
|
||||
gboolean ret;
|
||||
|
||||
ret = g_usb_device_interrupt_transfer(usb_device,
|
||||
self->epout,
|
||||
data,
|
||||
self->cmd_write_size,
|
||||
&actual_len,
|
||||
timeout,
|
||||
NULL,
|
||||
error);
|
||||
if (!ret) {
|
||||
g_prefix_error(error, "failed to write command: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (actual_len != self->cmd_write_size) {
|
||||
g_set_error(error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"wrong size written: %" G_GSIZE_FORMAT,
|
||||
actual_len);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
memset(data, 0, CORSAIR_MAX_CMD_SIZE);
|
||||
|
||||
ret = g_usb_device_interrupt_transfer(usb_device,
|
||||
self->epin,
|
||||
data,
|
||||
self->cmd_read_size,
|
||||
&actual_len,
|
||||
timeout,
|
||||
NULL,
|
||||
error);
|
||||
if (!ret) {
|
||||
g_prefix_error(error, "failed to get command response: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (actual_len != self->cmd_read_size) {
|
||||
g_set_error(error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"wrong size read: %" G_GSIZE_FORMAT,
|
||||
actual_len);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (data[CORSAIR_OFFSET_CMD_STATUS] != 0) {
|
||||
g_set_error(error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"device replied with error: 0x%02x",
|
||||
data[CORSAIR_OFFSET_CMD_STATUS]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_device_probe(FuDevice *device, GError **error)
|
||||
{
|
||||
@ -116,6 +39,15 @@ fu_corsair_device_probe(FuDevice *device, GError **error)
|
||||
GUsbEndpoint *ep2 = NULL;
|
||||
g_autoptr(GPtrArray) ifaces = NULL;
|
||||
g_autoptr(GPtrArray) endpoints = NULL;
|
||||
g_autoptr(FuCorsairBp) bp = NULL;
|
||||
guint16 cmd_write_size;
|
||||
guint16 cmd_read_size;
|
||||
guint8 epin;
|
||||
guint8 epout;
|
||||
|
||||
/* probing are skipped for subdevices */
|
||||
if (fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE))
|
||||
return TRUE;
|
||||
|
||||
if (!FU_DEVICE_CLASS(fu_corsair_device_parent_class)->probe(device, error))
|
||||
return FALSE;
|
||||
@ -143,19 +75,18 @@ fu_corsair_device_probe(FuDevice *device, GError **error)
|
||||
ep1 = g_ptr_array_index(endpoints, 0);
|
||||
ep2 = g_ptr_array_index(endpoints, 1);
|
||||
if (g_usb_endpoint_get_direction(ep1) == G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST) {
|
||||
self->epin = g_usb_endpoint_get_address(ep1);
|
||||
self->epout = g_usb_endpoint_get_address(ep2);
|
||||
self->cmd_read_size = g_usb_endpoint_get_maximum_packet_size(ep1);
|
||||
self->cmd_write_size = g_usb_endpoint_get_maximum_packet_size(ep2);
|
||||
epin = g_usb_endpoint_get_address(ep1);
|
||||
epout = g_usb_endpoint_get_address(ep2);
|
||||
cmd_read_size = g_usb_endpoint_get_maximum_packet_size(ep1);
|
||||
cmd_write_size = g_usb_endpoint_get_maximum_packet_size(ep2);
|
||||
} else {
|
||||
self->epin = g_usb_endpoint_get_address(ep2);
|
||||
self->epout = g_usb_endpoint_get_address(ep1);
|
||||
self->cmd_read_size = g_usb_endpoint_get_maximum_packet_size(ep2);
|
||||
self->cmd_write_size = g_usb_endpoint_get_maximum_packet_size(ep1);
|
||||
epin = g_usb_endpoint_get_address(ep2);
|
||||
epout = g_usb_endpoint_get_address(ep1);
|
||||
cmd_read_size = g_usb_endpoint_get_maximum_packet_size(ep2);
|
||||
cmd_write_size = g_usb_endpoint_get_maximum_packet_size(ep1);
|
||||
}
|
||||
|
||||
if (self->cmd_write_size > CORSAIR_MAX_CMD_SIZE ||
|
||||
self->cmd_read_size > CORSAIR_MAX_CMD_SIZE) {
|
||||
if (cmd_write_size > FU_CORSAIR_MAX_CMD_SIZE || cmd_read_size > FU_CORSAIR_MAX_CMD_SIZE) {
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
@ -164,56 +95,223 @@ fu_corsair_device_probe(FuDevice *device, GError **error)
|
||||
}
|
||||
|
||||
fu_usb_device_add_interface(FU_USB_DEVICE(self), self->vendor_interface);
|
||||
|
||||
self->bp = fu_corsair_bp_new(usb_device, FALSE);
|
||||
fu_corsair_bp_set_cmd_size(self->bp, cmd_write_size, cmd_read_size);
|
||||
fu_corsair_bp_set_endpoints(self->bp, epin, epout);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_poll_subdevice(FuDevice *device, gboolean *subdevice_added, GError **error)
|
||||
{
|
||||
guint32 subdevices;
|
||||
g_autoptr(FuCorsairDevice) child = NULL;
|
||||
FuCorsairDevice *self = FU_CORSAIR_DEVICE(device);
|
||||
g_autoptr(FuCorsairBp) child_bp = NULL;
|
||||
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
||||
|
||||
if (!fu_corsair_bp_get_property(self->bp,
|
||||
FU_CORSAIR_BP_PROPERTY_SUBDEVICES,
|
||||
&subdevices,
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot get subdevices: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (subdevices == 0) {
|
||||
*subdevice_added = FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
child_bp = fu_corsair_bp_new(usb_device, TRUE);
|
||||
fu_device_incorporate(FU_DEVICE(child_bp), FU_DEVICE(self->bp));
|
||||
|
||||
child = fu_corsair_device_new(self, child_bp);
|
||||
fu_device_add_instance_id(FU_DEVICE(child), self->subdevice_id);
|
||||
fu_device_set_physical_id(FU_DEVICE(child), fu_device_get_physical_id(device));
|
||||
fu_device_set_logical_id(FU_DEVICE(child), "subdevice");
|
||||
fu_device_add_internal_flag(FU_DEVICE(child), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN);
|
||||
|
||||
if (!fu_device_probe(FU_DEVICE(child), error))
|
||||
return FALSE;
|
||||
if (!fu_device_setup(FU_DEVICE(child), error))
|
||||
return FALSE;
|
||||
|
||||
fu_device_add_child(device, FU_DEVICE(child));
|
||||
*subdevice_added = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
fu_corsair_get_version(FuDevice *device, GError **error)
|
||||
fu_corsair_device_get_version(FuDevice *device, GError **error)
|
||||
{
|
||||
FuCorsairDevice *self = FU_CORSAIR_DEVICE(device);
|
||||
guint32 version_raw;
|
||||
guint8 data[CORSAIR_MAX_CMD_SIZE] = {0x08, 0x02, 0x13};
|
||||
|
||||
if (!fu_corsair_device_command(device, data, CORSAIR_TRANSACTION_TIMEOUT, error))
|
||||
return NULL;
|
||||
|
||||
if (!fu_common_read_uint32_safe(data,
|
||||
sizeof(data),
|
||||
CORSAIR_OFFSET_CMD_VERSION,
|
||||
if (!fu_corsair_bp_get_property(self->bp,
|
||||
FU_CORSAIR_BP_PROPERTY_VERSION,
|
||||
&version_raw,
|
||||
G_LITTLE_ENDIAN,
|
||||
error)) {
|
||||
g_prefix_error(error, "parse fail: ");
|
||||
error))
|
||||
return NULL;
|
||||
|
||||
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
||||
gboolean broken_by_flag =
|
||||
fu_device_has_private_flag(device,
|
||||
FU_CORSAIR_DEVICE_FLAG_NO_VERSION_IN_BOOTLOADER);
|
||||
|
||||
/* Version 0xffffffff means that previous update was interrupted.
|
||||
Set version to 0.0.0 in both broken and interrupted cases to make sure that new
|
||||
firmware will not be rejected because of older version. It is safe to always
|
||||
pass firmware because setup in bootloader mode can only happen during
|
||||
emergency update */
|
||||
if (broken_by_flag || version_raw == G_MAXUINT32) {
|
||||
version_raw = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return fu_corsair_version_from_uint32(version_raw);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
fu_corsair_get_bootloader_version(FuDevice *device, GError **error)
|
||||
fu_corsair_device_get_bootloader_version(FuCorsairBp *self, GError **error)
|
||||
{
|
||||
guint32 version_raw;
|
||||
guint8 data[CORSAIR_MAX_CMD_SIZE] = {0x08, 0x02, 0x14};
|
||||
|
||||
if (!fu_corsair_device_command(device, data, CORSAIR_TRANSACTION_TIMEOUT, error))
|
||||
return NULL;
|
||||
|
||||
if (!fu_common_read_uint32_safe(data,
|
||||
sizeof(data),
|
||||
CORSAIR_OFFSET_CMD_VERSION,
|
||||
if (!fu_corsair_bp_get_property(self,
|
||||
FU_CORSAIR_BP_PROPERTY_BOOTLOADER_VERSION,
|
||||
&version_raw,
|
||||
G_LITTLE_ENDIAN,
|
||||
error)) {
|
||||
g_prefix_error(error, "parse fail: ");
|
||||
error))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return fu_corsair_version_from_uint32(version_raw);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_device_setup(FuDevice *device, GError **error)
|
||||
{
|
||||
guint32 mode;
|
||||
guint32 battery_level;
|
||||
g_autofree gchar *bootloader_version = NULL;
|
||||
g_autofree gchar *version = NULL;
|
||||
FuCorsairDevice *self = FU_CORSAIR_DEVICE(device);
|
||||
|
||||
if (!fu_corsair_bp_get_property(self->bp, FU_CORSAIR_BP_PROPERTY_MODE, &mode, error))
|
||||
return FALSE;
|
||||
if (mode == FU_CORSAIR_DEVICE_MODE_BOOTLOADER)
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
||||
|
||||
version = fu_corsair_device_get_version(device, error);
|
||||
if (version == NULL) {
|
||||
g_prefix_error(error, "cannot get version: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_device_set_version(device, version);
|
||||
|
||||
bootloader_version = fu_corsair_device_get_bootloader_version(self->bp, error);
|
||||
if (bootloader_version == NULL) {
|
||||
g_prefix_error(error, "cannot get bootloader version: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_device_set_version_bootloader(device, bootloader_version);
|
||||
|
||||
if (fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE) &&
|
||||
!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
||||
if (!fu_corsair_bp_get_property(self->bp,
|
||||
FU_CORSAIR_BP_PROPERTY_BATTERY_LEVEL,
|
||||
&battery_level,
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot get battery level: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_device_set_battery_level(device, battery_level / 10);
|
||||
}
|
||||
fu_corsair_bp_set_legacy_attach(
|
||||
self->bp,
|
||||
fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_LEGACY_ATTACH));
|
||||
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
|
||||
/* check for a subdevice */
|
||||
if (self->subdevice_id != NULL) {
|
||||
gboolean subdevice_added = FALSE;
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
if (!fu_corsair_poll_subdevice(device, &subdevice_added, &local_error)) {
|
||||
g_warning("error polling subdevice: %s", local_error->message);
|
||||
} else {
|
||||
if (!subdevice_added)
|
||||
fu_device_set_poll_interval(device, CORSAIR_SUBDEVICE_POLL_PERIOD);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_device_reload(FuDevice *device, GError **error)
|
||||
{
|
||||
if (fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE)) {
|
||||
return fu_corsair_device_setup(device, error);
|
||||
}
|
||||
|
||||
/* USB devices will be reloaded by FWUPD after reenumeration */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_is_subdevice_connected_cb(FuDevice *device, gpointer user_data, GError **error)
|
||||
{
|
||||
guint32 subdevices = 0;
|
||||
FuCorsairDevice *self = FU_CORSAIR_DEVICE(device);
|
||||
|
||||
if (!fu_corsair_bp_get_property(self->bp,
|
||||
FU_CORSAIR_BP_PROPERTY_SUBDEVICES,
|
||||
&subdevices,
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot get subdevices: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (subdevices == 0) {
|
||||
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "subdevice is not connected");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_reconnect_subdevice(FuDevice *device, GError **error)
|
||||
{
|
||||
FuDevice *parent = fu_device_get_parent(device);
|
||||
|
||||
if (parent == NULL) {
|
||||
g_prefix_error(error, "cannot get parent: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Wait some time to make sure that a subdevice was disconnected. */
|
||||
g_usleep(CORSAIR_SUBDEVICE_REBOOT_DELAY);
|
||||
|
||||
if (!fu_device_retry_full(parent,
|
||||
fu_corsair_is_subdevice_connected_cb,
|
||||
CORSAIR_SUBDEVICE_RECONNECT_RETRIES,
|
||||
CORSAIR_SUBDEVICE_RECONNECT_PERIOD,
|
||||
NULL,
|
||||
error)) {
|
||||
g_prefix_error(error, "a subdevice did not reconnect after attach: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_ensure_mode(FuDevice *device, FuCorsairDeviceMode mode, GError **error)
|
||||
{
|
||||
FuCorsairDevice *self = FU_CORSAIR_DEVICE(device);
|
||||
FuCorsairDeviceMode current_mode;
|
||||
guint8 set_mode_cmd[CORSAIR_MAX_CMD_SIZE] = {0x08, 0x01, 0x03};
|
||||
|
||||
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
||||
current_mode = FU_CORSAIR_DEVICE_MODE_BOOTLOADER;
|
||||
@ -224,55 +322,31 @@ fu_corsair_ensure_mode(FuDevice *device, FuCorsairDeviceMode mode, GError **erro
|
||||
if (mode == current_mode)
|
||||
return TRUE;
|
||||
|
||||
set_mode_cmd[CORSAIR_OFFSET_CMD_SET_MODE] = mode;
|
||||
if (!fu_corsair_device_command(device, set_mode_cmd, CORSAIR_TRANSACTION_TIMEOUT, error)) {
|
||||
g_prefix_error(error, "set mode command fail: ");
|
||||
if (mode == FU_CORSAIR_DEVICE_MODE_APPLICATION) {
|
||||
if (!fu_device_attach(FU_DEVICE(self->bp), error)) {
|
||||
g_prefix_error(error, "attach failed: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_device_setup(FuDevice *device, GError **error)
|
||||
{
|
||||
guint32 mode;
|
||||
g_autofree gchar *bootloader_version = NULL;
|
||||
g_autofree gchar *version = NULL;
|
||||
guint8 get_mode_cmd[CORSAIR_MAX_CMD_SIZE] = {0x08, 0x02, 0x03};
|
||||
|
||||
version = fu_corsair_get_version(device, error);
|
||||
if (version == NULL) {
|
||||
g_prefix_error(error, "cannot get version: ");
|
||||
} else {
|
||||
if (!fu_device_detach(FU_DEVICE(self->bp), error)) {
|
||||
g_prefix_error(error, "detach failed: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_device_set_version(FU_DEVICE(device), version);
|
||||
|
||||
bootloader_version = fu_corsair_get_bootloader_version(device, error);
|
||||
if (bootloader_version == NULL) {
|
||||
g_prefix_error(error, "cannot get bootloader version: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_device_set_version_bootloader(device, bootloader_version);
|
||||
|
||||
if (!fu_corsair_device_command(device, get_mode_cmd, CORSAIR_TRANSACTION_TIMEOUT, error))
|
||||
return FALSE;
|
||||
|
||||
if (!fu_common_read_uint32_safe(get_mode_cmd,
|
||||
sizeof(get_mode_cmd),
|
||||
CORSAIR_OFFSET_CMD_MODE,
|
||||
&mode,
|
||||
G_LITTLE_ENDIAN,
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot parse device mode: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (mode == FU_CORSAIR_DEVICE_MODE_BOOTLOADER)
|
||||
if (fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE)) {
|
||||
if (!fu_corsair_reconnect_subdevice(device, error)) {
|
||||
g_prefix_error(error, "subdevice did not reconnect: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (mode == FU_CORSAIR_DEVICE_MODE_BOOTLOADER) {
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
||||
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
} else {
|
||||
fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
||||
}
|
||||
} else {
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@ -289,120 +363,6 @@ fu_corsair_device_detach(FuDevice *device, FuProgress *progress, GError **error)
|
||||
return fu_corsair_ensure_mode(device, FU_CORSAIR_DEVICE_MODE_BOOTLOADER, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_activate_firmware(FuDevice *device, guint32 crc, GError **error)
|
||||
{
|
||||
guint8 commit_cmd[CORSAIR_MAX_CMD_SIZE] = {0x08, 0x05, 0x01, 0x00};
|
||||
guint8 activate_cmd[CORSAIR_MAX_CMD_SIZE] =
|
||||
{0x08, 0x16, 0x00, 0x01, 0x03, 0x00, 0x01, 0x01};
|
||||
if (!fu_corsair_device_command(device, commit_cmd, CORSAIR_TRANSACTION_TIMEOUT, error)) {
|
||||
g_prefix_error(error, "firmware commit fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!fu_common_write_uint32_safe(activate_cmd,
|
||||
sizeof(activate_cmd),
|
||||
CORSAIR_OFFSET_CMD_CRC,
|
||||
crc,
|
||||
G_LITTLE_ENDIAN,
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot serialize CRC: ");
|
||||
return FALSE;
|
||||
}
|
||||
return fu_corsair_device_command(device, activate_cmd, CORSAIR_ACTIVATION_TIMEOUT, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_write_first_chunk(FuDevice *device,
|
||||
FuChunk *chunk,
|
||||
guint32 firmware_size,
|
||||
GError **error)
|
||||
{
|
||||
guint8 init_cmd[CORSAIR_MAX_CMD_SIZE] = {0x08, 0x0d, 0x00, 0x03};
|
||||
guint8 write_cmd[CORSAIR_MAX_CMD_SIZE] = {0x08, 0x06, 0x00};
|
||||
if (!fu_corsair_device_command(device, init_cmd, CORSAIR_TRANSACTION_TIMEOUT, error)) {
|
||||
g_prefix_error(error, "firmware init fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!fu_common_write_uint32_safe(write_cmd,
|
||||
sizeof(write_cmd),
|
||||
CORSAIR_OFFSET_CMD_FIRMWARE_SIZE,
|
||||
firmware_size,
|
||||
G_LITTLE_ENDIAN,
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot serialize firmware size: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (!fu_memcpy_safe(write_cmd,
|
||||
sizeof(write_cmd),
|
||||
CORSAIR_FIRST_CHUNK_HEADER_SIZE,
|
||||
fu_chunk_get_data(chunk),
|
||||
fu_chunk_get_data_sz(chunk),
|
||||
0,
|
||||
fu_chunk_get_data_sz(chunk),
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot set data: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (!fu_corsair_device_command(device, write_cmd, CORSAIR_TRANSACTION_TIMEOUT, error)) {
|
||||
g_prefix_error(error, "write command fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_write_chunk(FuDevice *device, FuChunk *chunk, GError **error)
|
||||
{
|
||||
guint8 cmd[CORSAIR_MAX_CMD_SIZE] = {0x08, 0x07};
|
||||
if (!fu_memcpy_safe(cmd,
|
||||
sizeof(cmd),
|
||||
CORSAIR_NEXT_CHUNKS_HEADER_SIZE,
|
||||
fu_chunk_get_data(chunk),
|
||||
fu_chunk_get_data_sz(chunk),
|
||||
0,
|
||||
fu_chunk_get_data_sz(chunk),
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot set data: ");
|
||||
return FALSE;
|
||||
}
|
||||
if (!fu_corsair_device_command(device, cmd, CORSAIR_TRANSACTION_TIMEOUT, error)) {
|
||||
g_prefix_error(error, "write command fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_device_write_firmware_chunks(FuDevice *device,
|
||||
FuChunk *first_chunk,
|
||||
GPtrArray *chunks,
|
||||
FuProgress *progress,
|
||||
guint32 firmware_size,
|
||||
GError **error)
|
||||
{
|
||||
fu_progress_set_id(progress, G_STRLOC);
|
||||
fu_progress_set_steps(progress, chunks->len + 1);
|
||||
|
||||
if (!fu_corsair_write_first_chunk(device, first_chunk, firmware_size, error)) {
|
||||
g_prefix_error(error, "cannot write first chunk: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
for (guint id = 0; id < chunks->len; id++) {
|
||||
FuChunk *chunk = g_ptr_array_index(chunks, id);
|
||||
if (!fu_corsair_write_chunk(device, chunk, error)) {
|
||||
g_prefix_error(error, "cannot write chunk %u", id);
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_device_write_firmware(FuDevice *device,
|
||||
FuFirmware *firmware,
|
||||
@ -410,71 +370,38 @@ fu_corsair_device_write_firmware(FuDevice *device,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
const guint8 *firmware_raw;
|
||||
guint32 crc;
|
||||
gsize firmware_size;
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
g_autoptr(GPtrArray) chunks = NULL;
|
||||
g_autoptr(FuChunk) firstChunk = NULL;
|
||||
g_autoptr(GBytes) rest_of_firmware = NULL;
|
||||
FuCorsairDevice *self = FU_CORSAIR_DEVICE(device);
|
||||
guint32 first_chunk_size = self->cmd_write_size - CORSAIR_FIRST_CHUNK_HEADER_SIZE;
|
||||
g_autoptr(GBytes) firmware_bytes = fu_firmware_get_bytes(firmware, error);
|
||||
|
||||
blob = fu_firmware_get_bytes(firmware, error);
|
||||
if (blob == NULL) {
|
||||
if (firmware_bytes == NULL) {
|
||||
g_prefix_error(error, "cannot get firmware data");
|
||||
return FALSE;
|
||||
}
|
||||
firmware_raw = fu_bytes_get_data_safe(blob, &firmware_size, error);
|
||||
if (firmware_raw == NULL) {
|
||||
g_prefix_error(error, "cannot get firmware data: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* the firmware size should be greater than 1 chunk */
|
||||
if (firmware_size <= first_chunk_size) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"update file should be bigger");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
firstChunk = fu_chunk_new(0, 0, 0, g_bytes_get_data(blob, NULL), first_chunk_size);
|
||||
rest_of_firmware = fu_common_bytes_new_offset(blob,
|
||||
first_chunk_size,
|
||||
firmware_size - first_chunk_size,
|
||||
error);
|
||||
if (rest_of_firmware == NULL) {
|
||||
g_prefix_error(error, "cannot get firmware past first chunk: ");
|
||||
return FALSE;
|
||||
}
|
||||
chunks =
|
||||
fu_chunk_array_new_from_bytes(rest_of_firmware,
|
||||
first_chunk_size,
|
||||
0,
|
||||
self->cmd_write_size - CORSAIR_NEXT_CHUNKS_HEADER_SIZE);
|
||||
|
||||
fu_progress_set_id(progress, G_STRLOC);
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95);
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5);
|
||||
|
||||
crc = fu_corsair_calculate_crc(firmware_raw, firmware_size);
|
||||
if (!fu_corsair_device_write_firmware_chunks(device,
|
||||
firstChunk,
|
||||
chunks,
|
||||
if (!fu_device_write_firmware(FU_DEVICE(self->bp),
|
||||
firmware_bytes,
|
||||
fu_progress_get_child(progress),
|
||||
g_bytes_get_size(blob),
|
||||
error))
|
||||
flags,
|
||||
error)) {
|
||||
g_prefix_error(error, "cannot write firmware");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
if (!fu_corsair_activate_firmware(device, crc, error)) {
|
||||
if (!fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_LEGACY_ATTACH)) {
|
||||
if (!fu_corsair_bp_activate_firmware(self->bp, firmware, error)) {
|
||||
g_prefix_error(error, "firmware activation fail: ");
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
||||
}
|
||||
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@ -490,8 +417,8 @@ fu_corsair_device_to_string(FuDevice *device, guint idt, GString *str)
|
||||
idt,
|
||||
"DeviceKind",
|
||||
fu_corsair_device_type_to_string(self->device_kind));
|
||||
fu_common_string_append_kx(str, idt, "InEndpoint", self->epin);
|
||||
fu_common_string_append_kx(str, idt, "OutEndpoint", self->epout);
|
||||
|
||||
fu_device_add_string(FU_DEVICE(self->bp), idt, str);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -527,25 +454,70 @@ fu_corsair_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value,
|
||||
}
|
||||
self->vendor_interface = vendor_interface;
|
||||
return TRUE;
|
||||
} else if (g_strcmp0(key, "CorsairSubdeviceId") == 0) {
|
||||
self->subdevice_id = g_strdup(value);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_corsair_device_poll(FuDevice *device, GError **error)
|
||||
{
|
||||
gboolean subdevice_added = FALSE;
|
||||
g_autoptr(FuDeviceLocker) locker = NULL;
|
||||
|
||||
locker = fu_device_locker_new(device, error);
|
||||
if (locker == NULL) {
|
||||
g_prefix_error(error, "cannot open device: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!fu_corsair_poll_subdevice(device, &subdevice_added, error)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* stop polling if a subdevice was added */
|
||||
if (subdevice_added) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOTHING_TO_DO,
|
||||
"subdevice added successfully");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_corsair_device_finalize(GObject *object)
|
||||
{
|
||||
FuCorsairDevice *self = FU_CORSAIR_DEVICE(object);
|
||||
|
||||
g_free(self->subdevice_id);
|
||||
g_object_unref(self->bp);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_corsair_device_class_init(FuCorsairDeviceClass *klass)
|
||||
{
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
||||
|
||||
klass_device->poll = fu_corsair_device_poll;
|
||||
klass_device->probe = fu_corsair_device_probe;
|
||||
klass_device->set_quirk_kv = fu_corsair_set_quirk_kv;
|
||||
klass_device->setup = fu_corsair_device_setup;
|
||||
klass_device->reload = fu_corsair_device_reload;
|
||||
klass_device->attach = fu_corsair_device_attach;
|
||||
klass_device->detach = fu_corsair_device_detach;
|
||||
klass_device->write_firmware = fu_corsair_device_write_firmware;
|
||||
klass_device->to_string = fu_corsair_device_to_string;
|
||||
klass_device->set_progress = fu_corsair_device_set_progress;
|
||||
|
||||
object_class->finalize = fu_corsair_device_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -556,6 +528,16 @@ fu_corsair_device_init(FuCorsairDevice *device)
|
||||
self->device_kind = FU_CORSAIR_DEVICE_MOUSE;
|
||||
self->vendor_interface = CORSAIR_DEFAULT_VENDOR_INTERFACE_ID;
|
||||
|
||||
fu_device_register_private_flag(FU_DEVICE(device),
|
||||
FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE,
|
||||
"is-subdevice");
|
||||
fu_device_register_private_flag(FU_DEVICE(device),
|
||||
FU_CORSAIR_DEVICE_FLAG_LEGACY_ATTACH,
|
||||
"legacy-attach");
|
||||
fu_device_register_private_flag(FU_DEVICE(device),
|
||||
FU_CORSAIR_DEVICE_FLAG_NO_VERSION_IN_BOOTLOADER,
|
||||
"no-version-in-bl");
|
||||
|
||||
fu_device_set_remove_delay(FU_DEVICE(device), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
|
||||
fu_device_set_version_format(FU_DEVICE(device), FWUPD_VERSION_FORMAT_TRIPLET);
|
||||
|
||||
@ -564,3 +546,19 @@ fu_corsair_device_init(FuCorsairDevice *device)
|
||||
fu_device_add_internal_flag(FU_DEVICE(device), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID);
|
||||
fu_device_add_protocol(FU_DEVICE(device), "com.corsair.bp");
|
||||
}
|
||||
|
||||
FuCorsairDevice *
|
||||
fu_corsair_device_new(FuCorsairDevice *parent, FuCorsairBp *bp)
|
||||
{
|
||||
FuCorsairDevice *self = NULL;
|
||||
FuDevice *device = FU_DEVICE(parent);
|
||||
|
||||
self = g_object_new(FU_TYPE_CORSAIR_DEVICE,
|
||||
"context",
|
||||
fu_device_get_context(device),
|
||||
"usb_device",
|
||||
fu_usb_device_get_dev(FU_USB_DEVICE(device)),
|
||||
NULL);
|
||||
self->bp = g_object_ref(bp);
|
||||
return self;
|
||||
}
|
||||
|
@ -8,5 +8,15 @@
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include "fu-corsair-bp.h"
|
||||
#include "fu-corsair-common.h"
|
||||
|
||||
#define FU_TYPE_CORSAIR_DEVICE (fu_corsair_device_get_type())
|
||||
G_DECLARE_FINAL_TYPE(FuCorsairDevice, fu_corsair_device, FU, CORSAIR_DEVICE, FuUsbDevice)
|
||||
|
||||
struct _FuCorsairDeviceClass {
|
||||
FuUsbDeviceClass parent_class;
|
||||
};
|
||||
|
||||
FuCorsairDevice *
|
||||
fu_corsair_device_new(FuCorsairDevice *parent, FuCorsairBp *bp);
|
||||
|
@ -21,6 +21,7 @@ fu_plugin_corsair_load(FuContext *ctx)
|
||||
{
|
||||
fu_context_add_quirk_key(ctx, "CorsairDeviceKind");
|
||||
fu_context_add_quirk_key(ctx, "CorsairVendorInterfaceId");
|
||||
fu_context_add_quirk_key(ctx, "CorsairSubdeviceId");
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -11,6 +11,7 @@ shared_module('fu_plugin_corsair',
|
||||
'fu-plugin-corsair.c',
|
||||
'fu-corsair-common.c',
|
||||
'fu-corsair-device.c',
|
||||
'fu-corsair-bp.c',
|
||||
],
|
||||
include_directories : [
|
||||
root_incdir,
|
||||
|
Loading…
Reference in New Issue
Block a user