mirror of
https://git.proxmox.com/git/fwupd
synced 2025-07-04 21:07:34 +00:00

The DFU specification specifies that only one of the DFU interfaces has to export a functional descriptor; I assumed they all had to. Adding support for this kind of device rapidly turned into a massive restructure and it was all too complicated anyway. Reorganise the code so that we can support these kinds of devices and clean up the API so it's sane and easy to use. This also allows us to generate the GObject introspection GIR and to also install libdfu as a shared library. If you've got any comments about the API, please shout now as when 6.0 is released it will become API and ABI stable.
1082 lines
28 KiB
C
1082 lines
28 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* Licensed under the GNU Lesser General Public License Version 2.1
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/**
|
|
* SECTION:dfu-firmware
|
|
* @short_description: Object representing a DFU or DfuSe firmware file
|
|
*
|
|
* This object allows reading and writing firmware files either in
|
|
* raw, DFU or DfuSe formats.
|
|
*
|
|
* A #DfuFirmware can be made up of several #DfuImages, although
|
|
* typically there is only one.
|
|
*
|
|
* See also: #DfuImage
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "dfu-common.h"
|
|
#include "dfu-error.h"
|
|
#include "dfu-firmware.h"
|
|
#include "dfu-image-private.h"
|
|
|
|
static void dfu_firmware_finalize (GObject *object);
|
|
|
|
/**
|
|
* DfuFirmwarePrivate:
|
|
*
|
|
* Private #DfuFirmware data
|
|
**/
|
|
typedef struct {
|
|
GPtrArray *images;
|
|
guint16 vid;
|
|
guint16 pid;
|
|
guint16 release;
|
|
DfuFirmwareFormat format;
|
|
} DfuFirmwarePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (DfuFirmware, dfu_firmware, G_TYPE_OBJECT)
|
|
#define GET_PRIVATE(o) (dfu_firmware_get_instance_private (o))
|
|
|
|
/**
|
|
* dfu_firmware_class_init:
|
|
**/
|
|
static void
|
|
dfu_firmware_class_init (DfuFirmwareClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = dfu_firmware_finalize;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_init:
|
|
**/
|
|
static void
|
|
dfu_firmware_init (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
priv->vid = 0xffff;
|
|
priv->pid = 0xffff;
|
|
priv->release = 0xffff;
|
|
priv->images = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_finalize:
|
|
**/
|
|
static void
|
|
dfu_firmware_finalize (GObject *object)
|
|
{
|
|
DfuFirmware *firmware = DFU_FIRMWARE (object);
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
|
|
g_ptr_array_unref (priv->images);
|
|
|
|
G_OBJECT_CLASS (dfu_firmware_parent_class)->finalize (object);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_new:
|
|
*
|
|
* Creates a new DFU firmware object.
|
|
*
|
|
* Return value: a new #DfuFirmware
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuFirmware *
|
|
dfu_firmware_new (void)
|
|
{
|
|
DfuFirmware *firmware;
|
|
firmware = g_object_new (DFU_TYPE_FIRMWARE, NULL);
|
|
return firmware;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_image:
|
|
* @firmware: a #DfuFirmware
|
|
* @alt_setting: an alternative setting, typically 0x00
|
|
*
|
|
* Gets an image from the firmware file.
|
|
*
|
|
* Return value: (transfer none): a #DfuImage, or %NULL for not found
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuImage *
|
|
dfu_firmware_get_image (DfuFirmware *firmware, guint8 alt_setting)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
DfuImage *im;
|
|
guint i;
|
|
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL);
|
|
|
|
/* find correct image */
|
|
for (i = 0; i < priv->images->len; i++) {
|
|
im = g_ptr_array_index (priv->images, i);
|
|
if (dfu_image_get_alt_setting (im) == alt_setting)
|
|
return im;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_image_default:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Gets the default image from the firmware file.
|
|
*
|
|
* Return value: (transfer none): a #DfuImage, or %NULL for not found
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuImage *
|
|
dfu_firmware_get_image_default (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL);
|
|
if (priv->images->len == 0)
|
|
return NULL;
|
|
return g_ptr_array_index (priv->images, 0);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_images:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Gets all the images contained in this firmware file.
|
|
*
|
|
* Return value: (transfer none) (element-type DfuImage): list of images
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
GPtrArray *
|
|
dfu_firmware_get_images (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL);
|
|
return priv->images;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_size:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Gets the size of all the images in the firmware.
|
|
*
|
|
* This only returns actual data that would be sent to the device and
|
|
* does not include any padding.
|
|
*
|
|
* Return value: a integer value, or 0 if there are no images.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint32
|
|
dfu_firmware_get_size (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
guint32 length = 0;
|
|
guint i;
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0);
|
|
for (i = 0; i < priv->images->len; i++) {
|
|
DfuImage *image = g_ptr_array_index (priv->images, i);
|
|
length += dfu_image_get_size (image);
|
|
}
|
|
return length;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_add_image:
|
|
* @firmware: a #DfuFirmware
|
|
* @image: a #DfuImage
|
|
*
|
|
* Adds an image to the list of images.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_firmware_add_image (DfuFirmware *firmware, DfuImage *image)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_if_fail (DFU_IS_FIRMWARE (firmware));
|
|
g_return_if_fail (DFU_IS_IMAGE (image));
|
|
g_ptr_array_add (priv->images, g_object_ref (image));
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_vid:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Gets the vendor ID.
|
|
*
|
|
* Return value: a vendor ID, or 0xffff for unset
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint16
|
|
dfu_firmware_get_vid (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0xffff);
|
|
return priv->vid;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_pid:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Gets the product ID.
|
|
*
|
|
* Return value: a product ID, or 0xffff for unset
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint16
|
|
dfu_firmware_get_pid (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0xffff);
|
|
return priv->pid;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_release:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Gets the device ID.
|
|
*
|
|
* Return value: a device ID, or 0xffff for unset
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint16
|
|
dfu_firmware_get_release (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0xffff);
|
|
return priv->release;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_format:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Gets the DFU version.
|
|
*
|
|
* Return value: a version, or 0x0 for unset
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
guint16
|
|
dfu_firmware_get_format (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0xffff);
|
|
return priv->format;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_set_vid:
|
|
* @firmware: a #DfuFirmware
|
|
* @vid: vendor ID, or 0xffff for unset
|
|
*
|
|
* Sets the vendor ID.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_firmware_set_vid (DfuFirmware *firmware, guint16 vid)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_if_fail (DFU_IS_FIRMWARE (firmware));
|
|
priv->vid = vid;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_set_pid:
|
|
* @firmware: a #DfuFirmware
|
|
* @pid: product ID, or 0xffff for unset
|
|
*
|
|
* Sets the product ID.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_firmware_set_pid (DfuFirmware *firmware, guint16 pid)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_if_fail (DFU_IS_FIRMWARE (firmware));
|
|
priv->pid = pid;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_set_release:
|
|
* @firmware: a #DfuFirmware
|
|
* @release: device ID, or 0xffff for unset
|
|
*
|
|
* Sets the device ID.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_firmware_set_release (DfuFirmware *firmware, guint16 release)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_if_fail (DFU_IS_FIRMWARE (firmware));
|
|
priv->release = release;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_set_format:
|
|
* @firmware: a #DfuFirmware
|
|
* @format: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_DFUSE
|
|
*
|
|
* Sets the DFU version in BCD format.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_firmware_set_format (DfuFirmware *firmware, DfuFirmwareFormat format)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_if_fail (DFU_IS_FIRMWARE (firmware));
|
|
priv->format = format;
|
|
}
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint16 release;
|
|
guint16 pid;
|
|
guint16 vid;
|
|
guint16 ver;
|
|
guint8 sig[3];
|
|
guint8 len;
|
|
guint32 crc;
|
|
} DfuFirmwareFooter;
|
|
|
|
static guint32 _crctbl[] = {
|
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
|
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
|
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
|
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
|
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
|
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
|
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
|
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
|
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
|
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
|
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
|
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
|
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
|
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
|
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
|
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
|
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
|
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
|
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
|
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
|
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
|
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
|
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
|
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
|
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
|
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
|
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
|
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
|
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
|
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
|
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
|
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
|
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
|
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
|
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
|
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
|
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
|
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
|
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
|
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
|
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
|
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
|
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d };
|
|
|
|
/**
|
|
* dfu_firmware_generate_crc32:
|
|
**/
|
|
static guint32
|
|
dfu_firmware_generate_crc32 (const guint8 *data, gsize length)
|
|
{
|
|
guint i;
|
|
guint32 accum = 0xffffffff;
|
|
for (i = 0; i < length; i++)
|
|
accum = _crctbl[(accum^data[i]) & 0xff] ^ (accum >> 8);
|
|
return accum;
|
|
}
|
|
|
|
|
|
/**
|
|
* dfu_firmware_inhx32_parse_uint8:
|
|
**/
|
|
static guint8
|
|
dfu_firmware_inhx32_parse_uint8 (const gchar *data, guint pos)
|
|
{
|
|
gchar buffer[3];
|
|
buffer[0] = data[pos+0];
|
|
buffer[1] = data[pos+1];
|
|
buffer[2] = '\0';
|
|
return g_ascii_strtoull (buffer, NULL, 16);
|
|
}
|
|
|
|
#define DFU_INHX32_RECORD_TYPE_DATA 0
|
|
#define DFU_INHX32_RECORD_TYPE_EOF 1
|
|
#define DFU_INHX32_RECORD_TYPE_EXTENDED 4
|
|
|
|
/**
|
|
* dfu_firmware_parse_inhx32:
|
|
**/
|
|
static GBytes *
|
|
dfu_firmware_parse_inhx32 (GBytes *in, GError **error)
|
|
{
|
|
gchar *ptr;
|
|
gint checksum;
|
|
gint end;
|
|
gint i;
|
|
gint offset = 0;
|
|
guint8 data_tmp;
|
|
guint addr32 = 0;
|
|
guint addr32_last = 0;
|
|
guint addr_high = 0;
|
|
guint addr_low = 0;
|
|
guint j;
|
|
guint len_tmp;
|
|
guint type;
|
|
g_autoptr(GString) string = NULL;
|
|
const gchar *in_buffer = g_bytes_get_data (in, NULL);
|
|
|
|
g_return_val_if_fail (in != NULL, NULL);
|
|
|
|
/* only if set */
|
|
string = g_string_new ("");
|
|
while (TRUE) {
|
|
|
|
/* length, 16-bit address, type */
|
|
if (sscanf (&in_buffer[offset], ":%02x%04x%02x",
|
|
&len_tmp, &addr_low, &type) != 3) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"invalid inhx32 syntax");
|
|
return NULL;
|
|
}
|
|
|
|
/* position of checksum */
|
|
end = offset + 9 + len_tmp * 2;
|
|
|
|
/* verify checksum */
|
|
checksum = 0;
|
|
for (i = offset + 1; i < end; i += 2) {
|
|
data_tmp = dfu_firmware_inhx32_parse_uint8 (in_buffer, i);
|
|
checksum = (checksum + (0x100 - data_tmp)) & 0xff;
|
|
}
|
|
if (dfu_firmware_inhx32_parse_uint8 (in_buffer, end) != checksum) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"invalid checksum");
|
|
return NULL;
|
|
}
|
|
|
|
/* process different record types */
|
|
switch (type) {
|
|
case DFU_INHX32_RECORD_TYPE_DATA:
|
|
/* if not contiguous with previous record */
|
|
if ((addr_high + addr_low) != addr32)
|
|
addr32 = addr_high + addr_low;
|
|
|
|
/* parse bytes from line */
|
|
for (i = offset + 9; i < end; i += 2) {
|
|
/* any holes in the hex record */
|
|
len_tmp = addr32 - addr32_last;
|
|
if (addr32_last > 0x0 && len_tmp > 1) {
|
|
for (j = 1; j < len_tmp; j++) {
|
|
g_debug ("filling address 0x%04x",
|
|
addr32_last + j);
|
|
/* although 0xff might be clearer,
|
|
* we can't write 0xffff to pic14 */
|
|
g_string_append_c (string, 0x00);
|
|
}
|
|
}
|
|
/* write into buf */
|
|
data_tmp = dfu_firmware_inhx32_parse_uint8 (in_buffer, i);
|
|
g_string_append_c (string, data_tmp);
|
|
g_debug ("writing address 0x%04x", addr32);
|
|
addr32_last = addr32++;
|
|
}
|
|
break;
|
|
case DFU_INHX32_RECORD_TYPE_EOF:
|
|
break;
|
|
case DFU_INHX32_RECORD_TYPE_EXTENDED:
|
|
if (sscanf (&in_buffer[offset+9], "%04x", &addr_high) != 1) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"invalid hex syntax");
|
|
return NULL;
|
|
}
|
|
addr_high <<= 16;
|
|
addr32 = addr_high + addr_low;
|
|
break;
|
|
default:
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"invalid record type");
|
|
return NULL;
|
|
}
|
|
|
|
/* advance to start of next line */
|
|
ptr = strchr (&in_buffer[end+2], ':');
|
|
if (ptr == NULL)
|
|
break;
|
|
offset = ptr - in_buffer;
|
|
}
|
|
|
|
/* save data */
|
|
return g_bytes_new (string->str, string->len);
|
|
}
|
|
|
|
/* DfuSe header */
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 sig[5];
|
|
guint8 ver;
|
|
guint32 image_size;
|
|
guint8 targets;
|
|
} DfuSePrefix;
|
|
|
|
/**
|
|
* dfu_firmware_add_inhx32:
|
|
**/
|
|
static gboolean
|
|
dfu_firmware_add_inhx32 (DfuFirmware *firmware, GBytes *bytes, GError **error)
|
|
{
|
|
g_autoptr(DfuElement) element = NULL;
|
|
g_autoptr(DfuImage) image = NULL;
|
|
g_autoptr(GBytes) contents = NULL;
|
|
|
|
/* parse hex file */
|
|
contents = dfu_firmware_parse_inhx32 (bytes, error);
|
|
if (contents == NULL)
|
|
return FALSE;
|
|
|
|
/* add single image */
|
|
image = dfu_image_new ();
|
|
element = dfu_element_new ();
|
|
dfu_element_set_contents (element, contents);
|
|
dfu_image_add_element (image, element);
|
|
dfu_firmware_add_image (firmware, image);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_add_binary:
|
|
**/
|
|
static gboolean
|
|
dfu_firmware_add_binary (DfuFirmware *firmware, GBytes *bytes, GError **error)
|
|
{
|
|
g_autoptr(DfuElement) element = NULL;
|
|
g_autoptr(DfuImage) image = NULL;
|
|
image = dfu_image_new ();
|
|
element = dfu_element_new ();
|
|
dfu_element_set_contents (element, bytes);
|
|
dfu_image_add_element (image, element);
|
|
dfu_firmware_add_image (firmware, image);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_add_dfuse:
|
|
**/
|
|
static gboolean
|
|
dfu_firmware_add_dfuse (DfuFirmware *firmware, GBytes *bytes, GError **error)
|
|
{
|
|
DfuSePrefix *prefix;
|
|
gsize len;
|
|
guint32 offset = sizeof(DfuSePrefix);
|
|
guint8 *data;
|
|
guint i;
|
|
|
|
/* check the prefix (BE) */
|
|
data = (guint8 *) g_bytes_get_data (bytes, &len);
|
|
prefix = (DfuSePrefix *) data;
|
|
if (memcmp (prefix->sig, "DfuSe", 5) != 0) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"invalid DfuSe prefix");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check the version */
|
|
if (prefix->ver != 0x01) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"invalid DfuSe version, got %02x",
|
|
prefix->ver);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check image size */
|
|
if (GUINT32_FROM_LE (prefix->image_size) != len) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"invalid DfuSe image size, got %u, expected %lu",
|
|
GUINT32_FROM_LE (prefix->image_size),
|
|
len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse the image targets */
|
|
for (i = 0; i < prefix->targets; i++) {
|
|
guint consumed;
|
|
g_autoptr(DfuImage) image = NULL;
|
|
image = dfu_image_from_dfuse (data + offset,
|
|
len - offset,
|
|
&consumed,
|
|
error);
|
|
if (image == NULL)
|
|
return FALSE;
|
|
dfu_firmware_add_image (firmware, image);
|
|
offset += consumed;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_write_data_dfuse:
|
|
**/
|
|
static GBytes *
|
|
dfu_firmware_write_data_dfuse (DfuFirmware *firmware, GError **error)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
DfuSePrefix *prefix;
|
|
guint i;
|
|
guint32 image_size_total = 0;
|
|
guint32 offset = sizeof (DfuSePrefix);
|
|
guint8 *buf;
|
|
g_autoptr(GPtrArray) dfuse_images = NULL;
|
|
|
|
/* get all the image data */
|
|
dfuse_images = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
|
|
for (i = 0; i < priv->images->len; i++) {
|
|
DfuImage *im = g_ptr_array_index (priv->images, i);
|
|
GBytes *contents;
|
|
contents = dfu_image_to_dfuse (im);
|
|
image_size_total += g_bytes_get_size (contents);
|
|
g_ptr_array_add (dfuse_images, contents);
|
|
}
|
|
g_debug ("image_size_total: %i", image_size_total);
|
|
|
|
buf = g_malloc0 (sizeof (DfuSePrefix) + image_size_total);
|
|
|
|
/* DfuSe header */
|
|
prefix = (DfuSePrefix *) buf;
|
|
memcpy (prefix->sig, "DfuSe", 5);
|
|
prefix->ver = 0x01;
|
|
prefix->image_size = offset + image_size_total;
|
|
prefix->targets = priv->images->len;
|
|
|
|
/* copy images */
|
|
for (i = 0; i < dfuse_images->len; i++) {
|
|
GBytes *contents = g_ptr_array_index (dfuse_images, i);
|
|
gsize length;
|
|
const guint8 *data;
|
|
data = g_bytes_get_data (contents, &length);
|
|
memcpy (buf + offset, data, length);
|
|
offset += length;
|
|
}
|
|
|
|
/* return blob */
|
|
return g_bytes_new_take (buf, sizeof (DfuSePrefix) + image_size_total);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_parse_data:
|
|
* @firmware: a #DfuFirmware
|
|
* @bytes: raw firmware data
|
|
* @flags: optional flags, e.g. %DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Parses firmware data which may have an optional DFU suffix.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_firmware_parse_data (DfuFirmware *firmware, GBytes *bytes,
|
|
DfuFirmwareParseFlags flags, GError **error)
|
|
{
|
|
DfuFirmwareFooter *ftr;
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
gsize len;
|
|
guint32 crc_new;
|
|
guint8 *data;
|
|
g_autoptr(GBytes) contents = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), FALSE);
|
|
g_return_val_if_fail (bytes != NULL, FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* sanity check */
|
|
g_assert_cmpint(sizeof(DfuSePrefix), ==, 11);
|
|
|
|
/* set defaults */
|
|
priv->vid = 0xffff;
|
|
priv->pid = 0xffff;
|
|
priv->release = 0xffff;
|
|
|
|
/* this is inhx32 */
|
|
data = (guint8 *) g_bytes_get_data (bytes, &len);
|
|
if (data[0] == ':')
|
|
return dfu_firmware_add_inhx32 (firmware, bytes, error);
|
|
|
|
/* too small to be a DFU file */
|
|
if (len < 16) {
|
|
priv->format = DFU_FIRMWARE_FORMAT_RAW;
|
|
return dfu_firmware_add_binary (firmware, bytes, error);
|
|
}
|
|
|
|
/* check for DFU signature */
|
|
ftr = (DfuFirmwareFooter *) &data[len-sizeof(DfuFirmwareFooter)];
|
|
if (memcmp (ftr->sig, "UFD", 3) != 0) {
|
|
priv->format = DFU_FIRMWARE_FORMAT_RAW;
|
|
return dfu_firmware_add_binary (firmware, bytes, error);
|
|
}
|
|
|
|
/* check version */
|
|
priv->format = GUINT16_FROM_LE (ftr->ver);
|
|
if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_VERSION_TEST) == 0) {
|
|
if (priv->format != DFU_FIRMWARE_FORMAT_DFU_1_0 &&
|
|
priv->format != DFU_FIRMWARE_FORMAT_DFUSE) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"version check failed, got %04x",
|
|
priv->format);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* verify the checksum */
|
|
if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) {
|
|
crc_new = dfu_firmware_generate_crc32 (data, len - 4);
|
|
if (GUINT32_FROM_LE (ftr->crc) != crc_new) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"CRC failed, expected %04x, got %04x",
|
|
crc_new, GUINT32_FROM_LE (ftr->crc));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* set from footer */
|
|
dfu_firmware_set_vid (firmware, GUINT16_FROM_LE (ftr->vid));
|
|
dfu_firmware_set_pid (firmware, GUINT16_FROM_LE (ftr->pid));
|
|
dfu_firmware_set_release (firmware, GUINT16_FROM_LE (ftr->release));
|
|
|
|
/* parse DfuSe prefix */
|
|
contents = g_bytes_new_from_bytes (bytes, 0, len - GUINT16_FROM_LE (ftr->len));
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_DFUSE)
|
|
return dfu_firmware_add_dfuse (firmware, contents, error);
|
|
|
|
/* just copy old-plain DFU file */
|
|
return dfu_firmware_add_binary (firmware, contents, error);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_parse_file:
|
|
* @firmware: a #DfuFirmware
|
|
* @file: a #GFile to load and parse
|
|
* @flags: optional flags, e.g. %DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Parses a DFU firmware, which may contain an optional footer.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_firmware_parse_file (DfuFirmware *firmware, GFile *file,
|
|
DfuFirmwareParseFlags flags,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
gchar *contents = NULL;
|
|
gsize length = 0;
|
|
g_autoptr(GBytes) bytes = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), FALSE);
|
|
g_return_val_if_fail (G_IS_FILE (file), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!g_file_load_contents (file, cancellable, &contents,
|
|
&length, NULL, error))
|
|
return FALSE;
|
|
bytes = g_bytes_new_take (contents, length);
|
|
return dfu_firmware_parse_data (firmware, bytes, flags, error);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_add_footer:
|
|
**/
|
|
static GBytes *
|
|
dfu_firmware_add_footer (DfuFirmware *firmware, GBytes *contents)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
DfuFirmwareFooter *ftr;
|
|
const guint8 *data;
|
|
gsize length = 0;
|
|
guint8 *buf;
|
|
|
|
data = g_bytes_get_data (contents, &length);
|
|
buf = g_malloc0 (length + 0x10);
|
|
memcpy (buf, data, length);
|
|
|
|
/* set up LE footer */
|
|
ftr = (DfuFirmwareFooter *) &buf[length];
|
|
ftr->release = GUINT16_TO_LE (priv->release);
|
|
ftr->pid = GUINT16_TO_LE (priv->pid);
|
|
ftr->vid = GUINT16_TO_LE (priv->vid);
|
|
ftr->ver = GUINT16_TO_LE (priv->format);
|
|
ftr->len = GUINT16_TO_LE (0x10);
|
|
memcpy(ftr->sig, "UFD", 3);
|
|
ftr->crc = dfu_firmware_generate_crc32 (buf, length + 12);
|
|
|
|
/* return all data */
|
|
return g_bytes_new_take (buf, length + 0x10);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_write_data:
|
|
* @firmware: a #DfuFirmware
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Writes DFU data to a data blob with a DFU-specific footer.
|
|
*
|
|
* Return value: (transfer none): firmware data
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
GBytes *
|
|
dfu_firmware_write_data (DfuFirmware *firmware, GError **error)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
DfuImage *image;
|
|
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
/* at least one image */
|
|
if (priv->images == 0) {
|
|
g_set_error_literal (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"no image data to write");
|
|
return NULL;
|
|
}
|
|
|
|
/* DFU only supports one image */
|
|
if (priv->images->len > 1 &&
|
|
priv->format != DFU_FIRMWARE_FORMAT_DFUSE) {
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"only DfuSe format supports multiple images (%i)",
|
|
priv->images->len);
|
|
return NULL;
|
|
}
|
|
|
|
/* raw */
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_RAW) {
|
|
GBytes *contents;
|
|
DfuElement *element;
|
|
image = dfu_firmware_get_image_default (firmware);
|
|
g_assert (image != NULL);
|
|
element = dfu_image_get_element (image, 0);
|
|
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);
|
|
}
|
|
|
|
/* plain-old DFU */
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_DFU_1_0) {
|
|
GBytes *contents;
|
|
DfuElement *element;
|
|
image = dfu_firmware_get_image_default (firmware);
|
|
g_assert (image != NULL);
|
|
element = dfu_image_get_element (image, 0);
|
|
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);
|
|
}
|
|
|
|
/* DfuSe */
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_DFUSE) {
|
|
g_autoptr(GBytes) contents = NULL;
|
|
contents = dfu_firmware_write_data_dfuse (firmware, error);
|
|
if (contents == NULL)
|
|
return NULL;
|
|
return dfu_firmware_add_footer (firmware, contents);
|
|
}
|
|
|
|
/* invalid */
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"invalid format for write (0x%04x)",
|
|
priv->format);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_write_file:
|
|
* @firmware: a #DfuFirmware
|
|
* @file: a #GFile
|
|
* @cancellable: a #GCancellable, or %NULL
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Writes a DFU firmware with the optional footer.
|
|
*
|
|
* Return value: %TRUE for success
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gboolean
|
|
dfu_firmware_write_file (DfuFirmware *firmware, GFile *file,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
const guint8 *data;
|
|
gsize length = 0;
|
|
g_autoptr(GBytes) bytes = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), FALSE);
|
|
g_return_val_if_fail (G_IS_FILE (file), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* get blob */
|
|
bytes = dfu_firmware_write_data (firmware, error);
|
|
if (bytes == NULL)
|
|
return FALSE;
|
|
|
|
/* save to firmware */
|
|
data = g_bytes_get_data (bytes, &length);
|
|
return g_file_replace_contents (file,
|
|
(const gchar *) data,
|
|
length,
|
|
NULL,
|
|
FALSE,
|
|
G_FILE_CREATE_NONE,
|
|
NULL,
|
|
cancellable,
|
|
error);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_to_string:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Returns a string representaiton of the object.
|
|
*
|
|
* Return value: NULL terminated string, or %NULL for invalid
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
gchar *
|
|
dfu_firmware_to_string (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
DfuImage *image;
|
|
GString *str;
|
|
guint i;
|
|
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL);
|
|
|
|
str = g_string_new ("");
|
|
g_string_append_printf (str, "vid: 0x%04x\n", priv->vid);
|
|
g_string_append_printf (str, "pid: 0x%04x\n", priv->pid);
|
|
g_string_append_printf (str, "release: 0x%04x\n", priv->release);
|
|
g_string_append_printf (str, "format: %s [0x%04x]\n",
|
|
dfu_firmware_format_to_string (priv->format),
|
|
priv->format);
|
|
for (i = 0; i < priv->images->len; i++) {
|
|
g_autofree gchar *tmp = NULL;
|
|
image = g_ptr_array_index (priv->images, i);
|
|
tmp = dfu_image_to_string (image);
|
|
g_string_append_printf (str, "= IMAGE %i =\n", i);
|
|
g_string_append_printf (str, "%s\n", tmp);
|
|
}
|
|
|
|
g_string_truncate (str, str->len - 1);
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_format_to_string:
|
|
* @format: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_DFU_1_0
|
|
*
|
|
* Returns a string representaiton of the format.
|
|
*
|
|
* Return value: NULL terminated string, or %NULL for invalid
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
const gchar *
|
|
dfu_firmware_format_to_string (DfuFirmwareFormat format)
|
|
{
|
|
if (format == DFU_FIRMWARE_FORMAT_RAW)
|
|
return "RAW";
|
|
if (format == DFU_FIRMWARE_FORMAT_DFU_1_0)
|
|
return "DFU";
|
|
if (format == DFU_FIRMWARE_FORMAT_DFUSE)
|
|
return "DfuSe";
|
|
return NULL;
|
|
}
|