/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2015 Richard Hughes * * 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 #include #include "dfu-common.h" #include "dfu-device-private.h" #include "dfu-sector-private.h" #include "dfu-target-private.h" #include "fwupd-error.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 */ guint old_percentage; FwupdStatus old_action; } 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. **/ 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 FwupdStatus * * The ::action-changed signal is emitted when the high level action changes. **/ 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->old_percentage = G_MAXUINT; priv->old_action = FWUPD_STATUS_IDLE; } 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); /* 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); GString *str = g_string_new (""); for (guint i = 0; i < priv->sectors->len; i++) { DfuSector *sector = g_ptr_array_index (priv->sectors, i); g_autofree gchar *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); for (guint i = 0; i < priv->sectors->len; i++) { DfuSector *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; /* parse # of sectors */ nr_sectors = g_ascii_strtoull (dfuse_sector_id, &tmp, 10); if (nr_sectors > 999) { g_set_error (error, FWUPD_ERROR, FWUPD_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, FWUPD_ERROR, FWUPD_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, FWUPD_ERROR, FWUPD_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, FWUPD_ERROR, FWUPD_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, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid sector type: %s", tmp); return FALSE; } /* add all the sectors */ for (guint 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); 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; guint64 addr_tmp; addr_tmp = g_ascii_strtoull (alt_name + 6, NULL, 16); if (addr_tmp == 0 || addr_tmp > G_MAXUINT32) return FALSE; g_debug ("RAM description, so parsing"); sector = dfu_sector_new ((guint32) addr_tmp, 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); g_free (priv->alt_name_for_display); priv->alt_name_for_display = g_strdup (g_strchomp (zones[0] + 1)); for (guint i = 1; zones[i] != NULL; i += 2) { guint32 addr; guint64 addr_tmp; g_auto(GStrv) sectors = NULL; /* parse address */ if (!g_str_has_prefix (zones[i], "0x")) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_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, FWUPD_ERROR, FWUPD_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, FWUPD_ERROR, FWUPD_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 **/ 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 **/ 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 **/ 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; /* wait for dfuDNBUSY to not be set */ if (dfu_device_has_dfuse_support (priv->device)) { while (dfu_device_get_state (priv->device) == DFU_STATE_DFU_DNBUSY) { g_debug ("waiting for DFU_STATE_DFU_DNBUSY to clear"); g_usleep (dfu_device_get_download_timeout (priv->device) * 1000); 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, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Read protection is active"); return FALSE; } if (status == DFU_STATUS_ERR_TARGET) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Address is wrong or unsupported"); return FALSE; } } /* use a proper error description */ g_set_error_literal (error, FWUPD_ERROR, FWUPD_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 **/ 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, NULL, 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, FWUPD_ERROR, FWUPD_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 **/ static gboolean dfu_target_setup (DfuTarget *target, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); 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, guint16 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, FWUPD_ERROR, FWUPD_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_device_refresh (priv->device, cancellable, error)) return FALSE; /* give the target a chance to update */ g_usleep (dfu_device_get_download_timeout (priv->device) * 1000); 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 **/ 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, FWUPD_ERROR, FWUPD_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; } /* 2nd check required to get error code */ g_debug ("doing actual check status"); return dfu_target_check_status (target, cancellable, error); } /** * 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 **/ 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, FWUPD_ERROR, FWUPD_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; } /* 2nd check required to get error code */ g_debug ("doing actual check status"); 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 **/ 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, FWUPD_ERROR, FWUPD_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; } /* 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 **/ 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, FWUPD_ERROR, FWUPD_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, guint16 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, FWUPD_ERROR, FWUPD_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, FwupdStatus action) { DfuTargetPrivate *priv = GET_PRIVATE (target); /* unchanged */ if (priv->old_action == action) return; if (priv->old_action != FWUPD_STATUS_IDLE && action != FWUPD_STATUS_IDLE) { g_debug ("ignoring action %s as %s already set and not idle", fwupd_status_to_string (action), fwupd_status_to_string (priv->old_action)); return; } g_debug ("setting action %s", fwupd_status_to_string (action)); g_signal_emit (target, signals[SIGNAL_ACTION_CHANGED], 0, action); priv->old_action = action; } static void dfu_target_set_percentage_raw (DfuTarget *target, guint percentage) { DfuTargetPrivate *priv = GET_PRIVATE (target); if (percentage == priv->old_percentage) return; g_debug ("setting percentage %u%% of %s", percentage, fwupd_status_to_string (priv->old_action)); 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; /* get the size of all the chunks */ for (guint 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 (guint 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, gsize maximum_size, GCancellable *cancellable, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); DfuSector *sector; DfuElement *element = NULL; GBytes *chunk_tmp; guint32 offset = address; guint percentage_size = expected_size > 0 ? expected_size : maximum_size; gsize total_size = 0; guint16 transfer_size = dfu_device_get_transfer_size (priv->device); 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, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "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, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "memory sector at 0x%04x is not readble", (guint) offset); return NULL; } /* update UI */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_READ); /* manually set the sector address */ g_debug ("setting DfuSe address to 0x%04x", (guint) offset); if (!dfu_target_set_address (target, offset, cancellable, error)) return NULL; /* abort back to IDLE */ if (!dfu_device_abort (priv->device, cancellable, error)) return NULL; /* get all the chunks from the hardware */ chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); for (guint16 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, 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, percentage_size); /* detect short write as EOF */ if (chunk_size < transfer_size) break; /* more data than we needed */ if (maximum_size > 0 && total_size > maximum_size) break; } /* abort back to IDLE */ if (dfu_device_has_dfuse_support (priv->device)) { if (!dfu_device_abort (priv->device, cancellable, error)) return NULL; } /* check final size */ if (expected_size > 0) { if (total_size < expected_size) { g_set_error (error, FWUPD_ERROR, FWUPD_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); dfu_target_set_action (target, FWUPD_STATUS_IDLE); /* create new image */ contents = _g_bytes_array_join (chunks); if (expected_size > 0) contents_truncated = g_bytes_new_from_bytes (contents, 0, expected_size); else contents_truncated = g_bytes_ref (contents); element = dfu_element_new (); dfu_element_set_contents (element, contents_truncated); dfu_element_set_address (element, address); return element; } static DfuElement * dfu_target_upload_element_dfu (DfuTarget *target, guint32 address, gsize expected_size, gsize maximum_size, GCancellable *cancellable, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); DfuElement *element = NULL; GBytes *chunk_tmp; guint32 offset = 0; guint percentage_size = expected_size > 0 ? expected_size : maximum_size; gsize total_size = 0; guint16 transfer_size = dfu_device_get_transfer_size (priv->device); g_autoptr(GBytes) contents = NULL; g_autoptr(GPtrArray) chunks = NULL; /* update UI */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_READ); /* get all the chunks from the hardware */ chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); for (guint16 idx = 0; idx < G_MAXUINT16; idx++) { guint32 chunk_size; /* read chunk of data */ chunk_tmp = dfu_target_upload_chunk (target, 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, percentage_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, FWUPD_ERROR, FWUPD_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); dfu_target_set_action (target, FWUPD_STATUS_IDLE); /* 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, gsize maximum_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, maximum_size, cancellable, error); } return dfu_target_upload_element_dfu (target, address, expected_size, maximum_size, cancellable, error); } static guint32 dfu_target_get_size_of_zone (DfuTarget *target, guint16 zone) { DfuTargetPrivate *priv = GET_PRIVATE (target); guint32 len = 0; for (guint i = 0; i < priv->sectors->len; i++) { DfuSector *sector = g_ptr_array_index (priv->sectors, i); if (dfu_sector_get_zone (sector) != zone) continue; len += dfu_sector_get_size (sector); } return len; } /** * 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 **/ DfuImage * dfu_target_upload (DfuTarget *target, DfuTargetTransferFlags flags, GCancellable *cancellable, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); DfuSector *sector; guint16 zone_cur; guint32 zone_size = 0; guint32 zone_last = 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, FWUPD_ERROR, FWUPD_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, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sectors defined for target"); return NULL; } /* 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 (guint 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); zone_cur = dfu_sector_get_zone (sector); if (zone_cur == zone_last) continue; /* get the size of the entire continous zone */ zone_size = dfu_target_get_size_of_zone (target, zone_cur); zone_last = zone_cur; /* get the first element from the hardware */ g_debug ("starting upload from 0x%08x (0x%04x)", dfu_sector_get_address (sector), zone_size); element = dfu_target_upload_element (target, dfu_sector_get_address (sector), 0, /* expected */ zone_size, /* maximum */ cancellable, error); if (element == NULL) return NULL; /* this element was uploaded okay */ dfu_image_add_element (image, element); } /* 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; 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 (guint 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; guint16 nr_chunks; guint16 transfer_size = dfu_device_get_transfer_size (priv->device); /* 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, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "zero-length firmware"); return FALSE; } dfu_target_set_action (target, FWUPD_STATUS_DEVICE_WRITE); for (guint16 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, i, bytes_tmp, cancellable, error)) return FALSE; /* update UI */ dfu_target_set_percentage (target, offset, g_bytes_get_size (bytes)); } /* done */ dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_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 nr_chunks; guint zone_last = G_MAXUINT; guint16 transfer_size = dfu_device_get_transfer_size (priv->device); g_autoptr(GPtrArray) sectors_array = NULL; g_autoptr(GHashTable) sectors_hash = 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, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "zero-length firmware"); return FALSE; } /* 1st pass: work out which sectors need erasing */ sectors_array = g_ptr_array_new (); sectors_hash = g_hash_table_new (g_direct_hash, g_direct_equal); for (guint i = 0; i < nr_chunks; i++) { guint32 offset_dev; /* for DfuSe devices we need to handle the erase and setting * the sectory address manually */ offset_dev = dfu_element_get_address (element) + (i * transfer_size); sector = dfu_target_get_sector_for_addr (target, offset_dev); if (sector == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no memory sector at 0x%04x", (guint) offset_dev); return FALSE; } if (!dfu_sector_has_cap (sector, DFU_SECTOR_CAP_WRITEABLE)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "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 (sectors_hash, sector) == NULL) { g_hash_table_insert (sectors_hash, sector, GINT_TO_POINTER (1)); g_ptr_array_add (sectors_array, sector); g_debug ("marking sector 0x%04x-%04x to be erased", dfu_sector_get_address (sector), dfu_sector_get_address (sector) + dfu_sector_get_size (sector)); } } /* 2nd pass: actually erase sectors */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_ERASE); for (guint i = 0; i < sectors_array->len; i++) { sector = g_ptr_array_index (sectors_array, i); g_debug ("erasing sector at 0x%04x", dfu_sector_get_address (sector)); if (!dfu_target_erase_address (target, dfu_sector_get_address (sector), cancellable, error)) return FALSE; dfu_target_set_percentage (target, i + 1, sectors_array->len); } dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_IDLE); /* 3rd pass: write data */ dfu_target_set_action (target, FWUPD_STATUS_DEVICE_WRITE); for (guint 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 set the address manually */ sector = dfu_target_get_sector_for_addr (target, offset_dev); g_assert (sector != NULL); /* manually set the sector address */ if (dfu_sector_get_zone (sector) != zone_last) { g_debug ("setting address to 0x%04x", (guint) offset_dev); if (!dfu_target_set_address (target, (guint32) offset_dev, cancellable, error)) return FALSE; zone_last = dfu_sector_get_zone (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 sector at 0x%04x (0x%" G_GSIZE_FORMAT ")", offset_dev, 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; /* getting the status moves the state machine to DNLOAD-IDLE */ if (!dfu_target_check_status (target, cancellable, error)) return FALSE; /* update UI */ dfu_target_set_percentage (target, offset, g_bytes_get_size (bytes)); } /* done */ dfu_target_set_percentage_raw (target, 100); dfu_target_set_action (target, FWUPD_STATUS_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, FWUPD_STATUS_DEVICE_VERIFY); bytes = dfu_element_get_contents (element); element_tmp = dfu_target_upload_element (target, dfu_element_get_address (element), g_bytes_get_size (bytes), 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, FWUPD_ERROR, FWUPD_ERROR_READ, "verify failed: %s", bytes_cmp_str); return FALSE; } dfu_target_set_action (target, FWUPD_STATUS_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 **/ gboolean dfu_target_download (DfuTarget *target, DfuImage *image, DfuTargetTransferFlags flags, GCancellable *cancellable, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); GPtrArray *elements; gboolean ret; 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, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "target cannot do downloading"); return FALSE; } /* use correct alt */ if (!dfu_target_use_alt_setting (target, error)) return FALSE; /* download all elements in the image to the device */ elements = dfu_image_get_elements (image); if (elements->len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no image elements"); return FALSE; } for (guint i = 0; i < elements->len; i++) { DfuElement *element = dfu_image_get_element (image, (guint8) i); g_debug ("downloading element at 0x%04x", dfu_element_get_address (element)); /* auto-detect missing firmware address -- this assumes * that the first target is the main program memory and that * there is only one element in the firmware file */ if (flags & DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC && dfu_element_get_address (element) == 0x0 && elements->len == 1 && priv->sectors->len > 0) { DfuSector *sector = g_ptr_array_index (priv->sectors, 0); g_debug ("fixing up firmware address from 0x0 to 0x%x", dfu_sector_get_address (sector)); dfu_element_set_address (element, dfu_sector_get_address (sector)); } /* download to device */ ret = dfu_target_download_element (target, element, flags, cancellable, error); if (!ret) return FALSE; } /* 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, FWUPD_ERROR, FWUPD_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 **/ 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 **/ 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, FWUPD_ERROR, FWUPD_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 **/ 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, FWUPD_ERROR, FWUPD_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 **/ 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; }