fwupd/plugins/uf2/fu-uf2-firmware.c
2022-06-14 14:36:52 -05:00

349 lines
10 KiB
C

/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-uf2-firmware.h"
struct _FuUf2Firmware {
FuFirmware parent_instance;
};
G_DEFINE_TYPE(FuUf2Firmware, fu_uf2_firmware, FU_TYPE_FIRMWARE)
#define FU_UF2_FIRMWARE_MAGIC_START0 0x0A324655u
#define FU_UF2_FIRMWARE_MAGIC_START1 0x9E5D5157u
#define FU_UF2_FIRMWARE_MAGIC_END 0x0AB16F30u
#define FU_UF2_FIRMWARE_BLOCK_FLAG_NONE 0x00000000
#define FU_UF2_FIRMWARE_BLOCK_FLAG_NOFLASH 0x00000001
#define FU_UF2_FIRMWARE_BLOCK_FLAG_IS_CONTAINER 0x00001000
#define FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY 0x00002000
#define FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_MD5 0x00004000
#define FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG 0x00008000
#define FU_U2F_FIRMWARE_TAG_VERSION 0x9fc7bc /* semver of firmware file (UTF-8) */
#define FU_U2F_FIRMWARE_TAG_DESCRIPTION 0x650d9d /* description of device (UTF-8) */
#define FU_U2F_FIRMWARE_TAG_PAGE_SZ 0x0be9f7 /* page size of target device (uint32_t) */
#define FU_U2F_FIRMWARE_TAG_SHA1 0xb46db0 /* SHA-2 checksum of firmware */
#define FU_U2F_FIRMWARE_TAG_DEVICE_ID 0xc8a729 /* device type identifier (uint32_t or uint64_t) */
static gboolean
fu_uf2_firmware_parse_chunk(FuUf2Firmware *self, FuChunk *chk, GByteArray *tmp, GError **error)
{
gsize bufsz = fu_chunk_get_data_sz(chk);
const guint8 *buf = fu_chunk_get_data(chk);
guint32 magic = 0;
guint32 flags = 0;
guint32 addr = 0;
guint32 datasz = 0;
guint32 blockcnt = 0;
guint32 blocktotal = 0;
guint32 family_id = 0;
/* sanity check */
if (bufsz != 512) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"chunk size invalid, expected 512 bytes and got %u",
fu_chunk_get_data_sz(chk));
return FALSE;
}
/* check magic */
if (!fu_memread_uint32_safe(buf, bufsz, 0x000, &magic, G_LITTLE_ENDIAN, error))
return FALSE;
if (magic != FU_UF2_FIRMWARE_MAGIC_START0) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"magic bytes #1 failed, expected 0x%08x bytes and got 0x%08x",
FU_UF2_FIRMWARE_MAGIC_START0,
magic);
return FALSE;
}
if (!fu_memread_uint32_safe(buf, bufsz, 0x004, &magic, G_LITTLE_ENDIAN, error))
return FALSE;
if (magic != FU_UF2_FIRMWARE_MAGIC_START1) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"magic bytes #2 failed, expected 0x%08x bytes and got 0x%08x",
FU_UF2_FIRMWARE_MAGIC_START1,
magic);
return FALSE;
}
if (!fu_memread_uint32_safe(buf, bufsz, 0x008, &flags, G_LITTLE_ENDIAN, error))
return FALSE;
if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_IS_CONTAINER) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"container U2F firmware not supported");
return FALSE;
}
if (!fu_memread_uint32_safe(buf, bufsz, 0x00C, &addr, G_LITTLE_ENDIAN, error))
return FALSE;
if (!fu_memread_uint32_safe(buf, bufsz, 0x010, &datasz, G_LITTLE_ENDIAN, error))
return FALSE;
if (datasz > 476) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"data size impossible got 0x%08x",
datasz);
return FALSE;
}
if (!fu_memread_uint32_safe(buf, bufsz, 0x014, &blockcnt, G_LITTLE_ENDIAN, error))
return FALSE;
if (blockcnt != fu_chunk_get_idx(chk)) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"block count invalid, expected 0x%04x and got 0x%04x",
fu_chunk_get_idx(chk),
blockcnt);
return FALSE;
}
if (!fu_memread_uint32_safe(buf, bufsz, 0x018, &blocktotal, G_LITTLE_ENDIAN, error))
return FALSE;
if (blocktotal == 0) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"block count invalid, expected > 0");
return FALSE;
}
if (!fu_memread_uint32_safe(buf, bufsz, 0x01C, &family_id, G_LITTLE_ENDIAN, error))
return FALSE;
if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY) {
if (family_id == 0) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"family_id required but not supplied");
return FALSE;
}
}
/* assume first chunk is representative of firmware */
if (fu_chunk_get_idx(chk) == 0) {
fu_firmware_set_addr(FU_FIRMWARE(self), addr);
fu_firmware_set_idx(FU_FIRMWARE(self), family_id);
}
/* just append raw data */
g_byte_array_append(tmp, buf + 0x020, datasz);
if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_MD5) {
if (datasz < 24) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"not enough space for MD5 checksum");
return FALSE;
}
}
if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG) {
gsize offset = 0x20 + datasz;
while (offset < bufsz) {
guint8 sz = 0;
guint32 tag = 0;
/* [SZ][TAG][TAG][TAG][TAG][DATA....] */
if (!fu_memread_uint8_safe(buf, bufsz, offset, &sz, error))
return FALSE;
if (sz < 4) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid extension tag size");
return FALSE;
}
if (!fu_memread_uint32_safe(buf,
bufsz,
offset,
&tag,
G_LITTLE_ENDIAN,
error))
return FALSE;
tag &= 0xFFFFFF;
if (tag == FU_U2F_FIRMWARE_TAG_VERSION) {
g_autofree gchar *utf8buf = g_malloc0(sz);
if (!fu_memcpy_safe((guint8 *)utf8buf,
sz,
0x0, /* dst */
buf,
bufsz,
offset + 0x4, /* src */
sz - 4,
error))
return FALSE;
fu_firmware_set_version(FU_FIRMWARE(self), utf8buf);
} else if (tag == FU_U2F_FIRMWARE_TAG_DESCRIPTION) {
g_autofree gchar *utf8buf = g_malloc0(sz);
if (!fu_memcpy_safe((guint8 *)utf8buf,
sz,
0x0, /* dst */
buf,
bufsz,
offset + 0x4, /* src */
sz - 4,
error))
return FALSE;
fu_firmware_set_id(FU_FIRMWARE(self), utf8buf);
} else {
g_warning("unknown tag 0x%06x", tag);
}
/* next! */
offset += sz;
}
}
if (!fu_memread_uint32_safe(buf, bufsz, 0x1FC, &magic, G_LITTLE_ENDIAN, error))
return FALSE;
if (magic != FU_UF2_FIRMWARE_MAGIC_END) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"magic bytes #3 failed, expected 0x%08x bytes and got 0x%08x",
FU_UF2_FIRMWARE_MAGIC_END,
magic);
return FALSE;
}
/* dump */
if (g_getenv("FWUPD_U2F_VERBOSE") != NULL) {
g_debug("block: 0x%x/0x%x @0x%x", blockcnt, blocktotal - 1, addr);
g_debug("family_id: 0x%x", family_id);
g_debug("flags: 0x%x", flags);
g_debug("datasz: 0x%x", datasz);
}
/* success */
return TRUE;
}
static gboolean
fu_uf2_firmware_parse(FuFirmware *firmware,
GBytes *fw,
guint64 addr_start,
guint64 addr_end,
FwupdInstallFlags flags,
GError **error)
{
FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware);
g_autoptr(GByteArray) tmp = g_byte_array_new();
g_autoptr(GPtrArray) chunks = NULL;
/* read in fixed sized chunks */
chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, 512);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index(chunks, i);
if (!fu_uf2_firmware_parse_chunk(self, chk, tmp, error))
return FALSE;
}
/* success */
fu_firmware_set_bytes(firmware, g_byte_array_free_to_bytes(g_steal_pointer(&tmp)));
return TRUE;
}
static GByteArray *
fu_uf2_firmware_write_chunk(FuUf2Firmware *self, FuChunk *chk, guint chk_len, GError **error)
{
guint32 addr = fu_firmware_get_addr(FU_FIRMWARE(self));
guint32 family_id = fu_firmware_get_idx(FU_FIRMWARE(self));
guint32 flags = FU_UF2_FIRMWARE_BLOCK_FLAG_NONE;
g_autoptr(GByteArray) buf = g_byte_array_new();
g_autoptr(GByteArray) datapad = g_byte_array_new();
/* sanity check */
if (fu_chunk_get_data_sz(chk) > 476) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"chunk size invalid, expected < 476 bytes and got %u",
fu_chunk_get_data_sz(chk));
return NULL;
}
/* pad out data */
g_byte_array_append(datapad, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk));
fu_byte_array_set_size(datapad, 476, 0x0);
/* optional */
if (family_id > 0)
flags |= FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY;
/* offset from base address */
addr += fu_chunk_get_idx(chk) * fu_chunk_get_data_sz(chk);
/* build UF2 packet */
fu_byte_array_append_uint32(buf, FU_UF2_FIRMWARE_MAGIC_START0, G_LITTLE_ENDIAN);
fu_byte_array_append_uint32(buf, FU_UF2_FIRMWARE_MAGIC_START1, G_LITTLE_ENDIAN);
fu_byte_array_append_uint32(buf, flags, G_LITTLE_ENDIAN);
fu_byte_array_append_uint32(buf, addr, G_LITTLE_ENDIAN);
fu_byte_array_append_uint32(buf, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN);
fu_byte_array_append_uint32(buf, fu_chunk_get_idx(chk), G_LITTLE_ENDIAN);
fu_byte_array_append_uint32(buf, chk_len, G_LITTLE_ENDIAN);
fu_byte_array_append_uint32(buf, family_id, G_LITTLE_ENDIAN);
g_byte_array_append(buf, datapad->data, datapad->len);
fu_byte_array_append_uint32(buf, FU_UF2_FIRMWARE_MAGIC_END, G_LITTLE_ENDIAN);
/* success */
return g_steal_pointer(&buf);
}
static GBytes *
fu_uf2_firmware_write(FuFirmware *firmware, GError **error)
{
FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware);
g_autoptr(GByteArray) buf = g_byte_array_new();
g_autoptr(GBytes) fw = NULL;
g_autoptr(GPtrArray) chunks = NULL;
/* data first */
fw = fu_firmware_get_bytes_with_patches(firmware, error);
if (fw == NULL)
return NULL;
/* write in chunks */
chunks = fu_chunk_array_new_from_bytes(fw, fu_firmware_get_addr(firmware), 0x0, 256);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index(chunks, i);
g_autoptr(GByteArray) tmp = NULL;
tmp = fu_uf2_firmware_write_chunk(self, chk, chunks->len, error);
if (tmp == NULL)
return NULL;
g_byte_array_append(buf, tmp->data, tmp->len);
}
/* success */
return g_byte_array_free_to_bytes(g_steal_pointer(&buf));
}
static void
fu_uf2_firmware_init(FuUf2Firmware *self)
{
}
static void
fu_uf2_firmware_class_init(FuUf2FirmwareClass *klass)
{
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
klass_firmware->parse = fu_uf2_firmware_parse;
klass_firmware->write = fu_uf2_firmware_write;
}
FuFirmware *
fu_uf2_firmware_new(void)
{
return FU_FIRMWARE(g_object_new(FU_TYPE_UF2_FIRMWARE, NULL));
}