mirror of
https://git.proxmox.com/git/fwupd
synced 2025-07-05 07:43:20 +00:00
1270 lines
32 KiB
C
1270 lines
32 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-target
|
|
* @short_description: Object representing a DFU-capable target
|
|
*
|
|
* This object allows uploading and downloading an image onto a
|
|
* specific DFU-capable target.
|
|
*
|
|
* You only need to use this in preference to #DfuDevice if you only
|
|
* want to update one target on the device. Most users will want to
|
|
* update all the targets on the device at the same time.
|
|
*
|
|
* See also: #DfuDevice, #DfuImage
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "dfu-common.h"
|
|
#include "dfu-device-private.h"
|
|
#include "dfu-error.h"
|
|
#include "dfu-target-private.h"
|
|
|
|
static void dfu_target_finalize (GObject *object);
|
|
|
|
typedef enum {
|
|
DFU_ATTRIBUTE_NONE = 0,
|
|
DFU_ATTRIBUTE_CAN_DOWNLOAD = (1 << 0),
|
|
DFU_ATTRIBUTE_CAN_UPLOAD = (1 << 1),
|
|
DFU_ATTRIBUTE_MANIFEST_TOL = (1 << 2),
|
|
DFU_ATTRIBUTE_WILL_DETACH = (1 << 3),
|
|
DFU_ATTRIBUTE_LAST
|
|
} DfuAttributes;
|
|
|
|
typedef enum {
|
|
DFU_QUIRK_NONE = 0,
|
|
DFU_QUIRK_IGNORE_POLLTIMEOUT = (1 << 0),
|
|
DFU_QUIRK_FORCE_DFU_MODE = (1 << 1),
|
|
DFU_QUIRK_IGNORE_INVALID_VERSION = (1 << 2),
|
|
DFU_QUIRK_USE_PROTOCOL_ZERO = (1 << 3),
|
|
DFU_QUIRK_LAST
|
|
} DfuQuirks;
|
|
|
|
/**
|
|
* DfuTargetPrivate:
|
|
*
|
|
* Private #DfuTarget data
|
|
**/
|
|
typedef struct {
|
|
DfuMode mode;
|
|
DfuState state;
|
|
DfuStatus status;
|
|
DfuDevice *device;
|
|
gboolean interface_claimed;
|
|
guint16 transfer_size;
|
|
guint8 iface_number;
|
|
guint8 iface_alt_setting;
|
|
guint8 iface_alt_setting_idx;
|
|
gchar *iface_alt_setting_name;
|
|
guint dnload_timeout;
|
|
guint timeout_ms;
|
|
DfuAttributes attributes;
|
|
DfuQuirks quirks;
|
|
} DfuTargetPrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (DfuTarget, dfu_target, G_TYPE_OBJECT)
|
|
#define GET_PRIVATE(o) (dfu_target_get_instance_private (o))
|
|
|
|
/**
|
|
* dfu_target_class_init:
|
|
**/
|
|
static void
|
|
dfu_target_class_init (DfuTargetClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = dfu_target_finalize;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_init:
|
|
**/
|
|
static void
|
|
dfu_target_init (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
priv->state = DFU_STATE_APP_IDLE;
|
|
priv->status = DFU_STATUS_OK;
|
|
priv->timeout_ms = 500;
|
|
priv->transfer_size = 64;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_finalize:
|
|
**/
|
|
static void
|
|
dfu_target_finalize (GObject *object)
|
|
{
|
|
DfuTarget *target = DFU_TARGET (object);
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
|
|
g_free (priv->iface_alt_setting_name);
|
|
if (priv->device != NULL)
|
|
g_object_unref (priv->device);
|
|
|
|
G_OBJECT_CLASS (dfu_target_parent_class)->finalize (object);
|
|
}
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 bLength;
|
|
guint8 bDescriptorType;
|
|
guint8 bmAttributes;
|
|
guint16 wDetachTimeOut;
|
|
guint16 wTransferSize;
|
|
guint16 bcdDFUVersion;
|
|
} DfuFuncDescriptor;
|
|
|
|
/**
|
|
* dfu_target_get_quirks:
|
|
**/
|
|
static DfuQuirks
|
|
dfu_target_get_quirks (GUsbDevice *dev)
|
|
{
|
|
DfuQuirks quirks = DFU_QUIRK_NONE;
|
|
guint16 vid, pid, release;
|
|
|
|
vid = g_usb_device_get_vid (dev);
|
|
pid = g_usb_device_get_pid (dev);
|
|
release = g_usb_device_get_release (dev);
|
|
|
|
/* Openmoko Freerunner / GTA02 */
|
|
if ((vid == 0x1d50 || vid == 0x1457) &&
|
|
pid >= 0x5117 && pid <= 0x5126)
|
|
quirks |= DFU_QUIRK_IGNORE_POLLTIMEOUT;
|
|
|
|
/* OpenPCD Reader */
|
|
if (vid == 0x16c0 && pid == 0x076b)
|
|
quirks |= DFU_QUIRK_IGNORE_POLLTIMEOUT;
|
|
|
|
/* Siemens AG, PXM 40 & PXM 50 */
|
|
if (vid == 0x0908 && (pid == 0x02c4 || pid == 0x02c5) && release == 0x0)
|
|
quirks |= DFU_QUIRK_IGNORE_POLLTIMEOUT;
|
|
|
|
/* Midiman M-Audio Transit */
|
|
if (vid == 0x0763 && pid == 0x2806)
|
|
quirks |= DFU_QUIRK_IGNORE_POLLTIMEOUT;
|
|
|
|
/* the LPC DFU bootloader uses the wrong mode */
|
|
if (vid == 0x1fc9 && pid == 0x000c)
|
|
quirks |= DFU_QUIRK_FORCE_DFU_MODE;
|
|
|
|
/* the Leaflabs Maple3 is known broken */
|
|
if (vid == 0x1eaf && pid == 0x0003 && release == 0x0200)
|
|
quirks |= DFU_QUIRK_IGNORE_INVALID_VERSION;
|
|
|
|
/* the DSO Nano has uses 0 instead of 2 when in DFU mode */
|
|
// quirks |= DFU_QUIRK_USE_PROTOCOL_ZERO;
|
|
|
|
return quirks;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_update_from_iface:
|
|
**/
|
|
static gboolean
|
|
dfu_target_update_from_iface (DfuTarget *target, GUsbInterface *iface)
|
|
{
|
|
DfuMode mode = DFU_MODE_UNKNOWN;
|
|
DfuQuirks quirks;
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
GBytes *iface_data = NULL;
|
|
GUsbDevice *dev;
|
|
const DfuFuncDescriptor *desc;
|
|
gsize iface_data_length;
|
|
|
|
/* runtime */
|
|
if (g_usb_interface_get_protocol (iface) == 0x01)
|
|
mode = DFU_MODE_RUNTIME;
|
|
|
|
/* DFU */
|
|
if (g_usb_interface_get_protocol (iface) == 0x02)
|
|
mode = DFU_MODE_DFU;
|
|
|
|
/* the DSO Nano has uses 0 instead of 2 when in DFU mode */
|
|
dev = _dfu_device_get_usb_dev (priv->device);
|
|
quirks = dfu_target_get_quirks (dev);
|
|
if ((quirks & DFU_QUIRK_USE_PROTOCOL_ZERO) &&
|
|
g_usb_interface_get_protocol (iface) == 0x00)
|
|
mode = DFU_MODE_DFU;
|
|
|
|
/* nothing found */
|
|
if (mode == DFU_MODE_UNKNOWN)
|
|
return FALSE;
|
|
|
|
/* in DFU mode, the interface is supposed to be 0 */
|
|
if (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 (quirks & DFU_QUIRK_FORCE_DFU_MODE)
|
|
mode = DFU_MODE_DFU;
|
|
|
|
/* save for reset */
|
|
if (mode == DFU_MODE_RUNTIME) {
|
|
_dfu_device_set_runtime_vid (priv->device, g_usb_device_get_vid (dev));
|
|
_dfu_device_set_runtime_pid (priv->device, g_usb_device_get_pid (dev));
|
|
}
|
|
|
|
/* update */
|
|
priv->iface_number = g_usb_interface_get_number (iface);
|
|
priv->iface_alt_setting = g_usb_interface_get_alternate (iface);
|
|
priv->iface_alt_setting_idx = g_usb_interface_get_index (iface);
|
|
priv->quirks = quirks;
|
|
priv->mode = mode;
|
|
|
|
/* parse the functional descriptor */
|
|
iface_data = g_usb_interface_get_extra (iface);
|
|
desc = g_bytes_get_data (iface_data, &iface_data_length);
|
|
if (iface_data_length != 0x09) {
|
|
g_warning ("interface found, but no interface data");
|
|
return FALSE;
|
|
}
|
|
|
|
/* 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 (quirks & DFU_QUIRK_IGNORE_INVALID_VERSION) {
|
|
g_debug ("ignoring quirked DFU version");
|
|
} else {
|
|
if (desc->bcdDFUVersion != 0x0100 &&
|
|
desc->bcdDFUVersion != 0x0101) {
|
|
g_warning ("DFU version is invalid: 0x%04x",
|
|
desc->bcdDFUVersion);
|
|
}
|
|
}
|
|
|
|
/* get attributes about the DFU operation */
|
|
priv->attributes = desc->bmAttributes;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* _dfu_target_new:
|
|
* @device: a #DfuDevice
|
|
* @iface: a #GUsbInterface
|
|
*
|
|
* Creates a new DFU target, which represents an alt-setting on a
|
|
* DFU-capable device.
|
|
*
|
|
* Return value: a #DfuTarget, or %NULL if @iface was not DFU-capable
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuTarget *
|
|
_dfu_target_new (DfuDevice *device, GUsbInterface *iface)
|
|
{
|
|
DfuTargetPrivate *priv;
|
|
DfuTarget *target;
|
|
target = g_object_new (DFU_TYPE_TARGET, NULL);
|
|
priv = GET_PRIVATE (target);
|
|
priv->device = g_object_ref (device);
|
|
if (!dfu_target_update_from_iface (target, iface)) {
|
|
g_object_unref (target);
|
|
return NULL;
|
|
}
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_get_mode:
|
|
* @target: a #GUsbDevice
|
|
*
|
|
* Gets the target mode.
|
|
*
|
|
* Return value: enumerated mode, e.g. %DFU_MODE_RUNTIME
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuMode
|
|
dfu_target_get_mode (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), 0);
|
|
return priv->mode;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_get_state:
|
|
* @target: a #GUsbDevice
|
|
*
|
|
* Gets the target state.
|
|
*
|
|
* Return value: enumerated state, e.g. %DFU_STATE_DFU_UPLOAD_IDLE
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuState
|
|
dfu_target_get_state (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), 0);
|
|
return priv->state;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_get_status:
|
|
* @target: a #GUsbDevice
|
|
*
|
|
* Gets the target status.
|
|
*
|
|
* Return value: enumerated status, e.g. %DFU_STATUS_ERR_ADDRESS
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuStatus
|
|
dfu_target_get_status (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), 0);
|
|
return priv->status;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_can_upload:
|
|
* @target: a #GUsbDevice
|
|
*
|
|
* Gets if the target can upload.
|
|
*
|
|
* Return value: %TRUE if the target can upload from target to host
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_target_can_upload (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), FALSE);
|
|
return (priv->attributes & DFU_ATTRIBUTE_CAN_UPLOAD) > 0;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_can_download:
|
|
* @target: a #GUsbDevice
|
|
*
|
|
* Gets if the target can download.
|
|
*
|
|
* Return value: %TRUE if the target can download from host to target
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_target_can_download (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), FALSE);
|
|
return (priv->attributes & DFU_ATTRIBUTE_CAN_DOWNLOAD) > 0;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_get_transfer_size:
|
|
* @target: a #GUsbDevice
|
|
*
|
|
* Gets the transfer size in bytes.
|
|
*
|
|
* Return value: packet size, or 0 for unknown
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint16
|
|
dfu_target_get_transfer_size (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), 0xffff);
|
|
return priv->transfer_size;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_set_transfer_size:
|
|
* @target: a #GUsbDevice
|
|
* @transfer_size: maximum packet size
|
|
*
|
|
* Sets the transfer size in bytes.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_target_set_transfer_size (DfuTarget *target, guint16 transfer_size)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_if_fail (DFU_IS_TARGET (target));
|
|
priv->transfer_size = transfer_size;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_open:
|
|
* @target: a #DfuTarget
|
|
* @flags: #DfuTargetOpenFlags, e.g. %DFU_TARGET_OPEN_FLAG_NONE
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Opens a DFU-capable target.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_target_open (DfuTarget *target, DfuTargetOpenFlags flags,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
GUsbDevice *dev;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* already done */
|
|
if (priv->interface_claimed)
|
|
return TRUE;
|
|
|
|
/* ensure parent device is open */
|
|
if (!dfu_device_open (priv->device, error))
|
|
return FALSE;
|
|
|
|
/* claim the correct interface */
|
|
dev = _dfu_device_get_usb_dev (priv->device);
|
|
if (!g_usb_device_claim_interface (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;
|
|
}
|
|
priv->interface_claimed = TRUE;
|
|
|
|
/* use the correct setting */
|
|
if (priv->mode == DFU_MODE_DFU) {
|
|
if (!g_usb_device_set_interface_alt (dev,
|
|
(gint) priv->iface_number,
|
|
(gint) priv->iface_alt_setting,
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot set alternate setting 0x%02x on interface %i: %s",
|
|
priv->iface_alt_setting,
|
|
priv->iface_number,
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* get string */
|
|
if (priv->iface_alt_setting_idx != 0x00) {
|
|
priv->iface_alt_setting_name =
|
|
g_usb_device_get_string_descriptor (dev,
|
|
priv->iface_alt_setting_idx,
|
|
NULL);
|
|
}
|
|
|
|
/* automatically abort any uploads or downloads */
|
|
if ((flags & DFU_TARGET_OPEN_FLAG_NO_AUTO_REFRESH) == 0) {
|
|
if (!dfu_target_refresh (target, 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_target_abort (target, cancellable, error))
|
|
return FALSE;
|
|
break;
|
|
case DFU_STATE_DFU_ERROR:
|
|
g_debug ("clearing error %s", dfu_status_to_string (priv->status));
|
|
if (!dfu_target_clear_status (target, cancellable, error))
|
|
return FALSE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_close:
|
|
* @target: a #DfuTarget
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Closes a DFU-capable target.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_target_close (DfuTarget *target, GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
GUsbDevice *dev;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* this is our intention; the release might fail if the USB device
|
|
* has been disconnected already */
|
|
priv->interface_claimed = FALSE;
|
|
|
|
/* only release if claimed */
|
|
if (priv->interface_claimed) {
|
|
dev = _dfu_device_get_usb_dev (priv->device);
|
|
if (!g_usb_device_release_interface (dev,
|
|
(gint) priv->iface_number,
|
|
0,
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"cannot release interface %i: %s",
|
|
priv->iface_number, error_local->message);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_refresh:
|
|
* @target: a #DfuTarget
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Refreshes the cached properties on the DFU target.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_target_refresh (DfuTarget *target, GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
gsize actual_length = 0;
|
|
guint8 buf[6];
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (_dfu_device_get_usb_dev (priv->device),
|
|
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 target state: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
if (actual_length != 6) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"cannot get target status, invalid size: %04x",
|
|
(guint) actual_length);
|
|
}
|
|
priv->status = buf[0];
|
|
if (priv->quirks & DFU_QUIRK_IGNORE_POLLTIMEOUT) {
|
|
priv->dnload_timeout = 5;
|
|
} else {
|
|
priv->dnload_timeout = buf[1] +
|
|
(((guint32) buf[2]) << 8) +
|
|
(((guint32) buf[3]) << 16);
|
|
}
|
|
priv->state = buf[4];
|
|
g_debug ("refreshed status=%s and state=%s",
|
|
dfu_status_to_string (priv->status),
|
|
dfu_state_to_string (priv->state));
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_detach:
|
|
* @target: a #DfuTarget
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Detaches the target putting it into DFU-mode.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_target_detach (DfuTarget *target, GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (_dfu_device_get_usb_dev (priv->device),
|
|
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)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot detach target: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* do a host reset */
|
|
if ((priv->attributes & DFU_ATTRIBUTE_WILL_DETACH) == 0) {
|
|
g_debug ("doing target reset as host will not self-reset");
|
|
if (!dfu_device_reset (priv->device, error))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_abort:
|
|
* @target: a #DfuTarget
|
|
* @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_target_abort (DfuTarget *target, GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (_dfu_device_get_usb_dev (priv->device),
|
|
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)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot abort target: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* _dfu_target_update:
|
|
* @target: a #DfuTarget
|
|
* @iface: a #GUsbInterface
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Updates the target with new interface data. This only needs to be
|
|
* done after the device has been reset.
|
|
*
|
|
* Returns: %TRUE for success
|
|
**/
|
|
gboolean
|
|
_dfu_target_update (DfuTarget *target, GUsbInterface *iface,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
gboolean reclaim_interface = FALSE;
|
|
|
|
/* close */
|
|
if (priv->interface_claimed) {
|
|
if (!dfu_target_close (target, error))
|
|
return FALSE;
|
|
reclaim_interface = TRUE;
|
|
}
|
|
|
|
/* check this is _still_ a DFU-capable target */
|
|
if (!dfu_target_update_from_iface (target, iface)) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"replugged target is not DFU-capable");
|
|
return FALSE;
|
|
}
|
|
|
|
/* reclaim */
|
|
if (reclaim_interface) {
|
|
if (!dfu_device_open (priv->device, error))
|
|
return FALSE;
|
|
if (!dfu_target_open (target, DFU_TARGET_OPEN_FLAG_NONE,
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_clear_status:
|
|
* @target: a #DfuTarget
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Clears any error status on the DFU target.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_target_clear_status (DfuTarget *target, GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (_dfu_device_get_usb_dev (priv->device),
|
|
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)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot clear status on the target: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_upload_chunk:
|
|
**/
|
|
static GBytes *
|
|
dfu_target_upload_chunk (DfuTarget *target, guint8 index,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_autoptr(GError) error_local = NULL;
|
|
guint8 *buf;
|
|
gsize actual_length;
|
|
|
|
buf = g_new0 (guint8, priv->transfer_size);
|
|
if (!g_usb_device_control_transfer (_dfu_device_get_usb_dev (priv->device),
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
DFU_REQUEST_UPLOAD,
|
|
index,
|
|
priv->iface_number,
|
|
buf, (gsize) priv->transfer_size,
|
|
&actual_length,
|
|
priv->timeout_ms,
|
|
cancellable,
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot clear status on the target: %s",
|
|
error_local->message);
|
|
return NULL;
|
|
}
|
|
return g_bytes_new_take (buf, actual_length);
|
|
}
|
|
|
|
/**
|
|
* dfu_target_upload:
|
|
* @target: a #DfuTarget
|
|
* @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY
|
|
* @expected_size: the expected size of the firmware, or 0 for unknown
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @progress_cb: 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 image, or %NULL for error
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuImage *
|
|
dfu_target_upload (DfuTarget *target,
|
|
gsize expected_size,
|
|
DfuTargetTransferFlags flags,
|
|
GCancellable *cancellable,
|
|
DfuProgressCallback progress_cb,
|
|
gpointer progress_cb_data,
|
|
GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
DfuImage *image = NULL;
|
|
GBytes *chunk_tmp;
|
|
gsize chunk_size;
|
|
gsize offset = 0;
|
|
gsize total_size = 0;
|
|
guint8 *buffer;
|
|
guint i;
|
|
g_autoptr(DfuElement) element = NULL;
|
|
g_autoptr(GBytes) contents = NULL;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
/* can the target do this? */
|
|
if (!dfu_target_can_upload (target)) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"target cannot do uploading");
|
|
return NULL;
|
|
}
|
|
|
|
/* get all the chunks from the hardware */
|
|
chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
|
|
for (i = 0; i < 0xffff; i++) {
|
|
chunk_tmp = dfu_target_upload_chunk (target, i, cancellable, error);
|
|
if (chunk_tmp == NULL)
|
|
return NULL;
|
|
|
|
/* keep a sum of all the chunks */
|
|
chunk_size = g_bytes_get_size (chunk_tmp);
|
|
total_size += chunk_size;
|
|
|
|
/* add to array */
|
|
g_debug ("got #%04x chunk of size %li", i, chunk_size);
|
|
g_ptr_array_add (chunks, chunk_tmp);
|
|
|
|
/* update UI */
|
|
if (progress_cb != NULL && chunk_size > 0) {
|
|
progress_cb (DFU_STATE_DFU_UPLOAD_IDLE,
|
|
total_size,
|
|
expected_size,
|
|
progress_cb_data);
|
|
}
|
|
|
|
/* detect short write as EOF */
|
|
if (chunk_size < priv->transfer_size)
|
|
break;
|
|
}
|
|
|
|
/* check final size */
|
|
if (expected_size > 0) {
|
|
if (total_size != expected_size) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INVALID_FILE,
|
|
"invalid size, got %li, expected %li",
|
|
total_size, expected_size);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* do host reset */
|
|
if ((flags & DFU_TARGET_TRANSFER_FLAG_HOST_RESET) > 0 ||
|
|
(flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) > 0) {
|
|
if (!dfu_device_reset (priv->device, error))
|
|
return NULL;
|
|
}
|
|
|
|
/* boot to runtime */
|
|
if (flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) {
|
|
g_debug ("booting to runtime");
|
|
if (!dfu_device_wait_for_replug (priv->device, 2000, cancellable, error))
|
|
return NULL;
|
|
}
|
|
|
|
/* stitch them all together */
|
|
buffer = g_malloc0 (total_size);
|
|
for (i = 0; i < chunks->len; i++) {
|
|
const guint8 *chunk_data;
|
|
chunk_tmp = g_ptr_array_index (chunks, i);
|
|
chunk_data = g_bytes_get_data (chunk_tmp, &chunk_size);
|
|
memcpy (buffer + offset, chunk_data, chunk_size);
|
|
offset += chunk_size;
|
|
}
|
|
|
|
/* create new image */
|
|
contents = g_bytes_new_take (buffer, total_size);
|
|
image = dfu_image_new ();
|
|
dfu_image_set_name (image, priv->iface_alt_setting_name);
|
|
dfu_image_set_alt_setting (image, priv->iface_alt_setting);
|
|
element = dfu_element_new ();
|
|
dfu_element_set_contents (element, contents);
|
|
dfu_image_add_element (image, element);
|
|
return image;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_download_chunk:
|
|
**/
|
|
static gboolean
|
|
dfu_target_download_chunk (DfuTarget *target, guint8 index, GBytes *bytes,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_autoptr(GError) error_local = NULL;
|
|
gsize actual_length;
|
|
|
|
if (!g_usb_device_control_transfer (_dfu_device_get_usb_dev (priv->device),
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
DFU_REQUEST_DNLOAD,
|
|
index,
|
|
priv->iface_number,
|
|
(guint8 *) g_bytes_get_data (bytes, NULL),
|
|
g_bytes_get_size (bytes),
|
|
&actual_length,
|
|
priv->timeout_ms,
|
|
cancellable,
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"cannot download data to the target (state: %s): %s",
|
|
dfu_state_to_string (priv->state),
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
g_assert (actual_length == g_bytes_get_size (bytes));
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* _g_bytes_compare_verbose:
|
|
**/
|
|
static gchar *
|
|
_g_bytes_compare_verbose (GBytes *bytes1, GBytes *bytes2)
|
|
{
|
|
const guint8 *data1;
|
|
const guint8 *data2;
|
|
gsize length1;
|
|
gsize length2;
|
|
guint i;
|
|
|
|
data1 = g_bytes_get_data (bytes1, &length1);
|
|
data2 = g_bytes_get_data (bytes2, &length2);
|
|
|
|
/* not the same length */
|
|
if (length1 != length2) {
|
|
return g_strdup_printf ("got %li bytes, expected %li",
|
|
length1, length2);
|
|
}
|
|
|
|
/* return 00 01 02 03 */
|
|
for (i = 0; i < length1; i++) {
|
|
if (data1[i] != data2[i]) {
|
|
return g_strdup_printf ("got 0x%02x, expected 0x%02x @ 0x%04x",
|
|
data1[i], data2[i], i);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_download_bytes:
|
|
**/
|
|
static gboolean
|
|
dfu_target_download_bytes (DfuTarget *target, GBytes *bytes,
|
|
DfuTargetTransferFlags flags,
|
|
GCancellable *cancellable,
|
|
DfuProgressCallback progress_cb,
|
|
gpointer progress_cb_data,
|
|
GError **error)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
DfuElement *element;
|
|
guint i;
|
|
guint nr_chunks;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* can the target do this? */
|
|
if (!dfu_target_can_download (target)) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_NOT_SUPPORTED,
|
|
"target cannot do downloading");
|
|
return FALSE;
|
|
}
|
|
|
|
/* round up as we have to transfer incomplete blocks */
|
|
nr_chunks = ceil ((gdouble) g_bytes_get_size (bytes) /
|
|
(gdouble) priv->transfer_size);
|
|
if (nr_chunks == 0) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INVALID_FILE,
|
|
"zero-length firmware");
|
|
return FALSE;
|
|
}
|
|
for (i = 0; i < nr_chunks + 1; i++) {
|
|
gsize length;
|
|
gsize offset;
|
|
g_autoptr(GBytes) bytes_tmp = NULL;
|
|
|
|
/* we have to write one final zero-sized chunk for EOF */
|
|
offset = i * priv->transfer_size;
|
|
if (i < nr_chunks) {
|
|
length = g_bytes_get_size (bytes) - offset;
|
|
if (length > priv->transfer_size)
|
|
length = priv->transfer_size;
|
|
bytes_tmp = g_bytes_new_from_bytes (bytes, offset, length);
|
|
} else {
|
|
bytes_tmp = g_bytes_new (NULL, 0);
|
|
}
|
|
g_debug ("writing #%04x chunk of size %li",
|
|
i, g_bytes_get_size (bytes_tmp));
|
|
if (!dfu_target_download_chunk (target, i, bytes_tmp, cancellable, error)) {
|
|
if (dfu_target_refresh (target, cancellable, NULL)) {
|
|
g_prefix_error (error, "Device status %s: ",
|
|
dfu_status_to_string (priv->status));
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* update UI */
|
|
if (progress_cb != NULL) {
|
|
progress_cb (DFU_STATE_DFU_DNLOAD_IDLE,
|
|
offset,
|
|
g_bytes_get_size (bytes),
|
|
progress_cb_data);
|
|
}
|
|
|
|
/* give the target a chance to update */
|
|
g_usleep (priv->dnload_timeout * 1000);
|
|
|
|
/* getting the status moves the state machine to DNLOAD-IDLE */
|
|
if (!dfu_target_refresh (target, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify */
|
|
if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY) {
|
|
GBytes *bytes_tmp;
|
|
g_autoptr(DfuImage) image_tmp = NULL;
|
|
image_tmp = dfu_target_upload (target,
|
|
g_bytes_get_size (bytes),
|
|
DFU_TARGET_TRANSFER_FLAG_NONE,
|
|
cancellable,
|
|
progress_cb,
|
|
progress_cb_data,
|
|
error);
|
|
if (image_tmp == NULL)
|
|
return FALSE;
|
|
element = dfu_image_get_element (image_tmp, 0);
|
|
bytes_tmp = dfu_element_get_contents (element);
|
|
if (g_bytes_compare (bytes_tmp, bytes) != 0) {
|
|
g_autofree gchar *bytes_cmp_str = NULL;
|
|
bytes_cmp_str = _g_bytes_compare_verbose (bytes_tmp, bytes);
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_VERIFY_FAILED,
|
|
"verify failed: %s",
|
|
bytes_cmp_str);
|
|
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 (priv->device, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* boot to runtime */
|
|
if (flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) {
|
|
g_debug ("booting to runtime to set auto-boot");
|
|
if (!dfu_device_wait_for_replug (priv->device, 2000, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_download:
|
|
* @target: a #DfuTarget
|
|
* @image: a #DfuImage
|
|
* @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @progress_cb: 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_target_download (DfuTarget *target, DfuImage *image,
|
|
DfuTargetTransferFlags flags,
|
|
GCancellable *cancellable,
|
|
DfuProgressCallback progress_cb,
|
|
gpointer progress_cb_data,
|
|
GError **error)
|
|
{
|
|
GBytes *contents;
|
|
DfuElement *element;
|
|
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), FALSE);
|
|
g_return_val_if_fail (DFU_IS_IMAGE (image), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* get data */
|
|
element = dfu_image_get_element (image, 0);
|
|
if (element == NULL) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INVALID_FILE,
|
|
"no image elements");
|
|
return FALSE;
|
|
}
|
|
contents = dfu_element_get_contents (element);
|
|
return dfu_target_download_bytes (target, contents, flags, cancellable,
|
|
progress_cb, progress_cb_data, error);
|
|
}
|
|
|
|
/**
|
|
* dfu_target_set_timeout:
|
|
* @target: a #DfuTarget
|
|
* @timeout_ms: the timeout in ms
|
|
*
|
|
* Sets the USB timeout to use when contacting the USB target.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_target_set_timeout (DfuTarget *target, guint timeout_ms)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_if_fail (DFU_IS_TARGET (target));
|
|
priv->timeout_ms = timeout_ms;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_get_interface_number:
|
|
* @target: a #DfuTarget
|
|
*
|
|
* Gets the interface number.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint8
|
|
dfu_target_get_interface_number (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), 0xff);
|
|
return priv->iface_number;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_get_interface_alt_setting:
|
|
* @target: a #DfuTarget
|
|
*
|
|
* Gets the alternate setting to use for this interface.
|
|
*
|
|
* Return value: the alternative setting, typically zero
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint8
|
|
dfu_target_get_interface_alt_setting (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), 0xff);
|
|
return priv->iface_alt_setting;
|
|
}
|
|
|
|
/**
|
|
* dfu_target_get_interface_alt_name:
|
|
* @target: a #DfuTarget
|
|
*
|
|
* Gets the alternate setting name to use for this interface.
|
|
*
|
|
* Return value: the alternative setting name, typically %NULL
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
const gchar *
|
|
dfu_target_get_interface_alt_name (DfuTarget *target)
|
|
{
|
|
DfuTargetPrivate *priv = GET_PRIVATE (target);
|
|
g_return_val_if_fail (DFU_IS_TARGET (target), NULL);
|
|
return priv->iface_alt_setting_name;
|
|
}
|