mirror of
https://git.proxmox.com/git/fwupd
synced 2025-07-10 08:17:36 +00:00
225 lines
5.9 KiB
C
225 lines
5.9 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "dfu-element.h"
|
|
#include "dfu-format-srec.h"
|
|
#include "dfu-image.h"
|
|
|
|
#include "fu-wac-firmware.h"
|
|
|
|
#include "fwupd-error.h"
|
|
|
|
typedef struct {
|
|
guint32 addr;
|
|
guint32 sz;
|
|
guint32 prog_start_addr;
|
|
} DfuFirmwareWacHeaderRecord;
|
|
|
|
/**
|
|
* fu_wac_firmware_parse_data:
|
|
* @firmware: a #DfuFirmware
|
|
* @bytes: data to parse
|
|
* @flags: some #DfuFirmwareParseFlags
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Unpacks into a firmware object from DfuSe data.
|
|
*
|
|
* Returns: %TRUE for success
|
|
**/
|
|
gboolean
|
|
fu_wac_firmware_parse_data (DfuFirmware *firmware,
|
|
GBytes *bytes,
|
|
DfuFirmwareParseFlags flags,
|
|
GError **error)
|
|
{
|
|
gsize len;
|
|
guint8 *data;
|
|
g_auto(GStrv) lines = NULL;
|
|
g_autoptr(GString) image_buffer = NULL;
|
|
g_autofree gchar *data_str = NULL;
|
|
guint8 images_cnt = 0;
|
|
g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
/* check the prefix (BE) */
|
|
data = (guint8 *) g_bytes_get_data (bytes, &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 */
|
|
data_str = g_strndup ((const gchar *) data, len);
|
|
lines = g_strsplit (data_str, "\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 = dfu_utils_buffer_parse_uint4 (lines[i] + 5);
|
|
for (guint j = 0; j < header_image_cnt; j++) {
|
|
DfuFirmwareWacHeaderRecord *hdr = g_new0 (DfuFirmwareWacHeaderRecord, 1);
|
|
hdr->addr = dfu_utils_buffer_parse_uint32 (lines[i] + (j * 16) + 6);
|
|
hdr->sz = dfu_utils_buffer_parse_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) {
|
|
DfuFirmwareWacHeaderRecord *hdr;
|
|
guint8 idx = dfu_utils_buffer_parse_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 = dfu_utils_buffer_parse_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(DfuImage) image = dfu_image_new ();
|
|
DfuFirmwareWacHeaderRecord *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);
|
|
|
|
/* parse SREC file and add as image */
|
|
blob = g_bytes_new (image_buffer->str, image_buffer->len);
|
|
if (!dfu_image_from_srec (image, blob, hdr->addr, flags, error))
|
|
return FALSE;
|
|
|
|
/* the alt-setting is used for the firmware index */
|
|
dfu_image_set_alt_setting (image, images_cnt);
|
|
dfu_firmware_add_image (firmware, image);
|
|
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 */
|
|
dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_SREC);
|
|
return TRUE;
|
|
}
|