fwupd/plugins/ep963x/fu-ep963x-device.c
Richard Hughes a8c6c02459 Do not conditionalize attach() and detach() on IS_BOOTLOADER
This is nice in theory, until you need to look at the bootloader status of the
parent, or of a different device entirely. Handle this in plugins for the few
cases we care about and stop setting or clearing IS_BOOTLOADER manually just to
get the vfuncs to be run.

Note: I do not think we want to use cleanup() for attaching devices not in
bootloader states -- as cleanup is only run at the end of the composite update.
2020-04-09 09:55:30 +01:00

341 lines
8.9 KiB
C

/*#
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-chunk.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,
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 */
g_usleep (100 * 1000);
return TRUE;
}
static gboolean
fu_ep963x_device_write_icp (FuEp963xDevice *self,
guint8 cmd, guint8 *buf, gsize bufsz,
GError **error)
{
/* wait for hardware */
for (guint i = 0; i < 5; i++) {
guint8 bufhw[FU_EP963_FEATURE_ID1_SIZE] = {
FU_EP963_FEATURE_ID1_SIZE,
cmd,
};
if (!fu_ep963x_device_write (self, FU_EP963_FEATURE_ID1_SIZE,
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[7] == FU_EP963_ICP_DONE) {
/* optional data */
if (buf != NULL) {
if (!fu_memcpy_safe (buf, bufsz, 0x0,
bufhw, sizeof(bufhw), 0x02,
bufsz, error))
return FALSE;
}
return TRUE;
}
g_usleep (100 * 1000);
}
/* failed */
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to wait for icp-done");
return FALSE;
}
static gboolean
fu_ep963x_device_write_smbus (FuEp963xDevice *self,
guint8 cmd, guint8 *buf, gsize bufsz,
GError **error)
{
guint8 bufhw[FU_EP963_FEATURE_ID1_SIZE] = { 0x0 };
guint8 usb_state = FU_EP963_USB_STATE_UNKNOWN;
/* send request */
if (!fu_ep963x_device_write (self, FU_EP963_FEATURE_ID1_SIZE,
cmd, buf, bufsz, error))
return FALSE;
/* wait for hardware */
for (guint i = 0; i < 16; i++) {
bufhw[0] = FU_EP963_FEATURE_ID1_SIZE;
bufhw[1] = cmd;
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[0x07] != FU_EP963_SMBUS_ERROR_NONE) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"smbus failed %s",
fu_ep963x_smbus_strerror (bufhw[0x07]));
return FALSE;
}
if (bufhw[0x02] == FU_EP963_USB_STATE_READY)
return TRUE;
/* USB state changed, so reset counter */
if (usb_state != bufhw[0x02]) {
usb_state = bufhw[0x02];
i = 0;
}
g_usleep (100 * 1000);
}
/* 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, 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_OPCODE_SUBMCU_ENTER_ICP,
buf, sizeof(buf),
&error_local)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to detach: %s",
error_local->message);
return FALSE;
}
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
static gboolean
fu_ep963x_device_attach (FuDevice *device, GError **error)
{
FuEp963xDevice *self = FU_EP963X_DEVICE (device);
const guint8 buf[] = { 0x00 };
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;
}
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
if (!fu_ep963x_device_write_icp (self, 0x02,
buf, sizeof(buf),
&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;
/* get version */
if (!fu_ep963x_device_write_icp (self, FU_EP963_UF_CMD_VERSION,
buf, sizeof(buf),
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 FuFirmware *
fu_ep963x_device_prepare_firmware (FuDevice *device,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
g_autoptr(FuFirmware) firmware = fu_ep963x_firmware_new ();
fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING);
if (!fu_firmware_parse (firmware, fw, flags, error))
return NULL;
return g_steal_pointer (&firmware);
}
static gboolean
fu_ep963x_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
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;
/* get default image */
fw = fu_firmware_get_image_default_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* reset the block index */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
if (!fu_ep963x_device_write (self,
FU_EP963_USB_CONTROL_ID,
FU_EP963_OPCODE_SUBMCU_RESET_BLOCK_IDX,
NULL, 0, &error_local)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to reset block index: %s",
error_local->message);
return FALSE;
}
/* 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 *blk = g_ptr_array_index (blocks, i);
guint8 buf[] = { i };
g_autoptr(GPtrArray) chunks = NULL;
/* 4 byte chunks */
chunks = fu_chunk_array_new (blk->data, blk->data_sz,
blk->address, 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,
chk->data, chk->data_sz,
&error_loop)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to write 0x%x: %s",
(guint) chk->address,
error_loop->message);
return FALSE;
}
}
/* program block */
if (!fu_ep963x_device_write_smbus (self, 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) blk->address,
error_local->message);
return FALSE;
}
/* update progress */
fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len);
}
/* success! */
return TRUE;
}
static void
fu_ep963x_device_init (FuEp963xDevice *self)
{
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_set_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);
}
static void
fu_ep963x_device_class_init (FuEp963xDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
klass_device->prepare_firmware = fu_ep963x_device_prepare_firmware;
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;
}