mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-03 16:44:26 +00:00
477 lines
13 KiB
C
477 lines
13 KiB
C
/*
|
|
* Copyright (C) 2022 Andrii Dushko <andrii.dushko@developex.net>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-corsair-bp.h"
|
|
#include "fu-corsair-common.h"
|
|
|
|
#define CORSAIR_DEFAULT_VENDOR_INTERFACE_ID 1
|
|
#define CORSAIR_ACTIVATION_TIMEOUT 30000
|
|
#define CORSAIR_MODE_BOOTLOADER 3
|
|
#define CORSAIR_FIRST_CHUNK_HEADER_SIZE 7
|
|
#define CORSAIR_NEXT_CHUNKS_HEADER_SIZE 3
|
|
#define CORSAIR_TRANSACTION_TIMEOUT 10000
|
|
#define CORSAIR_DEFAULT_CMD_SIZE 64
|
|
|
|
#define CORSAIR_OFFSET_CMD_PROPERTY_ID 0x02
|
|
#define CORSAIR_OFFSET_CMD_PROPERTY_VALUE 0x03
|
|
#define CORSAIR_OFFSET_CMD_VERSION 0x03
|
|
#define CORSAIR_OFFSET_CMD_CRC 0x08
|
|
#define CORSAIR_OFFSET_CMD_MODE 0x03
|
|
#define CORSAIR_OFFSET_CMD_STATUS 0x02
|
|
#define CORSAIR_OFFSET_CMD_FIRMWARE_SIZE 0x03
|
|
#define CORSAIR_OFFSET_CMD_SET_MODE 0x04
|
|
#define CORSAIR_OFFSET_CMD_DESTINATION 0x00
|
|
|
|
#define CORSAIR_INPUT_FLUSH_TIMEOUT 10
|
|
#define CORSAIR_INPUT_FLUSH_ITERATIONS 3
|
|
|
|
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_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_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: 0x%02x",
|
|
data[CORSAIR_OFFSET_CMD_STATUS]);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Flush all input reports if there are any.
|
|
* @self: a #FuCorsairBp
|
|
*
|
|
* This function clears any dangling IN reports that
|
|
* the device may have sent after the enumeration.
|
|
*/
|
|
void
|
|
fu_corsair_bp_flush_input_reports(FuCorsairBp *self)
|
|
{
|
|
gsize actual_len;
|
|
g_autofree guint8 *buf = g_malloc0(self->cmd_read_size);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
|
|
for (guint i = 0; i < CORSAIR_INPUT_FLUSH_ITERATIONS; i++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (!g_usb_device_interrupt_transfer(usb_device,
|
|
self->epin,
|
|
buf,
|
|
self->cmd_read_size,
|
|
&actual_len,
|
|
CORSAIR_INPUT_FLUSH_TIMEOUT,
|
|
NULL,
|
|
&error_local))
|
|
g_debug("flushing status: %s", error_local->message);
|
|
}
|
|
}
|
|
|
|
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_memwrite_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_memwrite_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_memread_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_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_memwrite_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_string_append_kx(str, idt, "InEndpoint", self->epin);
|
|
fu_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;
|
|
}
|