From f04be98153dbfb676b424dbaa209ae7c3fd4013d Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Thu, 24 Oct 2019 13:14:07 +0100 Subject: [PATCH] vli-usbhub: Add support for updating V2 devices --- plugins/vli-usbhub/fu-vli-usbhub-common.c | 21 + plugins/vli-usbhub/fu-vli-usbhub-common.h | 49 ++- plugins/vli-usbhub/fu-vli-usbhub-device.c | 457 ++++++++++++++++++-- plugins/vli-usbhub/fu-vli-usbhub-firmware.c | 38 +- plugins/vli-usbhub/meson.build | 5 +- plugins/vli-usbhub/vli-usbhub-lenovo.quirk | 160 +++++++ plugins/vli-usbhub/vli-usbhub.quirk | 16 + 7 files changed, 686 insertions(+), 60 deletions(-) create mode 100644 plugins/vli-usbhub/vli-usbhub-lenovo.quirk diff --git a/plugins/vli-usbhub/fu-vli-usbhub-common.c b/plugins/vli-usbhub/fu-vli-usbhub-common.c index 7930658e6..44ecb3757 100644 --- a/plugins/vli-usbhub/fu-vli-usbhub-common.c +++ b/plugins/vli-usbhub/fu-vli-usbhub-common.c @@ -67,4 +67,25 @@ void fu_vli_usbhub_header_to_string (FuVliUsbhubHeader *hdr, guint idt, GString *str) { fu_common_string_append_kx (str, idt, "DevId", GUINT16_FROM_BE(hdr->dev_id)); + fu_common_string_append_kx (str, idt, "Variant", hdr->variant); + if (hdr->usb2_fw_sz > 0) { + fu_common_string_append_kx (str, idt, "Usb2FwAddr", + GUINT16_FROM_BE(hdr->usb2_fw_addr)); + fu_common_string_append_kx (str, idt, "Usb2FwSz", + GUINT16_FROM_BE(hdr->usb2_fw_sz)); + } + fu_common_string_append_kx (str, idt, "Usb3FwAddr", + GUINT16_FROM_BE(hdr->usb3_fw_addr)); + fu_common_string_append_kx (str, idt, "Usb3FwSz", + GUINT16_FROM_BE(hdr->usb3_fw_sz)); + if (hdr->prev_ptr != VLI_USBHUB_FLASHMAP_IDX_INVALID) { + fu_common_string_append_kx (str, idt, "PrevPtr", + VLI_USBHUB_FLASHMAP_IDX_TO_ADDR(hdr->prev_ptr)); + } + if (hdr->next_ptr != VLI_USBHUB_FLASHMAP_IDX_INVALID) { + fu_common_string_append_kx (str, idt, "NextPtr", + VLI_USBHUB_FLASHMAP_IDX_TO_ADDR(hdr->next_ptr)); + } + fu_common_string_append_kb (str, idt, "ChecksumOK", + hdr->checksum == fu_vli_usbhub_header_crc8 (hdr)); } diff --git a/plugins/vli-usbhub/fu-vli-usbhub-common.h b/plugins/vli-usbhub/fu-vli-usbhub-common.h index b5732e458..3d21c277a 100644 --- a/plugins/vli-usbhub/fu-vli-usbhub-common.h +++ b/plugins/vli-usbhub/fu-vli-usbhub-common.h @@ -31,22 +31,49 @@ typedef enum { typedef struct __attribute__ ((packed)) { guint16 dev_id; /* 0x00, BE */ - guint8 unknown_02; /* 0x02 */ - guint8 unknown_03; /* 0x03 */ - guint16 u3_addr; /* 0x04 */ - guint16 u3_sz; /* 0x06, BE */ - guint16 u2_addr; /* 0x08 */ - guint16 unknown_0a; /* 0x0a */ - guint8 u3_addr_h; /* 0x0c */ - guint8 unknown_0d[15]; /* 0x0d */ - guint8 prev_ptr; /* 0x1c */ - guint8 next_ptr; /* 0x1d */ - guint8 unknown_1e; /* 0x1e */ + guint8 strapping1; /* 0x02 */ + guint8 strapping2; /* 0x03 */ + guint16 usb3_fw_addr; /* 0x04, BE */ + guint16 usb3_fw_sz; /* 0x06, BE */ + guint16 usb2_fw_addr; /* 0x08, BE */ + guint16 usb2_fw_sz; /* 0x0a, BE */ + guint8 usb3_fw_addr_high; /* 0x0c */ + guint8 unknown_0d[3]; /* 0x0d */ + guint8 usb2_fw_addr_high; /* 0x10 */ + guint8 unknown_11[10]; /* 0x11 */ + guint8 inverse_pe41; /* 0x1b */ + guint8 prev_ptr; /* 0x1c, addr / 0x20 */ + guint8 next_ptr; /* 0x1d, addr / 0x20 */ + guint8 variant; /* 0x1e */ guint8 checksum; /* 0x1f */ } FuVliUsbhubHeader; G_STATIC_ASSERT(sizeof(FuVliUsbhubHeader) == 0x20); +#define FU_VLI_USBHUB_HEADER_STRAPPING1_SELFW1 (1 << 1) +#define FU_VLI_USBHUB_HEADER_STRAPPING1_76PIN (1 << 2) +#define FU_VLI_USBHUB_HEADER_STRAPPING1_B3UP (1 << 3) +#define FU_VLI_USBHUB_HEADER_STRAPPING1_LPC (1 << 4) +#define FU_VLI_USBHUB_HEADER_STRAPPING1_U1U2 (1 << 5) +#define FU_VLI_USBHUB_HEADER_STRAPPING1_BC (1 << 6) +#define FU_VLI_USBHUB_HEADER_STRAPPING1_Q4S (1 << 7) + +#define FU_VLI_USBHUB_HEADER_STRAPPING2_IDXEN (1 << 0) +#define FU_VLI_USBHUB_HEADER_STRAPPING2_FWRTY (1 << 1) +#define FU_VLI_USBHUB_HEADER_STRAPPING2_SELFW2 (1 << 7) + +#define VLI_USBHUB_FLASHMAP_ADDR_TO_IDX(addr) (addr / 0x20) +#define VLI_USBHUB_FLASHMAP_IDX_TO_ADDR(addr) (addr * 0x20) + +#define VLI_USBHUB_FLASHMAP_IDX_HD1 0x00 /* factory firmware */ +#define VLI_USBHUB_FLASHMAP_IDX_HD2 0x80 /* update firmware */ +#define VLI_USBHUB_FLASHMAP_IDX_INVALID 0xff + +#define VLI_USBHUB_FLASHMAP_ADDR_HD1 0x0 +#define VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP 0x1800 +#define VLI_USBHUB_FLASHMAP_ADDR_HD2 0x1000 +#define VLI_USBHUB_FLASHMAP_ADDR_FW 0x2000 + guint8 fu_vli_usbhub_header_crc8 (FuVliUsbhubHeader *hdr); void fu_vli_usbhub_header_to_string (FuVliUsbhubHeader *hdr, guint idt, diff --git a/plugins/vli-usbhub/fu-vli-usbhub-device.c b/plugins/vli-usbhub/fu-vli-usbhub-device.c index ca2169ac3..638c16158 100644 --- a/plugins/vli-usbhub/fu-vli-usbhub-device.c +++ b/plugins/vli-usbhub/fu-vli-usbhub-device.c @@ -25,7 +25,8 @@ struct _FuVliUsbhubDevice FuVliUsbhubDeviceKind kind; gboolean disable_powersave; guint8 update_protocol; - FuVliUsbhubHeader hdr; + FuVliUsbhubHeader hd1_hdr; /* factory */ + FuVliUsbhubHeader hd2_hdr; /* update */ guint32 flash_id; guint8 spi_cmd_read_id; guint8 spi_cmd_read_id_sz; @@ -72,9 +73,11 @@ fu_vli_usbhub_device_to_string (FuDevice *device, guint idt, GString *str) fu_common_string_append_kx (str, idt, "SpiCmdSectorErase", self->spi_cmd_sector_erase); fu_common_string_append_kx (str, idt, "SpiCmdWriteEn", self->spi_cmd_write_en); fu_common_string_append_kx (str, idt, "SpiCmdWriteStatus", self->spi_cmd_write_status); - if (self->update_protocol > 0x2) { - fu_common_string_append_kv (str, idt, "Version2RootHdr", NULL); - fu_vli_usbhub_header_to_string (&self->hdr, idt + 1, str); + if (self->update_protocol >= 0x2) { + fu_common_string_append_kv (str, idt, "H1Hdr@0x0", NULL); + fu_vli_usbhub_header_to_string (&self->hd1_hdr, idt + 1, str); + fu_common_string_append_kv (str, idt, "H2Hdr@0x1000", NULL); + fu_vli_usbhub_header_to_string (&self->hd2_hdr, idt + 1, str); } } @@ -172,6 +175,33 @@ fu_vli_usbhub_device_spi_read_flash_id (FuVliUsbhubDevice *self, GError **error) return TRUE; } +static gboolean +fu_vli_usbhub_device_spi_read_status (FuVliUsbhubDevice *self, guint8 *status, GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); + + /* sanity check */ + if (self->spi_cmd_read_status == 0x0) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "No value for SpiCmdReadStatus"); + 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, + 0xc1, self->spi_cmd_read_status, 0x0000, + status, 0x1, NULL, + FU_VLI_USBHUB_DEVICE_TIMEOUT, + NULL, error)) { + g_prefix_error (error, "failed to read status: "); + return FALSE; + } + return TRUE; +} + static gboolean fu_vli_usbhub_device_spi_read_data (FuVliUsbhubDevice *self, guint32 data_addr, @@ -289,6 +319,35 @@ fu_vli_usbhub_device_spi_erase_chip (FuVliUsbhubDevice *self, GError **error) return TRUE; } +static gboolean +fu_vli_usbhub_device_spi_erase_sector (FuVliUsbhubDevice *self, guint32 data_addr, GError **error) +{ + guint16 index = ((data_addr << 8) & 0xff00) | ((data_addr >> 8) & 0x00ff); + guint16 value = ((data_addr >> 8) & 0xff00) | self->spi_cmd_sector_erase; + GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); + + /* sanity check */ + if (self->spi_cmd_sector_erase == 0x0) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "No value for SpiCmdSectorErase"); + 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, + 0xd4, value, index, + NULL, 0x0, NULL, + FU_VLI_USBHUB_DEVICE_TIMEOUT, + NULL, error)) { + g_prefix_error (error, "failed to erase SPI sector @0x%x: ", data_addr); + return FALSE; + } + return TRUE; +} + static gboolean fu_vli_usbhub_device_spi_write_data (FuVliUsbhubDevice *self, guint32 data_addr, @@ -322,6 +381,116 @@ fu_vli_usbhub_device_spi_write_data (FuVliUsbhubDevice *self, return TRUE; } +static gboolean +fu_vli_usbhub_device_spi_wait_finish (FuVliUsbhubDevice *self, GError **error) +{ + const guint32 rdy_cnt = 2; + guint32 cnt = 0; + + /* sanity check */ + if (self->spi_cmd_read_status == 0x0) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "No value for SpiCmdReadStatus"); + return FALSE; + } + for (guint32 idx = 0; idx < 1000; idx++) { + guint8 status = 0x7f; + + /* must get bit[1:0] == 0 twice in a row for success */ + if (!fu_vli_usbhub_device_spi_read_status (self, &status, error)) + return FALSE; + if ((status & 0x03) == 0x00) { + if (cnt++ >= rdy_cnt) + return TRUE; + } else { + cnt = 0; + } + g_usleep (500 * 1000); + } + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "failed to wait for SPI"); + return FALSE; +} + +static gboolean +fu_vli_usbhub_device_erase_sector (FuVliUsbhubDevice *self, guint32 addr, GError **error) +{ + const guint32 bufsz = 0x1000; + + /* erase sector */ + if (!fu_vli_usbhub_device_spi_write_enable (self, error)) { + g_prefix_error (error, "fu_vli_usbhub_device_spi_write_enable failed: "); + return FALSE; + } + if (!fu_vli_usbhub_device_spi_write_status (self, 0x00, error)) { + g_prefix_error (error, "fu_vli_usbhub_device_spi_write_status failed: "); + return FALSE; + } + if (!fu_vli_usbhub_device_spi_write_enable (self, error)) { + g_prefix_error (error, "fu_vli_usbhub_device_spi_write_enable failed: "); + return FALSE; + } + if (!fu_vli_usbhub_device_spi_erase_sector (self, addr, error)) { + g_prefix_error (error, "fu_vli_usbhub_device_spi_erase_sector failed"); + return FALSE; + } + if (!fu_vli_usbhub_device_spi_wait_finish (self, error)) { + g_prefix_error (error, "fu_vli_usbhub_device_spi_wait_finish failed"); + return FALSE; + } + + /* verify it really was blanked */ + for (guint32 offset = 0; offset < bufsz; offset += FU_VLI_USBHUB_TXSIZE) { + guint8 buf[FU_VLI_USBHUB_TXSIZE] = { 0x0 }; + if (!fu_vli_usbhub_device_spi_read_data (self, + addr + offset, + buf, sizeof (buf), + error)) { + g_prefix_error (error, "failed to read back empty: "); + return FALSE; + } + for (guint i = 0; i < sizeof(buf); i++) { + if (buf[i] != 0xff) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "failed to check blank @0x%x", + addr + offset + i); + return FALSE; + } + } + } + + /* success */ + return TRUE; +} + +static gboolean +fu_vli_usbhub_device_erase_sectors (FuVliUsbhubDevice *self, + guint32 addr, + gsize sz, + GError **error) +{ + g_autoptr(GPtrArray) chunks = fu_chunk_array_new (NULL, sz, addr, 0x0, 0x1000); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chunk = g_ptr_array_index (chunks, i); + g_debug ("erasing @0x%x", chunk->address); + if (!fu_vli_usbhub_device_erase_sector (self, chunk->address, error)) { + g_prefix_error (error, + "failed to erase FW sector @0x%x", + chunk->address); + return FALSE; + } + fu_device_set_progress_full (FU_DEVICE (self), + (gsize) i, (gsize) chunks->len); + } + return TRUE; +} + /* disable hub sleep states -- not really required by 815~ hubs */ static gboolean fu_vli_usbhub_device_disable_u1u2 (FuVliUsbhubDevice *self, GError **error) @@ -508,6 +677,20 @@ fu_vli_usbhub_device_dump_firmware (FuVliUsbhubDevice *self, gsize bufsz, GError return g_bytes_new_take (g_steal_pointer (&buf), bufsz); } +static gboolean +fu_vli_usbhub_device_probe (FuDevice *device, GError **error) +{ + /* quirks now applied... */ + if (fu_device_has_custom_flag (device, "usb3")) { + fu_device_set_summary (device, "USB 3.x Hub"); + } else if (fu_device_has_custom_flag (device, "usb2")) { + fu_device_set_summary (device, "USB 2.x Hub"); + } else { + fu_device_set_summary (device, "USB Hub"); + } + return TRUE; +} + static gboolean fu_vli_usbhub_device_setup (FuDevice *device, GError **error) { @@ -517,13 +700,13 @@ fu_vli_usbhub_device_setup (FuDevice *device, GError **error) /* try to read a block of data which will fail for 813-type devices */ if (fu_device_has_custom_flag (device, "needs-unlock-legacy813") && - !fu_vli_usbhub_device_spi_read_data (self, 0x0, (guint8 *) &self->hdr, - sizeof(self->hdr), &error_tmp)) { + !fu_vli_usbhub_device_spi_read_data (self, 0x0, (guint8 *) &self->hd1_hdr, + sizeof(self->hd1_hdr), &error_tmp)) { g_warning ("failed to read, trying to unlock 813: %s", error_tmp->message); if (!fu_vli_usbhub_device_vdr_unlock_813 (self, error)) return FALSE; - if (!fu_vli_usbhub_device_spi_read_data (self, 0x0, (guint8 *) &self->hdr, - sizeof(self->hdr), error)) { + if (!fu_vli_usbhub_device_spi_read_data (self, 0x0, (guint8 *) &self->hd1_hdr, + sizeof(self->hd1_hdr), error)) { g_prefix_error (error, "813 unlock fail: "); return FALSE; } @@ -566,33 +749,49 @@ fu_vli_usbhub_device_setup (FuDevice *device, GError **error) } - /* read root header */ - if (!fu_vli_usbhub_device_spi_read_data (self, 0x0, (guint8 *) &self->hdr, - sizeof(self->hdr), error)) { - g_prefix_error (error, "failed to read root header"); + /* read HD1 (factory) header */ + if (!fu_vli_usbhub_device_spi_read_data (self, VLI_USBHUB_FLASHMAP_ADDR_HD1, + (guint8 *) &self->hd1_hdr, + sizeof(self->hd1_hdr), error)) { + g_prefix_error (error, "failed to read HD1 header"); return FALSE; } /* detect update protocol from the device ID */ - switch (GUINT16_FROM_BE(self->hdr.dev_id) >> 8) { + switch (GUINT16_FROM_BE(self->hd1_hdr.dev_id) >> 8) { /* VL810~VL813 */ case 0x0d: self->update_protocol = 0x1; self->disable_powersave = TRUE; fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); + fu_device_set_install_duration (FU_DEVICE (self), 10); /* seconds */ break; /* VL817~ */ case 0x05: self->update_protocol = 0x2; fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); + fu_device_set_install_duration (FU_DEVICE (self), 15); /* seconds */ break; default: g_warning ("unknown update protocol, device_id=0x%x", - GUINT16_FROM_BE(self->hdr.dev_id)); + GUINT16_FROM_BE(self->hd1_hdr.dev_id)); break; } + /* read HD2 (update) header */ + if (self->update_protocol >= 0x2) { + if (!fu_vli_usbhub_device_spi_read_data (self, VLI_USBHUB_FLASHMAP_ADDR_HD2, + (guint8 *) &self->hd2_hdr, + sizeof(self->hd2_hdr), error)) { + g_prefix_error (error, "failed to read HD2 header"); + return FALSE; + } + } + /* success */ return TRUE; } @@ -643,12 +842,12 @@ fu_vli_usbhub_device_prepare_firmware (FuDevice *device, return NULL; } device_id = fu_vli_usbhub_firmware_get_device_id (FU_VLI_USBHUB_FIRMWARE (firmware)); - if (GUINT16_FROM_BE(self->hdr.dev_id) != device_id) { + if (GUINT16_FROM_BE(self->hd1_hdr.dev_id) != device_id) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got 0x%04x, expected 0x%04x", - device_id, GUINT16_FROM_BE(self->hdr.dev_id)); + device_id, GUINT16_FROM_BE(self->hd1_hdr.dev_id)); return NULL; } @@ -771,11 +970,17 @@ fu_vli_usbhub_device_write_blocks (FuVliUsbhubDevice *self, static gboolean fu_vli_usbhub_device_update_v1 (FuVliUsbhubDevice *self, - GBytes *fw, + FuFirmware *firmware, GError **error) { gsize bufsz = 0; - const guint8 *buf = g_bytes_get_data (fw, &bufsz); + const guint8 *buf; + g_autoptr(GBytes) fw = NULL; + + /* simple image */ + fw = fu_firmware_get_image_default_bytes (firmware, error); + if (fw == NULL) + return FALSE; /* erase */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); @@ -786,6 +991,7 @@ fu_vli_usbhub_device_update_v1 (FuVliUsbhubDevice *self, /* write in chunks */ fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); + buf = g_bytes_get_data (fw, &bufsz); if (!fu_vli_usbhub_device_write_blocks (self, 0x0, buf, bufsz, error)) return FALSE; @@ -793,6 +999,202 @@ fu_vli_usbhub_device_update_v1 (FuVliUsbhubDevice *self, return TRUE; } +/* if no header1 or ROM code update, write data directly */ +static gboolean +fu_vli_usbhub_device_update_v2_recovery (FuVliUsbhubDevice *self, GBytes *fw, GError **error) +{ + gsize bufsz = 0; + const guint8 *buf = g_bytes_get_data (fw, &bufsz); + + /* erase */ + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); + for (guint32 addr = 0; addr < bufsz; addr += 0x1000) { + if (!fu_vli_usbhub_device_erase_sector (self, addr, error)) { + g_prefix_error (error, "failed to erase sector @0x%x", addr); + return FALSE; + } + } + + /* write in chunks */ + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); + if (!fu_vli_usbhub_device_write_blocks (self, VLI_USBHUB_FLASHMAP_ADDR_HD1, + buf, bufsz, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_vli_usbhub_device_hd1_is_valid (FuVliUsbhubHeader *hdr) +{ + if (hdr->prev_ptr != VLI_USBHUB_FLASHMAP_IDX_INVALID) + return FALSE; + if (hdr->checksum != fu_vli_usbhub_header_crc8 (hdr)) + return FALSE; + return TRUE; +} + +static gboolean +fu_vli_usbhub_device_hd1_recover (FuVliUsbhubDevice *self, FuVliUsbhubHeader *hdr, GError **error) +{ + /* point to HD2, i.e. updated firmware */ + if (hdr->next_ptr != VLI_USBHUB_FLASHMAP_IDX_HD2) { + hdr->next_ptr = VLI_USBHUB_FLASHMAP_IDX_HD2; + hdr->checksum = fu_vli_usbhub_header_crc8 (hdr); + } + + /* write new header block */ + if (!fu_vli_usbhub_device_erase_sector (self, VLI_USBHUB_FLASHMAP_ADDR_HD1, error)) { + g_prefix_error (error, + "failed to erase header1 sector at 0x%x: ", + (guint) VLI_USBHUB_FLASHMAP_ADDR_HD1); + return FALSE; + } + if (!fu_vli_usbhub_device_write_block (self, VLI_USBHUB_FLASHMAP_ADDR_HD1, + (const guint8 *) hdr, + sizeof(FuVliUsbhubHeader), + error)) { + g_prefix_error (error, + "failed to write header1 block at 0x%x: ", + (guint) VLI_USBHUB_FLASHMAP_ADDR_HD1); + return FALSE; + } + + /* update the cached copy */ + memcpy (&self->hd1_hdr, hdr, sizeof(self->hd1_hdr)); + return TRUE; +} + +static gboolean +fu_vli_usbhub_device_update_v2 (FuVliUsbhubDevice *self, FuFirmware *firmware, GError **error) +{ + gsize buf_fwsz = 0; + guint32 hd1_fw_sz; + guint32 hd2_fw_sz; + guint32 hd2_fw_addr; + guint32 hd2_fw_offset; + const guint8 *buf_fw; + FuVliUsbhubHeader hdr = { 0x0 }; + g_autoptr(GBytes) fw = NULL; + + /* simple image */ + fw = fu_firmware_get_image_default_bytes (firmware, error); + if (fw == NULL) + return FALSE; + + /* root header is valid */ + if (fu_vli_usbhub_device_hd1_is_valid (&self->hd1_hdr)) { + + /* no update has ever been done */ + if (self->hd1_hdr.next_ptr != VLI_USBHUB_FLASHMAP_IDX_HD2) { + + /* backup HD1 before recovering */ + if (!fu_vli_usbhub_device_erase_sector (self, VLI_USBHUB_FLASHMAP_ADDR_HD2, error)) { + g_prefix_error (error, "failed to erase sector at header 1: "); + return FALSE; + } + if (!fu_vli_usbhub_device_write_block (self, VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP, + (const guint8 *) &self->hd1_hdr, sizeof(hdr), + error)) { + g_prefix_error (error, "failed to write block at header 1: "); + return FALSE; + } + if (!fu_vli_usbhub_device_hd1_recover (self, &self->hd1_hdr, error)) { + g_prefix_error (error, "failed to write header: "); + return FALSE; + } + } + + /* copy the header from the backup zone */ + } else { + g_debug ("HD1 was invalid, reading backup"); + if (!fu_vli_usbhub_device_spi_read_data (self, VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP, + (guint8 *) &self->hd1_hdr, sizeof(hdr), + error)) { + g_prefix_error (error, + "failed to read root header from 0x%x", + (guint) VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP); + return FALSE; + } + if (!fu_vli_usbhub_device_hd1_is_valid (&self->hd1_hdr)) { + g_debug ("backup header is also invalid, starting recovery"); + return fu_vli_usbhub_device_update_v2_recovery (self, fw, error); + } + if (!fu_vli_usbhub_device_hd1_recover (self, &self->hd1_hdr, error)) { + g_prefix_error (error, "failed to get root header in backup zone: "); + return FALSE; + } + } + + /* align the update fw address to the sector after the factory size */ + hd1_fw_sz = GUINT16_FROM_BE(self->hd1_hdr.usb3_fw_sz); + if (hd1_fw_sz > 0xF000) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "FW1 size abnormal 0x%x", + (guint) hd1_fw_sz); + return FALSE; + } + hd2_fw_addr = (hd1_fw_sz + 0xfff) & 0xf000; + hd2_fw_addr += VLI_USBHUB_FLASHMAP_ADDR_FW; + + /* get the size and offset of the update firmware */ + buf_fw = g_bytes_get_data (fw, &buf_fwsz); + memcpy (&hdr, buf_fw, sizeof(hdr)); + hd2_fw_sz = GUINT16_FROM_BE(hdr.usb3_fw_sz); + hd2_fw_offset = GUINT16_FROM_BE(hdr.usb3_fw_addr); + g_debug ("FW2 @0x%x (length 0x%x, offset 0x%x)", + hd2_fw_addr, hd2_fw_sz, hd2_fw_offset); + + /* make space */ + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); + if (!fu_vli_usbhub_device_erase_sectors (self, hd2_fw_addr, hd2_fw_sz, error)) + return FALSE; + + /* perform the actual write */ + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); + if (!fu_vli_usbhub_device_write_blocks (self, + hd2_fw_addr, + buf_fw + hd2_fw_offset, + hd2_fw_sz, + error)) { + g_prefix_error (error, "failed to write payload: "); + return FALSE; + } + + /* map into header */ + if (!fu_memcpy_safe ((guint8 *) &self->hd2_hdr, sizeof(hdr), 0x0, + buf_fw, buf_fwsz, 0x0, sizeof(hdr), error)) { + g_prefix_error (error, "failed to read header: "); + return FALSE; + } + + /* write new HD2 */ + self->hd2_hdr.usb3_fw_addr = GUINT16_TO_BE(hd2_fw_addr & 0xffff); + self->hd2_hdr.usb3_fw_addr_high = (guint8) (hd2_fw_addr >> 16); + self->hd2_hdr.prev_ptr = VLI_USBHUB_FLASHMAP_IDX_HD1; + self->hd2_hdr.next_ptr = VLI_USBHUB_FLASHMAP_IDX_INVALID; + self->hd2_hdr.checksum = fu_vli_usbhub_header_crc8 (&self->hd2_hdr); + if (!fu_vli_usbhub_device_erase_sector (self, VLI_USBHUB_FLASHMAP_ADDR_HD2, error)) { + g_prefix_error (error, "failed to erase sectors for HD2: "); + return FALSE; + } + if (!fu_vli_usbhub_device_write_block (self, + VLI_USBHUB_FLASHMAP_ADDR_HD2, + (const guint8 *) &self->hd2_hdr, + sizeof(self->hd2_hdr), + error)) { + g_prefix_error (error, "failed to write HD2: "); + return FALSE; + } + + /* success */ + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); + return TRUE; +} + static FuFirmware * fu_vli_usbhub_device_read_firmware (FuDevice *device, GError **error) { @@ -812,12 +1214,6 @@ fu_vli_usbhub_device_write_firmware (FuDevice *device, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE (device); - g_autoptr(GBytes) fw = NULL; - - /* simple image */ - fw = fu_firmware_get_image_default_bytes (firmware, error); - if (fw == NULL) - return FALSE; /* disable powersaving if required */ if (self->disable_powersave) { @@ -829,7 +1225,9 @@ fu_vli_usbhub_device_write_firmware (FuDevice *device, /* use correct method */ if (self->update_protocol == 0x1) - return fu_vli_usbhub_device_update_v1 (self, fw, error); + return fu_vli_usbhub_device_update_v1 (self, firmware, error); + if (self->update_protocol == 0x2) + return fu_vli_usbhub_device_update_v2 (self, firmware, error); /* not sure what to do */ g_set_error (error, @@ -859,7 +1257,10 @@ fu_vli_usbhub_device_attach (FuDevice *device, GError **error) NULL, &error_local)) { if (g_error_matches (error_local, G_USB_DEVICE_ERROR, - G_USB_DEVICE_ERROR_NO_DEVICE)) { + G_USB_DEVICE_ERROR_NO_DEVICE) || + g_error_matches (error_local, + G_USB_DEVICE_ERROR, + G_USB_DEVICE_ERROR_FAILED)) { g_debug ("ignoring %s", error_local->message); } else { g_propagate_prefixed_error (error, @@ -909,12 +1310,11 @@ fu_vli_usbhub_device_init (FuVliUsbhubDevice *self) self->spi_cmd_read_data = 0x03; self->spi_cmd_read_status = 0x05; self->spi_cmd_write_en = 0x06; + self->spi_cmd_sector_erase = 0x20; self->spi_cmd_chip_erase = 0x60; self->spi_cmd_read_id = 0x9f; self->spi_cmd_read_id_sz = 2; fu_device_add_icon (FU_DEVICE (self), "audio-card"); - fu_device_set_summary (FU_DEVICE (self), "Expand a single USB port into several"); - fu_device_set_install_duration (FU_DEVICE (self), 3); /* seconds */ fu_device_set_firmware_size_max (FU_DEVICE (self), 0x20000); fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } @@ -925,6 +1325,7 @@ fu_vli_usbhub_device_class_init (FuVliUsbhubDeviceClass *klass) FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->to_string = fu_vli_usbhub_device_to_string; klass_device->set_quirk_kv = fu_vli_usbhub_device_set_quirk_kv; + klass_device->probe = fu_vli_usbhub_device_probe; klass_device->setup = fu_vli_usbhub_device_setup; klass_device->read_firmware = fu_vli_usbhub_device_read_firmware; klass_device->write_firmware = fu_vli_usbhub_device_write_firmware; diff --git a/plugins/vli-usbhub/fu-vli-usbhub-firmware.c b/plugins/vli-usbhub/fu-vli-usbhub-firmware.c index e2fbdafba..935bc1080 100644 --- a/plugins/vli-usbhub/fu-vli-usbhub-firmware.c +++ b/plugins/vli-usbhub/fu-vli-usbhub-firmware.c @@ -13,8 +13,8 @@ struct _FuVliUsbhubFirmware { FuFirmwareClass parent_instance; - guint16 device_id; FuVliUsbhubDeviceKind device_kind; + FuVliUsbhubHeader hdr; }; G_DEFINE_TYPE (FuVliUsbhubFirmware, fu_vli_usbhub_firmware, FU_TYPE_FIRMWARE) @@ -30,16 +30,16 @@ guint16 fu_vli_usbhub_firmware_get_device_id (FuVliUsbhubFirmware *self) { g_return_val_if_fail (FU_IS_VLI_USBHUB_FIRMWARE (self), 0); - return self->device_id; + return GUINT16_FROM_BE(self->hdr.dev_id); } static void fu_vli_usbhub_firmware_to_string (FuFirmware *firmware, guint idt, GString *str) { FuVliUsbhubFirmware *self = FU_VLI_USBHUB_FIRMWARE (firmware); - fu_common_string_append_kx (str, idt, "DeviceId", self->device_id); fu_common_string_append_kv (str, idt, "DeviceKind", fu_vli_usbhub_device_kind_to_string (self->device_kind)); + fu_vli_usbhub_header_to_string (&self->hdr, idt, str); } static gboolean @@ -55,20 +55,18 @@ fu_vli_usbhub_firmware_parse (FuFirmware *firmware, guint16 adr_ofs = 0; guint16 version = 0x0; guint8 tmp = 0x0; - FuVliUsbhubHeader hdr = { 0x0 }; const guint8 *buf = g_bytes_get_data (fw, &bufsz); g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (fw); /* map into header */ - if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, - buf, bufsz, 0x0, sizeof(hdr), error)) { + if (!fu_memcpy_safe ((guint8 *) &self->hdr, sizeof(self->hdr), 0x0, + buf, bufsz, 0x0, sizeof(self->hdr), error)) { g_prefix_error (error, "failed to read header: "); return FALSE; } /* get firmware versions */ - self->device_id = GUINT16_FROM_BE(hdr.dev_id); - switch (self->device_id) { + switch (GUINT16_FROM_BE(self->hdr.dev_id)) { case 0x0d12: /* VL81x */ if (!fu_common_read_uint16_safe (buf, bufsz, 0x1f4c, @@ -76,7 +74,7 @@ fu_vli_usbhub_firmware_parse (FuFirmware *firmware, g_prefix_error (error, "failed to get version: "); return FALSE; } - version |= (hdr.unknown_02 >> 4) & 0x07; + version |= (self->hdr.strapping1 >> 4) & 0x07; if ((version & 0x0f) == 0x04 ) { if (!fu_common_read_uint8_safe (buf, bufsz, 0x700d, &tmp, error)) { g_prefix_error (error, "failed to get version increment: "); @@ -93,7 +91,7 @@ fu_vli_usbhub_firmware_parse (FuFirmware *firmware, g_prefix_error (error, "failed to get version: "); return FALSE; } - version |= (hdr.unknown_02 >> 4) & 0x07; + version |= (self->hdr.strapping1 >> 4) & 0x07; if ((version & 0x0f) == 0x04) version += 1; break; @@ -109,7 +107,7 @@ fu_vli_usbhub_firmware_parse (FuFirmware *firmware, g_prefix_error (error, "failed to get offset version: "); return FALSE; } - version |= (hdr.unknown_02 >> 4) & 0x07; + version |= (self->hdr.strapping1 >> 4) & 0x07; } /* version is set */ @@ -120,19 +118,19 @@ fu_vli_usbhub_firmware_parse (FuFirmware *firmware, } /* get device type from firmware image */ - switch (self->device_id) { + switch (GUINT16_FROM_BE(self->hdr.dev_id)) { case 0x0d12: { guint16 binver1 = 0x0; guint16 binver2 = 0x0; - guint16 u2_addr = GUINT16_FROM_BE(hdr.u2_addr) + 0x1ff1; - guint16 u3_addr = GUINT16_FROM_BE(hdr.u3_addr) + 0x1ffa; - if (!fu_common_read_uint16_safe (buf, bufsz, u2_addr, + guint16 usb2_fw_addr = GUINT16_FROM_BE(self->hdr.usb2_fw_addr) + 0x1ff1; + guint16 usb3_fw_addr = GUINT16_FROM_BE(self->hdr.usb3_fw_addr) + 0x1ffa; + if (!fu_common_read_uint16_safe (buf, bufsz, usb2_fw_addr, &binver1, G_LITTLE_ENDIAN, error)) { g_prefix_error (error, "failed to get binver1: "); return FALSE; } - if (!fu_common_read_uint16_safe (buf, bufsz, u3_addr, + if (!fu_common_read_uint16_safe (buf, bufsz, usb3_fw_addr, &binver2, G_LITTLE_ENDIAN, error)) { g_prefix_error (error, "failed to get binver2: "); return FALSE; @@ -144,13 +142,13 @@ fu_vli_usbhub_firmware_parse (FuFirmware *firmware, self->device_kind = FU_VLI_USBHUB_DEVICE_KIND_VL813; /* VLQ4S == VT3470 (Q4S) */ - } else if (hdr.unknown_02 & (1 << 7)) { + } else if (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_Q4S) { self->device_kind = FU_VLI_USBHUB_DEVICE_KIND_VL812Q4S; /* VL812 == VT3470 (812/813) */ - } else if (hdr.unknown_02 & (1 << 2)) { + } else if (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_76PIN) { /* is B3 */ - if (hdr.unknown_02 & (1 << 3)) + if (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_B3UP) self->device_kind = FU_VLI_USBHUB_DEVICE_KIND_VL812B3; else self->device_kind = FU_VLI_USBHUB_DEVICE_KIND_VL812B0; @@ -158,7 +156,7 @@ fu_vli_usbhub_firmware_parse (FuFirmware *firmware, /* VL811P == VT3470 */ } else { /* is B3 */ - if (hdr.unknown_02 & (1 << 3)) + if (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_B3UP) self->device_kind = FU_VLI_USBHUB_DEVICE_KIND_VL811PB3; else self->device_kind = FU_VLI_USBHUB_DEVICE_KIND_VL811PB0; diff --git a/plugins/vli-usbhub/meson.build b/plugins/vli-usbhub/meson.build index 6e0744816..ca7c1d9f0 100644 --- a/plugins/vli-usbhub/meson.build +++ b/plugins/vli-usbhub/meson.build @@ -1,6 +1,9 @@ cargs = ['-DG_LOG_DOMAIN="FuPluginVliUsbhub"'] -install_data(['vli-usbhub.quirk'], +install_data([ + 'vli-usbhub.quirk', + 'vli-usbhub-lenovo.quirk', + ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) diff --git a/plugins/vli-usbhub/vli-usbhub-lenovo.quirk b/plugins/vli-usbhub/vli-usbhub-lenovo.quirk new file mode 100644 index 000000000..b43cc9856 --- /dev/null +++ b/plugins/vli-usbhub/vli-usbhub-lenovo.quirk @@ -0,0 +1,160 @@ +# Lenovo CS18 Ultra Dock +[DeviceInstanceId=USB\VID_17EF&PID_3070] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_17EF&PID_3071] +Plugin = vli_usbhub +Flags = usb2 + +# Lenovo CS18 Pro and Basic Dock +[DeviceInstanceId=USB\VID_17EF&PID_3072] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_17EF&PID_3073] +Plugin = vli_usbhub +Flags = usb2 + +# Lenovo TR Dock +[DeviceInstanceId=USB\VID_17EF&PID_307F] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_17EF&PID_3080] +Plugin = vli_usbhub +Flags = usb2 + +# Lenovo CS13 KG Dock +[DeviceInstanceId=USB\VID_17EF&PID_1010] +Plugin = vli_usbhub +Flags = usb2,usb3 + +# Lenovo CS13 GD Dock +[DeviceInstanceId=USB\VID_17EF&PID_1012] +Plugin = vli_usbhub +Flags = usb2,usb3 + +# Lenovo CS13 MO Dock +[DeviceInstanceId=USB\VID_17EF&PID_1013] +Plugin = vli_usbhub +Flags = usb2,usb3 + +# Lenovo Payton dock +[DeviceInstanceId=USB\VID_17EF&PID_305A] +Plugin = vli_usbhub +Flags = tier1,usb2,usb3 +[DeviceInstanceId=USB\VID_17EF&PID_305B] +Plugin = vli_usbhub +Flags = tier2,usb2,usb3 + +# Lenovo USB3 Ultra Dock +[DeviceInstanceId=USB\VID_17EF&PID_1014] +Plugin = vli_usbhub +Flags = tier1,usb2,usb3 +[DeviceInstanceId=USB\VID_17EF&PID_1015] +Plugin = vli_usbhub +Flags = tier2,usb2,usb3 + +# Lenovo USB3 Pro Dock +[DeviceInstanceId=USB\VID_17EF&PID_1016] +Plugin = vli_usbhub +Flags = tier1,usb2,usb3 +[DeviceInstanceId=USB\VID_17EF&PID_1018] +Plugin = vli_usbhub +Flags = tier2,usb2,usb3 + +# Lenovo Workstation D40 +[DeviceInstanceId=USB\VID_17EF&PID_1033] +Plugin = vli_usbhub +Flags = usb2,usb3 + +# Lenovo Workstation S40 +[DeviceInstanceId=USB\VID_17EF&PID_1034] +Plugin = vli_usbhub +Flags = usb2,usb3 + +# Lenovo Workstation v40 +[DeviceInstanceId=USB\VID_17EF&PID_1035] +Plugin = vli_usbhub +Flags = usb2,usb3 + +# Lenovo One Link Plus +[DeviceInstanceId=USB\VID_17EF&PID_1018] +Plugin = vli_usbhub +Flags = tier1,usb2,usb3 +[DeviceInstanceId=USB\VID_17EF&PID_1019] +Plugin = vli_usbhub +Flags = tier2,usb2,usb3 + +# Lenovo Hybrid dock +[DeviceInstanceId=USB\VID_17EF&PID_A356] +Plugin = vli_usbhub +Flags = tier1,usb3 +[DeviceInstanceId=USB\VID_17EF&PID_1028] +Plugin = vli_usbhub +Flags = tier1,usb2 +[DeviceInstanceId=USB\VID_17EF&PID_A357] +Plugin = vli_usbhub +Flags = tier2,usb3 +[DeviceInstanceId=USB\VID_17EF&PID_1029] +Plugin = vli_usbhub +Flags = tier2,usb2 + +# Lenovo Travel hub +[DeviceInstanceId=USB\VID_17EF&PID_7216] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_17EF&PID_7224] +Plugin = vli_usbhub +Flags = usb2 + +# Lenovo Travel hub Gen2 +[DeviceInstanceId=USB\VID_17EF&PID_721D] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_17EF&PID_7225] +Plugin = vli_usbhub +Flags = usb2 + +# Lenovo Mini dock +[DeviceInstanceId=USB\VID_17EF&PID_3074] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_17EF&PID_3095] +Plugin = vli_usbhub +Flags = usb2 + +# Lenovo Lenovo Travel Hub 1in3 +[DeviceInstanceId=USB\VID_17EF&PID_7228] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_17EF&PID_7226] +Plugin = vli_usbhub +Flags = usb2 + +# Lenovo Lenovo 1转7 Hub +[DeviceInstanceId=USB\VID_17EF&PID_722A] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_17EF&PID_7229] +Plugin = vli_usbhub +Flags = usb2 + +# Lenovo Lenovo Gen2 dock +[DeviceInstanceId=USB\VID_17EF&PID_A391] +Plugin = vli_usbhub +Flags = tier1,usb3 +[DeviceInstanceId=USB\VID_17EF&PID_A392] +Plugin = vli_usbhub +Flags = tier1,usb2 +[DeviceInstanceId=USB\VID_17EF&PID_A393] +Plugin = vli_usbhub +Flags = tier2,usb3 +[DeviceInstanceId=USB\VID_17EF&PID_A394] +Plugin = vli_usbhub +Flags = tier2,usb2 +[DeviceInstanceId=USB\VID_17EF&PID_A395] +Plugin = vli_usbhub + +# Lenovo Powered Hub +[DeviceInstanceId=USB\VID_17EF&PID_721C] +Plugin = vli_usbhub +Flags = usb2 diff --git a/plugins/vli-usbhub/vli-usbhub.quirk b/plugins/vli-usbhub/vli-usbhub.quirk index 0ba5544a5..39701ac3c 100644 --- a/plugins/vli-usbhub/vli-usbhub.quirk +++ b/plugins/vli-usbhub/vli-usbhub.quirk @@ -27,6 +27,22 @@ Plugin = vli_usbhub [DeviceInstanceId=USB\VID_2109&PID_2212] Plugin = vli_usbhub +# VL817 +[DeviceInstanceId=USB\VID_2109&PID_0817] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_2109&PID_2817] +Plugin = vli_usbhub +Flags = usb2 + +# VL820 +[DeviceInstanceId=USB\VID_2109&PID_0820] +Plugin = vli_usbhub +Flags = usb3 +[DeviceInstanceId=USB\VID_2109&PID_2820] +Plugin = vli_usbhub +Flags = usb2 + # A25Lxxx [Guid=VLI_USBHUB\\SPI_3730] SpiCmdChipErase = 0xc7