From e7ba943cf5129efe0bf07556bb3a03f64c96fbd7 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Fri, 20 Nov 2015 13:44:50 +0000 Subject: [PATCH] libdfu: Support download and upload to ST DfuSe devices Also, simplify the general download and upload API now we can deal with element data in chunks. --- docs/libdfu/Makefile.am | 1 + docs/libdfu/libdfu-docs.xml | 1 + libdfu/Makefile.am | 3 + libdfu/dfu-device.c | 77 ++- libdfu/dfu-device.h | 1 - libdfu/dfu-firmware.c | 16 +- libdfu/dfu-sector-private.h | 38 ++ libdfu/dfu-sector.c | 229 +++++++ libdfu/dfu-sector.h | 66 +++ libdfu/dfu-self-test.c | 106 +++- libdfu/dfu-target-private.h | 5 + libdfu/dfu-target.c | 1121 ++++++++++++++++++++++++++++++----- libdfu/dfu-target.h | 2 +- libdfu/dfu-tool.c | 20 +- libdfu/dfu.h | 1 + src/fu-provider-usb.c | 1 - 16 files changed, 1489 insertions(+), 199 deletions(-) create mode 100644 libdfu/dfu-sector-private.h create mode 100644 libdfu/dfu-sector.c create mode 100644 libdfu/dfu-sector.h diff --git a/docs/libdfu/Makefile.am b/docs/libdfu/Makefile.am index ef538d552..11fef2956 100644 --- a/docs/libdfu/Makefile.am +++ b/docs/libdfu/Makefile.am @@ -52,6 +52,7 @@ IGNORE_HFILES= \ dfu-device-private.h \ dfu-element-private.h \ dfu-image-private.h \ + dfu-sector-private.h \ dfu-target-private.h # Images to copy into HTML directory. diff --git a/docs/libdfu/libdfu-docs.xml b/docs/libdfu/libdfu-docs.xml index b36fecffa..1b92df966 100644 --- a/docs/libdfu/libdfu-docs.xml +++ b/docs/libdfu/libdfu-docs.xml @@ -80,6 +80,7 @@ if (!dfu_device_download (dfu_device, dfu_firmware, + diff --git a/libdfu/Makefile.am b/libdfu/Makefile.am index e202f4760..16cb65288 100644 --- a/libdfu/Makefile.am +++ b/libdfu/Makefile.am @@ -34,6 +34,9 @@ libdfu_private_la_SOURCES = \ dfu-image.c \ dfu-image.h \ dfu-image-private.h \ + dfu-sector.c \ + dfu-sector.h \ + dfu-sector-private.h \ dfu-target.c \ dfu-target.h \ dfu-target-private.h diff --git a/libdfu/dfu-device.c b/libdfu/dfu-device.c index 164a1dcd2..2b03a2118 100644 --- a/libdfu/dfu-device.c +++ b/libdfu/dfu-device.c @@ -603,7 +603,6 @@ dfu_device_reset (DfuDevice *device, GError **error) /** * dfu_device_upload: * @device: a #DfuDevice - * @expected_size: the expected size of the firmware, or 0 for unknown * @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY * @cancellable: a #GCancellable, or %NULL * @progress_cb: a #GFileProgressCallback, or %NULL @@ -618,7 +617,6 @@ dfu_device_reset (DfuDevice *device, GError **error) **/ DfuFirmware * dfu_device_upload (DfuDevice *device, - gsize expected_size, DfuTargetTransferFlags flags, GCancellable *cancellable, DfuProgressCallback progress_cb, @@ -646,25 +644,30 @@ dfu_device_upload (DfuDevice *device, dfu_firmware_set_release (firmware, 0xffff); /* APP -> DFU */ - if (flags & DFU_TARGET_TRANSFER_FLAG_DETACH) { - target_default = dfu_device_get_target_default (device, error); - if (target_default == NULL) - return NULL; - if (dfu_target_get_mode (target_default) == DFU_MODE_RUNTIME) { - g_debug ("detaching"); - - /* inform UI there's going to be a detach:attach */ - if (progress_cb != NULL) { - progress_cb (DFU_STATE_APP_DETACH, 0, 0, - progress_cb_data); - } - - /* detach and USB reset */ - if (!dfu_target_detach (target_default, NULL, error)) - return NULL; - if (!dfu_device_wait_for_replug (device, 5000, NULL, error)) - return NULL; + target_default = dfu_device_get_target_default (device, error); + if (target_default == NULL) + return NULL; + if (dfu_target_get_mode (target_default) == DFU_MODE_RUNTIME) { + if ((flags & DFU_TARGET_TRANSFER_FLAG_DETACH) == 0) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "device is not in DFU mode"); + return FALSE; } + g_debug ("detaching"); + + /* inform UI there's going to be a detach:attach */ + if (progress_cb != NULL) { + progress_cb (DFU_STATE_APP_DETACH, 0, 0, + progress_cb_data); + } + + /* detach and USB reset */ + if (!dfu_target_detach (target_default, NULL, error)) + return NULL; + if (!dfu_device_wait_for_replug (device, 5000, NULL, error)) + return NULL; } /* upload from each target */ @@ -673,7 +676,12 @@ dfu_device_upload (DfuDevice *device, DfuTarget *target; g_autoptr(DfuImage) image = NULL; target = g_ptr_array_index (targets, i); - image = dfu_target_upload (target, 0, + if (!dfu_target_open (target, + DFU_TARGET_OPEN_FLAG_NONE, + cancellable, + error)) + return NULL; + image = dfu_target_upload (target, DFU_TARGET_TRANSFER_FLAG_NONE, cancellable, progress_cb, @@ -811,7 +819,18 @@ dfu_device_download (DfuDevice *device, } /* APP -> DFU */ - if (flags & DFU_TARGET_TRANSFER_FLAG_DETACH) { + /* detach and USB reset */ + target_default = dfu_device_get_target_default (device, error); + if (target_default == NULL) + return FALSE; + if (dfu_target_get_mode (target_default) == DFU_MODE_RUNTIME) { + if ((flags & DFU_TARGET_TRANSFER_FLAG_DETACH) == 0) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "device is not in DFU mode"); + return FALSE; + } /* inform UI there's going to be a detach:attach */ if (progress_cb != NULL) { @@ -819,17 +838,11 @@ dfu_device_download (DfuDevice *device, progress_cb_data); } - /* detach and USB reset */ - target_default = dfu_device_get_target_default (device, error); - if (target_default == NULL) + g_debug ("detaching"); + if (!dfu_target_detach (target_default, NULL, error)) + return FALSE; + if (!dfu_device_wait_for_replug (device, 5000, NULL, error)) return FALSE; - if (dfu_target_get_mode (target_default) == DFU_MODE_RUNTIME) { - g_debug ("detaching"); - if (!dfu_target_detach (target_default, NULL, error)) - return FALSE; - if (!dfu_device_wait_for_replug (device, 5000, NULL, error)) - return FALSE; - } } /* download each target */ diff --git a/libdfu/dfu-device.h b/libdfu/dfu-device.h index a862031ee..2a0ebced2 100644 --- a/libdfu/dfu-device.h +++ b/libdfu/dfu-device.h @@ -62,7 +62,6 @@ gboolean dfu_device_wait_for_replug (DfuDevice *device, GCancellable *cancellable, GError **error); DfuFirmware *dfu_device_upload (DfuDevice *device, - gsize expected_size, DfuTargetTransferFlags flags, GCancellable *cancellable, DfuProgressCallback progress_cb, diff --git a/libdfu/dfu-firmware.c b/libdfu/dfu-firmware.c index 01827aefc..ac020e0d8 100644 --- a/libdfu/dfu-firmware.c +++ b/libdfu/dfu-firmware.c @@ -928,7 +928,13 @@ dfu_firmware_write_data (DfuFirmware *firmware, GError **error) image = dfu_firmware_get_image_default (firmware); g_assert (image != NULL); element = dfu_image_get_element (image, 0); - g_assert (element != NULL); + if (element == NULL) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_FOUND, + "no firmware element data to write"); + return NULL; + } contents = dfu_element_get_contents (element); return g_bytes_ref (contents); } @@ -940,7 +946,13 @@ dfu_firmware_write_data (DfuFirmware *firmware, GError **error) image = dfu_firmware_get_image_default (firmware); g_assert (image != NULL); element = dfu_image_get_element (image, 0); - g_assert (element != NULL); + if (element == NULL) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_FOUND, + "no firmware element data to write"); + return NULL; + } contents = dfu_element_get_contents (element); g_assert (contents != NULL); return dfu_firmware_add_footer (firmware, contents); diff --git a/libdfu/dfu-sector-private.h b/libdfu/dfu-sector-private.h new file mode 100644 index 000000000..fdd6e74be --- /dev/null +++ b/libdfu/dfu-sector-private.h @@ -0,0 +1,38 @@ +/* -*- 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 + */ + +#ifndef __DFU_SECTOR_PRIVATE_H +#define __DFU_SECTOR_PRIVATE_H + +#include "dfu-sector.h" + +G_BEGIN_DECLS + +DfuSector *dfu_sector_new (guint32 address, + guint32 size, + guint32 size_left, + guint16 zone, + guint16 number, + DfuSectorCap cap); + +G_END_DECLS + +#endif /* __DFU_SECTOR_PRIVATE_H */ diff --git a/libdfu/dfu-sector.c b/libdfu/dfu-sector.c new file mode 100644 index 000000000..064794e8b --- /dev/null +++ b/libdfu/dfu-sector.c @@ -0,0 +1,229 @@ +/* -*- 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-sector + * @short_description: Object representing a sector on a chip + * + * This object represents an sector of memory at a specific address on the + * device itself. + * + * This allows relocatable data segments to be stored in different + * locations on the device itself. + * + * You can think of these objects as flash segments on devices, where a + * complete block can be erased and then written to. + * + * See also: #DfuElement + */ + +#include "config.h" + +#include +#include + +#include "dfu-common.h" +#include "dfu-sector-private.h" + +/** + * DfuSectorPrivate: + * + * Private #DfuSector data + **/ +typedef struct { + guint32 address; + guint32 size; + guint32 size_left; + guint16 zone; + guint16 number; + DfuSectorCap cap; +} DfuSectorPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (DfuSector, dfu_sector, G_TYPE_OBJECT) +#define GET_PRIVATE(o) (dfu_sector_get_instance_private (o)) + +/** + * dfu_sector_class_init: + **/ +static void +dfu_sector_class_init (DfuSectorClass *klass) +{ +} + +/** + * dfu_sector_init: + **/ +static void +dfu_sector_init (DfuSector *sector) +{ +} + +/** + * dfu_sector_new: (skip) + * address: the address for the sector + * size: the size of this sector + * size_left: the size of the rest of the sector + * zone: the zone of memory the setor belongs + * number: the sector number in the zone + * cap: the #DfuSectorCap + * + * Creates a new DFU sector object. + * + * Return value: a new #DfuSector + * + * Since: 0.5.4 + **/ +DfuSector * +dfu_sector_new (guint32 address, guint32 size, guint32 size_left, + guint16 zone, guint16 number, DfuSectorCap cap) +{ + DfuSectorPrivate *priv; + DfuSector *sector; + sector = g_object_new (DFU_TYPE_SECTOR, NULL); + priv = GET_PRIVATE (sector); + priv->address = address; + priv->size = size; + priv->size_left = size_left; + priv->zone = zone; + priv->number = number; + priv->cap = cap; + return sector; +} + +/** + * dfu_sector_get_address: + * @sector: a #DfuSector + * + * Gets the alternate setting. + * + * Return value: integer, or 0x00 for unset + * + * Since: 0.5.4 + **/ +guint32 +dfu_sector_get_address (DfuSector *sector) +{ + DfuSectorPrivate *priv = GET_PRIVATE (sector); + g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); + return priv->address; +} + +/** + * dfu_sector_get_size: + * @sector: a #DfuSector + * + * Gets the alternate setting. + * + * Return value: integer, or 0x00 for unset + * + * Since: 0.5.4 + **/ +guint32 +dfu_sector_get_size (DfuSector *sector) +{ + DfuSectorPrivate *priv = GET_PRIVATE (sector); + g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); + return priv->size; +} + +/** + * dfu_sector_get_size_left: + * @sector: a #DfuSector + * + * Gets the alternate setting. + * + * Return value: integer, or 0x00 for unset + * + * Since: 0.5.4 + **/ +guint32 +dfu_sector_get_size_left (DfuSector *sector) +{ + DfuSectorPrivate *priv = GET_PRIVATE (sector); + g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); + return priv->size_left; +} + +/** + * dfu_sector_get_id: + * @sector: a #DfuSector + * + * Gets the sector ID which is a combination of the zone and sector number. + * You can use this number to check if the segment is the 'same' as the last + * written or read sector. + * + * Return value: integer ID, or 0x00 for unset + * + * Since: 0.5.4 + **/ +guint32 +dfu_sector_get_id (DfuSector *sector) +{ + DfuSectorPrivate *priv = GET_PRIVATE (sector); + g_return_val_if_fail (DFU_IS_SECTOR (sector), 0x00); + return (((guint32) priv->zone) << 16) | priv->number; +} + +/** + * dfu_sector_has_cap: + * @sector: a #DfuSector + * @cap: a #DfuSectorCap, e.g. %DFU_SECTOR_CAP_ERASEABLE + * + * Finds out if the sector has the required capability. + * + * Return value: %TRUE if the sector has the capabilily + * + * Since: 0.5.4 + **/ +gboolean +dfu_sector_has_cap (DfuSector *sector, DfuSectorCap cap) +{ + DfuSectorPrivate *priv = GET_PRIVATE (sector); + g_return_val_if_fail (DFU_IS_SECTOR (sector), FALSE); + return priv->cap & cap; +} + +/** + * dfu_sector_to_string: + * @sector: a #DfuSector + * + * Returns a string representaiton of the object. + * + * Return value: NULL terminated string, or %NULL for invalid + * + * Since: 0.5.4 + **/ +gchar * +dfu_sector_to_string (DfuSector *sector) +{ + DfuSectorPrivate *priv = GET_PRIVATE (sector); + GString *str; + + g_return_val_if_fail (DFU_IS_SECTOR (sector), NULL); + + str = g_string_new (""); + g_string_append_printf (str, + "Zone:%i, Sec#:%i, Addr:0x%08x, " + "Size:0x%04x, Caps:0x%01x", + priv->zone, priv->number, priv->address, + priv->size, priv->cap); + return g_string_free (str, FALSE); +} diff --git a/libdfu/dfu-sector.h b/libdfu/dfu-sector.h new file mode 100644 index 000000000..794e8ad14 --- /dev/null +++ b/libdfu/dfu-sector.h @@ -0,0 +1,66 @@ +/* -*- 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 + */ + +#ifndef __DFU_SECTOR_H +#define __DFU_SECTOR_H + +#include +#include + +G_BEGIN_DECLS + +#define DFU_TYPE_SECTOR (dfu_sector_get_type ()) +G_DECLARE_DERIVABLE_TYPE (DfuSector, dfu_sector, DFU, SECTOR, GObject) + +struct _DfuSectorClass +{ + GObjectClass parent_class; +}; + +/** + * DfuSectorCap: + * @DFU_SECTOR_CAP_NONE: No operations possible + * @DFU_SECTOR_CAP_READABLE: Sector can be read + * @DFU_SECTOR_CAP_WRITEABLE: Sector can be written + * @DFU_SECTOR_CAP_ERASEABLE: Sector can be erased + * + * The flags indicating what the sector can do. + **/ +typedef enum { + DFU_SECTOR_CAP_NONE = 0, + DFU_SECTOR_CAP_READABLE = 1 << 0, + DFU_SECTOR_CAP_WRITEABLE = 1 << 1, + DFU_SECTOR_CAP_ERASEABLE = 1 << 2, + /*< private >*/ + DFU_SECTOR_CAP_LAST +} DfuSectorCap; + +guint32 dfu_sector_get_id (DfuSector *sector); +guint32 dfu_sector_get_address (DfuSector *sector); +guint32 dfu_sector_get_size (DfuSector *sector); +guint32 dfu_sector_get_size_left (DfuSector *sector); +gboolean dfu_sector_has_cap (DfuSector *sector, + DfuSectorCap cap); +gchar *dfu_sector_to_string (DfuSector *sector); + +G_END_DECLS + +#endif /* __DFU_SECTOR_H */ diff --git a/libdfu/dfu-self-test.c b/libdfu/dfu-self-test.c index f1e99b6d5..12c9320d5 100644 --- a/libdfu/dfu-self-test.c +++ b/libdfu/dfu-self-test.c @@ -28,7 +28,8 @@ #include "dfu-device.h" #include "dfu-error.h" #include "dfu-firmware.h" -#include "dfu-target.h" +#include "dfu-sector-private.h" +#include "dfu-target-private.h" /** * dfu_test_get_filename: @@ -322,6 +323,7 @@ dfu_device_func (void) static void dfu_colorhug_plus_func (void) { + GPtrArray *elements; gboolean ret; gboolean seen_app_idle = FALSE; g_autoptr(DfuDevice) device = NULL; @@ -425,10 +427,13 @@ dfu_colorhug_plus_func (void) } /* get a dump of the existing firmware */ - image = dfu_target_upload (target, 0, DFU_TARGET_TRANSFER_FLAG_NONE, + image = dfu_target_upload (target, DFU_TARGET_TRANSFER_FLAG_NONE, NULL, NULL, NULL, &error); g_assert_no_error (error); g_assert (DFU_IS_IMAGE (image)); + elements = dfu_image_get_elements (image); + g_assert_nonnull (elements); + g_assert_cmpint (elements->len, ==, 1); /* download a new firmware */ ret = dfu_target_download (target, image, @@ -460,6 +465,102 @@ dfu_colorhug_plus_func (void) g_assert (ret); } +/** + * _dfu_target_sectors_to_string: + **/ +static gchar * +_dfu_target_sectors_to_string (DfuTarget *target) +{ + DfuSector *sector; + GPtrArray *sectors; + GString *str; + guint i; + + str = g_string_new (""); + sectors = dfu_target_get_sectors (target); + for (i = 0; i < sectors->len; i++) { + g_autofree gchar *tmp = NULL; + sector = g_ptr_array_index (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 void +dfu_target_dfuse_func (void) +{ + gboolean ret; + gchar *tmp; + g_autoptr(DfuTarget) target = NULL; + g_autoptr(GError) error = NULL; + + /* NULL */ + target = g_object_new (DFU_TYPE_TARGET, NULL); + ret = dfu_target_parse_sectors (target, NULL, &error); + g_assert_no_error (error); + g_assert (ret); + tmp = _dfu_target_sectors_to_string (target); + g_assert_cmpstr (tmp, ==, ""); + g_free (tmp); + + /* no addresses */ + ret = dfu_target_parse_sectors (target, "@Flash3", &error); + g_assert_no_error (error); + g_assert (ret); + tmp = _dfu_target_sectors_to_string (target); + g_assert_cmpstr (tmp, ==, ""); + g_free (tmp); + + /* one sector, no space */ + ret = dfu_target_parse_sectors (target, "@Internal Flash /0x08000000/2*001Ka", &error); + g_assert_no_error (error); + g_assert (ret); + tmp = _dfu_target_sectors_to_string (target); + g_assert_cmpstr (tmp, ==, "Zone:0, Sec#:0, Addr:0x08000000, Size:0x0400, Caps:0x1\n" + "Zone:0, Sec#:0, Addr:0x08000400, Size:0x0400, Caps:0x1"); + g_free (tmp); + + /* multiple sectors */ + ret = dfu_target_parse_sectors (target, "@Flash1 /0x08000000/2*001 Ka,4*001 Kg", &error); + g_assert_no_error (error); + g_assert (ret); + tmp = _dfu_target_sectors_to_string (target); + g_assert_cmpstr (tmp, ==, "Zone:0, Sec#:0, Addr:0x08000000, Size:0x0400, Caps:0x1\n" + "Zone:0, Sec#:0, Addr:0x08000400, Size:0x0400, Caps:0x1\n" + "Zone:0, Sec#:1, Addr:0x08000000, Size:0x0400, Caps:0x7\n" + "Zone:0, Sec#:1, Addr:0x08000400, Size:0x0400, Caps:0x7\n" + "Zone:0, Sec#:1, Addr:0x08000800, Size:0x0400, Caps:0x7\n" + "Zone:0, Sec#:1, Addr:0x08000c00, Size:0x0400, Caps:0x7"); + g_free (tmp); + + /* non-contiguous */ + ret = dfu_target_parse_sectors (target, "@Flash2 /0xF000/4*100Ba/0xE000/3*8Kg/0x80000/2*24Kg", &error); + g_assert_no_error (error); + g_assert (ret); + tmp = _dfu_target_sectors_to_string (target); + g_assert_cmpstr (tmp, ==, "Zone:0, Sec#:0, Addr:0x0000f000, Size:0x0064, Caps:0x1\n" + "Zone:0, Sec#:0, Addr:0x0000f064, Size:0x0064, Caps:0x1\n" + "Zone:0, Sec#:0, Addr:0x0000f0c8, Size:0x0064, Caps:0x1\n" + "Zone:0, Sec#:0, Addr:0x0000f12c, Size:0x0064, Caps:0x1\n" + "Zone:1, Sec#:0, Addr:0x0000e000, Size:0x2000, Caps:0x7\n" + "Zone:1, Sec#:0, Addr:0x00010000, Size:0x2000, Caps:0x7\n" + "Zone:1, Sec#:0, Addr:0x00012000, Size:0x2000, Caps:0x7\n" + "Zone:2, Sec#:0, Addr:0x00080000, Size:0x6000, Caps:0x7\n" + "Zone:2, Sec#:0, Addr:0x00086000, Size:0x6000, Caps:0x7"); + g_free (tmp); + + /* invalid */ + ret = dfu_target_parse_sectors (target, "Flash", NULL); + g_assert (ret); + ret = dfu_target_parse_sectors (target, "@Internal Flash /0x08000000", NULL); + g_assert (!ret); + ret = dfu_target_parse_sectors (target, "@Internal Flash /0x08000000/12*001a", NULL); + g_assert (!ret); +} + int main (int argc, char **argv) { @@ -470,6 +571,7 @@ main (int argc, char **argv) /* tests go here */ g_test_add_func ("/libdfu/enums", dfu_enums_func); + g_test_add_func ("/libdfu/target(DfuSe}", dfu_target_dfuse_func); g_test_add_func ("/libdfu/firmware{raw}", dfu_firmware_raw_func); g_test_add_func ("/libdfu/firmware{dfu}", dfu_firmware_dfu_func); g_test_add_func ("/libdfu/firmware{dfuse}", dfu_firmware_dfuse_func); diff --git a/libdfu/dfu-target-private.h b/libdfu/dfu-target-private.h index 46ee19e93..f0f05180b 100644 --- a/libdfu/dfu-target-private.h +++ b/libdfu/dfu-target-private.h @@ -36,6 +36,11 @@ gboolean _dfu_target_update (DfuTarget *target, GCancellable *cancellable, GError **error); +/* export this just for the self tests */ +gboolean dfu_target_parse_sectors (DfuTarget *target, + const gchar *alt_name, + GError **error); + G_END_DECLS #endif /* __DFU_TARGET_PRIVATE_H */ diff --git a/libdfu/dfu-target.c b/libdfu/dfu-target.c index 68fd2aa93..2e32a4ccd 100644 --- a/libdfu/dfu-target.c +++ b/libdfu/dfu-target.c @@ -41,6 +41,7 @@ #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); @@ -64,6 +65,13 @@ typedef enum { DFU_QUIRK_LAST } DfuQuirks; +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, +} DfuCmdDfuse; + /** * DfuTargetPrivate: * @@ -85,6 +93,8 @@ typedef struct { guint timeout_ms; DfuAttributes attributes; DfuQuirks quirks; + GPtrArray *sectors; /* of DfuSector */ + GHashTable *sectors_erased; /* of DfuSector:1 */ } DfuTargetPrivate; G_DEFINE_TYPE_WITH_PRIVATE (DfuTarget, dfu_target, G_TYPE_OBJECT) @@ -111,6 +121,8 @@ dfu_target_init (DfuTarget *target) priv->status = DFU_STATUS_OK; priv->timeout_ms = 500; priv->transfer_size = 64; + 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); } /** @@ -123,12 +135,255 @@ dfu_target_finalize (GObject *object) DfuTargetPrivate *priv = GET_PRIVATE (target); g_free (priv->iface_alt_setting_name); + g_ptr_array_unref (priv->sectors); + g_hash_table_unref (priv->sectors_erased); if (priv->device != NULL) g_object_unref (priv->device); G_OBJECT_CLASS (dfu_target_parent_class)->finalize (object); } +/** + * dfu_target_sectors_to_string: + **/ +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); +} + +/** + * dfu_target_get_sector_for_addr: + * + * Returns: the sector that should be used for a specific address, or %NULL + **/ +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; +} + +/** + * dfu_target_parse_sector: + * + * Parse the DfuSe sector format according to UM0424 + **/ +static gboolean +dfu_target_parse_sector (DfuTarget *target, + const gchar *dfuse_sector_id, + guint32 addr, + guint zone, + guint 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; + } + + /* optional space */ + if (tmp[0] == ' ') + tmp++; + + /* get multiplier */ + switch (tmp[0]) { + case 'B': /* byte */ + 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, + sector_size, + (nr_sectors * sector_size)- addr_offset, + zone, + number, + cap); + g_ptr_array_add (priv->sectors, sector); + addr_offset += dfu_sector_get_size (sector); + } + return TRUE; +} + +/** + * dfu_target_parse_sectors: (skip) + * + * Parse the DfuSe format according to UM0424 + **/ +gboolean +dfu_target_parse_sectors (DfuTarget *target, const gchar *alt_name, GError **error) +{ + DfuTargetPrivate *priv = GET_PRIVATE (target); + guint i; + guint j; + g_autofree gchar *str_debug = NULL; + g_auto(GStrv) zones = NULL; + + /* not set */ + if (alt_name == NULL) + return TRUE; + + /* 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_debug ("DfuSe nice alt-name: %s", g_strchomp (zones[0] + 1)); + for (i = 1; zones[i] != NULL; i += 2) { + guint64 addr; + g_auto(GStrv) sectors = NULL; + + /* parse address */ + if (!g_str_has_prefix (zones[i], "0x")) + return FALSE; + addr = g_ascii_strtoull (zones[i] + 2, NULL, 16); + if (addr > G_MAXUINT32) + return FALSE; + + /* 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 (j = 0; sectors[j] != NULL; j++) { + if (!dfu_target_parse_sector (target, + sectors[j], + addr, + (i - 1) / 2, j, + error)) + return FALSE; + } + } + + /* success */ + str_debug = dfu_target_sectors_to_string (target); + g_debug ("%s", str_debug); + return TRUE; +} + typedef struct __attribute__((packed)) { guint8 bLength; guint8 bDescriptorType; @@ -313,6 +568,24 @@ _dfu_target_new (DfuDevice *device, GUsbInterface *iface) 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_get_mode: * @target: a #GUsbDevice @@ -438,6 +711,96 @@ dfu_target_set_transfer_size (DfuTarget *target, guint16 transfer_size) priv->transfer_size = transfer_size; } +/** + * dfu_target_error_fixup: + **/ +static void +dfu_target_error_fixup (DfuTarget *target, + GCancellable *cancellable, + GError **error) +{ + DfuTargetPrivate *priv = GET_PRIVATE (target); + + /* sad panda */ + if (error == NULL) + return; + + /* not the right error to query */ + if (!g_error_matches (*error, + G_USB_DEVICE_ERROR, + G_USB_DEVICE_ERROR_NOT_SUPPORTED)) + return; + + /* get the status */ + if (!dfu_target_refresh (target, cancellable, NULL)) + return; + + /* not in an error state */ + if (priv->state != DFU_STATE_DFU_ERROR) + return; + + /* prefix the error */ + switch (priv->status) { + case DFU_STATUS_OK: + /* ignore */ + break; + case DFU_STATUS_ERR_VENDOR: + g_prefix_error (error, "read protection is active: "); + break; + default: + g_prefix_error (error, "[%s,%s]: ", + dfu_state_to_string (priv->state), + dfu_status_to_string (priv->status)); + break; + } +} + +/** + * dfu_target_check_status: + **/ +static gboolean +dfu_target_check_status (DfuTarget *target, + GCancellable *cancellable, + GError **error) +{ + DfuTargetPrivate *priv = GET_PRIVATE (target); + + /* get the status */ + if (!dfu_target_refresh (target, cancellable, error)) + return FALSE; + + /* not in an error state */ + if (priv->state != DFU_STATE_DFU_ERROR) + return TRUE; + + /* read protection */ + if (priv->dfuse_supported) { + if (priv->status == DFU_STATUS_ERR_VENDOR) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "failed, read protection is active"); + return FALSE; + } + if (priv->status == DFU_STATUS_ERR_TARGET) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "failed, address is wrong or unsupported"); + return FALSE; + } + } + + /* prefix the error */ + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "failed, state:%s status:%s]: ", + dfu_state_to_string (priv->state), + dfu_status_to_string (priv->status)); + return FALSE; +} + /** * dfu_target_open: * @target: a #DfuTarget @@ -529,6 +892,26 @@ dfu_target_open (DfuTarget *target, DfuTargetOpenFlags flags, } } + /* parse the DfuSe format according to UM0424 */ + if (!dfu_target_parse_sectors (target, + priv->iface_alt_setting_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 descripton, so adding dummy"); + g_ptr_array_add (priv->sectors, sector); + } + return TRUE; } @@ -672,6 +1055,8 @@ dfu_target_detach (DfuTarget *target, GCancellable *cancellable, GError **error) priv->timeout_ms, cancellable, &error_local)) { + /* refresh the error code */ + dfu_target_error_fixup (target, cancellable, &error_local); g_set_error (error, DFU_ERROR, DFU_ERROR_NOT_SUPPORTED, @@ -721,6 +1106,8 @@ dfu_target_abort (DfuTarget *target, GCancellable *cancellable, GError **error) priv->timeout_ms, cancellable, &error_local)) { + /* refresh the error code */ + dfu_target_error_fixup (target, cancellable, &error_local); g_set_error (error, DFU_ERROR, DFU_ERROR_NOT_SUPPORTED, @@ -728,6 +1115,7 @@ dfu_target_abort (DfuTarget *target, GCancellable *cancellable, GError **error) error_local->message); return FALSE; } + return TRUE; } @@ -740,7 +1128,7 @@ dfu_target_abort (DfuTarget *target, GCancellable *cancellable, GError **error) * * Updates the target with new interface data. This only needs to be * done after the device has been reset. - * + * * Returns: %TRUE for success **/ gboolean @@ -809,6 +1197,8 @@ dfu_target_clear_status (DfuTarget *target, GCancellable *cancellable, GError ** priv->timeout_ms, cancellable, &error_local)) { + /* refresh the error code */ + dfu_target_error_fixup (target, cancellable, &error_local); g_set_error (error, DFU_ERROR, DFU_ERROR_NOT_SUPPORTED, @@ -819,6 +1209,240 @@ dfu_target_clear_status (DfuTarget *target, GCancellable *cancellable, GError ** return TRUE; } +/** + * dfu_target_download_chunk: + **/ +static gboolean +dfu_target_download_chunk (DfuTarget *target, guint8 index, GBytes *bytes, + GCancellable *cancellable, GError **error) +{ + DfuTargetPrivate *priv = GET_PRIVATE (target); + g_autoptr(GError) error_local = NULL; + gsize actual_length; + + if (!g_usb_device_control_transfer (_dfu_device_get_usb_dev (priv->device), + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_CLASS, + G_USB_DEVICE_RECIPIENT_INTERFACE, + DFU_REQUEST_DNLOAD, + index, + priv->iface_number, + (guint8 *) g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + &actual_length, + priv->timeout_ms, + cancellable, + &error_local)) { + /* refresh the error code */ + dfu_target_error_fixup (target, 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 (!priv->dfuse_supported) { + 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)) + 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 (!priv->dfuse_supported) { + 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)) + 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 (!priv->dfuse_supported) { + 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)) + 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 (!priv->dfuse_supported) { + 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)) + return FALSE; + + /* for ST devices, the action only occurs when we do GetStatus */ + return dfu_target_check_status (target, cancellable, error); +} + +#endif + /** * dfu_target_upload_chunk: **/ @@ -844,84 +1468,105 @@ dfu_target_upload_chunk (DfuTarget *target, guint8 index, priv->timeout_ms, cancellable, &error_local)) { + /* refresh the error code */ + dfu_target_error_fixup (target, cancellable, &error_local); g_set_error (error, DFU_ERROR, DFU_ERROR_NOT_SUPPORTED, - "cannot clear status on the target: %s", + "cannot upload data: %s", error_local->message); return NULL; } + + /* for ST devices, the action only occurs when we do GetStatus */ + if (!dfu_target_check_status (target, cancellable, error)) + return FALSE; + return g_bytes_new_take (buf, actual_length); } /** - * dfu_target_upload: - * @target: a #DfuTarget - * @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY - * @expected_size: the expected size of the firmware, or 0 for unknown - * @cancellable: a #GCancellable, or %NULL - * @progress_cb: a #GFileProgressCallback, or %NULL - * @progress_cb_data: user data to pass to @progress_cb - * @error: a #GError, or %NULL - * - * Uploads firmware from the target to the host. - * - * Return value: (transfer full): the uploaded image, or %NULL for error - * - * Since: 0.5.4 + * dfu_target_upload_element: **/ -DfuImage * -dfu_target_upload (DfuTarget *target, - gsize expected_size, - DfuTargetTransferFlags flags, - GCancellable *cancellable, - DfuProgressCallback progress_cb, - gpointer progress_cb_data, - GError **error) +static DfuElement * +dfu_target_upload_element (DfuTarget *target, + guint32 address, + gsize expected_size, + GCancellable *cancellable, + DfuProgressCallback progress_cb, + gpointer progress_cb_data, + GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); - DfuImage *image = NULL; + DfuSector *sector; + DfuElement *element = NULL; GBytes *chunk_tmp; gsize chunk_size; gsize offset = 0; gsize total_size = 0; guint8 *buffer; + guint32 last_sector_id = G_MAXUINT; + guint dfuse_sector_offset = 0; guint i; - g_autoptr(DfuElement) element = NULL; g_autoptr(GBytes) contents = NULL; g_autoptr(GPtrArray) chunks = NULL; - g_return_val_if_fail (DFU_IS_TARGET (target), NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - /* can the target do this? */ - if (!dfu_target_can_upload (target)) { - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_NOT_SUPPORTED, - "target cannot do uploading"); - return NULL; - } - - /* net yet... */ + /* ST uses wBlockNum=0 for DfuSe commands and wBlockNum=1 is reserved */ if (priv->dfuse_supported) { - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_NOT_SUPPORTED, - "DfuSe support not complete"); - return NULL; + offset += address; + dfuse_sector_offset = 2; } /* get all the chunks from the hardware */ chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); for (i = 0; i < 0xffff; i++) { - chunk_tmp = dfu_target_upload_chunk (target, i, cancellable, error); + + /* for DfuSe devices we need to handle the address manually */ + if (priv->dfuse_supported) { + + /* 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_READABLE)) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INVALID_DEVICE, + "memory sector at 0x%04x is not readble", + (guint) offset); + return FALSE; + } + + /* 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); + } + } + + /* read chunk of data */ + chunk_tmp = dfu_target_upload_chunk (target, + i + dfuse_sector_offset, + cancellable, + error); if (chunk_tmp == NULL) return NULL; /* keep a sum of all the chunks */ chunk_size = g_bytes_get_size (chunk_tmp); total_size += chunk_size; + offset += chunk_size; /* add to array */ g_debug ("got #%04x chunk of size %li", i, chunk_size); @@ -952,6 +1597,109 @@ dfu_target_upload (DfuTarget *target, } } + /* stitch them all together */ + offset = 0; + buffer = g_malloc0 (total_size); + for (i = 0; i < chunks->len; i++) { + const guint8 *chunk_data; + chunk_tmp = g_ptr_array_index (chunks, i); + chunk_data = g_bytes_get_data (chunk_tmp, &chunk_size); + memcpy (buffer + offset, chunk_data, chunk_size); + offset += chunk_size; + } + + /* create new image */ + contents = g_bytes_new_take (buffer, total_size); + element = dfu_element_new (); + dfu_element_set_contents (element, contents); + return element; +} + +/** + * dfu_target_upload: + * @target: a #DfuTarget + * @flags: flags to use, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY + * @cancellable: a #GCancellable, or %NULL + * @progress_cb: a #GFileProgressCallback, or %NULL + * @progress_cb_data: user data to pass to @progress_cb + * @error: a #GError, or %NULL + * + * 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, + DfuProgressCallback progress_cb, + gpointer progress_cb_data, + 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); + + /* can the target do this? */ + if (!dfu_target_can_upload (target)) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "target cannot do uploading"); + return NULL; + } + + /* 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; + } + + /* create a new image */ + image = dfu_image_new (); + dfu_image_set_name (image, priv->iface_alt_setting_name); + dfu_image_set_alt_setting (image, priv->iface_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, + progress_cb, + progress_cb_data, + 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); + } + /* do host reset */ if ((flags & DFU_TARGET_TRANSFER_FLAG_HOST_RESET) > 0 || (flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) > 0) { @@ -966,61 +1714,8 @@ dfu_target_upload (DfuTarget *target, return NULL; } - /* stitch them all together */ - buffer = g_malloc0 (total_size); - for (i = 0; i < chunks->len; i++) { - const guint8 *chunk_data; - chunk_tmp = g_ptr_array_index (chunks, i); - chunk_data = g_bytes_get_data (chunk_tmp, &chunk_size); - memcpy (buffer + offset, chunk_data, chunk_size); - offset += chunk_size; - } - - /* create new image */ - contents = g_bytes_new_take (buffer, total_size); - image = dfu_image_new (); - dfu_image_set_name (image, priv->iface_alt_setting_name); - dfu_image_set_alt_setting (image, priv->iface_alt_setting); - element = dfu_element_new (); - dfu_element_set_contents (element, contents); - dfu_image_add_element (image, element); - return image; -} - -/** - * dfu_target_download_chunk: - **/ -static gboolean -dfu_target_download_chunk (DfuTarget *target, guint8 index, GBytes *bytes, - GCancellable *cancellable, GError **error) -{ - DfuTargetPrivate *priv = GET_PRIVATE (target); - g_autoptr(GError) error_local = NULL; - gsize actual_length; - - if (!g_usb_device_control_transfer (_dfu_device_get_usb_dev (priv->device), - G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, - G_USB_DEVICE_REQUEST_TYPE_CLASS, - G_USB_DEVICE_RECIPIENT_INTERFACE, - DFU_REQUEST_DNLOAD, - index, - priv->iface_number, - (guint8 *) g_bytes_get_data (bytes, NULL), - g_bytes_get_size (bytes), - &actual_length, - priv->timeout_ms, - cancellable, - &error_local)) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_NOT_SUPPORTED, - "cannot download data to the target (state: %s): %s", - dfu_state_to_string (priv->state), - error_local->message); - return FALSE; - } - g_assert (actual_length == g_bytes_get_size (bytes)); - return TRUE; + /* success */ + return g_object_ref (image); } /** @@ -1055,32 +1750,32 @@ _g_bytes_compare_verbose (GBytes *bytes1, GBytes *bytes2) } /** - * dfu_target_download_bytes: + * dfu_target_download_element: **/ static gboolean -dfu_target_download_bytes (DfuTarget *target, GBytes *bytes, - DfuTargetTransferFlags flags, - GCancellable *cancellable, - DfuProgressCallback progress_cb, - gpointer progress_cb_data, - GError **error) +dfu_target_download_element (DfuTarget *target, + DfuElement *element, + DfuTargetTransferFlags flags, + GCancellable *cancellable, + DfuProgressCallback progress_cb, + gpointer progress_cb_data, + GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); - DfuElement *element; + DfuSector *sector; + GBytes *bytes; guint i; guint nr_chunks; + guint dfuse_sector_offset = 0; + guint last_sector_id = G_MAXUINT; g_autoptr(GError) error_local = NULL; - /* can the target do this? */ - if (!dfu_target_can_download (target)) { - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_NOT_SUPPORTED, - "target cannot do downloading"); - return FALSE; - } + /* ST uses wBlockNum=0 for DfuSe commands and wBlockNum=1 is reserved */ + if (priv->dfuse_supported) + 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) priv->transfer_size); if (nr_chunks == 0) { @@ -1095,8 +1790,59 @@ dfu_target_download_bytes (DfuTarget *target, GBytes *bytes, gsize offset; g_autoptr(GBytes) bytes_tmp = NULL; - /* we have to write one final zero-sized chunk for EOF */ + /* caclulate the offset into the element data */ offset = i * priv->transfer_size; + + /* for DfuSe devices we need to handle the erase and setting + * the address manually */ + if (priv->dfuse_supported) { + + /* 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 > priv->transfer_size) @@ -1107,13 +1853,12 @@ dfu_target_download_bytes (DfuTarget *target, GBytes *bytes, } g_debug ("writing #%04x chunk of size %li", i, g_bytes_get_size (bytes_tmp)); - if (!dfu_target_download_chunk (target, i, bytes_tmp, cancellable, error)) { - if (dfu_target_refresh (target, cancellable, NULL)) { - g_prefix_error (error, "Device status %s: ", - dfu_status_to_string (priv->status)); - } + if (!dfu_target_download_chunk (target, + i + dfuse_sector_offset, + bytes_tmp, + cancellable, + error)) return FALSE; - } /* update UI */ if (progress_cb != NULL) { @@ -1134,18 +1879,17 @@ dfu_target_download_bytes (DfuTarget *target, GBytes *bytes, /* verify */ if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY) { GBytes *bytes_tmp; - g_autoptr(DfuImage) image_tmp = NULL; - image_tmp = dfu_target_upload (target, - g_bytes_get_size (bytes), - DFU_TARGET_TRANSFER_FLAG_NONE, - cancellable, - progress_cb, - progress_cb_data, - error); - if (image_tmp == NULL) + g_autoptr(DfuElement) element_tmp = NULL; + element_tmp = dfu_target_upload_element (target, + dfu_element_get_address (element), + g_bytes_get_size (bytes), + cancellable, + progress_cb, + progress_cb_data, + error); + if (element_tmp == NULL) return FALSE; - element = dfu_image_get_element (image_tmp, 0); - bytes_tmp = dfu_element_get_contents (element); + 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); @@ -1158,20 +1902,6 @@ dfu_target_download_bytes (DfuTarget *target, GBytes *bytes, } } - /* do a host reset */ - if ((flags & DFU_TARGET_TRANSFER_FLAG_HOST_RESET) > 0 || - (flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) > 0) { - if (!dfu_device_reset (priv->device, error)) - return FALSE; - } - - /* boot to runtime */ - if (flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) { - g_debug ("booting to runtime to set auto-boot"); - if (!dfu_device_wait_for_replug (priv->device, 2000, cancellable, error)) - return FALSE; - } - return TRUE; } @@ -1201,36 +1931,111 @@ dfu_target_download (DfuTarget *target, DfuImage *image, GError **error) { DfuTargetPrivate *priv = GET_PRIVATE (target); - GBytes *contents; 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); - /* net yet... */ - if (priv->dfuse_supported) { + /* can the target do this? */ + if (!dfu_target_can_download (target)) { g_set_error_literal (error, DFU_ERROR, DFU_ERROR_NOT_SUPPORTED, - "DfuSe support not complete"); + "target cannot do downloading"); return FALSE; } - /* get data */ - element = dfu_image_get_element (image, 0); - if (element == NULL) { + /* mark these as all erased */ + if (priv->dfuse_supported) + 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; } - contents = dfu_element_get_contents (element); - return dfu_target_download_bytes (target, contents, flags, cancellable, - progress_cb, progress_cb_data, error); + 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, + progress_cb, + progress_cb_data, + error); + if (!ret) + return FALSE; + } + + /* do a host reset */ + if ((flags & DFU_TARGET_TRANSFER_FLAG_HOST_RESET) > 0 || + (flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) > 0) { + if (!dfu_device_reset (priv->device, error)) + return FALSE; + } + + /* boot to runtime */ + if (flags & DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME) { + g_debug ("booting to runtime to set auto-boot"); + if (!dfu_device_wait_for_replug (priv->device, 2000, cancellable, error)) + return FALSE; + } + + /* success */ + return TRUE; } +#if 0 +/** + * dfu_target_get_commands: + **/ +static gboolean +dfu_target_get_commands (DfuTarget *target, + GCancellable *cancellable, + GError **error) +{ + GBytes *data_in; + GBytes *data_out; + guint8 buf[1]; + + /* invalid */ + if (!priv->dfuse_supported) { + 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)) + 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_set_timeout: * @target: a #DfuTarget diff --git a/libdfu/dfu-target.h b/libdfu/dfu-target.h index a6c53a333..6f7ecde2a 100644 --- a/libdfu/dfu-target.h +++ b/libdfu/dfu-target.h @@ -90,6 +90,7 @@ gboolean dfu_target_close (DfuTarget *target, DfuMode dfu_target_get_mode (DfuTarget *target); DfuState dfu_target_get_state (DfuTarget *target); DfuStatus dfu_target_get_status (DfuTarget *target); +GPtrArray *dfu_target_get_sectors (DfuTarget *target); gboolean dfu_target_can_upload (DfuTarget *target); gboolean dfu_target_can_download (DfuTarget *target); gboolean dfu_target_refresh (DfuTarget *target, @@ -105,7 +106,6 @@ gboolean dfu_target_clear_status (DfuTarget *target, GCancellable *cancellable, GError **error); DfuImage *dfu_target_upload (DfuTarget *target, - gsize expected_size, DfuTargetTransferFlags flags, GCancellable *cancellable, DfuProgressCallback progress_cb, diff --git a/libdfu/dfu-tool.c b/libdfu/dfu-tool.c index c3a1c01a0..2bdb8d077 100644 --- a/libdfu/dfu-tool.c +++ b/libdfu/dfu-tool.c @@ -861,7 +861,7 @@ dfu_tool_upload_target (DfuToolPrivate *priv, gchar **values, GError **error) helper.last_state = DFU_STATE_DFU_ERROR; helper.marks_total = 30; helper.marks_shown = 0; - image = dfu_target_upload (target, 0, flags, NULL, + image = dfu_target_upload (target, flags, NULL, fu_tool_transfer_progress_cb, &helper, error); if (image == NULL) @@ -928,7 +928,7 @@ dfu_tool_upload (DfuToolPrivate *priv, gchar **values, GError **error) helper.last_state = DFU_STATE_DFU_ERROR; helper.marks_total = 30; helper.marks_shown = 0; - firmware = dfu_device_upload (device, 0, flags, NULL, + firmware = dfu_device_upload (device, flags, NULL, fu_tool_transfer_progress_cb, &helper, error); if (firmware == NULL) @@ -1181,6 +1181,7 @@ dfu_tool_list_target (DfuTarget *target) { const gchar *tmp; gboolean ret; + guint i; g_autofree gchar *alt_id = NULL; g_autoptr(GError) error_local = NULL; @@ -1199,6 +1200,7 @@ dfu_tool_list_target (DfuTarget *target) DFU_TARGET_OPEN_FLAG_NONE, NULL, &error_local); if (ret) { + GPtrArray *sectors; tmp = dfu_status_to_string (dfu_target_get_status (target)); /* TRANSLATORS: device status, e.g. "OK" */ dfu_tool_print_indent (_("Status"), tmp, 2); @@ -1206,6 +1208,19 @@ dfu_tool_list_target (DfuTarget *target) tmp = dfu_state_to_string (dfu_target_get_state (target)); /* TRANSLATORS: device state, i.e. appIDLE */ dfu_tool_print_indent (_("State"), tmp, 2); + + /* print sector information */ + sectors = dfu_target_get_sectors (target); + for (i = 0; i < sectors->len; i++) { + DfuSector *sector; + g_autofree gchar *msg = NULL; + g_autofree gchar *title = NULL; + sector = g_ptr_array_index (sectors, i); + msg = dfu_sector_to_string (sector); + /* TRANSLATORS: these are areas of memory on the chip */ + title = g_strdup_printf ("%s 0x%02x", _("Region"), i); + dfu_tool_print_indent (title, msg, 3); + } } else { if (g_error_matches (error_local, DFU_ERROR, @@ -1217,6 +1232,7 @@ dfu_tool_list_target (DfuTarget *target) dfu_tool_print_indent (_("Status"), error_local->message, 2); } } + dfu_target_close (target, NULL); } diff --git a/libdfu/dfu.h b/libdfu/dfu.h index c0bca8825..1d1a92377 100644 --- a/libdfu/dfu.h +++ b/libdfu/dfu.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #undef __DFU_H_INSIDE__ diff --git a/src/fu-provider-usb.c b/src/fu-provider-usb.c index eb179c647..49fde70a3 100644 --- a/src/fu-provider-usb.c +++ b/src/fu-provider-usb.c @@ -417,7 +417,6 @@ fu_provider_usb_verify (FuProvider *provider, /* get data from hardware */ dfu_firmware = dfu_device_upload (dfu_device, - 0, DFU_TARGET_TRANSFER_FLAG_DETACH | DFU_TARGET_TRANSFER_FLAG_BOOT_RUNTIME, NULL,