ChainloadApp hands the payload over on AArch64 with the MMU and caches enabled, on translation tables it owns. ArmMmuLib allocates every level of that hierarchy through MemoryAllocationLib::AllocatePages(), which the stock UefiMemoryAllocationLib backs with EfiBootServicesData, and the payload's memory-map HOB reports EfiBootServicesData as free RAM, so DXE could allocate straight over the live hierarchy. The handover itself arrives with a later change; its allocator is added first so that it is reviewable on its own.
Add ReservedUefiMemoryAllocationLib, a per-module MemoryAllocationLib override for ChainloadApp that redirects AllocatePages() to EfiReservedMemoryType. Every page it hands out then sits in an isolated Reserved descriptor in the outer memory-map snapshot, never coalesces with adjacent conventional memory, and the payload's HOB-memory search cannot select it. Pool allocations stay EfiBootServicesData: they back short-lived buffers (Print(), the memory-map snapshot) that are freed before the branch. The library implements only the MemoryAllocationLib functions this module links against; the runtime, aligned, copy and reallocate variants are omitted. The library also records the base and page count of every Reserved page allocation it returns, in a 64-entry table; on overflow it warns with DEBUG_WARN and the page stays Reserved. A later change publishes those ranges to the payload as an explicit boot-time reservation list, so the OS reclaims the pages instead of losing them to a Reserved descriptor for the rest of the boot. We use Reserved for placement, not lifetime: in a normal ArmMmuLib-based boot the table pages are ordinary EfiBootServicesData and need not survive ExitBootServices(). Cc: Benjamin Doron <[email protected]> Cc: Gua Guo <[email protected]> Cc: Guo Dong <[email protected]> Cc: James Lu <[email protected]> Cc: Sean Rhodes <[email protected]> Cc: Shuo Liu <[email protected]> Cc: Ard Biesheuvel <[email protected]> Cc: Leif Lindholm <[email protected]> Cc: Sami Mujawar <[email protected]> Cc: Vishal Oliyil Kunnnil <[email protected]> Assisted-by: claude-opus-5 Signed-off-by: Alexander Graf <[email protected]> --- .../ReservedUefiMemoryAllocationLib.c | 191 ++++++++++++++++++ .../ReservedUefiMemoryAllocationLib.inf | 32 +++ UefiPayloadPkg/UefiPayloadPkg.dsc | 7 + 3 files changed, 230 insertions(+) create mode 100644 UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.c create mode 100644 UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.inf diff --git a/UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.c b/UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.c new file mode 100644 index 0000000000..0fc67aa14d --- /dev/null +++ b/UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.c @@ -0,0 +1,191 @@ +/** @file + MemoryAllocationLib instance that keeps ArmMmuLib's translation-table + pages as EfiReservedMemoryType. + + ChainloadApp installs its own translation tables via + ArmConfigureMmu() while the outer firmware is still running, so + that the payload can be entered with the MMU and caches enabled. + ArmMmuLib allocates every level of that hierarchy through + MemoryAllocationLib::AllocatePages(), which the stock + UefiMemoryAllocationLib backs with EfiBootServicesData. After + ExitBootServices() the payload's memory-map HOB reports + EfiBootServicesData as free RAM, so DXE could allocate over the + live tables. Overriding AllocatePages() to EfiReservedMemoryType + keeps every table page in an isolated Reserved descriptor in the + outer memory-map snapshot, so it never coalesces with adjacent + conventional memory and the payload's HOB-memory search cannot + select it. Every Reserved page allocation is also recorded so + ChainloadApp can hand the payload an explicit list of boot-time + reservations for it to publish as SYSTEM_MEMORY pinned by an + EfiBootServicesData allocation HOB, letting the OS reclaim the + table pages after it has installed its own translation. + + Only AllocatePages() is redirected. Pool allocations remain + EfiBootServicesData: they back short-lived buffers (Print(), the + memory-map snapshot) that are freed before the branch and never + carried across the handoff. The library implements only the + MemoryAllocationLib functions this module links against; the + runtime, aligned, copy and reallocate variants are omitted. + + Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR> + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include <Uefi.h> +#include <Library/MemoryAllocationLib.h> +#include <Library/UefiBootServicesTableLib.h> +#include <Library/BaseMemoryLib.h> +#include <Library/DebugLib.h> + +// +// Every Reserved page allocation this library returns is recorded so +// that ChainloadApp can hand the payload an explicit list of the +// launcher's own boot-time-only reservations (the translation-table +// pages ArmMmuLib allocates through here, in addition to the FV, HOB +// list and stack ChainloadApp allocates itself). The library is a +// per-module override for ChainloadApp only, so exporting these as +// plain globals is sufficient; there is no MemoryAllocationLib +// interface for it. +// +#define RESERVED_PAGE_ALLOC_MAX 64 + +EFI_PHYSICAL_ADDRESS gReservedPageAllocBase[RESERVED_PAGE_ALLOC_MAX]; +UINTN gReservedPageAllocPages[RESERVED_PAGE_ALLOC_MAX]; +UINTN gReservedPageAllocCount; + +STATIC +VOID +RecordReservedPageAlloc ( + IN EFI_PHYSICAL_ADDRESS Base, + IN UINTN Pages + ) +{ + if (gReservedPageAllocCount < RESERVED_PAGE_ALLOC_MAX) { + gReservedPageAllocBase[gReservedPageAllocCount] = Base; + gReservedPageAllocPages[gReservedPageAllocCount] = Pages; + gReservedPageAllocCount++; + } else { + DEBUG (( + DEBUG_WARN, + "%a: table full at 0x%Lx (%u pages); page stays Reserved and is " + "not reclaimed to the OS\n", + __func__, + (UINT64)Base, + (UINT32)Pages + )); + } +} + +STATIC +VOID * +InternalAllocatePages ( + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN Pages + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS Memory; + + if (Pages == 0) { + return NULL; + } + + Status = gBS->AllocatePages (AllocateAnyPages, MemoryType, Pages, &Memory); + if (EFI_ERROR (Status)) { + return NULL; + } + + if (MemoryType == EfiReservedMemoryType) { + RecordReservedPageAlloc (Memory, Pages); + } + + return (VOID *)(UINTN)Memory; +} + +VOID * +EFIAPI +AllocatePages ( + IN UINTN Pages + ) +{ + return InternalAllocatePages (EfiReservedMemoryType, Pages); +} + +VOID +EFIAPI +FreePages ( + IN VOID *Buffer, + IN UINTN Pages + ) +{ + EFI_STATUS Status; + + ASSERT (Pages != 0); + Status = gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)Buffer, Pages); + ASSERT_EFI_ERROR (Status); +} + +STATIC +VOID * +InternalAllocatePool ( + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN AllocationSize + ) +{ + EFI_STATUS Status; + VOID *Memory; + + Status = gBS->AllocatePool (MemoryType, AllocationSize, &Memory); + if (EFI_ERROR (Status)) { + Memory = NULL; + } + + return Memory; +} + +VOID * +EFIAPI +AllocatePool ( + IN UINTN AllocationSize + ) +{ + return InternalAllocatePool (EfiBootServicesData, AllocationSize); +} + +STATIC +VOID * +InternalAllocateZeroPool ( + IN EFI_MEMORY_TYPE PoolType, + IN UINTN AllocationSize + ) +{ + VOID *Memory; + + Memory = InternalAllocatePool (PoolType, AllocationSize); + if (Memory != NULL) { + ZeroMem (Memory, AllocationSize); + } + + return Memory; +} + +VOID * +EFIAPI +AllocateZeroPool ( + IN UINTN AllocationSize + ) +{ + return InternalAllocateZeroPool (EfiBootServicesData, AllocationSize); +} + +VOID +EFIAPI +FreePool ( + IN VOID *Buffer + ) +{ + EFI_STATUS Status; + + Status = gBS->FreePool (Buffer); + ASSERT_EFI_ERROR (Status); +} diff --git a/UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.inf b/UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.inf new file mode 100644 index 0000000000..4cba2796aa --- /dev/null +++ b/UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.inf @@ -0,0 +1,32 @@ +## @file +# MemoryAllocationLib whose page allocations are EfiReservedMemoryType. +# +# Overrides UefiMemoryAllocationLib for ChainloadApp so that the +# translation-table pages ArmMmuLib allocates through AllocatePages() +# are Reserved and each allocation is recorded for ChainloadApp to +# publish as a boot-time reservation. Pool allocations remain +# EfiBootServicesData. +# +# Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR> +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = ReservedUefiMemoryAllocationLib + FILE_GUID = 4e4b7c1d-6a34-4a02-9c8e-9c9e9d0e0aa1 + MODULE_TYPE = UEFI_APPLICATION + VERSION_STRING = 1.0 + LIBRARY_CLASS = MemoryAllocationLib|UEFI_APPLICATION + +[Sources] + ReservedUefiMemoryAllocationLib.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + DebugLib + BaseMemoryLib + UefiBootServicesTableLib diff --git a/UefiPayloadPkg/UefiPayloadPkg.dsc b/UefiPayloadPkg/UefiPayloadPkg.dsc index 3f5e2661a4..b6306ac7c3 100644 --- a/UefiPayloadPkg/UefiPayloadPkg.dsc +++ b/UefiPayloadPkg/UefiPayloadPkg.dsc @@ -1403,4 +1403,11 @@ # Route DEBUG() through the outer firmware's ConOut instead. # DebugLib|MdePkg/Library/UefiDebugLibConOut/UefiDebugLibConOut.inf + # + # ArmMmuLib allocates translation-table pages via + # MemoryAllocationLib::AllocatePages(). Redirect those to + # EfiReservedMemoryType so the payload's memory-map HOB reports + # them as Reserved and DXE cannot allocate over the live tables. + # + MemoryAllocationLib|UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.inf } -- 2.47.3 -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#122100): https://edk2.groups.io/g/devel/message/122100 Mute This Topic: https://groups.io/mt/120797293/21656 Group Owner: [email protected] Unsubscribe: https://edk2.groups.io/g/devel/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
