mirror of
https://git.proxmox.com/git/fwupd
synced 2025-07-27 07:52:24 +00:00

Some parsers are ignoring the magic when using _FLAG_IGNORE_CHECKSUM (which is wrong; fuzzers have no problem with enforcing a static prefix) and other either disregard the offset or check the magic in an unsafe way. Also, use FWUPD_ERROR_INVALID_FILE consistently for magic failure. Add a vfunc, and move all the clever code into one place.
1577 lines
47 KiB
C
1577 lines
47 KiB
C
/*
|
|
* Copyright (C) 2022 Gaël PORTAY <gael.portay@collabora.com>
|
|
* Copyright (C) 2021 Ricardo Cañuelo <ricardo.canuelo@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <gusb.h>
|
|
#include <string.h>
|
|
|
|
#include "fu-genesys-common.h"
|
|
#include "fu-genesys-scaler-device.h"
|
|
#include "fu-genesys-usbhub-device.h"
|
|
#include "fu-genesys-usbhub-firmware.h"
|
|
|
|
/**
|
|
* FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER:
|
|
*
|
|
* Device has a MStar scaler attached via I2C.
|
|
*
|
|
* Since 1.7.6
|
|
*/
|
|
#define FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER (1 << 0)
|
|
/**
|
|
* FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY:
|
|
*
|
|
* Device has a public-key appended to firmware.
|
|
*
|
|
* Since 1.8.0
|
|
*/
|
|
#define FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY (1 << 1)
|
|
|
|
#define GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_3_0 0x84
|
|
#define GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_3_0 0x85
|
|
#define GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_2_0 0x81
|
|
#define GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_2_0 0x82
|
|
#define GENESYS_USBHUB_FW_INFO_DESC_IDX 0x83
|
|
#define GENESYS_USBHUB_VENDOR_SUPPORT_DESC_IDX 0x86
|
|
|
|
#define GENESYS_USBHUB_GL_HUB_VERIFY 0x71
|
|
#define GENESYS_USBHUB_GL_HUB_SWITCH 0x81
|
|
#define GENESYS_USBHUB_GL_HUB_READ 0x82
|
|
#define GENESYS_USBHUB_GL_HUB_WRITE 0x83
|
|
|
|
#define GENESYS_USBHUB_ENCRYPT_REGION_START 0x01
|
|
#define GENESYS_USBHUB_ENCRYPT_REGION_END 0x15
|
|
|
|
#define GL3523_PUBLIC_KEY_LEN 0x212
|
|
#define GL3523_SIG_LEN 0x100
|
|
|
|
#define GENESYS_USBHUB_USB_TIMEOUT 5000 /* ms */
|
|
#define GENESYS_USBHUB_FLASH_WRITE_TIMEOUT 500 /* ms */
|
|
|
|
typedef enum {
|
|
TOOL_STRING_VERSION_9BYTE_DYNAMIC,
|
|
TOOL_STRING_VERSION_BONDING,
|
|
TOOL_STRING_VERSION_BONDING_QC,
|
|
TOOL_STRING_VERSION_VENDOR_SUPPORT,
|
|
TOOL_STRING_VERSION_MULTI_TOKEN,
|
|
TOOL_STRING_VERSION_2ND_DYNAMIC,
|
|
TOOL_STRING_VERSION_RESERVED,
|
|
TOOL_STRING_VERSION_13BYTE_DYNAMIC,
|
|
} FuGenesysToolStringVersion;
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 running_mode; /* 'M' for mask code, the others for bank code */
|
|
|
|
guint8 ss_port_number; /* super-speed port number */
|
|
guint8 hs_port_number; /* high-speed port number */
|
|
|
|
guint8 ss_connection_status; /* bit field. ON = DFP is a super-speed device */
|
|
guint8 hs_connection_status; /* bit field. ON = DFP is a high-speed device */
|
|
guint8 fs_connection_status; /* bit field. ON = DFP is a full-speed device */
|
|
guint8 ls_connection_status; /* bit field. ON = DFP is a low-speed device */
|
|
|
|
guint8 charging; /* bit field. ON = DFP is a charging port */
|
|
guint8 non_removable_port_status; /* bit field. ON = DFP is a non-removable port */
|
|
|
|
/*
|
|
* Bonding reports Hardware register status for GL3523:
|
|
* 2 / 4 ports : 1 means 4 ports, 0 means 2 ports
|
|
* MTT / STT : 1 means Multi Token Transfer, 0 means Single TT
|
|
* Type - C : 1 means disable, 0 means enable
|
|
* QC : 1 means disable, 0 means enable
|
|
* Flash dump location : 1 means 32KB offset bank 1, 0 means 0 offset bank 0.
|
|
*
|
|
* Tool string Version 1:
|
|
* Bit3 : Flash dump location
|
|
* BIT2 : Type - C
|
|
* BIT1 : MTT / STT
|
|
* BIT0 : 2 / 4 ports
|
|
*
|
|
* Tool string Version 2 or newer :
|
|
* Bit4 : Flash dump location
|
|
* BIT3 : Type - C
|
|
* BIT2 : MTT / STT
|
|
* BIT1 : 2 / 4 ports
|
|
* BIT0 : QC
|
|
*
|
|
* Default use '0'~'F', plus Bit4 may over value, should extract that.
|
|
*
|
|
* Bonding for GL3590:
|
|
* Bit7 : Flash dump location, 0 means bank 0, 1 means bank 1.
|
|
*/
|
|
guint8 bonding;
|
|
|
|
guint8 reserved[22];
|
|
} FuGenesysDynamicToolString;
|
|
|
|
typedef enum {
|
|
BANK_MASK_CODE,
|
|
BANK_FIRST,
|
|
BANK_SECOND,
|
|
} FuGenesysRunningBank;
|
|
|
|
#define GL3523_BONDING_VALID_BIT 0x0F
|
|
#define GL3590_BONDING_VALID_BIT 0x7F
|
|
|
|
#define GL3523_BONDING_FLASH_DUMP_LOCATION_BIT 1 << 4
|
|
#define GL3590_BONDING_FLASH_DUMP_LOCATION_BIT 1 << 7
|
|
|
|
typedef enum {
|
|
ISP_EXIT,
|
|
ISP_ENTER,
|
|
} FuGenesysIspMode;
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 tool_version[6]; /* ISP tool defined by itself */
|
|
guint8 address_mode;
|
|
guint8 build_fw_time[12]; /* YYYYMMDDhhmm */
|
|
guint8 update_fw_time[12]; /* YYYYMMDDhhmm */
|
|
} FuGenesysFirmwareInfoToolString;
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 version[2];
|
|
guint8 supports[29];
|
|
} FuGenesysVendorSupportToolString;
|
|
|
|
typedef struct {
|
|
guint8 req_switch;
|
|
guint8 req_read;
|
|
guint8 req_write;
|
|
} FuGenesysVendorCommandSetting;
|
|
|
|
struct _FuGenesysUsbhubDevice {
|
|
FuUsbDevice parent_instance;
|
|
FuGenesysStaticToolString static_ts;
|
|
FuGenesysDynamicToolString dynamic_ts;
|
|
FuGenesysFirmwareInfoToolString fwinfo_ts;
|
|
FuGenesysVendorSupportToolString vs_ts;
|
|
FuGenesysVendorCommandSetting vcs;
|
|
FuGenesysChip chip;
|
|
guint32 running_bank;
|
|
guint8 bonding;
|
|
gboolean flash_dump_location_bit;
|
|
guint32 flash_erase_delay;
|
|
guint32 flash_write_delay;
|
|
guint32 flash_block_size;
|
|
guint32 flash_sector_size;
|
|
guint32 flash_rw_size;
|
|
|
|
guint32 fw_bank_addr[2];
|
|
guint16 fw_bank_vers[2];
|
|
guint32 code_size; /* 0: get from device */
|
|
guint32 fw_data_total_count;
|
|
guint32 extend_size;
|
|
gboolean read_first_bank;
|
|
gboolean write_recovery_bank;
|
|
|
|
FuGenesysPublicKey public_key;
|
|
FuCfiDevice *cfi_device;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuGenesysUsbhubDevice, fu_genesys_usbhub_device, FU_TYPE_USB_DEVICE)
|
|
|
|
#if G_USB_CHECK_VERSION(0, 3, 8)
|
|
static gboolean
|
|
fu_genesys_usbhub_device_mstar_scaler_setup(FuGenesysUsbhubDevice *self, GError **error)
|
|
{
|
|
FuContext *ctx = fu_device_get_context(FU_DEVICE(self));
|
|
g_autoptr(FuGenesysScalerDevice) scaler_device = fu_genesys_scaler_device_new(ctx);
|
|
|
|
fu_device_add_child(FU_DEVICE(self), FU_DEVICE(scaler_device));
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_read_flash(FuGenesysUsbhubDevice *self,
|
|
guint start_addr,
|
|
guint8 *buf,
|
|
guint bufsz,
|
|
FuProgress *progress,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
chunks = fu_chunk_array_mutable_new(buf, bufsz, start_addr, 0x0, self->flash_rw_size);
|
|
if (progress != NULL) {
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_set_steps(progress, chunks->len);
|
|
}
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index(chunks, i);
|
|
|
|
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,
|
|
self->vcs.req_read,
|
|
(fu_chunk_get_address(chk) & 0x0f0000) >>
|
|
4, /* value */
|
|
fu_chunk_get_address(chk) & 0xffff, /* idx */
|
|
fu_chunk_get_data_out(chk), /* data */
|
|
fu_chunk_get_data_sz(chk), /* data length */
|
|
NULL, /* actual length */
|
|
GENESYS_USBHUB_USB_TIMEOUT,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"error reading flash at 0x%04x: ",
|
|
fu_chunk_get_address(chk));
|
|
return FALSE;
|
|
}
|
|
if (progress != NULL)
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_reset(FuGenesysUsbhubDevice *self, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
|
|
/* send data to device */
|
|
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,
|
|
self->vcs.req_switch,
|
|
0x0003, /* value */
|
|
0, /* idx */
|
|
NULL, /* data */
|
|
0, /* data length */
|
|
NULL, /* actual length */
|
|
GENESYS_USBHUB_USB_TIMEOUT,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error, "error resetting device: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
#if G_USB_CHECK_VERSION(0, 3, 8)
|
|
static FuCfiDevice *
|
|
fu_genesys_usbhub_device_cfi_setup(FuGenesysUsbhubDevice *self, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
const guint8 rdid_dummy_addr[] = {0x01, 0x02};
|
|
const guint8 rdid_cmd[] = {0x9f, 0x90, 0xAB, 0x1D, 0x15, 0x4D, 0x4B};
|
|
|
|
for (guint8 i = 0; i < G_N_ELEMENTS(rdid_cmd); i++) {
|
|
for (guint8 j = 0; j < G_N_ELEMENTS(rdid_dummy_addr); j++) {
|
|
guint16 val = ((guint16)rdid_cmd[i] << 8) | rdid_dummy_addr[j];
|
|
guint8 buf[2 * 3] = {0}; /* 2 x 3-bytes JEDEC-ID-bytes */
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(FuCfiDevice) cfi_device = NULL;
|
|
g_autofree gchar *flash_id = NULL;
|
|
|
|
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,
|
|
self->vcs.req_read,
|
|
val, /* value */
|
|
0, /* idx */
|
|
buf, /* data */
|
|
sizeof(buf), /* data length */
|
|
NULL, /* actual length */
|
|
GENESYS_USBHUB_USB_TIMEOUT,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error, "error reading flash chip: ");
|
|
return NULL;
|
|
}
|
|
|
|
flash_id = g_strdup_printf("%02X%02X%02X", buf[0], buf[1], buf[2]);
|
|
cfi_device =
|
|
fu_cfi_device_new(fu_device_get_context(FU_DEVICE(self)), flash_id);
|
|
if (cfi_device == NULL)
|
|
continue;
|
|
|
|
if (!fu_device_setup(FU_DEVICE(cfi_device), &error_local)) {
|
|
g_debug("ignoring %s: %s", flash_id, error_local->message);
|
|
continue;
|
|
}
|
|
|
|
if (fu_device_get_name(FU_DEVICE(cfi_device)) == NULL)
|
|
continue;
|
|
|
|
if (g_getenv("FWUPD_GENESYS_USBHUB_VERBOSE") != NULL) {
|
|
guint len;
|
|
|
|
/*
|
|
* The USB vendor command loops over the JEDEC-ID-bytes.
|
|
*
|
|
* Therefore, the CFI is 3-bytes long if the first 3-bytes are
|
|
* identical to the last 3-bytes.
|
|
*/
|
|
if (buf[0] == buf[3] && buf[1] == buf[4] && buf[2] == buf[5])
|
|
len = 3;
|
|
else
|
|
len = 2;
|
|
|
|
fu_dump_raw(G_LOG_DOMAIN, "Flash ID", buf, len);
|
|
g_debug("CFI: %s", fu_device_get_name(FU_DEVICE(cfi_device)));
|
|
}
|
|
|
|
return g_steal_pointer(&cfi_device);
|
|
}
|
|
}
|
|
|
|
/* failure */
|
|
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no CFI device found");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_wait_flash_status_register_cb(FuDevice *device,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
|
guint8 status = 0;
|
|
FuGenesysWaitFlashRegisterHelper *helper = user_data;
|
|
|
|
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,
|
|
self->vcs.req_read,
|
|
helper->reg << 8 | 0x02, /* value */
|
|
0, /* idx */
|
|
&status, /* data */
|
|
1, /* data length */
|
|
NULL, /* actual length */
|
|
GENESYS_USBHUB_USB_TIMEOUT,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"error getting flash status register (0x%02x): ",
|
|
helper->reg);
|
|
return FALSE;
|
|
}
|
|
if (status != helper->expected_val) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"wrong value in flash status register");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_set_isp_mode(FuGenesysUsbhubDevice *self,
|
|
FuGenesysIspMode mode,
|
|
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,
|
|
self->vcs.req_switch,
|
|
mode, /* value */
|
|
0, /* idx */
|
|
NULL, /* data */
|
|
0, /* data length */
|
|
NULL, /* actual length */
|
|
GENESYS_USBHUB_USB_TIMEOUT,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"error setting isp mode - "
|
|
"control transfer error (reg 0x%02x) ",
|
|
self->vcs.req_switch);
|
|
return FALSE;
|
|
}
|
|
|
|
if (mode == ISP_ENTER) {
|
|
FuGenesysWaitFlashRegisterHelper helper = {.reg = 5, .expected_val = 0};
|
|
|
|
/* 150ms */
|
|
if (!fu_device_retry(FU_DEVICE(self),
|
|
fu_genesys_usbhub_device_wait_flash_status_register_cb,
|
|
5,
|
|
&helper,
|
|
error)) {
|
|
g_prefix_error(error, "error setting isp mode: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_authentication_request(FuGenesysUsbhubDevice *self,
|
|
guint8 offset_start,
|
|
guint8 offset_end,
|
|
guint8 data_check,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
guint8 buf = 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,
|
|
GENESYS_USBHUB_GL_HUB_VERIFY,
|
|
(offset_end << 8) | offset_start, /* value */
|
|
0, /* idx */
|
|
&buf, /* data */
|
|
1, /* data length */
|
|
NULL, /* actual length */
|
|
GENESYS_USBHUB_USB_TIMEOUT,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"control transfer error (req: 0x%0x): ",
|
|
(guint)GENESYS_USBHUB_GL_HUB_VERIFY);
|
|
return FALSE;
|
|
}
|
|
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,
|
|
GENESYS_USBHUB_GL_HUB_VERIFY,
|
|
(offset_end << 8) | offset_start, /* value */
|
|
1 | (data_check << 8), /* idx */
|
|
&buf, /* data */
|
|
1, /* data length */
|
|
NULL, /* actual length */
|
|
GENESYS_USBHUB_USB_TIMEOUT,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"control transfer error (req: 0x%0x): ",
|
|
(guint)GENESYS_USBHUB_GL_HUB_VERIFY);
|
|
return FALSE;
|
|
}
|
|
if (buf != 1) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_AUTH_FAILED,
|
|
"device authentication failed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_authenticate(FuGenesysUsbhubDevice *self, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
guint8 low_byte;
|
|
guint8 high_byte;
|
|
guint8 temp_byte;
|
|
guint8 offset_start;
|
|
guint8 offset_end;
|
|
guint8 *fwinfo = (guint8 *)&self->fwinfo_ts;
|
|
|
|
if (self->vcs.req_switch == GENESYS_USBHUB_GL_HUB_SWITCH) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"device authentication not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
low_byte = g_usb_device_get_release(usb_device) & 0xff;
|
|
high_byte = (g_usb_device_get_release(usb_device) & 0xff00) >> 8;
|
|
temp_byte = low_byte ^ high_byte;
|
|
|
|
offset_start = g_random_int_range(GENESYS_USBHUB_ENCRYPT_REGION_START,
|
|
GENESYS_USBHUB_ENCRYPT_REGION_END - 1);
|
|
offset_end = g_random_int_range(offset_start + 1, GENESYS_USBHUB_ENCRYPT_REGION_END);
|
|
for (guint8 i = offset_start; i <= offset_end; i++) {
|
|
temp_byte ^= fwinfo[i];
|
|
}
|
|
if (!fu_genesys_usbhub_device_authentication_request(self,
|
|
offset_start,
|
|
offset_end,
|
|
temp_byte,
|
|
error)) {
|
|
g_prefix_error(error, "error authenticating device: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
#if G_USB_CHECK_VERSION(0, 3, 8)
|
|
static gboolean
|
|
fu_genesys_usbhub_device_get_descriptor_data(GBytes *desc_bytes,
|
|
guint8 *dst,
|
|
guint dst_size,
|
|
GError **error)
|
|
{
|
|
gsize bufsz = 0;
|
|
const guint8 *buf = g_bytes_get_data(desc_bytes, &bufsz);
|
|
|
|
if (bufsz <= 2) {
|
|
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "data is too small");
|
|
return FALSE;
|
|
}
|
|
|
|
/* discard first 2 bytes (desc. length and type) */
|
|
buf += 2;
|
|
bufsz -= 2;
|
|
for (gsize i = 0, j = 0; i < bufsz && j < dst_size; i += 2, j++)
|
|
dst[j] = buf[i];
|
|
|
|
/* legacy hub replies "USB2.0 Hub" or "USB3.0 Hub" */
|
|
if (memcmp(dst, "USB", 3) == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"tool string unsupported");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_check_fw_signature(FuGenesysUsbhubDevice *self,
|
|
int bank_num,
|
|
GError **error)
|
|
{
|
|
guint8 sig[GENESYS_USBHUB_FW_SIG_LEN] = {0};
|
|
g_return_val_if_fail(bank_num < 2, FALSE);
|
|
|
|
if (!fu_genesys_usbhub_device_read_flash(self,
|
|
self->fw_bank_addr[bank_num] +
|
|
GENESYS_USBHUB_FW_SIG_OFFSET,
|
|
sig,
|
|
GENESYS_USBHUB_FW_SIG_LEN,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"error getting fw signature (bank %d) from device: ",
|
|
bank_num);
|
|
return FALSE;
|
|
}
|
|
if (memcmp(sig, GENESYS_USBHUB_FW_SIG_TEXT_HUB, GENESYS_USBHUB_FW_SIG_LEN) != 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_SIGNATURE_INVALID,
|
|
"wrong firmware signature");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/* read the code size from the firmware stored in the device */
|
|
static gboolean
|
|
fu_genesys_usbhub_device_get_code_size(FuGenesysUsbhubDevice *self, int bank_num, GError **error)
|
|
{
|
|
guint8 kbs = 0;
|
|
g_return_val_if_fail(bank_num < 2, FALSE);
|
|
|
|
if (!fu_genesys_usbhub_device_check_fw_signature(self, bank_num, error))
|
|
return FALSE;
|
|
|
|
/* get code size from device */
|
|
if (!fu_genesys_usbhub_device_read_flash(self,
|
|
self->fw_bank_addr[bank_num] +
|
|
GENESYS_USBHUB_CODE_SIZE_OFFSET,
|
|
&kbs,
|
|
1,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error, "error getting fw size from device: ");
|
|
return FALSE;
|
|
}
|
|
self->code_size = 1024 * kbs;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
fu_genesys_tsdigit_value(gchar c)
|
|
{
|
|
if (c >= 'A' && c <= 'Z')
|
|
return c - 'A' + 10;
|
|
if (c >= 'a' && c <= 'z')
|
|
return c - 'a' + 10;
|
|
return g_ascii_digit_value(c);
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_get_bonding_and_flash_dump_location_bit(FuGenesysUsbhubDevice *self,
|
|
GError **error)
|
|
{
|
|
gint bonding;
|
|
|
|
/* bonding is not supported */
|
|
if (fu_genesys_tsdigit_value(self->static_ts.tool_string_version) <
|
|
TOOL_STRING_VERSION_BONDING ||
|
|
self->dynamic_ts.bonding == 0) {
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
if (self->chip.model == ISP_MODEL_HUB_GL3590) {
|
|
self->bonding = (self->dynamic_ts.bonding & GL3590_BONDING_VALID_BIT);
|
|
self->flash_dump_location_bit =
|
|
self->dynamic_ts.bonding & GL3590_BONDING_FLASH_DUMP_LOCATION_BIT;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
bonding = fu_genesys_tsdigit_value((gchar)self->dynamic_ts.bonding);
|
|
if (bonding == -1) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"wrong bonding value 0x%02x",
|
|
self->dynamic_ts.bonding);
|
|
return FALSE;
|
|
}
|
|
|
|
if (fu_genesys_tsdigit_value(self->static_ts.tool_string_version) <
|
|
TOOL_STRING_VERSION_BONDING_QC)
|
|
bonding <<= 1;
|
|
self->bonding = (bonding & GL3523_BONDING_VALID_BIT);
|
|
self->flash_dump_location_bit = bonding & GL3523_BONDING_FLASH_DUMP_LOCATION_BIT;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_detach(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
if (fu_device_has_private_flag(device, FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY)) {
|
|
if (!fu_genesys_usbhub_device_authenticate(self, error))
|
|
return FALSE;
|
|
}
|
|
if (!fu_genesys_usbhub_device_set_isp_mode(self, ISP_ENTER, error))
|
|
return FALSE;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_attach(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
if (!fu_genesys_usbhub_device_reset(self, error))
|
|
return FALSE;
|
|
|
|
/* success */
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
return TRUE;
|
|
}
|
|
|
|
static GBytes *
|
|
fu_genesys_usbhub_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
gsize size = fu_cfi_device_get_size(self->cfi_device);
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
g_autofree guint8 *buf = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "detach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 99, NULL);
|
|
|
|
/* require detach -> attach */
|
|
locker = fu_device_locker_new_full(device,
|
|
(FuDeviceLockerFunc)fu_device_detach,
|
|
(FuDeviceLockerFunc)fu_device_attach,
|
|
error);
|
|
if (locker == NULL)
|
|
return NULL;
|
|
fu_progress_step_done(progress);
|
|
|
|
buf = g_malloc0(size);
|
|
if (!fu_genesys_usbhub_device_read_flash(self,
|
|
0,
|
|
buf,
|
|
size,
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return NULL;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* success */
|
|
return g_bytes_new_take(g_steal_pointer(&buf), size);
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_setup(FuDevice *device, GError **error)
|
|
{
|
|
#if G_USB_CHECK_VERSION(0, 3, 8)
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
|
gsize bufsz;
|
|
guint32 block_size;
|
|
guint32 sector_size;
|
|
guint64 revision_tmp = 0;
|
|
guint16 version_raw;
|
|
guint8 static_idx = 0;
|
|
guint8 dynamic_idx = 0;
|
|
gchar rev[3] = {0};
|
|
gint tool_string_version = 0;
|
|
g_autoptr(FuFirmware) firmware = NULL;
|
|
g_autoptr(GBytes) static_buf = NULL;
|
|
g_autoptr(GBytes) dynamic_buf = NULL;
|
|
g_autoptr(GBytes) fw_buf = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
g_autofree guint8 *buf = NULL;
|
|
g_autofree gchar *ic_type = NULL;
|
|
|
|
/* FuUsbDevice->setup */
|
|
if (!FU_DEVICE_CLASS(fu_genesys_usbhub_device_parent_class)->setup(device, error)) {
|
|
g_prefix_error(error, "error setuping device: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* [DEBUG] - additional info from device:
|
|
* release version: g_usb_device_get_release(usb_device)
|
|
*/
|
|
|
|
/* read standard string descriptors */
|
|
if (g_usb_device_get_spec(usb_device) >= 0x300) {
|
|
static_idx = GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_3_0;
|
|
dynamic_idx = GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_3_0;
|
|
} else {
|
|
static_idx = GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_2_0;
|
|
dynamic_idx = GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_2_0;
|
|
}
|
|
|
|
/*
|
|
* Read/parse vendor-specific string descriptors and use that
|
|
* data to setup device attributes.
|
|
*/
|
|
static_buf =
|
|
g_usb_device_get_string_descriptor_bytes_full(usb_device,
|
|
static_idx,
|
|
G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES,
|
|
64,
|
|
error);
|
|
if (static_buf == NULL) {
|
|
g_prefix_error(error, "failed to get static tool info from device: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_genesys_usbhub_device_get_descriptor_data(static_buf,
|
|
(guint8 *)&self->static_ts,
|
|
sizeof(FuGenesysStaticToolString),
|
|
error)) {
|
|
g_prefix_error(error, "failed to get static tool info from device: ");
|
|
return FALSE;
|
|
}
|
|
if (g_getenv("FWUPD_GENESYS_USBHUB_VERBOSE") != NULL) {
|
|
fu_dump_raw(G_LOG_DOMAIN,
|
|
"Static info",
|
|
(guint8 *)&self->static_ts,
|
|
sizeof(FuGenesysStaticToolString));
|
|
}
|
|
|
|
if (memcmp(self->static_ts.mask_project_ic_type, "3521", 4) == 0) {
|
|
self->chip.model = ISP_MODEL_HUB_GL3521;
|
|
} else if (memcmp(self->static_ts.mask_project_ic_type, "3523", 4) == 0) {
|
|
self->chip.model = ISP_MODEL_HUB_GL3523;
|
|
} else if (memcmp(self->static_ts.mask_project_ic_type, "3590", 4) == 0) {
|
|
self->chip.model = ISP_MODEL_HUB_GL3590;
|
|
} else {
|
|
ic_type = fu_strsafe((const gchar *)&self->static_ts.mask_project_ic_type,
|
|
sizeof(self->static_ts.mask_project_ic_type));
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"IC type %s not supported",
|
|
ic_type);
|
|
return FALSE;
|
|
}
|
|
memcpy(rev, &self->static_ts.mask_project_ic_type[4], 2);
|
|
|
|
if (!fu_strtoull(rev, &revision_tmp, 0, G_MAXINT32, error)) {
|
|
g_prefix_error(error, "failed to parse %s: ", rev);
|
|
return FALSE;
|
|
}
|
|
self->chip.revision = revision_tmp;
|
|
|
|
dynamic_buf =
|
|
g_usb_device_get_string_descriptor_bytes_full(usb_device,
|
|
dynamic_idx,
|
|
G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES,
|
|
64,
|
|
error);
|
|
if (dynamic_buf == NULL) {
|
|
g_prefix_error(error, "failed to get dynamic tool info from device: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_genesys_usbhub_device_get_descriptor_data(dynamic_buf,
|
|
(guint8 *)&self->dynamic_ts,
|
|
sizeof(FuGenesysDynamicToolString),
|
|
error)) {
|
|
g_prefix_error(error, "failed to get dynamic tool info from device: ");
|
|
return FALSE;
|
|
}
|
|
if (g_getenv("FWUPD_GENESYS_USBHUB_VERBOSE") != NULL) {
|
|
fu_dump_raw(G_LOG_DOMAIN,
|
|
"Dynamic info",
|
|
(guint8 *)&self->dynamic_ts,
|
|
sizeof(FuGenesysDynamicToolString));
|
|
}
|
|
|
|
fw_buf =
|
|
g_usb_device_get_string_descriptor_bytes_full(usb_device,
|
|
GENESYS_USBHUB_FW_INFO_DESC_IDX,
|
|
G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES,
|
|
64,
|
|
error);
|
|
if (fw_buf == NULL) {
|
|
g_prefix_error(error, "failed to get firmware info from device: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_genesys_usbhub_device_get_descriptor_data(fw_buf,
|
|
(guint8 *)&self->fwinfo_ts,
|
|
sizeof(FuGenesysFirmwareInfoToolString),
|
|
error)) {
|
|
g_prefix_error(error, "failed to get firmware info from device: ");
|
|
return FALSE;
|
|
}
|
|
if (g_getenv("FWUPD_GENESYS_USBHUB_VERBOSE") != NULL) {
|
|
fu_dump_raw(G_LOG_DOMAIN,
|
|
"Fw info",
|
|
(guint8 *)&self->fwinfo_ts,
|
|
sizeof(FuGenesysFirmwareInfoToolString));
|
|
}
|
|
|
|
tool_string_version = fu_genesys_tsdigit_value(self->static_ts.tool_string_version);
|
|
if (tool_string_version >= TOOL_STRING_VERSION_VENDOR_SUPPORT) {
|
|
g_autoptr(GBytes) vendor_buf = g_usb_device_get_string_descriptor_bytes_full(
|
|
usb_device,
|
|
GENESYS_USBHUB_VENDOR_SUPPORT_DESC_IDX,
|
|
G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES,
|
|
64,
|
|
error);
|
|
if (vendor_buf == NULL) {
|
|
g_prefix_error(error, "failed to get vendor support info from device: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_genesys_usbhub_device_get_descriptor_data(
|
|
vendor_buf,
|
|
(guint8 *)&self->vs_ts,
|
|
sizeof(FuGenesysVendorSupportToolString),
|
|
error)) {
|
|
g_prefix_error(error, "failed to get vendor support info from device: ");
|
|
return FALSE;
|
|
}
|
|
if (g_getenv("FWUPD_GENESYS_USBHUB_VERBOSE") != NULL) {
|
|
fu_dump_raw(G_LOG_DOMAIN,
|
|
"Vendor support",
|
|
(guint8 *)&self->vs_ts,
|
|
sizeof(FuGenesysVendorSupportToolString));
|
|
}
|
|
}
|
|
|
|
if (fu_device_has_private_flag(device, FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY)) {
|
|
if (!fu_genesys_usbhub_device_authenticate(self, error))
|
|
return FALSE;
|
|
}
|
|
if (!fu_genesys_usbhub_device_set_isp_mode(self, ISP_ENTER, error))
|
|
return FALSE;
|
|
/* setup cfi device */
|
|
self->cfi_device = fu_genesys_usbhub_device_cfi_setup(self, error);
|
|
if (self->cfi_device == NULL)
|
|
return FALSE;
|
|
block_size = fu_cfi_device_get_block_size(self->cfi_device);
|
|
if (block_size != 0)
|
|
self->flash_block_size = block_size;
|
|
sector_size = fu_cfi_device_get_sector_size(self->cfi_device);
|
|
if (sector_size != 0)
|
|
self->flash_sector_size = sector_size;
|
|
|
|
/* get bonding and flash dump location bit */
|
|
if (!fu_genesys_usbhub_device_get_bonding_and_flash_dump_location_bit(self, error))
|
|
return FALSE;
|
|
fu_device_add_instance_u8(device, "BONDING", self->bonding);
|
|
|
|
if (self->dynamic_ts.running_mode == 'M') {
|
|
self->running_bank = BANK_MASK_CODE;
|
|
} else if (self->flash_dump_location_bit) {
|
|
self->running_bank = BANK_SECOND;
|
|
} else {
|
|
self->running_bank = BANK_FIRST;
|
|
}
|
|
|
|
/* setup firmware parameters */
|
|
switch (self->chip.model) {
|
|
case ISP_MODEL_HUB_GL3521:
|
|
self->code_size = 0x5000;
|
|
self->fw_bank_addr[0] = 0x0000;
|
|
self->fw_data_total_count = 0x5000;
|
|
break;
|
|
case ISP_MODEL_HUB_GL3523: {
|
|
self->fw_bank_addr[0] = 0x0000;
|
|
self->fw_bank_addr[1] = 0x8000;
|
|
|
|
if (fu_device_has_private_flag(device, FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY))
|
|
self->extend_size = GL3523_PUBLIC_KEY_LEN + GL3523_SIG_LEN;
|
|
|
|
if (self->chip.revision == 50) {
|
|
self->fw_data_total_count = 0x8000;
|
|
if (!fu_genesys_usbhub_device_get_code_size(self, 0, error))
|
|
return FALSE;
|
|
} else {
|
|
self->fw_data_total_count = 0x6000;
|
|
self->code_size = self->fw_data_total_count;
|
|
}
|
|
break;
|
|
}
|
|
case ISP_MODEL_HUB_GL3590:
|
|
if (!fu_genesys_usbhub_device_get_code_size(self, 0, error))
|
|
return FALSE;
|
|
self->fw_bank_addr[0] = 0x0000;
|
|
self->fw_bank_addr[1] = 0x10000;
|
|
self->fw_data_total_count = 0x10000;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
fu_device_set_firmware_size_max(device, self->fw_data_total_count + self->extend_size);
|
|
|
|
/* verify firmware integrity */
|
|
bufsz = self->fw_data_total_count + self->extend_size;
|
|
buf = g_malloc0(bufsz);
|
|
if (!fu_genesys_usbhub_device_read_flash(self,
|
|
self->fw_bank_addr[0],
|
|
buf,
|
|
bufsz,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
blob = g_bytes_new_take(g_steal_pointer(&buf), bufsz);
|
|
firmware = fu_genesys_usbhub_firmware_new();
|
|
if (!fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, &error_local)) {
|
|
g_debug("ignoring firmware: %s", error_local->message);
|
|
self->fw_bank_vers[0] = 0;
|
|
} else {
|
|
version_raw = fu_firmware_get_version_raw(firmware);
|
|
if (version_raw != 0xffff)
|
|
self->fw_bank_vers[0] = version_raw;
|
|
}
|
|
|
|
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) {
|
|
gsize address = self->fw_bank_addr[0];
|
|
gsize bufsz_dual;
|
|
g_autoptr(FuFirmware) firmware_dual = NULL;
|
|
g_autoptr(GError) error_local_dual = NULL;
|
|
g_autoptr(GBytes) blob_dual = NULL;
|
|
g_autofree guint8 *buf_dual = NULL;
|
|
|
|
/* verify dual firmware integrity */
|
|
bufsz_dual = self->fw_data_total_count + self->extend_size;
|
|
buf_dual = g_malloc0(bufsz_dual);
|
|
if (!fu_genesys_usbhub_device_read_flash(self,
|
|
self->fw_bank_addr[1],
|
|
buf_dual,
|
|
bufsz_dual,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
blob_dual = g_bytes_new_take(g_steal_pointer(&buf_dual), bufsz_dual);
|
|
firmware_dual = fu_genesys_usbhub_firmware_new();
|
|
if (!fu_firmware_parse(firmware_dual,
|
|
blob_dual,
|
|
FWUPD_INSTALL_FLAG_NO_SEARCH,
|
|
&error_local_dual)) {
|
|
g_debug("ignoring recovery firmware: %s", error_local_dual->message);
|
|
self->fw_bank_vers[1] = 0;
|
|
} else {
|
|
version_raw = fu_firmware_get_version_raw(firmware_dual);
|
|
if (version_raw != 0xffff)
|
|
self->fw_bank_vers[1] = version_raw;
|
|
}
|
|
|
|
/* write recovery needed? */
|
|
if (self->fw_bank_vers[0] == 0 && self->fw_bank_vers[1] == 0) {
|
|
/* first bank and recovery are both blanks: write fw on both */
|
|
address = self->fw_bank_addr[1];
|
|
} else if (self->fw_bank_vers[0] > self->fw_bank_vers[1]) {
|
|
/* first bank is more recent than recovery: write fw on recovery first */
|
|
address = self->fw_bank_addr[1];
|
|
} else {
|
|
/* recovery is more recent than first bank: write fw on first bank only */
|
|
address = self->fw_bank_addr[0];
|
|
}
|
|
|
|
self->read_first_bank =
|
|
(self->chip.model == ISP_MODEL_HUB_GL3523) && self->fw_bank_vers[0] != 0;
|
|
self->write_recovery_bank = address == self->fw_bank_addr[1];
|
|
}
|
|
|
|
/* has public key */
|
|
if (fu_device_has_private_flag(device, FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY)) {
|
|
g_autofree gchar *guid = NULL;
|
|
if (!fu_memcpy_safe((guint8 *)&self->public_key,
|
|
sizeof(self->public_key),
|
|
0, /* dst */
|
|
g_bytes_get_data(blob, NULL),
|
|
g_bytes_get_size(blob),
|
|
self->fw_data_total_count, /* src */
|
|
sizeof(self->public_key),
|
|
error))
|
|
return FALSE;
|
|
if (memcmp(&self->public_key.N, "N = ", 4) != 0 &&
|
|
memcmp(&self->public_key.E, "E = ", 4) != 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_SIGNATURE_INVALID,
|
|
"invalid public-key");
|
|
return FALSE;
|
|
}
|
|
guid = fwupd_guid_hash_data((const guint8 *)&self->public_key,
|
|
sizeof(self->public_key),
|
|
FWUPD_GUID_FLAG_NONE);
|
|
fu_device_add_instance_strup(device, "PUBKEY", guid);
|
|
}
|
|
|
|
/* add specific product info */
|
|
ic_type = fu_strsafe((const gchar *)self->static_ts.mask_project_ic_type,
|
|
sizeof(self->static_ts.mask_project_ic_type));
|
|
fu_device_add_instance_str(device, "IC", ic_type);
|
|
|
|
if (self->running_bank != BANK_MASK_CODE) {
|
|
const gchar *vendor = fwupd_device_get_vendor(FWUPD_DEVICE(device));
|
|
guint8 portnum = fu_genesys_tsdigit_value(self->dynamic_ts.ss_port_number) << 4 |
|
|
fu_genesys_tsdigit_value(self->dynamic_ts.hs_port_number);
|
|
g_autofree gchar *guid = NULL;
|
|
|
|
guid = fwupd_guid_hash_data((const guint8 *)&self->vs_ts,
|
|
sizeof(self->vs_ts),
|
|
FWUPD_GUID_FLAG_NONE);
|
|
fu_device_add_instance_strup(device, "VENDOR", vendor);
|
|
fu_device_add_instance_u8(device, "PORTNUM", portnum);
|
|
fu_device_add_instance_strup(device, "VENDORSUP", guid);
|
|
}
|
|
|
|
fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "IC", NULL);
|
|
fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "IC", "BONDING", NULL);
|
|
fu_device_build_instance_id(device,
|
|
NULL,
|
|
"USB",
|
|
"VID",
|
|
"PID",
|
|
"VENDOR",
|
|
"IC",
|
|
"BONDING",
|
|
"PORTNUM",
|
|
"VENDORSUP",
|
|
NULL);
|
|
fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "PUBKEY", NULL);
|
|
|
|
/* have MStar scaler */
|
|
if (fu_device_has_private_flag(device, FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER))
|
|
if (!fu_genesys_usbhub_device_mstar_scaler_setup(self, error))
|
|
return FALSE;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
#else
|
|
/* failure */
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"GUsb version is too old, "
|
|
"fwupd needs to be rebuilt against 0.3.8 or later");
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
fu_genesys_usbhub_device_to_string(FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
fu_string_append_kx(str, idt, "FlashEraseDelay", self->flash_erase_delay);
|
|
fu_string_append_kx(str, idt, "FlashWriteDelay", self->flash_write_delay);
|
|
fu_string_append_kx(str, idt, "FlashBlockSize", self->flash_block_size);
|
|
fu_string_append_kx(str, idt, "FlashSectorSize", self->flash_sector_size);
|
|
fu_string_append_kx(str, idt, "FlashRwSize", self->flash_rw_size);
|
|
fu_string_append_kx(str, idt, "FwBank0Addr", self->fw_bank_addr[0]);
|
|
fu_string_append_kx(str, idt, "FwBank0Vers", self->fw_bank_vers[0]);
|
|
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) {
|
|
fu_string_append_kx(str, idt, "FwBank1Addr", self->fw_bank_addr[1]);
|
|
fu_string_append_kx(str, idt, "FwBank1Vers", self->fw_bank_vers[1]);
|
|
}
|
|
fu_string_append_kx(str, idt, "CodeSize", self->code_size);
|
|
fu_string_append_kx(str, idt, "FwDataTotalCount", self->fw_data_total_count);
|
|
fu_string_append_kx(str, idt, "ExtendSize", self->extend_size);
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_genesys_usbhub_device_prepare_firmware(FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
g_autoptr(FuFirmware) firmware = fu_genesys_usbhub_firmware_new();
|
|
|
|
/* parse firmware */
|
|
if (!fu_firmware_parse(firmware, fw, flags, error))
|
|
return NULL;
|
|
|
|
/* has public-key */
|
|
if (g_bytes_get_size(fw) >= fu_firmware_get_size(firmware) + sizeof(self->public_key)) {
|
|
gsize bufsz = 0;
|
|
const guint8 *buf = g_bytes_get_data(fw, &bufsz);
|
|
|
|
if (g_getenv("FWUPD_GENESYS_USBHUB_VERBOSE") != NULL)
|
|
fu_dump_raw(G_LOG_DOMAIN, "PublicKey", buf, bufsz);
|
|
if (memcmp(buf + fu_firmware_get_size(firmware),
|
|
&self->public_key,
|
|
sizeof(self->public_key)) != 0 &&
|
|
(flags & FWUPD_INSTALL_FLAG_FORCE) == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_SIGNATURE_INVALID,
|
|
"mismatch public-key");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* check size */
|
|
if (g_bytes_get_size(fw) > fu_device_get_firmware_size_max(device)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"firmware too large, got 0x%x, expected <= 0x%x",
|
|
(guint)g_bytes_get_size(fw),
|
|
(guint)fu_device_get_firmware_size_max(device));
|
|
return NULL;
|
|
}
|
|
|
|
/* success */
|
|
return fu_firmware_new_from_bytes(fw);
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_erase_flash(FuGenesysUsbhubDevice *self,
|
|
guint start_addr,
|
|
guint len,
|
|
FuProgress *progress,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
FuGenesysWaitFlashRegisterHelper helper = {.reg = 5, .expected_val = 0};
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
chunks = fu_chunk_array_new(NULL,
|
|
len,
|
|
start_addr,
|
|
self->flash_block_size,
|
|
self->flash_sector_size);
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_set_steps(progress, chunks->len);
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index(chunks, i);
|
|
guint16 sectornum = fu_chunk_get_address(chk) / self->flash_sector_size;
|
|
guint16 blocknum = fu_chunk_get_page(chk);
|
|
guint16 index = (0x01 << 8) | (sectornum << 4) | blocknum;
|
|
|
|
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,
|
|
self->vcs.req_write,
|
|
0x2001, /* value */
|
|
index, /* idx */
|
|
NULL, /* data */
|
|
0, /* data length */
|
|
NULL, /* actual length */
|
|
GENESYS_USBHUB_USB_TIMEOUT,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"error erasing flash at sector 0x%02x in block 0x%02x",
|
|
sectornum,
|
|
blocknum);
|
|
return FALSE;
|
|
}
|
|
|
|
/* 8s */
|
|
if (!fu_device_retry(FU_DEVICE(self),
|
|
fu_genesys_usbhub_device_wait_flash_status_register_cb,
|
|
self->flash_erase_delay / 30,
|
|
&helper,
|
|
error)) {
|
|
g_prefix_error(error, "error erasing flash: ");
|
|
return FALSE;
|
|
}
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_write_flash(FuGenesysUsbhubDevice *self,
|
|
guint start_addr,
|
|
const guint8 *buf,
|
|
guint bufsz,
|
|
FuProgress *progress,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
FuGenesysWaitFlashRegisterHelper helper = {.reg = 5, .expected_val = 0};
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
chunks =
|
|
fu_chunk_array_new(buf, bufsz, start_addr, self->flash_block_size, self->flash_rw_size);
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_set_steps(progress, chunks->len);
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index(chunks, i);
|
|
g_autofree guint8 *chkbuf_mut = NULL;
|
|
|
|
chkbuf_mut =
|
|
fu_memdup_safe(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error);
|
|
if (chkbuf_mut == NULL)
|
|
return FALSE;
|
|
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,
|
|
self->vcs.req_write,
|
|
(fu_chunk_get_page(chk) & 0x000f)
|
|
<< 12, /* value */
|
|
fu_chunk_get_address(chk) & 0x00ffff, /* idx */
|
|
chkbuf_mut, /* data */
|
|
fu_chunk_get_data_sz(chk), /* data length */
|
|
NULL, /* actual length */
|
|
GENESYS_USBHUB_USB_TIMEOUT,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"error writing flash at 0x%02x%04x: ",
|
|
fu_chunk_get_page(chk),
|
|
fu_chunk_get_address(chk));
|
|
return FALSE;
|
|
}
|
|
|
|
/* 5s */
|
|
if (!fu_device_retry(FU_DEVICE(self),
|
|
fu_genesys_usbhub_device_wait_flash_status_register_cb,
|
|
self->flash_write_delay / 30,
|
|
&helper,
|
|
error)) {
|
|
g_prefix_error(error, "error writing flash: ");
|
|
return FALSE;
|
|
}
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_write_recovery(FuGenesysUsbhubDevice *self,
|
|
GBytes *blob,
|
|
FuProgress *progress,
|
|
GError **error)
|
|
{
|
|
gsize bufsz = 0;
|
|
g_autofree guint8 *buf = NULL;
|
|
g_autofree guint8 *buf_verify = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
if (self->read_first_bank)
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 20, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 30, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 20, NULL);
|
|
|
|
/* reuse fw on first bank for GL3523 */
|
|
if (self->read_first_bank) {
|
|
bufsz = self->code_size;
|
|
if (bufsz == 0) {
|
|
g_set_error_literal(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"code size is zero");
|
|
return FALSE;
|
|
}
|
|
|
|
buf = g_malloc0(bufsz);
|
|
if (!fu_genesys_usbhub_device_read_flash(self,
|
|
self->fw_bank_addr[0],
|
|
buf,
|
|
bufsz,
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
} else {
|
|
bufsz = g_bytes_get_size(blob);
|
|
buf = fu_memdup_safe(g_bytes_get_data(blob, NULL), bufsz, error);
|
|
if (buf == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
/* erase */
|
|
if (!fu_genesys_usbhub_device_erase_flash(self,
|
|
self->fw_bank_addr[1],
|
|
bufsz,
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* write */
|
|
if (!fu_genesys_usbhub_device_write_flash(self,
|
|
self->fw_bank_addr[1],
|
|
buf,
|
|
bufsz,
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* verify */
|
|
buf_verify = g_malloc0(bufsz);
|
|
if (!fu_genesys_usbhub_device_read_flash(self,
|
|
self->fw_bank_addr[1],
|
|
buf_verify,
|
|
bufsz,
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
if (!fu_memcmp_safe(buf_verify, bufsz, buf, bufsz, error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
g_autoptr(GBytes) blob = NULL;
|
|
g_autofree guint8 *buf_verify = NULL;
|
|
|
|
blob = fu_firmware_get_bytes(firmware, error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
if (self->write_recovery_bank) {
|
|
if (self->read_first_bank)
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 120, NULL);
|
|
else
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, NULL);
|
|
}
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 30, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 20, NULL);
|
|
|
|
/* write fw to recovery bank first? */
|
|
if (self->write_recovery_bank) {
|
|
if (!fu_genesys_usbhub_device_write_recovery(self,
|
|
blob,
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* write fw to first bank then */
|
|
if (!fu_genesys_usbhub_device_erase_flash(self,
|
|
self->fw_bank_addr[0],
|
|
g_bytes_get_size(blob),
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
if (!fu_genesys_usbhub_device_write_flash(self,
|
|
self->fw_bank_addr[0],
|
|
g_bytes_get_data(blob, NULL),
|
|
g_bytes_get_size(blob),
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* verify */
|
|
buf_verify = g_malloc0(g_bytes_get_size(blob));
|
|
if (!fu_genesys_usbhub_device_read_flash(self,
|
|
self->fw_bank_addr[0],
|
|
buf_verify,
|
|
g_bytes_get_size(blob),
|
|
fu_progress_get_child(progress),
|
|
error))
|
|
return FALSE;
|
|
if (!fu_memcmp_safe(buf_verify,
|
|
g_bytes_get_size(blob),
|
|
g_bytes_get_data(blob, NULL),
|
|
g_bytes_get_size(blob),
|
|
error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_genesys_usbhub_device_set_progress(FuDevice *device, FuProgress *progress)
|
|
{
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
if (self->write_recovery_bank) {
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 30, "write");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 70, "reload");
|
|
} else {
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 15, "write");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 85, "reload");
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
fu_genesys_usbhub_device_set_quirk_kv(FuDevice *device,
|
|
const gchar *key,
|
|
const gchar *value,
|
|
GError **error)
|
|
{
|
|
FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device);
|
|
guint64 tmp;
|
|
|
|
if (g_strcmp0(key, "GenesysUsbhubDeviceTransferSize") == 0) {
|
|
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error))
|
|
return FALSE;
|
|
self->flash_rw_size = tmp;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0(key, "GenesysUsbhubSwitchRequest") == 0) {
|
|
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error))
|
|
return FALSE;
|
|
self->vcs.req_switch = tmp;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0(key, "GenesysUsbhubReadRequest") == 0) {
|
|
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error))
|
|
return FALSE;
|
|
self->vcs.req_read = tmp;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0(key, "GenesysUsbhubWriteRequest") == 0) {
|
|
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error))
|
|
return FALSE;
|
|
self->vcs.req_write = tmp;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/* failure */
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"quirk key not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
fu_genesys_usbhub_device_init(FuGenesysUsbhubDevice *self)
|
|
{
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE);
|
|
fu_device_add_protocol(FU_DEVICE(self), "com.genesys.usbhub");
|
|
fu_device_retry_set_delay(FU_DEVICE(self), 30); /* 30ms */
|
|
fu_device_set_remove_delay(FU_DEVICE(self), 5000); /* 5s */
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER,
|
|
"has-mstar-scaler");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY,
|
|
"has-public-key");
|
|
fu_device_set_install_duration(FU_DEVICE(self), 9); /* 9 s */
|
|
|
|
self->vcs.req_switch = GENESYS_USBHUB_GL_HUB_SWITCH;
|
|
self->vcs.req_read = GENESYS_USBHUB_GL_HUB_READ;
|
|
self->vcs.req_write = GENESYS_USBHUB_GL_HUB_WRITE;
|
|
self->flash_erase_delay = 8000; /* 8s */
|
|
self->flash_write_delay = 500; /* 500ms */
|
|
self->flash_block_size = 0x10000; /* 64KB */
|
|
self->flash_sector_size = 0x1000; /* 4KB */
|
|
self->flash_rw_size = 0x40; /* 64B */
|
|
}
|
|
|
|
static void
|
|
fu_genesys_usbhub_device_class_init(FuGenesysUsbhubDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
klass_device->setup = fu_genesys_usbhub_device_setup;
|
|
klass_device->dump_firmware = fu_genesys_usbhub_device_dump_firmware;
|
|
klass_device->prepare_firmware = fu_genesys_usbhub_device_prepare_firmware;
|
|
klass_device->write_firmware = fu_genesys_usbhub_device_write_firmware;
|
|
klass_device->set_progress = fu_genesys_usbhub_device_set_progress;
|
|
klass_device->detach = fu_genesys_usbhub_device_detach;
|
|
klass_device->attach = fu_genesys_usbhub_device_attach;
|
|
klass_device->to_string = fu_genesys_usbhub_device_to_string;
|
|
klass_device->set_quirk_kv = fu_genesys_usbhub_device_set_quirk_kv;
|
|
}
|