diff options
| author | Tom Rini <[email protected]> | 2025-03-26 14:07:09 -0600 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2025-03-26 14:07:09 -0600 |
| commit | df11ac859d366d96f749f2b9c3d2d5c564b325bc (patch) | |
| tree | 7f22d24b51d695d43fce63b6a2ebcda582399a16 /lib | |
| parent | b052de94fa14577cb5d9e724edeb09fee674d582 (diff) | |
| parent | 61e0a20aecf4af6cdc223bd8cd1bd82fe5e3d9f6 (diff) | |
Merge tag 'efi-next-26032025' of https://source.denx.de/u-boot/custodians/u-boot-tpm into next
When trying to boot an OS installer or a live image via EFI HTTP the
following happens
- U-Boot downloads the image and mounts it in memory
- The EFI subsystem is invoked and the image is started
- The OS calls ExitBootServices and the memory that holds the mounted
image might get overwritten
This results in installers complaining that they can't find installer
medium or live images complaining they can't find the root filesystem.
ACPI already deals with it by having NFIT and NVDIMM to provide ramdisks
that need to be preserved by the OS. Linux and device trees have support
for persistent memory devices (pmem).
We can use them and inject a pmem node in the DT to preserve memory across the
entire boot sequence. Linux will just create a block device over the reserved
memory and installers/images can re-discover it.
This is what it looks like from the OS perspective:
nd_pmem namespace0.0: unable to guarantee persistence of writes
pmem0: p1 p2 p3
EXT4-fs (pmem0p3): mounted filesystem f40f64a4-5b41-4828-856e-caaae2c1c2a0 r/w with ordered data mode. Quota mode: disabled.
EXT4-fs (pmem0p3): re-mounted f40f64a4-5b41-4828-856e-caaae2c1c2a0 r/w. Quota mode: disabled.
Adding 45052k swap on /dev/pmem0p2. Priority:-2 extents:1 across:45052k SS
root@genericarm64:~# mount | grep pmem
/dev/pmem0p3 on / type ext4 (rw,relatime)
/dev/pmem0p1 on /boot type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro)
It's worth noting that Linux behaves differently with reserved memory
(at least on arm64) and that depends on kernel config options.
CONFIG_ZONE_DEVICES and CONFIG_ARM64_PMEM are such options. It boils down to
how the kernel tries to map pages. If devm_memremap_pages() gets called instead
of devm_memremap() mapping the memory fails.
The only safe way is to remove the memory from the EFI memory map,
rather than defining it as /reserved no-map;/ in the DT.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/efi_loader/efi_bootmgr.c | 25 | ||||
| -rw-r--r-- | lib/efi_loader/efi_helper.c | 85 | ||||
| -rw-r--r-- | lib/efi_loader/efi_memory.c | 34 |
3 files changed, 105 insertions, 39 deletions
diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c index c6124c590d9..f9534ef85ed 100644 --- a/lib/efi_loader/efi_bootmgr.c +++ b/lib/efi_loader/efi_bootmgr.c @@ -18,6 +18,8 @@ #include <efi_loader.h> #include <efi_variable.h> #include <asm/unaligned.h> +#include <linux/kernel.h> +#include <linux/sizes.h> static const struct efi_boot_services *bs; static const struct efi_runtime_services *rs; @@ -348,6 +350,7 @@ static efi_status_t prepare_loaded_image(u16 *label, ulong addr, ulong size, struct efi_device_path **dp, struct udevice **blk) { + u64 pages; efi_status_t ret; struct udevice *ramdisk_blk; @@ -362,13 +365,18 @@ static efi_status_t prepare_loaded_image(u16 *label, ulong addr, ulong size, } /* - * TODO: expose the ramdisk to OS. - * Need to pass the ramdisk information by the architecture-specific - * methods such as 'pmem' device-tree node. + * Linux supports 'pmem' which allows OS installers to find, reclaim + * the mounted images and continue the installation since the contents + * of the pmem region are treated as local media. + * + * The memory regions used for it needs to be carved out of the EFI + * memory map. */ - ret = efi_add_memory_map(addr, size, EFI_RESERVED_MEMORY_TYPE); + pages = efi_size_in_pages(size + (addr & EFI_PAGE_MASK)); + ret = efi_update_memory_map(addr, pages, EFI_CONVENTIONAL_MEMORY, + false, true); if (ret != EFI_SUCCESS) { - log_err("Memory reservation failed\n"); + log_err("Failed to reserve memory\n"); goto err; } @@ -490,6 +498,13 @@ static efi_status_t try_load_from_uri_path(struct efi_device_path_uri *uridp, ret = EFI_INVALID_PARAMETER; goto err; } + /* + * Depending on the kernel configuration, pmem memory areas must be + * page aligned or 2MiB aligned. PowerPC is an exception here and + * requires 16MiB alignment, but since we don't have EFI support for + * it, limit the alignment to 2MiB. + */ + image_size = ALIGN(image_size, SZ_2M); /* * If the file extension is ".iso" or ".img", mount it and try to load diff --git a/lib/efi_loader/efi_helper.c b/lib/efi_loader/efi_helper.c index 04b2efc4a3b..8c32059edda 100644 --- a/lib/efi_loader/efi_helper.c +++ b/lib/efi_loader/efi_helper.c @@ -5,6 +5,7 @@ #define LOG_CATEGORY LOGC_EFI +#include <blkmap.h> #include <bootm.h> #include <env.h> #include <image.h> @@ -454,22 +455,29 @@ efi_status_t efi_env_set_load_options(efi_handle_t handle, */ static efi_status_t copy_fdt(void **fdtp) { - unsigned long fdt_ram_start = -1L, fdt_pages; efi_status_t ret = 0; void *fdt, *new_fdt; - u64 new_fdt_addr; - uint fdt_size; - int i; + static u64 new_fdt_addr; + static efi_uintn_t fdt_pages; + ulong fdt_size; - for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { - u64 ram_start = gd->bd->bi_dram[i].start; - u64 ram_size = gd->bd->bi_dram[i].size; + /* + * Remove the configuration table that might already be + * installed, ignoring EFI_NOT_FOUND if no device-tree + * is installed + */ + efi_install_configuration_table(&efi_guid_fdt, NULL); - if (!ram_size) - continue; + if (new_fdt_addr) { + log_debug("%s: Found allocated memory at %#llx, with %#zx pages\n", + __func__, new_fdt_addr, fdt_pages); - if (ram_start < fdt_ram_start) - fdt_ram_start = ram_start; + ret = efi_free_pages(new_fdt_addr, fdt_pages); + if (ret != EFI_SUCCESS) + log_err("Unable to free up existing FDT memory region\n"); + + new_fdt_addr = 0; + fdt_pages = 0; } /* @@ -485,15 +493,18 @@ static efi_status_t copy_fdt(void **fdtp) &new_fdt_addr); if (ret != EFI_SUCCESS) { log_err("Failed to reserve space for FDT\n"); - goto done; + return ret; } + log_debug("%s: Allocated memory at %#llx, with %#zx pages\n", + __func__, new_fdt_addr, fdt_pages); + new_fdt = (void *)(uintptr_t)new_fdt_addr; memcpy(new_fdt, fdt, fdt_totalsize(fdt)); fdt_set_totalsize(new_fdt, fdt_size); - *fdtp = (void *)(uintptr_t)new_fdt_addr; -done: - return ret; + *fdtp = new_fdt; + + return EFI_SUCCESS; } /** @@ -546,9 +557,6 @@ efi_status_t efi_install_fdt(void *fdt) const char *fdt_opt; uintptr_t fdt_addr; - /* Look for device tree that is already installed */ - if (efi_get_configuration_table(&efi_guid_fdt)) - return EFI_SUCCESS; /* Check if there is a hardware device tree */ fdt_opt = env_get("fdt_addr"); /* Use our own device tree as fallback */ @@ -680,3 +688,44 @@ out: return ret; } + +/** + * pmem_node_efi_memmap_setup() - Add pmem node and tweak EFI memmap + * @fdt: The devicetree to which pmem node is added + * @addr: start address of the pmem node + * @size: size of the memory of the pmem node + * + * The function adds the pmem node to the device-tree along with removing + * the corresponding region from the EFI memory map. Used primarily to + * pass the information of a RAM based ISO image to the OS. + * + * Return: 0 on success, -ve value on error + */ +static int pmem_node_efi_memmap_setup(void *fdt, u64 addr, u64 size) +{ + int ret; + u64 pages; + efi_status_t status; + + ret = fdt_fixup_pmem_region(fdt, addr, size); + if (ret) { + log_err("Failed to setup pmem node for addr %#llx, size %#llx, err %d\n", + addr, size, ret); + return ret; + } + + /* Remove the pmem region from the EFI memory map */ + pages = efi_size_in_pages(size + (addr & EFI_PAGE_MASK)); + status = efi_update_memory_map(addr, pages, EFI_CONVENTIONAL_MEMORY, + false, true); + if (status != EFI_SUCCESS) + return -1; + + return 0; +} + +int fdt_efi_pmem_setup(void *fdt) +{ + return blkmap_get_preserved_pmem_slices(pmem_node_efi_memmap_setup, + fdt); +} diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c index c39b53922bb..0abb1f6159a 100644 --- a/lib/efi_loader/efi_memory.c +++ b/lib/efi_loader/efi_memory.c @@ -258,7 +258,7 @@ static s64 efi_mem_carve_out(struct efi_mem_list *map, } /** - * efi_add_memory_map_pg() - add pages to the memory map + * efi_update_memory_map() - update the memory map by adding/removing pages * * @start: start address, must be a multiple of * EFI_PAGE_SIZE @@ -266,12 +266,11 @@ static s64 efi_mem_carve_out(struct efi_mem_list *map, * @memory_type: type of memory added * @overlap_conventional: region may only overlap free(conventional) * memory + * @remove: remove memory map * Return: status code */ -static -efi_status_t efi_add_memory_map_pg(u64 start, u64 pages, - int memory_type, - bool overlap_conventional) +efi_status_t efi_update_memory_map(u64 start, u64 pages, int memory_type, + bool overlap_conventional, bool remove) { struct efi_mem_list *lmem; struct efi_mem_list *newlist; @@ -279,9 +278,9 @@ efi_status_t efi_add_memory_map_pg(u64 start, u64 pages, uint64_t carved_pages = 0; struct efi_event *evt; - EFI_PRINT("%s: 0x%llx 0x%llx %d %s\n", __func__, + EFI_PRINT("%s: 0x%llx 0x%llx %d %s %s\n", __func__, start, pages, memory_type, overlap_conventional ? - "yes" : "no"); + "yes" : "no", remove ? "remove" : "add"); if (memory_type >= EFI_MAX_MEMORY_TYPE) return EFI_INVALID_PARAMETER; @@ -364,7 +363,10 @@ efi_status_t efi_add_memory_map_pg(u64 start, u64 pages, } /* Add our new map */ - list_add_tail(&newlist->link, &efi_mem); + if (!remove) + list_add_tail(&newlist->link, &efi_mem); + else + free(newlist); /* And make sure memory is listed in descending order */ efi_mem_sort(); @@ -401,7 +403,7 @@ efi_status_t efi_add_memory_map(u64 start, u64 size, int memory_type) pages = efi_size_in_pages(size + (start & EFI_PAGE_MASK)); start &= ~EFI_PAGE_MASK; - return efi_add_memory_map_pg(start, pages, memory_type, false); + return efi_update_memory_map(start, pages, memory_type, false, false); } /** @@ -501,7 +503,7 @@ efi_status_t efi_allocate_pages(enum efi_allocate_type type, efi_addr = (u64)(uintptr_t)map_sysmem(addr, 0); /* Reserve that map in our memory maps */ - ret = efi_add_memory_map_pg(efi_addr, pages, memory_type, true); + ret = efi_update_memory_map(efi_addr, pages, memory_type, true, false); if (ret != EFI_SUCCESS) { /* Map would overlap, bail out */ lmb_free_flags(addr, (u64)pages << EFI_PAGE_SHIFT, flags); @@ -822,8 +824,8 @@ static void add_u_boot_and_runtime(void) uboot_stack_size) & ~EFI_PAGE_MASK; uboot_pages = ((uintptr_t)map_sysmem(gd->ram_top - 1, 0) - uboot_start + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT; - efi_add_memory_map_pg(uboot_start, uboot_pages, EFI_BOOT_SERVICES_CODE, - false); + efi_update_memory_map(uboot_start, uboot_pages, EFI_BOOT_SERVICES_CODE, + false, false); #if defined(__aarch64__) /* * Runtime Services must be 64KiB aligned according to the @@ -841,8 +843,8 @@ static void add_u_boot_and_runtime(void) runtime_end = (uintptr_t)__efi_runtime_stop; runtime_end = (runtime_end + runtime_mask) & ~runtime_mask; runtime_pages = (runtime_end - runtime_start) >> EFI_PAGE_SHIFT; - efi_add_memory_map_pg(runtime_start, runtime_pages, - EFI_RUNTIME_SERVICES_CODE, false); + efi_update_memory_map(runtime_start, runtime_pages, + EFI_RUNTIME_SERVICES_CODE, false, false); } int efi_memory_init(void) @@ -877,11 +879,11 @@ int efi_map_update_notify(phys_addr_t addr, phys_size_t size, pages = efi_size_in_pages(size + (efi_addr & EFI_PAGE_MASK)); efi_addr &= ~EFI_PAGE_MASK; - status = efi_add_memory_map_pg(efi_addr, pages, + status = efi_update_memory_map(efi_addr, pages, op == LMB_MAP_OP_RESERVE ? EFI_BOOT_SERVICES_DATA : EFI_CONVENTIONAL_MEMORY, - false); + false, false); if (status != EFI_SUCCESS) { log_err("LMB Map notify failure %lu\n", status & ~EFI_ERROR_MASK); |
