fwupd/libdfu/dfu-target.c
Richard Hughes a5c48ce07f libdfu: Fix verification of written DfuSe firmware
DfuSe doesn't stop returning data when the image is empty. Take as least as
much data as we require and then truncate the result to what we want.
2016-10-14 18:07:34 +01:00

1831 lines
47 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-sector-private.h"
#include "dfu-target-private.h"
static void dfu_target_finalize (GObject *object);
typedef enum {
DFU_CMD_DFUSE_GET_COMMAND = 0x00,
DFU_CMD_DFUSE_SET_ADDRESS_POINTER = 0x21,
DFU_CMD_DFUSE_ERASE = 0x41,
DFU_CMD_DFUSE_READ_UNPROTECT = 0x92,
DFU_CMD_DFUSE_LAST
} DfuCmdDfuse;
typedef struct {
DfuDevice *device; /* not refcounted */
DfuCipherKind cipher_kind;
gboolean done_setup;
guint8 alt_setting;
guint8 alt_idx;
gchar *alt_name;
gchar *alt_name_for_display;
GPtrArray *sectors; /* of DfuSector */
GHashTable *sectors_erased; /* of DfuSector:1 */
guint old_percentage;
} DfuTargetPrivate;
enum {
SIGNAL_PERCENTAGE_CHANGED,
SIGNAL_ACTION_CHANGED,
SIGNAL_LAST
};
static guint signals [SIGNAL_LAST] = { 0 };
G_DEFINE_TYPE_WITH_PRIVATE (DfuTarget, dfu_target, G_TYPE_OBJECT)
#define GET_PRIVATE(o) (dfu_target_get_instance_private (o))
static void
dfu_target_class_init (DfuTargetClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
/**
* DfuTarget::percentage-changed:
* @device: the #DfuTarget instance that emitted the signal
* @percentage: the new percentage
*
* The ::percentage-changed signal is emitted when the percentage changes.
*
* Since: 0.5.4
**/
signals [SIGNAL_PERCENTAGE_CHANGED] =
g_signal_new ("percentage-changed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (DfuTargetClass, percentage_changed),
NULL, NULL, g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
/**
* DfuTarget::action-changed:
* @device: the #DfuTarget instance that emitted the signal
* @action: the new DfuAction
*
* The ::action-changed signal is emitted when the high level action changes.
*
* Since: 0.7.5
**/
signals [SIGNAL_ACTION_CHANGED] =
g_signal_new ("action-changed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (DfuTargetClass, action_changed),
NULL, NULL, g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
object_class->finalize = dfu_target_finalize;
}
static void
dfu_target_init (DfuTarget *target)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
priv->sectors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
priv->sectors_erased = g_hash_table_new (g_direct_hash, g_direct_equal);
priv->old_percentage = G_MAXUINT;
}
static void
dfu_target_finalize (GObject *object)
{
DfuTarget *target = DFU_TARGET (object);
DfuTargetPrivate *priv = GET_PRIVATE (target);
g_free (priv->alt_name);
g_free (priv->alt_name_for_display);
g_ptr_array_unref (priv->sectors);
g_hash_table_unref (priv->sectors_erased);
/* we no longer care */
if (priv->device != NULL) {
g_object_remove_weak_pointer (G_OBJECT (priv->device),
(gpointer *) &priv->device);
}
G_OBJECT_CLASS (dfu_target_parent_class)->finalize (object);
}
static gchar *
dfu_target_sectors_to_string (DfuTarget *target)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
DfuSector *sector;
GString *str;
guint i;
str = g_string_new ("");
for (i = 0; i < priv->sectors->len; i++) {
g_autofree gchar *tmp = NULL;
sector = g_ptr_array_index (priv->sectors, i);
tmp = dfu_sector_to_string (sector);
g_string_append_printf (str, "%s\n", tmp);
}
if (str->len > 0)
g_string_truncate (str, str->len - 1);
return g_string_free (str, FALSE);
}
static DfuSector *
dfu_target_get_sector_for_addr (DfuTarget *target, guint32 addr)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
DfuSector *sector;
guint i;
for (i = 0; i < priv->sectors->len; i++) {
sector = g_ptr_array_index (priv->sectors, i);
if (addr < dfu_sector_get_address (sector))
continue;
if (addr > dfu_sector_get_address (sector) +
dfu_sector_get_size (sector))
continue;
return sector;
}
return NULL;
}
static gboolean
dfu_target_parse_sector (DfuTarget *target,
const gchar *dfuse_sector_id,
guint32 *addr,
guint16 zone,
guint16 number,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
DfuSectorCap cap = DFU_SECTOR_CAP_NONE;
gchar *tmp;
guint32 addr_offset = 0;
guint64 nr_sectors;
guint64 sector_size;
guint i;
/* parse # of sectors */
nr_sectors = g_ascii_strtoull (dfuse_sector_id, &tmp, 10);
if (nr_sectors > 999) {
g_set_error (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"Invalid number of sectors: %s",
dfuse_sector_id);
return FALSE;
}
/* check this is the delimiter */
if (tmp[0] != '*') {
g_set_error (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"Invalid sector ID: %s",
dfuse_sector_id);
return FALSE;
}
/* parse sector size */
sector_size = g_ascii_strtoull (tmp + 1, &tmp, 10);
if (sector_size > 999) {
g_set_error (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"Invalid sector size: %s",
dfuse_sector_id);
return FALSE;
}
/* get multiplier */
switch (tmp[0]) {
case 'B': /* byte */
case ' ': /* byte, ST reference bootloader :/ */
break;
case 'K': /* Kilo */
sector_size *= 0x400;
break;
case 'M': /* Mega */
sector_size *= 0x100000 ;
break;
default:
g_set_error (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"Invalid sector multiplier: %s",
tmp);
return FALSE;
}
/* get sector type */
switch (tmp[1]) {
case 'a':
cap = DFU_SECTOR_CAP_READABLE;
break;
case 'b':
cap = DFU_SECTOR_CAP_ERASEABLE;
break;
case 'c':
cap = DFU_SECTOR_CAP_READABLE |
DFU_SECTOR_CAP_ERASEABLE;
break;
case 'd':
cap = DFU_SECTOR_CAP_WRITEABLE;
break;
case 'e':
cap = DFU_SECTOR_CAP_READABLE |
DFU_SECTOR_CAP_WRITEABLE;
break;
case 'f':
cap = DFU_SECTOR_CAP_ERASEABLE |
DFU_SECTOR_CAP_WRITEABLE;
break;
case 'g':
cap = DFU_SECTOR_CAP_READABLE |
DFU_SECTOR_CAP_ERASEABLE |
DFU_SECTOR_CAP_WRITEABLE;
break;
default:
g_set_error (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"Invalid sector type: %s",
tmp);
return FALSE;
}
/* add all the sectors */
for (i = 0; i < nr_sectors; i++) {
DfuSector *sector;
sector = dfu_sector_new (*addr + addr_offset,
(guint32) sector_size,
(guint32) ((nr_sectors * sector_size) - addr_offset),
zone,
number,
cap);
g_ptr_array_add (priv->sectors, sector);
addr_offset += dfu_sector_get_size (sector);
}
/* update for next sector */
*addr += addr_offset;
return TRUE;
}
gboolean
dfu_target_parse_sectors (DfuTarget *target, const gchar *alt_name, GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
guint32 addr;
g_autofree gchar *str_debug = NULL;
g_auto(GStrv) zones = NULL;
/* not set */
if (alt_name == NULL)
return TRUE;
/* do we have any hint for the cipher */
if (g_strstr_len (alt_name, -1, "|XTEA") != NULL)
priv->cipher_kind = DFU_CIPHER_KIND_XTEA;
/* From the Neo Freerunner */
if (g_str_has_prefix (alt_name, "RAM 0x")) {
DfuSector *sector;
addr = g_ascii_strtoull (alt_name + 6, NULL, 16);
if (addr == 0 && addr > G_MAXUINT32)
return FALSE;
g_debug ("RAM description, so parsing");
sector = dfu_sector_new ((guint32) addr, /* addr */
0x0, /* size */
0x0, /* size_left */
0x0, /* zone */
0x0, /* number */
DFU_SECTOR_CAP_READABLE |
DFU_SECTOR_CAP_WRITEABLE);
g_ptr_array_add (priv->sectors, sector);
}
/* not a DfuSe alternative name */
if (alt_name[0] != '@')
return TRUE;
/* clear any existing zones */
g_ptr_array_set_size (priv->sectors, 0);
/* parse zones */
zones = g_strsplit (alt_name, "/", -1);
priv->alt_name_for_display = g_strdup (g_strchomp (zones[0] + 1));
for (guint i = 1; zones[i] != NULL; i += 2) {
guint64 addr_tmp;
g_auto(GStrv) sectors = NULL;
/* parse address */
if (!g_str_has_prefix (zones[i], "0x")) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"No sector address");
return FALSE;
}
addr_tmp = g_ascii_strtoull (zones[i] + 2, NULL, 16);
if (addr_tmp > G_MAXUINT32) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"Sector address too large");
return FALSE;
}
addr = (guint32) addr_tmp;
/* no sectors?! */
if (zones[i+1] == NULL) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"No sector section");
return FALSE;
}
/* parse sectors */
sectors = g_strsplit (zones[i+1], ",", -1);
for (guint16 j = 0; sectors[j] != NULL; j++) {
if (!dfu_target_parse_sector (target,
sectors[j],
&addr,
(i - 1) / 2, j,
error)) {
g_prefix_error (error,
"Failed to parse: '%s': ",
sectors[j]);
return FALSE;
}
}
}
/* success */
str_debug = dfu_target_sectors_to_string (target);
g_debug ("%s", str_debug);
return TRUE;
}
/**
* dfu_target_new: (skip)
* @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 = device;
priv->alt_idx = g_usb_interface_get_index (iface);
priv->alt_setting = g_usb_interface_get_alternate (iface);
/* if we try to ref the target and destroy the device */
g_object_add_weak_pointer (G_OBJECT (priv->device),
(gpointer *) &priv->device);
return target;
}
/**
* dfu_target_get_sectors:
* @target: a #GUsbDevice
*
* Gets the sectors exported by the device.
*
* Return value: (transfer none) (element-type DfuSector): sectors
*
* Since: 0.5.4
**/
GPtrArray *
dfu_target_get_sectors (DfuTarget *target)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
g_return_val_if_fail (DFU_IS_TARGET (target), NULL);
return priv->sectors;
}
/**
* dfu_target_status_to_error_msg:
* @status: a #DfuStatus, e.g. %DFU_STATUS_ERR_ERASE
*
* Converts an enumerated value to an error description.
*
* Return value: a string
*
* Since: 0.5.4
**/
static const gchar *
dfu_target_status_to_error_msg (DfuStatus status)
{
if (status == DFU_STATUS_OK)
return "No error condition is present";
if (status == DFU_STATUS_ERR_TARGET)
return "Firmware is not for designed this device";
if (status == DFU_STATUS_ERR_FILE)
return "Firmware is for this device but fails verification";
if (status == DFU_STATUS_ERR_WRITE)
return "Device is unable to write memory";
if (status == DFU_STATUS_ERR_ERASE)
return "Memory erase function failed";
if (status == DFU_STATUS_ERR_CHECK_ERASED)
return "Memory erase check failed";
if (status == DFU_STATUS_ERR_PROG)
return "Program memory function failed";
if (status == DFU_STATUS_ERR_VERIFY)
return "Programmed memory failed verification";
if (status == DFU_STATUS_ERR_ADDRESS)
return "Cannot program memory due to address out of range";
if (status == DFU_STATUS_ERR_NOTDONE)
return "Received zero-length download but data is incomplete";
if (status == DFU_STATUS_ERR_FIRMWARE)
return "Device firmware is corrupt";
if (status == DFU_STATUS_ERR_VENDOR)
return "Vendor-specific error";
if (status == DFU_STATUS_ERR_USBR)
return "Device detected unexpected USB reset signaling";
if (status == DFU_STATUS_ERR_POR)
return "Device detected unexpected power on reset";
if (status == DFU_STATUS_ERR_UNKNOWN)
return "Something unexpected went wrong";
if (status == DFU_STATUS_ERR_STALLDPKT)
return "Device stalled an unexpected request";
return NULL;
}
static gboolean
dfu_target_check_status (DfuTarget *target,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
DfuStatus status;
/* get the status */
if (!dfu_device_refresh (priv->device, cancellable, error))
return FALSE;
/* not in an error state */
if (dfu_device_get_state (priv->device) != DFU_STATE_DFU_ERROR)
return TRUE;
/* DfuSe-specific long errors */
status = dfu_device_get_status (priv->device);
if (dfu_device_has_dfuse_support (priv->device)) {
if (status == DFU_STATUS_ERR_VENDOR) {
g_set_error (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"Read protection is active");
return FALSE;
}
if (status == DFU_STATUS_ERR_TARGET) {
g_set_error (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"Address is wrong or unsupported");
return FALSE;
}
}
/* use a proper error description */
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
dfu_target_status_to_error_msg (status));
return FALSE;
}
/**
* dfu_target_use_alt_setting:
* @target: a #DfuTarget
* @error: a #GError, or %NULL
*
* Opens a DFU-capable target.
*
* Return value: %TRUE for success
*
* Since: 0.5.4
**/
static gboolean
dfu_target_use_alt_setting (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);
/* ensure interface is claimed */
if (!dfu_device_ensure_interface (priv->device, error))
return FALSE;
/* use the correct setting */
dev = dfu_device_get_usb_dev (priv->device);
if (dfu_device_get_mode (priv->device) == DFU_MODE_DFU) {
if (!g_usb_device_set_interface_alt (dev,
(gint) dfu_device_get_interface (priv->device),
(gint) priv->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->alt_setting,
dfu_device_get_interface (priv->device),
error_local->message);
return FALSE;
}
}
return TRUE;
}
/**
* dfu_target_setup:
* @target: a #DfuTarget
* @error: a #GError, or %NULL
*
* Opens a DFU-capable target.
*
* Return value: %TRUE for success
*
* Since: 0.5.4
**/
static gboolean
dfu_target_setup (DfuTarget *target, 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);
/* already done */
if (priv->done_setup)
return TRUE;
/* get string */
if (priv->alt_idx != 0x00) {
GUsbDevice *dev;
dev = dfu_device_get_usb_dev (priv->device);
priv->alt_name =
g_usb_device_get_string_descriptor (dev,
priv->alt_idx,
NULL);
}
/* parse the DfuSe format according to UM0424 */
if (!dfu_target_parse_sectors (target,
priv->alt_name,
error))
return FALSE;
/* add a dummy entry */
if (priv->sectors->len == 0) {
DfuSector *sector;
sector = dfu_sector_new (0x0, /* addr */
0x0, /* size */
0x0, /* size_left */
0x0, /* zone */
0x0, /* number */
DFU_SECTOR_CAP_READABLE |
DFU_SECTOR_CAP_WRITEABLE);
g_debug ("no UM0424 sector description in %s", priv->alt_name);
g_ptr_array_add (priv->sectors, sector);
}
priv->done_setup = TRUE;
return TRUE;
}
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,
dfu_device_get_interface (priv->device),
(guint8 *) g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
&actual_length,
dfu_device_get_timeout (priv->device),
cancellable,
&error_local)) {
/* refresh the error code */
dfu_device_error_fixup (priv->device, cancellable, &error_local);
g_set_error (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"cannot download data: %s",
error_local->message);
return FALSE;
}
/* for ST devices, the action only occurs when we do GetStatus */
if (!dfu_target_check_status (target, cancellable, error))
return FALSE;
g_assert (actual_length == g_bytes_get_size (bytes));
return TRUE;
}
/**
* dfu_target_set_address:
* @target: a #DfuTarget
* @address: memory address
* @cancellable: a #GCancellable, or %NULL
* @error: a #GError, or %NULL
*
* Sets the address used for the next download or upload request.
*
* IMPORTANT: This only works on DfuSe-capable devices from ST.
*
* Return value: %TRUE for success
*
* Since: 0.5.4
**/
static gboolean
dfu_target_set_address (DfuTarget *target,
guint32 address,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
GBytes *data_in;
guint8 buf[5];
/* invalid */
if (!dfu_device_has_dfuse_support (priv->device)) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"only supported for DfuSe targets");
return FALSE;
}
/* format buffer */
buf[0] = DFU_CMD_DFUSE_SET_ADDRESS_POINTER;
memcpy (buf + 1, &address, 4);
data_in = g_bytes_new_static (buf, sizeof(buf));
if (!dfu_target_download_chunk (target, 0, data_in, cancellable, error)) {
g_prefix_error (error, "cannot set address 0x%x: ", address);
return FALSE;
}
/* for ST devices, the action only occurs when we do GetStatus */
if (!dfu_target_check_status (target, cancellable, error))
return FALSE;
return TRUE;
}
/**
* dfu_target_erase_address:
* @target: a #DfuTarget
* @address: memory address
* @cancellable: a #GCancellable, or %NULL
* @error: a #GError, or %NULL
*
* Erases a memory sector at a given address.
*
* IMPORTANT: This only works on DfuSe-capable devices from ST.
*
* Return value: %TRUE for success
*
* Since: 0.5.4
**/
static gboolean
dfu_target_erase_address (DfuTarget *target,
guint32 address,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
GBytes *data_in;
guint8 buf[5];
/* invalid */
if (!dfu_device_has_dfuse_support (priv->device)) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"only supported for DfuSe targets");
return FALSE;
}
/* format buffer */
buf[0] = DFU_CMD_DFUSE_ERASE;
memcpy (buf + 1, &address, 4);
data_in = g_bytes_new_static (buf, sizeof(buf));
if (!dfu_target_download_chunk (target, 0, data_in, cancellable, error)) {
g_prefix_error (error, "cannot erase address 0x%x: ", address);
return FALSE;
}
/* for ST devices, the action only occurs when we do GetStatus */
if (!dfu_target_check_status (target, cancellable, error))
return FALSE;
/* 2nd check required to get error code */
return dfu_target_check_status (target, cancellable, error);
}
#if 0
/**
* dfu_target_mass_erase:
* @target: a #DfuTarget
* @cancellable: a #GCancellable, or %NULL
* @error: a #GError, or %NULL
*
* Mass erases the device clearing all SRAM and EEPROM memory.
*
* This may not be supported on all devices, a better way of doing this action
* is to enable read protection and then doing dfu_target_read_unprotect().
*
* IMPORTANT: This only works on DfuSe-capable devices from ST.
*
* Return value: %TRUE for success
*
* Since: 0.5.4
**/
static gboolean
dfu_target_mass_erase (DfuTarget *target,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
GBytes *data_in;
guint8 buf[1];
/* invalid */
if (!dfu_device_has_dfuse_support (priv->device)) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"only supported for DfuSe targets");
return FALSE;
}
/* format buffer */
buf[0] = DFU_CMD_DFUSE_ERASE;
data_in = g_bytes_new_static (buf, sizeof(buf));
if (!dfu_target_download_chunk (target, 0, data_in, cancellable, error)) {
g_prefix_error (error, "cannot mass-erase: ");
return FALSE;
}
/* for ST devices, the action only occurs when we do GetStatus */
if (!dfu_target_check_status (target, cancellable, error))
return FALSE;
/* 2nd check required to get error code */
return dfu_target_check_status (target, cancellable, error);
}
/**
* dfu_target_read_unprotect:
* @target: a #DfuTarget
* @cancellable: a #GCancellable, or %NULL
* @error: a #GError, or %NULL
*
* Turns of read protection on the device, clearing all SRAM and EEPROM memory.
*
* IMPORTANT: This only works on DfuSe-capable devices from ST.
*
* Return value: %TRUE for success
*
* Since: 0.5.4
**/
static gboolean
dfu_target_read_unprotect (DfuTarget *target,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
GBytes *data_in;
guint8 buf[5];
/* invalid */
if (!dfu_device_has_dfuse_support (priv->device)) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"only supported for DfuSe targets");
return FALSE;
}
/* format buffer */
buf[0] = DFU_CMD_DFUSE_READ_UNPROTECT;
memcpy (buf + 1, &address, 4);
data_in = g_bytes_new_static (buf, sizeof(buf));
if (!dfu_target_download_chunk (target, 0, data_in, cancellable, error)) {
g_prefix_error (error, "cannot read-unprotect: ");
return FALSE;
}
/* for ST devices, the action only occurs when we do GetStatus */
return dfu_target_check_status (target, cancellable, error);
}
#endif
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;
guint16 transfer_size = dfu_device_get_transfer_size (priv->device);
buf = g_new0 (guint8, 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,
dfu_device_get_interface (priv->device),
buf, (gsize) transfer_size,
&actual_length,
dfu_device_get_timeout (priv->device),
cancellable,
&error_local)) {
/* refresh the error code */
dfu_device_error_fixup (priv->device, cancellable, &error_local);
g_set_error (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"cannot upload data: %s",
error_local->message);
return NULL;
}
return g_bytes_new_take (buf, actual_length);
}
static void
dfu_target_set_action (DfuTarget *target, DfuAction task)
{
g_signal_emit (target, signals[SIGNAL_ACTION_CHANGED], 0, task);
}
static void
dfu_target_set_percentage_raw (DfuTarget *target, guint percentage)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
if (percentage == priv->old_percentage)
return;
g_signal_emit (target,
signals[SIGNAL_PERCENTAGE_CHANGED],
0, percentage);
priv->old_percentage = percentage;
}
static void
dfu_target_set_percentage (DfuTarget *target, guint value, guint total)
{
guint percentage;
g_return_if_fail (total > 0);
percentage = (value * 100) / total;
if (percentage >= 100)
return;
dfu_target_set_percentage_raw (target, percentage);
}
static GBytes *
_g_bytes_array_join (GPtrArray *chunks)
{
gsize total_size = 0;
guint32 offset = 0;
guint8 *buffer;
guint i;
/* get the size of all the chunks */
for (i = 0; i < chunks->len; i++) {
GBytes *chunk_tmp = g_ptr_array_index (chunks, i);
total_size += g_bytes_get_size (chunk_tmp);
}
/* copy them into a buffer */
buffer = g_malloc0 (total_size);
for (i = 0; i < chunks->len; i++) {
const guint8 *chunk_data;
gsize chunk_size = 0;
GBytes *chunk_tmp = g_ptr_array_index (chunks, i);
chunk_data = g_bytes_get_data (chunk_tmp, &chunk_size);
if (chunk_size == 0)
continue;
memcpy (buffer + offset, chunk_data, chunk_size);
offset += chunk_size;
}
return g_bytes_new_take (buffer, total_size);
}
static DfuElement *
dfu_target_upload_element_dfuse (DfuTarget *target,
guint32 address,
gsize expected_size,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
DfuSector *sector;
DfuElement *element = NULL;
GBytes *chunk_tmp;
guint32 offset = address;
gsize total_size = 0;
guint16 transfer_size = dfu_device_get_transfer_size (priv->device);
guint32 last_sector_id = G_MAXUINT;
guint idx;
g_autoptr(GBytes) contents = NULL;
g_autoptr(GBytes) contents_truncated = NULL;
g_autoptr(GPtrArray) chunks = NULL;
/* for DfuSe devices we need to handle the address manually */
sector = dfu_target_get_sector_for_addr (target, offset);
if (sector == NULL) {
g_set_error (error,
DFU_ERROR,
DFU_ERROR_INVALID_DEVICE,
"no memory sector at 0x%04x",
(guint) offset);
return NULL;
}
g_debug ("using sector %u for read of %x",
dfu_sector_get_id (sector),
offset);
if (!dfu_sector_has_cap (sector, DFU_SECTOR_CAP_READABLE)) {
g_set_error (error,
DFU_ERROR,
DFU_ERROR_INVALID_DEVICE,
"memory sector at 0x%04x is not readble",
(guint) offset);
return NULL;
}
/* manually set the sector address */
if (dfu_sector_get_id (sector) != last_sector_id) {
g_debug ("setting DfuSe address to 0x%04x", (guint) offset);
if (!dfu_target_set_address (target,
offset,
cancellable,
error))
return NULL;
last_sector_id = dfu_sector_get_id (sector);
}
/* abort back to IDLE */
if (!dfu_device_abort (priv->device, cancellable, error))
return FALSE;
/* get all the chunks from the hardware */
chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
for (idx = 0; idx < G_MAXUINT16; idx++) {
guint32 chunk_size;
/* read chunk of data -- ST uses wBlockNum=0 for DfuSe commands
* and wBlockNum=1 is reserved */
chunk_tmp = dfu_target_upload_chunk (target,
(guint8) (idx + 2),
cancellable,
error);
if (chunk_tmp == NULL)
return NULL;
/* add to array */
chunk_size = (guint32) g_bytes_get_size (chunk_tmp);
g_debug ("got #%04x chunk @0x%x of size %" G_GUINT32_FORMAT,
idx, offset, chunk_size);
g_ptr_array_add (chunks, chunk_tmp);
total_size += chunk_size;
offset += chunk_size;
/* update UI */
if (chunk_size > 0)
dfu_target_set_percentage (target, total_size, expected_size);
/* detect short write as EOF */
if (chunk_size < transfer_size)
break;
/* more data than we needed */
if (expected_size > 0 && total_size > expected_size)
break;
}
/* abort back to IDLE */
if (dfu_device_has_dfuse_support (priv->device)) {
if (!dfu_device_abort (priv->device, cancellable, error))
return FALSE;
}
/* 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 %" G_GSIZE_FORMAT ", "
"expected %" G_GSIZE_FORMAT ,
total_size, expected_size);
return NULL;
}
}
/* done */
dfu_target_set_percentage_raw (target, 100);
/* create new image */
contents = _g_bytes_array_join (chunks);
contents_truncated = g_bytes_new_from_bytes (contents, 0, expected_size);
element = dfu_element_new ();
dfu_element_set_contents (element, contents_truncated);
return element;
}
static DfuElement *
dfu_target_upload_element_dfu (DfuTarget *target,
guint32 address,
gsize expected_size,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
DfuElement *element = NULL;
GBytes *chunk_tmp;
guint32 offset = 0;
gsize total_size = 0;
guint16 transfer_size = dfu_device_get_transfer_size (priv->device);
guint idx;
g_autoptr(GBytes) contents = NULL;
g_autoptr(GPtrArray) chunks = NULL;
/* get all the chunks from the hardware */
chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
for (idx = 0; idx < G_MAXUINT16; idx++) {
guint32 chunk_size;
/* read chunk of data */
chunk_tmp = dfu_target_upload_chunk (target,
(guint8) idx,
cancellable,
error);
if (chunk_tmp == NULL)
return NULL;
/* keep a sum of all the chunks */
chunk_size = (guint32) g_bytes_get_size (chunk_tmp);
total_size += chunk_size;
offset += chunk_size;
/* add to array */
g_debug ("got #%04x chunk of size %" G_GUINT32_FORMAT,
idx, chunk_size);
g_ptr_array_add (chunks, chunk_tmp);
/* update UI */
if (chunk_size > 0)
dfu_target_set_percentage (target, total_size, expected_size);
/* detect short write as EOF */
if (chunk_size < 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 %" G_GSIZE_FORMAT ", "
"expected %" G_GSIZE_FORMAT ,
total_size, expected_size);
return NULL;
}
}
/* done */
dfu_target_set_percentage_raw (target, 100);
/* create new image */
contents = _g_bytes_array_join (chunks);
element = dfu_element_new ();
dfu_element_set_contents (element, contents);
return element;
}
static DfuElement *
dfu_target_upload_element (DfuTarget *target,
guint32 address,
gsize expected_size,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
if (dfu_device_has_dfuse_support (priv->device)) {
return dfu_target_upload_element_dfuse (target,
address,
expected_size,
cancellable,
error);
}
return dfu_target_upload_element_dfu (target,
address,
expected_size,
cancellable,
error);
}
/**
* dfu_target_upload:
* @target: a #DfuTarget
* @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY
* @cancellable: a #GCancellable, or %NULL
* @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,
DfuTargetTransferFlags flags,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
DfuSector *sector;
guint i;
guint32 last_sector_id = G_MAXUINT;
g_autoptr(DfuImage) image = NULL;
g_return_val_if_fail (DFU_IS_TARGET (target), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* ensure populated */
if (!dfu_target_setup (target, error))
return NULL;
/* can the target do this? */
if (!dfu_device_can_upload (priv->device)) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"target cannot do uploading");
return NULL;
}
/* use correct alt */
if (!dfu_target_use_alt_setting (target, error))
return NULL;
/* no open?! */
if (priv->sectors->len == 0) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"no sectors defined for target");
return NULL;
}
/* update UI */
dfu_target_set_action (target, DFU_ACTION_READ);
/* create a new image */
image = dfu_image_new ();
dfu_image_set_name (image, priv->alt_name);
dfu_image_set_alt_setting (image, priv->alt_setting);
/* get all the sectors for the device */
for (i = 0; i < priv->sectors->len; i++) {
g_autoptr(DfuElement) element = NULL;
/* only upload to the start of any zone:sector */
sector = g_ptr_array_index (priv->sectors, i);
if (dfu_sector_get_id (sector) == last_sector_id)
continue;
/* get the first element from the hardware */
g_debug ("starting upload from 0x%08x (0x%04x)",
dfu_sector_get_address (sector),
dfu_sector_get_size_left (sector));
element = dfu_target_upload_element (target,
dfu_sector_get_address (sector),
dfu_sector_get_size_left (sector),
cancellable,
error);
if (element == NULL)
return NULL;
/* this element was uploaded okay */
dfu_image_add_element (image, element);
/* ignore sectors until one of these changes */
last_sector_id = dfu_sector_get_id (sector);
}
/* update UI */
dfu_target_set_action (target, DFU_ACTION_IDLE);
/* do host reset */
if ((flags & DFU_TARGET_TRANSFER_FLAG_ATTACH) > 0 ||
(flags & DFU_TARGET_TRANSFER_FLAG_WAIT_RUNTIME) > 0) {
if (!dfu_device_attach (priv->device, error))
return NULL;
}
/* boot to runtime */
if (flags & DFU_TARGET_TRANSFER_FLAG_WAIT_RUNTIME) {
g_debug ("booting to runtime");
if (!dfu_device_wait_for_replug (priv->device,
DFU_DEVICE_REPLUG_TIMEOUT,
cancellable,
error))
return NULL;
}
/* success */
return g_object_ref (image);
}
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 %" G_GSIZE_FORMAT " bytes, "
"expected %" G_GSIZE_FORMAT,
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;
}
static gboolean
dfu_target_download_element_dfu (DfuTarget *target,
DfuElement *element,
DfuTargetTransferFlags flags,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
GBytes *bytes;
guint i;
guint nr_chunks;
guint16 transfer_size = dfu_device_get_transfer_size (priv->device);
g_autoptr(GError) error_local = NULL;
/* round up as we have to transfer incomplete blocks */
bytes = dfu_element_get_contents (element);
nr_chunks = (guint) ceil ((gdouble) g_bytes_get_size (bytes) /
(gdouble) 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;
guint32 offset;
g_autoptr(GBytes) bytes_tmp = NULL;
/* caclulate the offset into the element data */
offset = i * transfer_size;
/* we have to write one final zero-sized chunk for EOF */
if (i < nr_chunks) {
length = g_bytes_get_size (bytes) - offset;
if (length > transfer_size)
length = 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 %" G_GSIZE_FORMAT,
i, g_bytes_get_size (bytes_tmp));
if (!dfu_target_download_chunk (target,
(guint8) i,
bytes_tmp,
cancellable,
error))
return FALSE;
/* update UI */
dfu_target_set_percentage (target, offset, g_bytes_get_size (bytes));
/* give the target a chance to update */
g_usleep (dfu_device_get_download_timeout (priv->device) * 1000);
/* getting the status moves the state machine to DNLOAD-IDLE */
if (!dfu_device_refresh (priv->device, cancellable, error))
return FALSE;
}
/* done */
dfu_target_set_percentage_raw (target, 100);
dfu_target_set_action (target, DFU_ACTION_IDLE);
/* success */
return TRUE;
}
static gboolean
dfu_target_download_element_dfuse (DfuTarget *target,
DfuElement *element,
DfuTargetTransferFlags flags,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
DfuSector *sector;
GBytes *bytes;
guint i;
guint nr_chunks;
guint last_sector_id = G_MAXUINT;
guint16 transfer_size = dfu_device_get_transfer_size (priv->device);
g_autoptr(GError) error_local = NULL;
/* round up as we have to transfer incomplete blocks */
bytes = dfu_element_get_contents (element);
nr_chunks = (guint) ceil ((gdouble) g_bytes_get_size (bytes) /
(gdouble) 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; i++) {
gsize length;
guint32 offset;
guint32 offset_dev;
g_autoptr(GBytes) bytes_tmp = NULL;
/* caclulate the offset into the element data */
offset = i * transfer_size;
offset_dev = dfu_element_get_address (element) + offset;
/* for DfuSe devices we need to handle the erase and setting
* the sectory address manually */
sector = dfu_target_get_sector_for_addr (target, offset_dev);
if (sector == NULL) {
g_set_error (error,
DFU_ERROR,
DFU_ERROR_INVALID_DEVICE,
"no memory sector at 0x%04x",
(guint) offset);
return FALSE;
}
if (!dfu_sector_has_cap (sector, DFU_SECTOR_CAP_WRITEABLE)) {
g_set_error (error,
DFU_ERROR,
DFU_ERROR_INVALID_DEVICE,
"memory sector at 0x%04x is not writable",
(guint) offset_dev);
return FALSE;
}
/* if it's erasable and not yet blanked */
if (!dfu_sector_has_cap (sector, DFU_SECTOR_CAP_ERASEABLE) &&
g_hash_table_lookup (priv->sectors_erased, sector) == NULL) {
g_debug ("erasing DfuSe address at 0x%04x", offset_dev);
if (!dfu_target_erase_address (target,
offset_dev,
cancellable,
error))
return FALSE;
g_hash_table_insert (priv->sectors_erased,
sector,
GINT_TO_POINTER (1));
}
/* manually set the sector address */
if (dfu_sector_get_id (sector) != last_sector_id) {
g_debug ("setting DfuSe address to 0x%04x", (guint) offset);
if (!dfu_target_set_address (target,
(guint32) offset_dev,
cancellable,
error))
return FALSE;
last_sector_id = dfu_sector_get_id (sector);
}
/* we have to write one final zero-sized chunk for EOF */
length = g_bytes_get_size (bytes) - offset;
if (length > transfer_size)
length = transfer_size;
bytes_tmp = g_bytes_new_from_bytes (bytes, offset, length);
g_debug ("writing #%04x chunk of size %" G_GSIZE_FORMAT,
i, g_bytes_get_size (bytes_tmp));
/* ST uses wBlockNum=0 for DfuSe commands and wBlockNum=1 is reserved */
if (!dfu_target_download_chunk (target,
(guint8) (i + 2),
bytes_tmp,
cancellable,
error))
return FALSE;
/* update UI */
dfu_target_set_percentage (target, offset, g_bytes_get_size (bytes));
/* give the target a chance to update */
g_usleep (dfu_device_get_download_timeout (priv->device) * 1000);
/* getting the status moves the state machine to DNLOAD-IDLE */
if (!dfu_device_refresh (priv->device, cancellable, error))
return FALSE;
}
/* done */
dfu_target_set_percentage_raw (target, 100);
dfu_target_set_action (target, DFU_ACTION_IDLE);
/* success */
return TRUE;
}
static gboolean
dfu_target_download_element (DfuTarget *target,
DfuElement *element,
DfuTargetTransferFlags flags,
GCancellable *cancellable,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
/* DfuSe specific */
if (dfu_device_has_dfuse_support (priv->device)) {
if (!dfu_target_download_element_dfuse (target,
element,
flags,
cancellable,
error))
return FALSE;
} else {
if (!dfu_target_download_element_dfu (target,
element,
flags,
cancellable,
error))
return FALSE;
}
/* verify */
if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY) {
GBytes *bytes;
GBytes *bytes_tmp;
g_autoptr(DfuElement) element_tmp = NULL;
dfu_target_set_action (target, DFU_ACTION_VERIFY);
bytes = dfu_element_get_contents (element);
element_tmp = dfu_target_upload_element (target,
dfu_element_get_address (element),
g_bytes_get_size (bytes),
cancellable,
error);
if (element_tmp == NULL)
return FALSE;
bytes_tmp = dfu_element_get_contents (element_tmp);
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;
}
dfu_target_set_action (target, DFU_ACTION_IDLE);
}
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
* @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,
GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
DfuElement *element;
GPtrArray *elements;
gboolean ret;
guint i;
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);
/* ensure populated */
if (!dfu_target_setup (target, error))
return FALSE;
/* can the target do this? */
if (!dfu_device_can_download (priv->device)) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"target cannot do downloading");
return FALSE;
}
/* use correct alt */
if (!dfu_target_use_alt_setting (target, error))
return FALSE;
/* mark these as all erased */
if (dfu_device_has_dfuse_support (priv->device))
g_hash_table_remove_all (priv->sectors_erased);
/* update UI */
dfu_target_set_action (target, DFU_ACTION_WRITE);
/* download all elements in the image to the device */
elements = dfu_image_get_elements (image);
if (elements->len == 0) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_INVALID_FILE,
"no image elements");
return FALSE;
}
for (i = 0; i < elements->len; i++) {
element = dfu_image_get_element (image, (guint8) i);
g_debug ("downloading element at 0x%04x",
dfu_element_get_address (element));
ret = dfu_target_download_element (target,
element,
flags,
cancellable,
error);
if (!ret)
return FALSE;
}
/* update UI */
dfu_target_set_action (target, DFU_ACTION_IDLE);
/* attempt to switch back to runtime */
if ((flags & DFU_TARGET_TRANSFER_FLAG_ATTACH) > 0 ||
(flags & DFU_TARGET_TRANSFER_FLAG_WAIT_RUNTIME) > 0) {
if (!dfu_device_attach (priv->device, error))
return FALSE;
}
/* boot to runtime */
if (flags & DFU_TARGET_TRANSFER_FLAG_WAIT_RUNTIME) {
g_debug ("booting to runtime to set auto-boot");
if (!dfu_device_wait_for_replug (priv->device,
DFU_DEVICE_REPLUG_TIMEOUT,
cancellable,
error))
return FALSE;
}
/* success */
return TRUE;
}
#if 0
static gboolean
dfu_target_get_commands (DfuTarget *target,
GCancellable *cancellable,
GError **error)
{
GBytes *data_in;
GBytes *data_out;
guint8 buf[1];
/* invalid */
if (!dfu_device_has_dfuse_support (priv->device)) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_SUPPORTED,
"only supported for DfuSe targets");
return FALSE;
}
/* format buffer */
buf[0] = DFU_CMD_DFUSE_GET_COMMAND;
data_in = g_bytes_new_static (buf, sizeof(buf));
if (!dfu_target_download_chunk (target, 0, data_in, cancellable, error)) {
g_prefix_error (error, "cannot get DfuSe commands: ");
return FALSE;
}
/* return results */
data_out = dfu_target_upload_chunk (target, 0, cancellable, error);
if (data_out == NULL)
return FALSE;
// N bytes,
// each byte is the command code
// FIXME: parse?
return TRUE;
}
#endif
/**
* dfu_target_get_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_alt_setting (DfuTarget *target)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
g_return_val_if_fail (DFU_IS_TARGET (target), 0xff);
return priv->alt_setting;
}
/**
* dfu_target_get_alt_name:
* @target: a #DfuTarget
* @error: a #GError, or %NULL
*
* 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_alt_name (DfuTarget *target, GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
g_return_val_if_fail (DFU_IS_TARGET (target), NULL);
/* ensure populated */
if (!dfu_target_setup (target, error))
return NULL;
/* nothing */
if (priv->alt_name == NULL) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_FOUND,
"no alt-name");
return NULL;
}
return priv->alt_name;
}
/**
* dfu_target_get_alt_name_for_display:
* @target: a #DfuTarget
* @error: a #GError, or %NULL
*
* Gets the alternate setting name to use for this interface that can be
* shown on the display.
*
* Return value: the alternative setting name
*
* Since: 0.7.5
**/
const gchar *
dfu_target_get_alt_name_for_display (DfuTarget *target, GError **error)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
g_return_val_if_fail (DFU_IS_TARGET (target), NULL);
/* ensure populated */
if (!dfu_target_setup (target, error))
return NULL;
/* nothing */
if (priv->alt_name_for_display == NULL) {
g_set_error_literal (error,
DFU_ERROR,
DFU_ERROR_NOT_FOUND,
"no alt-name");
return NULL;
}
return priv->alt_name_for_display;
}
/**
* dfu_target_get_cipher_kind:
* @target: a #DfuTarget
*
* Gets the cipher used for data sent to this interface.
*
* Return value: the cipher, typically %DFU_CIPHER_KIND_NONE
*
* Since: 0.5.4
**/
DfuCipherKind
dfu_target_get_cipher_kind (DfuTarget *target)
{
DfuTargetPrivate *priv = GET_PRIVATE (target);
g_return_val_if_fail (DFU_IS_TARGET (target), 0);
return priv->cipher_kind;
}