/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-ifd-bios.h" #include "fu-ifd-common.h" #include "fu-ifd-firmware.h" #include "fu-ifd-image.h" #include "fu-mem.h" /** * FuIfdFirmware: * * An Intel Flash Descriptor. * * See also: [class@FuFirmware] */ typedef struct { gboolean new_layout; guint32 descriptor_map0; guint32 descriptor_map1; guint32 descriptor_map2; guint8 num_regions; guint8 num_components; guint32 flash_region_base_addr; guint32 flash_component_base_addr; guint32 flash_master_base_addr; guint32 flash_master[4]; /* indexed from 1, ignore [0] */ guint32 flash_ich_strap_base_addr; guint32 flash_mch_strap_base_addr; guint32 components_rcd; guint32 illegal_jedec; guint32 illegal_jedec1; guint32 *flash_descriptor_regs; } FuIfdFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuIfdFirmware, fu_ifd_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_ifd_firmware_get_instance_private(o)) #define FU_IFD_SIZE 0x1000 #define FU_IFD_SIGNATURE 0x0FF0A55A #define FU_IFD_FDBAR_RESERVED 0x0000 #define FU_IFD_FDBAR_SIGNATURE 0x0010 #define FU_IFD_FDBAR_DESCRIPTOR_MAP0 0x0014 #define FU_IFD_FDBAR_DESCRIPTOR_MAP1 0x0018 #define FU_IFD_FDBAR_DESCRIPTOR_MAP2 0x001C #define FU_IFD_FDBAR_FLASH_UPPER_MAP1 0x0EFC #define FU_IFD_FDBAR_OEM_SECTION 0x0F00 #define FU_IFD_FCBA_FLCOMP 0x0000 #define FU_IFD_FCBA_FLILL 0x0004 #define FU_IFD_FCBA_FLILL1 0x0008 #define FU_IFD_FREG_BASE(freg) (((freg) << 12) & 0x07FFF000) #define FU_IFD_FREG_LIMIT(freg) ((((freg) >> 4) & 0x07FFF000) | 0x00000FFF) static void fu_ifd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuIfdFirmware *self = FU_IFD_FIRMWARE(firmware); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "descriptor_map0", priv->descriptor_map0); fu_xmlb_builder_insert_kx(bn, "descriptor_map1", priv->descriptor_map1); fu_xmlb_builder_insert_kx(bn, "descriptor_map2", priv->descriptor_map2); fu_xmlb_builder_insert_kx(bn, "num_regions", priv->num_regions); fu_xmlb_builder_insert_kx(bn, "num_components", priv->num_components + 1); fu_xmlb_builder_insert_kx(bn, "flash_region_base_addr", priv->flash_region_base_addr); fu_xmlb_builder_insert_kx(bn, "flash_component_base_addr", priv->flash_component_base_addr); fu_xmlb_builder_insert_kx(bn, "flash_master_base_addr", priv->flash_master_base_addr); fu_xmlb_builder_insert_kx(bn, "flash_ich_strap_base_addr", priv->flash_ich_strap_base_addr); fu_xmlb_builder_insert_kx(bn, "flash_mch_strap_base_addr", priv->flash_mch_strap_base_addr); fu_xmlb_builder_insert_kx(bn, "components_rcd", priv->components_rcd); fu_xmlb_builder_insert_kx(bn, "illegal_jedec", priv->illegal_jedec); fu_xmlb_builder_insert_kx(bn, "illegal_jedec1", priv->illegal_jedec1); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { for (guint i = 1; i < 3; i++) { g_autofree gchar *title = g_strdup_printf("flash_master%x", i + 1); fu_xmlb_builder_insert_kx(bn, title, priv->flash_master[i]); } if (priv->flash_descriptor_regs != NULL) { for (guint i = 0; i < priv->num_regions; i++) { g_autofree gchar *title = g_strdup_printf("flash_descriptor_reg%x", i); fu_xmlb_builder_insert_kx(bn, title, priv->flash_descriptor_regs[i]); } } } } static gboolean fu_ifd_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint32 magic = 0; if (!fu_memread_uint32_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset + FU_IFD_FDBAR_SIGNATURE, &magic, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read magic: "); return FALSE; } if (magic != FU_IFD_SIGNATURE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "signature invalid, got 0x%x, expected 0x%x", magic, (guint)FU_IFD_SIGNATURE); return FALSE; } /* success */ return TRUE; } static gboolean fu_ifd_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuIfdFirmware *self = FU_IFD_FIRMWARE(firmware); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* check size */ if (bufsz < FU_IFD_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "file is too small, expected bufsz >= 0x%x", (guint)FU_IFD_SIZE); return FALSE; } /* descriptor registers */ priv->descriptor_map0 = fu_memread_uint32(buf + FU_IFD_FDBAR_DESCRIPTOR_MAP0, G_LITTLE_ENDIAN); priv->num_regions = (priv->descriptor_map0 >> 24) & 0b111; if (priv->num_regions == 0) priv->num_regions = 10; priv->num_components = (priv->descriptor_map0 >> 8) & 0b11; priv->flash_component_base_addr = (priv->descriptor_map0 << 4) & 0x00000FF0; priv->flash_region_base_addr = (priv->descriptor_map0 >> 12) & 0x00000FF0; priv->descriptor_map1 = fu_memread_uint32(buf + FU_IFD_FDBAR_DESCRIPTOR_MAP1, G_LITTLE_ENDIAN); priv->flash_master_base_addr = (priv->descriptor_map1 << 4) & 0x00000FF0; priv->flash_ich_strap_base_addr = (priv->descriptor_map1 >> 12) & 0x00000FF0; priv->descriptor_map2 = fu_memread_uint32(buf + FU_IFD_FDBAR_DESCRIPTOR_MAP2, G_LITTLE_ENDIAN); priv->flash_mch_strap_base_addr = (priv->descriptor_map2 << 4) & 0x00000FF0; /* FCBA */ if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_component_base_addr + FU_IFD_FCBA_FLCOMP, &priv->components_rcd, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_component_base_addr + FU_IFD_FCBA_FLILL, &priv->illegal_jedec, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_component_base_addr + FU_IFD_FCBA_FLILL1, &priv->illegal_jedec1, G_LITTLE_ENDIAN, error)) return FALSE; /* FMBA */ if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_master_base_addr + 0x0, &priv->flash_master[1], G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_master_base_addr + 0x4, &priv->flash_master[2], G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_master_base_addr + 0x8, &priv->flash_master[3], G_LITTLE_ENDIAN, error)) return FALSE; /* FRBA */ priv->flash_descriptor_regs = g_new0(guint32, priv->num_regions); for (guint i = 0; i < priv->num_regions; i++) { if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_region_base_addr + (i * sizeof(guint32)), &priv->flash_descriptor_regs[i], G_LITTLE_ENDIAN, error)) return FALSE; } for (guint i = 0; i < priv->num_regions; i++) { const gchar *freg_str = fu_ifd_region_to_string(i); guint32 freg_base = FU_IFD_FREG_BASE(priv->flash_descriptor_regs[i]); guint32 freg_limt = FU_IFD_FREG_LIMIT(priv->flash_descriptor_regs[i]); guint32 freg_size = (freg_limt - freg_base) + 1; g_autoptr(FuFirmware) img = NULL; g_autoptr(GBytes) contents = NULL; /* invalid */ if (freg_base > freg_limt) continue; /* create image */ g_debug("freg %s 0x%04x -> 0x%04x", freg_str, freg_base, freg_limt); contents = fu_bytes_new_offset(fw, freg_base, freg_size, error); if (contents == NULL) return FALSE; if (i == FU_IFD_REGION_BIOS) { img = fu_ifd_bios_new(); } else { img = fu_ifd_image_new(); } if (!fu_firmware_parse(img, contents, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; fu_firmware_set_addr(img, freg_base); fu_firmware_set_idx(img, i); if (freg_str != NULL) fu_firmware_set_id(img, freg_str); fu_firmware_add_image(firmware, img); /* is writable by anything other than the region itself */ for (FuIfdRegion r = 1; r <= 3; r++) { FuIfdAccess acc; acc = fu_ifd_region_to_access(i, priv->flash_master[r], priv->new_layout); fu_ifd_image_set_access(FU_IFD_IMAGE(img), r, acc); } } /* success */ return TRUE; } /** * fu_ifd_firmware_check_jedec_cmd: * @self: a #FuIfdFirmware * @cmd: a JEDEC command, e.g. 0x42 for "whole chip erase" * * Checks a JEDEC command to see if it has been put on the "illegal_jedec" list. * * Returns: %TRUE if the command is allowed * * Since: 1.6.2 **/ gboolean fu_ifd_firmware_check_jedec_cmd(FuIfdFirmware *self, guint8 cmd) { FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); for (guint j = 0; j < 32; j += 8) { if (((priv->illegal_jedec >> j) & 0xff) == cmd) return FALSE; if (((priv->illegal_jedec1 >> j) & 0xff) == cmd) return FALSE; } return TRUE; } static GBytes * fu_ifd_firmware_write(FuFirmware *firmware, GError **error) { FuIfdFirmware *self = FU_IFD_FIRMWARE(firmware); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); gsize bufsz_max = 0x0; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GHashTable) blobs = NULL; g_autoptr(FuFirmware) img_desc = NULL; /* if the descriptor does not exist, then add something plausible */ img_desc = fu_firmware_get_image_by_idx(firmware, FU_IFD_REGION_DESC, NULL); if (img_desc == NULL) { g_autoptr(GByteArray) buf_desc = g_byte_array_new(); g_autoptr(GBytes) blob_desc = NULL; fu_byte_array_set_size(buf_desc, FU_IFD_SIZE, 0x00); /* success */ blob_desc = g_byte_array_free_to_bytes(g_steal_pointer(&buf_desc)); img_desc = fu_firmware_new_from_bytes(blob_desc); fu_firmware_set_addr(img_desc, 0x0); fu_firmware_set_idx(img_desc, FU_IFD_REGION_DESC); fu_firmware_set_id(img_desc, "desc"); fu_firmware_add_image(firmware, img_desc); } /* generate ahead of time */ blobs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_bytes_unref); for (guint i = 0; i < priv->num_regions; i++) { g_autoptr(FuFirmware) img = fu_firmware_get_image_by_idx(firmware, i, NULL); g_autoptr(GBytes) blob = NULL; if (img == NULL) continue; blob = fu_firmware_write(img, error); if (blob == NULL) { g_prefix_error(error, "failed to write %s: ", fu_firmware_get_id(img)); return NULL; } if (g_bytes_get_data(blob, NULL) == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to write %s", fu_firmware_get_id(img)); return NULL; } g_hash_table_insert(blobs, GUINT_TO_POINTER(i), g_bytes_ref(blob)); /* check total size */ bufsz_max = MAX(fu_firmware_get_addr(img) + g_bytes_get_size(blob), bufsz_max); } fu_byte_array_set_size(buf, bufsz_max, 0x00); /* reserved */ for (guint i = 0; i < 0x10; i++) buf->data[FU_IFD_FDBAR_RESERVED + i] = 0xff; /* signature */ fu_memwrite_uint32(buf->data + FU_IFD_FDBAR_SIGNATURE, FU_IFD_SIGNATURE, G_LITTLE_ENDIAN); /* descriptor map */ fu_memwrite_uint32(buf->data + FU_IFD_FDBAR_DESCRIPTOR_MAP0, priv->descriptor_map0, G_LITTLE_ENDIAN); fu_memwrite_uint32(buf->data + FU_IFD_FDBAR_DESCRIPTOR_MAP1, priv->descriptor_map1, G_LITTLE_ENDIAN); fu_memwrite_uint32(buf->data + FU_IFD_FDBAR_DESCRIPTOR_MAP2, priv->descriptor_map2, G_LITTLE_ENDIAN); /* FCBA */ if (!fu_memwrite_uint32_safe(buf->data, buf->len, priv->flash_component_base_addr + FU_IFD_FCBA_FLCOMP, priv->components_rcd, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint32_safe(buf->data, buf->len, priv->flash_component_base_addr + FU_IFD_FCBA_FLILL, priv->illegal_jedec, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint32_safe(buf->data, buf->len, priv->flash_component_base_addr + FU_IFD_FCBA_FLILL1, priv->illegal_jedec1, G_LITTLE_ENDIAN, error)) return NULL; /* FRBA */ for (guint i = 0; i < priv->num_regions; i++) { guint32 freg_base = 0x7FFF000; guint32 freg_limt = 0x0; guint32 flreg; g_autoptr(FuFirmware) img = fu_firmware_get_image_by_idx(firmware, i, NULL); if (img != NULL) { GBytes *blob = g_hash_table_lookup(blobs, GUINT_TO_POINTER(fu_firmware_get_idx(img))); freg_base = fu_firmware_get_addr(img); freg_limt = (freg_base + g_bytes_get_size(blob)) - 1; } flreg = ((freg_limt << 4) & 0xFFFF0000) | (freg_base >> 12); g_debug("freg 0x%04x -> 0x%04x = 0x%08x", freg_base, freg_limt, flreg); if (!fu_memwrite_uint32_safe(buf->data, buf->len, priv->flash_region_base_addr + (i * sizeof(guint32)), flreg, G_LITTLE_ENDIAN, error)) return NULL; } /* write images at correct offsets */ for (guint i = 1; i < priv->num_regions; i++) { GBytes *blob; g_autoptr(FuFirmware) img = fu_firmware_get_image_by_idx(firmware, i, NULL); if (img == NULL) continue; blob = g_hash_table_lookup(blobs, GUINT_TO_POINTER(fu_firmware_get_idx(img))); if (!fu_memcpy_safe(buf->data, buf->len, fu_firmware_get_addr(img), g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), 0x0, g_bytes_get_size(blob), error)) return NULL; } /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static gboolean fu_ifd_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuIfdFirmware *self = FU_IFD_FIRMWARE(firmware); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "descriptor_map0", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->descriptor_map0 = tmp; tmp = xb_node_query_text_as_uint(n, "descriptor_map1", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->descriptor_map1 = tmp; tmp = xb_node_query_text_as_uint(n, "descriptor_map2", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->descriptor_map2 = tmp; tmp = xb_node_query_text_as_uint(n, "components_rcd", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->components_rcd = tmp; tmp = xb_node_query_text_as_uint(n, "illegal_jedec", NULL); if (tmp != G_MAXUINT64) { priv->illegal_jedec = tmp & 0xFFFFFFFF; priv->illegal_jedec1 = tmp >> 32; } /* success */ return TRUE; } static void fu_ifd_firmware_init(FuIfdFirmware *self) { FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); /* some good defaults */ priv->new_layout = TRUE; priv->num_regions = 10; priv->flash_region_base_addr = 0x40; priv->flash_component_base_addr = 0x30; priv->flash_master_base_addr = 0x80; priv->flash_master[1] = 0x00A00F00; priv->flash_master[2] = 0x00400D00; priv->flash_master[3] = 0x00800900; priv->flash_ich_strap_base_addr = 0x100; priv->flash_mch_strap_base_addr = 0x300; } static void fu_ifd_firmware_finalize(GObject *object) { FuIfdFirmware *self = FU_IFD_FIRMWARE(object); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); g_free(priv->flash_descriptor_regs); G_OBJECT_CLASS(fu_ifd_firmware_parent_class)->finalize(object); } static void fu_ifd_firmware_class_init(FuIfdFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_ifd_firmware_finalize; klass_firmware->check_magic = fu_ifd_firmware_check_magic; klass_firmware->export = fu_ifd_firmware_export; klass_firmware->parse = fu_ifd_firmware_parse; klass_firmware->write = fu_ifd_firmware_write; klass_firmware->build = fu_ifd_firmware_build; } /** * fu_ifd_firmware_new: * * Creates a new #FuFirmware of sub type Ifd * * Since: 1.6.2 **/ FuFirmware * fu_ifd_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IFD_FIRMWARE, NULL)); }