mirror of
https://git.proxmox.com/git/fwupd
synced 2025-04-29 01:50:33 +00:00

The DFU protocol can be used to transfer both signed and unsigned content, and it's up to the device to do the right thing.
1769 lines
52 KiB
C
1769 lines
52 KiB
C
/*
|
|
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
/**
|
|
* FuDfuDevice:
|
|
*
|
|
* This object allows two things:
|
|
*
|
|
* - Downloading from the host to the device, optionally with
|
|
* verification using a DFU or DfuSe firmware file.
|
|
*
|
|
* - Uploading from the device to the host to a DFU or DfuSe firmware
|
|
* file. The file format is chosen automatically, with DfuSe being
|
|
* chosen if the device contains more than one target.
|
|
*
|
|
* See also: [class@FuDfuTarget], [class@FuDfuseFirmware]
|
|
*/
|
|
|
|
/**
|
|
* FU_QUIRKS_DFU_FORCE_VERSION:
|
|
* @key: the USB device ID, e.g. `USB\VID_0763&PID_2806`
|
|
* @value: the uint16_t DFU version, encoded in base 16, e.g. `0110`
|
|
*
|
|
* Forces a specific DFU version for the hardware device. This is required
|
|
* if the device does not set, or sets incorrectly, items in the DFU functional
|
|
* descriptor. If zero, then DFU functionality is disabled.
|
|
*
|
|
* Since: 1.0.1
|
|
*/
|
|
#define FU_QUIRKS_DFU_FORCE_VERSION "DfuForceVersion"
|
|
|
|
#define DFU_DEVICE_DNLOAD_TIMEOUT_DEFAULT 5 /* ms */
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include "fu-dfu-common.h"
|
|
#include "fu-dfu-device.h"
|
|
#include "fu-dfu-target-avr.h"
|
|
#include "fu-dfu-target-private.h" /* waive-pre-commit */
|
|
#include "fu-dfu-target-stm.h"
|
|
|
|
static void
|
|
fu_dfu_device_finalize(GObject *object);
|
|
|
|
typedef struct {
|
|
FuDfuState state;
|
|
FuDfuStatus status;
|
|
GPtrArray *targets;
|
|
gboolean done_upload_or_download;
|
|
gboolean claimed_interface;
|
|
gchar *chip_id;
|
|
guint16 version;
|
|
guint16 force_version;
|
|
guint16 force_transfer_size;
|
|
guint16 runtime_pid;
|
|
guint16 runtime_vid;
|
|
guint16 runtime_release;
|
|
guint16 transfer_size;
|
|
guint8 iface_number;
|
|
guint dnload_timeout;
|
|
guint timeout_ms;
|
|
} FuDfuDevicePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE(FuDfuDevice, fu_dfu_device, FU_TYPE_USB_DEVICE)
|
|
#define GET_PRIVATE(o) (fu_dfu_device_get_instance_private(o))
|
|
|
|
static void
|
|
fu_dfu_device_set_state(FuDfuDevice *self, FuDfuState state);
|
|
|
|
static void
|
|
fu_dfu_device_to_string(FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
fu_string_append(str, idt, "State", fu_dfu_state_to_string(priv->state));
|
|
fu_string_append(str, idt, "Status", fu_dfu_status_to_string(priv->status));
|
|
fu_string_append_kb(str, idt, "DoneUploadOrDownload", priv->done_upload_or_download);
|
|
fu_string_append_kb(str, idt, "ClaimedInterface", priv->claimed_interface);
|
|
if (priv->chip_id != NULL)
|
|
fu_string_append(str, idt, "ChipId", priv->chip_id);
|
|
fu_string_append_kx(str, idt, "Version", priv->version);
|
|
if (priv->force_version != G_MAXUINT16)
|
|
fu_string_append_kx(str, idt, "ForceVersion", priv->force_version);
|
|
if (priv->force_transfer_size != 0x0) {
|
|
fu_string_append_kx(str, idt, "ForceTransferSize", priv->force_transfer_size);
|
|
}
|
|
fu_string_append_kx(str, idt, "RuntimePid", priv->runtime_pid);
|
|
fu_string_append_kx(str, idt, "RuntimeVid", priv->runtime_vid);
|
|
fu_string_append_kx(str, idt, "RuntimeRelease", priv->runtime_release);
|
|
fu_string_append_kx(str, idt, "TransferSize", priv->transfer_size);
|
|
fu_string_append_kx(str, idt, "IfaceNumber", priv->iface_number);
|
|
fu_string_append_kx(str, idt, "DnloadTimeout", priv->dnload_timeout);
|
|
fu_string_append_kx(str, idt, "TimeoutMs", priv->timeout_ms);
|
|
|
|
for (guint i = 0; i < priv->targets->len; i++) {
|
|
FuDfuTarget *target = g_ptr_array_index(priv->targets, i);
|
|
fu_device_add_string(FU_DEVICE(target), idt + 1, str);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_transfer_size:
|
|
* @device: a USB device
|
|
*
|
|
* Gets the transfer size in bytes.
|
|
*
|
|
* Returns: packet size, or 0 for unknown
|
|
**/
|
|
guint16
|
|
fu_dfu_device_get_transfer_size(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff);
|
|
return priv->transfer_size;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_version:
|
|
* @self: a #FuDfuDevice
|
|
*
|
|
* Gets the DFU specification version supported by the device.
|
|
*
|
|
* Returns: integer, or 0 for unknown, e.g. %FU_DFU_FIRMARE_VERSION_DFU_1_1
|
|
**/
|
|
guint16
|
|
fu_dfu_device_get_version(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff);
|
|
return priv->version;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_download_timeout:
|
|
* @self: a #FuDfuDevice
|
|
*
|
|
* Gets the download timeout in ms.
|
|
*
|
|
* Returns: delay, or 0 for unknown
|
|
**/
|
|
guint
|
|
fu_dfu_device_get_download_timeout(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0);
|
|
return priv->dnload_timeout;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_set_transfer_size:
|
|
* @self: a #FuDfuDevice
|
|
* @transfer_size: maximum packet size
|
|
*
|
|
* Sets the transfer size in bytes.
|
|
**/
|
|
void
|
|
fu_dfu_device_set_transfer_size(FuDfuDevice *self, guint16 transfer_size)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_if_fail(FU_IS_DFU_DEVICE(self));
|
|
priv->transfer_size = transfer_size;
|
|
}
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 bLength;
|
|
guint8 bDescriptorType;
|
|
guint8 bmAttributes;
|
|
guint16 wDetachTimeOut;
|
|
guint16 wTransferSize;
|
|
guint16 bcdDFUVersion;
|
|
} DfuFuncDescriptor;
|
|
|
|
static gboolean
|
|
fu_dfu_device_parse_iface_data(FuDfuDevice *self, GBytes *iface_data, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
DfuFuncDescriptor desc = {0x0};
|
|
const guint8 *buf;
|
|
gsize sz;
|
|
|
|
/* parse the functional descriptor */
|
|
buf = g_bytes_get_data(iface_data, &sz);
|
|
if (sz == sizeof(DfuFuncDescriptor)) {
|
|
memcpy(&desc, buf, sz);
|
|
} else if (sz > sizeof(DfuFuncDescriptor)) {
|
|
g_debug("DFU interface with %" G_GSIZE_FORMAT " bytes vendor data",
|
|
sz - sizeof(DfuFuncDescriptor));
|
|
memcpy(&desc, buf, sizeof(DfuFuncDescriptor));
|
|
} else if (sz == sizeof(DfuFuncDescriptor) - 2) {
|
|
g_warning("truncated DFU interface data, no bcdDFUVersion");
|
|
memcpy(&desc, buf, sz);
|
|
desc.bcdDFUVersion = FU_DFU_FIRMARE_VERSION_DFU_1_1;
|
|
} else {
|
|
g_autoptr(GString) bufstr = g_string_new(NULL);
|
|
for (gsize i = 0; i < sz; i++)
|
|
g_string_append_printf(bufstr, "%02x ", buf[i]);
|
|
if (bufstr->len > 0)
|
|
g_string_truncate(bufstr, bufstr->len - 1);
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"interface found, but not the correct length for "
|
|
"functional data: %" G_GSIZE_FORMAT " bytes: %s",
|
|
sz,
|
|
bufstr->str);
|
|
return FALSE;
|
|
}
|
|
|
|
/* get transfer size and version */
|
|
priv->transfer_size = GUINT16_FROM_LE(desc.wTransferSize);
|
|
priv->version = GUINT16_FROM_LE(desc.bcdDFUVersion);
|
|
|
|
/* ST-specific */
|
|
if (priv->version == FU_DFU_FIRMARE_VERSION_DFUSE &&
|
|
desc.bmAttributes & FU_DFU_DEVICE_FLAG_CAN_ACCELERATE)
|
|
priv->transfer_size = 0x1000;
|
|
|
|
/* get attributes about the DFU operation */
|
|
fu_device_add_private_flag(FU_DEVICE(self), desc.bmAttributes);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_dfu_device_guess_state_from_iface(FuDfuDevice *self, GUsbInterface *iface)
|
|
{
|
|
/* some devices use the wrong interface */
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE)) {
|
|
g_debug("quirking device into DFU mode");
|
|
fu_dfu_device_set_state(self, FU_DFU_STATE_DFU_IDLE);
|
|
return;
|
|
}
|
|
|
|
/* runtime */
|
|
if (g_usb_interface_get_protocol(iface) == 0x01) {
|
|
fu_dfu_device_set_state(self, FU_DFU_STATE_APP_IDLE);
|
|
return;
|
|
}
|
|
|
|
/* DFU */
|
|
if (g_usb_interface_get_protocol(iface) == 0x02) {
|
|
fu_dfu_device_set_state(self, FU_DFU_STATE_DFU_IDLE);
|
|
return;
|
|
}
|
|
g_warning("unable to guess initial device state from interface %u",
|
|
g_usb_interface_get_protocol(iface));
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_add_targets(FuDfuDevice *self, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
g_autoptr(GPtrArray) ifaces = NULL;
|
|
|
|
/* disabled using quirk */
|
|
if (priv->force_version == 0x0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"ignoring device as DFU version set to 0x0");
|
|
return FALSE;
|
|
}
|
|
|
|
/* add all DFU-capable targets */
|
|
ifaces = g_usb_device_get_interfaces(usb_device, error);
|
|
if (ifaces == NULL)
|
|
return FALSE;
|
|
g_ptr_array_set_size(priv->targets, 0);
|
|
for (guint i = 0; i < ifaces->len; i++) {
|
|
GBytes *iface_data = NULL;
|
|
FuDfuTarget *target;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
GUsbInterface *iface = g_ptr_array_index(ifaces, i);
|
|
|
|
/* some devices don't use the right class and subclass */
|
|
if (!fu_device_has_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_USE_ANY_INTERFACE)) {
|
|
if (g_usb_interface_get_class(iface) !=
|
|
G_USB_DEVICE_CLASS_APPLICATION_SPECIFIC)
|
|
continue;
|
|
if (g_usb_interface_get_subclass(iface) != 0x01)
|
|
continue;
|
|
}
|
|
/* parse any interface data */
|
|
iface_data = g_usb_interface_get_extra(iface);
|
|
if (iface_data != NULL && g_bytes_get_size(iface_data) > 0) {
|
|
if (!fu_dfu_device_parse_iface_data(self, iface_data, &error_local)) {
|
|
g_warning("failed to parse interface data for %04x:%04x: %s",
|
|
g_usb_device_get_vid(usb_device),
|
|
g_usb_device_get_pid(usb_device),
|
|
error_local->message);
|
|
continue;
|
|
}
|
|
} else {
|
|
fu_device_add_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD |
|
|
FU_DFU_DEVICE_FLAG_CAN_UPLOAD);
|
|
}
|
|
|
|
/* fix up the version */
|
|
if (priv->force_version != G_MAXUINT16)
|
|
priv->version = priv->force_version;
|
|
if (priv->version == FU_DFU_FIRMARE_VERSION_DFU_1_0 ||
|
|
priv->version == FU_DFU_FIRMARE_VERSION_DFU_1_1) {
|
|
g_debug("DFU v1.1");
|
|
} else if (priv->version == FU_DFU_FIRMARE_VERSION_ATMEL_AVR) {
|
|
g_debug("AVR-DFU support");
|
|
priv->version = FU_DFU_FIRMARE_VERSION_ATMEL_AVR;
|
|
} else if (priv->version == FU_DFU_FIRMARE_VERSION_DFUSE) {
|
|
g_debug("STM-DFU support");
|
|
} else if (priv->version == 0x0101) {
|
|
g_debug("DFU v1.1 assumed");
|
|
priv->version = FU_DFU_FIRMARE_VERSION_DFU_1_1;
|
|
} else {
|
|
g_warning("DFU version 0x%04x invalid, v1.1 assumed", priv->version);
|
|
priv->version = FU_DFU_FIRMARE_VERSION_DFU_1_1;
|
|
}
|
|
|
|
/* set expected protocol */
|
|
if (priv->version == FU_DFU_FIRMARE_VERSION_DFUSE) {
|
|
fu_device_add_protocol(FU_DEVICE(self), "com.st.dfuse");
|
|
} else {
|
|
fu_device_add_protocol(FU_DEVICE(self), "org.usb.dfu");
|
|
}
|
|
|
|
/* fix up the transfer size */
|
|
if (priv->force_transfer_size != 0x0) {
|
|
priv->transfer_size = priv->force_transfer_size;
|
|
g_debug("forcing DFU transfer size 0x%04x bytes", priv->transfer_size);
|
|
} else if (priv->transfer_size == 0xffff) {
|
|
priv->transfer_size = 0x0400;
|
|
g_debug("DFU transfer size unspecified, guessing");
|
|
} else if (priv->transfer_size == 0x0) {
|
|
g_warning("DFU transfer size invalid, using default");
|
|
priv->transfer_size = 64;
|
|
} else {
|
|
g_debug("using DFU transfer size 0x%04x bytes", priv->transfer_size);
|
|
}
|
|
|
|
/* create a target of the required type */
|
|
switch (priv->version) {
|
|
case FU_DFU_FIRMARE_VERSION_DFUSE:
|
|
target = fu_dfu_target_stm_new();
|
|
break;
|
|
case FU_DFU_FIRMARE_VERSION_ATMEL_AVR:
|
|
target = fu_dfu_target_avr_new();
|
|
break;
|
|
default:
|
|
target = fu_dfu_target_new();
|
|
break;
|
|
}
|
|
fu_device_set_proxy(FU_DEVICE(target), FU_DEVICE(self));
|
|
fu_dfu_target_set_alt_idx(target, g_usb_interface_get_index(iface));
|
|
fu_dfu_target_set_alt_setting(target, g_usb_interface_get_alternate(iface));
|
|
|
|
/* add target */
|
|
priv->iface_number = g_usb_interface_get_number(iface);
|
|
g_ptr_array_add(priv->targets, target);
|
|
fu_dfu_device_guess_state_from_iface(self, iface);
|
|
}
|
|
|
|
/* save for reset */
|
|
if (priv->state == FU_DFU_STATE_APP_IDLE ||
|
|
fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_PID_CHANGE)) {
|
|
priv->runtime_vid = g_usb_device_get_vid(usb_device);
|
|
priv->runtime_pid = g_usb_device_get_pid(usb_device);
|
|
priv->runtime_release = g_usb_device_get_release(usb_device);
|
|
}
|
|
|
|
/* the device has no DFU runtime, so cheat */
|
|
if (priv->targets->len == 0 &&
|
|
fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) {
|
|
g_debug("no DFU runtime, so faking device");
|
|
fu_dfu_device_set_state(self, FU_DFU_STATE_APP_IDLE);
|
|
priv->iface_number = 0xff;
|
|
priv->runtime_vid = g_usb_device_get_vid(usb_device);
|
|
priv->runtime_pid = g_usb_device_get_pid(usb_device);
|
|
priv->runtime_release = g_usb_device_get_release(usb_device);
|
|
fu_device_add_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD |
|
|
FU_DFU_DEVICE_FLAG_CAN_UPLOAD);
|
|
return TRUE;
|
|
}
|
|
|
|
/* no targets */
|
|
if (priv->targets->len == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"no DFU interfaces");
|
|
return FALSE;
|
|
}
|
|
|
|
/* the device upload is broken */
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_IGNORE_UPLOAD))
|
|
fu_device_remove_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_UPLOAD);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_set_timeout:
|
|
* @self: a #FuDfuDevice
|
|
* @timeout_ms: the timeout in ms
|
|
*
|
|
* Sets the USB timeout to use when contacting the USB device.
|
|
**/
|
|
void
|
|
fu_dfu_device_set_timeout(FuDfuDevice *self, guint timeout_ms)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_if_fail(FU_IS_DFU_DEVICE(self));
|
|
priv->timeout_ms = timeout_ms;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_timeout:
|
|
* @device: a #FuDfuDevice
|
|
*
|
|
* Gets the device timeout.
|
|
*
|
|
* Returns: enumerated timeout in ms
|
|
**/
|
|
guint
|
|
fu_dfu_device_get_timeout(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0);
|
|
return priv->timeout_ms;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_state:
|
|
* @device: a #FuDfuDevice
|
|
*
|
|
* Gets the device state.
|
|
*
|
|
* Returns: enumerated state, e.g. %FU_DFU_STATE_DFU_UPLOAD_IDLE
|
|
**/
|
|
FuDfuState
|
|
fu_dfu_device_get_state(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0);
|
|
return priv->state;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_status:
|
|
* @device: a USB device
|
|
*
|
|
* Gets the device status.
|
|
*
|
|
* Returns: enumerated status, e.g. %FU_DFU_STATUS_ERR_ADDRESS
|
|
**/
|
|
FuDfuStatus
|
|
fu_dfu_device_get_status(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0);
|
|
return priv->status;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_new:
|
|
*
|
|
* Creates a new DFU device object.
|
|
*
|
|
* Returns: a new #FuDfuDevice
|
|
**/
|
|
FuDfuDevice *
|
|
fu_dfu_device_new(FuContext *ctx, GUsbDevice *usb_device)
|
|
{
|
|
FuDfuDevice *self;
|
|
self = g_object_new(FU_TYPE_DFU_DEVICE, "usb-device", usb_device, "context", ctx, NULL);
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_target_by_alt_setting:
|
|
* @self: a #FuDfuDevice
|
|
* @alt_setting: the setting used to find
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Gets a target with a specific alternative setting.
|
|
*
|
|
* Returns: (transfer full): a #FuDfuTarget, or %NULL
|
|
**/
|
|
FuDfuTarget *
|
|
fu_dfu_device_get_target_by_alt_setting(FuDfuDevice *self, guint8 alt_setting, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
/* find by ID */
|
|
for (guint i = 0; i < priv->targets->len; i++) {
|
|
FuDfuTarget *target = g_ptr_array_index(priv->targets, i);
|
|
if (fu_dfu_target_get_alt_setting(target) == alt_setting)
|
|
return g_object_ref(target);
|
|
}
|
|
|
|
/* failed */
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"No target with alt-setting %i",
|
|
alt_setting);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_target_by_alt_name:
|
|
* @self: a #FuDfuDevice
|
|
* @alt_name: the name used to find
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Gets a target with a specific alternative name.
|
|
*
|
|
* Returns: (transfer full): a #FuDfuTarget, or %NULL
|
|
**/
|
|
FuDfuTarget *
|
|
fu_dfu_device_get_target_by_alt_name(FuDfuDevice *self, const gchar *alt_name, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
/* find by ID */
|
|
for (guint i = 0; i < priv->targets->len; i++) {
|
|
FuDfuTarget *target = g_ptr_array_index(priv->targets, i);
|
|
if (g_strcmp0(fu_device_get_logical_id(FU_DEVICE(target)), alt_name) == 0)
|
|
return g_object_ref(target);
|
|
}
|
|
|
|
/* failed */
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"No target with alt-name %s",
|
|
alt_name);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_runtime_vid:
|
|
* @self: a #FuDfuDevice
|
|
*
|
|
* Gets the runtime vendor ID.
|
|
*
|
|
* Returns: vendor ID, or 0xffff for unknown
|
|
**/
|
|
guint16
|
|
fu_dfu_device_get_runtime_vid(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff);
|
|
return priv->runtime_vid;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_runtime_pid:
|
|
* @self: a #FuDfuDevice
|
|
*
|
|
* Gets the runtime product ID.
|
|
*
|
|
* Returns: product ID, or 0xffff for unknown
|
|
**/
|
|
guint16
|
|
fu_dfu_device_get_runtime_pid(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff);
|
|
return priv->runtime_pid;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_runtime_release:
|
|
* @self: a #FuDfuDevice
|
|
*
|
|
* Gets the runtime release number in BCD format.
|
|
*
|
|
* Returns: release number, or 0xffff for unknown
|
|
**/
|
|
guint16
|
|
fu_dfu_device_get_runtime_release(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff);
|
|
return priv->runtime_release;
|
|
}
|
|
|
|
const gchar *
|
|
fu_dfu_device_get_chip_id(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), NULL);
|
|
return priv->chip_id;
|
|
}
|
|
|
|
void
|
|
fu_dfu_device_set_chip_id(FuDfuDevice *self, const gchar *chip_id)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_if_fail(FU_IS_DFU_DEVICE(self));
|
|
g_debug("chip ID set to: %s", chip_id);
|
|
priv->chip_id = g_strdup(chip_id);
|
|
}
|
|
|
|
static void
|
|
fu_dfu_device_set_state(FuDfuDevice *self, FuDfuState state)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
if (priv->state == state)
|
|
return;
|
|
priv->state = state;
|
|
|
|
/* set bootloader status */
|
|
if (state == FU_DFU_STATE_APP_IDLE || state == FU_DFU_STATE_APP_DETACH) {
|
|
fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
|
} else {
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fu_dfu_device_set_status(FuDfuDevice *self, FuDfuStatus status)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
if (priv->status == status)
|
|
return;
|
|
priv->status = status;
|
|
}
|
|
|
|
gboolean
|
|
fu_dfu_device_ensure_interface(FuDfuDevice *self, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* already done */
|
|
if (priv->claimed_interface)
|
|
return TRUE;
|
|
|
|
/* nothing set */
|
|
if (priv->iface_number == 0xff)
|
|
return TRUE;
|
|
|
|
/* claim, without detaching kernel driver */
|
|
if (!g_usb_device_claim_interface(usb_device,
|
|
(gint)priv->iface_number,
|
|
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
|
|
&error_local)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot claim interface %i: %s",
|
|
priv->iface_number,
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
priv->claimed_interface = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_refresh_and_clear:
|
|
* @self: a #FuDfuDevice
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Refreshes the cached properties on the DFU device. If there are any transfers
|
|
* in progress they are cancelled, and if there are any pending errors they are
|
|
* cancelled.
|
|
*
|
|
* Returns: %TRUE for success
|
|
**/
|
|
gboolean
|
|
fu_dfu_device_refresh_and_clear(FuDfuDevice *self, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
if (!fu_dfu_device_refresh(self, error))
|
|
return FALSE;
|
|
switch (priv->state) {
|
|
case FU_DFU_STATE_DFU_UPLOAD_IDLE:
|
|
case FU_DFU_STATE_DFU_DNLOAD_IDLE:
|
|
case FU_DFU_STATE_DFU_DNLOAD_SYNC:
|
|
g_debug("aborting transfer %s", fu_dfu_status_to_string(priv->status));
|
|
if (!fu_dfu_device_abort(self, error))
|
|
return FALSE;
|
|
break;
|
|
case FU_DFU_STATE_DFU_ERROR:
|
|
g_debug("clearing error %s", fu_dfu_status_to_string(priv->status));
|
|
if (!fu_dfu_device_clear_status(self, error))
|
|
return FALSE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_refresh:
|
|
* @self: a #FuDfuDevice
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Refreshes the cached properties on the DFU device.
|
|
*
|
|
* Returns: %TRUE for success
|
|
**/
|
|
gboolean
|
|
fu_dfu_device_refresh(FuDfuDevice *self, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
gsize actual_length = 0;
|
|
guint8 buf[6] = {0x0};
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* the device has no DFU runtime, so cheat */
|
|
if (priv->state == FU_DFU_STATE_APP_IDLE &&
|
|
fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME))
|
|
return TRUE;
|
|
|
|
/* ensure interface is claimed */
|
|
if (!fu_dfu_device_ensure_interface(self, error))
|
|
return FALSE;
|
|
|
|
/* Device that cannot communicate via the USB after the
|
|
* Manifestation phase indicated this limitation to the
|
|
* host by clearing bmAttributes bit bitManifestationTolerant.
|
|
* so we assume the operation was successful */
|
|
if (priv->state == FU_DFU_STATE_DFU_MANIFEST &&
|
|
!fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_MANIFEST_TOL))
|
|
return TRUE;
|
|
|
|
if (!g_usb_device_control_transfer(usb_device,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
FU_DFU_REQUEST_GETSTATUS,
|
|
0,
|
|
priv->iface_number,
|
|
buf,
|
|
sizeof(buf),
|
|
&actual_length,
|
|
priv->timeout_ms,
|
|
NULL, /* cancellable */
|
|
&error_local)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot get device state: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
if (actual_length != 6) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"cannot get device status, invalid size: %04x",
|
|
(guint)actual_length);
|
|
return FALSE;
|
|
}
|
|
|
|
/* some devices use the wrong state value */
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE) &&
|
|
fu_dfu_device_get_state(self) != FU_DFU_STATE_DFU_IDLE) {
|
|
g_debug("quirking device into DFU mode");
|
|
fu_dfu_device_set_state(self, FU_DFU_STATE_DFU_IDLE);
|
|
} else {
|
|
fu_dfu_device_set_state(self, buf[4]);
|
|
}
|
|
|
|
/* status or state changed */
|
|
fu_dfu_device_set_status(self, buf[0]);
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_IGNORE_POLLTIMEOUT)) {
|
|
priv->dnload_timeout = DFU_DEVICE_DNLOAD_TIMEOUT_DEFAULT;
|
|
} else {
|
|
priv->dnload_timeout = fu_memread_uint24(&buf[1], G_LITTLE_ENDIAN);
|
|
if (priv->dnload_timeout == 0 &&
|
|
!fu_device_has_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_ALLOW_ZERO_POLLTIMEOUT)) {
|
|
priv->dnload_timeout = DFU_DEVICE_DNLOAD_TIMEOUT_DEFAULT;
|
|
g_debug("no dnload-timeout, using default of %ums", priv->dnload_timeout);
|
|
}
|
|
}
|
|
g_debug("refreshed status=%s and state=%s (dnload=%u)",
|
|
fu_dfu_status_to_string(priv->status),
|
|
fu_dfu_state_to_string(priv->state),
|
|
priv->dnload_timeout);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_request_detach(FuDfuDevice *self, FuProgress *progress, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
const guint16 timeout_reset_ms = 1000;
|
|
guint16 ctrl_setup_index = priv->iface_number;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_INDEX_FORCE_DETACH))
|
|
ctrl_setup_index |= 0x01u << 8;
|
|
|
|
if (!g_usb_device_control_transfer(usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
FU_DFU_REQUEST_DETACH,
|
|
timeout_reset_ms,
|
|
ctrl_setup_index,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
priv->timeout_ms,
|
|
NULL, /* cancellable */
|
|
&error_local)) {
|
|
/* some devices just reboot and stall the endpoint :/ */
|
|
if (g_error_matches(error_local,
|
|
G_USB_DEVICE_ERROR,
|
|
G_USB_DEVICE_ERROR_NOT_SUPPORTED) ||
|
|
g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) {
|
|
g_debug("ignoring while detaching: %s", error_local->message);
|
|
} else {
|
|
/* refresh the error code */
|
|
fu_dfu_device_error_fixup(self, &error_local);
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot detach device: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_reload(FuDevice *device, GError **error)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
return fu_dfu_device_refresh_and_clear(self, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_detach(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* already in DFU mode */
|
|
if (!fu_dfu_device_refresh_and_clear(self, error))
|
|
return FALSE;
|
|
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER))
|
|
return TRUE;
|
|
|
|
/* the device has no DFU runtime, so cheat */
|
|
if (priv->state == FU_DFU_STATE_APP_IDLE &&
|
|
fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME))
|
|
return TRUE;
|
|
|
|
/* ensure interface is claimed */
|
|
if (!fu_dfu_device_ensure_interface(self, error))
|
|
return FALSE;
|
|
|
|
/* inform UI there's going to be a detach:attach */
|
|
if (!fu_dfu_device_request_detach(self, progress, error))
|
|
return FALSE;
|
|
|
|
/* do a host reset */
|
|
if (!fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_WILL_DETACH)) {
|
|
g_debug("doing device reset as host will not self-reset");
|
|
if (!fu_dfu_device_reset(self, progress, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
priv->force_version = G_MAXUINT16;
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_abort:
|
|
* @self: a #FuDfuDevice
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Aborts any upload or download in progress.
|
|
*
|
|
* Returns: %TRUE for success
|
|
**/
|
|
gboolean
|
|
fu_dfu_device_abort(FuDfuDevice *self, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE);
|
|
g_return_val_if_fail(G_USB_IS_DEVICE(usb_device), FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* the device has no DFU runtime, so cheat */
|
|
if (priv->state == FU_DFU_STATE_APP_IDLE &&
|
|
fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"not supported as no DFU runtime");
|
|
return FALSE;
|
|
}
|
|
|
|
/* ensure interface is claimed */
|
|
if (!fu_dfu_device_ensure_interface(self, error))
|
|
return FALSE;
|
|
|
|
if (!g_usb_device_control_transfer(usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
FU_DFU_REQUEST_ABORT,
|
|
0,
|
|
priv->iface_number,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
priv->timeout_ms,
|
|
NULL, /* cancellable */
|
|
&error_local)) {
|
|
/* refresh the error code */
|
|
fu_dfu_device_error_fixup(self, &error_local);
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot abort device: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_clear_status:
|
|
* @self: a #FuDfuDevice
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Clears any error status on the DFU device.
|
|
*
|
|
* Returns: %TRUE for success
|
|
**/
|
|
gboolean
|
|
fu_dfu_device_clear_status(FuDfuDevice *self, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* the device has no DFU runtime, so cheat */
|
|
if (priv->state == FU_DFU_STATE_APP_IDLE &&
|
|
fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"not supported as no DFU runtime");
|
|
return FALSE;
|
|
}
|
|
|
|
/* ensure interface is claimed */
|
|
if (!fu_dfu_device_ensure_interface(self, error))
|
|
return FALSE;
|
|
|
|
if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)),
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
FU_DFU_REQUEST_CLRSTATUS,
|
|
0,
|
|
priv->iface_number,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
priv->timeout_ms,
|
|
NULL, /* cancellable */
|
|
&error_local)) {
|
|
/* refresh the error code */
|
|
fu_dfu_device_error_fixup(self, &error_local);
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot clear status on the device: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_get_interface:
|
|
* @self: a #FuDfuDevice
|
|
*
|
|
* Gets the interface number.
|
|
**/
|
|
guint8
|
|
fu_dfu_device_get_interface(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xff);
|
|
return priv->iface_number;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_open:
|
|
* @self: a #FuDfuDevice
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Opens a DFU-capable device.
|
|
*
|
|
* Returns: %TRUE for success
|
|
**/
|
|
static gboolean
|
|
fu_dfu_device_open(FuDevice *device, GError **error)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(device), FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* FuUsbDevice->open */
|
|
if (!FU_DEVICE_CLASS(fu_dfu_device_parent_class)->open(device, error))
|
|
return FALSE;
|
|
|
|
/* the device has no DFU runtime, so cheat */
|
|
if (priv->state == FU_DFU_STATE_APP_IDLE &&
|
|
fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) {
|
|
fu_dfu_device_set_state(self, FU_DFU_STATE_APP_IDLE);
|
|
priv->status = FU_DFU_STATUS_OK;
|
|
}
|
|
|
|
/* GD32VF103 encodes the serial number in UTF-8 (rather than UTF-16)
|
|
* and also uses the first two bytes as the model identifier */
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_GD32)) {
|
|
#if G_USB_CHECK_VERSION(0, 3, 6)
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
|
const guint8 *buf;
|
|
gsize bufsz = 0;
|
|
guint16 langid = G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES;
|
|
guint8 idx = g_usb_device_get_serial_number_index(usb_device);
|
|
g_autofree gchar *chip_id = NULL;
|
|
g_autofree gchar *serial_str = NULL;
|
|
g_autoptr(GBytes) serial_blob = NULL;
|
|
serial_blob =
|
|
g_usb_device_get_string_descriptor_bytes(usb_device, idx, langid, error);
|
|
if (serial_blob == NULL)
|
|
return FALSE;
|
|
if (g_getenv("FWUPD_DFU_VERBOSE") != NULL)
|
|
fu_dump_bytes(G_LOG_DOMAIN, "GD32 serial", serial_blob);
|
|
buf = g_bytes_get_data(serial_blob, &bufsz);
|
|
if (bufsz < 2) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"GD32 serial number invalid");
|
|
return FALSE;
|
|
}
|
|
|
|
/* ID is first two bytes */
|
|
chip_id = g_strdup_printf("%02x%02x", buf[0], buf[1]);
|
|
fu_dfu_device_set_chip_id(self, chip_id);
|
|
|
|
/* serial number follows */
|
|
serial_str = g_strndup((const gchar *)buf + 2, bufsz - 2);
|
|
fu_device_set_serial(FU_DEVICE(device), serial_str);
|
|
#else
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"GUsb version too old to support GD32, "
|
|
"fwupd needs to be rebuilt against 0.3.6 or later");
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
/* set up target ready for use */
|
|
for (guint j = 0; j < priv->targets->len; j++) {
|
|
FuDfuTarget *target = g_ptr_array_index(priv->targets, j);
|
|
if (!fu_dfu_target_setup(target, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_close:
|
|
* @self: a #FuDfuDevice
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Closes a DFU device.
|
|
*
|
|
* Returns: %TRUE for success
|
|
**/
|
|
static gboolean
|
|
fu_dfu_device_close(FuDevice *device, GError **error)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
|
|
|
/* release interface */
|
|
if (priv->claimed_interface) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (!g_usb_device_release_interface(usb_device,
|
|
(gint)priv->iface_number,
|
|
0,
|
|
&error_local)) {
|
|
if (!g_error_matches(error_local,
|
|
G_USB_DEVICE_ERROR,
|
|
G_USB_DEVICE_ERROR_NO_DEVICE)) {
|
|
g_warning("failed to release interface: %s", error_local->message);
|
|
}
|
|
}
|
|
priv->claimed_interface = FALSE;
|
|
}
|
|
|
|
/* FuUsbDevice->close */
|
|
return FU_DEVICE_CLASS(fu_dfu_device_parent_class)->close(device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_probe(FuDevice *device, GError **error)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
|
|
|
/* add all the targets */
|
|
if (!fu_dfu_device_add_targets(self, error)) {
|
|
g_prefix_error(error,
|
|
"%04x:%04x is not supported: ",
|
|
g_usb_device_get_vid(usb_device),
|
|
g_usb_device_get_pid(usb_device));
|
|
return FALSE;
|
|
}
|
|
|
|
/* check capabilities */
|
|
if (!fu_device_has_private_flag(device, FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD)) {
|
|
g_debug("%04x:%04x is missing download capability",
|
|
g_usb_device_get_vid(usb_device),
|
|
g_usb_device_get_pid(usb_device));
|
|
}
|
|
|
|
/* hardware from Jabra literally reboots if you try to retry a failed
|
|
* write -- there's no way to avoid blocking the daemon like this... */
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_ATTACH_EXTRA_RESET))
|
|
fu_device_sleep(device, 10000);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_dfu_device_reset(FuDfuDevice *self, FuProgress *progress, GError **error)
|
|
{
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(GTimer) timer = g_timer_new();
|
|
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_reset(fu_usb_device_get_dev(FU_USB_DEVICE(self)), &error_local)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot reset USB device: %s [%i]",
|
|
error_local->message,
|
|
error_local->code);
|
|
return FALSE;
|
|
}
|
|
g_debug("reset took %.2lfms", g_timer_elapsed(timer, NULL) * 1000);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_attach(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
g_autoptr(FuDfuTarget) target = NULL;
|
|
|
|
g_return_val_if_fail(FU_IS_DFU_DEVICE(device), FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* already in runtime mode */
|
|
if (!fu_dfu_device_refresh_and_clear(self, error))
|
|
return FALSE;
|
|
if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER))
|
|
return TRUE;
|
|
|
|
/* handle weirdness */
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_DETACH_FOR_ATTACH)) {
|
|
if (!fu_dfu_device_request_detach(self, progress, error))
|
|
return FALSE;
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
return TRUE;
|
|
}
|
|
|
|
/* handle m-stack DFU bootloaders */
|
|
if (!priv->done_upload_or_download &&
|
|
fu_device_has_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_ATTACH_UPLOAD_DOWNLOAD)) {
|
|
g_autoptr(GBytes) chunk = NULL;
|
|
g_autoptr(FuDfuTarget) target_zero = NULL;
|
|
g_debug("doing dummy upload to work around m-stack quirk");
|
|
target_zero = fu_dfu_device_get_target_by_alt_setting(self, 0, error);
|
|
if (target_zero == NULL)
|
|
return FALSE;
|
|
chunk = fu_dfu_target_upload_chunk(target_zero, 0, 0, progress, error);
|
|
if (chunk == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
/* get default target */
|
|
target = fu_dfu_device_get_target_by_alt_setting(self, 0, error);
|
|
if (target == NULL)
|
|
return FALSE;
|
|
|
|
/* normal DFU mode just needs a bus reset */
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_BUS_RESET_ATTACH) &&
|
|
fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_WILL_DETACH)) {
|
|
g_debug("Bus reset is not required. Device will reboot to normal");
|
|
} else if (!fu_dfu_target_attach(target, progress, error)) {
|
|
g_prefix_error(error, "failed to attach target: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* there is no USB runtime whatsoever */
|
|
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR))
|
|
return TRUE;
|
|
|
|
/* success */
|
|
priv->force_version = 0x0;
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_dfu_device_upload:
|
|
* @self: a #FuDfuDevice
|
|
* @flags: DFU target flags, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Uploads firmware from the target to the host.
|
|
*
|
|
* Returns: (transfer full): the uploaded firmware, or %NULL for error
|
|
**/
|
|
FuFirmware *
|
|
fu_dfu_device_upload(FuDfuDevice *self,
|
|
FuProgress *progress,
|
|
FuDfuTargetTransferFlags flags,
|
|
GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
gboolean use_dfuse = FALSE;
|
|
g_autoptr(FuFirmware) firmware = NULL;
|
|
|
|
/* ensure interface is claimed */
|
|
if (!fu_dfu_device_ensure_interface(self, error))
|
|
return NULL;
|
|
|
|
/* choose the most appropriate type */
|
|
for (guint i = 0; i < priv->targets->len; i++) {
|
|
FuDfuTarget *target = g_ptr_array_index(priv->targets, i);
|
|
if (fu_device_get_logical_id(FU_DEVICE(target)) != NULL || i > 0) {
|
|
use_dfuse = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (use_dfuse) {
|
|
firmware = fu_dfuse_firmware_new();
|
|
g_debug("switching to DefuSe automatically");
|
|
} else {
|
|
firmware = fu_dfu_firmware_new();
|
|
}
|
|
fu_dfu_firmware_set_vid(FU_DFU_FIRMWARE(firmware), priv->runtime_vid);
|
|
fu_dfu_firmware_set_pid(FU_DFU_FIRMWARE(firmware), priv->runtime_pid);
|
|
fu_dfu_firmware_set_release(FU_DFU_FIRMWARE(firmware), 0xffff);
|
|
|
|
/* upload from each target */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_set_steps(progress, priv->targets->len);
|
|
for (guint i = 0; i < priv->targets->len; i++) {
|
|
FuDfuTarget *target;
|
|
|
|
/* upload to target and proxy signals */
|
|
target = g_ptr_array_index(priv->targets, i);
|
|
|
|
/* ignore some target types */
|
|
if (g_strcmp0(fu_device_get_name(FU_DEVICE(target)), "Option Bytes") == 0) {
|
|
g_debug("ignoring target %s", fu_device_get_name(target));
|
|
continue;
|
|
}
|
|
if (!fu_dfu_target_upload(target,
|
|
firmware,
|
|
fu_progress_get_child(progress),
|
|
DFU_TARGET_TRANSFER_FLAG_NONE,
|
|
error))
|
|
return NULL;
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* do not do the dummy upload for quirked devices */
|
|
priv->done_upload_or_download = TRUE;
|
|
|
|
/* success */
|
|
return g_object_ref(firmware);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_id_compatible(guint16 id_file, guint16 id_runtime, guint16 id_dev)
|
|
{
|
|
/* file doesn't specify */
|
|
if (id_file == 0xffff)
|
|
return TRUE;
|
|
|
|
/* runtime matches */
|
|
if (id_runtime != 0xffff && id_file == id_runtime)
|
|
return TRUE;
|
|
|
|
/* bootloader matches */
|
|
if (id_dev != 0xffff && id_file == id_dev)
|
|
return TRUE;
|
|
|
|
/* nothing */
|
|
return FALSE;
|
|
}
|
|
|
|
static gsize
|
|
fu_dfu_device_calculate_chunks_size(GPtrArray *chunks)
|
|
{
|
|
gsize total = 0;
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index(chunks, i);
|
|
total += fu_chunk_get_data_sz(chk);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_download(FuDfuDevice *self,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FuDfuTargetTransferFlags flags,
|
|
GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
gboolean ret;
|
|
g_autoptr(GPtrArray) images = NULL;
|
|
guint16 firmware_pid = 0xffff;
|
|
guint16 firmware_vid = 0xffff;
|
|
|
|
/* ensure interface is claimed */
|
|
if (!fu_dfu_device_ensure_interface(self, error))
|
|
return FALSE;
|
|
|
|
/* firmware supports footer? */
|
|
if (FU_IS_DFU_FIRMWARE(firmware)) {
|
|
firmware_vid = fu_dfu_firmware_get_vid(FU_DFU_FIRMWARE(firmware));
|
|
firmware_pid = fu_dfu_firmware_get_pid(FU_DFU_FIRMWARE(firmware));
|
|
} else {
|
|
flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID;
|
|
flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID;
|
|
}
|
|
|
|
/* do we allow wildcard VID:PID matches */
|
|
if ((flags & DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID) == 0) {
|
|
if (firmware_vid == 0xffff) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"firmware vendor ID not specified");
|
|
return FALSE;
|
|
}
|
|
}
|
|
if ((flags & DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID) == 0) {
|
|
if (firmware_pid == 0xffff) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"firmware product ID not specified");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* check vendor matches */
|
|
if (priv->runtime_vid != 0xffff) {
|
|
if (!fu_dfu_device_id_compatible(firmware_vid,
|
|
priv->runtime_vid,
|
|
fu_usb_device_get_vid(FU_USB_DEVICE(self)))) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"vendor ID incorrect, expected 0x%04x "
|
|
"got 0x%04x and 0x%04x\n",
|
|
firmware_vid,
|
|
priv->runtime_vid,
|
|
fu_usb_device_get_vid(FU_USB_DEVICE(self)));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* check product matches */
|
|
if (priv->runtime_pid != 0xffff) {
|
|
if (!fu_dfu_device_id_compatible(firmware_pid,
|
|
priv->runtime_pid,
|
|
fu_usb_device_get_pid(FU_USB_DEVICE(self)))) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"product ID incorrect, expected 0x%04x "
|
|
"got 0x%04x and 0x%04x",
|
|
firmware_pid,
|
|
priv->runtime_pid,
|
|
fu_usb_device_get_pid(FU_USB_DEVICE(self)));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* download each target */
|
|
images = fu_firmware_get_images(firmware);
|
|
if (images->len == 0)
|
|
g_ptr_array_add(images, g_object_ref(firmware));
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
for (guint i = 0; i < images->len; i++) {
|
|
FuFirmware *image = g_ptr_array_index(images, i);
|
|
g_autoptr(GPtrArray) chunks = fu_firmware_get_chunks(image, error);
|
|
if (chunks == NULL)
|
|
return FALSE;
|
|
fu_progress_add_step(progress,
|
|
FWUPD_STATUS_DEVICE_WRITE,
|
|
fu_dfu_device_calculate_chunks_size(chunks),
|
|
NULL);
|
|
}
|
|
for (guint i = 0; i < images->len; i++) {
|
|
FuFirmware *image = g_ptr_array_index(images, i);
|
|
FuDfuTargetTransferFlags flags_local = DFU_TARGET_TRANSFER_FLAG_NONE;
|
|
guint8 alt;
|
|
g_autoptr(FuDfuTarget) target_tmp = NULL;
|
|
|
|
alt = fu_firmware_get_idx(image);
|
|
target_tmp = fu_dfu_device_get_target_by_alt_setting(self, alt, error);
|
|
if (target_tmp == NULL)
|
|
return FALSE;
|
|
if (!fu_dfu_target_setup(target_tmp, error))
|
|
return FALSE;
|
|
g_debug("downloading to target: %s",
|
|
fu_device_get_logical_id(FU_DEVICE(target_tmp)));
|
|
|
|
/* download onto target */
|
|
if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY)
|
|
flags_local = DFU_TARGET_TRANSFER_FLAG_VERIFY;
|
|
if (!FU_IS_DFU_FIRMWARE(firmware) ||
|
|
fu_dfu_firmware_get_version(FU_DFU_FIRMWARE(firmware)) == 0x0)
|
|
flags_local |= DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC;
|
|
ret = fu_dfu_target_download(target_tmp,
|
|
image,
|
|
fu_progress_get_child(progress),
|
|
flags_local,
|
|
error);
|
|
if (!ret)
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* do not do the dummy upload for quirked devices */
|
|
priv->done_upload_or_download = TRUE;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
fu_dfu_device_error_fixup(FuDfuDevice *self, GError **error)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
|
|
/* sad panda */
|
|
if (error == NULL)
|
|
return;
|
|
|
|
/* not the right error to query */
|
|
if (!g_error_matches(*error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED))
|
|
return;
|
|
|
|
/* get the status */
|
|
if (!fu_dfu_device_refresh(self, NULL))
|
|
return;
|
|
|
|
/* not in an error state */
|
|
if (priv->state != FU_DFU_STATE_DFU_ERROR)
|
|
return;
|
|
|
|
/* prefix the error */
|
|
switch (priv->status) {
|
|
case FU_DFU_STATUS_OK:
|
|
/* ignore */
|
|
break;
|
|
case FU_DFU_STATUS_ERR_VENDOR:
|
|
g_prefix_error(error, "read protection is active: ");
|
|
break;
|
|
default:
|
|
g_prefix_error(error,
|
|
"[%s,%s]: ",
|
|
fu_dfu_state_to_string(priv->state),
|
|
fu_dfu_status_to_string(priv->status));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GBytes *
|
|
fu_dfu_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
g_autoptr(FuFirmware) firmware = NULL;
|
|
|
|
/* get data from hardware */
|
|
g_debug("uploading from device->host");
|
|
if (!fu_dfu_device_refresh_and_clear(self, error))
|
|
return NULL;
|
|
firmware = fu_dfu_device_upload(self, progress, DFU_TARGET_TRANSFER_FLAG_NONE, error);
|
|
if (firmware == NULL)
|
|
return NULL;
|
|
|
|
/* get the checksum */
|
|
return fu_firmware_write(firmware, error);
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_dfu_device_prepare_firmware(FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
return fu_firmware_new_from_gtypes(fw,
|
|
flags,
|
|
error,
|
|
FU_TYPE_IHEX_FIRMWARE,
|
|
FU_TYPE_DFUSE_FIRMWARE,
|
|
FU_TYPE_DFU_FIRMWARE,
|
|
FU_TYPE_FIRMWARE,
|
|
G_TYPE_INVALID);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
FuDfuTargetTransferFlags transfer_flags = DFU_TARGET_TRANSFER_FLAG_VERIFY;
|
|
|
|
/* open it */
|
|
if (!fu_dfu_device_refresh_and_clear(self, error))
|
|
return FALSE;
|
|
if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) {
|
|
transfer_flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID;
|
|
transfer_flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID;
|
|
}
|
|
|
|
/* hit hardware */
|
|
return fu_dfu_device_download(self, firmware, progress, transfer_flags, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(device);
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
guint64 tmp = 0;
|
|
|
|
if (g_strcmp0(key, FU_QUIRKS_DFU_FORCE_VERSION) == 0) {
|
|
if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT16, error))
|
|
return FALSE;
|
|
priv->force_version = tmp;
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0(key, "DfuForceTimeout") == 0) {
|
|
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error))
|
|
return FALSE;
|
|
priv->timeout_ms = tmp;
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0(key, "DfuForceTransferSize") == 0) {
|
|
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error))
|
|
return FALSE;
|
|
priv->force_transfer_size = tmp;
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0(key, "DfuAltName") == 0) {
|
|
fu_dfu_device_set_chip_id(self, value);
|
|
return TRUE;
|
|
}
|
|
|
|
/* failed */
|
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
fu_dfu_device_set_progress(FuDevice *self, FuProgress *progress)
|
|
{
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 88, "write");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "reload");
|
|
}
|
|
|
|
static void
|
|
fu_dfu_device_finalize(GObject *object)
|
|
{
|
|
FuDfuDevice *self = FU_DFU_DEVICE(object);
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
|
|
g_free(priv->chip_id);
|
|
g_ptr_array_unref(priv->targets);
|
|
|
|
G_OBJECT_CLASS(fu_dfu_device_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
fu_dfu_device_class_init(FuDfuDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
klass_device->set_quirk_kv = fu_dfu_device_set_quirk_kv;
|
|
klass_device->to_string = fu_dfu_device_to_string;
|
|
klass_device->dump_firmware = fu_dfu_device_dump_firmware;
|
|
klass_device->write_firmware = fu_dfu_device_write_firmware;
|
|
klass_device->prepare_firmware = fu_dfu_device_prepare_firmware;
|
|
klass_device->attach = fu_dfu_device_attach;
|
|
klass_device->detach = fu_dfu_device_detach;
|
|
klass_device->reload = fu_dfu_device_reload;
|
|
klass_device->open = fu_dfu_device_open;
|
|
klass_device->close = fu_dfu_device_close;
|
|
klass_device->probe = fu_dfu_device_probe;
|
|
klass_device->set_progress = fu_dfu_device_set_progress;
|
|
object_class->finalize = fu_dfu_device_finalize;
|
|
}
|
|
|
|
static void
|
|
fu_dfu_device_init(FuDfuDevice *self)
|
|
{
|
|
FuDfuDevicePrivate *priv = GET_PRIVATE(self);
|
|
priv->iface_number = 0xff;
|
|
priv->runtime_pid = 0xffff;
|
|
priv->runtime_vid = 0xffff;
|
|
priv->runtime_release = 0xffff;
|
|
priv->state = FU_DFU_STATE_APP_IDLE;
|
|
priv->status = FU_DFU_STATUS_OK;
|
|
priv->targets = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
|
priv->timeout_ms = 1500;
|
|
priv->transfer_size = 64;
|
|
priv->force_version = G_MAXUINT16;
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS);
|
|
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID);
|
|
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED);
|
|
fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
|
|
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD,
|
|
"can-download");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_CAN_UPLOAD,
|
|
"can-upload");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_MANIFEST_TOL,
|
|
"manifest-tol");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_WILL_DETACH,
|
|
"will-detach");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_CAN_ACCELERATE,
|
|
"can-accelerate");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_ATTACH_EXTRA_RESET,
|
|
"attach-extra-reset");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_ATTACH_UPLOAD_DOWNLOAD,
|
|
"attach-upload-download");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE,
|
|
"force-dfu-mode");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_IGNORE_POLLTIMEOUT,
|
|
"ignore-polltimeout");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_IGNORE_RUNTIME,
|
|
"ignore-runtime");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_IGNORE_UPLOAD,
|
|
"ignore-upload");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME,
|
|
"no-dfu-runtime");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_NO_GET_STATUS_UPLOAD,
|
|
"no-get-status-upload");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_NO_PID_CHANGE,
|
|
"no-pid-change");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_USE_ANY_INTERFACE,
|
|
"use-any-interface");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_USE_ATMEL_AVR,
|
|
"use-atmel-avr");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_USE_PROTOCOL_ZERO,
|
|
"use-protocol-zero");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL,
|
|
"legacy-protocol");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_DETACH_FOR_ATTACH,
|
|
"detach-for-attach");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_ABSENT_SECTOR_SIZE,
|
|
"absent-sector-size");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_MANIFEST_POLL,
|
|
"manifest-poll");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_NO_BUS_RESET_ATTACH,
|
|
"no-bus-reset-attach");
|
|
fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_GD32, "gd32");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_ALLOW_ZERO_POLLTIMEOUT,
|
|
"allow-zero-polltimeout");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_DEVICE_FLAG_INDEX_FORCE_DETACH,
|
|
"index-force-detach");
|
|
}
|