mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-06 13:02:18 +00:00

The DFU specification specifies that only one of the DFU interfaces has to export a functional descriptor; I assumed they all had to. Adding support for this kind of device rapidly turned into a massive restructure and it was all too complicated anyway. Reorganise the code so that we can support these kinds of devices and clean up the API so it's sane and easy to use. This also allows us to generate the GObject introspection GIR and to also install libdfu as a shared library. If you've got any comments about the API, please shout now as when 6.0 is released it will become API and ABI stable.
1521 lines
38 KiB
C
1521 lines
38 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* Licensed under the GNU Lesser General Public License Version 2.1
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/**
|
|
* SECTION:dfu-device
|
|
* @short_description: Object representing a DFU-capable device
|
|
*
|
|
* 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: #DfuTarget, #DfuFirmware
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "dfu-common.h"
|
|
#include "dfu-device-private.h"
|
|
#include "dfu-error.h"
|
|
#include "dfu-target-private.h"
|
|
|
|
static void dfu_device_finalize (GObject *object);
|
|
|
|
/**
|
|
* DfuDevicePrivate:
|
|
*
|
|
* Private #DfuDevice data
|
|
**/
|
|
typedef struct {
|
|
DfuDeviceAttributes attributes;
|
|
DfuDeviceQuirks quirks;
|
|
DfuMode mode;
|
|
DfuState state;
|
|
DfuStatus status;
|
|
GPtrArray *targets;
|
|
GUsbDevice *dev;
|
|
gboolean device_open;
|
|
gboolean dfuse_supported;
|
|
guint16 runtime_pid;
|
|
guint16 runtime_vid;
|
|
guint16 transfer_size;
|
|
guint8 iface_number;
|
|
guint dnload_timeout;
|
|
guint timeout_ms;
|
|
} DfuDevicePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (DfuDevice, dfu_device, G_TYPE_OBJECT)
|
|
#define GET_PRIVATE(o) (dfu_device_get_instance_private (o))
|
|
|
|
/**
|
|
* dfu_device_class_init:
|
|
**/
|
|
static void
|
|
dfu_device_class_init (DfuDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = dfu_device_finalize;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_init:
|
|
**/
|
|
static void
|
|
dfu_device_init (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
priv->runtime_pid = 0xffff;
|
|
priv->runtime_vid = 0xffff;
|
|
priv->state = DFU_STATE_APP_IDLE;
|
|
priv->status = DFU_STATUS_OK;
|
|
priv->targets = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
|
priv->timeout_ms = 500;
|
|
priv->transfer_size = 64;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_transfer_size:
|
|
* @device: a #GUsbDevice
|
|
*
|
|
* Gets the transfer size in bytes.
|
|
*
|
|
* Return value: packet size, or 0 for unknown
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint16
|
|
dfu_device_get_transfer_size (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff);
|
|
return priv->transfer_size;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_download_timeout:
|
|
* @device: a #GUsbDevice
|
|
*
|
|
* Gets the download timeout in ms.
|
|
*
|
|
* Return value: delay, or 0 for unknown
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint
|
|
dfu_device_get_download_timeout (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0);
|
|
return priv->dnload_timeout;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_set_transfer_size:
|
|
* @device: a #GUsbDevice
|
|
* @transfer_size: maximum packet size
|
|
*
|
|
* Sets the transfer size in bytes.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_device_set_transfer_size (DfuDevice *device, guint16 transfer_size)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_if_fail (DFU_IS_DEVICE (device));
|
|
priv->transfer_size = transfer_size;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_finalize:
|
|
**/
|
|
static void
|
|
dfu_device_finalize (GObject *object)
|
|
{
|
|
DfuDevice *device = DFU_DEVICE (object);
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
|
|
/* don't rely on this */
|
|
if (priv->device_open) {
|
|
g_debug ("auto-closing DfuDevice, call dfu_device_close()");
|
|
g_usb_device_close (priv->dev, NULL);
|
|
}
|
|
|
|
g_ptr_array_unref (priv->targets);
|
|
|
|
G_OBJECT_CLASS (dfu_device_parent_class)->finalize (object);
|
|
}
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 bLength;
|
|
guint8 bDescriptorType;
|
|
guint8 bmAttributes;
|
|
guint16 wDetachTimeOut;
|
|
guint16 wTransferSize;
|
|
guint16 bcdDFUVersion;
|
|
} DfuFuncDescriptor;
|
|
|
|
/**
|
|
* dfu_device_parse_iface_data:
|
|
**/
|
|
static void
|
|
dfu_device_parse_iface_data (DfuDevice *device, GBytes *iface_data)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
const DfuFuncDescriptor *desc;
|
|
gsize iface_data_length;
|
|
|
|
/* parse the functional descriptor */
|
|
desc = g_bytes_get_data (iface_data, &iface_data_length);
|
|
if (iface_data_length != 0x09) {
|
|
g_warning ("interface found, but not interface data");
|
|
return;
|
|
}
|
|
|
|
/* check sanity */
|
|
if (desc->bLength != 0x09) {
|
|
g_warning ("DFU interface data has incorrect length: 0x%02x",
|
|
desc->bLength);
|
|
}
|
|
|
|
/* check transfer size */
|
|
priv->transfer_size = desc->wTransferSize;
|
|
if (priv->transfer_size == 0x0000) {
|
|
g_warning ("DFU transfer size invalid, using default: 0x%04x",
|
|
desc->wTransferSize);
|
|
priv->transfer_size = 64;
|
|
}
|
|
|
|
/* check DFU version */
|
|
if (priv->quirks & DFU_DEVICE_QUIRK_IGNORE_INVALID_VERSION) {
|
|
g_debug ("ignoring quirked DFU version");
|
|
} else {
|
|
if (desc->bcdDFUVersion == 0x0100 ||
|
|
desc->bcdDFUVersion == 0x0101) {
|
|
g_debug ("basic DFU, no DfuSe support");
|
|
priv->dfuse_supported = FALSE;
|
|
} else if (desc->bcdDFUVersion == 0x011a) {
|
|
g_debug ("DfuSe support");
|
|
priv->dfuse_supported = TRUE;
|
|
} else {
|
|
g_warning ("DFU version is invalid: 0x%04x",
|
|
desc->bcdDFUVersion);
|
|
}
|
|
}
|
|
|
|
/* ST-specific */
|
|
if (priv->dfuse_supported &&
|
|
desc->bmAttributes & DFU_DEVICE_ATTRIBUTE_CAN_ACCELERATE)
|
|
priv->transfer_size = 0x1000;
|
|
|
|
/* get attributes about the DFU operation */
|
|
priv->attributes = desc->bmAttributes;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_update_from_iface:
|
|
**/
|
|
static gboolean
|
|
dfu_device_update_from_iface (DfuDevice *device, GUsbInterface *iface)
|
|
{
|
|
DfuMode target_mode = DFU_MODE_UNKNOWN;
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
|
|
/* runtime */
|
|
if (g_usb_interface_get_protocol (iface) == 0x01)
|
|
target_mode = DFU_MODE_RUNTIME;
|
|
|
|
/* DFU */
|
|
if (g_usb_interface_get_protocol (iface) == 0x02)
|
|
target_mode = DFU_MODE_DFU;
|
|
|
|
/* the DSO Nano has uses 0 instead of 2 when in DFU target_mode */
|
|
if (dfu_device_has_quirk (device, DFU_DEVICE_QUIRK_USE_PROTOCOL_ZERO) &&
|
|
g_usb_interface_get_protocol (iface) == 0x00)
|
|
target_mode = DFU_MODE_DFU;
|
|
|
|
/* nothing found */
|
|
if (target_mode == DFU_MODE_UNKNOWN)
|
|
return FALSE;
|
|
|
|
/* in DFU mode, the interface is supposed to be 0 */
|
|
if (target_mode == DFU_MODE_DFU && g_usb_interface_get_number (iface) != 0)
|
|
g_warning ("iface has to be 0 in DFU mode, got 0x%02i",
|
|
g_usb_interface_get_number (iface));
|
|
|
|
/* some devices set the wrong mode */
|
|
if (dfu_device_has_quirk (device, DFU_DEVICE_QUIRK_FORCE_DFU_MODE))
|
|
target_mode = DFU_MODE_DFU;
|
|
|
|
/* save for reset */
|
|
if (target_mode == DFU_MODE_RUNTIME ||
|
|
(priv->quirks & DFU_DEVICE_QUIRK_NO_PID_CHANGE)) {
|
|
priv->runtime_vid = g_usb_device_get_vid (priv->dev);
|
|
priv->runtime_pid = g_usb_device_get_pid (priv->dev);
|
|
}
|
|
|
|
priv->mode = target_mode;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_add_targets:
|
|
**/
|
|
static gboolean
|
|
dfu_device_add_targets (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
guint i;
|
|
GUsbInterface *iface;
|
|
g_autoptr(GPtrArray) ifaces = NULL;
|
|
|
|
/* add all DFU-capable targets */
|
|
ifaces = g_usb_device_get_interfaces (priv->dev, NULL);
|
|
if (ifaces == NULL)
|
|
return FALSE;
|
|
g_ptr_array_set_size (priv->targets, 0);
|
|
for (i = 0; i < ifaces->len; i++) {
|
|
GBytes *iface_data = NULL;
|
|
DfuTarget *target;
|
|
iface = g_ptr_array_index (ifaces, i);
|
|
if (g_usb_interface_get_class (iface) != G_USB_DEVICE_CLASS_APPLICATION_SPECIFIC)
|
|
continue;
|
|
if (g_usb_interface_get_subclass (iface) != 0x01)
|
|
continue;
|
|
target = dfu_target_new (device, iface);
|
|
if (target == NULL)
|
|
continue;
|
|
|
|
/* add target */
|
|
priv->iface_number = g_usb_interface_get_number (iface);
|
|
g_ptr_array_add (priv->targets, target);
|
|
dfu_device_update_from_iface (device, iface);
|
|
|
|
/* parse any interface data */
|
|
iface_data = g_usb_interface_get_extra (iface);
|
|
if (g_bytes_get_size (iface_data) > 0)
|
|
dfu_device_parse_iface_data (device, iface_data);
|
|
}
|
|
return priv->targets->len > 0;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_has_quirk: (skip)
|
|
* @device: A #DfuDevice
|
|
* @quirk: A #DfuDeviceQuirks
|
|
*
|
|
* Returns if a device has a specific quirk
|
|
*
|
|
* Return value: %TRUE if the device has this quirk
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_has_quirk (DfuDevice *device, DfuDeviceQuirks quirk)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0x0);
|
|
return (priv->quirks & quirk) > 0;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_can_upload:
|
|
* @device: a #GUsbDevice
|
|
*
|
|
* Gets if the device can upload.
|
|
*
|
|
* Return value: %TRUE if the device can upload from device to host
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_can_upload (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
return (priv->attributes & DFU_DEVICE_ATTRIBUTE_CAN_UPLOAD) > 0;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_can_download:
|
|
* @device: a #GUsbDevice
|
|
*
|
|
* Gets if the device can download.
|
|
*
|
|
* Return value: %TRUE if the device can download from host to device
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_can_download (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
return (priv->attributes & DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD) > 0;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_set_timeout:
|
|
* @device: a #DfuDevice
|
|
* @timeout_ms: the timeout in ms
|
|
*
|
|
* Sets the USB timeout to use when contacting the USB device.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_device_set_timeout (DfuDevice *device, guint timeout_ms)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_if_fail (DFU_IS_DEVICE (device));
|
|
priv->timeout_ms = timeout_ms;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_mode:
|
|
* @device: a #GUsbDevice
|
|
*
|
|
* Gets the device mode.
|
|
*
|
|
* Return value: enumerated mode, e.g. %DFU_MODE_RUNTIME
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuMode
|
|
dfu_device_get_mode (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0);
|
|
return priv->mode;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_timeout:
|
|
* @device: a #GUsbDevice
|
|
*
|
|
* Gets the device timeout.
|
|
*
|
|
* Return value: enumerated timeout in ms
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint
|
|
dfu_device_get_timeout (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0);
|
|
return priv->timeout_ms;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_state:
|
|
* @device: a #GUsbDevice
|
|
*
|
|
* Gets the device state.
|
|
*
|
|
* Return value: enumerated state, e.g. %DFU_STATE_DFU_UPLOAD_IDLE
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuState
|
|
dfu_device_get_state (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0);
|
|
return priv->state;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_status:
|
|
* @device: a #GUsbDevice
|
|
*
|
|
* Gets the device status.
|
|
*
|
|
* Return value: enumerated status, e.g. %DFU_STATUS_ERR_ADDRESS
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuStatus
|
|
dfu_device_get_status (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0);
|
|
return priv->status;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_has_attribute: (skip)
|
|
* @device: A #DfuDevice
|
|
* @attribute: A #DfuDeviceAttributes, e.g. %DFU_DEVICE_ATTRIBUTE_CAN_DOWNLOAD
|
|
*
|
|
* Returns if an attribute set for the device.
|
|
*
|
|
* Return value: %TRUE if the attribute is set
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_has_attribute (DfuDevice *device, DfuDeviceAttributes attribute)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0x0);
|
|
return (priv->attributes & attribute) > 0;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_has_dfuse_support:
|
|
* @device: A #DfuDevice
|
|
*
|
|
* Returns is DfuSe is supported on a device.
|
|
*
|
|
* Return value: %TRUE for DfuSe
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_has_dfuse_support (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
return priv->dfuse_supported;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_set_quirks:
|
|
**/
|
|
static void
|
|
dfu_target_set_quirks (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
guint16 vid, pid, release;
|
|
|
|
vid = g_usb_device_get_vid (priv->dev);
|
|
pid = g_usb_device_get_pid (priv->dev);
|
|
release = g_usb_device_get_release (priv->dev);
|
|
|
|
/* Openmoko Freerunner / GTA02 */
|
|
if ((vid == 0x1d50 || vid == 0x1457) &&
|
|
pid >= 0x5117 && pid <= 0x5126)
|
|
priv->quirks |= DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT |
|
|
DFU_DEVICE_QUIRK_NO_PID_CHANGE |
|
|
DFU_DEVICE_QUIRK_NO_GET_STATUS_UPLOAD;
|
|
|
|
/* OpenPCD Reader */
|
|
if (vid == 0x16c0 && pid == 0x076b)
|
|
priv->quirks |= DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT;
|
|
|
|
/* Siemens AG, PXM 40 & PXM 50 */
|
|
if (vid == 0x0908 && (pid == 0x02c4 || pid == 0x02c5) && release == 0x0)
|
|
priv->quirks |= DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT;
|
|
|
|
/* Midiman M-Audio Transit */
|
|
if (vid == 0x0763 && pid == 0x2806)
|
|
priv->quirks |= DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT;
|
|
|
|
/* the LPC DFU bootloader uses the wrong mode */
|
|
if (vid == 0x1fc9 && pid == 0x000c)
|
|
priv->quirks |= DFU_DEVICE_QUIRK_FORCE_DFU_MODE;
|
|
|
|
/* the Leaflabs Maple3 is known broken */
|
|
if (vid == 0x1eaf && pid == 0x0003 && release == 0x0200)
|
|
priv->quirks |= DFU_DEVICE_QUIRK_IGNORE_INVALID_VERSION;
|
|
|
|
/* the DSO Nano has uses 0 instead of 2 when in DFU mode */
|
|
// quirks |= DFU_DEVICE_QUIRK_USE_PROTOCOL_ZERO;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_new:
|
|
* @dev: A #GUsbDevice
|
|
*
|
|
* Creates a new DFU device object.
|
|
*
|
|
* Return value: a new #DfuDevice, or %NULL if @dev was not DFU-capable
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuDevice *
|
|
dfu_device_new (GUsbDevice *dev)
|
|
{
|
|
DfuDevicePrivate *priv;
|
|
DfuDevice *device;
|
|
device = g_object_new (DFU_TYPE_DEVICE, NULL);
|
|
priv = GET_PRIVATE (device);
|
|
priv->dev = g_object_ref (dev);
|
|
if (!dfu_device_add_targets (device)) {
|
|
g_object_unref (device);
|
|
return NULL;
|
|
}
|
|
dfu_target_set_quirks (device);
|
|
return device;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_targets:
|
|
* @device: a #DfuDevice
|
|
*
|
|
* Gets all the targets for this device.
|
|
*
|
|
* Return value: (transfer none) (element-type DfuTarget): #DfuTarget, or %NULL
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
GPtrArray *
|
|
dfu_device_get_targets (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), NULL);
|
|
return priv->targets;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_target_by_alt_setting:
|
|
* @device: a #DfuDevice
|
|
* @alt_setting: the setting used to find
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Gets a target with a specific alternative setting.
|
|
*
|
|
* Return value: (transfer full): a #DfuTarget, or %NULL
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuTarget *
|
|
dfu_device_get_target_by_alt_setting (DfuDevice *device,
|
|
guint8 alt_setting,
|
|
GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
DfuTarget *target;
|
|
guint i;
|
|
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
/* find by ID */
|
|
for (i = 0; i < priv->targets->len; i++) {
|
|
target = g_ptr_array_index (priv->targets, i);
|
|
if (dfu_target_get_alt_setting (target) == alt_setting)
|
|
return g_object_ref (target);
|
|
}
|
|
|
|
/* failed */
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_FOUND,
|
|
"No target with alt-setting %i",
|
|
alt_setting);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_target_by_alt_name:
|
|
* @device: a #DfuDevice
|
|
* @alt_name: the name used to find
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Gets a target with a specific alternative name.
|
|
*
|
|
* Return value: (transfer full): a #DfuTarget, or %NULL
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuTarget *
|
|
dfu_device_get_target_by_alt_name (DfuDevice *device,
|
|
const gchar *alt_name,
|
|
GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
DfuTarget *target;
|
|
guint i;
|
|
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
/* find by ID */
|
|
for (i = 0; i < priv->targets->len; i++) {
|
|
target = g_ptr_array_index (priv->targets, i);
|
|
if (g_strcmp0 (dfu_target_get_alt_name (target, NULL), alt_name) == 0)
|
|
return g_object_ref (target);
|
|
}
|
|
|
|
/* failed */
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_FOUND,
|
|
"No target with alt-name %s",
|
|
alt_name);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_runtime_vid:
|
|
* @device: a #DfuDevice
|
|
*
|
|
* Gets the runtime vendor ID.
|
|
*
|
|
* Return value: vendor ID, or 0xffff for unknown
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint16
|
|
dfu_device_get_runtime_vid (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff);
|
|
return priv->runtime_vid;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_runtime_pid:
|
|
* @device: a #DfuDevice
|
|
*
|
|
* Gets the runtime product ID.
|
|
*
|
|
* Return value: product ID, or 0xffff for unknown
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint16
|
|
dfu_device_get_runtime_pid (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0xffff);
|
|
return priv->runtime_pid;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_usb_dev: (skip)
|
|
* @device: a #DfuDevice
|
|
*
|
|
* Gets the internal USB device for the #DfuDevice.
|
|
*
|
|
* NOTE: This may change at runtime if the device is replugged or
|
|
* reset.
|
|
*
|
|
* Returns: (transfer none): the internal USB device
|
|
**/
|
|
GUsbDevice *
|
|
dfu_device_get_usb_dev (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), NULL);
|
|
return priv->dev;
|
|
}
|
|
|
|
|
|
/**
|
|
* dfu_device_refresh:
|
|
* @device: a #DfuDevice
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Refreshes the cached properties on the DFU device.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_refresh (DfuDevice *device, GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
gsize actual_length = 0;
|
|
guint8 buf[6];
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (priv->dev,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
DFU_REQUEST_GETSTATUS,
|
|
0,
|
|
priv->iface_number,
|
|
buf, sizeof(buf), &actual_length,
|
|
priv->timeout_ms,
|
|
cancellable,
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot get device state: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
if (actual_length != 6) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"cannot get device status, invalid size: %04x",
|
|
(guint) actual_length);
|
|
}
|
|
priv->status = buf[0];
|
|
priv->state = buf[4];
|
|
if (dfu_device_has_quirk (device, DFU_DEVICE_QUIRK_IGNORE_POLLTIMEOUT)) {
|
|
priv->dnload_timeout = 5;
|
|
} else {
|
|
priv->dnload_timeout = buf[1] +
|
|
(((guint32) buf[2]) << 8) +
|
|
(((guint32) buf[3]) << 16);
|
|
}
|
|
g_debug ("refreshed status=%s and state=%s",
|
|
dfu_status_to_string (priv->status),
|
|
dfu_state_to_string (priv->state));
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_detach:
|
|
* @device: a #DfuDevice
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Detaches the device putting it into DFU-mode.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_detach (DfuDevice *device, GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (priv->dev,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
DFU_REQUEST_DETACH,
|
|
0,
|
|
priv->iface_number,
|
|
NULL, 0, NULL,
|
|
priv->timeout_ms,
|
|
cancellable,
|
|
&error_local)) {
|
|
/* refresh the error code */
|
|
dfu_device_error_fixup (device, cancellable, &error_local);
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot detach device: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* do a host reset */
|
|
if ((priv->attributes & DFU_DEVICE_ATTRIBUTE_WILL_DETACH) == 0) {
|
|
g_debug ("doing device reset as host will not self-reset");
|
|
if (!dfu_device_reset (device, error))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_abort:
|
|
* @device: a #DfuDevice
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Aborts any upload or download in progress.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_abort (DfuDevice *device, GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (priv->dev,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
DFU_REQUEST_ABORT,
|
|
0,
|
|
priv->iface_number,
|
|
NULL, 0, NULL,
|
|
priv->timeout_ms,
|
|
cancellable,
|
|
&error_local)) {
|
|
/* refresh the error code */
|
|
dfu_device_error_fixup (device, cancellable, &error_local);
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot abort device: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_clear_status:
|
|
* @device: a #DfuDevice
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Clears any error status on the DFU device.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_clear_status (DfuDevice *device, GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (priv->dev,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
DFU_REQUEST_CLRSTATUS,
|
|
0,
|
|
priv->iface_number,
|
|
NULL, 0, NULL,
|
|
priv->timeout_ms,
|
|
cancellable,
|
|
&error_local)) {
|
|
/* refresh the error code */
|
|
dfu_device_error_fixup (device, cancellable, &error_local);
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot clear status on the device: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_get_interface:
|
|
* @device: a #DfuDevice
|
|
*
|
|
* Gets the interface number.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint8
|
|
dfu_device_get_interface (DfuDevice *device)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), 0xff);
|
|
return priv->iface_number;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_open:
|
|
* @device: a #DfuDevice
|
|
* @flags: #DfuDeviceOpenFlags, e.g. %DFU_DEVICE_OPEN_FLAG_NONE
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Opens a DFU-capable device.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_open (DfuDevice *device, DfuDeviceOpenFlags flags,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* just ignore */
|
|
if (priv->device_open)
|
|
return TRUE;
|
|
|
|
/* open */
|
|
if (!g_usb_device_open (priv->dev, &error_local)) {
|
|
if (g_error_matches (error_local,
|
|
G_USB_DEVICE_ERROR,
|
|
G_USB_DEVICE_ERROR_PERMISSION_DENIED)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_PERMISSION_DENIED,
|
|
"%s", error_local->message);
|
|
return FALSE;
|
|
}
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INVALID_DEVICE,
|
|
"cannot open device %s: %s",
|
|
g_usb_device_get_platform_id (priv->dev),
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* claim the correct interface */
|
|
if (!g_usb_device_claim_interface (priv->dev, (gint) priv->iface_number, 0, &error_local)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INVALID_DEVICE,
|
|
"cannot claim interface %i: %s",
|
|
priv->iface_number, error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* automatically abort any uploads or downloads */
|
|
if ((flags & DFU_DEVICE_OPEN_FLAG_NO_AUTO_REFRESH) == 0) {
|
|
if (!dfu_device_refresh (device, cancellable, error))
|
|
return FALSE;
|
|
switch (priv->state) {
|
|
case DFU_STATE_DFU_UPLOAD_IDLE:
|
|
case DFU_STATE_DFU_DNLOAD_IDLE:
|
|
case DFU_STATE_DFU_DNLOAD_SYNC:
|
|
g_debug ("aborting transfer %s", dfu_status_to_string (priv->status));
|
|
if (!dfu_device_abort (device, cancellable, error))
|
|
return FALSE;
|
|
break;
|
|
case DFU_STATE_DFU_ERROR:
|
|
g_debug ("clearing error %s", dfu_status_to_string (priv->status));
|
|
if (!dfu_device_clear_status (device, cancellable, error))
|
|
return FALSE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
priv->device_open = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_close:
|
|
* @device: a #DfuDevice
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Closes a DFU device.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_close (DfuDevice *device, GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* only close if open */
|
|
if (priv->device_open) {
|
|
g_usb_device_release_interface (priv->dev,
|
|
(gint) priv->iface_number,
|
|
0, NULL);
|
|
if (!g_usb_device_close (priv->dev, error))
|
|
return FALSE;
|
|
priv->device_open = FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_set_new_usb_dev:
|
|
**/
|
|
static gboolean
|
|
dfu_device_set_new_usb_dev (DfuDevice *device, GUsbDevice *dev,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
gboolean reopen_device = FALSE;
|
|
|
|
/* close */
|
|
if (priv->device_open) {
|
|
if (!dfu_device_close (device, error))
|
|
return FALSE;
|
|
reopen_device = TRUE;
|
|
}
|
|
|
|
/* set the new USB device */
|
|
g_set_object (&priv->dev, dev);
|
|
|
|
/* update all the targets */
|
|
g_ptr_array_set_size (priv->targets, 0);
|
|
if (!dfu_device_add_targets (device)) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"replugged device is not DFU-capable");
|
|
return FALSE;
|
|
}
|
|
|
|
/* reclaim */
|
|
if (reopen_device) {
|
|
if (!dfu_device_open (device, DFU_DEVICE_OPEN_FLAG_NONE,
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_wait_for_replug:
|
|
* @device: a #DfuDevice
|
|
* @timeout: the maximum amount of time to wait
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Waits for a DFU device to disconnect and reconnect.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_wait_for_replug (DfuDevice *device, guint timeout,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
const guint poll_interval_ms = 100;
|
|
gboolean went_away = FALSE;
|
|
guint16 pid;
|
|
guint16 vid;
|
|
guint i;
|
|
g_autofree gchar *platform_id = NULL;
|
|
g_autoptr(GUsbContext) usb_ctx = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* keep copies */
|
|
platform_id = g_strdup (g_usb_device_get_platform_id (priv->dev));
|
|
vid = g_usb_device_get_vid (priv->dev);
|
|
pid = g_usb_device_get_pid (priv->dev);
|
|
|
|
/* keep trying */
|
|
g_object_get (priv->dev, "context", &usb_ctx, NULL);
|
|
for (i = 0; i < timeout / poll_interval_ms; i++) {
|
|
g_autoptr(GUsbDevice) dev_tmp = NULL;
|
|
g_usleep (poll_interval_ms * 1000);
|
|
g_usb_context_enumerate (usb_ctx);
|
|
dev_tmp = g_usb_context_find_by_platform_id (usb_ctx, platform_id, NULL);
|
|
if (dev_tmp == NULL) {
|
|
went_away = TRUE;
|
|
continue;
|
|
}
|
|
|
|
/* some devices use the same PID for DFU mode */
|
|
if (priv->quirks & DFU_DEVICE_QUIRK_NO_PID_CHANGE) {
|
|
return dfu_device_set_new_usb_dev (device, dev_tmp,
|
|
cancellable, error);
|
|
}
|
|
|
|
/* VID:PID changed so find a DFU iface with the same alt */
|
|
if (vid != g_usb_device_get_vid (dev_tmp) ||
|
|
pid != g_usb_device_get_pid (dev_tmp)) {
|
|
return dfu_device_set_new_usb_dev (device, dev_tmp,
|
|
cancellable, error);
|
|
}
|
|
}
|
|
|
|
/* target went off into the woods */
|
|
if (went_away) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INVALID_DEVICE,
|
|
"target went away but did not come back");
|
|
return FALSE;
|
|
}
|
|
|
|
/* VID and PID did not change */
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INVALID_DEVICE,
|
|
"target came back with same VID:PID values");
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_reset:
|
|
* @device: a #DfuDevice
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Resets the USB device.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_reset (DfuDevice *device, GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_reset (priv->dev, &error_local)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INVALID_DEVICE,
|
|
"cannot reset USB device: %s [%i]",
|
|
error_local->message,
|
|
error_local->code);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_upload:
|
|
* @device: a #DfuDevice
|
|
* @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @progress_cb: (scope call): a #GFileProgressCallback, or %NULL
|
|
* @progress_cb_data: user data to pass to @progress_cb
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Uploads firmware from the target to the host.
|
|
*
|
|
* Return value: (transfer full): the uploaded firmware, or %NULL for error
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuFirmware *
|
|
dfu_device_upload (DfuDevice *device,
|
|
DfuTargetTransferFlags flags,
|
|
GCancellable *cancellable,
|
|
DfuProgressCallback progress_cb,
|
|
gpointer progress_cb_data,
|
|
GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
guint i;
|
|
g_autoptr(DfuFirmware) firmware = NULL;
|
|
g_autoptr(GPtrArray) targets = NULL;
|
|
|
|
/* create ahead of time */
|
|
firmware = dfu_firmware_new ();
|
|
dfu_firmware_set_vid (firmware, priv->runtime_vid);
|
|
dfu_firmware_set_pid (firmware, priv->runtime_pid);
|
|
dfu_firmware_set_release (firmware, 0xffff);
|
|
|
|
/* APP -> DFU */
|
|
if (dfu_device_get_mode (device) == DFU_MODE_RUNTIME) {
|
|
if ((flags & DFU_TARGET_TRANSFER_FLAG_DETACH) == 0) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"device is not in DFU mode");
|
|
return FALSE;
|
|
}
|
|
g_debug ("detaching");
|
|
|
|
/* inform UI there's going to be a detach:attach */
|
|
if (progress_cb != NULL) {
|
|
progress_cb (DFU_STATE_APP_DETACH, 0, 0,
|
|
progress_cb_data);
|
|
}
|
|
|
|
/* detach and USB reset */
|
|
if (!dfu_device_detach (device, NULL, error))
|
|
return NULL;
|
|
if (!dfu_device_wait_for_replug (device, 5000, NULL, error))
|
|
return NULL;
|
|
}
|
|
|
|
/* upload from each target */
|
|
targets = dfu_device_get_targets (device);
|
|
for (i = 0; i < targets->len; i++) {
|
|
DfuTarget *target;
|
|
g_autoptr(DfuImage) image = NULL;
|
|
target = g_ptr_array_index (targets, i);
|
|
image = dfu_target_upload (target,
|
|
DFU_TARGET_TRANSFER_FLAG_NONE,
|
|
cancellable,
|
|
progress_cb,
|
|
progress_cb_data,
|
|
error);
|
|
if (image == NULL)
|
|
return NULL;
|
|
dfu_firmware_add_image (firmware, image);
|
|
}
|
|
|
|
/* choose the most appropriate type */
|
|
if (targets->len > 1) {
|
|
g_debug ("switching to DefuSe automatically");
|
|
dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_DFUSE);
|
|
} else {
|
|
dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_DFU_1_0);
|
|
}
|
|
|
|
/* do host reset */
|
|
if ((flags & DFU_TARGET_TRANSFER_FLAG_HOST_RESET) > 0 ||
|
|
(flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) > 0) {
|
|
if (!dfu_device_reset (device, error))
|
|
return NULL;
|
|
}
|
|
|
|
/* boot to runtime */
|
|
if (flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) {
|
|
g_debug ("booting to runtime");
|
|
|
|
/* inform UI there's going to be a detach:attach */
|
|
if (progress_cb != NULL) {
|
|
progress_cb (DFU_STATE_APP_DETACH, 0, 0,
|
|
progress_cb_data);
|
|
}
|
|
|
|
/* DFU -> APP */
|
|
if (!dfu_device_wait_for_replug (device, 2000, cancellable, error))
|
|
return NULL;
|
|
}
|
|
|
|
/* success */
|
|
return g_object_ref (firmware);
|
|
}
|
|
|
|
/**
|
|
* dfu_device_download:
|
|
* @device: a #DfuDevice
|
|
* @firmware: a #DfuFirmware
|
|
* @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @progress_cb: (scope call): a #GFileProgressCallback, or %NULL
|
|
* @progress_cb_data: user data to pass to @progress_cb
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Downloads firmware from the host to the target, optionally verifying
|
|
* the transfer.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_device_download (DfuDevice *device,
|
|
DfuFirmware *firmware,
|
|
DfuTargetTransferFlags flags,
|
|
GCancellable *cancellable,
|
|
DfuProgressCallback progress_cb,
|
|
gpointer progress_cb_data,
|
|
GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
GPtrArray *images;
|
|
guint i;
|
|
g_autoptr(GPtrArray) targets = NULL;
|
|
|
|
/* do we allow wildcard VID:PID matches */
|
|
if ((flags & DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID) == 0) {
|
|
if (dfu_firmware_get_vid (firmware) == 0xffff) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"firmware vendor ID not specified");
|
|
return FALSE;
|
|
}
|
|
}
|
|
if ((flags & DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID) == 0) {
|
|
if (dfu_firmware_get_pid (firmware) == 0xffff) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"firmware product ID not specified");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* check vendor matches */
|
|
if (dfu_firmware_get_vid (firmware) != 0xffff &&
|
|
priv->runtime_pid != 0xffff &&
|
|
dfu_firmware_get_vid (firmware) != priv->runtime_vid) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"vendor ID incorrect, expected 0x%04x got 0x%04x\n",
|
|
dfu_firmware_get_vid (firmware),
|
|
priv->runtime_vid);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check product matches */
|
|
if (dfu_firmware_get_pid (firmware) != 0xffff &&
|
|
priv->runtime_pid != 0xffff &&
|
|
dfu_firmware_get_pid (firmware) != priv->runtime_pid) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"product ID incorrect, expected 0x%04x got 0x%04x",
|
|
dfu_firmware_get_pid (firmware),
|
|
priv->runtime_pid);
|
|
return FALSE;
|
|
}
|
|
|
|
/* APP -> DFU */
|
|
if (priv->mode == DFU_MODE_RUNTIME) {
|
|
if ((flags & DFU_TARGET_TRANSFER_FLAG_DETACH) == 0) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"device is not in DFU mode");
|
|
return FALSE;
|
|
}
|
|
|
|
/* inform UI there's going to be a detach:attach */
|
|
if (progress_cb != NULL) {
|
|
progress_cb (DFU_STATE_APP_DETACH, 0, 0,
|
|
progress_cb_data);
|
|
}
|
|
|
|
/* detach and USB reset */
|
|
g_debug ("detaching");
|
|
if (!dfu_device_detach (device, NULL, error))
|
|
return FALSE;
|
|
if (!dfu_device_wait_for_replug (device, 5000, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* download each target */
|
|
images = dfu_firmware_get_images (firmware);
|
|
if (images->len == 0) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INVALID_FILE,
|
|
"no images in firmware file");
|
|
return FALSE;
|
|
}
|
|
for (i = 0; i < images->len; i++) {
|
|
DfuImage *image;
|
|
DfuTargetTransferFlags flags_local = DFU_TARGET_TRANSFER_FLAG_NONE;
|
|
g_autoptr(DfuTarget) target_tmp = NULL;
|
|
image = g_ptr_array_index (images, i);
|
|
target_tmp = dfu_device_get_target_by_alt_setting (device,
|
|
dfu_image_get_alt_setting (image),
|
|
error);
|
|
if (target_tmp == NULL)
|
|
return FALSE;
|
|
if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY)
|
|
flags_local = DFU_TARGET_TRANSFER_FLAG_VERIFY;
|
|
if (!dfu_target_download (target_tmp,
|
|
image,
|
|
flags_local,
|
|
cancellable,
|
|
progress_cb,
|
|
progress_cb_data,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* do a host reset */
|
|
if ((flags & DFU_TARGET_TRANSFER_FLAG_HOST_RESET) > 0 ||
|
|
(flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) > 0) {
|
|
if (!dfu_device_reset (device, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* boot to runtime */
|
|
if (flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) {
|
|
g_debug ("booting to runtime to set auto-boot");
|
|
|
|
/* inform UI there's going to be a detach:attach */
|
|
if (progress_cb != NULL) {
|
|
progress_cb (DFU_STATE_APP_DETACH, 0, 0,
|
|
progress_cb_data);
|
|
}
|
|
|
|
/* DFU -> APP */
|
|
if (!dfu_device_wait_for_replug (device, 2000, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_device_error_fixup:
|
|
**/
|
|
void
|
|
dfu_device_error_fixup (DfuDevice *device,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
DfuDevicePrivate *priv = GET_PRIVATE (device);
|
|
|
|
/* 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 (!dfu_device_refresh (device, cancellable, NULL))
|
|
return;
|
|
|
|
/* not in an error state */
|
|
if (priv->state != DFU_STATE_DFU_ERROR)
|
|
return;
|
|
|
|
/* prefix the error */
|
|
switch (priv->status) {
|
|
case DFU_STATUS_OK:
|
|
/* ignore */
|
|
break;
|
|
case DFU_STATUS_ERR_VENDOR:
|
|
g_prefix_error (error, "read protection is active: ");
|
|
break;
|
|
default:
|
|
g_prefix_error (error, "[%s,%s]: ",
|
|
dfu_state_to_string (priv->state),
|
|
dfu_status_to_string (priv->status));
|
|
break;
|
|
}
|
|
}
|