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]]
-=-=-=-=-=-=-=-=-=-=-=-


Reply via email to