/* * 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; } 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; }