mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-16 13:54:31 +00:00
966 lines
24 KiB
C
966 lines
24 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 <appstream-glib.h>
|
|
|
|
#include "dfu-common.h"
|
|
#include "dfu-error.h"
|
|
#include "dfu-firmware-private.h"
|
|
#include "dfu-format-elf.h"
|
|
#include "dfu-format-dfu.h"
|
|
#include "dfu-format-ihex.h"
|
|
#include "dfu-format-raw.h"
|
|
#include "dfu-image.h"
|
|
|
|
static void dfu_firmware_finalize (GObject *object);
|
|
|
|
typedef struct {
|
|
GHashTable *metadata;
|
|
GHashTable *symtab;
|
|
GPtrArray *images;
|
|
guint16 vid;
|
|
guint16 pid;
|
|
guint16 release;
|
|
DfuCipherKind cipher_kind;
|
|
DfuFirmwareFormat format;
|
|
} DfuFirmwarePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (DfuFirmware, dfu_firmware, G_TYPE_OBJECT)
|
|
#define GET_PRIVATE(o) (dfu_firmware_get_instance_private (o))
|
|
|
|
static void
|
|
dfu_firmware_class_init (DfuFirmwareClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = dfu_firmware_finalize;
|
|
}
|
|
|
|
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);
|
|
priv->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
priv->symtab = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
}
|
|
|
|
static void
|
|
dfu_firmware_finalize (GObject *object)
|
|
{
|
|
DfuFirmware *firmware = DFU_FIRMWARE (object);
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
|
|
g_ptr_array_unref (priv->images);
|
|
g_hash_table_destroy (priv->metadata);
|
|
g_hash_table_destroy (priv->symtab);
|
|
|
|
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_by_name:
|
|
* @firmware: a #DfuFirmware
|
|
* @name: an alternative setting name
|
|
*
|
|
* 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_by_name (DfuFirmware *firmware, const gchar *name)
|
|
{
|
|
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 (g_strcmp0 (dfu_image_get_name (im), name) == 0)
|
|
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;
|
|
}
|
|
|
|
static void
|
|
dfu_firmware_parse_altos_vid_pid (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
guint32 addr32;
|
|
|
|
/* find symbol */
|
|
addr32 = dfu_firmware_lookup_symbol (firmware, "ao_usb_descriptors");
|
|
if (addr32 == 0x0)
|
|
return;
|
|
|
|
/* search each element */
|
|
for (guint i = 0; i < priv->images->len; i++) {
|
|
DfuImage *image = g_ptr_array_index (priv->images, i);
|
|
DfuElement *element;
|
|
GBytes *bytes_tmp;
|
|
const guint8 *data;
|
|
gsize length;
|
|
guint32 element_address;
|
|
guint16 vid, pid, release;
|
|
|
|
/* get element data */
|
|
element = dfu_image_get_element_default (image);
|
|
if (element == NULL)
|
|
continue;
|
|
|
|
/* check address is in this element */
|
|
element_address = dfu_element_get_address (element);
|
|
if (element_address > addr32)
|
|
continue;
|
|
bytes_tmp = dfu_element_get_contents (element);
|
|
if (bytes_tmp == NULL)
|
|
continue;
|
|
|
|
/* check this element is big enough */
|
|
data = g_bytes_get_data (bytes_tmp, &length);
|
|
if (addr32 - element_address > length)
|
|
continue;
|
|
|
|
/* read the USB descriptor */
|
|
addr32 -= element_address;
|
|
memcpy (&vid, &data[addr32 + 8], 2);
|
|
memcpy (&pid, &data[addr32 + 10], 2);
|
|
memcpy (&release, &data[addr32 + 12], 2);
|
|
dfu_firmware_set_vid (firmware, GUINT32_FROM_LE (vid));
|
|
dfu_firmware_set_pid (firmware, GUINT32_FROM_LE (pid));
|
|
dfu_firmware_set_release (firmware, GUINT32_FROM_LE (release));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
|
|
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);
|
|
|
|
/* set defaults */
|
|
priv->vid = 0xffff;
|
|
priv->pid = 0xffff;
|
|
priv->release = 0xffff;
|
|
|
|
/* try to get format if not already set */
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN)
|
|
priv->format = dfu_firmware_detect_ihex (bytes);
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN)
|
|
priv->format = dfu_firmware_detect_dfu (bytes);
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN)
|
|
priv->format = dfu_firmware_detect_elf (bytes);
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN)
|
|
priv->format = dfu_firmware_detect_raw (bytes);
|
|
|
|
/* handled easily */
|
|
switch (priv->format) {
|
|
case DFU_FIRMWARE_FORMAT_INTEL_HEX:
|
|
if (!dfu_firmware_from_ihex (firmware, bytes, flags, error))
|
|
return FALSE;
|
|
break;
|
|
case DFU_FIRMWARE_FORMAT_ELF:
|
|
if (!dfu_firmware_from_elf (firmware, bytes, flags, error))
|
|
return FALSE;
|
|
break;
|
|
case DFU_FIRMWARE_FORMAT_DFU:
|
|
case DFU_FIRMWARE_FORMAT_DFUSE:
|
|
if (!dfu_firmware_from_dfu (firmware, bytes, flags, error))
|
|
return FALSE;
|
|
break;
|
|
default:
|
|
if (!dfu_firmware_from_raw (firmware, bytes, flags, error))
|
|
return FALSE;
|
|
break;
|
|
}
|
|
|
|
/* get the VID/PID for altos devices */
|
|
dfu_firmware_parse_altos_vid_pid (firmware);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
gchar *contents = NULL;
|
|
gsize length = 0;
|
|
g_autofree gchar *basename = NULL;
|
|
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);
|
|
|
|
/* guess cipher kind based on file extension */
|
|
basename = g_file_get_basename (file);
|
|
if (g_str_has_suffix (basename, ".xdfu"))
|
|
priv->cipher_kind = DFU_CIPHER_KIND_XTEA;
|
|
|
|
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_get_metadata:
|
|
* @firmware: a #DfuFirmware
|
|
* @key: metadata string key
|
|
*
|
|
* Gets metadata from the store with a specific key.
|
|
*
|
|
* Return value: the metadata value, or %NULL for unset
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
const gchar *
|
|
dfu_firmware_get_metadata (DfuFirmware *firmware, const gchar *key)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
return g_hash_table_lookup (priv->metadata, key);
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_metadata_table:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Gets all metadata from the store.
|
|
*
|
|
* Return value: (transfer none): the metadata hash table
|
|
*
|
|
* Since: 0.6.3
|
|
**/
|
|
GHashTable *
|
|
dfu_firmware_get_metadata_table (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
return priv->metadata;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_set_metadata:
|
|
* @firmware: a #DfuFirmware
|
|
* @key: metadata string key
|
|
* @value: metadata string value
|
|
*
|
|
* Sets a metadata value with a specific key.
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_firmware_set_metadata (DfuFirmware *firmware, const gchar *key, const gchar *value)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_debug ("adding metadata %s=%s", key, value);
|
|
g_hash_table_insert (priv->metadata, g_strdup (key), g_strdup (value));
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_remove_metadata:
|
|
* @firmware: a #DfuFirmware
|
|
* @key: metadata string key
|
|
*
|
|
* Removes a metadata item from the store
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
void
|
|
dfu_firmware_remove_metadata (DfuFirmware *firmware, const gchar *key)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_debug ("removing metadata %s", key);
|
|
g_hash_table_remove (priv->metadata, key);
|
|
}
|
|
|
|
static gboolean
|
|
dfu_firmware_check_acceptable_for_format (DfuFirmware *firmware, GError **error)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
|
|
/* always okay */
|
|
if (priv->images->len <= 1)
|
|
return TRUE;
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_DFUSE)
|
|
return TRUE;
|
|
|
|
/* one is usual, and 2 is okay if one image is the signature */
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_INTEL_HEX) {
|
|
if (priv->images->len == 2 &&
|
|
dfu_firmware_get_image_by_name (firmware, "signature") != NULL)
|
|
return TRUE;
|
|
}
|
|
|
|
/* unsupported */
|
|
g_set_error (error,
|
|
DFU_ERROR,
|
|
DFU_ERROR_INTERNAL,
|
|
"multiple images (%u) not supported for %s",
|
|
priv->images->len,
|
|
dfu_firmware_format_to_string (priv->format));
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
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;
|
|
}
|
|
|
|
/* does the format support this many images */
|
|
if (!dfu_firmware_check_acceptable_for_format (firmware, error))
|
|
return NULL;
|
|
|
|
/* raw */
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_RAW)
|
|
return dfu_firmware_to_raw (firmware, error);
|
|
|
|
/* DFU or DfuSe*/
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_DFU ||
|
|
priv->format == DFU_FIRMWARE_FORMAT_DFUSE)
|
|
return dfu_firmware_to_dfu (firmware, error);
|
|
|
|
/* Intel HEX */
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_INTEL_HEX)
|
|
return dfu_firmware_to_ihex (firmware, error);
|
|
|
|
/* ELF */
|
|
if (priv->format == DFU_FIRMWARE_FORMAT_ELF)
|
|
return dfu_firmware_to_elf (firmware, error);
|
|
|
|
/* 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;
|
|
GList *l;
|
|
GString *str;
|
|
guint i;
|
|
g_autofree gchar *release_str = NULL;
|
|
g_autoptr(GList) keys = NULL;
|
|
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL);
|
|
|
|
release_str = as_utils_version_from_uint16 (priv->release,
|
|
AS_VERSION_PARSE_FLAG_NONE);
|
|
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 [%s]\n",
|
|
priv->release, release_str);
|
|
g_string_append_printf (str, "format: %s [0x%04x]\n",
|
|
dfu_firmware_format_to_string (priv->format),
|
|
priv->format);
|
|
g_string_append_printf (str, "cipher: %s\n",
|
|
dfu_cipher_kind_to_string (priv->cipher_kind));
|
|
|
|
/* print metadata */
|
|
keys = g_hash_table_get_keys (priv->metadata);
|
|
for (l = keys; l != NULL; l = l->next) {
|
|
const gchar *key;
|
|
const gchar *value;
|
|
key = l->data;
|
|
value = g_hash_table_lookup (priv->metadata, key);
|
|
g_string_append_printf (str, "metadata: %s=%s\n", key, value);
|
|
}
|
|
|
|
/* print images */
|
|
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 %u =\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
|
|
*
|
|
* 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)
|
|
return "dfu";
|
|
if (format == DFU_FIRMWARE_FORMAT_DFUSE)
|
|
return "dfuse";
|
|
if (format == DFU_FIRMWARE_FORMAT_INTEL_HEX)
|
|
return "ihex";
|
|
if (format == DFU_FIRMWARE_FORMAT_ELF)
|
|
return "elf";
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_format_from_string:
|
|
* @format: a format string, e.g. `dfuse`
|
|
*
|
|
* Returns an enumerated version of the format.
|
|
*
|
|
* Return value: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_DFUSE
|
|
*
|
|
* Since: 0.7.3
|
|
**/
|
|
DfuFirmwareFormat
|
|
dfu_firmware_format_from_string (const gchar *format)
|
|
{
|
|
if (g_strcmp0 (format, "raw") == 0)
|
|
return DFU_FIRMWARE_FORMAT_RAW;
|
|
if (g_strcmp0 (format, "dfu") == 0)
|
|
return DFU_FIRMWARE_FORMAT_DFU;
|
|
if (g_strcmp0 (format, "dfuse") == 0)
|
|
return DFU_FIRMWARE_FORMAT_DFUSE;
|
|
if (g_strcmp0 (format, "ihex") == 0)
|
|
return DFU_FIRMWARE_FORMAT_INTEL_HEX;
|
|
if (g_strcmp0 (format, "elf") == 0)
|
|
return DFU_FIRMWARE_FORMAT_ELF;
|
|
return DFU_FIRMWARE_FORMAT_UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_cipher_kind:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Returns the kind of cipher used by the firmware file.
|
|
*
|
|
* NOTE: this value is based on a heuristic, and may not be accurate.
|
|
* The value %DFU_CIPHER_KIND_NONE will be returned when the cipher
|
|
* is not recognised.
|
|
*
|
|
* Return value: NULL terminated string, or %NULL for invalid
|
|
*
|
|
* Since: 0.5.4
|
|
**/
|
|
DfuCipherKind
|
|
dfu_firmware_get_cipher_kind (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0);
|
|
return priv->cipher_kind;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_set_cipher_kind:
|
|
* @firmware: a #DfuFirmware
|
|
* @cipher_kind: a #DfuCipherKind, e.g. %DFU_CIPHER_KIND_XTEA
|
|
*
|
|
* Sets the kind of cipher used by the firmware file.
|
|
*
|
|
* Since: 0.6.3
|
|
**/
|
|
void
|
|
dfu_firmware_set_cipher_kind (DfuFirmware *firmware, DfuCipherKind cipher_kind)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_return_if_fail (DFU_IS_FIRMWARE (firmware));
|
|
priv->cipher_kind = cipher_kind;
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_add_symbol:
|
|
* @firmware: a #DfuFirmware
|
|
* @symbol_name: a valid symbol name
|
|
* @symbol_addr: a symbol memory address
|
|
*
|
|
* Adds a symbol to the global map for the firmware.
|
|
*
|
|
* NOTE: Only certain types of firmware can contain a symbol table.
|
|
*
|
|
* Since: 0.7.4
|
|
**/
|
|
void
|
|
dfu_firmware_add_symbol (DfuFirmware *firmware,
|
|
const gchar *symbol_name,
|
|
guint32 symbol_addr)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
g_hash_table_insert (priv->symtab,
|
|
g_strdup (symbol_name),
|
|
GUINT_TO_POINTER (symbol_addr));
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_lookup_symbol:
|
|
* @firmware: a #DfuFirmware
|
|
* @symbol_name: a valid symbol name
|
|
*
|
|
* Returns the address of a symbol from the global map.
|
|
*
|
|
* NOTE: Only certain types of firmware can contain a symbol table.
|
|
*
|
|
* Return value: address of the symbol, or 0x0 for not found.
|
|
*
|
|
* Since: 0.7.4
|
|
**/
|
|
guint32
|
|
dfu_firmware_lookup_symbol (DfuFirmware *firmware, const gchar *symbol_name)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
return GPOINTER_TO_UINT (g_hash_table_lookup (priv->symtab,
|
|
symbol_name));
|
|
}
|
|
|
|
/**
|
|
* dfu_firmware_get_symbols:
|
|
* @firmware: a #DfuFirmware
|
|
*
|
|
* Gets all the symbols currently defined.
|
|
*
|
|
* Return value: (element-type utf8) (transfer container): symbol names
|
|
*
|
|
* Since: 0.7.4
|
|
**/
|
|
GPtrArray *
|
|
dfu_firmware_get_symbols (DfuFirmware *firmware)
|
|
{
|
|
DfuFirmwarePrivate *priv = GET_PRIVATE (firmware);
|
|
GPtrArray *array = g_ptr_array_new_with_free_func (g_free);
|
|
GList *l;
|
|
g_autoptr(GList) keys = g_hash_table_get_keys (priv->symtab);
|
|
for (l = keys; l != NULL; l = l->next)
|
|
g_ptr_array_add (array, g_strdup (l->data));
|
|
return array;
|
|
}
|