mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-22 09:12:32 +00:00
430 lines
12 KiB
C
430 lines
12 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-chunk.h"
|
|
#include "fu-rts54hub-device.h"
|
|
|
|
struct _FuRts54HubDevice {
|
|
FuUsbDevice parent_instance;
|
|
gboolean fw_auth;
|
|
gboolean dual_bank;
|
|
gboolean running_on_flash;
|
|
guint8 vendor_cmd;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuRts54HubDevice, fu_rts54hub_device, FU_TYPE_USB_DEVICE)
|
|
|
|
#define FU_RTS54HUB_DEVICE_TIMEOUT 100 /* ms */
|
|
#define FU_RTS54HUB_DEVICE_TIMEOUT_RW 1000 /* ms */
|
|
#define FU_RTS54HUB_DEVICE_TIMEOUT_ERASE 5000 /* ms */
|
|
#define FU_RTS54HUB_DEVICE_TIMEOUT_AUTH 10000 /* ms */
|
|
#define FU_RTS54HUB_DEVICE_BLOCK_SIZE 4096
|
|
#define FU_RTS54HUB_DEVICE_STATUS_LEN 25
|
|
|
|
typedef enum {
|
|
FU_RTS54HUB_VENDOR_CMD_NONE = 0x00,
|
|
FU_RTS54HUB_VENDOR_CMD_STATUS = 1 << 0,
|
|
FU_RTS54HUB_VENDOR_CMD_FLASH = 1 << 1,
|
|
} FuRts54HubVendorCmd;
|
|
|
|
static void
|
|
fu_rts54hub_device_to_string (FuDevice *device, GString *str)
|
|
{
|
|
FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device);
|
|
g_string_append (str, " FuRts54HubDevice:\n");
|
|
g_string_append_printf (str, " fw-auth: %i\n", self->fw_auth);
|
|
g_string_append_printf (str, " dual-bank: %i\n", self->dual_bank);
|
|
g_string_append_printf (str, " running-on-flash: %i\n", self->running_on_flash);
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hub_device_highclockmode (FuRts54HubDevice *self, guint16 value, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
if (!g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0x06, /* request */
|
|
value, /* value */
|
|
0, /* idx */
|
|
NULL, 0, /* data */
|
|
NULL, /* actual */
|
|
FU_RTS54HUB_DEVICE_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to set highclockmode: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
fu_rts54hub_device_reset_flash (FuRts54HubDevice *self, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
if (!g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0xC0 + 0x29, /* request */
|
|
0x0, /* value */
|
|
0x0, /* idx */
|
|
NULL, 0, /* data */
|
|
NULL, /* actual */
|
|
FU_RTS54HUB_DEVICE_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to reset flash: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hub_device_write_flash (FuRts54HubDevice *self,
|
|
guint32 addr,
|
|
const guint8 *data,
|
|
gsize datasz,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
gsize actual_len = 0;
|
|
g_autofree guint8 *datarw = g_memdup (data, datasz);
|
|
if (!g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0xC0 + 0x08, /* request */
|
|
addr % (1 << 16), /* value */
|
|
addr / (1 << 16), /* idx */
|
|
datarw, datasz, /* data */
|
|
&actual_len,
|
|
FU_RTS54HUB_DEVICE_TIMEOUT_RW,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to write flash: ");
|
|
return FALSE;
|
|
}
|
|
if (actual_len != datasz) {
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
|
"only wrote %" G_GSIZE_FORMAT "bytes", actual_len);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
#if 0
|
|
static gboolean
|
|
fu_rts54hub_device_read_flash (FuRts54HubDevice *self,
|
|
guint32 addr,
|
|
guint8 *data,
|
|
gsize datasz,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
gsize actual_len = 0;
|
|
if (!g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0xC0 + 0x18, /* request */
|
|
addr % (1 << 16), /* value */
|
|
addr / (1 << 16), /* idx */
|
|
data, datasz, /* data */
|
|
&actual_len,
|
|
FU_RTS54HUB_DEVICE_TIMEOUT_RW,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to read flash: ");
|
|
return FALSE;
|
|
}
|
|
if (actual_len != datasz) {
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
|
"only read %" G_GSIZE_FORMAT "bytes", actual_len);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
fu_rts54hub_device_flash_authentication (FuRts54HubDevice *self, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
if (!g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0xC0 + 0x19, /* request */
|
|
0x01, /* value */
|
|
0x0, /* idx */
|
|
NULL, 0, /* data */
|
|
NULL, /* actual */
|
|
FU_RTS54HUB_DEVICE_TIMEOUT_AUTH,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to authenticate: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hub_device_erase_flash (FuRts54HubDevice *self,
|
|
guint8 erase_type,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
if (!g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0xC0 + 0x28, /* request */
|
|
erase_type * 256, /* value */
|
|
0x0, /* idx */
|
|
NULL, 0, /* data */
|
|
NULL, /* actual */
|
|
FU_RTS54HUB_DEVICE_TIMEOUT_ERASE,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to erase flash: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hub_device_vendor_cmd (FuRts54HubDevice *self, guint8 value, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
|
|
/* don't set something that's already set */
|
|
if (self->vendor_cmd == value) {
|
|
g_debug ("skipping vendor command 0x%02x as already set", value);
|
|
return TRUE;
|
|
}
|
|
if (!g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0x02, /* request */
|
|
value, /* value */
|
|
0x0bda, /* idx */
|
|
NULL, 0, /* data */
|
|
NULL, /* actual */
|
|
FU_RTS54HUB_DEVICE_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to issue vendor cmd 0x%02x: ", value);
|
|
return FALSE;
|
|
}
|
|
self->vendor_cmd = value;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hub_device_ensure_status (FuRts54HubDevice *self, GError **error)
|
|
{
|
|
guint8 data[FU_RTS54HUB_DEVICE_STATUS_LEN] = { 0 };
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
gsize actual_len = 0;
|
|
|
|
if (!g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0x09, /* request */
|
|
0x0, /* value */
|
|
0x0, /* idx */
|
|
data, sizeof(data),
|
|
&actual_len, /* actual */
|
|
FU_RTS54HUB_DEVICE_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to get status: ");
|
|
return FALSE;
|
|
}
|
|
if (actual_len != FU_RTS54HUB_DEVICE_STATUS_LEN) {
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
|
"only read %" G_GSIZE_FORMAT "bytes", actual_len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check the hardware capabilities */
|
|
self->dual_bank = (data[7] & 0x80) == 0x80;
|
|
self->fw_auth = (data[13] & 0x02) > 0;
|
|
self->running_on_flash = (data[15] & 0x02) > 0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hub_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device);
|
|
|
|
/* check this device is correct */
|
|
if (!fu_rts54hub_device_vendor_cmd (self, FU_RTS54HUB_VENDOR_CMD_STATUS, error)) {
|
|
g_prefix_error (error, "failed to vendor enable: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_rts54hub_device_ensure_status (self, error))
|
|
return FALSE;
|
|
|
|
/* all three conditons must be set */
|
|
if (!self->running_on_flash) {
|
|
fu_device_set_update_error (device,
|
|
"device is abnormally running from ROM");
|
|
} else 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_rts54hub_device_close (FuUsbDevice *device, GError **error)
|
|
{
|
|
FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device);
|
|
|
|
/* disable vendor commands */
|
|
if (self->vendor_cmd != FU_RTS54HUB_VENDOR_CMD_NONE) {
|
|
if (!fu_rts54hub_device_vendor_cmd (self, FU_RTS54HUB_VENDOR_CMD_NONE, error)) {
|
|
g_prefix_error (error, "failed to disable vendor command: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_rts54hub_device_write_firmware (FuDevice *device, GBytes *fw, GError **error)
|
|
{
|
|
FuRts54HubDevice *self = FU_RTS54HUB_DEVICE (device);
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* enable vendor commands */
|
|
if (!fu_rts54hub_device_vendor_cmd (self,
|
|
FU_RTS54HUB_VENDOR_CMD_STATUS |
|
|
FU_RTS54HUB_VENDOR_CMD_FLASH,
|
|
error)) {
|
|
g_prefix_error (error, "failed to cmd enable: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* erase spare flash bank only if it is not empty */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
|
|
if (!fu_rts54hub_device_erase_flash (self, 1, error))
|
|
return FALSE;
|
|
|
|
/* set MCU clock to high clock mode */
|
|
if (!fu_rts54hub_device_highclockmode (self, 0x0001, error)) {
|
|
g_prefix_error (error, "failed to enable MCU clock: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* set SPI controller clock to high clock mode */
|
|
if (!fu_rts54hub_device_highclockmode (self, 0x0101, error)) {
|
|
g_prefix_error (error, "failed to enable SPI clock: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* build packets */
|
|
chunks = fu_chunk_array_new_from_bytes (fw,
|
|
0x00, /* start addr */
|
|
0x00, /* page_sz */
|
|
FU_RTS54HUB_DEVICE_BLOCK_SIZE);
|
|
|
|
/* write each block */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index (chunks, i);
|
|
|
|
/* write chunk */
|
|
if (!fu_rts54hub_device_write_flash (self,
|
|
chk->address,
|
|
chk->data,
|
|
chk->data_sz,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* update progress */
|
|
fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len - 1);
|
|
}
|
|
|
|
/* get device to authenticate the firmware */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY);
|
|
if (!fu_rts54hub_device_flash_authentication (self, error))
|
|
return FALSE;
|
|
|
|
/* send software reset to run available flash code */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
|
|
if (!fu_rts54hub_device_reset_flash (self, error))
|
|
return FALSE;
|
|
|
|
/* don't reset the vendor command enable, the device will be rebooted */
|
|
self->vendor_cmd = FU_RTS54HUB_VENDOR_CMD_NONE;
|
|
|
|
/* success! */
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
return TRUE;
|
|
}
|
|
|
|
static GBytes *
|
|
fu_rts54hub_device_prepare_firmware (FuDevice *device, GBytes *fw, GError **error)
|
|
{
|
|
gsize sz = 0;
|
|
const guint8 *data = g_bytes_get_data (fw, &sz);
|
|
if (sz < 0x7ef3) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"firmware was too small");
|
|
return NULL;
|
|
}
|
|
if ((data[0x7ef3] & 0xf0) != 0x80) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"firmware needs to be dual bank");
|
|
return NULL;
|
|
}
|
|
return g_bytes_ref (fw);
|
|
}
|
|
|
|
static void
|
|
fu_rts54hub_device_init (FuRts54HubDevice *self)
|
|
{
|
|
fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
|
|
}
|
|
|
|
static void
|
|
fu_rts54hub_device_class_init (FuRts54HubDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass);
|
|
klass_device->write_firmware = fu_rts54hub_device_write_firmware;
|
|
klass_device->setup = fu_rts54hub_device_setup;
|
|
klass_device->to_string = fu_rts54hub_device_to_string;
|
|
klass_device->prepare_firmware = fu_rts54hub_device_prepare_firmware;
|
|
klass_usb_device->close = fu_rts54hub_device_close;
|
|
}
|
|
|
|
FuRts54HubDevice *
|
|
fu_rts54hub_device_new (FuUsbDevice *device)
|
|
{
|
|
FuRts54HubDevice *self = g_object_new (FU_TYPE_RTS54HUB_DEVICE, NULL);
|
|
fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device));
|
|
return self;
|
|
}
|