fwupd/plugins/wacom-usb/fu-wac-firmware.c
2021-03-11 15:48:02 +00:00

327 lines
8.8 KiB
C

/*
* Copyright (C) 2018 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 (len < 5 || 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) {
gsize cmdlen = strlen (lines[i]);
/* header info record */
if (cmdlen > 3 && 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 %" G_GSIZE_FORMAT " bytes",
cmdlen);
return FALSE;
}
if (!fu_firmware_strparse_uint4_safe (lines[i],
cmdlen,
5,
&header_image_cnt,
error))
return FALSE;
for (guint j = 0; j < header_image_cnt; j++) {
g_autofree FuFirmwareWacHeaderRecord *hdr = NULL;
hdr = g_new0 (FuFirmwareWacHeaderRecord, 1);
if (!fu_firmware_strparse_uint32_safe (lines[i], cmdlen,
(j * 16) + 6,
&hdr->addr,
error))
return FALSE;
if (!fu_firmware_strparse_uint32_safe (lines[i], cmdlen,
(j * 16) + 14,
&hdr->sz,
error))
return FALSE;
g_debug ("header_fw%u_addr: 0x%x", j, hdr->addr);
g_debug ("header_fw%u_sz: 0x%x", j, hdr->sz);
g_ptr_array_add (header_infos, g_steal_pointer (&hdr));
}
continue;
}
/* firmware headline record */
if (cmdlen == 13) {
FuFirmwareWacHeaderRecord *hdr;
guint8 idx = 0;
if (!fu_firmware_strparse_uint4_safe (lines[i],
cmdlen,
2,
&idx,
error))
return FALSE;
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);
if (!fu_firmware_strparse_uint32_safe (lines[i],
cmdlen,
3,
&hdr->prog_start_addr,
error))
return FALSE;
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(GBytes) fw_srec = NULL;
g_autoptr(FuFirmware) firmware_srec = fu_srec_firmware_new ();
g_autoptr(FuFirmware) img = fu_firmware_new ();
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;
fw_srec = fu_firmware_get_bytes (firmware_srec, error);
if (fw_srec == NULL)
return FALSE;
fu_firmware_set_bytes (img, fw_srec);
fu_firmware_set_addr (img, fu_firmware_get_addr (firmware_srec));
fu_firmware_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 guint8
fu_wac_firmware_calc_checksum (GByteArray *buf)
{
guint8 csum = 0;
for (guint i = 0; i < buf->len; i++)
csum += buf->data[i];
return csum ^ 0xFF;
}
static GBytes *
fu_wac_firmware_write (FuFirmware *firmware, GError **error)
{
g_autoptr(GPtrArray) images = fu_firmware_get_images (firmware);
g_autoptr(GString) str = g_string_new (NULL);
g_autoptr(GByteArray) buf_hdr = g_byte_array_new ();
/* fw header */
for (guint i = 0; i < images->len; i++) {
FuFirmware *img = g_ptr_array_index (images, i);
fu_byte_array_append_uint32 (buf_hdr, fu_firmware_get_addr (img), G_BIG_ENDIAN);
fu_byte_array_append_uint32 (buf_hdr, fu_firmware_get_size (img), G_BIG_ENDIAN);
}
g_string_append_printf (str, "WACOM%u", images->len);
for (guint i = 0; i < buf_hdr->len; i++)
g_string_append_printf (str, "%02X", buf_hdr->data[i]);
g_string_append_printf (str, "%02X\n", fu_wac_firmware_calc_checksum (buf_hdr));
/* payload */
for (guint i = 0; i < images->len; i++) {
FuFirmware *img = g_ptr_array_index (images, i);
g_autoptr(GBytes) img_blob = NULL;
g_autoptr(GByteArray) buf_img = g_byte_array_new ();
/* img header */
g_string_append_printf (str, "WA%u", (guint) fu_firmware_get_idx (img) + 1);
fu_byte_array_append_uint32 (buf_img, fu_firmware_get_addr (img), G_BIG_ENDIAN);
for (guint j = 0; j < buf_img->len; j++)
g_string_append_printf (str, "%02X", buf_img->data[j]);
g_string_append_printf (str, "%02X\n", fu_wac_firmware_calc_checksum (buf_img));
/* srec */
img_blob = fu_firmware_write (img, error);
if (img_blob == NULL)
return NULL;
g_string_append_len (str,
(const gchar *) g_bytes_get_data (img_blob, NULL),
g_bytes_get_size (img_blob));
}
/* success */
return g_string_free_to_bytes (g_steal_pointer (&str));
}
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;
klass_firmware->write = fu_wac_firmware_write;
}
FuFirmware *
fu_wac_firmware_new (void)
{
return FU_FIRMWARE (g_object_new (FU_TYPE_WAC_FIRMWARE, NULL));
}