/** * dfu_element_to_dfuse: (skip) * @element: a #DfuElement * * Packs a DfuSe element. * * Returns: (transfer full): the packed data **/ static GBytes * dfu_element_to_dfuse (DfuElement *element) { DfuSeElementPrefix *el; const guint8 *data; gsize length; guint8 *buf; data = g_bytes_get_data (dfu_element_get_contents (element), &length); buf = g_malloc0 (length + sizeof (DfuSeElementPrefix)); el = (DfuSeElementPrefix *) buf; el->address = GUINT32_TO_LE (dfu_element_get_address (element)); el->size = GUINT32_TO_LE (length); memcpy (buf + sizeof (DfuSeElementPrefix), data, length); return g_bytes_new_take (buf, length + sizeof (DfuSeElementPrefix)); }
/** * 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 NULL; /* 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); /* 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, 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; } /* 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; }
/** * dfu_target_download_element: **/ static gboolean dfu_target_download_element (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 dfuse_sector_offset = 0; guint last_sector_id = G_MAXUINT; guint old_percentage = G_MAXUINT; guint16 transfer_size = dfu_device_get_transfer_size (priv->device); g_autoptr(GError) error_local = NULL; /* ST uses wBlockNum=0 for DfuSe commands and wBlockNum=1 is reserved */ if (dfu_device_has_dfuse_support (priv->device)) dfuse_sector_offset = 2; /* round up as we have to transfer incomplete blocks */ bytes = dfu_element_get_contents (element); nr_chunks = 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; gsize offset; guint percentage; g_autoptr(GBytes) bytes_tmp = NULL; /* caclulate the offset into the element data */ offset = i * transfer_size; /* for DfuSe devices we need to handle the erase and setting * the address manually */ if (dfu_device_has_dfuse_support (priv->device)) { /* check the sector with this element address is suitable */ 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 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); 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", (guint) offset); if (!dfu_target_erase_address (target, offset, 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, offset, cancellable, error)) return FALSE; last_sector_id = dfu_sector_get_id (sector); } } /* 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 + dfuse_sector_offset, bytes_tmp, cancellable, error)) return FALSE; /* update UI */ percentage = (offset * 100) / g_bytes_get_size (bytes); if (percentage != old_percentage) { g_signal_emit (target, signals[SIGNAL_PERCENTAGE_CHANGED], 0, percentage); } /* 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; } /* verify */ if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY) { GBytes *bytes_tmp; g_autoptr(DfuElement) element_tmp = NULL; 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; } } return TRUE; }