mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-05 10:40:20 +00:00
Add a plugin for Corsair devices
Supported devices: * SABRE RGB PRO WIRELESS * SLIPSTREAM WIRELESS USB Receiver
This commit is contained in:
parent
f3e05f3ced
commit
47381ba54a
@ -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}
|
||||
|
14
plugins/corsair/README.md
Normal file
14
plugins/corsair/README.md
Normal file
@ -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.
|
15
plugins/corsair/corsair.quirk
Normal file
15
plugins/corsair/corsair.quirk
Normal file
@ -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
|
70
plugins/corsair/fu-corsair-common.c
Normal file
70
plugins/corsair/fu-corsair-common.c
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Andrii Dushko <andrii.dushko@developex.net>
|
||||
*
|
||||
* 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);
|
||||
}
|
27
plugins/corsair/fu-corsair-common.h
Normal file
27
plugins/corsair/fu-corsair-common.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Andrii Dushko <andrii.dushko@developex.net>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
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);
|
566
plugins/corsair/fu-corsair-device.c
Normal file
566
plugins/corsair/fu-corsair-device.c
Normal file
@ -0,0 +1,566 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Andrii Dushko <andrii.dushko@developex.net>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string.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
|
||||
|
||||
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");
|
||||
}
|
16
plugins/corsair/fu-corsair-device.h
Normal file
16
plugins/corsair/fu-corsair-device.h
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Andrii Dushko <andrii.dushko@developex.net>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fwupdplugin.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;
|
||||
};
|
27
plugins/corsair/fu-plugin-corsair.c
Normal file
27
plugins/corsair/fu-plugin-corsair.c
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Andrii Dushko <andrii.dushko@developex.net>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#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;
|
||||
}
|
31
plugins/corsair/meson.build
Normal file
31
plugins/corsair/meson.build
Normal file
@ -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
|
@ -25,6 +25,7 @@ subdir('ccgx')
|
||||
subdir('cfu')
|
||||
subdir('ch341a')
|
||||
subdir('colorhug')
|
||||
subdir('corsair')
|
||||
subdir('cpu')
|
||||
subdir('cros-ec')
|
||||
subdir('dell')
|
||||
|
Loading…
Reference in New Issue
Block a user