diff --git a/plugins/corsair/README.md b/plugins/corsair/README.md index 9ba703baf..933b209cf 100644 --- a/plugins/corsair/README.md +++ b/plugins/corsair/README.md @@ -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. diff --git a/plugins/corsair/corsair.quirk b/plugins/corsair/corsair.quirk index b215e2003..7e46b10e7 100644 --- a/plugins/corsair/corsair.quirk +++ b/plugins/corsair/corsair.quirk @@ -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 diff --git a/plugins/corsair/fu-corsair-bp.c b/plugins/corsair/fu-corsair-bp.c new file mode 100644 index 000000000..3bb192af8 --- /dev/null +++ b/plugins/corsair/fu-corsair-bp.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2022 Andrii Dushko + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#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; +} diff --git a/plugins/corsair/fu-corsair-bp.h b/plugins/corsair/fu-corsair-bp.h new file mode 100644 index 000000000..de5f25dcd --- /dev/null +++ b/plugins/corsair/fu-corsair-bp.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 Andrii Dushko + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#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); diff --git a/plugins/corsair/fu-corsair-common.h b/plugins/corsair/fu-corsair-common.h index 17ea8c257..cefc5517c 100644 --- a/plugins/corsair/fu-corsair-common.h +++ b/plugins/corsair/fu-corsair-common.h @@ -6,7 +6,13 @@ #pragma once -#include +#include + +#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); diff --git a/plugins/corsair/fu-corsair-device.c b/plugins/corsair/fu-corsair-device.c index 62c9e9583..df4fcba14 100644 --- a/plugins/corsair/fu-corsair-device.c +++ b/plugins/corsair/fu-corsair-device.c @@ -8,104 +8,27 @@ #include +#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: "); - 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: "); - 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_APPLICATION) { + if (!fu_device_attach(FU_DEVICE(self->bp), error)) { + g_prefix_error(error, "attach failed: "); + return FALSE; + } + } else { + if (!fu_device_detach(FU_DEVICE(self->bp), error)) { + g_prefix_error(error, "detach failed: "); + 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); + 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); + } 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, - fu_progress_get_child(progress), - g_bytes_get_size(blob), - error)) - return FALSE; - fu_progress_step_done(progress); - - if (!fu_corsair_activate_firmware(device, crc, error)) { - g_prefix_error(error, "firmware activation fail: "); + if (!fu_device_write_firmware(FU_DEVICE(self->bp), + firmware_bytes, + fu_progress_get_child(progress), + flags, + error)) { + g_prefix_error(error, "cannot write firmware"); return FALSE; } + + fu_progress_step_done(progress); + + 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_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); + } + fu_progress_step_done(progress); - fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); 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; +} diff --git a/plugins/corsair/fu-corsair-device.h b/plugins/corsair/fu-corsair-device.h index b76cb56bb..6bde0e595 100644 --- a/plugins/corsair/fu-corsair-device.h +++ b/plugins/corsair/fu-corsair-device.h @@ -8,5 +8,15 @@ #include +#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); diff --git a/plugins/corsair/fu-plugin-corsair.c b/plugins/corsair/fu-plugin-corsair.c index 5d7d261b6..2f3b9489f 100644 --- a/plugins/corsair/fu-plugin-corsair.c +++ b/plugins/corsair/fu-plugin-corsair.c @@ -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 diff --git a/plugins/corsair/meson.build b/plugins/corsair/meson.build index c135d9368..9c9efcabf 100644 --- a/plugins/corsair/meson.build +++ b/plugins/corsair/meson.build @@ -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,