diff --git a/plugins/meson.build b/plugins/meson.build index 33ebdfea0..741bfec99 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -78,6 +78,7 @@ subdir('pci-mei') subdir('pci-psp') subdir('pixart-rf') subdir('powerd') +subdir('qsi-dock') subdir('realtek-mst') subdir('redfish') subdir('rts54hid') diff --git a/plugins/qsi-dock/README.md b/plugins/qsi-dock/README.md new file mode 100644 index 000000000..9a2699e56 --- /dev/null +++ b/plugins/qsi-dock/README.md @@ -0,0 +1,32 @@ +# QSI 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.qsi.dock + +## GUID Generation + +These devices use the standard USB DeviceInstanceId values, e.g. + +* `USB\VID_047D&PID_808D&REV_0001` +* `USB\VID_047D&PID_808D` +* `USB\VID_047D` + +## 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/qsi-dock/fu-qsi-dock-child-device.c b/plugins/qsi-dock/fu-qsi-dock-child-device.c new file mode 100644 index 000000000..40c240ab9 --- /dev/null +++ b/plugins/qsi-dock/fu-qsi-dock-child-device.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-qsi-dock-child-device.h" +#include "fu-qsi-dock-mcu-device.h" + +struct _FuQsiDockChildDevice { + FuDevice parent_instance; + guint8 chip_idx; +}; + +G_DEFINE_TYPE(FuQsiDockChildDevice, fu_qsi_dock_child_device, FU_TYPE_DEVICE) + +void +fu_qsi_dock_child_device_set_chip_idx(FuQsiDockChildDevice *self, guint8 chip_idx) +{ + self->chip_idx = chip_idx; +} + +static void +fu_qsi_dock_child_device_to_string(FuDevice *device, guint idt, GString *str) +{ + FuQsiDockChildDevice *self = FU_QSI_DOCK_CHILD_DEVICE(device); + fu_string_append_kx(str, idt, "ChipIdx", self->chip_idx); +} + +/* use the parents parser */ +static FuFirmware * +fu_qsi_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_qsi_dock_mcu_device_write_firmware(FuDevice *device, + FuFirmware *firmware, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error) +{ + FuQsiDockChildDevice *self = FU_QSI_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_qsi_dock_mcu_device_write_firmware_with_idx(FU_QSI_DOCK_MCU_DEVICE(parent), + firmware, + self->chip_idx, + progress, + flags, + error); +} + +static void +fu_qsi_dock_child_device_init(FuQsiDockChildDevice *self) +{ + fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); +} + +static void +fu_qsi_dock_child_device_class_init(FuQsiDockChildDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + klass_device->to_string = fu_qsi_dock_child_device_to_string; + klass_device->prepare_firmware = fu_qsi_dock_mcu_device_prepare_firmware; + klass_device->write_firmware = fu_qsi_dock_mcu_device_write_firmware; +} + +FuDevice * +fu_qsi_dock_child_new(FuContext *ctx) +{ + return g_object_new(FU_TYPE_QSI_DOCK_CHILD_DEVICE, "context", ctx, NULL); +} diff --git a/plugins/qsi-dock/fu-qsi-dock-child-device.h b/plugins/qsi-dock/fu-qsi-dock-child-device.h new file mode 100644 index 000000000..6e910d0cd --- /dev/null +++ b/plugins/qsi-dock/fu-qsi-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_QSI_DOCK_CHILD_DEVICE (fu_qsi_dock_child_device_get_type()) +G_DECLARE_FINAL_TYPE(FuQsiDockChildDevice, + fu_qsi_dock_child_device, + FU, + QSI_DOCK_CHILD_DEVICE, + FuDevice) + +FuDevice * +fu_qsi_dock_child_new(FuContext *ctx); +void +fu_qsi_dock_child_device_set_chip_idx(FuQsiDockChildDevice *self, guint8 chip_idx); diff --git a/plugins/qsi-dock/fu-qsi-dock-common.h b/plugins/qsi-dock/fu-qsi-dock-common.h new file mode 100644 index 000000000..e52f3f652 --- /dev/null +++ b/plugins/qsi-dock/fu-qsi-dock-common.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 Richard Hughes + * Copyright (C) 2022 Kevin Chen + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_QSI_DOCK_REPORT_ID 5 + +#define FU_QSI_DOCK_FIRMWARE_IDX_NONE 0x00 +#define FU_QSI_DOCK_FIRMWARE_IDX_DMC_PD 0x01 +#define FU_QSI_DOCK_FIRMWARE_IDX_DP 0x02 +#define FU_QSI_DOCK_FIRMWARE_IDX_TBT4 0x04 +#define FU_QSI_DOCK_FIRMWARE_IDX_USB3 0x08 +#define FU_QSI_DOCK_FIRMWARE_IDX_USB2 0x10 +#define FU_QSI_DOCK_FIRMWARE_IDX_AUDIO 0x20 +#define FU_QSI_DOCK_FIRMWARE_IDX_I225 0x40 +#define FU_QSI_DOCK_FIRMWARE_IDX_MCU 0x80 + +typedef struct __attribute__((packed)) { + 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]; +} FuQsiDockIspVersionInMcu; + +typedef enum { + FU_QSI_DOCK_CMD1_BOOT = 0x11, + FU_QSI_DOCK_CMD1_SYSTEM = 0x31, + FU_QSI_DOCK_CMD1_MCU = 0x51, + FU_QSI_DOCK_CMD1_SPI = 0x61, + FU_QSI_DOCK_CMD1_I2C_VMM = 0x71, + FU_QSI_DOCK_CMD1_I2C_CCG = 0x81, + + FU_QSI_DOCK_CMD1_MASS_MCU = 0xC0, + FU_QSI_DOCK_CMD1_MASS_SPI, + FU_QSI_DOCK_CMD1_MASS_I2C_VMM, + FU_QSI_DOCK_CMD1_MASS_I2C_CY, +} FuQsiDockCmd; + +typedef enum { + FU_QSI_DOCK_CMD2_CMD_DEVICE_STATUS, + FU_QSI_DOCK_CMD2_CMD_SET_BOOT_MODE, + FU_QSI_DOCK_CMD2_CMD_SET_AP_MODE, + FU_QSI_DOCK_CMD2_CMD_ERASE_AP_PAGE, + FU_QSI_DOCK_CMD2_CMD_CHECKSUM, + FU_QSI_DOCK_CMD2_CMD_DEVICE_VERSION, + FU_QSI_DOCK_CMD2_CMD_DEVICE_PCB_VERSION, + FU_QSI_DOCK_CMD2_CMD_DEVICE_SN, +} FuQsiDockCmd2_0x51; + +typedef enum { + FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_INI, + FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_ERASE, + FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_CHECKSUM, +} FuQsiDockCmd2_0x61; diff --git a/plugins/qsi-dock/fu-qsi-dock-mcu-device.c b/plugins/qsi-dock/fu-qsi-dock-mcu-device.c new file mode 100644 index 000000000..455786535 --- /dev/null +++ b/plugins/qsi-dock/fu-qsi-dock-mcu-device.c @@ -0,0 +1,580 @@ +/* + * Copyright (C) 2021 Richard Hughes + * Copyright (C) 2022 Kevin Chen + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-qsi-dock-child-device.h" +#include "fu-qsi-dock-common.h" +#include "fu-qsi-dock-mcu-device.h" + +struct _FuQsiDockMcuDevice { + FuHidDevice parent_instance; +}; + +G_DEFINE_TYPE(FuQsiDockMcuDevice, fu_qsi_dock_mcu_device, FU_TYPE_HID_DEVICE) + +#define FU_QSI_DOCK_MCU_DEVICE_TIMEOUT 90000 /* ms */ + +#define FU_QSI_DOCK_TX_ISP_LENGTH 61 +#define FU_QSI_DOCK_TX_ISP_LENGTH_MCU 60 +#define FU_QSI_DOCK_EXTERN_FLASH_PAGE_SIZE 256 + +static gboolean +fu_qsi_dock_mcu_device_tx(FuQsiDockMcuDevice *self, + guint8 CmdPrimary, + guint8 CmdSecond, + const guint8 *inbuf, + gsize inbufsz, + GError **error) +{ + guint8 buf[64] = {FU_QSI_DOCK_REPORT_ID, CmdPrimary, CmdSecond}; + + return fu_hid_device_set_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error); +} + +static gboolean +fu_qsi_dock_mcu_device_rx(FuQsiDockMcuDevice *self, guint8 *outbuf, gsize outbufsz, GError **error) +{ + guint8 buf[64]; + + if (!fu_hid_device_get_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) { + 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_qsi_dock_mcu_device_txrx(FuQsiDockMcuDevice *self, + guint8 cmd1, + guint8 cmd2, + const guint8 *inbuf, + gsize inbufsz, + guint8 *outbuf, + gsize outbufsz, + GError **error) +{ + if (!fu_qsi_dock_mcu_device_tx(self, cmd1, cmd2, inbuf, inbufsz, error)) + return FALSE; + return fu_qsi_dock_mcu_device_rx(self, outbuf, outbufsz, error); +} + +static gboolean +fu_qsi_dock_mcu_device_get_status(FuQsiDockMcuDevice *self, GError **error) +{ + guint8 response = 0; + guint8 cmd1 = FU_QSI_DOCK_CMD1_MCU; + guint8 cmd2 = FU_QSI_DOCK_CMD2_CMD_DEVICE_STATUS; + + if (!fu_qsi_dock_mcu_device_txrx(self, + cmd1, + cmd2, + &cmd1, + sizeof(cmd1), + &response, + sizeof(response), + error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_qsi_dock_mcu_device_enumerate_children(FuQsiDockMcuDevice *self, GError **error) +{ + guint8 outbuf[49] = {0}; + struct { + const gchar *name; + guint8 chip_idx; + gsize offset; + } components[] = { + {"MCU", FU_QSI_DOCK_FIRMWARE_IDX_MCU, G_STRUCT_OFFSET(FuQsiDockIspVersionInMcu, MCU)}, + {"bcdVersion", + FU_QSI_DOCK_FIRMWARE_IDX_NONE, + G_STRUCT_OFFSET(FuQsiDockIspVersionInMcu, bcdVersion)}, + {NULL, 0, 0}}; + + for (guint i = 0; components[i].name != NULL; i++) { + const guint8 *val = outbuf + components[i].offset; + g_autofree gchar *version = NULL; + g_autoptr(FuDevice) child = NULL; + + child = fu_qsi_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; + } + + 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, "MCU") == 0) { + if ((val[0] == 0x00 && val[1] == 0x00) || + (val[0] == 0xFF && val[1] == 0xFF)) { + g_debug("ignoring %s", components[i].name); + continue; + } + + 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 */ + fu_device_add_instance_u16(child, + "VID", + fu_usb_device_get_vid(FU_USB_DEVICE(self))); + fu_device_add_instance_u16(child, + "PID", + fu_usb_device_get_pid(FU_USB_DEVICE(self))); + fu_device_add_instance_str(child, "CID", components[i].name); + if (!fu_device_build_instance_id(child, error, "USB", "VID", "PID", "CID", NULL)) + return FALSE; + 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_qsi_dock_child_device_set_chip_idx(FU_QSI_DOCK_CHILD_DEVICE(child), + components[i].chip_idx); + fu_device_add_child(FU_DEVICE(self), child); + } + + /* success */ + return TRUE; +} + +static gboolean +fu_qsi_dock_mcu_device_setup(FuDevice *device, GError **error) +{ + FuQsiDockMcuDevice *self = FU_QSI_DOCK_MCU_DEVICE(device); + + /* FuUsbDevice->setup */ + if (!FU_DEVICE_CLASS(fu_qsi_dock_mcu_device_parent_class)->setup(device, error)) + return FALSE; + + /* get status and component versions */ + if (!fu_qsi_dock_mcu_device_get_status(self, error)) + return FALSE; + if (!fu_qsi_dock_mcu_device_enumerate_children(self, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_qsi_dock_mcu_device_checksum(FuQsiDockMcuDevice *self, + guint32 checksum, + guint32 length, + GError **error) +{ + guint8 buf[64] = {FU_QSI_DOCK_REPORT_ID, + FU_QSI_DOCK_CMD1_SPI, + FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_CHECKSUM, + 0}; + guint8 fw_length[4]; + guint8 checksum_val[2]; + + fu_memwrite_uint32(fw_length, length, G_LITTLE_ENDIAN); + fu_memwrite_uint16(checksum_val, checksum, G_LITTLE_ENDIAN); + + /* fw length */ + if (!fu_memcpy_safe(buf, + sizeof(buf), + 0x03, /* dst */ + fw_length, + sizeof(fw_length), + 0x0, + sizeof(fw_length), + error)) + return FALSE; + + /* checksum */ + if (!fu_memcpy_safe(buf, + sizeof(buf), + 0x07, /* dst */ + checksum_val, + sizeof(checksum_val), + 0x0, + sizeof(checksum_val), + error)) + return FALSE; + + /* SetReport+GetReport */ + if (!fu_hid_device_set_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) + return FALSE; + memset(buf, 0x0, sizeof(buf)); + if (!fu_hid_device_get_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) + return FALSE; + + /* MCU Checksum Compare Result 0:Pass 1:Fail */ + if (buf[2] != 0) { + g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum did not match"); + return FALSE; + } + + return TRUE; +} + +static gboolean +fu_qsi_dock_mcu_device_write_chunk(FuQsiDockMcuDevice *self, + FuChunk *chk_page, + guint32 *checksum_tmp, + FuProgress *progress, + GError **error) +{ + g_autoptr(GPtrArray) chunks = NULL; + + chunks = fu_chunk_array_new(fu_chunk_get_data(chk_page), + fu_chunk_get_data_sz(chk_page), + 0x0, + 0x0, + FU_QSI_DOCK_TX_ISP_LENGTH_MCU); + 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); + guint8 checksum_buf[FU_QSI_DOCK_TX_ISP_LENGTH_MCU] = {0x0}; + guint8 buf[64] = {FU_QSI_DOCK_REPORT_ID, + FU_QSI_DOCK_CMD1_MASS_SPI, + fu_chunk_get_data_sz(chk)}; + + /* SetReport */ + if (!fu_memcpy_safe(buf, + sizeof(buf), + 0x04, /* dst */ + fu_chunk_get_data(chk), + fu_chunk_get_data_sz(chk), + 0x0, /* src */ + fu_chunk_get_data_sz(chk), + error)) + return FALSE; + if (!fu_hid_device_set_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) + return FALSE; + + /* sum checksum value */ + if (!fu_memcpy_safe(checksum_buf, + sizeof(checksum_buf), + 0x0, /* dst */ + fu_chunk_get_data(chk), + fu_chunk_get_data_sz(chk), + 0x0, /* src */ + fu_chunk_get_data_sz(chk), + error)) + return FALSE; + *checksum_tmp += fu_sum32(checksum_buf, sizeof(checksum_buf)); + + /* GetReport */ + memset(buf, 0x0, sizeof(buf)); + if (!fu_hid_device_get_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) + return FALSE; + + /* MCU ACK 0:Pass 1:Fail */ + if (buf[2] != 0) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "ACK error for chunk %u", + i); + return FALSE; + } + fu_progress_step_done(progress); + } + + /* success */ + return TRUE; +} + +static gboolean +fu_qsi_dock_mcu_device_write_chunks(FuQsiDockMcuDevice *self, + GPtrArray *chunks, + guint32 *checksum, + 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_qsi_dock_mcu_device_write_chunk(self, + chk, + checksum, + fu_progress_get_child(progress), + error)) { + g_prefix_error(error, "failed to write chunk 0x%x", i); + return FALSE; + } + fu_progress_step_done(progress); + } + + return TRUE; +} + +static gboolean +fu_qsi_dock_mcu_device_wait_for_spi_initial_ready_cb(FuDevice *device, + gpointer user_data, + GError **error) +{ + FuQsiDockMcuDevice *self = FU_QSI_DOCK_MCU_DEVICE(device); + guint8 buf[64] = {FU_QSI_DOCK_REPORT_ID, + FU_QSI_DOCK_CMD1_SPI, + FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_INI}; + + /* SetReport+GetReport */ + if (!fu_hid_device_set_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) + return FALSE; + memset(buf, 0x0, sizeof(buf)); + if (!fu_hid_device_get_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_qsi_dock_mcu_device_wait_for_spi_erase_ready_cb(FuQsiDockMcuDevice *self, + guint32 length, + gpointer user_data, + GError **error) +{ + guint8 buf[64] = {FU_QSI_DOCK_REPORT_ID, + FU_QSI_DOCK_CMD1_SPI, + FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_ERASE}; + guint32 offset = 0; + guint8 fw_length[4]; + guint8 flash_offset[4]; + + fu_memwrite_uint32(fw_length, length, G_LITTLE_ENDIAN); + fu_memwrite_uint32(flash_offset, offset, G_LITTLE_ENDIAN); + + /* write erase flash size */ + if (!fu_memcpy_safe(buf, + sizeof(buf), + 0x03, /* dst */ + fw_length, + sizeof(fw_length), + 0x0, + sizeof(fw_length), + error)) + return FALSE; + if (!fu_memcpy_safe(buf, + sizeof(buf), + 0x07, /* dst */ + flash_offset, + sizeof(flash_offset), + 0x0, + sizeof(flash_offset), + error)) + return FALSE; + + /* SetReport+GetReport */ + if (!fu_hid_device_set_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) + return FALSE; + memset(buf, 0x0, sizeof(buf)); + if (!fu_hid_device_get_report(FU_HID_DEVICE(self), + FU_QSI_DOCK_REPORT_ID, + buf, + sizeof(buf), + FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, + FU_HID_DEVICE_FLAG_NONE, + error)) + return FALSE; + + /* success */ + return TRUE; +} + +gboolean +fu_qsi_dock_mcu_device_write_firmware_with_idx(FuQsiDockMcuDevice *self, + FuFirmware *firmware, + guint8 chip_idx, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error) +{ + guint32 checksum_val = 0; + g_autoptr(GBytes) fw_align = NULL; + g_autoptr(GBytes) fw = NULL; + g_autoptr(GPtrArray) chunks = NULL; + + /* 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_WRITE, 90, NULL); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, NULL); + + /* align data */ + fw = fu_firmware_get_bytes(firmware, error); + if (fw == NULL) + return FALSE; + fw_align = fu_bytes_align(fw, FU_QSI_DOCK_EXTERN_FLASH_PAGE_SIZE, 0x0); + + /* initial external flash */ + if (!fu_device_retry(FU_DEVICE(self), + fu_qsi_dock_mcu_device_wait_for_spi_initial_ready_cb, + 30, + NULL, + error)) { + g_prefix_error(error, "failed to wait for initial: "); + return FALSE; + } + if (!fu_qsi_dock_mcu_device_wait_for_spi_erase_ready_cb(self, + g_bytes_get_size(fw), + fu_progress_get_child(progress), + error)) + return FALSE; + + /* write external flash */ + chunks = fu_chunk_array_new_from_bytes(fw_align, 0, 0, FU_QSI_DOCK_EXTERN_FLASH_PAGE_SIZE); + if (!fu_qsi_dock_mcu_device_write_chunks(self, + chunks, + &checksum_val, + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + + /* verify flash data */ + if (!fu_qsi_dock_mcu_device_checksum(self, checksum_val, g_bytes_get_size(fw), error)) + return FALSE; + fu_progress_step_done(progress); + + /* success */ + return TRUE; +} + +static gboolean +fu_qsi_dock_mcu_device_write_firmware(FuDevice *device, + FuFirmware *firmware, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error) +{ + return fu_qsi_dock_mcu_device_write_firmware_with_idx(FU_QSI_DOCK_MCU_DEVICE(device), + firmware, + 0xFF, + progress, + flags, + error); +} + +static gboolean +fu_qsi_dock_mcu_device_attach(FuDevice *device, FuProgress *progress, GError **error) +{ + FuQsiDockMcuDevice *self = FU_QSI_DOCK_MCU_DEVICE(device); + + if (!fu_qsi_dock_mcu_device_get_status(self, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static void +fu_qsi_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_RESTART, 0, "detach"); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); +} + +static void +fu_qsi_dock_mcu_device_init(FuQsiDockMcuDevice *self) +{ + fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); + fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER); + 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_NO_SERIAL_NUMBER); + fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_NUMBER); + fu_device_add_protocol(FU_DEVICE(self), "com.qsi.dock"); +} + +static void +fu_qsi_dock_mcu_device_class_init(FuQsiDockMcuDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + + klass_device->setup = fu_qsi_dock_mcu_device_setup; + klass_device->attach = fu_qsi_dock_mcu_device_attach; + klass_device->set_progress = fu_qsi_dock_mcu_device_set_progress; + klass_device->write_firmware = fu_qsi_dock_mcu_device_write_firmware; +} diff --git a/plugins/qsi-dock/fu-qsi-dock-mcu-device.h b/plugins/qsi-dock/fu-qsi-dock-mcu-device.h new file mode 100644 index 000000000..781e552c6 --- /dev/null +++ b/plugins/qsi-dock/fu-qsi-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_QSI_DOCK_MCU_DEVICE (fu_qsi_dock_mcu_device_get_type()) +G_DECLARE_FINAL_TYPE(FuQsiDockMcuDevice, + fu_qsi_dock_mcu_device, + FU, + QSI_DOCK_MCU_DEVICE, + FuHidDevice) +gboolean +fu_qsi_dock_mcu_device_write_firmware_with_idx(FuQsiDockMcuDevice *self, + FuFirmware *firmware, + guint8 chip_idx, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error); diff --git a/plugins/qsi-dock/fu-qsi-dock-plugin.c b/plugins/qsi-dock/fu-qsi-dock-plugin.c new file mode 100644 index 000000000..cba9809d2 --- /dev/null +++ b/plugins/qsi-dock/fu-qsi-dock-plugin.c @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 Richard Hughes + * Copyright (C) 2022 Kevin Chen + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-qsi-dock-mcu-device.h" +#include "fu-qsi-dock-plugin.h" + +struct _FuQsiDockPlugin { + FuPlugin parent_instance; +}; + +G_DEFINE_TYPE(FuQsiDockPlugin, fu_qsi_dock_plugin, FU_TYPE_PLUGIN) + +static void +fu_qsi_dock_plugin_init(FuQsiDockPlugin *self) +{ +} + +static void +fu_qsi_dock_plugin_constructed(GObject *obj) +{ + FuPlugin *plugin = FU_PLUGIN(obj); + fu_plugin_add_device_gtype(plugin, FU_TYPE_QSI_DOCK_MCU_DEVICE); +} + +static void +fu_qsi_dock_plugin_class_init(FuQsiDockPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + object_class->constructed = fu_qsi_dock_plugin_constructed; +} diff --git a/plugins/qsi-dock/fu-qsi-dock-plugin.h b/plugins/qsi-dock/fu-qsi-dock-plugin.h new file mode 100644 index 000000000..d78f713df --- /dev/null +++ b/plugins/qsi-dock/fu-qsi-dock-plugin.h @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +G_DECLARE_FINAL_TYPE(FuQsiDockPlugin, fu_qsi_dock_plugin, FU, QSI_DOCK_PLUGIN, FuPlugin) diff --git a/plugins/qsi-dock/meson.build b/plugins/qsi-dock/meson.build new file mode 100644 index 000000000..caf3d6c0d --- /dev/null +++ b/plugins/qsi-dock/meson.build @@ -0,0 +1,16 @@ +if gusb.found() +cargs = ['-DG_LOG_DOMAIN="FuPluginQsiDock"'] + +plugin_quirks += files('qsi-dock.quirk') +plugin_builtins += static_library('fu_plugin_qsi_dock', + sources: [ + 'fu-qsi-dock-child-device.c', + 'fu-qsi-dock-mcu-device.c', + 'fu-qsi-dock-plugin.c', + ], + include_directories: plugin_incdirs, + link_with: plugin_libs, + c_args: cargs, + dependencies: plugin_deps, +) +endif diff --git a/plugins/qsi-dock/qsi-dock.quirk b/plugins/qsi-dock/qsi-dock.quirk new file mode 100644 index 000000000..7b8aa09e8 --- /dev/null +++ b/plugins/qsi-dock/qsi-dock.quirk @@ -0,0 +1,3 @@ +[USB\VID_047D&PID_808D] +Plugin = qsi_dock +GType = FuQsiDockMcuDevice