mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-22 10:51:01 +00:00

In many plugins we've wanted to use ->prepare_firmware() to parse the firmware ahead of ->detach() and ->write_firmware() but this has the limitation that it can only return a single blob of data. For many devices, multiple binary blobs are required from one parsed image, for instance providing signatures, config and data blobs that have to be pushed to the device in different way. This also means we parse the firmware *before* we ask the user to detach. Break the internal FuDevice API to support these firmware types as they become more popular. This also allows us to move the Intel HEX and SREC parsing out of the dfu plugin as they are used by a few plugins now, and resolving symbols between plugins isn't exactly awesome.
245 lines
6.3 KiB
C
245 lines
6.3 KiB
C
/*
|
|
* Copyright (C) 2018-2019 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-common.h"
|
|
#include "fu-srec-firmware.h"
|
|
#include "fu-firmware-common.h"
|
|
#include "fu-wac-firmware.h"
|
|
|
|
#include "fwupd-error.h"
|
|
|
|
struct _FuWacFirmware {
|
|
FuFirmware parent_instance;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuWacFirmware, fu_wac_firmware, FU_TYPE_FIRMWARE)
|
|
|
|
typedef struct {
|
|
guint32 addr;
|
|
guint32 sz;
|
|
guint32 prog_start_addr;
|
|
} FuFirmwareWacHeaderRecord;
|
|
|
|
static gboolean
|
|
fu_wac_firmware_parse (FuFirmware *firmware,
|
|
GBytes *fw,
|
|
guint64 addr_start,
|
|
guint64 addr_end,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
gsize len;
|
|
guint8 *data;
|
|
guint8 images_cnt = 0;
|
|
g_auto(GStrv) lines = NULL;
|
|
g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func (g_free);
|
|
g_autoptr(GString) image_buffer = NULL;
|
|
|
|
/* check the prefix (BE) */
|
|
data = (guint8 *) g_bytes_get_data (fw, &len);
|
|
if (memcmp (data, "WACOM", 5) != 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"invalid .wac prefix");
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse each line */
|
|
lines = fu_common_strnsplit ((const gchar *) data, len, "\n", -1);
|
|
for (guint i = 0; lines[i] != NULL; i++) {
|
|
g_autofree gchar *cmd = g_strndup (lines[i], 2);
|
|
|
|
/* remove windows line endings */
|
|
g_strdelimit (lines[i], "\r", '\0');
|
|
|
|
/* Wacom-specific metadata */
|
|
if (g_strcmp0 (cmd, "WA") == 0) {
|
|
guint cmdlen = strlen (lines[i]);
|
|
|
|
/* header info record */
|
|
if (memcmp (lines[i] + 2, "COM", 3) == 0) {
|
|
guint8 header_image_cnt = 0;
|
|
if (cmdlen != 40) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"invalid header, got %u bytes",
|
|
cmdlen);
|
|
return FALSE;
|
|
}
|
|
header_image_cnt = fu_firmware_strparse_uint4 (lines[i] + 5);
|
|
for (guint j = 0; j < header_image_cnt; j++) {
|
|
FuFirmwareWacHeaderRecord *hdr = g_new0 (FuFirmwareWacHeaderRecord, 1);
|
|
hdr->addr = fu_firmware_strparse_uint32 (lines[i] + (j * 16) + 6);
|
|
hdr->sz = fu_firmware_strparse_uint32 (lines[i] + (j * 16) + 14);
|
|
g_ptr_array_add (header_infos, hdr);
|
|
g_debug ("header_fw%u_addr: 0x%x", j, hdr->addr);
|
|
g_debug ("header_fw%u_sz: 0x%x", j, hdr->sz);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* firmware headline record */
|
|
if (cmdlen == 13) {
|
|
FuFirmwareWacHeaderRecord *hdr;
|
|
guint8 idx = fu_firmware_strparse_uint4 (lines[i] + 2);
|
|
if (idx == 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"headline %u invalid",
|
|
idx);
|
|
return FALSE;
|
|
}
|
|
if (idx > header_infos->len) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"headline %u exceeds header count %u",
|
|
idx, header_infos->len);
|
|
return FALSE;
|
|
}
|
|
hdr = g_ptr_array_index (header_infos, idx - 1);
|
|
hdr->prog_start_addr = fu_firmware_strparse_uint32 (lines[i] + 3);
|
|
if (hdr->prog_start_addr != hdr->addr) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"programming address 0x%x != "
|
|
"base address 0x%0x for idx %u",
|
|
hdr->prog_start_addr,
|
|
hdr->addr,
|
|
idx);
|
|
return FALSE;
|
|
}
|
|
g_debug ("programing-start-address: 0x%x", hdr->prog_start_addr);
|
|
continue;
|
|
}
|
|
|
|
g_debug ("unknown Wacom-specific metadata");
|
|
continue;
|
|
}
|
|
|
|
/* start */
|
|
if (g_strcmp0 (cmd, "S0") == 0) {
|
|
if (image_buffer != NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"duplicate S0 without S7");
|
|
return FALSE;
|
|
}
|
|
image_buffer = g_string_new (NULL);
|
|
}
|
|
|
|
/* these are things we want to include in the image */
|
|
if (g_strcmp0 (cmd, "S0") == 0 ||
|
|
g_strcmp0 (cmd, "S1") == 0 ||
|
|
g_strcmp0 (cmd, "S2") == 0 ||
|
|
g_strcmp0 (cmd, "S3") == 0 ||
|
|
g_strcmp0 (cmd, "S5") == 0 ||
|
|
g_strcmp0 (cmd, "S7") == 0 ||
|
|
g_strcmp0 (cmd, "S8") == 0 ||
|
|
g_strcmp0 (cmd, "S9") == 0) {
|
|
if (image_buffer == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"%s without S0", cmd);
|
|
return FALSE;
|
|
}
|
|
g_string_append_printf (image_buffer, "%s\n", lines[i]);
|
|
}
|
|
|
|
/* end */
|
|
if (g_strcmp0 (cmd, "S7") == 0) {
|
|
g_autoptr(GBytes) blob = NULL;
|
|
g_autoptr(FuFirmware) firmware_srec = fu_srec_firmware_new ();
|
|
g_autoptr(FuFirmwareImage) img = NULL;
|
|
FuFirmwareWacHeaderRecord *hdr;
|
|
|
|
/* get the correct relocated start address */
|
|
if (images_cnt >= header_infos->len) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"%s without header", cmd);
|
|
return FALSE;
|
|
}
|
|
hdr = g_ptr_array_index (header_infos, images_cnt);
|
|
|
|
if (image_buffer == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"%s with missing image buffer", cmd);
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse SREC file and add as image */
|
|
blob = g_bytes_new (image_buffer->str, image_buffer->len);
|
|
if (!fu_firmware_parse_full (firmware_srec, blob, hdr->addr, 0x0, flags, error))
|
|
return FALSE;
|
|
img = fu_firmware_get_image_default (firmware_srec, error);
|
|
if (img == NULL)
|
|
return FALSE;
|
|
fu_firmware_image_set_idx (img, images_cnt);
|
|
fu_firmware_add_image (firmware, img);
|
|
images_cnt++;
|
|
|
|
/* clear the image buffer */
|
|
g_string_free (image_buffer, TRUE);
|
|
image_buffer = NULL;
|
|
}
|
|
}
|
|
|
|
/* verify data is complete */
|
|
if (image_buffer != NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"truncated data: no S7");
|
|
return FALSE;
|
|
}
|
|
|
|
/* ensure this matched the header */
|
|
if (header_infos->len != images_cnt) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"not enough images %u for header count %u",
|
|
images_cnt,
|
|
header_infos->len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_wac_firmware_init (FuWacFirmware *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_wac_firmware_class_init (FuWacFirmwareClass *klass)
|
|
{
|
|
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
|
|
klass_firmware->parse = fu_wac_firmware_parse;
|
|
}
|
|
|
|
FuFirmware *
|
|
fu_wac_firmware_new (void)
|
|
{
|
|
return FU_FIRMWARE (g_object_new (FU_TYPE_WAC_FIRMWARE, NULL));
|
|
}
|