
From: Simon Glass <sjg@chromium.org> When booting Linux with EFI the devicetree memory-map is ignored and Linux calls through EFI to obtain the real memory map. When booting Linux from the EFI app, without EFI, we must pass the reserved memory onto Linux using the devicetree. Add a function to support this. It reads the EFI memory-map and adds any missing regions to the reserved-memory node. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- include/efi.h | 16 +++ lib/efi_client/efi.c | 249 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 263 insertions(+), 2 deletions(-) diff --git a/include/efi.h b/include/efi.h index 530b127bde4..b3f9c0b694f 100644 --- a/include/efi.h +++ b/include/efi.h @@ -794,6 +794,22 @@ static inline bool efi_mem_is_boot_services(int type) */ const char *efi_mem_type_name(enum efi_memory_type type); +/** + * efi_mem_reserved_sync() - Sync EFI memory map with DT reserved-memory nodes + * + * This function compares the EFI memory map with the device tree's reserved-memory + * nodes and prints out regions that are reserved in EFI but not mentioned in the + * device tree's /reserved-memory node. This helps identify memory regions that + * EFI considers reserved but which Linux might try to use. + * + * Note: This only works with #address-cells and #size-cells of 2 + * + * @fdt: Pointer to the device tree blob + * @verbose: If true, show detailed output; if false, only show errors + * Return: Number of regions synced, or -ve on error + */ +int efi_mem_reserved_sync(void *fdt, bool verbose); + /** * efi_dump_mem_table() - Dump out the EFI memory map * diff --git a/lib/efi_client/efi.c b/lib/efi_client/efi.c index 12a646a36b7..9d064484dbf 100644 --- a/lib/efi_client/efi.c +++ b/lib/efi_client/efi.c @@ -13,10 +13,13 @@ #include <debug_uart.h> #include <errno.h> #include <malloc.h> -#include <linux/err.h> -#include <linux/types.h> #include <efi.h> #include <efi_api.h> +#include <mapmem.h> +#include <linux/err.h> +#include <linux/libfdt.h> +#include <linux/types.h> +#include <fdt_support.h> enum { /* magic number to trigger gdb breakpoint */ @@ -213,3 +216,245 @@ uint16_t *efi_dp_str(struct efi_device_path *dp) return val; } + +/** + * is_reserved() - Check if EFI memory type should be preserved + * + * @type: EFI memory type + * Return: true if memory type should be preserved, false otherwise + */ +static bool is_reserved(u32 type) +{ + switch (type) { + case EFI_RESERVED_MEMORY_TYPE: + case EFI_RUNTIME_SERVICES_CODE: + case EFI_RUNTIME_SERVICES_DATA: + case EFI_UNUSABLE_MEMORY: + case EFI_ACPI_RECLAIM_MEMORY: + case EFI_ACPI_MEMORY_NVS: + return true; + default: + return false; + } +} + +/** + * dt_region_exists() - Check if memory region is covered by DT reserved-memory + * + * @fdt: Device tree blob + * @start: Start address of region to check + * @end: End address of region to check + * Return: true if region overlaps with any reserved-memory node, else false + */ +static bool dt_region_exists(void *fdt, u64 start, u64 end) +{ + int node, reserved; + + reserved = fdt_path_offset(fdt, "/reserved-memory"); + if (reserved < 0) + return false; + + fdt_for_each_subnode(node, fdt, reserved) { + const fdt32_t *reg; + u64 start, size, end; + int len; + + reg = fdt_getprop(fdt, node, "reg", &len); + if (!reg || len < sizeof(u32) * 2) + continue; + + /* Parse reg property - assuming #address-cells=2, #size-cells=2 */ + start = fdt64_to_cpu(*(fdt64_t *)reg); + size = fdt64_to_cpu(*((fdt64_t *)reg + 1)); + end = start + size - 1; + + /* Check for overlap */ + if (!(end < start || start > end)) + return true; + } + + return false; +} + +/** + * dt_add_reserved() - Add EFI reserved region to device tree reserved-memory + * + * @fdt: Device tree blob + * @start: Start address of region + * @size: Size of region + * @type_name: EFI memory type name for node naming + * Return: 0 on success, negative error code on failure + */ +static int dt_add_reserved(void *fdt, u64 start, u64 size, + const char *type_name) +{ + int reserved, node; + char node_name[64]; + fdt32_t reg_prop[4]; + char *p; + int ret; + + /* Find or create /reserved-memory node */ + reserved = fdt_path_offset(fdt, "/reserved-memory"); + if (reserved < 0) { + /* Create /reserved-memory node */ + reserved = fdt_add_subnode(fdt, 0, "reserved-memory"); + if (reserved < 0) { + printf("Failed to create /reserved-memory node: %s\n", + fdt_strerror(reserved)); + return reserved; + } + + ret = fdt_setprop_u64(fdt, reserved, "#address-cells", 2); + if (ret) + return ret; + + ret = fdt_setprop_u64(fdt, reserved, "#size-cells", 2); + if (ret) + return ret; + + ret = fdt_setprop(fdt, reserved, "ranges", NULL, 0); + if (ret) + return ret; + } + + /* Create node name based on type and address */ + snprintf(node_name, sizeof(node_name), "efi-%s@%llx", type_name, start); + + /* Convert spaces and underscores to hyphens for a valid node name */ + for (p = node_name; *p; p++) { + if (*p == ' ' || *p == '_') + *p = '-'; + } + + /* Add new subnode */ + node = fdt_add_subnode(fdt, reserved, node_name); + if (node < 0) { + printf("Failed to create node %s: %s\n", node_name, + fdt_strerror(node)); + return node; + } + + /* Set reg property - #address-cells=2, #size-cells=2 */ + reg_prop[0] = cpu_to_fdt32(start >> 32); + reg_prop[1] = cpu_to_fdt32(start & 0xffffffff); + reg_prop[2] = cpu_to_fdt32(size >> 32); + reg_prop[3] = cpu_to_fdt32(size & 0xffffffff); + + ret = fdt_setprop(fdt, node, "reg", reg_prop, sizeof(reg_prop)); + if (ret) { + printf("Failed to set reg property: %s\n", fdt_strerror(ret)); + return ret; + } + + /* Add no-map property to prevent Linux from using this memory */ + ret = fdt_setprop(fdt, node, "no-map", NULL, 0); + if (ret) { + printf("Failed to set no-map property: %s\n", + fdt_strerror(ret)); + return ret; + } + + printf("added reserved-memory node: %s (0x%llx - 0x%llx)\n", + node_name, start, start + size - 1); + + return 0; +} + +/** + * sync_to_dt() - Print EFI reserved regions and add missing ones to DT + * + * @fdt: Device tree blob + * Return: true if any uncovered regions found, false otherwise + */ +static int sync_to_dt(void *fdt, bool verbose) +{ + struct efi_mem_desc *map, *desc, *end; + int desc_size, size, upto; + uint version, key; + int synced = 0; + int ret; + + /* Get the EFI memory map */ + ret = efi_get_mmap(&map, &size, &key, &desc_size, &version); + if (ret) { + printf("Failed to get EFI memory map: %d\n", ret); + return ret; + } + + if (verbose) { + printf("EFI Memory Map Analysis:\n"); + printf("%-4s %-18s %-18s %-18s %s\n", "ID", "Type", "Start", + "End", "In DT?"); + printf("-------------------------------------------------------" + "-----------------\n"); + } + + end = (void *)map + size; + for (upto = 0, desc = map; desc < end; + desc = efi_get_next_mem_desc(desc, desc_size), upto++) { + u64 start = desc->physical_start; + u64 end_addr = start + (desc->num_pages << EFI_PAGE_SHIFT) - 1; + u64 region_size = desc->num_pages << EFI_PAGE_SHIFT; + bool present; + + if (!is_reserved(desc->type)) + continue; + + present = dt_region_exists(fdt, start, end_addr); + + /* Print the region */ + if (verbose) { + printf("%-4d %-18s 0x%-16llx 0x%-16llx %s", upto, + efi_mem_type_name(desc->type), start, end_addr, + present ? "yes" : "no"); + } + + if (!present) { + const char *type_name; + int ret; + + if (verbose) + printf(" -> adding\n"); + + /* Add this region to device tree */ + type_name = efi_mem_type_name(desc->type); + ret = dt_add_reserved(fdt, start, region_size, + type_name); + if (ret) { + printf("Failed to add region: %s\n", + fdt_strerror(ret)); + free(map); + return ret; + } + synced++; + } else if (verbose) { + printf("\n"); + } + } + free(map); + + return synced; +} + +int efi_mem_reserved_sync(void *fdt, bool verbose) +{ + int synced; + + if (verbose) + printf("Comparing EFI memory-map with reserved-memory\n"); + + synced = sync_to_dt(fdt, verbose); + if (synced < 0) { + printf("Failed to sync EFI reserved regions: error %d\n", + synced); + return synced; + } + + if (verbose) { + printf("Regions added: %d\n", synced); + fdt_print_reserved(fdt); + } + + return synced; +} -- 2.43.0