fwupd/plugins/qsi-dock/fu-qsi-dock-mcu-device.c

581 lines
15 KiB
C

/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2022 Kevin Chen <hsinfu.chen@qsitw.com>
*
* 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;
}