diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index c1e97978c..e26013a44 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -422,6 +422,7 @@ done %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ccgx.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ch341a.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_colorhug.so +%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_corsair.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cros_ec.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cpu.so %if 0%{?have_dell} diff --git a/plugins/corsair/README.md b/plugins/corsair/README.md new file mode 100644 index 000000000..9ba703baf --- /dev/null +++ b/plugins/corsair/README.md @@ -0,0 +1,14 @@ +# Corsair + +## Introduction + +This plugin allows to update firmware on Corsair mice and receivers: + +* SABRE RGB PRO WIRELESS +* SLIPSTREAM WIRELESS USB Receiver + +## 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. diff --git a/plugins/corsair/corsair.quirk b/plugins/corsair/corsair.quirk new file mode 100644 index 000000000..7855d2640 --- /dev/null +++ b/plugins/corsair/corsair.quirk @@ -0,0 +1,15 @@ +# SABRE RGB PRO WIRELESS +[USB\VID_1B1C&PID_1B98] +Plugin = corsair +GType = FuCorsairDevice +Name = SABRE RGB PRO WIRELESS Gaming Mouse +Icon = input-mouse +CorsairDeviceKind = mouse + +# SLIPSTREAM WIRELESS USB Receiver +[USB\VID_1B1C&PID_1BA6] +Plugin = corsair +GType = FuCorsairDevice +Name = SLIPSTREAM WIRELESS USB Receiver +Icon = input-mouse +CorsairDeviceKind = receiver diff --git a/plugins/corsair/fu-corsair-common.c b/plugins/corsair/fu-corsair-common.c new file mode 100644 index 000000000..8db5eada7 --- /dev/null +++ b/plugins/corsair/fu-corsair-common.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 Andrii Dushko + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "fu-corsair-common.h" + +FuCorsairDeviceKind +fu_corsair_device_type_from_string(const gchar *kind) +{ + if (g_strcmp0(kind, "mouse") == 0) + return FU_CORSAIR_DEVICE_MOUSE; + if (g_strcmp0(kind, "receiver") == 0) + return FU_CORSAIR_DEVICE_DONGLE; + return FU_CORSAIR_DEVICE_UNKNOWN; +} + +const gchar * +fu_corsair_device_type_to_string(FuCorsairDeviceKind type) +{ + if (type == FU_CORSAIR_DEVICE_MOUSE) + return "mouse"; + if (type == FU_CORSAIR_DEVICE_DONGLE) + return "receiver"; + + return "unknown"; +} + +guint32 +fu_corsair_calculate_crc(const guint8 *data, guint32 data_len) +{ + gboolean bit; + guint8 c; + guint32 crc = 0xffffffff; + + while (data_len--) { + c = *data++; + for (guint i = 0x80; i > 0; i >>= 1) { + bit = crc & 0x80000000; + if (c & i) { + bit = !bit; + } + crc <<= 1; + if (bit) { + crc ^= 0x04c11db7; + } + } + } + return crc; +} + +/** + * fu_corsair_version_from_uint32: + * @val: version in corsair device format + * + * fu_common_version_from_uint32(... %FWUPD_VERSION_FORMAT_TRIPLET) + * cannot be used because bytes in the version are in non-standard + * order: 0xCCDD.BB.AA. + * + * Returns: a version number, e.g. `1.0.3`. + **/ +gchar * +fu_corsair_version_from_uint32(guint32 value) +{ + return g_strdup_printf("%u.%u.%u", + value & 0xff, + (value >> 8) & 0xff, + (value >> 16) & 0xffff); +} diff --git a/plugins/corsair/fu-corsair-common.h b/plugins/corsair/fu-corsair-common.h new file mode 100644 index 000000000..a66b245c6 --- /dev/null +++ b/plugins/corsair/fu-corsair-common.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 Andrii Dushko + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +typedef enum { + FU_CORSAIR_DEVICE_UNKNOWN = 0, + FU_CORSAIR_DEVICE_MOUSE, + FU_CORSAIR_DEVICE_DONGLE +} FuCorsairDeviceKind; + +FuCorsairDeviceKind +fu_corsair_device_type_from_string(const gchar *kind); + +const gchar * +fu_corsair_device_type_to_string(FuCorsairDeviceKind type); + +guint32 +fu_corsair_calculate_crc(const guint8 *data, guint32 data_len); + +gchar * +fu_corsair_version_from_uint32(guint32 val); diff --git a/plugins/corsair/fu-corsair-device.c b/plugins/corsair/fu-corsair-device.c new file mode 100644 index 000000000..bb6303531 --- /dev/null +++ b/plugins/corsair/fu-corsair-device.c @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2022 Andrii Dushko + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#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 + +struct _FuCorsairDevice { + FuUsbDevice parent_instance; + FuCorsairDeviceKind device_kind; + guint8 vendor_interface; + guint8 epin; + guint8 epout; + guint16 cmd_write_size; + guint16 cmd_read_size; +}; +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: %" G_GSIZE_FORMAT, + data[CORSAIR_OFFSET_CMD_STATUS]); + return FALSE; + } + + return TRUE; +} + +static gboolean +fu_corsair_device_probe(FuDevice *device, GError **error) +{ + FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); + GUsbInterface *iface = NULL; + GUsbEndpoint *ep1 = NULL; + GUsbEndpoint *ep2 = NULL; + g_autoptr(GPtrArray) ifaces = NULL; + g_autoptr(GPtrArray) endpoints = NULL; + + if (!FU_DEVICE_CLASS(fu_corsair_device_parent_class)->probe(device, error)) + return FALSE; + + ifaces = g_usb_device_get_interfaces(usb_device, error); + if (ifaces == NULL || (ifaces->len < (self->vendor_interface + 1u))) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_FOUND, + "update interface not found"); + return FALSE; + } + + iface = g_ptr_array_index(ifaces, self->vendor_interface); + endpoints = g_usb_interface_get_endpoints(iface); + /* expecting to have two endpoints for communication */ + if (endpoints == NULL || endpoints->len != 2) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_FOUND, + "update interface endpoints not found"); + return FALSE; + } + + 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); + } 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); + } + + if (self->cmd_write_size > CORSAIR_MAX_CMD_SIZE || + self->cmd_read_size > CORSAIR_MAX_CMD_SIZE) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "endpoint size is bigger than allowed command size"); + return FALSE; + } + + fu_usb_device_add_interface(FU_USB_DEVICE(self), self->vendor_interface); + return TRUE; +} + +static gchar * +fu_corsair_get_version(FuDevice *device, GError **error) +{ + 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, + &version_raw, + G_LITTLE_ENDIAN, + error)) { + g_prefix_error(error, "parse fail: "); + return NULL; + } + return fu_corsair_version_from_uint32(version_raw); +} + +static gchar * +fu_corsair_get_bootloader_version(FuDevice *device, 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, + &version_raw, + G_LITTLE_ENDIAN, + error)) { + g_prefix_error(error, "parse fail: "); + return NULL; + } + return fu_corsair_version_from_uint32(version_raw); +} + +static gboolean +fu_corsair_ensure_mode(FuDevice *device, FuCorsairDeviceMode mode, GError **error) +{ + 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; + } else { + current_mode = FU_CORSAIR_DEVICE_MODE_APPLICATION; + } + + 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_BOOTLOADER) + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); + + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); + + return TRUE; +} + +static gboolean +fu_corsair_device_attach(FuDevice *device, FuProgress *progress, GError **error) +{ + return fu_corsair_ensure_mode(device, FU_CORSAIR_DEVICE_MODE_APPLICATION, error); +} + +static gboolean +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, + FuProgress *progress, + 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; + + 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); + + 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: "); + return FALSE; + } + fu_progress_step_done(progress); + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); + + return TRUE; +} + +static void +fu_corsair_device_to_string(FuDevice *device, guint idt, GString *str) +{ + FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); + + FU_DEVICE_CLASS(fu_corsair_device_parent_class)->to_string(device, idt, str); + + fu_common_string_append_kv(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); +} + +static void +fu_corsair_device_set_progress(FuDevice *self, FuProgress *progress) +{ + fu_progress_set_id(progress, G_STRLOC); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 4); /* detach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 92); /* write */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 4); /* attach */ +} + +static gboolean +fu_corsair_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) +{ + FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); + guint64 vendor_interface; + + if (g_strcmp0(key, "CorsairDeviceKind") == 0) { + self->device_kind = fu_corsair_device_type_from_string(value); + if (self->device_kind != FU_CORSAIR_DEVICE_UNKNOWN) + return TRUE; + + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "unsupported device in quirk"); + return FALSE; + } else if (g_strcmp0(key, "CorsairVendorInterfaceId") == 0) { + /* clapped to uint8 because bNumInterfaces is 8 bits long */ + if (!fu_common_strtoull_full(value, &vendor_interface, 0, 255, error)) { + g_prefix_error(error, "cannot parse CorsairVendorInterface: "); + return FALSE; + } + self->vendor_interface = vendor_interface; + return TRUE; + } + + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); + return FALSE; +} + +static void +fu_corsair_device_class_init(FuCorsairDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + + 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->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; +} + +static void +fu_corsair_device_init(FuCorsairDevice *device) +{ + FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); + + self->device_kind = FU_CORSAIR_DEVICE_MOUSE; + self->vendor_interface = CORSAIR_DEFAULT_VENDOR_INTERFACE_ID; + + 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); + + fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); + + 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"); +} diff --git a/plugins/corsair/fu-corsair-device.h b/plugins/corsair/fu-corsair-device.h new file mode 100644 index 000000000..b970d2a75 --- /dev/null +++ b/plugins/corsair/fu-corsair-device.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2021 Andrii Dushko + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#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; +}; diff --git a/plugins/corsair/fu-plugin-corsair.c b/plugins/corsair/fu-plugin-corsair.c new file mode 100644 index 000000000..7915c2d07 --- /dev/null +++ b/plugins/corsair/fu-plugin-corsair.c @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 Andrii Dushko + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-corsair-device.h" + +static void +fu_plugin_corsair_init(FuPlugin *plugin) +{ + FuContext *ctx = fu_plugin_get_context(plugin); + fu_plugin_add_device_gtype(plugin, FU_TYPE_CORSAIR_DEVICE); + fu_context_add_quirk_key(ctx, "CorsairDeviceKind"); + fu_context_add_quirk_key(ctx, "CorsairVendorInterfaceId"); +} + +void +fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) +{ + vfuncs->build_hash = FU_BUILD_HASH; + vfuncs->init = fu_plugin_corsair_init; +} diff --git a/plugins/corsair/meson.build b/plugins/corsair/meson.build new file mode 100644 index 000000000..c135d9368 --- /dev/null +++ b/plugins/corsair/meson.build @@ -0,0 +1,31 @@ +if gusb.found() +cargs = ['-DG_LOG_DOMAIN="FuPluginCorsair"'] + +install_data(['corsair.quirk'], + install_dir: join_paths(datadir, 'fwupd', 'quirks.d') +) + +shared_module('fu_plugin_corsair', + fu_hash, + sources : [ + 'fu-plugin-corsair.c', + 'fu-corsair-common.c', + 'fu-corsair-device.c', + ], + include_directories : [ + root_incdir, + fwupd_incdir, + fwupdplugin_incdir, + ], + install : true, + install_dir: plugin_dir, + link_with : [ + fwupd, + fwupdplugin, + ], + c_args : cargs, + dependencies : [ + plugin_deps, + ], +) +endif diff --git a/plugins/meson.build b/plugins/meson.build index b23a42dd3..fb1723259 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -25,6 +25,7 @@ subdir('ccgx') subdir('cfu') subdir('ch341a') subdir('colorhug') +subdir('corsair') subdir('cpu') subdir('cros-ec') subdir('dell')