mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-03 13:41:12 +00:00

Now incorporate is fixed to copy across the properties we need in the superclass, we don't need to do the subclass ->probe(). Note, we still need to do the subclassed ->probe() when using FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT or when looking at properties on the parent device. This also removes the spurious 'already set GType to FuVliUsbhubDevice, ignoring FuVliUsbhubDevice' messages when running the daemon.
1763 lines
51 KiB
C
1763 lines
51 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.
|
|
*
|
|
* 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 <string.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);
|
|
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;
|
|
|
|
/* 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 (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 > 0)
|
|
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 transers
|
|
* 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];
|
|
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;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
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,
|
|
priv->iface_number,
|
|
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 = 0x0;
|
|
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)) {
|
|
g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
|
|
fu_progress_sleep(progress, 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 (value != NULL) {
|
|
gsize valuesz = strlen(value);
|
|
return fu_firmware_strparse_uint16_safe(value,
|
|
valuesz,
|
|
0,
|
|
&priv->force_version,
|
|
error);
|
|
}
|
|
g_set_error_literal(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid DFU version");
|
|
return FALSE;
|
|
}
|
|
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;
|
|
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_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");
|
|
}
|