mirror of
				https://git.proxmox.com/git/fwupd
				synced 2025-11-04 10:27:26 +00:00 
			
		
		
		
	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.
		
			
				
	
	
		
			341 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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;
 | 
						|
}
 |