/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-fmap-firmware.h" #include "fu-mem.h" /** * FuFmapFirmware: * * A FMAP firmware image. * * See also: [class@FuFirmware] */ #define FMAP_SIGNATURE "__FMAP__" #define FMAP_AREANAME "FMAP" G_DEFINE_TYPE(FuFmapFirmware, fu_fmap_firmware, FU_TYPE_FIRMWARE) static gboolean fu_fmap_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint8 magic[8] = {0x0}; if (!fu_memcpy_safe(magic, sizeof(magic), 0, /* dst */ g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset, sizeof(magic), error)) { g_prefix_error(error, "failed to read magic: "); return FALSE; } if (memcmp(magic, FMAP_SIGNATURE, sizeof(magic)) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid magic for file"); return FALSE; } /* success */ return TRUE; } static gboolean fu_fmap_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuFmapFirmwareClass *klass_firmware = FU_FMAP_FIRMWARE_GET_CLASS(firmware); gsize bufsz; const guint8 *buf = g_bytes_get_data(fw, &bufsz); FuFmap fmap; /* corrupt */ if (g_bytes_get_size(fw) < sizeof(FuFmap)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware too small for fmap"); return FALSE; } /* load header */ if (!fu_memcpy_safe((guint8 *)&fmap, sizeof(fmap), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(fmap), error)) return FALSE; fu_firmware_set_addr(firmware, GUINT64_FROM_LE(fmap.base)); if (GUINT32_FROM_LE(fmap.size) != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file size incorrect, expected 0x%04x got 0x%04x", (guint)fmap.size, (guint)bufsz); return FALSE; } if (GUINT16_FROM_LE(fmap.nareas) < 1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "number of areas too small, got %" G_GUINT16_FORMAT, GUINT16_FROM_LE(fmap.nareas)); return FALSE; } offset += sizeof(fmap); for (gsize i = 0; i < GUINT16_FROM_LE(fmap.nareas); i++) { FuFmapArea area; g_autoptr(FuFirmware) img = NULL; g_autoptr(GBytes) bytes = NULL; g_autofree gchar *area_name = NULL; /* load area */ if (!fu_memcpy_safe((guint8 *)&area, sizeof(area), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(area), error)) return FALSE; /* skip */ if (area.size == 0) continue; bytes = fu_bytes_new_offset(fw, (gsize)GUINT32_FROM_LE(area.offset), (gsize)GUINT32_FROM_LE(area.size), error); if (bytes == NULL) return FALSE; area_name = g_strndup((const gchar *)area.name, FU_FMAP_FIRMWARE_STRLEN); img = fu_firmware_new_from_bytes(bytes); fu_firmware_set_id(img, area_name); fu_firmware_set_idx(img, i + 1); fu_firmware_set_addr(img, GUINT32_FROM_LE(area.offset)); fu_firmware_add_image(firmware, img); if (g_strcmp0(area_name, FMAP_AREANAME) == 0) { g_autofree gchar *version = NULL; version = g_strdup_printf("%d.%d", fmap.ver_major, fmap.ver_minor); fu_firmware_set_version(img, version); } offset += sizeof(area); } /* subclassed */ if (klass_firmware->parse != NULL) { if (!klass_firmware->parse(firmware, fw, offset, flags, error)) return FALSE; } /* success */ return TRUE; } static GBytes * fu_fmap_firmware_write(FuFirmware *firmware, GError **error) { gsize total_sz; gsize offset; g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); FuFmap hdr = { .signature = {FMAP_SIGNATURE}, .ver_major = 0x1, .ver_minor = 0x1, .base = GUINT64_TO_LE(fu_firmware_get_addr(firmware)), .size = 0x0, .name = "", .nareas = GUINT16_TO_LE(images->len), }; /* pad to offset */ if (fu_firmware_get_offset(firmware) > 0) fu_byte_array_set_size(buf, fu_firmware_get_offset(firmware), 0x00); /* add header */ total_sz = offset = sizeof(hdr) + (sizeof(FuFmapArea) * images->len); for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, error); if (fw == NULL) return NULL; total_sz += g_bytes_get_size(fw); } hdr.size = GUINT32_TO_LE(fu_firmware_get_offset(firmware) + total_sz); g_byte_array_append(buf, (const guint8 *)&hdr, sizeof(hdr)); /* add each area */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); const gchar *id = fu_firmware_get_id(img); g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, NULL); FuFmapArea area = { .offset = GUINT32_TO_LE(fu_firmware_get_offset(firmware) + offset), .size = GUINT32_TO_LE(g_bytes_get_size(fw)), .name = {""}, .flags = 0x0, }; if (id != NULL) strncpy((gchar *)area.name, id, sizeof(area.name) - 1); g_byte_array_append(buf, (const guint8 *)&area, sizeof(area)); offset += g_bytes_get_size(fw); } /* add the images */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, NULL); fu_byte_array_append_bytes(buf, fw); } /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static void fu_fmap_firmware_init(FuFmapFirmware *self) { } static void fu_fmap_firmware_class_init(FuFmapFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_fmap_firmware_check_magic; klass_firmware->parse = fu_fmap_firmware_parse; klass_firmware->write = fu_fmap_firmware_write; } /** * fu_fmap_firmware_new * * Creates a new #FuFirmware of sub type fmap * * Since: 1.5.0 **/ FuFirmware * fu_fmap_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_FMAP_FIRMWARE, NULL)); }