mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-17 10:06:38 +00:00

This allows us to ignore all the delays when the device is emulated, with the idea being to do dozens of device emulations in the CI tests. Also, do not call fu_progress_sleep() when the device is emulated.
431 lines
11 KiB
C
431 lines
11 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include "fu-rts54hid-common.h"
|
|
#include "fu-rts54hid-device.h"
|
|
|
|
struct _FuRts54HidDevice {
|
|
FuHidDevice parent_instance;
|
|
gboolean fw_auth;
|
|
gboolean dual_bank;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuRts54HidDevice, fu_rts54hid_device, FU_TYPE_HID_DEVICE)
|
|
|
|
static void
|
|
fu_rts54hid_device_to_string(FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuRts54HidDevice *self = FU_RTS54HID_DEVICE(device);
|
|
fu_string_append_kb(str, idt, "FwAuth", self->fw_auth);
|
|
fu_string_append_kb(str, idt, "DualBank", self->dual_bank);
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hid_device_set_clock_mode(FuRts54HidDevice *self, gboolean enable, GError **error)
|
|
{
|
|
FuRts54HidCmdBuffer cmd_buffer = {
|
|
.cmd = FU_RTS54HID_CMD_WRITE_DATA,
|
|
.ext = FU_RTS54HID_EXT_MCUMODIFYCLOCK,
|
|
.cmd_data0 = (guint8)enable,
|
|
.cmd_data1 = 0,
|
|
.cmd_data2 = 0,
|
|
.cmd_data3 = 0,
|
|
.bufferlen = 0,
|
|
.parameters = 0,
|
|
};
|
|
guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0};
|
|
|
|
if (!fu_memcpy_safe(buf,
|
|
sizeof(buf),
|
|
0x0, /* dst */
|
|
(const guint8 *)&cmd_buffer,
|
|
sizeof(cmd_buffer),
|
|
0x0, /* src */
|
|
sizeof(cmd_buffer),
|
|
error))
|
|
return FALSE;
|
|
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
|
|
0x0,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_RTS54HID_DEVICE_TIMEOUT * 2,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to set clock-mode=%i: ", enable);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hid_device_reset_to_flash(FuRts54HidDevice *self, GError **error)
|
|
{
|
|
FuRts54HidCmdBuffer cmd_buffer = {
|
|
.cmd = FU_RTS54HID_CMD_WRITE_DATA,
|
|
.ext = FU_RTS54HID_EXT_RESET2FLASH,
|
|
.dwregaddr = 0,
|
|
.bufferlen = 0,
|
|
.parameters = 0,
|
|
};
|
|
guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0};
|
|
|
|
if (!fu_memcpy_safe(buf,
|
|
sizeof(buf),
|
|
0x0, /* dst */
|
|
(const guint8 *)&cmd_buffer,
|
|
sizeof(cmd_buffer),
|
|
0x0, /* src */
|
|
sizeof(cmd_buffer),
|
|
error))
|
|
return FALSE;
|
|
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
|
|
0x0,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_RTS54HID_DEVICE_TIMEOUT * 2,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to soft reset: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hid_device_write_flash(FuRts54HidDevice *self,
|
|
guint32 addr,
|
|
const guint8 *data,
|
|
guint16 data_sz,
|
|
GError **error)
|
|
{
|
|
FuRts54HidCmdBuffer cmd_buffer = {
|
|
.cmd = FU_RTS54HID_CMD_WRITE_DATA,
|
|
.ext = FU_RTS54HID_EXT_WRITEFLASH,
|
|
.dwregaddr = GUINT32_TO_LE(addr),
|
|
.bufferlen = GUINT16_TO_LE(data_sz),
|
|
.parameters = 0,
|
|
};
|
|
guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0};
|
|
|
|
g_return_val_if_fail(data_sz <= 128, FALSE);
|
|
g_return_val_if_fail(data != NULL, FALSE);
|
|
g_return_val_if_fail(data_sz != 0, FALSE);
|
|
|
|
if (!fu_memcpy_safe(buf,
|
|
sizeof(buf),
|
|
0x0, /* dst */
|
|
(const guint8 *)&cmd_buffer,
|
|
sizeof(cmd_buffer),
|
|
0x0, /* src */
|
|
sizeof(cmd_buffer),
|
|
error))
|
|
return FALSE;
|
|
if (!fu_memcpy_safe(buf,
|
|
sizeof(buf),
|
|
FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, /* dst */
|
|
data,
|
|
data_sz,
|
|
0x0, /* src */
|
|
data_sz,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
|
|
0x0,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_RTS54HID_DEVICE_TIMEOUT * 2,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to write flash @%08x: ", (guint)addr);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hid_device_verify_update_fw(FuRts54HidDevice *self, FuProgress *progress, GError **error)
|
|
{
|
|
const FuRts54HidCmdBuffer cmd_buffer = {
|
|
.cmd = FU_RTS54HID_CMD_WRITE_DATA,
|
|
.ext = FU_RTS54HID_EXT_VERIFYUPDATE,
|
|
.cmd_data0 = 1,
|
|
.cmd_data1 = 0,
|
|
.cmd_data2 = 0,
|
|
.cmd_data3 = 0,
|
|
.bufferlen = GUINT16_TO_LE(1),
|
|
.parameters = 0,
|
|
};
|
|
guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0};
|
|
|
|
/* set then get */
|
|
if (!fu_memcpy_safe(buf,
|
|
sizeof(buf),
|
|
0x0, /* dst */
|
|
(const guint8 *)&cmd_buffer,
|
|
sizeof(cmd_buffer),
|
|
0x0, /* src */
|
|
sizeof(cmd_buffer),
|
|
error))
|
|
return FALSE;
|
|
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
|
|
0x0,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_RTS54HID_DEVICE_TIMEOUT * 2,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
fu_device_sleep_full(FU_DEVICE(self), 4000, progress); /* ms */
|
|
if (!fu_hid_device_get_report(FU_HID_DEVICE(self),
|
|
0x0,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_RTS54HID_DEVICE_TIMEOUT,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* check device status */
|
|
if (buf[0] != 0x01) {
|
|
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "firmware flash failed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hid_device_erase_spare_bank(FuRts54HidDevice *self, GError **error)
|
|
{
|
|
FuRts54HidCmdBuffer cmd_buffer = {
|
|
.cmd = FU_RTS54HID_CMD_WRITE_DATA,
|
|
.ext = FU_RTS54HID_EXT_ERASEBANK,
|
|
.cmd_data0 = 0,
|
|
.cmd_data1 = 1,
|
|
.cmd_data2 = 0,
|
|
.cmd_data3 = 0,
|
|
.bufferlen = 0,
|
|
.parameters = 0,
|
|
};
|
|
guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0};
|
|
|
|
if (!fu_memcpy_safe(buf,
|
|
sizeof(buf),
|
|
0x0, /* dst */
|
|
(const guint8 *)&cmd_buffer,
|
|
sizeof(cmd_buffer),
|
|
0x0, /* src */
|
|
sizeof(cmd_buffer),
|
|
error))
|
|
return FALSE;
|
|
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
|
|
0x0,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_RTS54HID_DEVICE_TIMEOUT * 2,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to erase spare bank: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hid_device_ensure_status(FuRts54HidDevice *self, GError **error)
|
|
{
|
|
const FuRts54HidCmdBuffer cmd_buffer = {
|
|
.cmd = FU_RTS54HID_CMD_READ_DATA,
|
|
.ext = FU_RTS54HID_EXT_READ_STATUS,
|
|
.cmd_data0 = 0,
|
|
.cmd_data1 = 0,
|
|
.cmd_data2 = 0,
|
|
.cmd_data3 = 0,
|
|
.bufferlen = GUINT16_TO_LE(32),
|
|
.parameters = 0,
|
|
};
|
|
guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0};
|
|
g_autofree gchar *version = NULL;
|
|
|
|
/* set then get */
|
|
if (!fu_memcpy_safe(buf,
|
|
sizeof(buf),
|
|
0x0, /* dst */
|
|
(const guint8 *)&cmd_buffer,
|
|
sizeof(cmd_buffer),
|
|
0x0, /* src */
|
|
sizeof(cmd_buffer),
|
|
error))
|
|
return FALSE;
|
|
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
|
|
0x0,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_RTS54HID_DEVICE_TIMEOUT * 2,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_hid_device_get_report(FU_HID_DEVICE(self),
|
|
0x0,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_RTS54HID_DEVICE_TIMEOUT,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* check the hardware capabilities */
|
|
self->dual_bank = (buf[7] & 0xf0) == 0x80;
|
|
self->fw_auth = (buf[13] & 0x02) > 0;
|
|
|
|
/* hub version is more accurate than bcdVersion */
|
|
version = g_strdup_printf("%x.%x", buf[10], buf[11]);
|
|
fu_device_set_version(FU_DEVICE(self), version);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hid_device_setup(FuDevice *device, GError **error)
|
|
{
|
|
FuRts54HidDevice *self = FU_RTS54HID_DEVICE(device);
|
|
|
|
/* FuUsbDevice->setup */
|
|
if (!FU_DEVICE_CLASS(fu_rts54hid_device_parent_class)->setup(device, error))
|
|
return FALSE;
|
|
|
|
/* check this device is correct */
|
|
if (!fu_rts54hid_device_ensure_status(self, error))
|
|
return FALSE;
|
|
|
|
/* both conditions must be set */
|
|
if (!self->fw_auth) {
|
|
fu_device_set_update_error(device, "device does not support authentication");
|
|
} else if (!self->dual_bank) {
|
|
fu_device_set_update_error(device, "device does not support dual-bank updating");
|
|
} else {
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hid_device_close(FuDevice *device, GError **error)
|
|
{
|
|
FuRts54HidDevice *self = FU_RTS54HID_DEVICE(device);
|
|
|
|
/* set MCU to normal clock rate */
|
|
if (!fu_rts54hid_device_set_clock_mode(self, FALSE, error))
|
|
return FALSE;
|
|
|
|
/* FuHidDevice->close */
|
|
return FU_DEVICE_CLASS(fu_rts54hid_device_parent_class)->close(device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hid_device_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuRts54HidDevice *self = FU_RTS54HID_DEVICE(device);
|
|
g_autoptr(GBytes) fw = NULL;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 46, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 52, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reset");
|
|
|
|
/* get default image */
|
|
fw = fu_firmware_get_bytes(firmware, error);
|
|
if (fw == NULL)
|
|
return FALSE;
|
|
|
|
/* set MCU to high clock rate for better ISP performance */
|
|
if (!fu_rts54hid_device_set_clock_mode(self, TRUE, error))
|
|
return FALSE;
|
|
|
|
/* erase spare flash bank only if it is not empty */
|
|
if (!fu_rts54hid_device_erase_spare_bank(self, error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* write each block */
|
|
chunks = fu_chunk_array_new_from_bytes(fw,
|
|
0x00, /* start addr */
|
|
0x00, /* page_sz */
|
|
FU_RTS54HID_TRANSFER_BLOCK_SIZE);
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index(chunks, i);
|
|
/* write chunk */
|
|
if (!fu_rts54hid_device_write_flash(self,
|
|
fu_chunk_get_address(chk),
|
|
fu_chunk_get_data(chk),
|
|
fu_chunk_get_data_sz(chk),
|
|
error))
|
|
return FALSE;
|
|
|
|
/* update progress */
|
|
fu_progress_set_percentage_full(fu_progress_get_child(progress),
|
|
(gsize)i + 1,
|
|
(gsize)chunks->len);
|
|
}
|
|
fu_progress_step_done(progress);
|
|
|
|
/* get device to authenticate the firmware */
|
|
if (!fu_rts54hid_device_verify_update_fw(self, fu_progress_get_child(progress), error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* send software reset to run available flash code */
|
|
if (!fu_rts54hid_device_reset_to_flash(self, error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* success! */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_rts54hid_device_set_progress(FuDevice *self, FuProgress *progress)
|
|
{
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 62, "write");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 38, "attach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload");
|
|
}
|
|
|
|
static void
|
|
fu_rts54hid_device_init(FuRts54HidDevice *self)
|
|
{
|
|
fu_device_add_protocol(FU_DEVICE(self), "com.realtek.rts54");
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD);
|
|
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR);
|
|
}
|
|
|
|
static void
|
|
fu_rts54hid_device_class_init(FuRts54HidDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
klass_device->write_firmware = fu_rts54hid_device_write_firmware;
|
|
klass_device->to_string = fu_rts54hid_device_to_string;
|
|
klass_device->setup = fu_rts54hid_device_setup;
|
|
klass_device->close = fu_rts54hid_device_close;
|
|
klass_device->set_progress = fu_rts54hid_device_set_progress;
|
|
}
|