fwupd/plugins/ep963x/fu-ep963x-device.c
Richard Hughes 99df74f0c2 Add API to wait for a device
This allows us to ignore all the delays when the device is emulated, with the
idea being to do dozens of device emulations in the CI tests.

Also, do not call fu_progress_sleep() when the device is emulated.
2023-02-01 09:42:08 +00:00

382 lines
9.8 KiB
C

/*#
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-ep963x-common.h"
#include "fu-ep963x-device.h"
#include "fu-ep963x-firmware.h"
struct _FuEp963xDevice {
FuHidDevice parent_instance;
};
G_DEFINE_TYPE(FuEp963xDevice, fu_ep963x_device, FU_TYPE_HID_DEVICE)
#define FU_EP963_DEVICE_TIMEOUT 5000 /* ms */
static gboolean
fu_ep963x_device_write(FuEp963xDevice *self,
guint8 ctrl_id,
guint8 cmd,
const guint8 *buf,
gsize bufsz,
GError **error)
{
guint8 bufhw[FU_EP963_FEATURE_ID1_SIZE] = {
ctrl_id,
cmd,
0x0,
};
if (buf != NULL) {
if (!fu_memcpy_safe(bufhw,
sizeof(bufhw),
0x02, /* dst */
buf,
bufsz,
0x0, /* src */
bufsz,
error))
return FALSE;
}
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
0x00,
bufhw,
sizeof(bufhw),
FU_EP963_DEVICE_TIMEOUT,
FU_HID_DEVICE_FLAG_IS_FEATURE,
error))
return FALSE;
/* wait for hardware */
fu_device_sleep(FU_DEVICE(self), 100);
return TRUE;
}
static gboolean
fu_ep963x_device_write_icp(FuEp963xDevice *self,
guint8 cmd,
const guint8 *buf,
gsize bufsz,
guint8 *bufout,
gsize bufoutsz,
GError **error)
{
/* wait for hardware */
for (guint i = 0; i < 5; i++) {
guint8 bufhw[FU_EP963_FEATURE_ID1_SIZE] = {
FU_EP963_USB_CONTROL_ID,
cmd,
};
if (!fu_ep963x_device_write(self, FU_EP963_USB_CONTROL_ID, cmd, buf, bufsz, error))
return FALSE;
if (!fu_hid_device_get_report(FU_HID_DEVICE(self),
0x00,
bufhw,
sizeof(bufhw),
FU_EP963_DEVICE_TIMEOUT,
FU_HID_DEVICE_FLAG_IS_FEATURE,
error)) {
return FALSE;
}
if (bufhw[2] == FU_EP963_USB_STATE_READY) {
/* optional data */
if (bufout != NULL) {
if (!fu_memcpy_safe(bufout,
bufoutsz,
0x0,
bufhw,
sizeof(bufhw),
0x02,
bufoutsz,
error))
return FALSE;
}
return TRUE;
}
fu_device_sleep(FU_DEVICE(self), 100);
}
/* failed */
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to wait for icp-done");
return FALSE;
}
static gboolean
fu_ep963x_device_detach(FuDevice *device, FuProgress *progress, GError **error)
{
FuEp963xDevice *self = FU_EP963X_DEVICE(device);
const guint8 buf[] = {'E', 'P', '9', '6', '3'};
g_autoptr(GError) error_local = NULL;
/* sanity check */
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
g_debug("already in bootloader mode, skipping");
return TRUE;
}
if (!fu_ep963x_device_write_icp(self,
FU_EP963_ICP_ENTER,
buf,
sizeof(buf), /* in */
NULL,
0x0, /* out */
&error_local)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to detach: %s",
error_local->message);
return FALSE;
}
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
static gboolean
fu_ep963x_device_attach(FuDevice *device, FuProgress *progress, GError **error)
{
FuEp963xDevice *self = FU_EP963X_DEVICE(device);
g_autoptr(GError) error_local = NULL;
/* sanity check */
if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
g_debug("already in runtime mode, skipping");
return TRUE;
}
if (!fu_ep963x_device_write(self,
FU_EP963_USB_CONTROL_ID,
FU_EP963_OPCODE_SUBMCU_PROGRAM_FINISHED,
NULL,
0,
&error_local)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to boot to runtime: %s",
error_local->message);
return FALSE;
}
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
static gboolean
fu_ep963x_device_setup(FuDevice *device, GError **error)
{
FuEp963xDevice *self = FU_EP963X_DEVICE(device);
guint8 buf[] = {0x0};
g_autofree gchar *version = NULL;
/* FuUsbDevice->setup */
if (!FU_DEVICE_CLASS(fu_ep963x_device_parent_class)->setup(device, error))
return FALSE;
/* get version */
if (!fu_ep963x_device_write_icp(self,
FU_EP963_UF_CMD_VERSION,
NULL,
0, /* in */
buf,
sizeof(buf), /* out */
error)) {
return FALSE;
}
version = g_strdup_printf("%i", buf[0]);
fu_device_set_version(device, version);
/* the VID and PID are unchanged between bootloader modes */
if (buf[0] == 0x00) {
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
} else {
fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
}
/* success */
return TRUE;
}
static gboolean
fu_ep963x_device_wait_cb(FuDevice *device, gpointer user_data, GError **error)
{
guint8 bufhw[FU_EP963_FEATURE_ID1_SIZE] = {
FU_EP963_USB_CONTROL_ID,
FU_EP963_OPCODE_SUBMCU_PROGRAM_BLOCK,
0xFF,
};
if (!fu_hid_device_get_report(FU_HID_DEVICE(device),
0x00,
bufhw,
sizeof(bufhw),
FU_EP963_DEVICE_TIMEOUT,
FU_HID_DEVICE_FLAG_IS_FEATURE,
error)) {
return FALSE;
}
if (bufhw[2] != FU_EP963_USB_STATE_READY) {
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "hardware is not ready");
return FALSE;
}
return TRUE;
}
static gboolean
fu_ep963x_device_write_firmware(FuDevice *device,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
FuEp963xDevice *self = FU_EP963X_DEVICE(device);
g_autoptr(GBytes) fw = NULL;
g_autoptr(GError) error_local = NULL;
g_autoptr(GPtrArray) blocks = 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_BUSY, 5, "icp");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95, NULL);
/* get default image */
fw = fu_firmware_get_bytes(firmware, error);
if (fw == NULL)
return FALSE;
/* reset the block index */
if (!fu_ep963x_device_write(self,
FU_EP963_USB_CONTROL_ID,
FU_EP963_OPCODE_SUBMCU_ENTER_ICP,
NULL,
0,
&error_local)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to reset block index: %s",
error_local->message);
return FALSE;
}
fu_progress_step_done(progress);
/* write each block */
blocks = fu_chunk_array_new_from_bytes(fw, 0x00, 0x00, FU_EP963_TRANSFER_BLOCK_SIZE);
for (guint i = 0; i < blocks->len; i++) {
FuChunk *chk2 = g_ptr_array_index(blocks, i);
guint8 buf[] = {i};
g_autoptr(GPtrArray) chunks = NULL;
/* set the block index */
if (!fu_ep963x_device_write(self,
FU_EP963_USB_CONTROL_ID,
FU_EP963_OPCODE_SUBMCU_RESET_BLOCK_IDX,
buf,
sizeof(buf),
&error_local)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to reset block index: %s",
error_local->message);
return FALSE;
}
/* 4 byte chunks */
chunks = fu_chunk_array_new(fu_chunk_get_data(chk2),
fu_chunk_get_data_sz(chk2),
fu_chunk_get_address(chk2),
0x0,
FU_EP963_TRANSFER_CHUNK_SIZE);
for (guint j = 0; j < chunks->len; j++) {
FuChunk *chk = g_ptr_array_index(chunks, j);
g_autoptr(GError) error_loop = NULL;
/* copy data and write */
if (!fu_ep963x_device_write(self,
FU_EP963_USB_CONTROL_ID,
FU_EP963_OPCODE_SUBMCU_WRITE_BLOCK_DATA,
fu_chunk_get_data(chk),
fu_chunk_get_data_sz(chk),
&error_loop)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to write 0x%x: %s",
(guint)fu_chunk_get_address(chk),
error_loop->message);
return FALSE;
}
}
/* program block */
if (!fu_ep963x_device_write(self,
FU_EP963_USB_CONTROL_ID,
FU_EP963_OPCODE_SUBMCU_PROGRAM_BLOCK,
buf,
sizeof(buf),
&error_local)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to write 0x%x: %s",
(guint)fu_chunk_get_address(chk2),
error_local->message);
return FALSE;
}
/* wait for program finished */
if (!fu_device_retry(device, fu_ep963x_device_wait_cb, 5, NULL, error))
return FALSE;
/* update progress */
fu_progress_set_percentage_full(fu_progress_get_child(progress),
(gsize)i + 1,
(gsize)chunks->len);
}
fu_progress_step_done(progress);
/* success! */
return TRUE;
}
static void
fu_ep963x_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, 2, "detach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload");
}
static void
fu_ep963x_device_init(FuEp963xDevice *self)
{
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
fu_device_add_protocol(FU_DEVICE(self), "tw.com.exploretech.ep963x");
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_set_firmware_size(FU_DEVICE(self), FU_EP963_FIRMWARE_SIZE);
fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_EP963X_FIRMWARE);
fu_device_retry_set_delay(FU_DEVICE(self), 100);
}
static void
fu_ep963x_device_class_init(FuEp963xDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->write_firmware = fu_ep963x_device_write_firmware;
klass_device->attach = fu_ep963x_device_attach;
klass_device->detach = fu_ep963x_device_detach;
klass_device->setup = fu_ep963x_device_setup;
klass_device->set_progress = fu_ep963x_device_set_progress;
}