/* * Copyright (C) 2022 Gaël PORTAY * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #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; }