UefiPayloadPkg supports exactly two bootloaders, Slim Bootloader and coreboot, so anyone who wants to start a UEFIPAYLOAD.fd from inside a running UEFI environment has nowhere to begin. Running it nested is useful on a platform whose own firmware cannot be replaced or lacks the drivers a workload needs, and for testing UefiPayloadPkg under QEMU/OVMF.
Add ChainloadApp, a UEFI application that carries an embedded UEFIPAYLOAD.fd, translates the outer firmware's UEFI memory map, GCD MMIO map, ACPI RSDP and SMBIOS entry point into a Slim Bootloader style HOB list, exits boot services and jumps to the payload's entry point. The FV copy goes to PcdPayloadFdMemBase when that address is available, so that the payload's own PcdPayloadFdMemBase-relative image references stay valid; otherwise it lands anywhere below 4 GiB and ChainloadApp relocates the payload's SEC image in place. The ExtraData HOB carries the actual FV base either way. PeCoffLoaderRelocateImage() reports success without doing anything on a stripped image, so we check RelocationsStripped ourselves and refuse to launch. The FV copy, the HOB list buffer and the payload stack are allocated as EfiReservedMemoryType before the memory map is snapshotted, so the snapshot already describes all three; injecting separate records would duplicate them, and the payload's MemInfoCallbackMmio() builds one HOB per record with no dedup. BuildPayloadHobList() asserts that it really does report them as Reserved, so an outer firmware that does not honour EfiReservedMemoryType fails visibly instead of handing the payload a free-memory map that covers its own FV. A gUniversalPayloadPciRootBridgeInfoGuid HOB is deliberately not emitted: its absence, together with PcdPciDisableBusEnumeration=TRUE (the UefiPayloadPkg default), tells the payload that the outer firmware has already assigned bus numbers, BARs and bridge windows and that it must preserve them. ExitBootServices() returns EFI_INVALID_PARAMETER when a notification function invalidated MapKey, so the call sits in a bounded retry loop. Once the first attempt has been made only GetMemoryMap() and ExitBootServices() may be called (UEFI 2.10 section 7.4.6), so the map buffer is sized with slack up front, and a failure after that point dead-loops because FreePool() is gone too. Only X64 is functional. Entering the payload on AArch64 additionally needs the launcher to own the translation tables it is entered on, which a later change in this series adds; until then the AArch64 build refuses with EFI_UNSUPPORTED. UefiPayloadPkg.dsc sets -mcmodel=tiny for AArch64 under GCC and CLANGDWARF. Its ADR-based +/-1 MiB reach cannot span an embedded payload array merged into .text, so guard those two lines out of the CHAINLOAD_DEFAULTS configuration. Without a generated EmbeddedPayload.h an in-tree stub resolves the payload to zero bytes, so the module builds in CI. DEBUG() goes through the outer firmware's ConOut, because the payload's HOB-driven SerialPortLib has no HOB list to read at that point. 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]> --- .../ChainloadApp/AArch64/PayloadEntry.S | 32 + UefiPayloadPkg/ChainloadApp/ChainloadApp.c | 1280 +++++++++++++++++ UefiPayloadPkg/ChainloadApp/ChainloadApp.inf | 70 + .../ChainloadApp/EmbeddedPayloadStub.h | 20 + .../ChainloadApp/X64/PayloadEntry.nasm | 37 + UefiPayloadPkg/UefiPayloadPkg.dsc | 19 + 6 files changed, 1458 insertions(+) create mode 100644 UefiPayloadPkg/ChainloadApp/AArch64/PayloadEntry.S create mode 100644 UefiPayloadPkg/ChainloadApp/ChainloadApp.c create mode 100644 UefiPayloadPkg/ChainloadApp/ChainloadApp.inf create mode 100644 UefiPayloadPkg/ChainloadApp/EmbeddedPayloadStub.h create mode 100644 UefiPayloadPkg/ChainloadApp/X64/PayloadEntry.nasm diff --git a/UefiPayloadPkg/ChainloadApp/AArch64/PayloadEntry.S b/UefiPayloadPkg/ChainloadApp/AArch64/PayloadEntry.S new file mode 100644 index 0000000000..25be0c66e9 --- /dev/null +++ b/UefiPayloadPkg/ChainloadApp/AArch64/PayloadEntry.S @@ -0,0 +1,32 @@ +/** @file + AArch64 payload entry stub. Masks all interrupts, switches + stack and branches to the payload entry point with the HOB + list in x0. + + The MMU and caches are left ENABLED. ChainloadApp installs its + own translation tables (in EfiReservedMemoryType pages) via + ArmConfigureMmu() while the outer firmware's boot services are + still available, and only branches here once TCR/MAIR/TTBR0 point + at those tables. There is no cache-off window, so no data-cache + maintenance is done here; the caller has already invalidated the + instruction cache over the FV for I/D coherency. + + Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR> + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include <AArch64/AsmMacroLib.h> + +// VOID +// EFIAPI +// JumpToPayload ( +// IN UINTN NewStack, // x0 +// IN UINTN HobList, // x1 +// IN UINTN EntryPoint // x2 +// ); +ASM_FUNC(JumpToPayload) + msr daifset, #0xf + bic x0, x0, #0xf + mov sp, x0 + mov x0, x1 + br x2 diff --git a/UefiPayloadPkg/ChainloadApp/ChainloadApp.c b/UefiPayloadPkg/ChainloadApp/ChainloadApp.c new file mode 100644 index 0000000000..ef1e9c1f51 --- /dev/null +++ b/UefiPayloadPkg/ChainloadApp/ChainloadApp.c @@ -0,0 +1,1280 @@ +/** @file + UEFI-hosted launcher for a Universal Payload firmware volume. + + This application locates a firmware volume embedded in its own image, + builds a Platform-Init HOB list describing the outer firmware's memory + map, ACPI, SMBIOS and PCIe ECAM information, calls ExitBootServices(), + and transfers control to the payload entry point on a fresh stack. + + Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR> + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include <Uefi.h> +#include <Pi/PiDxeCis.h> +#include <Pi/PiHob.h> +#include <Pi/PiFirmwareVolume.h> +#include <Library/UefiLib.h> +#include <Library/UefiBootServicesTableLib.h> +#include <Library/DxeServicesTableLib.h> +#include <Library/BaseMemoryLib.h> +#include <Library/MemoryAllocationLib.h> +#include <Library/BaseLib.h> +#include <Library/DebugLib.h> +#include <Library/PcdLib.h> +#include <Library/CacheMaintenanceLib.h> +#include <Library/PeCoffGetEntryPointLib.h> +#include <Library/PeCoffLib.h> +#include <Guid/Acpi.h> +#include <Guid/SmBios.h> +#include <Guid/MemoryMapInfoGuid.h> +#include <Guid/SerialPortInfoGuid.h> +#include <UniversalPayload/UniversalPayload.h> +#include <UniversalPayload/SerialPortInfo.h> +#include <IndustryStandard/Acpi.h> +#include <IndustryStandard/PeImage.h> +#include <UniversalPayload/ExtraData.h> +#include <UniversalPayload/SmbiosTable.h> +#include <UniversalPayload/AcpiTable.h> + +#if defined (MDE_CPU_X64) + #include <Register/Intel/Cpuid.h> +#endif + +#if defined (MDE_CPU_AARCH64) + #include <Library/ArmLib.h> +#endif + +// +// EmbeddedPayload.h is generated by BuildChainloadEmbedded.sh into +// $(DEBUG_DIR), which the edk2 build system already places on the +// include path. A stub header +// (EmbeddedPayloadStub.h) resolving mPayloadData / mPayloadSize to an +// empty payload lets a plain package build (edk2 CI) succeed; the stub +// payload is rejected at run time with a clear diagnostic. +// +#if defined (__has_include ) + #if __has_include ("EmbeddedPayload.h") + #include "EmbeddedPayload.h" +#define CHAINLOAD_HAVE_EMBEDDED_PAYLOAD 1 + #endif +#endif +#ifndef CHAINLOAD_HAVE_EMBEDDED_PAYLOAD + #include "EmbeddedPayloadStub.h" +#endif + +#define HOB_LIST_PAGES 64 +#define HOB_LIST_SIZE (EFI_PAGES_TO_SIZE (HOB_LIST_PAGES)) +#define PAYLOAD_STACK_PAGES 4 +#define PAYLOAD_STACK_SIZE (EFI_PAGES_TO_SIZE (PAYLOAD_STACK_PAGES)) + +// +// A single HOB's length is UINT16 and must be 8-byte aligned (PI 5.2). +// The largest data payload a GUID HOB can therefore carry: +// +#define MAX_GUID_HOB_DATA_SIZE (0xFFF8 - sizeof (EFI_HOB_GUID_TYPE)) + +// +// Upper bound on MEMORY_MAP_ENTRY records that fit in one GUID HOB. +// +#define MAX_MEM_MAP_ENTRIES \ + ((MAX_GUID_HOB_DATA_SIZE - sizeof (MEMORY_MAP_INFO)) / sizeof (MEMORY_MAP_ENTRY)) + +// +// Headroom, in descriptors, added to the size GetMemoryMap() reports. +// The map can grow between the sizing call and the fetch, because the +// allocations made in between are themselves recorded in it. +// +#define MEM_MAP_SIZING_SLACK 32 + +// +// Headroom for the buffer used by the ExitBootServices() retry loop. +// After the first ExitBootServices() attempt only GetMemoryMap() and +// ExitBootServices() may be called (UEFI 2.10 section 7.4.6), so the +// buffer cannot be resized: it is sized once with enough slack to +// survive every retry. +// +#define MEM_MAP_EXIT_SLACK 128 + +// +// ExitBootServices() returns EFI_INVALID_PARAMETER when an +// ExitBootServices notification changed the memory map and invalidated +// MapKey. Re-fetch the map and retry a bounded number of times. +// +#define EXIT_BOOT_SERVICES_ATTEMPTS 8 + +typedef VOID (EFIAPI *PAYLOAD_ENTRY)(UINTN HobList); + +/** + Architecture-specific stack switch and branch to the payload entry + point. Provided by X64/PayloadEntry.nasm or AArch64/PayloadEntry.S. + + @param[in] NewStack Top of the payload's initial stack (16-byte aligned). + @param[in] HobList Physical address of the handoff HOB list. + @param[in] EntryPoint Physical address of the payload entry point. +**/ +VOID +EFIAPI +JumpToPayload ( + IN UINTN NewStack, + IN UINTN HobList, + IN UINTN EntryPoint + ); + +/** + Reserve one HOB in the HOB list buffer, filling in its generic header + and advancing the write cursor. The requested length is rounded up to + the 8-byte alignment required by PI 5.2 and checked against both the + UINT16 HobLength field and the remaining buffer space. + + @param[in] HobList Base of the HOB list buffer. + @param[in,out] HobOffset Current write cursor; advanced on success. + @param[in] HobLimit Size of the HOB list buffer in bytes. + @param[in] HobType EFI_HOB_TYPE_* value for the new HOB. + @param[in] HobLength Requested HOB length in bytes. + + @retval NULL The HOB does not fit; an error was printed. + @return Pointer to the new HOB, header populated, body zeroed. +**/ +STATIC +VOID * +EmitHob ( + IN VOID *HobList, + IN OUT UINTN *HobOffset, + IN UINTN HobLimit, + IN UINT16 HobType, + IN UINTN HobLength + ) +{ + EFI_HOB_GENERIC_HEADER *Hdr; + UINTN Aligned; + + Aligned = ALIGN_VALUE (HobLength, 8); + + if (Aligned > 0xFFF8) { + Print (L"ChainloadApp: HOB type 0x%x length 0x%lx exceeds UINT16\n", HobType, (UINT64)HobLength); + return NULL; + } + + if ((*HobOffset + Aligned) > HobLimit) { + Print ( + L"ChainloadApp: HOB type 0x%x length 0x%lx overflows list (offset 0x%lx limit 0x%lx)\n", + HobType, + (UINT64)Aligned, + (UINT64)*HobOffset, + (UINT64)HobLimit + ); + return NULL; + } + + Hdr = (EFI_HOB_GENERIC_HEADER *)((UINTN)HobList + *HobOffset); + Hdr->HobType = HobType; + Hdr->HobLength = (UINT16)Aligned; + Hdr->Reserved = 0; + if (Aligned > sizeof (EFI_HOB_GENERIC_HEADER)) { + ZeroMem (Hdr + 1, Aligned - sizeof (EFI_HOB_GENERIC_HEADER)); + } + + *HobOffset += Aligned; + return Hdr; +} + +/** + Reserve one GUID-extension HOB, filling in the header and Name GUID. + + @param[in] HobList Base of the HOB list buffer. + @param[in,out] HobOffset Current write cursor; advanced on success. + @param[in] HobLimit Size of the HOB list buffer in bytes. + @param[in] Guid GUID identifying the HOB payload. + @param[in] DataLength Length in bytes of the caller's payload data. + + @retval NULL The HOB does not fit; an error was printed. + @return Pointer to the zeroed data area following the GUID header. +**/ +STATIC +VOID * +EmitGuidHob ( + IN VOID *HobList, + IN OUT UINTN *HobOffset, + IN UINTN HobLimit, + IN EFI_GUID *Guid, + IN UINTN DataLength + ) +{ + EFI_HOB_GUID_TYPE *GuidHob; + + GuidHob = EmitHob ( + HobList, + HobOffset, + HobLimit, + EFI_HOB_TYPE_GUID_EXTENSION, + sizeof (EFI_HOB_GUID_TYPE) + DataLength + ); + if (GuidHob == NULL) { + return NULL; + } + + CopyGuid (&GuidHob->Name, Guid); + return GuidHob + 1; +} + +/** + Translate an EFI_MEMORY_TYPE from the outer firmware's memory map into + the SBL memory-map HOB Type and Flag fields. + + Device MMIO has no type of its own in a namespace that mirrors the ACPI + Address Range Types, so it is reported as Reserved with + MEM_MAP_FLAG_MMIO set in the Flag byte. A payload that understands the + flag maps the range as device memory; one that predates it ignores the + bit and falls back to its own classification heuristic. + + @param[in] EfiType UEFI memory type. + @param[out] Type SBL memory-map type value (1 = RAM, 2 = Reserved, + 3 = ACPI, 4 = NVS). + @param[out] Flag MEMORY_MAP_ENTRY Flag bits for the range. +**/ +STATIC +VOID +EfiTypeToSblEntry ( + IN EFI_MEMORY_TYPE EfiType, + OUT UINT8 *Type, + OUT UINT8 *Flag + ) +{ + *Flag = 0; + + switch (EfiType) { + case EfiConventionalMemory: + case EfiBootServicesCode: + case EfiBootServicesData: + case EfiLoaderCode: + case EfiLoaderData: + *Type = 1; + break; + case EfiACPIReclaimMemory: + *Type = 3; + break; + case EfiACPIMemoryNVS: + *Type = 4; + break; + case EfiMemoryMappedIO: + case EfiMemoryMappedIOPortSpace: + *Type = 2; + *Flag = MEM_MAP_FLAG_MMIO; + break; + default: + *Type = 2; + break; + } +} + +/** + Return the CPU physical-address width in bits. + + On X64/IA32 the value is taken from CPUID.80000008h:EAX[7:0] after + first verifying that the extended leaf exists via CPUID.80000000h. + On AArch64 the value comes from ID_AA64MMFR0_EL1.PARange via + ArmGetPhysicalAddressBits(). + + @return Physical-address width in bits, or a conservative default of + 36 (X64/IA32) or 48 (AArch64) if the CPU does not report one. +**/ +STATIC +UINT8 +GetPhysicalAddressBits ( + VOID + ) +{ + #if defined (MDE_CPU_X64) + UINT32 MaxExt; + CPUID_VIR_PHY_ADDRESS_SIZE_EAX Eax; + + AsmCpuid (CPUID_EXTENDED_FUNCTION, &MaxExt, NULL, NULL, NULL); + if (MaxExt < CPUID_VIR_PHY_ADDRESS_SIZE) { + return 36; + } + + AsmCpuid (CPUID_VIR_PHY_ADDRESS_SIZE, &Eax.Uint32, NULL, NULL, NULL); + return (UINT8)Eax.Bits.PhysicalAddressBits; + #elif defined (MDE_CPU_AARCH64) + UINTN Bits; + + // + // ArmGetPhysicalAddressBits() returns 0 for the reserved PARange + // encoding 0b0111. Publish a conservative 48 rather than 0. + // + Bits = ArmGetPhysicalAddressBits (); + if (Bits == 0) { + Bits = 48; + } + + return (UINT8)Bits; + #else + return 48; + #endif +} + +/** + Check whether a range is described by the outer firmware's memory-map + snapshot as EfiReservedMemoryType. + + @param[in] MemoryMap Outer firmware's UEFI memory map. + @param[in] MemoryMapSize Size of MemoryMap in bytes. + @param[in] DescriptorSize Size of one EFI_MEMORY_DESCRIPTOR in bytes. + @param[in] Base Base address of the range to look for. + @param[in] Size Size of the range in bytes. + + @retval TRUE One EfiReservedMemoryType descriptor covers the range. + @retval FALSE No single Reserved descriptor covers the range. +**/ +STATIC +BOOLEAN +IsReservedInMemoryMap ( + IN EFI_MEMORY_DESCRIPTOR *MemoryMap, + IN UINTN MemoryMapSize, + IN UINTN DescriptorSize, + IN EFI_PHYSICAL_ADDRESS Base, + IN UINT64 Size + ) +{ + EFI_MEMORY_DESCRIPTOR *Entry; + EFI_PHYSICAL_ADDRESS EntryEnd; + UINTN DescCount; + UINTN Index; + + DescCount = MemoryMapSize / DescriptorSize; + for (Index = 0; Index < DescCount; Index++) { + Entry = (EFI_MEMORY_DESCRIPTOR *)((UINTN)MemoryMap + (Index * DescriptorSize)); + EntryEnd = Entry->PhysicalStart + EFI_PAGES_TO_SIZE (Entry->NumberOfPages); + if ((Entry->Type == EfiReservedMemoryType) && + (Base >= Entry->PhysicalStart) && + ((Base + Size) <= EntryEnd)) + { + return TRUE; + } + } + + return FALSE; +} + +/** + Populate the caller-allocated HOB list buffer with the handoff HOB, CPU + HOB, SBL memory-map GUID HOB, ACPI/SMBIOS information and Universal + Payload extra-data GUID HOBs, terminated with an end-of-list HOB. + + UefiPayloadEntry rebuilds its own resource-descriptor HOB list from + the SBL memory-map GUID HOB (MemInfoCallback), so this function does + not emit EFI_HOB_TYPE_RESOURCE_DESCRIPTOR HOBs itself; the SBL + memory-map GUID HOB is the sole memory-topology contract. + + A gUniversalPayloadPciRootBridgeInfoGuid HOB is deliberately not + emitted: its absence, together with PcdPciDisableBusEnumeration=TRUE + (the UefiPayloadPkg default), is the signal that the outer firmware + has already assigned bus numbers, BARs and bridge windows and that + the payload must preserve them. + + The gUefiAcpiBoardInfoGuid HOB is not emitted: no library constructor + linked into UefiPayloadEntry reads it from the bootloader HOB list + before the HOB-list swap, and the payload derives it from the ACPI + RSDP HOB that is emitted here. + + The FV image, HOB list buffer, payload stack and (on AArch64) the + translation-table pages are allocated as EfiReservedMemoryType + before the caller takes its memory-map snapshot, so the snapshot + already describes all of them and EfiTypeToSblEntry() + maps them to SBL type 2 (Reserved); the payload's free-memory search + therefore cannot select them. No separate records are injected for + them, and this function asserts that the snapshot really does report + them as Reserved. + + @param[in] HobList Pre-allocated HOB list buffer (Reserved memory). + @param[in] HobBufSize Size of HobList in bytes. + @param[in] FvBase Physical address of the copied payload FV. + @param[in] FvSize Size of the copied payload FV in bytes. + @param[in] StackBase Physical base of the payload stack allocation. + @param[in] StackSize Size of the payload stack allocation in bytes. + @param[in] MemoryMap Outer firmware's UEFI memory map. + @param[in] MemoryMapSize Size of MemoryMap in bytes. + @param[in] DescriptorSize Size of one EFI_MEMORY_DESCRIPTOR in bytes. + @param[in] AcpiRsdp Physical address of the ACPI RSDP, or 0. + @param[in] SmbiosTable Physical address of the SMBIOS entry point, or 0. + @param[in] GcdMap GCD memory-space map from the outer firmware. + @param[in] GcdMapCount Number of entries in GcdMap. + + @retval EFI_SUCCESS HOB list built. + @retval EFI_BUFFER_TOO_SMALL A HOB did not fit; the buffer is unusable. +**/ +STATIC +EFI_STATUS +BuildPayloadHobList ( + IN VOID *HobList, + IN UINTN HobBufSize, + IN EFI_PHYSICAL_ADDRESS FvBase, + IN UINT64 FvSize, + IN EFI_PHYSICAL_ADDRESS StackBase, + IN UINTN StackSize, + IN EFI_MEMORY_DESCRIPTOR *MemoryMap, + IN UINTN MemoryMapSize, + IN UINTN DescriptorSize, + IN EFI_PHYSICAL_ADDRESS AcpiRsdp, + IN EFI_PHYSICAL_ADDRESS SmbiosTable, + IN EFI_GCD_MEMORY_SPACE_DESCRIPTOR *GcdMap, + IN UINTN GcdMapCount + ) +{ + EFI_HOB_HANDOFF_INFO_TABLE *HandoffHob; + EFI_HOB_CPU *CpuHob; + EFI_HOB_GENERIC_HEADER *HobEnd; + EFI_MEMORY_DESCRIPTOR *Entry; + MEMORY_MAP_INFO *MemMapInfo; + UNIVERSAL_PAYLOAD_EXTRA_DATA *ExtraData; + UNIVERSAL_PAYLOAD_SMBIOS_TABLE *SmbiosHob; + UNIVERSAL_PAYLOAD_ACPI_TABLE *AcpiHob; + SERIAL_PORT_INFO *SblSerial; + UNIVERSAL_PAYLOAD_SERIAL_PORT_INFO *UplSerial; + UNIVERSAL_PAYLOAD_SERIAL_PORT_INFO Serial; + UINTN HobOffset; + UINTN HobLimit; + UINTN Index; + UINTN EntryIndex; + UINTN MemEntryCount; + UINTN DescCount; + EFI_PHYSICAL_ADDRESS MemoryBottom; + EFI_PHYSICAL_ADDRESS MemoryTop; + EFI_PHYSICAL_ADDRESS RegionEnd; + + HobOffset = 0; + // + // Reserve room for the terminating end-of-list HOB. + // + HobLimit = HobBufSize - ALIGN_VALUE (sizeof (EFI_HOB_GENERIC_HEADER), 8); + DescCount = MemoryMapSize / DescriptorSize; + + // + // Serial console. Default to the legacy 0x3F8 I/O port on X64. On + // other architectures Serial stays zeroed, neither serial HOB is + // emitted below, and the payload falls back to its built-in + // PcdSerialRegisterBase. + // + ZeroMem (&Serial, sizeof (Serial)); + #if defined (MDE_CPU_X64) + Serial.Header.Revision = UNIVERSAL_PAYLOAD_SERIAL_PORT_INFO_REVISION; + Serial.Header.Length = sizeof (Serial); + Serial.UseMmio = FALSE; + Serial.RegisterStride = 1; + Serial.BaudRate = 115200; + Serial.RegisterBase = 0x3F8; + #endif + + // + // Handoff HOB. EfiFreeMemoryTop/Bottom are fixed up after all HOBs + // are emitted; the payload's HobConstructor() establishes its own + // free-memory pool, so no free space is exposed here. + // + HandoffHob = EmitHob (HobList, &HobOffset, HobLimit, EFI_HOB_TYPE_HANDOFF, sizeof (EFI_HOB_HANDOFF_INFO_TABLE)); + if (HandoffHob == NULL) { + return EFI_BUFFER_TOO_SMALL; + } + + MemoryBottom = MAX_UINT64; + MemoryTop = 0; + for (Index = 0; Index < DescCount; Index++) { + Entry = (EFI_MEMORY_DESCRIPTOR *)((UINTN)MemoryMap + (Index * DescriptorSize)); + if ((Entry->Type == EfiConventionalMemory) || + (Entry->Type == EfiBootServicesCode) || + (Entry->Type == EfiBootServicesData)) + { + RegionEnd = Entry->PhysicalStart + EFI_PAGES_TO_SIZE (Entry->NumberOfPages); + if (Entry->PhysicalStart < MemoryBottom) { + MemoryBottom = Entry->PhysicalStart; + } + + if (RegionEnd > MemoryTop) { + MemoryTop = RegionEnd; + } + } + } + + if (MemoryBottom == MAX_UINT64) { + MemoryBottom = SIZE_1MB; + MemoryTop = SIZE_1GB; + } + + HandoffHob->Version = EFI_HOB_HANDOFF_TABLE_VERSION; + HandoffHob->BootMode = BOOT_WITH_FULL_CONFIGURATION; + HandoffHob->EfiMemoryTop = MemoryTop; + HandoffHob->EfiMemoryBottom = MemoryBottom; + + // + // CPU HOB. + // + CpuHob = EmitHob (HobList, &HobOffset, HobLimit, EFI_HOB_TYPE_CPU, sizeof (EFI_HOB_CPU)); + if (CpuHob == NULL) { + return EFI_BUFFER_TOO_SMALL; + } + + CpuHob->SizeOfMemorySpace = GetPhysicalAddressBits (); + CpuHob->SizeOfIoSpace = 16; + + // + // SBL memory-map GUID HOB. The FV image, HOB list buffer, payload + // stack and (on AArch64) the translation-table pages were allocated + // as EfiReservedMemoryType before the caller took its snapshot, so + // the snapshot already reports all of them and + // EfiTypeToSblEntry() maps them to SBL type 2 (Reserved). Emitting + // separate records for them would produce exact duplicates: + // MemInfoCallbackMmio() in the payload calls + // BuildResourceDescriptorHob() once per record with no dedup and no + // overlap check, so on X64 the second copy is silently rejected by + // CoreInternalAddMemorySpace() and on AArch64 ConfigureMmuFromHobs() + // builds overlapping region descriptors from it. Assert the + // expectation rather than adding a second copy. + // + // The GCD MMIO regions are recorded as MMIO, including the ECAM + // window: the payload maps the resulting MEMORY_RESERVED resource as + // DEVICE memory on AArch64. The count is capped so that the HOB never + // exceeds MAX_UINT16. + // + // The condition is evaluated outside the ASSERT so that + // IsReservedInMemoryMap() is still referenced in a RELEASE build, + // where ASSERT() expands to nothing. + // + if ( !IsReservedInMemoryMap ( + MemoryMap, + MemoryMapSize, + DescriptorSize, + FvBase, + ALIGN_VALUE (FvSize, EFI_PAGE_SIZE) + ) || + !IsReservedInMemoryMap ( + MemoryMap, + MemoryMapSize, + DescriptorSize, + (EFI_PHYSICAL_ADDRESS)(UINTN)HobList, + HobBufSize + ) || + !IsReservedInMemoryMap ( + MemoryMap, + MemoryMapSize, + DescriptorSize, + StackBase, + StackSize + ) + ) + { + ASSERT (FALSE); + } + + MemEntryCount = (Serial.UseMmio ? 1 : 0); + for (Index = 0; Index < DescCount; Index++) { + Entry = (EFI_MEMORY_DESCRIPTOR *)((UINTN)MemoryMap + (Index * DescriptorSize)); + if (Entry->NumberOfPages > 0) { + MemEntryCount++; + } + } + + for (Index = 0; Index < GcdMapCount; Index++) { + if ((GcdMap[Index].GcdMemoryType == EfiGcdMemoryTypeMemoryMappedIo) && + (GcdMap[Index].Length > 0)) + { + MemEntryCount++; + } + } + + if (MemEntryCount > MAX_MEM_MAP_ENTRIES) { + // + // Continuing with a truncated map would hand the payload an + // incomplete memory topology, which is worse than not booting. The + // bound is around 2730 records, so reaching it means something is + // badly wrong upstream. + // + Print ( + L"ChainloadApp: memory map needs %lu records, the HOB holds at most %lu\n", + (UINT64)MemEntryCount, + (UINT64)MAX_MEM_MAP_ENTRIES + ); + return EFI_BUFFER_TOO_SMALL; + } + + MemMapInfo = EmitGuidHob ( + HobList, + &HobOffset, + HobLimit, + &gLoaderMemoryMapInfoGuid, + sizeof (MEMORY_MAP_INFO) + (MemEntryCount * sizeof (MEMORY_MAP_ENTRY)) + ); + if (MemMapInfo == NULL) { + return EFI_BUFFER_TOO_SMALL; + } + + MemMapInfo->Revision = 1; + + EntryIndex = 0; + + for (Index = 0; (Index < DescCount) && (EntryIndex < MemEntryCount); Index++) { + Entry = (EFI_MEMORY_DESCRIPTOR *)((UINTN)MemoryMap + (Index * DescriptorSize)); + if (Entry->NumberOfPages == 0) { + continue; + } + + MemMapInfo->Entry[EntryIndex].Base = Entry->PhysicalStart; + MemMapInfo->Entry[EntryIndex].Size = EFI_PAGES_TO_SIZE (Entry->NumberOfPages); + EfiTypeToSblEntry ( + Entry->Type, + &MemMapInfo->Entry[EntryIndex].Type, + &MemMapInfo->Entry[EntryIndex].Flag + ); + EntryIndex++; + } + + for (Index = 0; (Index < GcdMapCount) && (EntryIndex < MemEntryCount); Index++) { + if ((GcdMap[Index].GcdMemoryType != EfiGcdMemoryTypeMemoryMappedIo) || + (GcdMap[Index].Length == 0)) + { + continue; + } + + MemMapInfo->Entry[EntryIndex].Base = GcdMap[Index].BaseAddress; + MemMapInfo->Entry[EntryIndex].Size = GcdMap[Index].Length; + MemMapInfo->Entry[EntryIndex].Type = 2; + MemMapInfo->Entry[EntryIndex].Flag = MEM_MAP_FLAG_MMIO; + EntryIndex++; + } + + // + // Publish the SPCR MMIO UART page so the payload maps it after + // ArmConfigureMmu(): the outer firmware's GCD map does not always + // cover it, and an unmapped serial write after the payload enables + // its own MMU aborts. + // + if (Serial.UseMmio && (Serial.RegisterBase != 0) && (EntryIndex < MemEntryCount)) { + MemMapInfo->Entry[EntryIndex].Base = Serial.RegisterBase & ~(UINT64)EFI_PAGE_MASK; + MemMapInfo->Entry[EntryIndex].Size = EFI_PAGE_SIZE; + MemMapInfo->Entry[EntryIndex].Type = 2; + MemMapInfo->Entry[EntryIndex].Flag = MEM_MAP_FLAG_MMIO; + EntryIndex++; + } + + MemMapInfo->Count = (UINT32)EntryIndex; + + if (Serial.RegisterBase != 0) { + // + // SERIAL_PORT_INFO.BaseAddr is UINT32, but SPCR can legally place an + // MMIO UART above 4 GiB and the Universal Payload HOB emitted below + // carries the full 64-bit value. Truncating would leave the two + // HOBs disagreeing, with the winner decided by which SerialPortLib + // instance the payload's build resolved; the failure mode is then + // not a missing console but MMIO writes to a truncated low address. + // Emit only the 64-bit-capable HOB in that case. + // + if (Serial.RegisterBase > MAX_UINT32) { + Print ( + L"ChainloadApp: serial base 0x%lx exceeds 32 bits; omitting the SBL serial HOB\n", + Serial.RegisterBase + ); + } else { + SblSerial = EmitGuidHob (HobList, &HobOffset, HobLimit, &gUefiSerialPortInfoGuid, sizeof (SERIAL_PORT_INFO)); + if (SblSerial == NULL) { + return EFI_BUFFER_TOO_SMALL; + } + + SblSerial->Revision = 1; + SblSerial->Type = Serial.UseMmio ? PLD_SERIAL_TYPE_MEMORY_MAPPED : PLD_SERIAL_TYPE_IO_MAPPED; + SblSerial->BaseAddr = (UINT32)Serial.RegisterBase; + SblSerial->Baud = Serial.BaudRate; + SblSerial->RegWidth = Serial.RegisterStride; + // + // SPCR carries no clock frequency, so the standard 1.8432 MHz + // 16550 input clock is the only value available here. A platform + // with a non-standard UART clock gets the wrong divisor, and + // therefore garbage output, with nothing to say why. + // + SblSerial->InputHertz = 1843200; + } + + UplSerial = EmitGuidHob ( + HobList, + &HobOffset, + HobLimit, + &gUniversalPayloadSerialPortInfoGuid, + sizeof (UNIVERSAL_PAYLOAD_SERIAL_PORT_INFO) + ); + if (UplSerial == NULL) { + return EFI_BUFFER_TOO_SMALL; + } + + CopyMem (UplSerial, &Serial, sizeof (Serial)); + } + + // + // Universal Payload extra-data HOB carrying the DXE FV location. + // + ExtraData = EmitGuidHob ( + HobList, + &HobOffset, + HobLimit, + &gUniversalPayloadExtraDataGuid, + sizeof (UNIVERSAL_PAYLOAD_EXTRA_DATA) + sizeof (UNIVERSAL_PAYLOAD_EXTRA_DATA_ENTRY) + ); + if (ExtraData == NULL) { + return EFI_BUFFER_TOO_SMALL; + } + + ExtraData->Header.Revision = UNIVERSAL_PAYLOAD_EXTRA_DATA_REVISION; + ExtraData->Header.Length = sizeof (UNIVERSAL_PAYLOAD_EXTRA_DATA) + sizeof (UNIVERSAL_PAYLOAD_EXTRA_DATA_ENTRY); + ExtraData->Count = 1; + CopyMem (ExtraData->Entry[0].Identifier, "uefi_fv", 8); + ExtraData->Entry[0].Base = FvBase; + ExtraData->Entry[0].Size = FvSize; + + if (SmbiosTable != 0) { + SmbiosHob = EmitGuidHob ( + HobList, + &HobOffset, + HobLimit, + &gUniversalPayloadSmbiosTableGuid, + sizeof (UNIVERSAL_PAYLOAD_SMBIOS_TABLE) + ); + if (SmbiosHob == NULL) { + return EFI_BUFFER_TOO_SMALL; + } + + SmbiosHob->Header.Revision = UNIVERSAL_PAYLOAD_SMBIOS_TABLE_REVISION; + SmbiosHob->Header.Length = sizeof (UNIVERSAL_PAYLOAD_SMBIOS_TABLE); + SmbiosHob->SmBiosEntryPoint = SmbiosTable; + } + + AcpiHob = EmitGuidHob ( + HobList, + &HobOffset, + HobLimit, + &gUniversalPayloadAcpiTableGuid, + sizeof (UNIVERSAL_PAYLOAD_ACPI_TABLE) + ); + if (AcpiHob == NULL) { + return EFI_BUFFER_TOO_SMALL; + } + + AcpiHob->Header.Revision = UNIVERSAL_PAYLOAD_ACPI_TABLE_REVISION; + AcpiHob->Header.Length = sizeof (UNIVERSAL_PAYLOAD_ACPI_TABLE); + AcpiHob->Rsdp = AcpiRsdp; + + // + // Terminator. Space was reserved for it via HobLimit. + // + HobEnd = (EFI_HOB_GENERIC_HEADER *)((UINTN)HobList + HobOffset); + HobEnd->HobType = EFI_HOB_TYPE_END_OF_HOB_LIST; + HobEnd->HobLength = (UINT16)sizeof (EFI_HOB_GENERIC_HEADER); + HobEnd->Reserved = 0; + HobOffset += ALIGN_VALUE (sizeof (EFI_HOB_GENERIC_HEADER), 8); + + HandoffHob->EfiEndOfHobList = (EFI_PHYSICAL_ADDRESS)(UINTN)HobEnd; + HandoffHob->EfiFreeMemoryTop = (EFI_PHYSICAL_ADDRESS)((UINTN)HobList + HobOffset); + HandoffHob->EfiFreeMemoryBottom = HandoffHob->EfiFreeMemoryTop; + + return EFI_SUCCESS; +} + +/** + Scan the embedded payload for a firmware-volume header. + + @param[in] PayloadStart Base of the embedded payload image. + @param[in] PayloadSize Size of the embedded payload image in bytes. + + @retval NULL No plausible FV header found. + @return Pointer to the first FV header inside the payload. +**/ +STATIC +EFI_FIRMWARE_VOLUME_HEADER * +FindFvInPayload ( + IN VOID *PayloadStart, + IN UINTN PayloadSize + ) +{ + EFI_FIRMWARE_VOLUME_HEADER *Fv; + UINTN Offset; + + if (PayloadSize < sizeof (EFI_FIRMWARE_VOLUME_HEADER)) { + return NULL; + } + + for (Offset = 0; Offset <= (PayloadSize - sizeof (EFI_FIRMWARE_VOLUME_HEADER)); Offset += 0x10) { + Fv = (EFI_FIRMWARE_VOLUME_HEADER *)((UINTN)PayloadStart + Offset); + if ((Fv->Signature == EFI_FVH_SIGNATURE) && + (Fv->FvLength > SIZE_4KB) && + (Fv->FvLength <= (PayloadSize - Offset))) + { + return Fv; + } + } + + return NULL; +} + +/** + Locate the first PE/COFF image inside a firmware volume by scanning + for a validated PE/COFF header. UEFIPAYLOAD.fd places + UefiPayloadEntry as the first SEC-core FFS file directly after the FV + header. + + A structured FfsFindNextFile()/FfsFindSectionData() walk would be + cleaner but pulls in a FvLib dependency; a scan suffices for the fixed + FV layout produced by UefiPayloadPkg.fdf. To keep the scan from + latching onto something that merely starts with 'MZ' -- the FV header, + the FV extended header and the first FFS file header all live inside + the scanned window -- a candidate is accepted only if the DOS + signature, the e_lfanew bounds, the PE\0\0 signature and the machine + type all check out. If the FDF rule changes so that the first image + is no longer where this expects it, the scan therefore returns NULL + and the caller fails with a diagnostic, rather than handing + PeCoffLoaderGetEntryPoint() a pointer into the middle of a file. + + @param[in] Fv Pointer to the copied firmware volume. + @param[in] Limit Number of bytes to scan. + + @return Pointer to the PE/COFF image, or NULL if not found. +**/ +STATIC +VOID * +FindPeInFv ( + IN VOID *Fv, + IN UINTN Limit + ) +{ + EFI_IMAGE_DOS_HEADER *Dos; + EFI_IMAGE_NT_HEADERS32 *Pe; + UINT8 *Base; + UINTN Offset; + UINTN Remaining; + + Base = (UINT8 *)Fv; + if (Limit < (sizeof (EFI_IMAGE_DOS_HEADER) + sizeof (EFI_IMAGE_NT_HEADERS32))) { + return NULL; + } + + for (Offset = 0; Offset <= (Limit - sizeof (EFI_IMAGE_DOS_HEADER)); Offset += 4) { + Dos = (EFI_IMAGE_DOS_HEADER *)(Base + Offset); + if (Dos->e_magic != EFI_IMAGE_DOS_SIGNATURE) { + continue; + } + + // + // e_lfanew is relative to the DOS header. Require the NT headers to + // start after the DOS header and to lie wholly inside the scanned + // window. EFI_IMAGE_NT_HEADERS32 is used only for its Signature and + // FileHeader, which PE32 and PE32+ share, and its size is a + // conservative lower bound for either. + // + Remaining = Limit - Offset; + if ((Dos->e_lfanew < sizeof (EFI_IMAGE_DOS_HEADER)) || + (Dos->e_lfanew >= Remaining) || + ((Remaining - Dos->e_lfanew) < sizeof (EFI_IMAGE_NT_HEADERS32))) + { + continue; + } + + Pe = (EFI_IMAGE_NT_HEADERS32 *)(Base + Offset + Dos->e_lfanew); + if (Pe->Signature != EFI_IMAGE_NT_SIGNATURE) { + continue; + } + + // + // The payload FV is built for the same ISA as this application, so + // an image for anything else is not the one being looked for. + // + #if defined (MDE_CPU_X64) + if (Pe->FileHeader.Machine != IMAGE_FILE_MACHINE_X64) { + continue; + } + + #elif defined (MDE_CPU_AARCH64) + if (Pe->FileHeader.Machine != IMAGE_FILE_MACHINE_ARM64) { + continue; + } + + #else + #error "Unsupported architecture" + #endif + + return Base + Offset; + } + + return NULL; +} + +/** + Application entry point. Locates the embedded FV, allocates reserved + memory for the FV copy, HOB list and payload stack, builds the HOB + list, calls ExitBootServices() and jumps to the payload. + + @param[in] ImageHandle Handle of this loaded image. + @param[in] SystemTable Pointer to the outer firmware's system table. + + @retval EFI_SUCCESS Never returned; control passes to the payload. + @return Underlying failure status when the payload cannot + be launched. +**/ +EFI_STATUS +EFIAPI +ChainloadEntry ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + EFI_MEMORY_DESCRIPTOR *MemoryMap; + EFI_GCD_MEMORY_SPACE_DESCRIPTOR *GcdMap; + EFI_FIRMWARE_VOLUME_HEADER *EmbeddedFv; + PE_COFF_LOADER_IMAGE_CONTEXT ImageContext; + VOID *HobList; + VOID *EntryPoint; + VOID *PeImage; + EFI_PHYSICAL_ADDRESS FvAddress; + EFI_PHYSICAL_ADDRESS HobAddress; + EFI_PHYSICAL_ADDRESS StackAddress; + EFI_PHYSICAL_ADDRESS AcpiRsdp; + EFI_PHYSICAL_ADDRESS SmbiosTable; + UINTN FvSize; + UINTN FvPages; + UINTN MapKey; + UINTN MemoryMapSize; + UINTN MemoryMapCap; + UINTN DescriptorSize; + UINTN GcdMapCount; + UINTN Index; + UINTN Retry; + UINTN MmioCount; + UINT32 DescriptorVersion; + BOOLEAN FvRelocated; + + MemoryMap = NULL; + GcdMap = NULL; + HobAddress = 0; + StackAddress = 0; + FvRelocated = FALSE; + + Print (L"ChainloadApp: embedded payload %lu bytes\n", (UINT64)mPayloadSize); + + if (mPayloadSize == 0) { + Print (L"ChainloadApp: no embedded payload (stub build); nothing to launch.\n"); + return EFI_NOT_FOUND; + } + + #if defined (MDE_CPU_AARCH64) + // + // The AArch64 translation-table handover is added by a later + // change; refuse cleanly rather than jump without it. + // + Print (L"ChainloadApp: AArch64 handover not yet supported\n"); + return EFI_UNSUPPORTED; + #endif + + EmbeddedFv = FindFvInPayload ((VOID *)mPayloadData, mPayloadSize); + if (EmbeddedFv == NULL) { + Print (L"ChainloadApp: no FV header found in embedded payload\n"); + return EFI_NOT_FOUND; + } + + FvSize = (UINTN)EmbeddedFv->FvLength; + FvPages = EFI_SIZE_TO_PAGES (FvSize); + + // + // Collect ACPI RSDP and SMBIOS entry-point pointers from the outer + // firmware's configuration table. + // + AcpiRsdp = 0; + SmbiosTable = 0; + for (Index = 0; Index < SystemTable->NumberOfTableEntries; Index++) { + if (CompareGuid (&SystemTable->ConfigurationTable[Index].VendorGuid, &gEfiAcpiTableGuid)) { + AcpiRsdp = (EFI_PHYSICAL_ADDRESS)(UINTN)SystemTable->ConfigurationTable[Index].VendorTable; + } else if ((AcpiRsdp == 0) && + CompareGuid (&SystemTable->ConfigurationTable[Index].VendorGuid, &gEfiAcpi10TableGuid)) + { + AcpiRsdp = (EFI_PHYSICAL_ADDRESS)(UINTN)SystemTable->ConfigurationTable[Index].VendorTable; + } else if (CompareGuid (&SystemTable->ConfigurationTable[Index].VendorGuid, &gEfiSmbiosTableGuid) || + CompareGuid (&SystemTable->ConfigurationTable[Index].VendorGuid, &gEfiSmbios3TableGuid)) + { + SmbiosTable = (EFI_PHYSICAL_ADDRESS)(UINTN)SystemTable->ConfigurationTable[Index].VendorTable; + } + } + + Print (L"ChainloadApp: ACPI RSDP 0x%lx SMBIOS 0x%lx\n", AcpiRsdp, SmbiosTable); + + // + // Reserve memory for the FV image at PcdPayloadFdMemBase so that the + // payload's own PcdPayloadFdMemBase-relative image references remain + // valid. If that fixed address is unavailable, fall back to any + // address below 4 GiB and remember that we did: the payload's SEC + // image is then not at the address its relocations were computed for, + // so they are applied in place further down before the entry point is + // used. The ExtraData HOB records the actual FV base, so nothing + // downstream still assumes the PCD value. The HOB list and payload + // stack are also placed below 4 GiB. All three are typed as + // EfiReservedMemoryType so that they survive as SBL type 2 (Reserved) + // in the memory-map HOB. + // + FvAddress = PcdGet32 (PcdPayloadFdMemBase); + Status = gBS->AllocatePages (AllocateAddress, EfiReservedMemoryType, FvPages, &FvAddress); + if (EFI_ERROR (Status)) { + FvAddress = MAX_UINT32; + Status = gBS->AllocatePages (AllocateMaxAddress, EfiReservedMemoryType, FvPages, &FvAddress); + if (EFI_ERROR (Status)) { + Print (L"ChainloadApp: FV AllocatePages failed: %r\n", Status); + return Status; + } + + FvRelocated = TRUE; + + Print ( + L"ChainloadApp: FV relocated to 0x%lx (PcdPayloadFdMemBase 0x%x unavailable)\n", + FvAddress, + PcdGet32 (PcdPayloadFdMemBase) + ); + } + + CopyMem ((VOID *)(UINTN)FvAddress, EmbeddedFv, FvSize); + Print (L"ChainloadApp: FV 0x%lx bytes copied to 0x%lx\n", (UINT64)FvSize, FvAddress); + + HobAddress = MAX_UINT32; + Status = gBS->AllocatePages (AllocateMaxAddress, EfiReservedMemoryType, HOB_LIST_PAGES, &HobAddress); + if (EFI_ERROR (Status)) { + HobAddress = 0; + Print (L"ChainloadApp: HOB-list AllocatePages failed: %r\n", Status); + goto FreeReserved; + } + + HobList = (VOID *)(UINTN)HobAddress; + ZeroMem (HobList, HOB_LIST_SIZE); + + StackAddress = MAX_UINT32; + Status = gBS->AllocatePages (AllocateMaxAddress, EfiReservedMemoryType, PAYLOAD_STACK_PAGES, &StackAddress); + if (EFI_ERROR (Status)) { + StackAddress = 0; + Print (L"ChainloadApp: stack AllocatePages failed: %r\n", Status); + goto FreeReserved; + } + + // + // Fetch the GCD memory space map. On AArch64 it drives the + // ARM_MEMORY_REGION_DESCRIPTOR list handed to ArmConfigureMmu(); on + // both architectures its MMIO entries are published in the SBL + // memory-map HOB. + // + GcdMapCount = 0; + Status = gDS->GetMemorySpaceMap (&GcdMapCount, &GcdMap); + if (EFI_ERROR (Status)) { + Print (L"ChainloadApp: GetMemorySpaceMap failed: %r; continuing without MMIO HOBs\n", Status); + GcdMap = NULL; + GcdMapCount = 0; + } else { + MmioCount = 0; + for (Index = 0; Index < GcdMapCount; Index++) { + if (GcdMap[Index].GcdMemoryType == EfiGcdMemoryTypeMemoryMappedIo) { + MmioCount++; + } + } + + Print (L"ChainloadApp: GCD map %lu entries, %lu MMIO\n", (UINT64)GcdMapCount, (UINT64)MmioCount); + } + + // + // Snapshot the memory map for HOB construction. + // + MemoryMapSize = 0; + Status = gBS->GetMemoryMap (&MemoryMapSize, NULL, &MapKey, &DescriptorSize, &DescriptorVersion); + if (Status != EFI_BUFFER_TOO_SMALL) { + Print (L"ChainloadApp: GetMemoryMap sizing failed: %r\n", Status); + goto FreeReserved; + } + + MemoryMapSize += DescriptorSize * MEM_MAP_SIZING_SLACK; + MemoryMap = AllocatePool (MemoryMapSize); + if (MemoryMap == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto FreeReserved; + } + + Status = gBS->GetMemoryMap (&MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion); + if (EFI_ERROR (Status)) { + Print (L"ChainloadApp: GetMemoryMap failed: %r\n", Status); + goto FreeReserved; + } + + Status = BuildPayloadHobList ( + HobList, + HOB_LIST_SIZE, + FvAddress, + FvSize, + StackAddress, + PAYLOAD_STACK_SIZE, + MemoryMap, + MemoryMapSize, + DescriptorSize, + AcpiRsdp, + SmbiosTable, + GcdMap, + GcdMapCount + ); + FreePool (MemoryMap); + MemoryMap = NULL; + if (GcdMap != NULL) { + FreePool (GcdMap); + GcdMap = NULL; + } + + if (EFI_ERROR (Status)) { + Print (L"ChainloadApp: BuildPayloadHobList failed: %r\n", Status); + goto FreeReserved; + } + + Print (L"ChainloadApp: HOB list at 0x%lx, stack at 0x%lx\n", HobAddress, StackAddress); + + // + // Locate the SEC-core PE/COFF image inside the copied FV, then use + // the standard PE/COFF library to resolve its entry point. + // + PeImage = FindPeInFv ((VOID *)(UINTN)FvAddress, SIZE_4KB); + if (PeImage == NULL) { + Print (L"ChainloadApp: no PE/COFF image found in FV\n"); + Status = EFI_NOT_FOUND; + goto FreeReserved; + } + + if (FvRelocated) { + // + // The FV did not land at PcdPayloadFdMemBase, so the payload's SEC + // image is not at the address its relocations were computed for. + // Apply them in place rather than relying on the image happening to + // have been built position-independent -- that is a property of one + // build of one module with one toolchain, and nothing fails loudly + // if it stops holding. + // + ZeroMem (&ImageContext, sizeof (ImageContext)); + ImageContext.Handle = PeImage; + ImageContext.ImageRead = PeCoffLoaderImageReadFromMemory; + + Status = PeCoffLoaderGetImageInfo (&ImageContext); + if (EFI_ERROR (Status)) { + Print (L"ChainloadApp: PeCoffLoaderGetImageInfo failed: %r\n", Status); + goto FreeReserved; + } + + if (ImageContext.RelocationsStripped) { + // + // PeCoffLoaderRelocateImage() returns success without doing + // anything for an image whose relocations were stripped, so the + // refusal has to be explicit: there is no way to fix the image up, + // and branching into it anyway is a wild branch. + // + Print ( + L"ChainloadApp: payload SEC image has no relocations and the FV is not at 0x%x; refusing to launch\n", + PcdGet32 (PcdPayloadFdMemBase) + ); + Status = EFI_UNSUPPORTED; + goto FreeReserved; + } + + // + // ImageAddress is where the image actually is; PeCoffLoaderRelocateImage() + // fixes up in place using the delta against the header's ImageBase. + // + ImageContext.ImageAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)PeImage; + + Status = PeCoffLoaderRelocateImage (&ImageContext); + if (EFI_ERROR (Status)) { + Print (L"ChainloadApp: PeCoffLoaderRelocateImage failed: %r\n", Status); + goto FreeReserved; + } + + Print (L"ChainloadApp: payload SEC image relocated for 0x%lx\n", (UINT64)(UINTN)PeImage); + } + + EntryPoint = NULL; + Status = PeCoffLoaderGetEntryPoint (PeImage, &EntryPoint); + if (EFI_ERROR (Status) || (EntryPoint == NULL)) { + Print (L"ChainloadApp: PeCoffLoaderGetEntryPoint failed: %r\n", Status); + Status = EFI_NOT_FOUND; + goto FreeReserved; + } + + Print (L"ChainloadApp: entry point 0x%lx\n", (UINT64)(UINTN)EntryPoint); + + // + // Pre-allocate a generously-sized memory-map buffer for the + // ExitBootServices() retry loop. Once the first ExitBootServices() + // attempt has been made, only GetMemoryMap() and ExitBootServices() + // may be called (UEFI 2.10 7.4.6); the loop must not touch the pool. + // + MemoryMapCap = 0; + Status = gBS->GetMemoryMap (&MemoryMapCap, NULL, &MapKey, &DescriptorSize, &DescriptorVersion); + if (Status != EFI_BUFFER_TOO_SMALL) { + goto FreeReserved; + } + + MemoryMapCap += DescriptorSize * MEM_MAP_EXIT_SLACK; + MemoryMap = AllocatePool (MemoryMapCap); + if (MemoryMap == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto FreeReserved; + } + + Print (L"ChainloadApp: calling ExitBootServices\n"); + + Status = EFI_INVALID_PARAMETER; + for (Retry = 0; Retry < EXIT_BOOT_SERVICES_ATTEMPTS; Retry++) { + MemoryMapSize = MemoryMapCap; + Status = gBS->GetMemoryMap (&MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion); + if (EFI_ERROR (Status)) { + break; + } + + Status = gBS->ExitBootServices (ImageHandle, MapKey); + if (!EFI_ERROR (Status)) { + break; + } + } + + if (EFI_ERROR (Status)) { + // + // After the first ExitBootServices() call only GetMemoryMap() and + // ExitBootServices() may be called (UEFI 2.10 section 7.4.6), so + // FreePool()/FreePages() are unsafe here. The platform is + // unbootable regardless; dead-loop rather than leak into a shell + // that may no longer exist. + // + DEBUG ((DEBUG_ERROR, "ChainloadApp: ExitBootServices failed: %r\n", Status)); + CpuDeadLoop (); + } + + JumpToPayload ( + (UINTN)StackAddress + PAYLOAD_STACK_SIZE, + (UINTN)HobList, + (UINTN)EntryPoint + ); + + // + // Never reached. + // + CpuDeadLoop (); + return EFI_SUCCESS; + +FreeReserved: + if (MemoryMap != NULL) { + FreePool (MemoryMap); + } + + if (GcdMap != NULL) { + FreePool (GcdMap); + } + + if (StackAddress != 0) { + gBS->FreePages (StackAddress, PAYLOAD_STACK_PAGES); + } + + if (HobAddress != 0) { + gBS->FreePages (HobAddress, HOB_LIST_PAGES); + } + + gBS->FreePages (FvAddress, FvPages); + return Status; +} diff --git a/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf b/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf new file mode 100644 index 0000000000..fc110ddb34 --- /dev/null +++ b/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf @@ -0,0 +1,70 @@ +## @file +# Chainload Application +# +# Embeds a UniversalPayload binary and chainloads into it. +# The payload must be generated first using GenPayloadHdr.py to create +# EmbeddedPayload.h in the build directory before building this application. +# +# 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 = ChainloadApp + FILE_GUID = 13DCF199-C146-4467-9353-0A601AA148FB + MODULE_TYPE = UEFI_APPLICATION + VERSION_STRING = 1.0 + ENTRY_POINT = ChainloadEntry + +# +# VALID_ARCHITECTURES = X64 AARCH64 +# + +[Sources] + ChainloadApp.c + EmbeddedPayloadStub.h + +[Sources.X64] + X64/PayloadEntry.nasm + +[Sources.AARCH64] + AArch64/PayloadEntry.S + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + UefiPayloadPkg/UefiPayloadPkg.dec + +[LibraryClasses] + UefiApplicationEntryPoint + UefiBootServicesTableLib + DxeServicesTableLib + UefiLib + BaseMemoryLib + MemoryAllocationLib + BaseLib + DebugLib + PcdLib + CacheMaintenanceLib + PeCoffGetEntryPointLib + PeCoffLib + +[LibraryClasses.AARCH64] + ArmLib + +[Guids] + gEfiAcpiTableGuid + gEfiAcpi10TableGuid + gEfiSmbiosTableGuid + gEfiSmbios3TableGuid + gUniversalPayloadExtraDataGuid + gLoaderMemoryMapInfoGuid + gUefiSerialPortInfoGuid + gUniversalPayloadSerialPortInfoGuid + gUniversalPayloadSmbiosTableGuid + gUniversalPayloadAcpiTableGuid + +[Pcd] + gUefiPayloadPkgTokenSpaceGuid.PcdPayloadFdMemBase diff --git a/UefiPayloadPkg/ChainloadApp/EmbeddedPayloadStub.h b/UefiPayloadPkg/ChainloadApp/EmbeddedPayloadStub.h new file mode 100644 index 0000000000..3e0003f7ee --- /dev/null +++ b/UefiPayloadPkg/ChainloadApp/EmbeddedPayloadStub.h @@ -0,0 +1,20 @@ +/** @file + Stub embedded payload for standalone builds of ChainloadApp. + + BuildChainloadEmbedded.sh overrides these definitions by generating + EmbeddedPayload.h in the build output directory; ChainloadApp.c + selects it via __has_include() when present. In a plain UefiPayloadPkg.dsc build the + stub resolves to an empty payload that ChainloadEntry() rejects at + run time with a clear diagnostic. + + Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR> + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef EMBEDDED_PAYLOAD_STUB_H_ +#define EMBEDDED_PAYLOAD_STUB_H_ + +STATIC CONST UINT8 mPayloadData[] = { 0 }; +STATIC CONST UINTN mPayloadSize = 0; + +#endif diff --git a/UefiPayloadPkg/ChainloadApp/X64/PayloadEntry.nasm b/UefiPayloadPkg/ChainloadApp/X64/PayloadEntry.nasm new file mode 100644 index 0000000000..c019a368eb --- /dev/null +++ b/UefiPayloadPkg/ChainloadApp/X64/PayloadEntry.nasm @@ -0,0 +1,37 @@ +;; @file +; X64 payload entry - sets up stack and jumps to payload +; +; Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR> +; SPDX-License-Identifier: BSD-2-Clause-Patent +;; + + SECTION .text + +;------------------------------------------------------------------------------ +; VOID +; EFIAPI +; JumpToPayload ( +; IN UINTN NewStack, // rcx +; IN UINTN HobList, // rdx +; IN UINTN EntryPoint // r8 +; ); +;------------------------------------------------------------------------------ +global ASM_PFX(JumpToPayload) +ASM_PFX(JumpToPayload): + ; + ; Mask interrupts, as the AArch64 stub does. Not a hole today -- + ; CoreExitBootServices() calls gTimer->SetTimerPeriod (gTimer, 0), so + ; the timer is already off -- but a device the outer firmware left + ; armed can still raise an interrupt into an IDT that is about to + ; become the payload's free RAM. + ; + cli + mov rsp, rcx ; Set new stack + and rsp, ~0xF ; Align to 16 bytes + sub rsp, 0x20 ; Shadow space + mov rcx, rdx ; HobList as first arg + call r8 ; Call payload entry + ; Never returns +.loop: + hlt + jmp .loop diff --git a/UefiPayloadPkg/UefiPayloadPkg.dsc b/UefiPayloadPkg/UefiPayloadPkg.dsc index 495bb85868..3f5e2661a4 100644 --- a/UefiPayloadPkg/UefiPayloadPkg.dsc +++ b/UefiPayloadPkg/UefiPayloadPkg.dsc @@ -204,8 +204,10 @@ [BuildOptions.AARCH64] GCC:*_*_*_CC_FLAGS = -mstrict-align +!if $(CHAINLOAD_DEFAULTS) == FALSE GCC:*_GCC_*_CC_FLAGS = -mcmodel=tiny GCC:*_CLANGDWARF_*_CC_FLAGS = -mcmodel=tiny +!endif [BuildOptions.common.EDKII.DXE_RUNTIME_DRIVER] GCC:*_*_*_DLINK_FLAGS = -z common-page-size=0x1000 @@ -1385,3 +1387,20 @@ } !endif + +# +# ChainloadApp: UEFI-hosted payload launcher. Always compiled so CI +# covers it; without a generated EmbeddedPayload.h it links against +# the in-tree stub and refuses to hand off at run time. See +# BuildChainloadEmbedded.sh for the two-stage embedded build. +# +[Components.X64, Components.AARCH64] + UefiPayloadPkg/ChainloadApp/ChainloadApp.inf { + <LibraryClasses> + # + # ChainloadApp runs under the outer firmware and must not pull + # in the payload's HOB-driven SerialPortLib (no HOB list yet). + # Route DEBUG() through the outer firmware's ConOut instead. + # + DebugLib|MdePkg/Library/UefiDebugLibConOut/UefiDebugLibConOut.inf + } -- 2.47.3 -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#122096): https://edk2.groups.io/g/devel/message/122096 Mute This Topic: https://groups.io/mt/120797279/21656 Group Owner: [email protected] Unsubscribe: https://edk2.groups.io/g/devel/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
