| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #include <boot_device.h> |
| #include <cbmem.h> |
| #include <console/console.h> |
| #include <fmap.h> |
| #include <metadata_hash.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <symbols.h> |
| #include <endian.h> |
| |
| #include "fmap_config.h" |
| |
| /* |
| * See http://code.google.com/p/flashmap/ for more information on FMAP. |
| */ |
| |
| static int fmap_print_once; |
| static struct region_device fmap_cache; |
| |
| #define print_once(...) do { \ |
| if (!fmap_print_once) \ |
| printk(__VA_ARGS__); \ |
| } while (0) |
| |
| uint64_t get_fmap_flash_offset(void) |
| { |
| return FMAP_OFFSET; |
| } |
| |
| static int verify_fmap(const struct fmap *fmap) |
| { |
| if (memcmp(fmap->signature, FMAP_SIGNATURE, sizeof(fmap->signature))) { |
| if (ENV_INITIAL_STAGE) |
| printk(BIOS_ERR, "Invalid FMAP at %#x\n", FMAP_OFFSET); |
| return -1; |
| } |
| |
| static bool done = false; |
| if (!CONFIG(CBFS_VERIFICATION) || !ENV_INITIAL_STAGE || done) |
| return 0; /* Only need to check hash in first stage. */ |
| |
| /* On error we need to die right here, lest we risk a TOCTOU attack where the cache is |
| filled with a tampered FMAP but the later fallback path is fed a valid one. */ |
| if (metadata_hash_verify_fmap(fmap, FMAP_SIZE) != VB2_SUCCESS) |
| die("FMAP verification failure"); |
| |
| done = true; |
| return 0; |
| } |
| |
| static void report(const struct fmap *fmap) |
| { |
| print_once(BIOS_DEBUG, "FMAP: Found \"%s\" version %d.%d at %#x.\n", |
| fmap->name, fmap->ver_major, fmap->ver_minor, FMAP_OFFSET); |
| print_once(BIOS_DEBUG, "FMAP: base = %#llx size = %#x #areas = %d\n", |
| (long long)le64toh(fmap->base), le32toh(fmap->size), |
| le16toh(fmap->nareas)); |
| fmap_print_once = 1; |
| } |
| |
| static void setup_preram_cache(struct region_device *cache_rdev) |
| { |
| if (CONFIG(NO_FMAP_CACHE)) |
| return; |
| |
| /* No need to use FMAP cache in SMM */ |
| if (ENV_SMM) |
| return; |
| |
| if (!ENV_ROMSTAGE_OR_BEFORE) { |
| /* We get here if ramstage makes an FMAP access before calling |
| cbmem_initialize(). We should avoid letting it come to that, |
| so print a warning. */ |
| print_once(BIOS_WARNING, |
| "WARNING: Post-RAM FMAP access too early for cache!\n"); |
| return; |
| } |
| |
| struct fmap *fmap = (struct fmap *)_fmap_cache; |
| if (!(ENV_INITIAL_STAGE)) { |
| /* NOTE: This assumes that the first stage will make |
| at least one FMAP access (usually from finding CBFS). */ |
| if (!verify_fmap(fmap)) |
| goto register_cache; |
| |
| /* This shouldn't happen, so no point providing a fallback path here. */ |
| die("FMAP cache corrupted?!\n"); |
| } |
| |
| /* In case we fail below, make sure the cache is invalid. */ |
| memset(fmap->signature, 0, sizeof(fmap->signature)); |
| |
| boot_device_init(); |
| const struct region_device *boot_rdev = boot_device_ro(); |
| if (!boot_rdev) |
| return; |
| |
| /* memlayout statically guarantees that the FMAP_CACHE is big enough. */ |
| if (rdev_readat(boot_rdev, fmap, FMAP_OFFSET, FMAP_SIZE) != FMAP_SIZE) |
| return; |
| if (verify_fmap(fmap)) |
| return; |
| report(fmap); |
| |
| register_cache: |
| rdev_chain_mem(cache_rdev, fmap, FMAP_SIZE); |
| } |
| |
| static int find_fmap_directory(struct region_device *fmrd) |
| { |
| const struct region_device *boot; |
| struct fmap *fmap; |
| size_t offset = FMAP_OFFSET; |
| |
| /* Try FMAP cache first */ |
| if (!region_device_sz(&fmap_cache)) |
| setup_preram_cache(&fmap_cache); |
| if (region_device_sz(&fmap_cache)) |
| return rdev_chain_full(fmrd, &fmap_cache); |
| |
| /* Cache setup in pre-RAM stages can't fail, unless flash I/O in general failed. */ |
| if (!CONFIG(NO_FMAP_CACHE) && ENV_ROMSTAGE_OR_BEFORE) |
| return -1; |
| |
| boot_device_init(); |
| boot = boot_device_ro(); |
| |
| if (boot == NULL) |
| return -1; |
| |
| fmap = rdev_mmap(boot, offset, |
| CONFIG(CBFS_VERIFICATION) ? FMAP_SIZE : sizeof(struct fmap)); |
| |
| if (fmap == NULL) |
| return -1; |
| |
| if (verify_fmap(fmap)) { |
| rdev_munmap(boot, fmap); |
| return -1; |
| } |
| |
| report(fmap); |
| |
| rdev_munmap(boot, fmap); |
| |
| return rdev_chain(fmrd, boot, offset, FMAP_SIZE); |
| } |
| |
| int fmap_locate_area_as_rdev(const char *name, struct region_device *area) |
| { |
| struct region ar; |
| |
| if (fmap_locate_area(name, &ar)) |
| return -1; |
| |
| return boot_device_ro_subregion(&ar, area); |
| } |
| |
| int fmap_locate_area_as_rdev_rw(const char *name, struct region_device *area) |
| { |
| struct region ar; |
| |
| if (fmap_locate_area(name, &ar)) |
| return -1; |
| |
| return boot_device_rw_subregion(&ar, area); |
| } |
| |
| int fmap_locate_area(const char *name, struct region *ar) |
| { |
| struct region_device fmrd; |
| size_t offset; |
| |
| if (name == NULL || ar == NULL) |
| return -1; |
| |
| if (find_fmap_directory(&fmrd)) |
| return -1; |
| |
| /* Start reading the areas just after fmap header. */ |
| offset = sizeof(struct fmap); |
| |
| while (1) { |
| struct fmap_area *area; |
| |
| area = rdev_mmap(&fmrd, offset, sizeof(*area)); |
| |
| if (area == NULL) |
| return -1; |
| |
| if (strcmp((const char *)area->name, name)) { |
| rdev_munmap(&fmrd, area); |
| offset += sizeof(struct fmap_area); |
| continue; |
| } |
| |
| printk(BIOS_DEBUG, "FMAP: area %s found @ %x (%d bytes)\n", |
| name, le32toh(area->offset), le32toh(area->size)); |
| |
| ar->offset = le32toh(area->offset); |
| ar->size = le32toh(area->size); |
| |
| rdev_munmap(&fmrd, area); |
| |
| return 0; |
| } |
| |
| printk(BIOS_DEBUG, "FMAP: area %s not found\n", name); |
| |
| return -1; |
| } |
| |
| int fmap_find_region_name(const struct region * const ar, |
| char name[FMAP_STRLEN]) |
| { |
| struct region_device fmrd; |
| size_t offset; |
| |
| if (name == NULL || ar == NULL) |
| return -1; |
| |
| if (find_fmap_directory(&fmrd)) |
| return -1; |
| |
| /* Start reading the areas just after fmap header. */ |
| offset = sizeof(struct fmap); |
| |
| while (1) { |
| struct fmap_area *area; |
| |
| area = rdev_mmap(&fmrd, offset, sizeof(*area)); |
| |
| if (area == NULL) |
| return -1; |
| |
| if ((ar->offset != le32toh(area->offset)) || |
| (ar->size != le32toh(area->size))) { |
| rdev_munmap(&fmrd, area); |
| offset += sizeof(struct fmap_area); |
| continue; |
| } |
| |
| printk(BIOS_DEBUG, "FMAP: area (%zx, %zx) found, named %s\n", |
| ar->offset, ar->size, area->name); |
| |
| memcpy(name, area->name, FMAP_STRLEN); |
| |
| rdev_munmap(&fmrd, area); |
| |
| return 0; |
| } |
| |
| printk(BIOS_DEBUG, "FMAP: area (%zx, %zx) not found\n", |
| ar->offset, ar->size); |
| |
| return -1; |
| } |
| |
| ssize_t fmap_read_area(const char *name, void *buffer, size_t size) |
| { |
| struct region_device rdev; |
| if (fmap_locate_area_as_rdev(name, &rdev)) |
| return -1; |
| return rdev_readat(&rdev, buffer, 0, |
| MIN(size, region_device_sz(&rdev))); |
| } |
| |
| ssize_t fmap_overwrite_area(const char *name, const void *buffer, size_t size) |
| { |
| struct region_device rdev; |
| |
| if (fmap_locate_area_as_rdev_rw(name, &rdev)) |
| return -1; |
| if (size > region_device_sz(&rdev)) |
| return -1; |
| if (rdev_eraseat(&rdev, 0, region_device_sz(&rdev)) < 0) |
| return -1; |
| return rdev_writeat(&rdev, buffer, 0, size); |
| } |
| |
| static void fmap_register_cbmem_cache(void) |
| { |
| const struct cbmem_entry *e; |
| |
| /* Find the FMAP cache installed by previous stage */ |
| e = cbmem_entry_find(CBMEM_ID_FMAP); |
| /* Don't set fmap_cache so that find_fmap_directory will use regular path */ |
| if (!e) |
| return; |
| |
| rdev_chain_mem(&fmap_cache, cbmem_entry_start(e), cbmem_entry_size(e)); |
| } |
| |
| /* |
| * The main reason to copy the FMAP into CBMEM is to make it available to the |
| * OS on every architecture. As side effect use the CBMEM copy as cache. |
| */ |
| static void fmap_add_cbmem_cache(void) |
| { |
| struct region_device fmrd; |
| |
| if (find_fmap_directory(&fmrd)) |
| return; |
| |
| /* Reloads the FMAP even on ACPI S3 resume */ |
| const size_t s = region_device_sz(&fmrd); |
| struct fmap *fmap = cbmem_add(CBMEM_ID_FMAP, s); |
| if (!fmap) { |
| printk(BIOS_ERR, "Failed to allocate CBMEM\n"); |
| return; |
| } |
| |
| const ssize_t ret = rdev_readat(&fmrd, fmap, 0, s); |
| if (ret != s) { |
| printk(BIOS_ERR, "Failed to read FMAP into CBMEM\n"); |
| cbmem_entry_remove(cbmem_entry_find(CBMEM_ID_FMAP)); |
| return; |
| } |
| } |
| |
| static void fmap_setup_cbmem_cache(int unused) |
| { |
| if (ENV_CREATES_CBMEM) |
| fmap_add_cbmem_cache(); |
| |
| /* Finally advertise the cache for the current stage */ |
| fmap_register_cbmem_cache(); |
| } |
| |
| CBMEM_READY_HOOK(fmap_setup_cbmem_cache); |