From 56f6f996335c26b46b698d00a8595418b7c0b5ea Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 4 Oct 2021 16:53:52 +0100 Subject: [PATCH] usi-dock: Add support for various TBT4 docks for Lenovo and HP With much help from Victor Cheng --- contrib/fwupd.spec.in | 1 + plugins/meson.build | 1 + plugins/usi-dock/README.md | 37 + plugins/usi-dock/data/lsusb.txt | 68 ++ plugins/usi-dock/fu-plugin-usi-dock.c | 43 ++ plugins/usi-dock/fu-usi-dock-child-device.c | 88 +++ plugins/usi-dock/fu-usi-dock-child-device.h | 21 + plugins/usi-dock/fu-usi-dock-common.c | 40 ++ plugins/usi-dock/fu-usi-dock-common.h | 107 +++ plugins/usi-dock/fu-usi-dock-dmc-device.c | 55 ++ plugins/usi-dock/fu-usi-dock-dmc-device.h | 16 + plugins/usi-dock/fu-usi-dock-mcu-device.c | 738 ++++++++++++++++++++ plugins/usi-dock/fu-usi-dock-mcu-device.h | 23 + plugins/usi-dock/meson.build | 33 + plugins/usi-dock/usi-dock.quirk | 15 + 15 files changed, 1286 insertions(+) create mode 100644 plugins/usi-dock/README.md create mode 100644 plugins/usi-dock/data/lsusb.txt create mode 100644 plugins/usi-dock/fu-plugin-usi-dock.c create mode 100644 plugins/usi-dock/fu-usi-dock-child-device.c create mode 100644 plugins/usi-dock/fu-usi-dock-child-device.h create mode 100644 plugins/usi-dock/fu-usi-dock-common.c create mode 100644 plugins/usi-dock/fu-usi-dock-common.h create mode 100644 plugins/usi-dock/fu-usi-dock-dmc-device.c create mode 100644 plugins/usi-dock/fu-usi-dock-dmc-device.h create mode 100644 plugins/usi-dock/fu-usi-dock-mcu-device.c create mode 100644 plugins/usi-dock/fu-usi-dock-mcu-device.h create mode 100644 plugins/usi-dock/meson.build create mode 100644 plugins/usi-dock/usi-dock.quirk diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index cf7304eb3..fa46716e6 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -484,6 +484,7 @@ done %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_uefi_pk.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_uefi_recovery.so %endif +%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_usi_dock.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_logind.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_logitech_bulkcontroller.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_logitech_hidpp.so diff --git a/plugins/meson.build b/plugins/meson.build index de1c85ca7..e6ad21140 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -71,6 +71,7 @@ subdir('uefi-dbx') subdir('uefi-pk') subdir('uefi-recovery') subdir('upower') +subdir('usi-dock') subdir('vli') subdir('wacom-raw') subdir('wacom-usb') diff --git a/plugins/usi-dock/README.md b/plugins/usi-dock/README.md new file mode 100644 index 000000000..0cf1a70cb --- /dev/null +++ b/plugins/usi-dock/README.md @@ -0,0 +1,37 @@ +# USI Dock + +## Introduction + +This plugin uses the MCU to write all the dock firmware components. The MCU version +is provided by the DMC bcdDevice. + +This plugin supports the following protocol ID: + +* com.usi.dock + +## GUID Generation + +These devices use the standard USB DeviceInstanceId values, e.g. + +* `USB\VID_17EF&PID_7226&REV_0001` +* `USB\VID_17EF&PID_7226` +* `USB\VID_17EF` + +Additionally, some extra "component ID" instance IDs are added. + +* `USB\VID_17EF&PID_7226&CID_TBT4` +* `USB\VID_17EF&PID_7226&CID_USB3` + +## Update Behavior + +The device usually presents in runtime mode, but on detach re-enumerates with +the same USB PID in an unlocked mode. On attach the device again re-enumerates +back to the runtime locked mode. + +## Vendor ID Security + +The vendor ID is set from the USB vendor. + +## External Interface Access + +This plugin requires read/write access to `/dev/bus/usb`. diff --git a/plugins/usi-dock/data/lsusb.txt b/plugins/usi-dock/data/lsusb.txt new file mode 100644 index 000000000..4a9205e36 --- /dev/null +++ b/plugins/usi-dock/data/lsusb.txt @@ -0,0 +1,68 @@ +Bus 001 Device 024: ID 17ef:30b4 Lenovo ThinkPad Thunderbolt 4 Dock MCU Controller +Device Descriptor: + bLength 18 + bDescriptorType 1 + bcdUSB 2.00 + bDeviceClass 0 + bDeviceSubClass 0 + bDeviceProtocol 0 + bMaxPacketSize0 64 + idVendor 0x17ef Lenovo + idProduct 0x30b4 + bcdDevice 1.00 + iManufacturer 1 Lenovo + iProduct 2 ThinkPad Thunderbolt 4 Dock MCU Controller + iSerial 3 + bNumConfigurations 1 + Configuration Descriptor: + bLength 9 + bDescriptorType 2 + wTotalLength 0x0029 + bNumInterfaces 1 + bConfigurationValue 1 + iConfiguration 0 + bmAttributes 0x80 + (Bus Powered) + MaxPower 100mA + Interface Descriptor: + bLength 9 + bDescriptorType 4 + bInterfaceNumber 0 + bAlternateSetting 0 + bNumEndpoints 2 + bInterfaceClass 3 Human Interface Device + bInterfaceSubClass 0 + bInterfaceProtocol 0 + iInterface 0 + HID Device Descriptor: + bLength 9 + bDescriptorType 33 + bcdHID 1.11 + bCountryCode 0 Not supported + bNumDescriptors 1 + bDescriptorType 34 Report + wDescriptorLength 39 + Report Descriptors: + ** UNAVAILABLE ** + Endpoint Descriptor: + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x81 EP 1 IN + bmAttributes 3 + Transfer Type Interrupt + Synch Type None + Usage Type Data + wMaxPacketSize 0x0040 1x 64 bytes + bInterval 1 + Endpoint Descriptor: + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x02 EP 2 OUT + bmAttributes 3 + Transfer Type Interrupt + Synch Type None + Usage Type Data + wMaxPacketSize 0x0040 1x 64 bytes + bInterval 1 +Device Status: 0x0000 + (Bus Powered) diff --git a/plugins/usi-dock/fu-plugin-usi-dock.c b/plugins/usi-dock/fu-plugin-usi-dock.c new file mode 100644 index 000000000..eb7ea59ab --- /dev/null +++ b/plugins/usi-dock/fu-plugin-usi-dock.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Richard Hughes + * Copyright (C) 2021 Victor Cheng + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-usi-dock-dmc-device.h" +#include "fu-usi-dock-mcu-device.h" + +#define USI_DOCK_TBT_INSTANCE_ID "THUNDERBOLT\\VEN_0108&DEV_2031" + +static void +fu_plugin_usi_dock_dmc_registered(FuPlugin *plugin, FuDevice *device) +{ + /* usb device from thunderbolt plugin */ + if (g_strcmp0(fu_device_get_plugin(device), "thunderbolt") == 0 && + fu_device_has_guid(device, USI_DOCK_TBT_INSTANCE_ID)) { + g_autofree gchar *msg = NULL; + msg = g_strdup_printf("firmware update inhibited by [%s] plugin", + fu_plugin_get_name(plugin)); + fu_device_inhibit(device, "usb-blocked", msg); + } +} + +static void +fu_usi_dock_init(FuPlugin *plugin) +{ + fu_plugin_add_device_gtype(plugin, FU_TYPE_USI_DOCK_MCU_DEVICE); + fu_plugin_add_device_gtype(plugin, FU_TYPE_USI_DOCK_DMC_DEVICE); +} + +void +fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) +{ + vfuncs->build_hash = FU_BUILD_HASH; + vfuncs->init = fu_usi_dock_init; + vfuncs->device_registered = fu_plugin_usi_dock_dmc_registered; +} diff --git a/plugins/usi-dock/fu-usi-dock-child-device.c b/plugins/usi-dock/fu-usi-dock-child-device.c new file mode 100644 index 000000000..89fbeac98 --- /dev/null +++ b/plugins/usi-dock/fu-usi-dock-child-device.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-usi-dock-child-device.h" +#include "fu-usi-dock-mcu-device.h" + +struct _FuUsiDockChildDevice { + FuDevice parent_instance; + guint8 chip_idx; +}; + +G_DEFINE_TYPE(FuUsiDockChildDevice, fu_usi_dock_child_device, FU_TYPE_DEVICE) + +void +fu_usi_dock_child_device_set_chip_idx(FuUsiDockChildDevice *self, guint8 chip_idx) +{ + self->chip_idx = chip_idx; +} + +static void +fu_usi_dock_child_device_to_string(FuDevice *device, guint idt, GString *str) +{ + FuUsiDockChildDevice *self = FU_USI_DOCK_CHILD_DEVICE(device); + fu_common_string_append_kx(str, idt, "ChipIdx", self->chip_idx); +} + +/* use the parents parser */ +static FuFirmware * +fu_usi_dock_mcu_device_prepare_firmware(FuDevice *device, + GBytes *fw, + FwupdInstallFlags flags, + GError **error) +{ + FuDevice *parent = fu_device_get_parent(device); + if (parent == NULL) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no parent"); + return NULL; + } + return fu_device_prepare_firmware(parent, fw, flags, error); +} + +/* only update this specific child component */ +static gboolean +fu_usi_dock_mcu_device_write_firmware(FuDevice *device, + FuFirmware *firmware, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error) +{ + FuUsiDockChildDevice *self = FU_USI_DOCK_CHILD_DEVICE(device); + FuDevice *parent = fu_device_get_parent(device); + if (parent == NULL) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no parent"); + return FALSE; + } + return fu_usi_dock_mcu_device_write_firmware_with_idx(FU_USI_DOCK_MCU_DEVICE(parent), + firmware, + self->chip_idx, + progress, + flags, + error); +} + +static void +fu_usi_dock_child_device_init(FuUsiDockChildDevice *self) +{ + fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); +} + +static void +fu_usi_dock_child_device_class_init(FuUsiDockChildDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + klass_device->to_string = fu_usi_dock_child_device_to_string; + klass_device->prepare_firmware = fu_usi_dock_mcu_device_prepare_firmware; + klass_device->write_firmware = fu_usi_dock_mcu_device_write_firmware; +} + +FuDevice * +fu_usi_dock_child_new(FuContext *ctx) +{ + return g_object_new(FU_TYPE_USI_DOCK_CHILD_DEVICE, "context", ctx, NULL); +} diff --git a/plugins/usi-dock/fu-usi-dock-child-device.h b/plugins/usi-dock/fu-usi-dock-child-device.h new file mode 100644 index 000000000..023b5b1f4 --- /dev/null +++ b/plugins/usi-dock/fu-usi-dock-child-device.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_USI_DOCK_CHILD_DEVICE (fu_usi_dock_child_device_get_type()) +G_DECLARE_FINAL_TYPE(FuUsiDockChildDevice, + fu_usi_dock_child_device, + FU, + USI_DOCK_CHILD_DEVICE, + FuDevice) + +FuDevice * +fu_usi_dock_child_new(FuContext *ctx); +void +fu_usi_dock_child_device_set_chip_idx(FuUsiDockChildDevice *self, guint8 chip_idx); diff --git a/plugins/usi-dock/fu-usi-dock-common.c b/plugins/usi-dock/fu-usi-dock-common.c new file mode 100644 index 000000000..91baf98fe --- /dev/null +++ b/plugins/usi-dock/fu-usi-dock-common.c @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 Richard Hughes + * Copyright (C) 2021 Victor Cheng + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-usi-dock-common.h" + +const gchar * +fu_usi_dock_spi_state_to_string(guint8 val) +{ + if (val == SPI_STATE_NONE) + return "none"; + if (val == SPI_STATE_SWITCH_SUCCESS) + return "switch-success"; + if (val == SPI_STATE_SWITCH_FAIL) + return "switch-fail"; + if (val == SPI_STATE_CMD_SUCCESS) + return "cmd-success"; + if (val == SPI_STATE_CMD_FAIL) + return "cmd-fail"; + if (val == SPI_STATE_RW_SUCCESS) + return "rw-success"; + if (val == SPI_STATE_RW_FAIL) + return "rw-fail"; + if (val == SPI_STATE_READY) + return "ready"; + if (val == SPI_STATE_BUSY) + return "busy"; + if (val == SPI_STATE_TIMEOUT) + return "timeout"; + if (val == SPI_STATE_FLASH_FOUND) + return "flash-found"; + if (val == SPI_STATE_FLASH_NOT_FOUND) + return "flash-not-found"; + return NULL; +} diff --git a/plugins/usi-dock/fu-usi-dock-common.h b/plugins/usi-dock/fu-usi-dock-common.h new file mode 100644 index 000000000..e258dbc60 --- /dev/null +++ b/plugins/usi-dock/fu-usi-dock-common.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 Richard Hughes + * Copyright (C) 2021 Victor Cheng + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define USB_HID_REPORT_ID1 1u +#define USB_HID_REPORT_ID2 2u + +#define USBUID_ISP_DEVICE_CMD_MCU_NONE 0x0 +#define USBUID_ISP_DEVICE_CMD_MCU_STATUS 0x1 +#define USBUID_ISP_DEVICE_CMD_MCU_JUMP2BOOT 0x2 +#define USBUID_ISP_DEVICE_CMD_READ_MCU_VERSIONPAGE 0x3 +#define USBUID_ISP_DEVICE_CMD_SET_I225_PWR 0x4 +#define USBUID_ISP_DEVICE_CMD_DOCK_RESET 0x5 +#define USBUID_ISP_DEVICE_CMD_VERSION_WRITEBACK 0x6 + +#define USBUID_ISP_DEVICE_CMD_FWBUFER_INITIAL 0x01 +#define USBUID_ISP_DEVICE_CMD_FWBUFER_ERASE_FLASH 0x02 +#define USBUID_ISP_DEVICE_CMD_FWBUFER_PROGRAM 0x03 +#define USBUID_ISP_DEVICE_CMD_FWBUFER_WRITE_RESPONSE 0x04 +#define USBUID_ISP_DEVICE_CMD_FWBUFER_READ_STATUS 0x05 +#define USBUID_ISP_DEVICE_CMD_FWBUFER_CHECKSUM 0x06 +#define USBUID_ISP_DEVICE_CMD_FWBUFER_END 0x07 +#define USBUID_ISP_DEVICE_CMD_FWBUFER_TRANSFER_FINISH 0x08 +#define USBUID_ISP_DEVICE_CMD_FWBUFER_ERROR_END 0x09 + +#define USBUID_ISP_INTERNAL_FW_CMD_INITAL 0x0A +#define USBUID_ISP_INTERNAL_FW_CMD_UPDATE_FW 0x0B +#define USBUID_ISP_INTERNAL_FW_CMD_TARGET_CHECKSUM 0x0C +#define USBUID_ISP_INTERNAL_FW_CMD_ISP_END 0x0D +#define USBUID_ISP_CMD_ALL 0xFF + +#define TAG_TAG2_ISP_BOOT 0 /* before Common CMD for bootload, with TAG0, TAG1, CMD */ +#define TAG_TAG2_ISP 0x5a /* before Common, with TAG0, TAG1, CMD */ +#define TAG_TAG2_CMD_MCU 0x6a /* USB->MCU(Common-cmd mode), with TAG0, TAG1, CMD */ +#define TAG_TAG2_CMD_SPI 0x7a /* USB->MCU->SPI(Common-cmd mode), with TAG0, TAG1, CMD */ +#define TAG_TAG2_CMD_I2C 0x8a /* USB->MCU->I2C(Mass data transmission) */ +#define TAG_TAG2_MASS_DATA_MCU 0x6b /* MASS data transfer for MCU 0xA0 */ +#define TAG_TAG2_MASS_DATA_SPI 0x7b /* MASS data transfer for External flash 0xA1 */ +#define TAG_TAG2_MASS_DATA_I2C 0x8b /* MASS data transfer for TBT flash */ + +#define DP_VERSION_FROM_MCU 0x01 /* if in use */ +#define NIC_VERSION_FROM_MCU 0x2 /* if in use */ + +#define External_Valid_Value 0x37 +#define TX_ISP_LENGTH 61 + +#define W25Q16DV_PAGE_SIZE 256 + +#define FIRMWARE_IDX_NONE 0x00 +#define FIRMWARE_IDX_DMC_PD 0x01 +#define FIRMWARE_IDX_DP 0x02 +#define FIRMWARE_IDX_TBT4 0x04 +#define FIRMWARE_IDX_USB3 0x08 +#define FIRMWARE_IDX_USB2 0x10 +#define FIRMWARE_IDX_AUDIO 0x20 +#define FIRMWARE_IDX_I225 0x40 +#define FIRMWARE_IDX_MCU 0x80 + +typedef enum { + SPI_STATE_NONE, + SPI_STATE_SWITCH_SUCCESS, + SPI_STATE_SWITCH_FAIL, + SPI_STATE_CMD_SUCCESS, + SPI_STATE_CMD_FAIL, + SPI_STATE_RW_SUCCESS, + SPI_STATE_RW_FAIL, + SPI_STATE_READY, + SPI_STATE_BUSY, + SPI_STATE_TIMEOUT, + SPI_STATE_FLASH_FOUND, + SPI_STATE_FLASH_NOT_FOUND, +} SPI_BUS_STATE; + +typedef struct { + guint8 DMC[5]; + guint8 PD[5]; + guint8 DP5x[5]; + guint8 DP6x[5]; + guint8 TBT4[5]; + guint8 USB3[5]; + guint8 USB2[5]; + guint8 AUDIO[5]; + guint8 I255[5]; + guint8 MCU[2]; + guint8 bcdVersion[2]; +} IspVersionInMcu_t; + +typedef struct { + guint8 id; + guint8 length; + guint8 mcutag1; + guint8 mcutag2; + guint8 inbuf[59]; + guint8 mcutag3; +} UsiDockSetReportBuf; + +const gchar * +fu_usi_dock_idx_to_string(guint8 val); +const gchar * +fu_usi_dock_spi_state_to_string(guint8 val); diff --git a/plugins/usi-dock/fu-usi-dock-dmc-device.c b/plugins/usi-dock/fu-usi-dock-dmc-device.c new file mode 100644 index 000000000..c76f9a7b9 --- /dev/null +++ b/plugins/usi-dock/fu-usi-dock-dmc-device.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-usi-dock-dmc-device.h" + +struct _FuUsiDockDmcDevice { + FuUsbDevice parent_instance; +}; + +G_DEFINE_TYPE(FuUsiDockDmcDevice, fu_usi_dock_dmc_device, FU_TYPE_USB_DEVICE) + +static void +fu_usi_dock_dmc_device_parent_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data) +{ + FuDevice *parent = fu_device_get_parent(device); + if (parent != NULL) { + g_autofree gchar *instance_id = NULL; + + /* slightly odd: the MCU device uses the DMC version number */ + g_debug("absorbing DMC version into MCU"); + fu_device_set_version_format(parent, fu_device_get_version_format(device)); + fu_device_set_version(parent, fu_device_get_version(device)); + fu_device_set_serial(parent, fu_device_get_serial(device)); + + /* allow matching firmware */ + instance_id = g_strdup_printf("USB\\VID_%04X&PID_%04X&CID_%s", + fu_usb_device_get_vid(FU_USB_DEVICE(parent)), + fu_usb_device_get_pid(FU_USB_DEVICE(parent)), + fu_device_get_name(device)); + fu_device_add_instance_id(parent, instance_id); + + /* don't allow firmware updates on this */ + fu_device_set_name(device, "Dock Management Controller Information"); + fu_device_inhibit(device, "dummy", "Use the MCU to update the DMC device"); + } +} + +static void +fu_usi_dock_dmc_device_init(FuUsiDockDmcDevice *self) +{ + g_signal_connect(self, + "notify::parent", + G_CALLBACK(fu_usi_dock_dmc_device_parent_notify_cb), + NULL); +} + +static void +fu_usi_dock_dmc_device_class_init(FuUsiDockDmcDeviceClass *klass) +{ +} diff --git a/plugins/usi-dock/fu-usi-dock-dmc-device.h b/plugins/usi-dock/fu-usi-dock-dmc-device.h new file mode 100644 index 000000000..7332644b6 --- /dev/null +++ b/plugins/usi-dock/fu-usi-dock-dmc-device.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_USI_DOCK_DMC_DEVICE (fu_usi_dock_dmc_device_get_type()) +G_DECLARE_FINAL_TYPE(FuUsiDockDmcDevice, + fu_usi_dock_dmc_device, + FU, + USI_DOCK_DMC_DEVICE, + FuUsbDevice) diff --git a/plugins/usi-dock/fu-usi-dock-mcu-device.c b/plugins/usi-dock/fu-usi-dock-mcu-device.c new file mode 100644 index 000000000..436cbdfed --- /dev/null +++ b/plugins/usi-dock/fu-usi-dock-mcu-device.c @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2021 Richard Hughes + * Copyright (C) 2021 Victor Cheng + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-usi-dock-child-device.h" +#include "fu-usi-dock-common.h" +#include "fu-usi-dock-dmc-device.h" +#include "fu-usi-dock-mcu-device.h" + +struct _FuUsiDockMcuDevice { + FuHidDevice parent_instance; +}; + +G_DEFINE_TYPE(FuUsiDockMcuDevice, fu_usi_dock_mcu_device, FU_TYPE_HID_DEVICE) + +#define FU_USI_DOCK_MCU_DEVICE_TIMEOUT 5000 /* ms */ + +#define FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP (1 << 0) + +static gboolean +fu_usi_dock_mcu_device_tx(FuUsiDockMcuDevice *self, + guint8 tag2, + const guint8 *inbuf, + gsize inbufsz, + GError **error) +{ + UsiDockSetReportBuf tx_buffer = { + .id = USB_HID_REPORT_ID2, + .length = 0x3 + inbufsz, + .mcutag1 = 0xFE, + .mcutag2 = 0xFF, + .mcutag3 = tag2, + }; + + if (inbuf != NULL) { + if (!fu_memcpy_safe(tx_buffer.inbuf, + sizeof(tx_buffer.inbuf), + 0x0, /* dst */ + inbuf, + inbufsz, + 0x0, /* src */ + inbufsz, + error)) + return FALSE; + } + + /* special cases */ + if (tx_buffer.inbuf[0] == USBUID_ISP_INTERNAL_FW_CMD_UPDATE_FW) { + tx_buffer.inbuf[1] = 0xFF; + } + + return fu_hid_device_set_report(FU_HID_DEVICE(self), + USB_HID_REPORT_ID2, + (guint8 *)&tx_buffer, + sizeof(tx_buffer), + FU_USI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error); +} + +static gboolean +fu_usi_dock_mcu_device_rx(FuUsiDockMcuDevice *self, + guint8 cmd, + guint8 *outbuf, + gsize outbufsz, + GError **error) +{ + guint8 buf[64] = {0}; + + if (!fu_hid_device_get_report(FU_HID_DEVICE(self), + USB_HID_REPORT_ID2, + buf, + sizeof(buf), + FU_USI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) { + return FALSE; + } + if (buf[0] != USB_HID_REPORT_ID2) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid ID, expected 0x%02x, got 0x%02x", + USB_HID_REPORT_ID2, + buf[0]); + return FALSE; + } + if (buf[2] != 0xFE || buf[3] != 0xFF) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid tags, expected 0x%02x:0x%02x, got 0x%02x:0x%02x", + 0xFEu, + 0xFFu, + buf[2], + buf[3]); + return FALSE; + } + + if (outbuf != NULL) { + if (!fu_memcpy_safe(outbuf, + outbufsz, + 0x0, /* dst */ + buf, + sizeof(buf), + 0x5, /* src */ + outbufsz, + error)) + return FALSE; + } + return TRUE; +} + +static gboolean +fu_usi_dock_mcu_device_txrx(FuUsiDockMcuDevice *self, + guint8 tag2, + const guint8 *inbuf, + gsize inbufsz, + guint8 *outbuf, + gsize outbufsz, + GError **error) +{ + if (!fu_usi_dock_mcu_device_tx(self, tag2, inbuf, inbufsz, error)) + return FALSE; + return fu_usi_dock_mcu_device_rx(self, USBUID_ISP_CMD_ALL, outbuf, outbufsz, error); +} + +static gboolean +fu_usi_dock_mcu_device_get_status(FuUsiDockMcuDevice *self, GError **error) +{ + guint8 cmd = USBUID_ISP_DEVICE_CMD_MCU_STATUS; + guint8 response = 0; + + if (!fu_usi_dock_mcu_device_txrx(self, + TAG_TAG2_CMD_MCU, + &cmd, + sizeof(cmd), + &response, + sizeof(response), + error)) + return FALSE; + if (response == 0x1) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "device is busy"); + return FALSE; + } + if (response == 0xFF) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "device timed out"); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_usi_dock_mcu_device_enumerate_children(FuUsiDockMcuDevice *self, GError **error) +{ + guint8 inbuf[] = {USBUID_ISP_DEVICE_CMD_READ_MCU_VERSIONPAGE, + DP_VERSION_FROM_MCU | NIC_VERSION_FROM_MCU}; + guint8 outbuf[49] = {0x0}; + struct { + const gchar *name; + guint8 chip_idx; + gsize offset; + } components[] = { + {"DMC", FIRMWARE_IDX_DMC_PD, G_STRUCT_OFFSET(IspVersionInMcu_t, DMC)}, + {"PD", FIRMWARE_IDX_DP, G_STRUCT_OFFSET(IspVersionInMcu_t, PD)}, + {"DP5x", FIRMWARE_IDX_NONE, G_STRUCT_OFFSET(IspVersionInMcu_t, DP5x)}, + {"DP6x", FIRMWARE_IDX_NONE, G_STRUCT_OFFSET(IspVersionInMcu_t, DP6x)}, + {"TBT4", FIRMWARE_IDX_TBT4, G_STRUCT_OFFSET(IspVersionInMcu_t, TBT4)}, + {"USB3", FIRMWARE_IDX_USB3, G_STRUCT_OFFSET(IspVersionInMcu_t, USB3)}, + {"USB2", FIRMWARE_IDX_USB2, G_STRUCT_OFFSET(IspVersionInMcu_t, USB2)}, + {"AUDIO", FIRMWARE_IDX_AUDIO, G_STRUCT_OFFSET(IspVersionInMcu_t, AUDIO)}, + {"I255", FIRMWARE_IDX_I225, G_STRUCT_OFFSET(IspVersionInMcu_t, I255)}, + {"MCU", FIRMWARE_IDX_MCU, G_STRUCT_OFFSET(IspVersionInMcu_t, MCU)}, + {"bcdVersion", FIRMWARE_IDX_NONE, G_STRUCT_OFFSET(IspVersionInMcu_t, bcdVersion)}, + {NULL, 0, 0}}; + + /* assume DP and NIC in-use */ + if (!fu_usi_dock_mcu_device_txrx(self, + TAG_TAG2_CMD_MCU, + inbuf, + sizeof(inbuf), + outbuf, + sizeof(outbuf), + error)) + return FALSE; + + for (guint i = 0; components[i].name != NULL; i++) { + const guint8 *val = outbuf + components[i].offset; + g_autofree gchar *version = NULL; + g_autofree gchar *instance_id = NULL; + g_autoptr(FuDevice) child = NULL; + + child = fu_usi_dock_child_new(fu_device_get_context(FU_DEVICE(self))); + if (g_strcmp0(components[i].name, "bcdVersion") == 0) { + if ((val[0] == 0x00 && val[1] == 0x00) || + (val[0] == 0xFF && val[1] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + if (fu_device_has_private_flag(FU_DEVICE(self), + FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP)) { + version = g_strdup_printf("%x.%x.%x.%x", + val[0] >> 4, + val[0] & 0xFu, + val[1] >> 4, + val[1] & 0xFu); + fu_device_set_version_format(FU_DEVICE(self), + FWUPD_VERSION_FORMAT_QUAD); + fu_device_set_version(FU_DEVICE(self), version); + } else { + version = g_strdup_printf("%x.%x.%02x", + val[0] & 0xFu, + val[0] >> 4, + val[1]); + g_debug("ignoring %s --> %s", components[i].name, version); + } + continue; + } else if (g_strcmp0(components[i].name, "DMC") == 0) { + if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || + (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + version = g_strdup_printf("%d.%d.%d", val[2], val[3], val[4]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); + fu_device_set_version(child, version); + fu_device_set_name(child, "Dock Management Controller"); + } else if (g_strcmp0(components[i].name, "PD") == 0) { + if ((val[1] == 0x00 && val[2] == 0x00 && val[3] == 0x00 && + val[4] == 0x00) || + (val[1] == 0xFF && val[2] == 0xFF && val[3] == 0xFF && + val[4] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + if (fu_device_has_private_flag(FU_DEVICE(self), + FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP)) { + version = + g_strdup_printf("%d.%d.%d.%d", val[3], val[4], val[1], val[2]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_QUAD); + } else { + version = g_strdup_printf("%d.%d.%d", val[2], val[3], val[4]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); + } + fu_device_set_version(child, version); + fu_device_set_name(child, "Power Delivery"); + } else if (g_strcmp0(components[i].name, "TBT4") == 0) { + if ((val[1] == 0x00 && val[2] == 0x00 && val[3] == 0x00) || + (val[1] == 0xFF && val[2] == 0xFF && val[3] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + version = g_strdup_printf("%02x.%02x.%02x", val[1], val[2], val[3]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); + fu_device_set_version(child, version); + fu_device_add_icon(child, "thunderbolt"); + fu_device_set_name(child, "Thunderbolt 4 Controller"); + } else if (g_strcmp0(components[i].name, "DP5x") == 0) { + if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || + (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + version = g_strdup_printf("%d.%02d.%03d", val[2], val[3], val[4]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); + fu_device_set_version(child, version); + fu_device_add_icon(child, "video-display"); + fu_device_set_name(child, "Display Port 5"); + } else if (g_strcmp0(components[i].name, "DP6x") == 0) { + if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || + (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + if (fu_device_has_private_flag(FU_DEVICE(self), + FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP)) { + version = + g_strdup_printf("%x.%x.%x.%x", val[3], val[4], val[2], val[1]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_QUAD); + fu_device_set_name(child, "USB/PD HUB"); + } else { + version = g_strdup_printf("%d.%02d.%03d", val[2], val[3], val[4]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); + fu_device_set_name(child, "Display Port 6"); + } + fu_device_set_version(child, version); + fu_device_add_icon(child, "video-display"); + } else if (g_strcmp0(components[i].name, "USB3") == 0) { + if ((val[3] == 0x00 && val[4] == 0x00) || + (val[3] == 0xFF && val[4] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + version = g_strdup_printf("%02X%02X", val[3], val[4]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_NUMBER); + fu_device_set_version(child, version); + fu_device_set_name(child, "USB 3 Hub"); + } else if (g_strcmp0(components[i].name, "USB2") == 0) { + if ((val[0] == 0x00 && val[1] == 0x00 && val[2] == 0x00 && val[3] == 0x00 && + val[4] == 0x00) || + (val[0] == 0xFF && val[1] == 0xFF && val[2] == 0xFF && val[3] == 0xFF && + val[4] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + version = + g_strdup_printf("%c%c%c%c%c", val[0], val[1], val[2], val[3], val[4]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_PLAIN); + fu_device_set_version(child, version); + fu_device_set_name(child, "USB 2 Hub"); + } else if (g_strcmp0(components[i].name, "AUDIO") == 0) { + if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || + (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + version = g_strdup_printf("%02X-%02X-%02X", val[2], val[3], val[4]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_PLAIN); + fu_device_set_version(child, version); + fu_device_set_name(child, "Audio Controller"); + } else if (g_strcmp0(components[i].name, "I255") == 0) { + if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || + (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + version = g_strdup_printf("%x.%x.%x", val[2] >> 4, val[3], val[4]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); + fu_device_set_version(child, version); + fu_device_add_icon(child, "network-wired"); + fu_device_set_name(child, "Ethernet Adapter"); + } else if (g_strcmp0(components[i].name, "MCU") == 0) { + if ((val[0] == 0x00 && val[1] == 0x00) || + (val[0] == 0xFF && val[1] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + if (fu_device_has_private_flag(FU_DEVICE(self), + FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP)) { + version = g_strdup_printf("%x.%x.%x.%x", + val[0] >> 4, + val[0] & 0xFu, + val[1] >> 4, + val[1] & 0xFu); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_QUAD); + } else { + version = g_strdup_printf("%X.%X", val[0], val[1]); + fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_PLAIN); + } + fu_device_set_version(child, version); + fu_device_set_name(child, "Dock Management Controller"); + } else { + g_warning("unhandled %s", components[i].name); + } + + /* add virtual device */ + instance_id = g_strdup_printf("USB\\VID_%04X&PID_%04X&CID_%s", + fu_usb_device_get_vid(FU_USB_DEVICE(self)), + fu_usb_device_get_pid(FU_USB_DEVICE(self)), + components[i].name); + fu_device_add_instance_id(child, instance_id); + if (fu_device_get_name(child) == NULL) + fu_device_set_name(child, components[i].name); + fu_device_set_logical_id(child, components[i].name); + fu_usi_dock_child_device_set_chip_idx(FU_USI_DOCK_CHILD_DEVICE(child), + components[i].chip_idx); + fu_device_add_child(FU_DEVICE(self), child); + } + + /* success */ + return TRUE; +} + +static gboolean +fu_usi_dock_mcu_device_setup(FuDevice *device, GError **error) +{ + FuUsiDockMcuDevice *self = FU_USI_DOCK_MCU_DEVICE(device); + + /* FuUsbDevice->setup */ + if (!FU_DEVICE_CLASS(fu_usi_dock_mcu_device_parent_class)->setup(device, error)) + return FALSE; + + /* get status and component versions */ + if (!fu_usi_dock_mcu_device_get_status(self, error)) + return FALSE; + if (!fu_usi_dock_mcu_device_enumerate_children(self, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_usi_dock_mcu_device_write_chunk(FuUsiDockMcuDevice *self, FuChunk *chk, GError **error) +{ + guint8 buf[64] = {0x0}; + guint32 length = 0; + guint32 pagesize = fu_chunk_get_data_sz(chk); + + while (pagesize != 0) { + memset(buf, 0x0, sizeof(buf)); + buf[63] = TAG_TAG2_MASS_DATA_SPI; + buf[0] = USB_HID_REPORT_ID2; + + /* set length and buffer */ + if (pagesize >= TX_ISP_LENGTH) { + length = TX_ISP_LENGTH; + pagesize -= TX_ISP_LENGTH; + } else { + length = pagesize; + pagesize = 0; + } + buf[1] = length; + + /* SetReport */ + if (!fu_memcpy_safe(buf, + sizeof(buf), + 0x2, /* dst */ + fu_chunk_get_data(chk), + fu_chunk_get_data_sz(chk), + fu_chunk_get_data_sz(chk) - pagesize - length, /* src */ + length, + error)) + return FALSE; + + if (!fu_hid_device_set_report(FU_HID_DEVICE(self), + USB_HID_REPORT_ID2, + buf, + sizeof(buf), + FU_USI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) + return FALSE; + + /* GetReport */ + memset(buf, 0x0, sizeof(buf)); + if (!fu_hid_device_get_report(FU_HID_DEVICE(self), + USB_HID_REPORT_ID2, + buf, + sizeof(buf), + FU_USI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) { + return FALSE; + } + if (buf[0] != USB_HID_REPORT_ID2) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid ID, expected 0x%02x, got 0x%02x", + USB_HID_REPORT_ID2, + buf[0]); + return FALSE; + } + if (buf[63] != TAG_TAG2_CMD_SPI) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid tag2, expected 0x%02x, got 0x%02x", + (guint)TAG_TAG2_CMD_SPI, + buf[58]); + return FALSE; + } + } + return TRUE; +} + +static gboolean +fu_usi_dock_mcu_device_write_chunks(FuUsiDockMcuDevice *self, + GPtrArray *chunks, + FuProgress *progress, + GError **error) +{ + 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 (!fu_usi_dock_mcu_device_write_chunk(self, chk, error)) { + g_prefix_error(error, "failed to write chunk 0x%x", i); + return FALSE; + } + fu_progress_step_done(progress); + } + return TRUE; +} + +static gboolean +fu_usi_dock_mcu_device_wait_for_spi_ready_cb(FuDevice *device, gpointer user_data, GError **error) +{ + FuUsiDockMcuDevice *self = FU_USI_DOCK_MCU_DEVICE(device); + guint8 buf[] = {USBUID_ISP_DEVICE_CMD_FWBUFER_READ_STATUS}; + guint8 val = 0; + + if (!fu_usi_dock_mcu_device_txrx(self, + TAG_TAG2_CMD_SPI, + buf, + sizeof(buf), + &val, + sizeof(val), + error)) + return FALSE; + if (val != SPI_STATE_READY) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_BUSY, + "SPI state is %s [0x%02x]", + fu_usi_dock_spi_state_to_string(val), + val); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_usi_dock_mcu_device_wait_for_checksum_cb(FuDevice *device, gpointer user_data, GError **error) +{ + FuUsiDockMcuDevice *self = FU_USI_DOCK_MCU_DEVICE(device); + + if (!fu_usi_dock_mcu_device_rx(self, + USBUID_ISP_CMD_ALL, + (guint8 *)user_data, + sizeof(guint8), + error)) + return FALSE; + + /* success */ + return TRUE; +} + +gboolean +fu_usi_dock_mcu_device_write_firmware_with_idx(FuUsiDockMcuDevice *self, + FuFirmware *firmware, + guint8 chip_idx, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error) +{ + guint8 cmd; + guint8 val = 0x0; + g_autoptr(GBytes) fw = NULL; + g_autoptr(GPtrArray) chunks = NULL; + guint8 checksum = 0xFF; + + /* progress */ + fu_progress_set_id(progress, G_STRLOC); + fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 6); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 40); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 13); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 42); + + /* initial external flash */ + cmd = USBUID_ISP_DEVICE_CMD_FWBUFER_INITIAL; + if (!fu_usi_dock_mcu_device_txrx(self, + TAG_TAG2_CMD_SPI, + &cmd, + sizeof(cmd), + &val, + sizeof(val), + error)) + return FALSE; + if (val != SPI_STATE_READY) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid state for CMD_FWBUFER_INITIAL, got 0x%02x", + val); + return FALSE; + } + fu_progress_step_done(progress); + + /* erase external flash */ + cmd = USBUID_ISP_DEVICE_CMD_FWBUFER_ERASE_FLASH; + if (!fu_usi_dock_mcu_device_txrx(self, + TAG_TAG2_CMD_SPI, + &cmd, + sizeof(cmd), + NULL, + 0x0, + error)) + return FALSE; + if (!fu_device_retry(FU_DEVICE(self), + fu_usi_dock_mcu_device_wait_for_spi_ready_cb, + 30, + NULL, + error)) { + g_prefix_error(error, "failed to wait for erase: "); + return FALSE; + } + fu_progress_step_done(progress); + + /* write external flash */ + cmd = USBUID_ISP_DEVICE_CMD_FWBUFER_PROGRAM; + if (!fu_usi_dock_mcu_device_txrx(self, + TAG_TAG2_CMD_SPI, + &cmd, + sizeof(cmd), + NULL, + 0x0, + error)) + return FALSE; + + fw = fu_firmware_get_bytes(firmware, error); + if (fw == NULL) + return FALSE; + chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, W25Q16DV_PAGE_SIZE); + if (!fu_usi_dock_mcu_device_write_chunks(self, + chunks, + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + + /* file transfer – finished */ + cmd = USBUID_ISP_DEVICE_CMD_FWBUFER_TRANSFER_FINISH; + if (!fu_usi_dock_mcu_device_txrx(self, + TAG_TAG2_CMD_SPI, + &cmd, + sizeof(cmd), + NULL, + 0x0, + error)) + return FALSE; + + /* MCU checksum */ + if (!fu_device_retry(FU_DEVICE(self), + fu_usi_dock_mcu_device_wait_for_checksum_cb, + 300, + &checksum, + error)) { + g_prefix_error(error, "failed to wait for checksum: "); + return FALSE; + } + + if (checksum != 0x0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid checksum result for CMD_FWBUFER_CHECKSUM, got 0x%02x", + checksum); + return FALSE; + } + fu_progress_step_done(progress); + + /* internal flash */ + cmd = USBUID_ISP_INTERNAL_FW_CMD_UPDATE_FW; + if (!fu_usi_dock_mcu_device_txrx(self, + TAG_TAG2_CMD_MCU, + &cmd, + sizeof(cmd), + NULL, + 0x0, + error)) + return FALSE; + fu_progress_step_done(progress); + + /* success */ + return TRUE; +} + +static gboolean +fu_usi_dock_mcu_device_write_firmware(FuDevice *device, + FuFirmware *firmware, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error) +{ + return fu_usi_dock_mcu_device_write_firmware_with_idx(FU_USI_DOCK_MCU_DEVICE(device), + firmware, + 0xFF, /* all */ + progress, + flags, + error); +} + +static gboolean +fu_usi_dock_mcu_device_attach(FuDevice *device, FuProgress *progress, GError **error) +{ + fu_device_set_remove_delay(device, 500000); + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); + + /* success */ + return TRUE; +} + +static void +fu_usi_dock_mcu_device_set_progress(FuDevice *self, FuProgress *progress) +{ + fu_progress_set_id(progress, G_STRLOC); + fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 2); /* erase */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); /* write */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 6); /* attach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2); /* reload */ +} + +static void +fu_usi_dock_mcu_device_init(FuUsiDockMcuDevice *self) +{ + fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); + fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); + + fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER); + fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN); + fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); + + fu_device_register_private_flag(FU_DEVICE(self), + FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP, + "verfmt-hp"); + fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER); + fu_device_add_protocol(FU_DEVICE(self), "com.usi.dock"); + fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_NUMBER); + fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); + fu_device_retry_set_delay(FU_DEVICE(self), 1000); + fu_device_add_icon(FU_DEVICE(self), "dock"); +} + +static void +fu_usi_dock_mcu_device_class_init(FuUsiDockMcuDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + + klass_device->write_firmware = fu_usi_dock_mcu_device_write_firmware; + klass_device->attach = fu_usi_dock_mcu_device_attach; + klass_device->setup = fu_usi_dock_mcu_device_setup; + klass_device->set_progress = fu_usi_dock_mcu_device_set_progress; +} diff --git a/plugins/usi-dock/fu-usi-dock-mcu-device.h b/plugins/usi-dock/fu-usi-dock-mcu-device.h new file mode 100644 index 000000000..251d8ad39 --- /dev/null +++ b/plugins/usi-dock/fu-usi-dock-mcu-device.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_USI_DOCK_MCU_DEVICE (fu_usi_dock_mcu_device_get_type()) +G_DECLARE_FINAL_TYPE(FuUsiDockMcuDevice, + fu_usi_dock_mcu_device, + FU, + USI_DOCK_MCU_DEVICE, + FuHidDevice) +gboolean +fu_usi_dock_mcu_device_write_firmware_with_idx(FuUsiDockMcuDevice *self, + FuFirmware *firmware, + guint8 chip_idx, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error); diff --git a/plugins/usi-dock/meson.build b/plugins/usi-dock/meson.build new file mode 100644 index 000000000..060bafca5 --- /dev/null +++ b/plugins/usi-dock/meson.build @@ -0,0 +1,33 @@ +if get_option('gusb') +cargs = ['-DG_LOG_DOMAIN="FuPluginUsiDock"'] + +install_data(['usi-dock.quirk'], + install_dir: join_paths(datadir, 'fwupd', 'quirks.d') +) + +shared_module('fu_plugin_usi_dock', + fu_hash, + sources : [ + 'fu-usi-dock-common.c', + 'fu-usi-dock-child-device.c', + 'fu-usi-dock-dmc-device.c', + 'fu-usi-dock-mcu-device.c', + 'fu-plugin-usi-dock.c', + ], + include_directories : [ + root_incdir, + fwupd_incdir, + fwupdplugin_incdir, + ], + install : true, + install_dir: plugin_dir, + link_with : [ + fwupd, + fwupdplugin, + ], + c_args : cargs, + dependencies : [ + plugin_deps, + ], +) +endif diff --git a/plugins/usi-dock/usi-dock.quirk b/plugins/usi-dock/usi-dock.quirk new file mode 100644 index 000000000..af141b753 --- /dev/null +++ b/plugins/usi-dock/usi-dock.quirk @@ -0,0 +1,15 @@ +[USB\VID_17EF&PID_30B4] +Plugin = usi_dock +GType = FuUsiDockMcuDevice +Name = ThinkPad Thunderbolt 4 Dock + +[USB\VID_17EF&PID_30B5] +Plugin = usi_dock +GType = FuUsiDockDmcDevice +ParentGuid = USB\VID_17EF&PID_30B4 + +[USB\VID_03F0&PID_0505] +Plugin = usi_dock +GType = FuUsiDockMcuDevice +Name = USB-C G2 Multiport Hub MCU Controller +Flags = verfmt-hp