This commit is contained in:
Alexander Graf 2026-08-26 22:52:14 +00:00 committed by GitHub
commit 057ae7a3c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 6300 additions and 134 deletions

View file

@ -39,14 +39,25 @@ STATIC UINTN mGicMaxSpiIntId;
STATIC UINTN mGicMaxExtSpiIntId;
/**
* Return the base address of the GIC redistributor for the current CPU
*
* @retval Base address of the associated GIC Redistributor
*/
Return the base address of the GIC redistributor for the current CPU.
@param[in] GicRedistributorBase Base address of the first GIC
redistributor frame in the discovery
range.
@param[out] Base On success, the base address of the GIC
redistributor associated with the
current CPU.
@retval EFI_SUCCESS The redistributor of the current CPU was found.
@retval EFI_DEVICE_ERROR A redistributor frame could not be mapped.
@retval EFI_NOT_FOUND No redistributor frame matches the affinity of
the current CPU.
**/
STATIC
UINTN
EFI_STATUS
GicGetCpuRedistributorBase (
IN UINTN GicRedistributorBase
IN UINTN GicRedistributorBase,
OUT UINTN *Base
)
{
UINTN MpId;
@ -80,14 +91,14 @@ GicGetCpuRedistributorBase (
GicCpuRedistributorBase,
Status
));
ASSERT_EFI_ERROR (Status);
return 0;
return EFI_DEVICE_ERROR;
}
TypeRegister = MmioRead64 (GicCpuRedistributorBase + ARM_GICR_TYPER);
Affinity = ARM_GICR_TYPER_GET_AFFINITY (TypeRegister);
if (Affinity == CpuAffinity) {
return GicCpuRedistributorBase;
*Base = GicCpuRedistributorBase;
return EFI_SUCCESS;
}
// Move to the next GIC Redistributor frame.
@ -102,8 +113,13 @@ GicGetCpuRedistributorBase (
} while ((TypeRegister & ARM_GICR_TYPER_LAST) == 0);
// The Redistributor has not been found for the current CPU
ASSERT_EFI_ERROR (EFI_NOT_FOUND);
return 0;
DEBUG ((
DEBUG_ERROR,
"%a: No GICv3 redistributor found for CPU with affinity 0x%lx\n",
__func__,
(UINT64)CpuAffinity
));
return EFI_NOT_FOUND;
}
typedef enum {
@ -784,7 +800,13 @@ GicV3DxeInitialize (
return Status;
}
mGicRedistributorBase = GicGetCpuRedistributorBase (PcdGet64 (PcdGicRedistributorsBase));
Status = GicGetCpuRedistributorBase (
(UINTN)PcdGet64 (PcdGicRedistributorsBase),
&mGicRedistributorBase
);
if (EFI_ERROR (Status)) {
return Status;
}
RegValue = ArmGicV3GetControlSystemRegisterEnable ();
if ((RegValue & ICC_SRE_EL2_SRE) == 0) {

View file

@ -321,7 +321,8 @@ LibRtcInitialize (
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_STATUS Status;
EFI_GCD_MEMORY_SPACE_DESCRIPTOR Desc;
// Initialize RTC Base Address
mPL031RtcBase = PcdGet32 (PcdPL031RtcBase);
@ -333,7 +334,68 @@ LibRtcInitialize (
SIZE_4KB,
EFI_MEMORY_UC | EFI_MEMORY_RUNTIME | EFI_MEMORY_XP
);
if (EFI_ERROR (Status)) {
if (Status == EFI_ACCESS_DENIED) {
//
// The range is already present in the GCD map, e.g. because a
// preceding platform driver or the payload's own MMIO discovery
// added it. That is only acceptable if the existing descriptor
// is MMIO; refuse to touch anything else.
//
Status = gDS->GetMemorySpaceDescriptor (mPL031RtcBase, &Desc);
if (EFI_ERROR (Status)) {
return Status;
}
//
// AddMemorySpace() reports EFI_ACCESS_DENIED if any part of the
// requested range is already present, while GetMemorySpaceDescriptor()
// only returns the descriptor that contains the base address. The
// descriptor therefore says nothing about the rest of the range: a
// request that straddles this descriptor and another one would be
// accepted on the strength of the first descriptor alone. Require the
// whole range to lie inside it.
//
if ((Desc.BaseAddress > mPL031RtcBase) ||
((Desc.BaseAddress + Desc.Length) <
((UINT64)mPL031RtcBase + SIZE_4KB)))
{
return EFI_ACCESS_DENIED;
}
if ((Desc.GcdMemoryType != EfiGcdMemoryTypeMemoryMappedIo) ||
(Desc.ImageHandle != NULL))
{
return EFI_ACCESS_DENIED;
}
//
// A pre-existing MMIO descriptor need not carry every capability that
// the SetMemorySpaceAttributes() call below requests, and
// CoreSetMemorySpaceAttributes() rejects any attribute that is absent
// from Capabilities with EFI_UNSUPPORTED. Add the full set that is
// about to be requested, not just EFI_MEMORY_RUNTIME.
//
if ((Desc.Capabilities &
(EFI_MEMORY_UC | EFI_MEMORY_RUNTIME | EFI_MEMORY_XP)) !=
(EFI_MEMORY_UC | EFI_MEMORY_RUNTIME | EFI_MEMORY_XP))
{
Status = gDS->SetMemorySpaceCapabilities (
mPL031RtcBase,
SIZE_4KB,
Desc.Capabilities | EFI_MEMORY_UC |
EFI_MEMORY_RUNTIME | EFI_MEMORY_XP
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_WARN,
"%a: SetMemorySpaceCapabilities() failed: %r\n",
__func__,
Status
));
return Status;
}
}
} else if (EFI_ERROR (Status)) {
return Status;
}

View file

@ -0,0 +1,125 @@
#!/bin/bash
## @file
# Build ChainloadApp with embedded payload
#
# Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
##
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Under PACKAGES_PATH or any multi-repo workspace -- the normal
# edk2-platforms arrangement -- UefiPayloadPkg's parent directory is not
# the edk2 root and holds no edksetup.sh. Prefer $WORKSPACE when the
# caller has already sourced edksetup.sh, and only fall back to guessing
# from the script location otherwise.
if [ -n "$WORKSPACE" ]; then
EDK2_DIR="$WORKSPACE"
else
EDK2_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
if [ ! -f "$EDK2_DIR/edksetup.sh" ]; then
echo "Error: WORKSPACE is not set and $EDK2_DIR/edksetup.sh does not exist." >&2
echo "Source edksetup.sh from your edk2 workspace before running this script." >&2
exit 1
fi
cd "$EDK2_DIR"
# edksetup.sh returns non-zero on several paths, including some it
# recovers from, which under set -e would abort here before anything
# useful is printed. Check for the variable it is meant to export
# instead of trusting its exit status.
set +e
# shellcheck disable=SC1091
source edksetup.sh
set -e
if [ -z "$WORKSPACE" ]; then
echo "Error: sourcing $EDK2_DIR/edksetup.sh did not set WORKSPACE." >&2
exit 1
fi
fi
# Build configuration
BUILD_TARGET="${BUILD_TARGET:-RELEASE}"
ARCH="${ARCH:-X64}"
# Select toolchain based on architecture
if [ "$ARCH" = "AARCH64" ]; then
TOOL_CHAIN="${TOOL_CHAIN_TAG:-GCC5}"
export GCC5_AARCH64_PREFIX="${GCC5_AARCH64_PREFIX:-aarch64-unknown-linux-gnu-}"
else
TOOL_CHAIN="${TOOL_CHAIN_TAG:-GCC5}"
fi
cd "$EDK2_DIR"
# Both build passes below share one Build/ output tree, so any
# BUILD_DEFINES entry that changes library selection must be applied
# to both. BUILD_ARCH gives each ISA its own OUTPUT_DIRECTORY so
# concurrent X64 and AArch64 builds do not overwrite each other's
# UEFIPAYLOAD.fd or intermediate objects.
BUILD_DEFINES=(-D BOOTLOADER=SBL
-D TIMER_SUPPORT=LAPIC
-D CHAINLOAD_DEFAULTS=TRUE
-D VIRTIO_ENABLE=TRUE
-D BUILD_ARCH="Legacy${ARCH}")
OUT_DIR="$EDK2_DIR/Build/UefiPayloadPkgLegacy${ARCH}/${BUILD_TARGET}_${TOOL_CHAIN}"
echo "=== Building UniversalPayload ($BUILD_TARGET, $ARCH) ==="
build -p UefiPayloadPkg/UefiPayloadPkg.dsc \
-b "$BUILD_TARGET" \
-t "$TOOL_CHAIN" \
-a "$ARCH" \
"${BUILD_DEFINES[@]}"
PAYLOAD_FD="$OUT_DIR/FV/UEFIPAYLOAD.fd"
if [ ! -f "$PAYLOAD_FD" ]; then
echo "Error: Payload not found at $PAYLOAD_FD"
exit 1
fi
echo "=== Generating embedded payload header ==="
BUILD_DIR="$OUT_DIR/${ARCH}/UefiPayloadPkg/ChainloadApp/ChainloadApp/DEBUG"
mkdir -p "$BUILD_DIR"
PAYLOAD_HDR="$BUILD_DIR/EmbeddedPayload.h"
python3 "$SCRIPT_DIR/ChainloadApp/GenPayloadHdr.py" "$PAYLOAD_FD" "$PAYLOAD_HDR"
# The pass-1 build compiled ChainloadApp.c against the stub header
# before EmbeddedPayload.h existed, and its .deps file does not name
# the header. Remove the pass-1 object so pass 2 recompiles it. This
# is per-arch and per-target, so a concurrent build for another ISA is
# not disturbed (touching the shared source file would be).
rm -f "$OUT_DIR/${ARCH}/UefiPayloadPkg/ChainloadApp/ChainloadApp/OUTPUT/ChainloadApp.obj"
echo "=== Building ChainloadApp ==="
build -p UefiPayloadPkg/UefiPayloadPkg.dsc \
-b "$BUILD_TARGET" \
-t "$TOOL_CHAIN" \
-a "$ARCH" \
"${BUILD_DEFINES[@]}" \
-m UefiPayloadPkg/ChainloadApp/ChainloadApp.inf
CHAINLOAD_EFI="$OUT_DIR/${ARCH}/ChainloadApp.efi"
echo ""
echo "=== Build complete ==="
echo "Payload: $PAYLOAD_FD"
echo "ChainloadApp: $CHAINLOAD_EFI"
echo ""
if [ "$ARCH" = "X64" ]; then
echo "Test with QEMU (X64):"
echo " qemu-system-x86_64 -bios /path/to/OVMF_CODE.fd -m 1G -nographic -enable-kvm \\"
echo " -net none -netdev user,tftp=Build/UefiPayloadPkgLegacy${ARCH}/${BUILD_TARGET}_${TOOL_CHAIN}/${ARCH}/,bootfile=ChainloadApp.efi,id=nd \\"
echo " -device virtio-net-pci,netdev=nd"
else
echo "Test with QEMU (AArch64):"
echo " qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1G -nographic \\"
echo " -bios /path/to/AAVMF_CODE.fd \\"
echo " -net none -netdev user,tftp=Build/UefiPayloadPkgLegacy${ARCH}/${BUILD_TARGET}_${TOOL_CHAIN}/${ARCH}/,bootfile=ChainloadApp.efi,id=nd \\"
echo " -device virtio-net-pci,netdev=nd"
fi

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,77 @@
## @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
PciBarFixup.c
[Sources.X64]
X64/PayloadEntry.nasm
[Sources.AARCH64]
AArch64/PayloadEntry.S
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
UefiPayloadPkg/UefiPayloadPkg.dec
[Packages.AARCH64]
UefiCpuPkg/UefiCpuPkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiBootServicesTableLib
DxeServicesTableLib
UefiLib
BaseMemoryLib
MemoryAllocationLib
BaseLib
DebugLib
PcdLib
CacheMaintenanceLib
PeCoffGetEntryPointLib
PeCoffLib
AcpiTableWalkLib
[LibraryClasses.AARCH64]
ArmLib
ArmMmuLib
[Guids]
gEfiAcpiTableGuid
gEfiAcpi10TableGuid
gEfiSmbiosTableGuid
gEfiSmbios3TableGuid
gUniversalPayloadExtraDataGuid
gLoaderMemoryMapInfoGuid
gLoaderBootTimeReservationGuid
gUefiSerialPortInfoGuid
gUniversalPayloadSerialPortInfoGuid
gUniversalPayloadSmbiosTableGuid
gUniversalPayloadAcpiTableGuid
[Pcd]
gUefiPayloadPkgTokenSpaceGuid.PcdPayloadFdMemBase

View file

@ -0,0 +1,17 @@
/** @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
**/
#pragma once
STATIC CONST UINT8 mPayloadData[] = { 0 };
STATIC CONST UINTN mPayloadSize = 0;

View file

@ -0,0 +1,61 @@
#!/usr/bin/env python3
## @file
# Generate C header with embedded payload binary
#
# Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
##
import sys
import os
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <input.fd> <output.h>", file=sys.stderr)
return 1
input_file = sys.argv[1]
output_file = sys.argv[2]
if not os.path.exists(input_file):
print(f"Error: Input file '{input_file}' not found", file=sys.stderr)
return 1
with open(input_file, 'rb') as f:
data = f.read()
with open(output_file, 'w') as f:
f.write("// Auto-generated - do not edit\n")
f.write(f"// Source: {os.path.basename(input_file)}\n\n")
#
# FindFvInPayload() casts addresses inside this array to
# EFI_FIRMWARE_VOLUME_HEADER * and reads a UINT64 field from
# them, so the array needs a stated alignment rather than
# whatever a compiler happens to give a large object.
#
f.write("#if defined (_MSC_VER)\n")
f.write("#define CHAINLOAD_PAYLOAD_ALIGN __declspec (align (8))\n")
f.write("#else\n")
f.write("#define CHAINLOAD_PAYLOAD_ALIGN __attribute__ ((aligned (8)))\n")
f.write("#endif\n\n")
f.write("CHAINLOAD_PAYLOAD_ALIGN STATIC CONST UINT8 mPayloadData[] = {\n")
for i, byte in enumerate(data):
if i % 16 == 0:
f.write(" ")
f.write(f"0x{byte:02X},")
if i % 16 == 15:
f.write("\n")
else:
f.write(" ")
if len(data) % 16 != 0:
f.write("\n")
f.write("};\n\n")
f.write("STATIC CONST UINTN mPayloadSize = sizeof(mPayloadData);\n")
print(f"Generated {output_file} ({len(data)} bytes)")
return 0
if __name__ == '__main__':
sys.exit(main())

View file

@ -0,0 +1,844 @@
/** @file
Program endpoint BARs that the outer firmware left at zero, before
the payload's light enumeration reads them.
When PcdPciDisableBusEnumeration is TRUE the payload's PciBusDxe
trusts the bus numbers and bridge windows it finds and skips full
resource allocation. Some outer firmware nevertheless leaves the
BARs of particular endpoints at zero even though the parent PCI to
PCI bridge's non-prefetchable memory window is programmed and
forwarding. A downstream driver that later calls
PciIo->GetBarAttributes() on such a BAR trips the translation offset
ASSERT in PciIo.c on DEBUG builds and gets EFI_UNSUPPORTED on RELEASE
builds, so the device is unreachable either way.
The outer firmware's EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL cannot be used
for this repair: its root bridge takes its bus range from the
platform bus-range description, and on the platforms that motivate
this fixup that range is [00,00] while the endpoints needing repair
sit in a disjoint bus tree starting at bus 1 that no bridge on bus 0
leads to. The protocol's config accessors reject any bus outside
the declared range with EFI_INVALID_PARAMETER, so those buses are
unreachable through the protocol by construction. The payload
itself later reaches them by scanning ECAM directly and synthesizing
one root bridge per disjoint tree (ScanForRootBridges), so this
fixup does the same: locate the ECAM aperture from the ACPI MCFG
the outer firmware publishes, walk configuration space through
ECAM, group buses into disjoint trees the way the payload will, and
program any 32-bit non-prefetchable MEM BAR that reads back as zero
from the unused tail of its parent bridge's non-prefetchable
window. By the time the payload's PciBusDxe runs, the BARs are
simply programmed, as if the outer firmware had done its whole job;
neither the payload nor the PCI core needs any change.
A zero-BAR endpoint was unreachable under the outer firmware for the
same reason it would be unreachable under the payload, so no outer
driver can have it open, and no outer agent is doing MMIO to it
while its BARs move. Config-space sizing (the all-ones write) and
programming still happen at TPL_HIGH_LEVEL with memory decode
disabled, so a timer callback cannot observe a half-sized or
half-programmed device.
The fixup is deliberately narrow: only 32-bit non-prefetchable MEM
BARs on Type 0 endpoints under a PCI to PCI bridge with a valid non
prefetchable window are touched. Root-bus endpoints, the PPB's own
two BARs, prefetchable BARs, 64-bit BARs and I/O BARs are all left
as the outer firmware programmed them. A programmed Expansion ROM
BAR or a CardBus bridge child causes the whole parent bridge to be
skipped. The allocator is a bottom-up watermark inside the parent
window; it assumes the outer firmware allocated bottom-up, which is
true of the platforms that motivate this fixup, and is stated here
because it is a platform assumption rather than a spec requirement.
Copyright (c) 2026, Amazon.com, Inc. or its affiliates.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi.h>
#include <IndustryStandard/Acpi.h>
#include <IndustryStandard/Pci.h>
#include <IndustryStandard/MemoryMappedConfigurationSpaceAccessTable.h>
#include <Library/AcpiTableWalkLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
//
// The ECAM aperture of the MCFG allocation group currently being
// walked. mEcamBase already accounts for the group's StartBusNumber,
// so EcamAddress() takes absolute bus numbers.
//
STATIC UINTN mEcamBase;
STATIC UINT8 mEcamStartBus;
STATIC UINT8 mEcamEndBus;
//
// Description of one BAR read from configuration space.
//
typedef struct {
UINT16 Offset;
BOOLEAN IsMem;
BOOLEAN IsMem64;
BOOLEAN IsPref;
UINT64 Base;
UINT64 Length;
UINT64 Alignment;
} BAR_INFO;
/**
Compute the ECAM address of a configuration space register.
@param[in] Bus PCI bus number (absolute).
@param[in] Dev PCI device number.
@param[in] Func PCI function number.
@param[in] Offset Byte offset into configuration space.
@return CPU address of the register in the ECAM aperture.
**/
STATIC
UINTN
EcamAddress (
IN UINT8 Bus,
IN UINT8 Dev,
IN UINT8 Func,
IN UINT16 Offset
)
{
return mEcamBase +
(((UINTN)Bus - mEcamStartBus) << 20) +
((UINTN)Dev << 15) +
((UINTN)Func << 12) +
Offset;
}
/**
Read a naturally aligned 32-bit configuration space register.
@param[in] Bus PCI bus number (absolute).
@param[in] Dev PCI device number.
@param[in] Func PCI function number.
@param[in] Offset Byte offset into configuration space.
@return The register value.
**/
STATIC
UINT32
EcamRead32 (
IN UINT8 Bus,
IN UINT8 Dev,
IN UINT8 Func,
IN UINT16 Offset
)
{
UINT32 Value;
MemoryFence ();
Value = *(volatile UINT32 *)EcamAddress (Bus, Dev, Func, Offset);
MemoryFence ();
return Value;
}
/**
Write a naturally aligned 32-bit configuration space register.
@param[in] Bus PCI bus number (absolute).
@param[in] Dev PCI device number.
@param[in] Func PCI function number.
@param[in] Offset Byte offset into configuration space.
@param[in] Value The value to write.
**/
STATIC
VOID
EcamWrite32 (
IN UINT8 Bus,
IN UINT8 Dev,
IN UINT8 Func,
IN UINT16 Offset,
IN UINT32 Value
)
{
MemoryFence ();
*(volatile UINT32 *)EcamAddress (Bus, Dev, Func, Offset) = Value;
MemoryFence ();
}
/**
Read a naturally aligned 16-bit configuration space register.
@param[in] Bus PCI bus number (absolute).
@param[in] Dev PCI device number.
@param[in] Func PCI function number.
@param[in] Offset Byte offset into configuration space.
@return The register value.
**/
STATIC
UINT16
EcamRead16 (
IN UINT8 Bus,
IN UINT8 Dev,
IN UINT8 Func,
IN UINT16 Offset
)
{
UINT16 Value;
MemoryFence ();
Value = *(volatile UINT16 *)EcamAddress (Bus, Dev, Func, Offset);
MemoryFence ();
return Value;
}
/**
Write a naturally aligned 16-bit configuration space register.
COMMAND must be written with a true 16-bit access: a 32-bit
read-modify-write of the COMMAND/STATUS dword would write back the
RW1C STATUS bits currently set and clear them as a side effect.
@param[in] Bus PCI bus number (absolute).
@param[in] Dev PCI device number.
@param[in] Func PCI function number.
@param[in] Offset Byte offset into configuration space.
@param[in] Value The value to write.
**/
STATIC
VOID
EcamWrite16 (
IN UINT8 Bus,
IN UINT8 Dev,
IN UINT8 Func,
IN UINT16 Offset,
IN UINT16 Value
)
{
MemoryFence ();
*(volatile UINT16 *)EcamAddress (Bus, Dev, Func, Offset) = Value;
MemoryFence ();
}
/**
Read a device's configuration header and probe its BARs.
Both Type 0 and Type 1 headers are handled. A BAR whose read-only
low nibble marks it as I/O is recorded with IsMem = FALSE. For each
memory BAR the current base and, by writing all-ones and reading
back, the natural size and alignment are recorded. The original BAR
contents are restored before return.
Sizing happens at TPL_HIGH_LEVEL: the outer firmware is live, and a
timer callback that touches the device between the all-ones write
and the restore would observe a BAR pointing at nothing.
@param[in] Bus PCI bus number of the device.
@param[in] Dev PCI device number.
@param[in] Func PCI function number.
@param[out] Hdr The full 64-byte common configuration header.
@param[out] Bar Array of PCI_MAX_BAR BAR_INFO records. Only the
first two entries are meaningful for a Type 1
header.
@retval EFI_SUCCESS The header and BARs were read.
@retval EFI_NOT_FOUND No device responds at Bus/Dev/Func.
**/
STATIC
EFI_STATUS
ReadDevice (
IN UINT8 Bus,
IN UINT8 Dev,
IN UINT8 Func,
OUT PCI_TYPE01 *Hdr,
OUT BAR_INFO *Bar
)
{
UINT32 *Raw;
UINT32 Original[2];
UINT32 Value;
UINT32 Mask;
UINT16 VendorId;
UINT8 BarCount;
UINT16 Offset;
UINT8 Idx;
UINTN Word;
EFI_TPL OldTpl;
VendorId = EcamRead16 (Bus, Dev, Func, PCI_VENDOR_ID_OFFSET);
if (VendorId == 0xFFFF) {
return EFI_NOT_FOUND;
}
Raw = (UINT32 *)Hdr;
for (Word = 0; Word < sizeof (PCI_TYPE01) / sizeof (UINT32); Word++) {
Raw[Word] = EcamRead32 (Bus, Dev, Func, (UINT16)(Word * sizeof (UINT32)));
}
ZeroMem (Bar, PCI_MAX_BAR * sizeof (BAR_INFO));
if (IS_CARDBUS_BRIDGE (Hdr)) {
return EFI_SUCCESS;
}
BarCount = IS_PCI_BRIDGE (Hdr) ? 2 : PCI_MAX_BAR;
Offset = (UINT16)OFFSET_OF (PCI_TYPE00, Device.Bar[0]);
OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
for (Idx = 0; Idx < BarCount; Idx++) {
Bar[Idx].Offset = Offset;
Original[0] = EcamRead32 (Bus, Dev, Func, Offset);
Original[1] = 0;
if ((Original[0] & BIT0) != 0) {
//
// I/O space BAR: not touched by this fixup.
//
Bar[Idx].IsMem = FALSE;
Offset += 4;
continue;
}
Bar[Idx].IsMem = TRUE;
Bar[Idx].IsMem64 = ((Original[0] & (BIT1 | BIT2)) == BIT2);
Bar[Idx].IsPref = ((Original[0] & BIT3) != 0);
EcamWrite32 (Bus, Dev, Func, Offset, MAX_UINT32);
Value = EcamRead32 (Bus, Dev, Func, Offset);
EcamWrite32 (Bus, Dev, Func, Offset, Original[0]);
Mask = Value & 0xFFFFFFF0U;
if (Bar[Idx].IsMem64) {
Original[1] = EcamRead32 (Bus, Dev, Func, Offset + 4);
EcamWrite32 (Bus, Dev, Func, Offset + 4, MAX_UINT32);
Value = EcamRead32 (Bus, Dev, Func, Offset + 4);
EcamWrite32 (Bus, Dev, Func, Offset + 4, Original[1]);
Bar[Idx].Base = ((UINT64)Original[1] << 32) | (Original[0] & 0xFFFFFFF0U);
Bar[Idx].Length = ~(((UINT64)Value << 32) | Mask) + 1;
} else {
Bar[Idx].Base = Original[0] & 0xFFFFFFF0U;
Bar[Idx].Length = (~Mask) + 1;
}
if (Mask == 0) {
Bar[Idx].Length = 0;
}
Bar[Idx].Alignment = Bar[Idx].Length - 1;
if (Bar[Idx].IsMem64) {
Idx++;
Bar[Idx].Offset = Offset + 4;
Offset += 8;
} else {
Offset += 4;
}
}
gBS->RestoreTPL (OldTpl);
return EFI_SUCCESS;
}
/**
Walk one bus, recursing into any PCI to PCI bridge whose non
prefetchable memory window is programmed, and program 32-bit non
prefetchable MEM BARs that read back as zero from the unused tail of
the parent bridge's non-prefetchable window.
Space already occupied by sub-bridge non-prefetchable and
prefetchable windows and by sibling BARs is subtracted first, so the
fixup never overlaps a region the outer firmware already handed to
another device. Each fixup is atomic per device: all zero-base MEM
BARs on the device are placed before COMMAND is touched, and if any
is not a 32-bit non-prefetchable BAR that fits the remaining window
the device is left exactly as found.
@param[in] Bus The bus to walk.
@param[in] WindowBase Base of the enclosing PPB non-prefetchable
window, or MAX_UINT64 for the root bus.
@param[in] WindowLimit Inclusive limit of the same window, or 0 for
the root bus.
**/
STATIC
VOID
WalkBus (
IN UINT8 Bus,
IN UINT64 WindowBase,
IN UINT64 WindowLimit
)
{
EFI_STATUS Status;
PCI_TYPE01 Hdr;
BAR_INFO Bar[PCI_MAX_BAR];
UINT64 SubBase;
UINT64 SubLimit;
UINT64 Base;
UINT64 End;
UINT64 FreeBase;
UINT64 TryBase;
UINT64 Size;
UINT64 NewBase[PCI_MAX_BAR];
UINT32 Bar32;
UINT32 ReadBack;
UINT32 RomBase;
UINT16 MemoryBase;
UINT16 MemoryLimit;
UINT16 Command;
UINT8 Dev;
UINT8 Func;
UINT8 BarIdx;
UINT8 Idx2;
BOOLEAN NeedFixup;
BOOLEAN Unfixable;
BOOLEAN WriteFailed;
BOOLEAN Skip;
EFI_TPL OldTpl;
//
// Pass 1: find the highest already-occupied address inside this
// window so that fresh allocations start above it. Occupancy covers
// sub-bridge non-prefetch and prefetch windows and sibling BARs. A
// programmed Expansion ROM BAR or a CardBus bridge child causes the
// whole parent bridge to be skipped.
//
Skip = FALSE;
FreeBase = WindowBase;
for (Dev = 0; Dev <= PCI_MAX_DEVICE; Dev++) {
for (Func = 0; Func <= PCI_MAX_FUNC; Func++) {
Status = ReadDevice (Bus, Dev, Func, &Hdr, Bar);
if (EFI_ERROR (Status)) {
if (Func == 0) {
break;
}
continue;
}
if (IS_CARDBUS_BRIDGE (&Hdr)) {
DEBUG ((
DEBUG_WARN,
"ChainloadApp: [%02x|%02x|%02x] CardBus child; skipping bridge\n",
Bus,
Dev,
Func
));
Skip = TRUE;
} else if (IS_PCI_BRIDGE (&Hdr)) {
SubBase = ((UINT64)Hdr.Bridge.MemoryBase & 0xFFF0) << 16;
SubLimit = (((UINT64)Hdr.Bridge.MemoryLimit & 0xFFF0) << 16) | 0xFFFFF;
if (SubBase <= SubLimit) {
End = SubLimit + 1;
if ((End > WindowBase) && (SubBase <= WindowLimit) && (End > FreeBase)) {
FreeBase = End;
}
}
SubBase = ((UINT64)Hdr.Bridge.PrefetchableBaseUpper32 << 32) |
(((UINT64)Hdr.Bridge.PrefetchableMemoryBase & 0xFFF0) << 16);
SubLimit = ((UINT64)Hdr.Bridge.PrefetchableLimitUpper32 << 32) |
(((UINT64)Hdr.Bridge.PrefetchableMemoryLimit & 0xFFF0) << 16) | 0xFFFFF;
if (SubBase <= SubLimit) {
End = SubLimit + 1;
if ((End > WindowBase) && (SubBase <= WindowLimit) && (End > FreeBase)) {
FreeBase = End;
}
}
RomBase = Hdr.Bridge.ExpansionRomBAR & 0xFFFFF800U;
if (RomBase != 0) {
DEBUG ((
DEBUG_WARN,
"ChainloadApp: [%02x|%02x|%02x] bridge Expansion ROM at 0x%x; skipping bridge\n",
Bus,
Dev,
Func,
RomBase
));
Skip = TRUE;
}
} else {
RomBase = ((PCI_TYPE00 *)&Hdr)->Device.ExpansionRomBar & 0xFFFFF800U;
if (RomBase != 0) {
DEBUG ((
DEBUG_WARN,
"ChainloadApp: [%02x|%02x|%02x] Expansion ROM at 0x%x; skipping bridge\n",
Bus,
Dev,
Func,
RomBase
));
Skip = TRUE;
}
}
for (BarIdx = 0; BarIdx < PCI_MAX_BAR; BarIdx++) {
if (Bar[BarIdx].IsMem && (Bar[BarIdx].Length != 0) && (Bar[BarIdx].Base != 0)) {
Base = Bar[BarIdx].Base;
End = Base + MAX (Bar[BarIdx].Alignment + 1, Bar[BarIdx].Length);
if ((End > WindowBase) && (Base <= WindowLimit) && (End > FreeBase)) {
FreeBase = End;
}
}
}
if ((Func == 0) && !IS_PCI_MULTI_FUNC (&Hdr)) {
break;
}
}
}
//
// Pass 2: recurse into sub-bridges with their own window, and assign
// BARs on direct-child endpoints that still read back as zero.
//
for (Dev = 0; Dev <= PCI_MAX_DEVICE; Dev++) {
for (Func = 0; Func <= PCI_MAX_FUNC; Func++) {
Status = ReadDevice (Bus, Dev, Func, &Hdr, Bar);
if (EFI_ERROR (Status)) {
if (Func == 0) {
break;
}
continue;
}
if (IS_PCI_BRIDGE (&Hdr)) {
MemoryBase = Hdr.Bridge.MemoryBase;
MemoryLimit = Hdr.Bridge.MemoryLimit;
if ((MemoryBase != 0) || (MemoryLimit != 0)) {
SubBase = ((UINT64)MemoryBase & 0xFFF0) << 16;
SubLimit = (((UINT64)MemoryLimit & 0xFFF0) << 16) | 0xFFFFF;
if ((SubBase != 0) &&
((MemoryLimit & 0xFFF0) >= (MemoryBase & 0xFFF0)) &&
(Hdr.Bridge.SecondaryBus != 0) &&
(Hdr.Bridge.SecondaryBus > Bus) &&
(Hdr.Bridge.SecondaryBus <= mEcamEndBus))
{
if ((WindowBase <= WindowLimit) &&
((SubBase < WindowBase) || (SubLimit > WindowLimit)))
{
DEBUG ((
DEBUG_WARN,
"ChainloadApp: [%02x|%02x|%02x] sub-window [0x%Lx,0x%Lx] not inside "
"parent [0x%Lx,0x%Lx]; skipping\n",
Bus,
Dev,
Func,
SubBase,
SubLimit,
WindowBase,
WindowLimit
));
} else {
WalkBus (Hdr.Bridge.SecondaryBus, SubBase, SubLimit);
}
}
}
goto NextFunc;
}
if (IS_CARDBUS_BRIDGE (&Hdr)) {
goto NextFunc;
}
//
// Endpoint. Root-bus endpoints have no enclosing PPB window and
// are skipped (WindowBase > WindowLimit at the root). So is the
// whole bridge if pass 1 found an Expansion ROM or CardBus child.
//
if (Skip || (WindowBase > WindowLimit)) {
goto NextFunc;
}
//
// Collect every zero-base MEM BAR on this device. If any of
// them is not a 32-bit non-prefetchable BAR that fits the
// remaining window, skip the device entirely and leave COMMAND
// exactly as found.
//
ZeroMem (NewBase, sizeof (NewBase));
NeedFixup = FALSE;
Unfixable = FALSE;
TryBase = FreeBase;
for (BarIdx = 0; BarIdx < PCI_MAX_BAR; BarIdx++) {
if (!Bar[BarIdx].IsMem || (Bar[BarIdx].Length == 0) || (Bar[BarIdx].Base != 0)) {
continue;
}
NeedFixup = TRUE;
if (Bar[BarIdx].IsMem64 || Bar[BarIdx].IsPref) {
Unfixable = TRUE;
break;
}
Size = MAX (Bar[BarIdx].Alignment + 1, Bar[BarIdx].Length);
TryBase = ALIGN_VALUE (TryBase, Bar[BarIdx].Alignment + 1);
if ((TryBase == 0) || ((TryBase + Size - 1) > WindowLimit)) {
Unfixable = TRUE;
break;
}
NewBase[BarIdx] = TryBase;
TryBase += Size;
}
if (!NeedFixup) {
goto NextFunc;
}
if (Unfixable) {
DEBUG ((
DEBUG_WARN,
"ChainloadApp: [%02x|%02x|%02x] unassigned MEM BAR not fixable in window "
"[0x%Lx,0x%Lx]; skipping device\n",
Bus,
Dev,
Func,
WindowBase,
WindowLimit
));
goto NextFunc;
}
//
// Raise TPL so no timer callback pokes the device while its BARs
// are being rewritten with MSE clear. Keep the raised window to
// config accesses only; DEBUG output happens after RestoreTPL.
//
WriteFailed = FALSE;
OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
Command = Hdr.Hdr.Command & ~(UINT16)EFI_PCI_COMMAND_MEMORY_SPACE;
EcamWrite16 (Bus, Dev, Func, PCI_COMMAND_OFFSET, Command);
for (BarIdx = 0; BarIdx < PCI_MAX_BAR; BarIdx++) {
if (NewBase[BarIdx] == 0) {
continue;
}
Bar32 = (UINT32)NewBase[BarIdx];
EcamWrite32 (Bus, Dev, Func, Bar[BarIdx].Offset, Bar32);
ReadBack = EcamRead32 (Bus, Dev, Func, Bar[BarIdx].Offset);
if ((ReadBack & ~0xFU) != (Bar32 & ~0xFU)) {
WriteFailed = TRUE;
break;
}
}
if (WriteFailed) {
//
// Roll back so the device is left exactly as found: MSE is
// still clear here, so zeroing the BARs we already wrote is
// safe. Do not advance FreeBase; the next device may reuse
// this space.
//
for (Idx2 = 0; Idx2 <= BarIdx; Idx2++) {
if (NewBase[Idx2] != 0) {
EcamWrite32 (Bus, Dev, Func, Bar[Idx2].Offset, 0);
}
}
}
EcamWrite16 (Bus, Dev, Func, PCI_COMMAND_OFFSET, Hdr.Hdr.Command);
gBS->RestoreTPL (OldTpl);
if (WriteFailed) {
DEBUG ((
DEBUG_WARN,
"ChainloadApp: [%02x|%02x|%02x] BAR write/read-back failed; not committing\n",
Bus,
Dev,
Func
));
goto NextFunc;
}
for (BarIdx = 0; BarIdx < PCI_MAX_BAR; BarIdx++) {
if (NewBase[BarIdx] == 0) {
continue;
}
DEBUG ((
DEBUG_INFO,
"ChainloadApp: [%02x|%02x|%02x] BAR%u <- 0x%08x (Len=0x%Lx)\n",
Bus,
Dev,
Func,
BarIdx,
(UINT32)NewBase[BarIdx],
Bar[BarIdx].Length
));
}
FreeBase = TryBase;
NextFunc:
if ((Func == 0) && !IS_PCI_MULTI_FUNC (&Hdr)) {
break;
}
}
}
}
/**
Program endpoint BARs the outer firmware left at zero, for every
disjoint bus tree in every ECAM allocation the outer firmware's
MCFG describes.
Called from ChainloadEntry() while boot services are available.
Buses are grouped into disjoint trees the way the payload's own
ScanForRootBridges() later groups them: scan a candidate root bus,
take the highest subordinate bus number claimed by any PPB on it,
and resume with the bus after that. Each candidate root is walked
like a root bus: its own endpoints are left alone, and every PPB on
it supplies the window for the tree below it.
**/
VOID
FixupUnassignedBars (
VOID
)
{
EFI_STATUS Status;
EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE_HEADER *Mcfg;
EFI_ACPI_MEMORY_MAPPED_ENHANCED_CONFIGURATION_SPACE_BASE_ADDRESS_ALLOCATION_STRUCTURE *Alloc;
PCI_TYPE01 Hdr;
BAR_INFO Bar[PCI_MAX_BAR];
VOID *Rsdp;
UINTN Count;
UINTN Idx;
UINTN Jdx;
BOOLEAN Skip;
UINTN RootBus;
UINTN SubBus;
UINT8 Dev;
UINT8 Func;
Rsdp = NULL;
Status = EfiGetSystemConfigurationTable (&gEfiAcpiTableGuid, &Rsdp);
if (EFI_ERROR (Status) || (Rsdp == NULL)) {
Status = EfiGetSystemConfigurationTable (&gEfiAcpi10TableGuid, &Rsdp);
}
if (EFI_ERROR (Status) || (Rsdp == NULL)) {
return;
}
Mcfg = (EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE_HEADER *)AcpiFindTableFromRsdp (
(EFI_PHYSICAL_ADDRESS)(UINTN)Rsdp,
EFI_ACPI_2_0_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE_SIGNATURE
);
if ((Mcfg == NULL) || (Mcfg->Header.Length <= sizeof (*Mcfg))) {
//
// No ECAM description -- nothing was assigned, nothing to repair.
//
return;
}
Alloc = (VOID *)(Mcfg + 1);
Count = (Mcfg->Header.Length - sizeof (*Mcfg)) / sizeof (*Alloc);
for (Idx = 0; Idx < Count; Idx++) {
if ((Alloc[Idx].BaseAddress == 0) ||
(Alloc[Idx].EndBusNumber < Alloc[Idx].StartBusNumber))
{
continue;
}
//
// Standard ECAM semantics place bus N of an allocation at
// BaseAddress + (N - StartBusNumber) << 20, so two allocations can
// never share a BaseAddress: their windows would overlap. An MCFG
// that lists the same BaseAddress more than once is therefore
// describing one flat aperture in which bus N always decodes at
// BaseAddress + N << 20, split across entries whose StartBusNumber
// was not folded into the address. Merge such entries into one
// group anchored at the lowest StartBusNumber, which restores flat
// decode; a well-formed single allocation is its own group and is
// walked with standard semantics, unchanged.
//
Skip = FALSE;
for (Jdx = 0; Jdx < Idx; Jdx++) {
if ((Alloc[Jdx].BaseAddress == Alloc[Idx].BaseAddress) &&
(Alloc[Jdx].PciSegmentGroupNumber == Alloc[Idx].PciSegmentGroupNumber))
{
Skip = TRUE;
break;
}
}
if (Skip) {
continue;
}
mEcamBase = (UINTN)Alloc[Idx].BaseAddress;
mEcamStartBus = Alloc[Idx].StartBusNumber;
mEcamEndBus = Alloc[Idx].EndBusNumber;
for (Jdx = Idx + 1; Jdx < Count; Jdx++) {
if ((Alloc[Jdx].BaseAddress != Alloc[Idx].BaseAddress) ||
(Alloc[Jdx].PciSegmentGroupNumber != Alloc[Idx].PciSegmentGroupNumber) ||
(Alloc[Jdx].EndBusNumber < Alloc[Jdx].StartBusNumber))
{
continue;
}
if (Alloc[Jdx].StartBusNumber < mEcamStartBus) {
mEcamStartBus = Alloc[Jdx].StartBusNumber;
}
if (Alloc[Jdx].EndBusNumber > mEcamEndBus) {
mEcamEndBus = Alloc[Jdx].EndBusNumber;
}
}
DEBUG ((
DEBUG_INFO,
"ChainloadApp: ECAM 0x%Lx segment %u buses [%02x,%02x]\n",
Alloc[Idx].BaseAddress,
Alloc[Idx].PciSegmentGroupNumber,
mEcamStartBus,
mEcamEndBus
));
//
// Group buses into disjoint trees. RootBus and SubBus are UINTN so
// that SubBus + 1 cannot wrap when a tree ends at bus 255.
//
for (RootBus = mEcamStartBus; RootBus <= mEcamEndBus; RootBus = SubBus + 1) {
SubBus = RootBus;
for (Dev = 0; Dev <= PCI_MAX_DEVICE; Dev++) {
for (Func = 0; Func <= PCI_MAX_FUNC; Func++) {
Status = ReadDevice ((UINT8)RootBus, Dev, Func, &Hdr, Bar);
if (EFI_ERROR (Status)) {
if (Func == 0) {
break;
}
continue;
}
if (IS_PCI_BRIDGE (&Hdr) && (Hdr.Bridge.SubordinateBus > SubBus)) {
SubBus = Hdr.Bridge.SubordinateBus;
}
if ((Func == 0) && !IS_PCI_MULTI_FUNC (&Hdr)) {
break;
}
}
}
//
// The candidate root has no Type 1 memory window of its own, so
// start the walk with an empty window (Base > Limit); every
// first-level PPB supplies its own window from its configuration
// header.
//
WalkBus ((UINT8)RootBus, MAX_UINT64, 0);
}
}
}

View file

@ -0,0 +1,116 @@
# ChainloadApp
A UEFI application that chainloads a full UefiPayloadPkg firmware volume
from a running UEFI environment.
## Overview
ChainloadApp embeds `UEFIPAYLOAD.fd` and transfers control to it after
calling `ExitBootServices()`. This lets a fresh, self-contained UEFI
environment take over from a platform firmware image that cannot be
modified, e.g. for guest firmware development inside a VM whose outer
firmware is fixed.
## How it works
1. **Payload embedding.** `UEFIPAYLOAD.fd` is embedded as a C array in
`EmbeddedPayload.h`, generated at build time by
`ChainloadApp/GenPayloadHdr.py` from the compiled FV.
2. **HOB construction.** ChainloadApp builds the Hand-Off Blocks the
payload's entry point expects:
- `gUniversalPayloadAcpiTableGuid` (RSDP from the UEFI configuration table)
- `gUniversalPayloadSmbiosTableGuid` (SMBIOS entry point, if present)
- `gUniversalPayloadExtraDataGuid` (payload FV location)
- `gUefiSerialPortInfoGuid` and `gUniversalPayloadSerialPortInfoGuid`
(serial console configuration, derived from the ACPI SPCR table)
- Memory map records converted from the UEFI memory map, with GCD
MMIO regions surfaced as Reserved with `MEM_MAP_FLAG_MMIO` set
A `gUniversalPayloadPciRootBridgeInfoGuid` HOB is *not* emitted; the
payload's `PciHostBridgeLib` derives roots from ACPI MCFG.
3. **Control transfer.** After `ExitBootServices()`, ChainloadApp jumps to
the payload's `_ModuleEntryPoint` with the HOB list address.
On AArch64, ChainloadApp builds its own translation tables (in
`EfiReservedMemoryType` pages) via `ArmConfigureMmu()` while boot
services are still available, then installs them after
`ExitBootServices()` and branches with the MMU and caches enabled.
No data-cache maintenance is needed; the FV is invalidated from the
instruction cache for I/D coherency. The payload's `HandOffToDxeCore()`
adopts the live translation; CpuDxe later edits it in place.
## Supported architectures
- **X64**
- **AArch64**
## Building
`BuildChainloadEmbedded.sh` runs two build passes. The first builds
`UEFIPAYLOAD.fd`; the header generator then turns that FD into
`EmbeddedPayload.h`, and the second pass rebuilds ChainloadApp against
it. Both passes share `-D CHAINLOAD_DEFAULTS=TRUE`.
```bash
cd /path/to/edk2
source edksetup.sh
./UefiPayloadPkg/BuildChainloadEmbedded.sh
```
Output:
`Build/UefiPayloadPkgLegacy${ARCH}/${BUILD_TARGET}_${TOOL_CHAIN_TAG}/${ARCH}/ChainloadApp.efi`
— with the defaults below, that is
`Build/UefiPayloadPkgLegacyX64/RELEASE_GCC5/X64/ChainloadApp.efi`.
Environment variables:
| Variable | Default | Notes |
|---|---|---|
| `ARCH` | `X64` | `AARCH64` for arm64 |
| `BUILD_TARGET` | `RELEASE` | `DEBUG` / `NOOPT` |
| `TOOL_CHAIN_TAG` | `GCC5` | |
| `GCC5_AARCH64_PREFIX` | `aarch64-unknown-linux-gnu-` | AArch64 cross-toolchain prefix |
Example (AArch64, DEBUG):
```bash
ARCH=AARCH64 BUILD_TARGET=DEBUG \
GCC5_AARCH64_PREFIX=aarch64-linux-gnu- \
./UefiPayloadPkg/BuildChainloadEmbedded.sh
```
## Testing with QEMU
### X64 (OVMF)
```bash
qemu-system-x86_64 -m 1G -nographic -enable-kvm -bios OVMF_CODE.fd \
-netdev user,tftp=Build/UefiPayloadPkgLegacyX64/RELEASE_GCC5/X64/,bootfile=ChainloadApp.efi,id=n \
-device virtio-net-pci,netdev=n
```
### AArch64 (`-M virt`)
```bash
dd if=/dev/zero of=disk.img bs=1M count=64 && mkfs.vfat disk.img
mmd -i disk.img ::/EFI ::/EFI/BOOT
mcopy -i disk.img Build/UefiPayloadPkgLegacyAARCH64/RELEASE_GCC5/AARCH64/ChainloadApp.efi ::/EFI/BOOT/BOOTAA64.EFI
qemu-system-aarch64 -M virt -cpu cortex-a72 -m 1G -nographic \
-bios QEMU_EFI.fd -drive file=disk.img,format=raw,if=virtio
```
## Files
- `ChainloadApp.c` / `ChainloadApp.inf` — the application
- `AArch64/PayloadEntry.S`, `X64/PayloadEntry.nasm` — handoff trampolines
- `GenPayloadHdr.py` — FD-to-C-array generator
- `EmbeddedPayloadStub.h` — placeholder header for the first build pass
- `../BuildChainloadEmbedded.sh` — two-pass build script
---
Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

View file

@ -0,0 +1,395 @@
/** @file
MemoryAllocationLib instance that keeps ArmMmuLib's translation-table
pages as EfiReservedMemoryType.
ChainloadApp installs its own translation tables via
ArmConfigureMmu() while the outer firmware is still running, so
that the payload can be entered with the MMU and caches enabled.
ArmMmuLib allocates every level of that hierarchy through
MemoryAllocationLib::AllocatePages(), which the stock
UefiMemoryAllocationLib backs with EfiBootServicesData. After
ExitBootServices() the payload's memory-map HOB reports
EfiBootServicesData as free RAM, so DXE could allocate over the
live tables. Overriding AllocatePages() to EfiReservedMemoryType
keeps every table page in an isolated Reserved descriptor in the
outer memory-map snapshot, so it never coalesces with adjacent
conventional memory and the payload's HOB-memory search cannot
select it. Every Reserved page allocation is also recorded so
ChainloadApp can hand the payload an explicit list of boot-time
reservations for it to publish as SYSTEM_MEMORY pinned by an
EfiBootServicesData allocation HOB, letting the OS reclaim the
table pages after it has installed its own translation.
Only AllocatePages() is redirected. Pool allocations remain
EfiBootServicesData: they back short-lived buffers (Print(), the
memory-map snapshot) that are freed before the branch and never
carried across the handoff. The library implements only the
MemoryAllocationLib functions the module's objects reference;
the runtime, aligned and remaining variants are omitted.
AllocateCopyPool() and ReallocatePool() are implemented because
UefiLib and UefiDevicePathLib reference them, and a strict PE
linker resolves the reference even when the calling function is
never used.
Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
//
// Every Reserved page allocation this library returns is recorded so
// that ChainloadApp can hand the payload an explicit list of the
// launcher's own boot-time-only reservations (the translation-table
// pages ArmMmuLib allocates through here, in addition to the FV, HOB
// list and stack ChainloadApp allocates itself). The library is a
// per-module override for ChainloadApp only, so exporting these as
// plain globals is sufficient; there is no MemoryAllocationLib
// interface for it.
//
#define RESERVED_PAGE_ALLOC_MAX 64
EFI_PHYSICAL_ADDRESS gReservedPageAllocBase[RESERVED_PAGE_ALLOC_MAX];
UINTN gReservedPageAllocPages[RESERVED_PAGE_ALLOC_MAX];
UINTN gReservedPageAllocCount;
/**
Record a Reserved page allocation for later reclamation.
Appends the range to the exported gReservedPageAlloc* table that
ChainloadApp turns into the boot-time reservation HOB. If the
table is full the range is dropped with a warning; the pages stay
Reserved and are not reclaimed to the OS.
@param Base The base address of the allocation.
@param Pages The number of 4 KB pages allocated.
**/
STATIC
VOID
RecordReservedPageAlloc (
IN EFI_PHYSICAL_ADDRESS Base,
IN UINTN Pages
)
{
if (gReservedPageAllocCount < RESERVED_PAGE_ALLOC_MAX) {
gReservedPageAllocBase[gReservedPageAllocCount] = Base;
gReservedPageAllocPages[gReservedPageAllocCount] = Pages;
gReservedPageAllocCount++;
} else {
DEBUG ((
DEBUG_WARN,
"%a: table full at 0x%Lx (%u pages); page stays Reserved and is "
"not reclaimed to the OS\n",
__func__,
(UINT64)Base,
(UINT32)Pages
));
}
}
/**
Allocates one or more 4KB pages of a certain memory type.
Allocates the number of 4KB pages of a certain memory type and returns a pointer to the
allocated buffer. The buffer returned is aligned on a 4KB boundary. If Pages is 0, then NULL
is returned. If there is not enough memory remaining to satisfy the request, then NULL is
returned. Reserved-type allocations are recorded for later reclamation.
@param MemoryType The type of memory to allocate.
@param Pages The number of 4 KB pages to allocate.
@return A pointer to the allocated buffer or NULL if allocation fails.
**/
STATIC
VOID *
InternalAllocatePages (
IN EFI_MEMORY_TYPE MemoryType,
IN UINTN Pages
)
{
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS Memory;
if (Pages == 0) {
return NULL;
}
Status = gBS->AllocatePages (AllocateAnyPages, MemoryType, Pages, &Memory);
if (EFI_ERROR (Status)) {
return NULL;
}
if (MemoryType == EfiReservedMemoryType) {
RecordReservedPageAlloc (Memory, Pages);
}
return (VOID *)(UINTN)Memory;
}
/**
Allocates one or more 4KB pages of type EfiReservedMemoryType.
Allocates the number of 4KB pages of type EfiReservedMemoryType and returns a pointer to the
allocated buffer. The buffer returned is aligned on a 4KB boundary. If Pages is 0, then NULL
is returned. If there is not enough memory remaining to satisfy the request, then NULL is
returned.
@param Pages The number of 4 KB pages to allocate.
@return A pointer to the allocated buffer or NULL if allocation fails.
**/
VOID *
EFIAPI
AllocatePages (
IN UINTN Pages
)
{
return InternalAllocatePages (EfiReservedMemoryType, Pages);
}
/**
Frees one or more 4KB pages that were previously allocated with one of the page allocation
functions in the Memory Allocation Library.
Frees the number of 4KB pages specified by Pages from the buffer specified by Buffer. Buffer
must have been allocated on a previous call to the page allocation services of the Memory
Allocation Library. If it is not possible to free allocated pages, then this function will
perform no actions.
If Pages is zero, then ASSERT().
@param Buffer The pointer to the buffer of pages to free.
@param Pages The number of 4 KB pages to free.
**/
VOID
EFIAPI
FreePages (
IN VOID *Buffer,
IN UINTN Pages
)
{
EFI_STATUS Status;
ASSERT (Pages != 0);
Status = gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)Buffer, Pages);
ASSERT_EFI_ERROR (Status);
}
/**
Allocates a buffer of a certain pool type.
Allocates the number bytes specified by AllocationSize of a certain pool type and returns a
pointer to the allocated buffer. If AllocationSize is 0, then a valid buffer of 0 size is
returned. If there is not enough memory remaining to satisfy the request, then NULL is returned.
@param MemoryType The type of memory to allocate.
@param AllocationSize The number of bytes to allocate.
@return A pointer to the allocated buffer or NULL if allocation fails.
**/
STATIC
VOID *
InternalAllocatePool (
IN EFI_MEMORY_TYPE MemoryType,
IN UINTN AllocationSize
)
{
EFI_STATUS Status;
VOID *Memory;
Status = gBS->AllocatePool (MemoryType, AllocationSize, &Memory);
if (EFI_ERROR (Status)) {
Memory = NULL;
}
return Memory;
}
/**
Allocates a buffer of type EfiBootServicesData.
Allocates the number bytes specified by AllocationSize of type EfiBootServicesData and returns a
pointer to the allocated buffer. If AllocationSize is 0, then a valid buffer of 0 size is
returned. If there is not enough memory remaining to satisfy the request, then NULL is returned.
@param AllocationSize The number of bytes to allocate.
@return A pointer to the allocated buffer or NULL if allocation fails.
**/
VOID *
EFIAPI
AllocatePool (
IN UINTN AllocationSize
)
{
return InternalAllocatePool (EfiBootServicesData, AllocationSize);
}
/**
Allocates and zeros a buffer of a certain pool type.
Allocates the number bytes specified by AllocationSize of a certain pool type, clears the buffer
with zeros, and returns a pointer to the allocated buffer. If AllocationSize is 0, then a valid
buffer of 0 size is returned. If there is not enough memory remaining to satisfy the request,
then NULL is returned.
@param PoolType The type of memory to allocate.
@param AllocationSize The number of bytes to allocate and zero.
@return A pointer to the allocated buffer or NULL if allocation fails.
**/
STATIC
VOID *
InternalAllocateZeroPool (
IN EFI_MEMORY_TYPE PoolType,
IN UINTN AllocationSize
)
{
VOID *Memory;
Memory = InternalAllocatePool (PoolType, AllocationSize);
if (Memory != NULL) {
ZeroMem (Memory, AllocationSize);
}
return Memory;
}
/**
Allocates and zeros a buffer of type EfiBootServicesData.
Allocates the number bytes specified by AllocationSize of type EfiBootServicesData, clears the
buffer with zeros, and returns a pointer to the allocated buffer. If AllocationSize is 0, then a
valid buffer of 0 size is returned. If there is not enough memory remaining to satisfy the
request, then NULL is returned.
@param AllocationSize The number of bytes to allocate and zero.
@return A pointer to the allocated buffer or NULL if allocation fails.
**/
VOID *
EFIAPI
AllocateZeroPool (
IN UINTN AllocationSize
)
{
return InternalAllocateZeroPool (EfiBootServicesData, AllocationSize);
}
/**
Frees a buffer that was previously allocated with one of the pool allocation functions in the
Memory Allocation Library.
Frees the buffer specified by Buffer. Buffer must have been allocated on a previous call to the
pool allocation services of the Memory Allocation Library. If it is not possible to free pool
resources, then this function will perform no actions.
@param Buffer The pointer to the buffer to free.
**/
VOID
EFIAPI
FreePool (
IN VOID *Buffer
)
{
EFI_STATUS Status;
Status = gBS->FreePool (Buffer);
ASSERT_EFI_ERROR (Status);
}
/**
Copies a buffer to an allocated buffer of type EfiBootServicesData.
Allocates the number bytes specified by AllocationSize of type
EfiBootServicesData, copies AllocationSize bytes from SourceBuffer to
the newly allocated buffer, and returns a pointer to the allocated
buffer. If AllocationSize is 0, then a valid buffer of 0 size is
returned. If there is not enough memory remaining to satisfy the
request, then NULL is returned.
If SourceBuffer is NULL, then ASSERT().
If AllocationSize is greater than (MAX_ADDRESS - SourceBuffer + 1),
then ASSERT().
@param AllocationSize The number of bytes to allocate.
@param SourceBuffer The buffer to copy to the allocated buffer.
@return A pointer to the allocated buffer or NULL if allocation fails.
**/
VOID *
EFIAPI
AllocateCopyPool (
IN UINTN AllocationSize,
IN CONST VOID *SourceBuffer
)
{
VOID *Memory;
ASSERT (SourceBuffer != NULL);
ASSERT (AllocationSize <= (MAX_ADDRESS - (UINTN)SourceBuffer + 1));
Memory = InternalAllocatePool (EfiBootServicesData, AllocationSize);
if (Memory != NULL) {
Memory = CopyMem (Memory, SourceBuffer, AllocationSize);
}
return Memory;
}
/**
Reallocates a buffer of type EfiBootServicesData.
Allocates NewSize bytes of type EfiBootServicesData, copies
MIN (OldSize, NewSize) bytes from OldBuffer to the newly allocated
buffer, frees OldBuffer, and returns a pointer to the allocated
buffer. If NewSize is 0, then a valid buffer of 0 size is returned.
If there is not enough memory remaining to satisfy the request, then
NULL is returned and OldBuffer is not freed.
If the allocation of the new buffer fails, then OldBuffer is not
freed.
If OldSize is greater than NewSize, then ASSERT().
@param OldSize The size, in bytes, of OldBuffer.
@param NewSize The size, in bytes, of the buffer to reallocate.
@param OldBuffer The buffer to copy to the allocated buffer.
This is an optional parameter that may be NULL.
@return A pointer to the allocated buffer or NULL if allocation fails.
**/
VOID *
EFIAPI
ReallocatePool (
IN UINTN OldSize,
IN UINTN NewSize,
IN VOID *OldBuffer OPTIONAL
)
{
VOID *NewBuffer;
ASSERT (OldSize <= NewSize);
NewBuffer = InternalAllocatePool (EfiBootServicesData, NewSize);
if ((NewBuffer != NULL) && (OldBuffer != NULL)) {
NewBuffer = CopyMem (NewBuffer, OldBuffer, OldSize);
FreePool (OldBuffer);
}
return NewBuffer;
}

View file

@ -0,0 +1,32 @@
## @file
# MemoryAllocationLib whose page allocations are EfiReservedMemoryType.
#
# Overrides UefiMemoryAllocationLib for ChainloadApp so that the
# translation-table pages ArmMmuLib allocates through AllocatePages()
# are Reserved and each allocation is recorded for ChainloadApp to
# publish as a boot-time reservation. Pool allocations remain
# EfiBootServicesData.
#
# Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
##
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = ReservedUefiMemoryAllocationLib
FILE_GUID = 4e4b7c1d-6a34-4a02-9c8e-9c9e9d0e0aa1
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
LIBRARY_CLASS = MemoryAllocationLib|UEFI_APPLICATION
[Sources]
ReservedUefiMemoryAllocationLib.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
DebugLib
BaseMemoryLib
UefiBootServicesTableLib

View file

@ -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

View file

@ -0,0 +1,50 @@
/** @file
Boot-time reservation GUID HOB.
A UEFI-hosted launcher (ChainloadApp) allocates the payload FV, its
own HOB list, the payload's initial stack and, on AArch64, a full
translation-table hierarchy from the outer firmware as
EfiReservedMemoryType so that they appear as isolated Reserved
descriptors in the memory-map snapshot and cannot be selected as
free RAM by the payload's HOB-memory search. None of them, however,
needs to survive past the OS's ExitBootServices() call: DxeCore
loads every driver out of the FV into its own pages, the launcher's
HOB list is dead once UefiPayloadEntry has rebuilt the HOB list, the
launcher's stack is dead once HandOffToDxeCore() has switched to its
own, and firmware translation tables are the same EfiBootServicesData
in a normal ArmMmuLib-based boot.
This HOB tells the payload which of the Reserved records in the SBL
memory-map HOB are the launcher's own boot-time-only allocations.
UefiPayloadEntry excludes them from FindFreeMemForHobCallback(),
publishes each as EFI_RESOURCE_SYSTEM_MEMORY, and pins each with an
EfiBootServicesData memory-allocation HOB so that the payload's DXE
never allocates over them and the OS reclaims them after
ExitBootServices().
A launcher that does not emit this HOB (Slim Bootloader, coreboot)
gets the pre-existing behaviour unchanged: the payload publishes the
Reserved records as EFI_RESOURCE_MEMORY_RESERVED and the OS never
touches them.
Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights Reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#pragma once
extern EFI_GUID gLoaderBootTimeReservationGuid;
#pragma pack(1)
typedef struct {
EFI_PHYSICAL_ADDRESS Base;
UINT64 Size;
} LOADER_BOOT_TIME_RESERVATION_ENTRY;
typedef struct {
UINT8 Revision;
UINT8 Reserved0[3];
UINT32 Count;
LOADER_BOOT_TIME_RESERVATION_ENTRY Entry[0];
} LOADER_BOOT_TIME_RESERVATION;
#pragma pack()

View file

@ -15,6 +15,17 @@
///
extern EFI_GUID gLoaderMemoryMapInfoGuid;
///
/// MEMORY_MAP_ENTRY.Flag bits.
///
/// A bootloader that already knows a range is device MMIO can say so
/// explicitly rather than leaving UefiPayloadEntry to classify the range by
/// the below/above mTopOfLowerUsableDram heuristic. The bit is additive: a
/// payload that predates it simply falls back to the heuristic, and a
/// bootloader that does not set it behaves exactly as before.
///
#define MEM_MAP_FLAG_MMIO BIT0
#pragma pack(1)
typedef struct {
UINT64 Base;

View file

@ -0,0 +1,40 @@
/** @file
Locate ACPI tables by signature via a bootloader-supplied RSDP.
Copyright (c) 2026, Amazon.com, Inc. or its affiliates.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#pragma once
#include <Uefi/UefiBaseType.h>
#include <IndustryStandard/Acpi.h>
/**
Locate an ACPI table by signature via the RSDP.
Walks the XSDT (or the RSDT if the RSDP is Revision 0 or has no
XSDT address) referenced by Rsdp and returns the first table
whose header signature matches Signature.
The RSDP signature, the XSDT/RSDT signature and length, and each
entry pointer are all validated before use. A Length shorter than
the common ACPI description header would underflow the entry-count
subtraction and walk off the end of the table; that case is
rejected. XSDT entry pointers are read with ReadUnaligned64()
because the 36-byte header leaves the 64-bit entry array 4-byte
aligned.
@param[in] Rsdp Physical address of the ACPI RSDP, or 0.
@param[in] Signature 4-byte ACPI table signature to find.
@return Pointer to the first matching table's description header,
or NULL if Rsdp is 0, the RSDP or SDT header is invalid,
or no table with Signature is present.
**/
EFI_ACPI_DESCRIPTION_HEADER *
EFIAPI
AcpiFindTableFromRsdp (
IN EFI_PHYSICAL_ADDRESS Rsdp,
IN UINT32 Signature
);

View file

@ -0,0 +1,664 @@
/** @file
NULL library that discovers the GIC Distributor, Redistributor and
memory-mapped CPU-interface bases from the ACPI MADT supplied by
the bootloader and populates the corresponding PCDs before
ArmGicDxe consumes them.
UefiPayloadPkg carries QEMU-virt fixed defaults for the GIC PCDs.
When the outer firmware handed over ACPI tables (via ChainloadApp
or a Slim Bootloader), the actual GIC location is described by the
MADT GICD/GICR/GICC structures. Read those, override the PCDs, and
add the ranges to the GCD memory space so ArmGicDxe can touch them.
With no ACPI handover at all the fixed defaults stay in place, which
is the existing behaviour for a plain QEMU-virt boot.
Which base ArmGicDxe actually reads is a partly-runtime decision
(see the file-scope commentary at ArmPkg/Drivers/ArmGicDxe/
ArmGicDxe.c: it dispatches on the CPU's GICv3 system-register
feature and on whether ICC_SRE_EL2.SRE can be enabled, or on the
CPU's GICv5 system-register feature, and drives the interrupt
controller as a v2 otherwise). This library therefore
derives every base the MADT can supply, and only afterwards decides
whether what it has is sufficient for the path ArmGicDxe will take.
The MADT GICD's GicVersion byte is read purely as a diagnostic
cross-check: upstream deliberately does not trust a table-reported
GIC version, and neither does this code.
A MADT that is present but does not describe the base that
ArmGicDxe's chosen path needs is fatal, not a reason to fall back to
the QEMU-virt defaults: those addresses are not a GIC on any other
platform, so letting ArmGicDxe walk them yields either a bus abort
or a plausible-looking read followed by a mute hang with no
interrupt controller - and in a RELEASE build, no diagnostic either.
In every such case the specific reason is printed and CpuDeadLoop()
is called: the DXE AutoGen constructor wrapper only
ASSERT_EFI_ERROR()s a status returned from here, so in a RELEASE
build a returned error alone would be discarded and the entry point
would run anyway. A payload without an interrupt controller cannot
boot, so halting loses nothing over the mute hang and makes the
failure diagnosable.
Copyright (c) 2026, Amazon.com, Inc. or its affiliates.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <PiDxe.h>
#include <IndustryStandard/Acpi.h>
#include <UniversalPayload/AcpiTable.h>
#include <Library/AcpiTableWalkLib.h>
#include <Library/ArmLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/DxeServicesTableLib.h>
#include <Library/HobLib.h>
#include <Library/PcdLib.h>
/**
Locate an ACPI table by signature via the RSDP the bootloader
handed over in the gUniversalPayloadAcpiTableGuid HOB.
@param[in] Signature 4-byte ACPI table signature.
@return Pointer to the table header, or NULL if not found.
**/
STATIC
EFI_ACPI_DESCRIPTION_HEADER *
LocateAcpiTable (
IN UINT32 Signature
)
{
EFI_HOB_GUID_TYPE *GuidHob;
UNIVERSAL_PAYLOAD_ACPI_TABLE *AcpiHob;
GuidHob = GetFirstGuidHob (&gUniversalPayloadAcpiTableGuid);
if (GuidHob == NULL) {
return NULL;
}
AcpiHob = (UNIVERSAL_PAYLOAD_ACPI_TABLE *)GET_GUID_HOB_DATA (GuidHob);
return AcpiFindTableFromRsdp (AcpiHob->Rsdp, Signature);
}
/**
Add a device MMIO range to the GCD memory map and mark it uncacheable
and non-executable, so that ArmCpuDxe populates a page-table entry
for it.
If the range is already present in the GCD map, only proceed when the
existing descriptor is an unowned MMIO descriptor that covers the
whole request. A bogus MADT can point a GIC base into DRAM, and
remapping live system memory uncacheable and non-executable is not a
warning-level event. This is the same check the PL031 RTC library
performs for the same situation.
@param[in] Base Base physical address.
@param[in] Length Length in bytes.
@retval EFI_SUCCESS The range is MMIO and is now mapped UC|XP.
@retval EFI_ACCESS_DENIED The range is already described as something
other than unowned MMIO covering it whole.
@retval other GCD service failure.
**/
STATIC
EFI_STATUS
MapGicMmio (
IN EFI_PHYSICAL_ADDRESS Base,
IN UINT64 Length
)
{
EFI_STATUS Status;
EFI_GCD_MEMORY_SPACE_DESCRIPTOR Desc;
Status = gDS->AddMemorySpace (
EfiGcdMemoryTypeMemoryMappedIo,
Base,
Length,
EFI_MEMORY_UC | EFI_MEMORY_XP
);
if (Status == EFI_ACCESS_DENIED) {
//
// Something already describes part or all of the range, and
// EFI_ACCESS_DENIED does not say what. Refuse to touch the
// attributes unless it is MMIO that no driver owns, and unless that
// single descriptor covers the whole request: AddMemorySpace() also
// returns EFI_ACCESS_DENIED for a partial overlap, while
// GetMemorySpaceDescriptor() only returns the descriptor containing
// Base.
//
Status = gDS->GetMemorySpaceDescriptor (Base, &Desc);
if (EFI_ERROR (Status)) {
return Status;
}
if ((Desc.GcdMemoryType != EfiGcdMemoryTypeMemoryMappedIo) ||
(Desc.ImageHandle != NULL) ||
(Desc.BaseAddress > Base) ||
((Base + Length) > (Desc.BaseAddress + Desc.Length)))
{
DEBUG ((
DEBUG_ERROR,
"%a: 0x%Lx(0x%Lx) is already described as GCD type %u owned by %p "
"over 0x%Lx(0x%Lx); refusing to remap\n",
__func__,
Base,
Length,
(UINT32)Desc.GcdMemoryType,
Desc.ImageHandle,
Desc.BaseAddress,
Desc.Length
));
return EFI_ACCESS_DENIED;
}
//
// A pre-existing MMIO descriptor need not carry the UC and XP
// capabilities, and CoreSetMemorySpaceAttributes() rejects any
// attribute that is absent from Capabilities. Add them first.
//
if ((Desc.Capabilities & (EFI_MEMORY_UC | EFI_MEMORY_XP)) !=
(EFI_MEMORY_UC | EFI_MEMORY_XP))
{
Status = gDS->SetMemorySpaceCapabilities (
Base,
Length,
Desc.Capabilities | EFI_MEMORY_UC | EFI_MEMORY_XP
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: SetMemorySpaceCapabilities(0x%Lx, 0x%Lx): %r\n",
__func__,
Base,
Length,
Status
));
return Status;
}
}
} else if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: AddMemorySpace(0x%Lx, 0x%Lx): %r\n",
__func__,
Base,
Length,
Status
));
return Status;
}
Status = gDS->SetMemorySpaceAttributes (
Base,
Length,
EFI_MEMORY_UC | EFI_MEMORY_XP
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: SetMemorySpaceAttributes(0x%Lx, 0x%Lx): %r\n",
__func__,
Base,
Length,
Status
));
}
return Status;
}
/**
Halt after the caller has printed why the MADT is unusable.
See the file header for why the QEMU-virt build-time defaults must
not survive an unusable MADT, and why returning an error status is
not sufficient in a RELEASE build.
**/
STATIC
VOID
GicMadtFatal (
VOID
)
{
DEBUG ((
DEBUG_ERROR,
"AcpiGicPcdLib: no usable interrupt controller can be derived from "
"the MADT and the build-time PCD defaults are only correct on QEMU "
"virt; halting.\n"
));
CpuDeadLoop ();
}
/**
Constructor: parse the ACPI MADT for the GICD/GICR/GICC bases,
override the GIC PCDs, and map the MMIO ranges.
@param ImageHandle Image handle (unused).
@param SystemTable System table (unused).
@retval EFI_SUCCESS No ACPI handover, so the fixed PCD defaults
stand; or the base(s) required by ArmGicDxe's
chosen init path were derived and mapped.
@return An error status only reachable in the caller if
CpuDeadLoop() were to return; documents which
failure was hit.
**/
EFI_STATUS
EFIAPI
AcpiGicPcdLibConstructor (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_ACPI_DESCRIPTION_HEADER *Madt;
EFI_ACPI_6_0_GIC_DISTRIBUTOR_STRUCTURE *Gicd;
EFI_ACPI_6_0_GICR_STRUCTURE *Gicr;
EFI_ACPI_6_0_GIC_STRUCTURE *Gicc;
EFI_STATUS Status;
RETURN_STATUS PcdStatus;
UINT8 *Ptr;
UINT8 *End;
UINT64 DistBase;
UINT64 RedistBase;
UINT64 RedistLen;
UINT64 CpuIfBase;
UINT64 ThisCpuIfBase;
UINTN GicrCount;
UINTN GiccRedistCount;
UINTN Length;
UINT8 GicVersion;
BOOLEAN HaveSysRegs;
BOOLEAN CpuIfMismatch;
Madt = LocateAcpiTable (
EFI_ACPI_6_0_MULTIPLE_APIC_DESCRIPTION_TABLE_SIGNATURE
);
if (Madt == NULL) {
DEBUG ((DEBUG_INFO, "%a: no MADT, keeping fixed GIC PCDs\n", __func__));
return EFI_SUCCESS;
}
if (Madt->Length < sizeof (EFI_ACPI_6_0_MULTIPLE_APIC_DESCRIPTION_TABLE_HEADER)) {
DEBUG ((
DEBUG_ERROR,
"%a: MADT length %u is shorter than its own header\n",
__func__,
Madt->Length
));
GicMadtFatal ();
return EFI_VOLUME_CORRUPTED;
}
DistBase = 0;
RedistBase = 0;
RedistLen = 0;
CpuIfBase = 0;
GicrCount = 0;
GiccRedistCount = 0;
GicVersion = 0;
CpuIfMismatch = FALSE;
Ptr = (UINT8 *)Madt +
sizeof (EFI_ACPI_6_0_MULTIPLE_APIC_DESCRIPTION_TABLE_HEADER);
End = (UINT8 *)Madt + Madt->Length;
//
// Walk the interrupt controller structures. Each one is Type, Length,
// then a type-specific body, so both bytes must be present before
// Length can be read, Length must be large enough to advance, and it
// must not run past the end of the table.
//
while ((UINTN)(End - Ptr) >= 2) {
Length = Ptr[1];
if ((Length < 2) || (Length > (UINTN)(End - Ptr))) {
DEBUG ((
DEBUG_ERROR,
"%a: MADT structure type %u at offset 0x%Lx has bad length %u\n",
__func__,
(UINT32)Ptr[0],
(UINT64)(UINTN)(Ptr - (UINT8 *)Madt),
(UINT32)Length
));
GicMadtFatal ();
return EFI_VOLUME_CORRUPTED;
}
switch (Ptr[0]) {
case EFI_ACPI_6_0_GICD:
if (Length < (OFFSET_OF (EFI_ACPI_6_0_GIC_DISTRIBUTOR_STRUCTURE, PhysicalBaseAddress) +
sizeof (UINT64)))
{
DEBUG ((
DEBUG_WARN,
"%a: GICD structure is %u bytes, too short for a base address; ignoring\n",
__func__,
(UINT32)Length
));
break;
}
Gicd = (EFI_ACPI_6_0_GIC_DISTRIBUTOR_STRUCTURE *)Ptr;
DistBase = ReadUnaligned64 (&Gicd->PhysicalBaseAddress);
//
// GicVersion sits after SystemVectorBase and was added in
// ACPI 6.0. A pre-6.0 GICD is 24 bytes and does not carry it,
// so bound it separately and leave GicVersion at 0
// ("unspecified") when absent. It is read only for the
// cross-check warning below and is never a gate.
//
if (Length >= (OFFSET_OF (EFI_ACPI_6_0_GIC_DISTRIBUTOR_STRUCTURE, GicVersion) +
sizeof (UINT8)))
{
GicVersion = Gicd->GicVersion;
}
break;
case EFI_ACPI_6_0_GICR:
if (Length < (OFFSET_OF (EFI_ACPI_6_0_GICR_STRUCTURE, DiscoveryRangeLength) +
sizeof (UINT32)))
{
DEBUG ((
DEBUG_WARN,
"%a: GICR structure is %u bytes, too short for a discovery range; ignoring\n",
__func__,
(UINT32)Length
));
break;
}
Gicr = (EFI_ACPI_6_0_GICR_STRUCTURE *)Ptr;
GicrCount++;
//
// Several GICR structures are legal: they describe several
// discovery ranges. PcdGicRedistributorsBase carries one base,
// and ArmGicDxe walks frames from it until GICR_TYPER.Last, so
// it can never reach the others. Keep the first and warn below,
// rather than silently keeping whichever came last.
//
if (GicrCount == 1) {
RedistBase = ReadUnaligned64 (&Gicr->DiscoveryRangeBaseAddress);
RedistLen = ReadUnaligned32 (&Gicr->DiscoveryRangeLength);
}
break;
case EFI_ACPI_6_0_GIC:
//
// GICC.PhysicalBaseAddress is the memory-mapped CPU-interface
// address that GicV2DxeInitialize() reads via
// PcdGicInterruptInterfaceBase. In GICv2 all PEs share one
// GICC MMIO window (banked per-PE), which is why the PCD is a
// single value; every enabled GICC entry should therefore
// report the same address. The ACPI spec permits it to be 0
// on a platform without GICv2 compatibility support, so 0 is
// "not derivable" here rather than a value to publish.
//
// The field has been present since the GICC structure was
// introduced in ACPI 5.0, so bound it independently of
// GICRBaseAddress below.
//
if (Length >= (OFFSET_OF (EFI_ACPI_6_0_GIC_STRUCTURE, PhysicalBaseAddress) +
sizeof (UINT64)))
{
Gicc = (EFI_ACPI_6_0_GIC_STRUCTURE *)Ptr;
ThisCpuIfBase = ReadUnaligned64 (&Gicc->PhysicalBaseAddress);
if (ThisCpuIfBase != 0) {
if (CpuIfBase == 0) {
CpuIfBase = ThisCpuIfBase;
} else if (CpuIfBase != ThisCpuIfBase) {
CpuIfMismatch = TRUE;
}
}
}
//
// GICRBaseAddress was added to the GICC structure in ACPI 5.1;
// an older, shorter GICC simply does not describe it.
//
if (Length < (OFFSET_OF (EFI_ACPI_6_0_GIC_STRUCTURE, GICRBaseAddress) +
sizeof (UINT64)))
{
break;
}
Gicc = (EFI_ACPI_6_0_GIC_STRUCTURE *)Ptr;
if (ReadUnaligned64 (&Gicc->GICRBaseAddress) != 0) {
GiccRedistCount++;
}
break;
default:
break;
}
Ptr += Length;
}
if (GicrCount > 1) {
DEBUG ((
DEBUG_WARN,
"%a: MADT has %u GICR structures; only the first discovery range is used\n",
__func__,
(UINT32)GicrCount
));
}
if (CpuIfMismatch) {
DEBUG ((
DEBUG_WARN,
"%a: MADT GICC entries report differing PhysicalBaseAddress values; "
"using 0x%Lx for PcdGicInterruptInterfaceBase\n",
__func__,
CpuIfBase
));
}
if (DistBase == 0) {
DEBUG ((DEBUG_ERROR, "%a: MADT describes no GIC distributor\n", __func__));
GicMadtFatal ();
return EFI_NOT_FOUND;
}
//
// ArmGicDxe (see ArmPkg/Drivers/ArmGicDxe/ArmGicDxe.c) chooses its
// init path from the CPU, not from the MADT: GicV3Supported() checks
// ArmHasGicSystemRegisters() and then whether ICC_SRE_EL2.SRE can be
// set, because "the GICC IIDR Architecture version [...] does not
// seem to be very reliable"; ArmHasGicV5SystemRegisters() also
// selects the v3 path (ArmGicDxe.c:77). Only when neither is
// available does it drive the GIC as a v2. Whether SRE sticks depends
// on the higher exception level and cannot be predicted here without
// repeating the write ArmGicDxe is about to perform.
//
// Use the same first-order predicate to decide which base is
// required. When system registers are present the v3 path is the
// likely one and needs a redistributor; when they are absent the v2
// path is certain and needs the CPU interface. In either case
// derive and publish the CPU-interface base whenever GICC supplies a
// non-zero one, so the SRE-denied fallback to v2 has a correct value
// rather than the QEMU-virt build-time default.
//
HaveSysRegs = ArmHasGicSystemRegisters ();
if (HaveSysRegs) {
if (GicrCount == 0) {
if (GiccRedistCount != 0) {
//
// ACPI 6.0 lets a platform describe the redistributors per PE in
// GICC.GICRBaseAddress instead of as one contiguous discovery
// range, and that is exactly the case where the frames are not
// contiguous. PcdGicRedistributorsBase carries a single base and
// ArmGicDxe walks frames from it until GICR_TYPER.Last, so any
// range synthesised from the per-PE bases would be a guess about
// the platform's layout. Report it instead of guessing.
//
DEBUG ((
DEBUG_ERROR,
"%a: MADT describes the redistributors per PE in GICC.GICRBaseAddress "
"(%u of them); PcdGicRedistributorsBase cannot express that\n",
__func__,
(UINT32)GiccRedistCount
));
GicMadtFatal ();
return EFI_UNSUPPORTED;
}
DEBUG ((
DEBUG_ERROR,
"%a: CPU has GICv3 system registers but the MADT describes no "
"GIC redistributor in either form\n",
__func__
));
GicMadtFatal ();
return EFI_NOT_FOUND;
}
if ((RedistBase == 0) || (RedistLen == 0)) {
//
// ArmGicDxe walks redistributor frames from the published base until
// one reports GICR_TYPER.Last, so the whole discovery range has to
// be mapped. A GICv3 redistributor is 128 KiB and a GICv4 one is
// 256 KiB, so no fixed guess covers an SMP system: the walk would
// read past the mapping. If the MADT gives no length, fail.
//
DEBUG ((
DEBUG_ERROR,
"%a: MADT GICR discovery range is unusable: base 0x%Lx, length 0x%Lx\n",
__func__,
RedistBase,
RedistLen
));
GicMadtFatal ();
return EFI_UNSUPPORTED;
}
} else {
if (CpuIfBase == 0) {
DEBUG ((
DEBUG_ERROR,
"%a: CPU has no GICv3 system registers so ArmGicDxe will take the "
"v2 path, but the MADT GICC entries describe no memory-mapped CPU "
"interface (PhysicalBaseAddress is zero or absent)\n",
__func__
));
GicMadtFatal ();
return EFI_NOT_FOUND;
}
}
//
// Cross-check the GICD.GicVersion byte against what the structures
// and CPU imply, purely for diagnostics. 0 means the field is
// absent (pre-6.0 GICD) or the firmware left it unspecified. This
// never gates anything for the reason quoted above.
//
if (GicVersion != 0) {
if (HaveSysRegs && (GicVersion < EFI_ACPI_6_0_GIC_V3)) {
DEBUG ((
DEBUG_WARN,
"%a: MADT GICD.GicVersion is %u but the CPU implements GICv3 "
"system registers; ignoring the reported version\n",
__func__,
(UINT32)GicVersion
));
} else if (!HaveSysRegs && (GicVersion >= EFI_ACPI_6_0_GIC_V3)) {
DEBUG ((
DEBUG_WARN,
"%a: MADT GICD.GicVersion is %u but the CPU has no GICv3 system "
"registers; ignoring the reported version\n",
__func__,
(UINT32)GicVersion
));
}
}
//
// The MADT carries no distributor length. ArmGicDxe maps the
// distributor as GICD_V3_SIZE (64 KiB) on the v3 path and
// GICD_V2_SIZE (4 KiB) on the v2 path (see GicV3DxeInitialize()
// and GicV2DxeInitialize() respectively), so map at least what the
// path that will run needs. Mapping the larger unconditionally is
// not safe on a genuine GICv2 platform: the 60 KiB beyond the
// distributor is not the GIC's, and if any of it is already in the
// GCD map MapGicMmio() refuses to touch it and this constructor
// halts a boot that would otherwise succeed. HaveSysRegs is the
// same first-order predicate GicV3Supported() uses to choose the
// path, and the same predicate this constructor already used above
// to decide sufficiency. In the SRE-denied corner case (HaveSysRegs
// TRUE, ArmGicDxe falls back to v2) 64 KiB is mapped where the v2
// path uses only 4; that is safe because on such a platform the
// distributor block is architecturally 64 KiB regardless of which
// interface the driver chooses.
//
Status = MapGicMmio (DistBase, HaveSysRegs ? SIZE_64KB : SIZE_4KB);
if (EFI_ERROR (Status)) {
GicMadtFatal ();
return Status;
}
PcdStatus = PcdSet64S (PcdGicDistributorBase, DistBase);
ASSERT_RETURN_ERROR (PcdStatus);
if ((GicrCount != 0) && (RedistBase != 0) && (RedistLen != 0)) {
Status = MapGicMmio (RedistBase, RedistLen);
if (EFI_ERROR (Status)) {
GicMadtFatal ();
return Status;
}
PcdStatus = PcdSet64S (PcdGicRedistributorsBase, RedistBase);
ASSERT_RETURN_ERROR (PcdStatus);
} else {
DEBUG ((
DEBUG_WARN,
"%a: PcdGicRedistributorsBase left at its build-time default 0x%Lx\n",
__func__,
PcdGet64 (PcdGicRedistributorsBase)
));
}
if (CpuIfBase != 0) {
//
// GicV2DxeInitialize() maps the CPU interface itself as
// GICC_V2_SIZE, i.e. 8 KiB. Use the same size here.
//
Status = MapGicMmio (CpuIfBase, SIZE_8KB);
if (EFI_ERROR (Status)) {
GicMadtFatal ();
return Status;
}
PcdStatus = PcdSet64S (PcdGicInterruptInterfaceBase, CpuIfBase);
ASSERT_RETURN_ERROR (PcdStatus);
} else {
DEBUG ((
DEBUG_WARN,
"%a: PcdGicInterruptInterfaceBase left at its build-time default "
"0x%Lx; the GICv2 fallback would use it if ICC_SRE_EL2.SRE were "
"denied\n",
__func__,
PcdGet64 (PcdGicInterruptInterfaceBase)
));
}
DEBUG ((
DEBUG_INFO,
"%a: GICD 0x%Lx, GICR 0x%Lx (len 0x%Lx), GICC 0x%Lx from MADT; "
"CPU %a GICv3 sysregs\n",
__func__,
DistBase,
RedistBase,
RedistLen,
CpuIfBase,
HaveSysRegs ? "has" : "lacks"
));
return EFI_SUCCESS;
}

View file

@ -0,0 +1,50 @@
## @file
# Discover the GIC Distributor, Redistributor and CPU-interface bases
# from the ACPI MADT supplied by the bootloader and populate the
# ArmGic PCDs.
#
# Plugged into ArmGicDxe as a NULL library so that the constructor
# runs before InterruptDxeInitialize() reads PcdGicDistributorBase,
# PcdGicRedistributorsBase and PcdGicInterruptInterfaceBase.
#
# Copyright (c) 2026, Amazon.com, Inc. or its affiliates.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
##
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = AcpiGicPcdLib
FILE_GUID = 4E7CB4BF-DF23-4AA1-8B1F-5A47DA0EC1E6
MODULE_TYPE = DXE_DRIVER
VERSION_STRING = 1.0
LIBRARY_CLASS = NULL|DXE_DRIVER
CONSTRUCTOR = AcpiGicPcdLibConstructor
[Sources]
AcpiGicPcdLib.c
[Packages]
ArmPkg/ArmPkg.dec
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
UefiPayloadPkg/UefiPayloadPkg.dec
[LibraryClasses]
AcpiTableWalkLib
ArmLib
BaseLib
DebugLib
DxeServicesTableLib
HobLib
PcdLib
[Guids]
gUniversalPayloadAcpiTableGuid
[Pcd]
gArmTokenSpaceGuid.PcdGicDistributorBase
gArmTokenSpaceGuid.PcdGicRedistributorsBase
gArmTokenSpaceGuid.PcdGicInterruptInterfaceBase
[Depex]
gEfiCpuArchProtocolGuid

View file

@ -0,0 +1,169 @@
/** @file
Locate ACPI tables by signature via a bootloader-supplied RSDP.
UefiPayloadPkg has three separate places that walk the RSDP's
XSDT/RSDT to find a table by signature: ChainloadApp (before it
builds the payload's HOB list, and again after ExitBootServices()),
AcpiGicPcdLib (from the RSDP handed over in the ACPI HOB), and
UefiPayloadEntry/AcpiTable.c (deriving the ACPI board info HOB).
Each carried its own bounds checking, and each was subtly stricter
or laxer than the others. This library is the single validated
walk they now share.
The library is BASE and depends only on BaseLib and DebugLib, so it
links into a SEC-phase payload entry, a DXE_DRIVER NULL library and
a UEFI_APPLICATION running under an outer firmware alike.
Copyright (c) 2026, Amazon.com, Inc. or its affiliates.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Base.h>
#include <IndustryStandard/Acpi.h>
#include <Library/AcpiTableWalkLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
/**
Return the first table with Signature in a validated system
description table.
@param[in] Sdt The XSDT or RSDT header.
@param[in] SdtSignature The signature Sdt must carry.
@param[in] EntrySize sizeof (UINT64) for XSDT, sizeof (UINT32) for RSDT.
@param[in] Signature The 4-byte table signature to find.
@return Pointer to the matching table header, or NULL.
**/
STATIC
EFI_ACPI_DESCRIPTION_HEADER *
FindInSdt (
IN EFI_ACPI_DESCRIPTION_HEADER *Sdt,
IN UINT32 SdtSignature,
IN UINTN EntrySize,
IN UINT32 Signature
)
{
EFI_ACPI_DESCRIPTION_HEADER *Tbl;
UINT8 *Entry;
UINTN Count;
UINTN Idx;
UINTN Addr;
//
// Validate the SDT before deriving an entry count from its length.
// A Length below the header size would wrap the unsigned subtraction
// and walk the loop off the end of the table.
//
if ((Sdt->Signature != SdtSignature) ||
(Sdt->Length < sizeof (EFI_ACPI_DESCRIPTION_HEADER)))
{
DEBUG ((
DEBUG_WARN,
"%a: bad SDT at 0x%p: signature 0x%x, length %u\n",
__func__,
Sdt,
Sdt->Signature,
Sdt->Length
));
return NULL;
}
Entry = (UINT8 *)(Sdt + 1);
Count = (Sdt->Length - sizeof (EFI_ACPI_DESCRIPTION_HEADER)) / EntrySize;
for (Idx = 0; Idx < Count; Idx++) {
if (EntrySize == sizeof (UINT64)) {
//
// The 36-byte common header leaves the 64-bit entry array
// 4-byte aligned, so an aligned load may fault on a strict
// architecture.
//
Addr = (UINTN)ReadUnaligned64 ((UINT64 *)Entry);
} else {
Addr = (UINTN)ReadUnaligned32 ((UINT32 *)Entry);
}
Entry += EntrySize;
Tbl = (EFI_ACPI_DESCRIPTION_HEADER *)Addr;
if ((Tbl != NULL) && (Tbl->Signature == Signature)) {
return Tbl;
}
}
return NULL;
}
/**
Locate an ACPI table by signature via the RSDP.
Walks the XSDT (or the RSDT if the RSDP is Revision 0 or has no
XSDT address) referenced by Rsdp and returns the first table
whose header signature matches Signature.
The RSDP signature, the XSDT/RSDT signature and length, and each
entry pointer are all validated before use. A Length shorter than
the common ACPI description header would underflow the entry-count
subtraction and walk off the end of the table; that case is
rejected. XSDT entry pointers are read with ReadUnaligned64()
because the 36-byte header leaves the 64-bit entry array 4-byte
aligned.
@param[in] Rsdp Physical address of the ACPI RSDP, or 0.
@param[in] Signature 4-byte ACPI table signature to find.
@return Pointer to the first matching table's description header,
or NULL if Rsdp is 0, the RSDP or SDT header is invalid,
or no table with Signature is present.
**/
EFI_ACPI_DESCRIPTION_HEADER *
EFIAPI
AcpiFindTableFromRsdp (
IN EFI_PHYSICAL_ADDRESS Rsdp,
IN UINT32 Signature
)
{
EFI_ACPI_6_5_ROOT_SYSTEM_DESCRIPTION_POINTER *Rp;
if (Rsdp == 0) {
return NULL;
}
Rp = (EFI_ACPI_6_5_ROOT_SYSTEM_DESCRIPTION_POINTER *)(UINTN)Rsdp;
if (Rp->Signature != EFI_ACPI_6_5_ROOT_SYSTEM_DESCRIPTION_POINTER_SIGNATURE) {
DEBUG ((
DEBUG_WARN,
"%a: RSDP at 0x%Lx has bad signature 0x%Lx\n",
__func__,
(UINT64)Rsdp,
Rp->Signature
));
return NULL;
}
//
// ACPI 6.5 5.2.5.3: XsdtAddress is present only for Revision >= 2.
// Prefer the XSDT when present; fall back to the RSDT otherwise.
//
if ((Rp->Revision >= 2) && (Rp->XsdtAddress != 0)) {
return FindInSdt (
(EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)Rp->XsdtAddress,
EFI_ACPI_6_5_EXTENDED_SYSTEM_DESCRIPTION_TABLE_SIGNATURE,
sizeof (UINT64),
Signature
);
}
if (Rp->RsdtAddress != 0) {
return FindInSdt (
(EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)Rp->RsdtAddress,
EFI_ACPI_6_5_ROOT_SYSTEM_DESCRIPTION_TABLE_SIGNATURE,
sizeof (UINT32),
Signature
);
}
return NULL;
}

View file

@ -0,0 +1,29 @@
## @file
# Locate ACPI tables by signature via a bootloader-supplied RSDP.
#
# Copyright (c) 2026, Amazon.com, Inc. or its affiliates.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
##
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = AcpiTableWalkLib
FILE_GUID = 25DAFD0A-4FB0-497F-A069-2F7E0DE6819E
MODULE_TYPE = BASE
VERSION_STRING = 1.0
LIBRARY_CLASS = AcpiTableWalkLib
#
# VALID_ARCHITECTURES = IA32 X64 AARCH64 RISCV64
#
[Sources]
AcpiTableWalkLib.c
[Packages]
MdePkg/MdePkg.dec
UefiPayloadPkg/UefiPayloadPkg.dec
[LibraryClasses]
BaseLib
DebugLib

View file

@ -9,6 +9,8 @@
extern BOOLEAN mBaseSerialPortLibHobAtRuntime;
STATIC EFI_EVENT mBaseSerialPortLibHobExitBootServicesEvent;
/**
Set mSerialIoUartLibAtRuntime flag as TRUE after ExitBootServices.
@ -44,13 +46,46 @@ DxeBaseSerialPortLibHobConstructor (
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_EVENT SerialPortLibHobExitBootServicesEvent;
return SystemTable->BootServices->CreateEvent (
EVT_SIGNAL_EXIT_BOOT_SERVICES,
TPL_NOTIFY,
BaseSerialPortLibHobExitBootServicesEvent,
NULL,
&SerialPortLibHobExitBootServicesEvent
&mBaseSerialPortLibHobExitBootServicesEvent
);
}
/**
The destructor closes the ExitBootServices event.
A driver that fails its entry point is unloaded again by the DXE core, but
this library has already registered its ExitBootServices callback by then.
Close the event in the destructor so that a stale notification function
pointing into the unloaded image is not left behind.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS No event was registered, or it was closed.
@retval other CloseEvent () failed.
**/
EFI_STATUS
EFIAPI
DxeBaseSerialPortLibHobDestructor (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
if (mBaseSerialPortLibHobExitBootServicesEvent == NULL) {
return EFI_SUCCESS;
}
Status = SystemTable->BootServices->CloseEvent (mBaseSerialPortLibHobExitBootServicesEvent);
if (!EFI_ERROR (Status)) {
mBaseSerialPortLibHobExitBootServicesEvent = NULL;
}
return Status;
}

View file

@ -14,6 +14,7 @@
VERSION_STRING = 1.0
LIBRARY_CLASS = SerialPortLib|DXE_CORE DXE_DRIVER DXE_RUNTIME_DRIVER DXE_SMM_DRIVER UEFI_APPLICATION UEFI_DRIVER
CONSTRUCTOR = DxeBaseSerialPortLibHobConstructor
DESTRUCTOR = DxeBaseSerialPortLibHobDestructor
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec

View file

@ -42,7 +42,6 @@ ResetSystemLibConstructor (
ASSERT (mAcpiBoardInfo.ResetRegAddress != 0);
ASSERT (mAcpiBoardInfo.ResetValue != 0);
ASSERT (mAcpiBoardInfo.PmGpeEnBase != 0);
ASSERT (mAcpiBoardInfo.PmEvtBase != 0);
ASSERT (mAcpiBoardInfo.PmCtrlRegBase != 0);
@ -103,7 +102,9 @@ ResetShutdown (
//
// GPE0_EN should be disabled to avoid any GPI waking up the system from S5
//
IoWrite16 ((UINTN)mAcpiBoardInfo.PmGpeEnBase, 0);
if (mAcpiBoardInfo.PmGpeEnBase != 0) {
IoWrite16 ((UINTN)mAcpiBoardInfo.PmGpeEnBase, 0);
}
//
// Clear Power Button Status

View file

@ -129,7 +129,7 @@ ParseSmbiosTable (
TableInfo = (UNIVERSAL_PAYLOAD_SMBIOS_TABLE *)GetGuidHobDataFromSbl (&gUniversalPayloadSmbiosTableGuid);
if (TableInfo == NULL) {
ASSERT (FALSE);
DEBUG ((DEBUG_INFO, "No SMBIOS table from bootloader\n"));
return RETURN_NOT_FOUND;
}

View file

@ -13,11 +13,376 @@
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Library/HobLib.h>
#include <Library/ArmMmuLib.h>
#include <Library/ArmLib.h>
#include "UefiPayloadEntry.h"
#define STACK_SIZE 0x20000
//
// Upper bound on the number of mappable resource descriptor HOBs the
// bootloader may hand over. Exceeding it is an error rather than a
// reason to map a subset of the address space; see
// ConfigureMmuFromHobs().
//
#define MAX_RESOURCE_HOBS 256
//
// Splitting N input ranges at every distinct boundary yields at most
// 2 * N - 1 elementary intervals, so an output table of 2 * N entries
// plus the zero-Length terminator can never overflow.
//
#define MAX_DESCRIPTORS (2 * MAX_RESOURCE_HOBS)
//
// One mappable range collected from a resource descriptor HOB, before
// overlaps between HOBs are resolved.
//
typedef struct {
UINT64 Start;
UINT64 End;
ARM_MEMORY_REGION_ATTRIBUTES Attributes;
UINTN Priority;
} MMU_INPUT_REGION;
STATIC ARM_MEMORY_REGION_DESCRIPTOR mVirtualMemoryTable[MAX_DESCRIPTORS + 1];
STATIC MMU_INPUT_REGION mInputRegions[MAX_RESOURCE_HOBS];
STATIC UINT64 mBoundaries[2 * MAX_RESOURCE_HOBS];
/**
Return the ARM memory attributes for a HOB resource descriptor.
ResourceType is the primary discriminator: MEMORY_RESERVED covers
firmware-reserved DRAM (ACPI NVS, the payload FV/HOB/stack), which
must be write-back so that unaligned accesses do not fault and no
mismatched-attribute alias of DRAM is created; MEMORY_MAPPED_IO is
Device. The cacheability bits in ResourceAttribute state which
types the range supports, not which type is wanted (the payload's
MemInfoCallbackMmio() advertises UC|WC|WT|WB on everything), so
only an unambiguous UNCACHEABLE-only attribute overrides the type
switch: that is how ECAM published as MEMORY_RESERVED under
PcdPublishMcfgAsReservedMemory is mapped Device rather than cacheable.
@param[in] Resource The HOB resource descriptor.
@param[out] Attr Returned ARM memory attributes.
@retval TRUE Descriptor should be mapped with the returned Attr.
@retval FALSE Descriptor should be skipped.
**/
STATIC
BOOLEAN
ArmAttributesForResourceHob (
IN EFI_HOB_RESOURCE_DESCRIPTOR *Resource,
OUT ARM_MEMORY_REGION_ATTRIBUTES *Attr
)
{
EFI_RESOURCE_ATTRIBUTE_TYPE Ra;
Ra = Resource->ResourceAttribute;
if (((Ra & EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE) != 0) &&
((Ra & (EFI_RESOURCE_ATTRIBUTE_WRITE_BACK_CACHEABLE |
EFI_RESOURCE_ATTRIBUTE_WRITE_THROUGH_CACHEABLE |
EFI_RESOURCE_ATTRIBUTE_WRITE_COMBINEABLE)) == 0))
{
*Attr = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;
return TRUE;
}
switch (Resource->ResourceType) {
case EFI_RESOURCE_SYSTEM_MEMORY:
case EFI_RESOURCE_MEMORY_RESERVED:
*Attr = ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK;
return TRUE;
case EFI_RESOURCE_MEMORY_MAPPED_IO:
*Attr = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;
return TRUE;
default:
break;
}
return FALSE;
}
/**
Return a priority for an ARM memory attribute, used to resolve
overlaps between resource descriptor HOBs.
A cacheable alias of a device aperture is a mismatched-attribute
alias, and its failure mode is an abort or silent data corruption.
Mapping a few pages of DRAM as Device only costs performance. So
where two descriptors overlap, the device/uncached attribute wins.
@param[in] Attributes ARM memory region attributes.
@return Priority. The higher value wins where two ranges overlap.
**/
STATIC
UINTN
ArmAttributePriority (
IN ARM_MEMORY_REGION_ATTRIBUTES Attributes
)
{
switch (Attributes) {
case ARM_MEMORY_REGION_ATTRIBUTE_DEVICE:
case ARM_MEMORY_REGION_ATTRIBUTE_UNCACHED_UNBUFFERED:
return 1;
default:
return 0;
}
}
/**
Configure the MMU from the HOB resource descriptors.
Only called when the payload is entered with the MMU off, i.e.
from a raw bootloader. ArmConfigureMmu() then populates a fresh
translation table, installs it in TTBR0 and enables the MMU.
ChainloadApp instead installs its own tables (in Reserved pages)
before ExitBootServices() and enters the payload with the MMU and
caches on; HandOffToDxeCore() adopts that live translation and
never reaches this function. Calling ArmConfigureMmu() with the
MMU already enabled and an unknown incoming TCR/MAIR is not safe:
it programs TCR and MAIR while the outgoing TTBR0 is still live,
and ArmSetTTBR0() performs no TLB invalidation, so stale entries
from the previous tables stay usable afterwards.
The bootloader's descriptors may overlap. The payload emits a
SYSTEM_MEMORY descriptor covering all of DRAM together with
MEMORY_RESERVED carve-outs inside it, and some of those carve-outs
have to be mapped Device rather than cacheable - ECAM published
under PcdPublishMcfgAsReservedMemory, for one. ArmConfigureMmu()
applies the descriptor array in order and a later descriptor
overwrites an earlier one, so handing it the HOB list as-is would
make the attribute of an ECAM aperture depend on the order the
bootloader happened to emit its HOBs in. Instead the ranges are
collected, split at every distinct boundary, and each resulting
interval is given the highest-priority attribute among the ranges
covering it. The result has no overlapping entries at all, so it
does not depend on HOB order.
@retval EFI_SUCCESS MMU configured.
@retval EFI_OUT_OF_RESOURCES More mappable resource descriptor HOBs
than MAX_RESOURCE_HOBS; the address
space cannot be mapped in full.
@retval EFI_NOT_FOUND No mappable resource descriptor HOB.
@retval other ArmConfigureMmu() failure.
**/
STATIC
EFI_STATUS
ConfigureMmuFromHobs (
VOID
)
{
EFI_PEI_HOB_POINTERS Hob;
EFI_HOB_RESOURCE_DESCRIPTOR *Resource;
ARM_MEMORY_REGION_ATTRIBUTES Attr;
ARM_MEMORY_REGION_ATTRIBUTES IntervalAttr;
VOID *TranslationTableBase;
UINTN TranslationTableSize;
UINTN RegionCount;
UINTN BoundaryCount;
UINTN Count;
UINTN Index;
UINTN Inner;
UINTN Best;
UINT64 Base;
UINT64 End;
UINT64 Value;
BOOLEAN Truncated;
BOOLEAN Found;
RegionCount = 0;
Truncated = FALSE;
//
// Collect every mappable range. Overlaps are resolved below, so the
// order the HOBs arrive in does not matter here.
//
Hob.Raw = GetFirstHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR);
while (Hob.Raw != NULL) {
Resource = (EFI_HOB_RESOURCE_DESCRIPTOR *)Hob.Raw;
if (ArmAttributesForResourceHob (Resource, &Attr)) {
if (RegionCount == MAX_RESOURCE_HOBS) {
Truncated = TRUE;
break;
}
Base = Resource->PhysicalStart & ~(UINT64)EFI_PAGE_MASK;
End = ALIGN_VALUE (
Resource->PhysicalStart + Resource->ResourceLength,
EFI_PAGE_SIZE
);
//
// A sub-page-aligned MMIO region adjacent to RAM would otherwise
// produce two entries covering the same page with different
// attributes. Warn on bootloader-supplied misalignment and align
// outward (the end is already ALIGN_VALUE()'d up), rather than
// asserting on data this code does not control.
//
if (Resource->PhysicalStart != Base) {
DEBUG ((
DEBUG_WARN,
"%a: resource at 0x%Lx not page-aligned; aligning outward\n",
__func__,
Resource->PhysicalStart
));
}
if (End > Base) {
mInputRegions[RegionCount].Start = Base;
mInputRegions[RegionCount].End = End;
mInputRegions[RegionCount].Attributes = Attr;
mInputRegions[RegionCount].Priority = ArmAttributePriority (Attr);
RegionCount++;
}
}
Hob.Raw = GET_NEXT_HOB (Hob);
Hob.Raw = GetNextHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR, Hob.Raw);
}
//
// Truncating the HOB walk would leave regions the payload needs out
// of the page tables, and the failure would surface much later as an
// unrelated abort. Fail here instead, so that HandOffToDxeCore()
// reports the cause.
//
if (Truncated) {
DEBUG ((
DEBUG_ERROR,
"%a: more than %u mappable resource descriptor HOBs; "
"refusing to map a truncated address space\n",
__func__,
(UINT32)MAX_RESOURCE_HOBS
));
return EFI_OUT_OF_RESOURCES;
}
if (RegionCount == 0) {
DEBUG ((
DEBUG_ERROR,
"%a: no mappable resource descriptor HOB\n",
__func__
));
return EFI_NOT_FOUND;
}
//
// Collect, sort and deduplicate the range boundaries. Every interval
// between two adjacent boundaries is covered by a constant set of
// input ranges and so has one unambiguous attribute.
//
BoundaryCount = 0;
for (Index = 0; Index < RegionCount; Index++) {
mBoundaries[BoundaryCount++] = mInputRegions[Index].Start;
mBoundaries[BoundaryCount++] = mInputRegions[Index].End;
}
for (Index = 1; Index < BoundaryCount; Index++) {
Value = mBoundaries[Index];
for (Inner = Index; (Inner > 0) && (mBoundaries[Inner - 1] > Value); Inner--) {
mBoundaries[Inner] = mBoundaries[Inner - 1];
}
mBoundaries[Inner] = Value;
}
Count = 0;
for (Index = 0; Index < BoundaryCount; Index++) {
if ((Count == 0) || (mBoundaries[Count - 1] != mBoundaries[Index])) {
mBoundaries[Count++] = mBoundaries[Index];
}
}
BoundaryCount = Count;
//
// Emit one descriptor per interval, giving it the highest-priority
// attribute among the ranges covering it, and coalescing adjacent
// intervals that resolved to the same attribute.
//
Count = 0;
for (Index = 0; (Index + 1) < BoundaryCount; Index++) {
Base = mBoundaries[Index];
End = mBoundaries[Index + 1];
Found = FALSE;
Best = 0;
IntervalAttr = ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK;
for (Inner = 0; Inner < RegionCount; Inner++) {
if ((mInputRegions[Inner].Start > Base) || (mInputRegions[Inner].End < End)) {
continue;
}
if (!Found || (mInputRegions[Inner].Priority > Best)) {
Best = mInputRegions[Inner].Priority;
IntervalAttr = mInputRegions[Inner].Attributes;
Found = TRUE;
}
}
//
// A gap between two input ranges stays unmapped.
//
if (!Found) {
continue;
}
if ((Count > 0) &&
(mVirtualMemoryTable[Count - 1].Attributes == IntervalAttr) &&
(mVirtualMemoryTable[Count - 1].PhysicalBase +
mVirtualMemoryTable[Count - 1].Length == Base))
{
mVirtualMemoryTable[Count - 1].Length += End - Base;
continue;
}
ASSERT (Count < MAX_DESCRIPTORS);
mVirtualMemoryTable[Count].PhysicalBase = Base;
mVirtualMemoryTable[Count].VirtualBase = Base;
mVirtualMemoryTable[Count].Length = End - Base;
mVirtualMemoryTable[Count].Attributes = IntervalAttr;
Count++;
}
//
// The table is built from a sorted, deduplicated boundary list, so no
// two entries can overlap. Assert it rather than assume it: a
// violation would mean two descriptors with different attributes
// cover the same page, which is the mismatched-attribute alias this
// flattening exists to eliminate, and whose outcome would once again
// depend on the order the descriptors are applied in.
//
for (Index = 1; Index < Count; Index++) {
ASSERT (
mVirtualMemoryTable[Index - 1].PhysicalBase +
mVirtualMemoryTable[Index - 1].Length <=
mVirtualMemoryTable[Index].PhysicalBase
);
}
ZeroMem (&mVirtualMemoryTable[Count], sizeof (mVirtualMemoryTable[Count]));
DEBUG ((
DEBUG_INFO,
"%a: mapping %u regions from %u resource HOBs\n",
__func__,
(UINT32)Count,
(UINT32)RegionCount
));
return ArmConfigureMmu (
mVirtualMemoryTable,
&TranslationTableBase,
&TranslationTableSize
);
}
/**
Transfers control to DxeCore.
@ -35,8 +400,37 @@ HandOffToDxeCore (
IN EFI_PEI_HOB_POINTERS HobList
)
{
VOID *BaseOfStack;
VOID *TopOfStack;
VOID *BaseOfStack;
VOID *TopOfStack;
EFI_STATUS Status;
if (ArmMmuEnabled ()) {
//
// ChainloadApp entered the payload with the MMU and caches on and
// its own translation tables live: those tables are pinned by an
// EfiBootServicesData memory-allocation HOB emitted from the
// launcher's boot-time reservation list, so DXE cannot allocate
// over them and the OS reclaims them once it has installed its
// own translation. ArmPkg's CpuDxe
// (ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c) begins with
// ASSERT(ArmMmuEnabled()) and manages memory attributes by editing
// the live tables via ArmSetMemoryAttributes(); it never needs a
// freshly-built hierarchy. Adopt the incoming translation as-is.
//
DEBUG ((DEBUG_INFO, "HandOffToDxeCore: MMU already enabled, adopting live translation\n"));
} else {
//
// Raw-bootloader path: build our own page tables from the
// resource-descriptor HOBs. ArmConfigureMmu() populates a table
// in payload-owned pages, installs it in TTBR0 and enables the
// MMU.
//
Status = ConfigureMmuFromHobs ();
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "HandOffToDxeCore: Failed to enable MMU: %r\n", Status));
CpuDeadLoop ();
}
}
//
// Allocate 128KB for the Stack

View file

@ -25,92 +25,93 @@ ParseAcpiInfo (
OUT ACPI_BOARD_INFO *AcpiBoardInfo
)
{
EFI_ACPI_3_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp;
EFI_ACPI_DESCRIPTION_HEADER *Rsdt;
UINT32 *Entry32;
UINTN Entry32Num;
EFI_ACPI_3_0_FIXED_ACPI_DESCRIPTION_TABLE *Fadt;
EFI_ACPI_DESCRIPTION_HEADER *Xsdt;
UINT64 *Entry64;
UINTN Entry64Num;
UINTN Idx;
UINT32 *Signature;
UINTN Index;
UINTN MmCfgCount;
UINT8 MinStart;
UINT8 MaxEnd;
EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE_HEADER *MmCfgHdr;
EFI_ACPI_MEMORY_MAPPED_ENHANCED_CONFIGURATION_SPACE_BASE_ADDRESS_ALLOCATION_STRUCTURE *MmCfgBase;
Rsdp = (EFI_ACPI_3_0_ROOT_SYSTEM_DESCRIPTION_POINTER *)(UINTN)AcpiTableBase;
DEBUG ((DEBUG_INFO, "Rsdp at 0x%p\n", Rsdp));
DEBUG ((DEBUG_INFO, "Rsdt at 0x%x, Xsdt at 0x%lx\n", Rsdp->RsdtAddress, Rsdp->XsdtAddress));
//
// Search Rsdt First
//
Fadt = NULL;
MmCfgHdr = NULL;
Rsdt = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)(Rsdp->RsdtAddress);
if (Rsdt != NULL) {
Entry32 = (UINT32 *)(Rsdt + 1);
Entry32Num = (Rsdt->Length - sizeof (EFI_ACPI_DESCRIPTION_HEADER)) >> 2;
for (Idx = 0; Idx < Entry32Num; Idx++) {
Signature = (UINT32 *)(UINTN)Entry32[Idx];
if (*Signature == EFI_ACPI_3_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE) {
Fadt = (EFI_ACPI_3_0_FIXED_ACPI_DESCRIPTION_TABLE *)Signature;
DEBUG ((DEBUG_INFO, "Found Fadt in Rsdt\n"));
}
if (*Signature == EFI_ACPI_5_0_PCI_EXPRESS_MEMORY_MAPPED_CONFIGURATION_SPACE_BASE_ADDRESS_DESCRIPTION_TABLE_SIGNATURE) {
MmCfgHdr = (EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE_HEADER *)Signature;
DEBUG ((DEBUG_INFO, "Found MM config address in Rsdt\n"));
}
if ((Fadt != NULL) && (MmCfgHdr != NULL)) {
goto Done;
}
}
}
//
// Search Xsdt Second
//
Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)(Rsdp->XsdtAddress);
if (Xsdt != NULL) {
Entry64 = (UINT64 *)(Xsdt + 1);
Entry64Num = (Xsdt->Length - sizeof (EFI_ACPI_DESCRIPTION_HEADER)) >> 3;
for (Idx = 0; Idx < Entry64Num; Idx++) {
Signature = (UINT32 *)(UINTN)ReadUnaligned64 (&Entry64[Idx]);
if (*Signature == EFI_ACPI_3_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE) {
Fadt = (EFI_ACPI_3_0_FIXED_ACPI_DESCRIPTION_TABLE *)Signature;
DEBUG ((DEBUG_INFO, "Found Fadt in Xsdt\n"));
}
if (*Signature == EFI_ACPI_5_0_PCI_EXPRESS_MEMORY_MAPPED_CONFIGURATION_SPACE_BASE_ADDRESS_DESCRIPTION_TABLE_SIGNATURE) {
MmCfgHdr = (EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE_HEADER *)Signature;
DEBUG ((DEBUG_INFO, "Found MM config address in Xsdt\n"));
}
if ((Fadt != NULL) && (MmCfgHdr != NULL)) {
goto Done;
}
}
}
Fadt = (EFI_ACPI_3_0_FIXED_ACPI_DESCRIPTION_TABLE *)AcpiFindTableFromRsdp (
AcpiTableBase,
EFI_ACPI_3_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE
);
MmCfgHdr = (EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE_HEADER *)AcpiFindTableFromRsdp (
AcpiTableBase,
EFI_ACPI_5_0_PCI_EXPRESS_MEMORY_MAPPED_CONFIGURATION_SPACE_BASE_ADDRESS_DESCRIPTION_TABLE_SIGNATURE
);
if (Fadt == NULL) {
return RETURN_NOT_FOUND;
}
Done:
AcpiBoardInfo->PmCtrlRegBase = Fadt->Pm1aCntBlk;
AcpiBoardInfo->PmTimerRegBase = Fadt->PmTmrBlk;
AcpiBoardInfo->ResetRegAddress = Fadt->ResetReg.Address;
AcpiBoardInfo->ResetValue = Fadt->ResetValue;
AcpiBoardInfo->PmEvtBase = Fadt->Pm1aEvtBlk;
AcpiBoardInfo->PmGpeEnBase = Fadt->Gpe0Blk + Fadt->Gpe0BlkLen / 2;
if (MmCfgHdr != NULL) {
//
// The GPE0 enable register is the upper half of a GPE0 register pair
// that is Gpe0BlkLen bytes long. A FADT reporting a zero length has no
// enable register at all, and adding half of zero would name the GPE0
// status block instead: report 0 so that consumers skip the access
// rather than clearing status bits.
//
if (Fadt->Gpe0BlkLen != 0) {
AcpiBoardInfo->PmGpeEnBase = Fadt->Gpe0Blk + Fadt->Gpe0BlkLen / 2;
} else {
AcpiBoardInfo->PmGpeEnBase = 0;
}
//
// A table shorter than one allocation structure carries no usable
// allocation: report no MCFG rather than computing a count from an
// underflowing length subtraction and walking off the end of it.
//
if ((MmCfgHdr != NULL) &&
(MmCfgHdr->Header.Length >= sizeof (*MmCfgHdr) + sizeof (*MmCfgBase)))
{
MmCfgBase = (EFI_ACPI_MEMORY_MAPPED_ENHANCED_CONFIGURATION_SPACE_BASE_ADDRESS_ALLOCATION_STRUCTURE *)((UINT8 *)MmCfgHdr + sizeof (*MmCfgHdr));
AcpiBoardInfo->PcieBaseAddress = MmCfgBase->BaseAddress;
AcpiBoardInfo->PcieBaseSize = (MmCfgBase->EndBusNumber + 1 - MmCfgBase->StartBusNumber) * 4096 * 32 * 8;
//
// Some platforms describe multiple root bridges on segment 0 with
// separate MCFG allocation entries that share the same BaseAddress
// but split the bus range. PcieBaseSize gates every ECAM read via
// PcdPciExpressBaseSize, so span the lowest StartBusNumber to the
// highest EndBusNumber across all entries with the same base rather
// than only the first entry. The window starts at StartBusNumber,
// so that term has to stay in the size or config accesses past the
// end of the window are permitted on any platform whose allocation
// does not begin at bus 0.
//
MmCfgCount = (MmCfgHdr->Header.Length - sizeof (*MmCfgHdr)) / sizeof (*MmCfgBase);
MinStart = MmCfgBase->StartBusNumber;
MaxEnd = MmCfgBase->EndBusNumber;
for (Index = 1; Index < MmCfgCount; Index++) {
if (MmCfgBase[Index].BaseAddress != MmCfgBase->BaseAddress) {
continue;
}
if (MmCfgBase[Index].StartBusNumber < MinStart) {
MinStart = MmCfgBase[Index].StartBusNumber;
}
if (MmCfgBase[Index].EndBusNumber > MaxEnd) {
MaxEnd = MmCfgBase[Index].EndBusNumber;
}
}
if (MaxEnd >= MinStart) {
AcpiBoardInfo->PcieBaseSize = ((UINT64)MaxEnd + 1 - MinStart) * 4096 * 32 * 8;
} else {
//
// Reversed bus range: no usable window.
//
AcpiBoardInfo->PcieBaseSize = 0;
}
} else {
AcpiBoardInfo->PcieBaseAddress = 0;
AcpiBoardInfo->PcieBaseSize = 0;

View file

@ -55,6 +55,7 @@
UefiPayloadPkg/UefiPayloadPkg.dec
[LibraryClasses]
AcpiTableWalkLib
BaseMemoryLib
DebugLib
BaseLib

View file

@ -248,8 +248,13 @@ FileFindSection (
}
/**
Find DXE core from FV and build DXE core HOBs.
Find DXE core from the payload FV and build DXE core HOBs.
The caller resolves the payload FV location once, so that the FV this
function reads from is always the same one the entry point reserved
with BuildMemoryAllocationHob ().
@param[in] PayloadFv The payload FV that contains the DXE FV.
@param[out] DxeCoreEntryPoint DXE core entry point
@retval EFI_SUCCESS If it completed successfully.
@ -257,19 +262,17 @@ FileFindSection (
**/
EFI_STATUS
LoadDxeCore (
OUT PHYSICAL_ADDRESS *DxeCoreEntryPoint
IN EFI_FIRMWARE_VOLUME_HEADER *PayloadFv,
OUT PHYSICAL_ADDRESS *DxeCoreEntryPoint
)
{
EFI_STATUS Status;
EFI_FIRMWARE_VOLUME_HEADER *PayloadFv;
EFI_FIRMWARE_VOLUME_HEADER *DxeCoreFv;
EFI_FFS_FILE_HEADER *FileHeader;
VOID *PeCoffImage;
EFI_PHYSICAL_ADDRESS ImageAddress;
UINT64 ImageSize;
PayloadFv = (EFI_FIRMWARE_VOLUME_HEADER *)(UINTN)PcdGet32 (PcdPayloadFdMemBase);
//
// DXE FV is inside Payload FV. Here find DXE FV from Payload FV
//

View file

@ -8,10 +8,23 @@
**/
#include <Guid/MemoryTypeInformation.h>
#include <Guid/BootTimeReservationGuid.h>
#include <Library/BaseArchLibSupport.h>
#include "UefiPayloadEntry.h"
STATIC UINT32 mTopOfLowerUsableDram = 0;
STATIC UINT32 mTopOfLowerUsableDram = 0;
STATIC BOOLEAN mMcfgResourceHobBuilt = FALSE;
//
// Boot-time reservations from the launcher's
// gLoaderBootTimeReservationGuid HOB, if it emitted one.
// FindFreeMemForHobCallback() excludes ranges overlapping any of
// these; BuildGenericHob() pins each with an EfiBootServicesData
// memory-allocation HOB. A launcher that does not emit the HOB
// (Slim Bootloader, coreboot) leaves both NULL/0.
//
STATIC LOADER_BOOT_TIME_RESERVATION_ENTRY *mBootTimeReservation;
STATIC UINTN mBootTimeReservationCount;
EFI_MEMORY_TYPE_INFORMATION mDefaultMemoryTypeInformation[] = {
{ EfiACPIReclaimMemory, FixedPcdGet32 (PcdMemoryTypeEfiACPIReclaimMemory) },
@ -52,6 +65,20 @@ MemInfoCallbackMmio (
return EFI_INVALID_PARAMETER;
}
//
// Note any entry that overlaps the ECAM window, whether or not it
// starts at exactly its base. BuildHobFromBl() publishes the window
// from ACPI MCFG only when nothing in the bootloader's map covered it,
// because CoreInitializeGcdServices() does not tolerate overlapping
// resource descriptor HOBs.
//
if ((AcpiBoardInfo->PcieBaseSize != 0) &&
(MemoryMapEntry->Base < (AcpiBoardInfo->PcieBaseAddress + AcpiBoardInfo->PcieBaseSize)) &&
((MemoryMapEntry->Base + MemoryMapEntry->Size) > AcpiBoardInfo->PcieBaseAddress))
{
mMcfgResourceHobBuilt = TRUE;
}
//
// Skip types already handled in MemInfoCallback
//
@ -61,7 +88,44 @@ MemInfoCallbackMmio (
if (MemoryMapEntry->Base == AcpiBoardInfo->PcieBaseAddress) {
//
// MMCONF is always MMIO
// MMCONF is always MMIO. Optionally surface it as Reserved instead;
// see PcdPublishMcfgAsReservedMemory in UefiPayloadPkg.dec for why an
// OS may need that. This check runs before the MEM_MAP_FLAG_MMIO
// branch below because a bootloader that discovers MMIO from the
// outer firmware's GCD map will emit the ECAM window with that flag
// set.
//
if (FeaturePcdGet (PcdPublishMcfgAsReservedMemory)) {
//
// Reserved so Linux accepts it, but it is still device memory:
// advertise UNCACHEABLE only so that a consumer building page
// tables from these HOBs does not map config space write-back.
//
Type = EFI_RESOURCE_MEMORY_RESERVED;
BuildResourceDescriptorHob (
Type,
EFI_RESOURCE_ATTRIBUTE_PRESENT |
EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
EFI_RESOURCE_ATTRIBUTE_TESTED |
EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE,
(EFI_PHYSICAL_ADDRESS)MemoryMapEntry->Base,
MemoryMapEntry->Size
);
DEBUG ((
DEBUG_INFO,
"buildhob: base = 0x%lx, size = 0x%lx, type = 0x%x (MMCONF, UC-only)\n",
MemoryMapEntry->Base,
MemoryMapEntry->Size,
Type
));
return EFI_SUCCESS;
} else {
Type = EFI_RESOURCE_MEMORY_MAPPED_IO;
}
} else if ((MemoryMapEntry->Flag & MEM_MAP_FLAG_MMIO) != 0) {
//
// The bootloader explicitly marked this range as device MMIO, so
// take it at its word instead of guessing from the address.
//
Type = EFI_RESOURCE_MEMORY_MAPPED_IO;
} else if (MemoryMapEntry->Base < mTopOfLowerUsableDram) {
@ -69,11 +133,16 @@ MemInfoCallbackMmio (
// It's in DRAM and thus must be reserved
//
Type = EFI_RESOURCE_MEMORY_RESERVED;
#if defined (MDE_CPU_IA32) || defined (MDE_CPU_X64)
} else if ((MemoryMapEntry->Base < 0x100000000ULL) && (MemoryMapEntry->Base >= mTopOfLowerUsableDram)) {
//
// It's not in DRAM, must be MMIO
// On x86, reserved ranges above TOLUD and below 4 GiB are the
// MMIO hole. This heuristic does not apply on AArch64 where
// DRAM commonly starts at or above 1 GiB and the payload FV
// itself is a reserved range in that window.
//
Type = EFI_RESOURCE_MEMORY_MAPPED_IO;
#endif
} else {
Type = EFI_RESOURCE_MEMORY_RESERVED;
}
@ -169,6 +238,36 @@ FindToludCallback (
return EFI_SUCCESS;
}
/**
Return whether a range overlaps any of the launcher's boot-time
reservations.
@param[in] Base Range start.
@param[in] End Range end (exclusive).
@retval TRUE The range overlaps a boot-time reservation.
@retval FALSE It does not, or no launcher published any.
**/
STATIC
BOOLEAN
OverlapsBootTimeReservation (
IN UINT64 Base,
IN UINT64 End
)
{
UINTN Index;
for (Index = 0; Index < mBootTimeReservationCount; Index++) {
if ((Base < (mBootTimeReservation[Index].Base + mBootTimeReservation[Index].Size)) &&
(End > mBootTimeReservation[Index].Base))
{
return TRUE;
}
}
return FALSE;
}
/**
Callback function to find free and usable DRAM for HOB
The memory region returned will have at least PcdSystemMemoryUefiRegionSize bytes
@ -190,6 +289,7 @@ FindFreeMemForHobCallback (
)
{
EFI_STATUS Status;
MEMORY_MAP_ENTRY Entry;
MEMORY_MAP_ENTRY MemoryMapEntrySplit;
UINTN *HobMemBase = (UINTN *)Params;
@ -207,56 +307,81 @@ FindFreeMemForHobCallback (
return EFI_SUCCESS;
}
//
// Operate on a copy so the caller's memory map is not modified.
// SblParseLib passes pointers into the bootloader's HOB and the same
// array is walked again later to publish system memory resource HOBs.
//
Entry = *MemoryMapEntry;
//
// Align on 1 MiB
//
if (ALIGN_VALUE (MemoryMapEntry->Base, SIZE_1MB) > MemoryMapEntry->Base) {
if (ALIGN_VALUE (Entry.Base, SIZE_1MB) > Entry.Base) {
//
// Skip too small
//
if (ALIGN_VALUE (MemoryMapEntry->Base, SIZE_1MB) >= (MemoryMapEntry->Base + MemoryMapEntry->Size)) {
if (ALIGN_VALUE (Entry.Base, SIZE_1MB) >= (Entry.Base + Entry.Size)) {
return EFI_SUCCESS;
}
MemoryMapEntry->Size -= ALIGN_VALUE (MemoryMapEntry->Base, SIZE_1MB) - MemoryMapEntry->Base;
MemoryMapEntry->Base = ALIGN_VALUE (MemoryMapEntry->Base, SIZE_1MB);
Entry.Size -= ALIGN_VALUE (Entry.Base, SIZE_1MB) - Entry.Base;
Entry.Base = ALIGN_VALUE (Entry.Base, SIZE_1MB);
}
//
// Skip resources above 4GiB on x86_32
//
if ((sizeof (UINTN) == 4) && (MemoryMapEntry->Base >= 0x100000000ULL)) {
if ((sizeof (UINTN) == 4) && (Entry.Base >= 0x100000000ULL)) {
return EFI_SUCCESS;
}
if ((sizeof (UINTN) == 4) && ((MemoryMapEntry->Base + MemoryMapEntry->Size) > 0x100000000ULL)) {
MemoryMapEntry->Size = 0x100000000ULL - MemoryMapEntry->Base;
if ((sizeof (UINTN) == 4) && ((Entry.Base + Entry.Size) > 0x100000000ULL)) {
Entry.Size = 0x100000000ULL - Entry.Base;
}
//
// Skip too small
//
if (MemoryMapEntry->Size < FixedPcdGet32 (PcdSystemMemoryUefiRegionSize)) {
if (Entry.Size < FixedPcdGet32 (PcdSystemMemoryUefiRegionSize)) {
return EFI_SUCCESS;
}
//
// A ChainloadApp launcher reports its own FV/HOB/stack/page-table
// allocations as SBL type 1 records so they become SYSTEM_MEMORY
// rather than MEMORY_RESERVED. They are still live: HOB memory
// must not land on any of them. What actually protects them is
// the BuildMemoryAllocationHob() pin in BuildGenericHob() below;
// this check is belt-and-braces so an SBL type-1 entry that
// overlapped one is skipped outright rather than split. The
// launcher emits one SBL entry per outer memory-map descriptor
// and never merges Reserved with Conventional, so on today's
// launcher this check does not fire. A launcher that emits no
// gLoaderBootTimeReservationGuid HOB (Slim Bootloader, coreboot)
// never enters this branch.
//
if (OverlapsBootTimeReservation (Entry.Base, Entry.Base + Entry.Size)) {
return EFI_SUCCESS;
}
//
// Overlaps UefiPayload, split into smaller chunks
//
if ((MemoryMapEntry->Base <= PcdGet32 (PcdPayloadFdMemBase)) &&
((MemoryMapEntry->Base + MemoryMapEntry->Size) >= PcdGet32 (PcdPayloadFdMemBase)))
if ((Entry.Base <= PcdGet32 (PcdPayloadFdMemBase)) &&
((Entry.Base + Entry.Size) >= PcdGet32 (PcdPayloadFdMemBase)))
{
MemoryMapEntrySplit.Type = E820_RAM;
MemoryMapEntrySplit.Base = MemoryMapEntry->Base;
MemoryMapEntrySplit.Base = Entry.Base;
MemoryMapEntrySplit.Size = PcdGet32 (PcdPayloadFdMemBase) - MemoryMapEntrySplit.Base;
Status = FindFreeMemForHobCallback (&MemoryMapEntrySplit, Params);
if (EFI_ERROR (Status)) {
return Status;
}
if ((MemoryMapEntry->Base + MemoryMapEntry->Size) > (PcdGet32 (PcdPayloadFdMemBase) + PcdGet32 (PcdPayloadFdMemSize))) {
if ((Entry.Base + Entry.Size) > (PcdGet32 (PcdPayloadFdMemBase) + PcdGet32 (PcdPayloadFdMemSize))) {
MemoryMapEntrySplit.Base = PcdGet32 (PcdPayloadFdMemBase) + PcdGet32 (PcdPayloadFdMemSize);
MemoryMapEntrySplit.Size = (MemoryMapEntry->Base + MemoryMapEntry->Size) - MemoryMapEntrySplit.Base;
MemoryMapEntrySplit.Size = (Entry.Base + Entry.Size) - MemoryMapEntrySplit.Base;
Status = FindFreeMemForHobCallback (&MemoryMapEntrySplit, Params);
if (EFI_ERROR (Status)) {
return Status;
@ -266,7 +391,7 @@ FindFreeMemForHobCallback (
return EFI_SUCCESS;
}
*HobMemBase = MemoryMapEntry->Base;
*HobMemBase = Entry.Base;
return EFI_ALREADY_STARTED;
}
@ -348,6 +473,7 @@ BuildHobFromBl (
EFI_PEI_GRAPHICS_INFO_HOB *NewGfxInfo;
EFI_PEI_GRAPHICS_DEVICE_INFO_HOB GfxDeviceInfo;
EFI_PEI_GRAPHICS_DEVICE_INFO_HOB *NewGfxDeviceInfo;
UNIVERSAL_PAYLOAD_SMBIOS_TABLE SmBiosTable;
UNIVERSAL_PAYLOAD_SMBIOS_TABLE *SmBiosTableHob;
UNIVERSAL_PAYLOAD_ACPI_TABLE *AcpiTableHob;
@ -415,13 +541,14 @@ BuildHobFromBl (
//
// Create SmBios table Hob
//
SmBiosTableHob = BuildGuidHob (&gUniversalPayloadSmbiosTableGuid, sizeof (UNIVERSAL_PAYLOAD_SMBIOS_TABLE));
ASSERT (SmBiosTableHob != NULL);
SmBiosTableHob->Header.Revision = UNIVERSAL_PAYLOAD_SMBIOS_TABLE_REVISION;
SmBiosTableHob->Header.Length = sizeof (UNIVERSAL_PAYLOAD_SMBIOS_TABLE);
DEBUG ((DEBUG_INFO, "Create smbios table gUniversalPayloadSmbiosTableGuid guid hob\n"));
Status = ParseSmbiosTable (SmBiosTableHob);
ZeroMem (&SmBiosTable, sizeof (SmBiosTable));
Status = ParseSmbiosTable (&SmBiosTable);
if (!EFI_ERROR (Status)) {
SmBiosTableHob = BuildGuidHob (&gUniversalPayloadSmbiosTableGuid, sizeof (UNIVERSAL_PAYLOAD_SMBIOS_TABLE));
ASSERT (SmBiosTableHob != NULL);
SmBiosTableHob->Header.Revision = UNIVERSAL_PAYLOAD_SMBIOS_TABLE_REVISION;
SmBiosTableHob->Header.Length = sizeof (UNIVERSAL_PAYLOAD_SMBIOS_TABLE);
SmBiosTableHob->SmBiosEntryPoint = SmBiosTable.SmBiosEntryPoint;
DEBUG ((DEBUG_INFO, "Detected Smbios Table at 0x%lx\n", SmBiosTableHob->SmBiosEntryPoint));
}
@ -453,6 +580,40 @@ BuildHobFromBl (
return Status;
}
//
// The bootloader's memory map may not cover the ECAM range at all,
// in which case MemInfoCallbackMmio() never fires for it. Publish
// the range parsed from ACPI MCFG so that it is present in the GCD
// memory map (and, on AArch64, in the payload page tables) before
// PciHostBridgeDxe touches config space.
//
// Gated on the same PCD as the Reserved publication above: a platform
// that leaves the PCD at its default keeps the memory map it always
// had, and one that describes the window inside a larger range does
// not get a second, overlapping descriptor for it.
//
if (FeaturePcdGet (PcdPublishMcfgAsReservedMemory) &&
(AcpiBoardInfo->PcieBaseAddress != 0) &&
(AcpiBoardInfo->PcieBaseSize != 0) &&
!mMcfgResourceHobBuilt)
{
BuildResourceDescriptorHob (
EFI_RESOURCE_MEMORY_RESERVED,
EFI_RESOURCE_ATTRIBUTE_PRESENT |
EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
EFI_RESOURCE_ATTRIBUTE_TESTED |
EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE,
(EFI_PHYSICAL_ADDRESS)AcpiBoardInfo->PcieBaseAddress,
AcpiBoardInfo->PcieBaseSize
);
DEBUG ((
DEBUG_INFO,
"buildhob: base = 0x%lx, size = 0x%lx (MMCONF from ACPI MCFG)\n",
AcpiBoardInfo->PcieBaseAddress,
AcpiBoardInfo->PcieBaseSize
));
}
//
// Parse the misc info provided by bootloader
//
@ -483,25 +644,220 @@ BuildHobFromBl (
}
/**
This function will build some generic HOBs that doesn't depend on information from bootloaders.
Locate the bootloader's ExtraData HOB and report how many entries it
actually has room for.
Only bootloaders that hand over a PEI-format HOB list (Slim Bootloader
and compatible frontends such as ChainloadApp) can carry an ExtraData
HOB. coreboot passes a coreboot table pointer, which fails the handoff
header check and returns NULL.
@param[in] BootloaderParameter Bootloader-provided argument.
@param[out] Count Entry count, clamped to what the HOB's
own data size can hold.
@return The ExtraData structure, or NULL if the bootloader did not hand
over a PEI HOB list carrying one.
**/
STATIC
UNIVERSAL_PAYLOAD_EXTRA_DATA *
FindBootloaderExtraDataHob (
IN UINTN BootloaderParameter,
OUT UINTN *Count
)
{
EFI_PEI_HOB_POINTERS BlHob;
UNIVERSAL_PAYLOAD_EXTRA_DATA *ExtraData;
UINTN DataSize;
BlHob.Raw = (UINT8 *)BootloaderParameter;
if ((BlHob.Raw == NULL) ||
(BlHob.Header->HobType != EFI_HOB_TYPE_HANDOFF) ||
(BlHob.Header->HobLength != sizeof (EFI_HOB_HANDOFF_INFO_TABLE)))
{
return NULL;
}
BlHob.Raw = GetNextGuidHob (&gUniversalPayloadExtraDataGuid, BlHob.Raw);
if (BlHob.Raw == NULL) {
return NULL;
}
ExtraData = (UNIVERSAL_PAYLOAD_EXTRA_DATA *)GET_GUID_HOB_DATA (BlHob.Raw);
DataSize = GET_GUID_HOB_DATA_SIZE (BlHob.Raw);
if (DataSize < sizeof (UNIVERSAL_PAYLOAD_EXTRA_DATA)) {
return NULL;
}
*Count = MIN (
(UINTN)ExtraData->Count,
(DataSize - sizeof (UNIVERSAL_PAYLOAD_EXTRA_DATA)) /
sizeof (UNIVERSAL_PAYLOAD_EXTRA_DATA_ENTRY)
);
return ExtraData;
}
/**
Locate the launcher's boot-time reservation HOB, if it published one.
@param[in] BootloaderParameter Bootloader-provided argument.
@param[out] Count Entry count, clamped to what the
HOB's own data size can hold.
@return First reservation entry, or NULL if the launcher did not
hand over a PEI HOB list carrying the GUID HOB.
**/
STATIC
LOADER_BOOT_TIME_RESERVATION_ENTRY *
FindBootloaderBootTimeReservationHob (
IN UINTN BootloaderParameter,
OUT UINTN *Count
)
{
EFI_PEI_HOB_POINTERS BlHob;
LOADER_BOOT_TIME_RESERVATION *BootTimeRes;
UINTN DataSize;
*Count = 0;
BlHob.Raw = (UINT8 *)BootloaderParameter;
if ((BlHob.Raw == NULL) ||
(BlHob.Header->HobType != EFI_HOB_TYPE_HANDOFF) ||
(BlHob.Header->HobLength != sizeof (EFI_HOB_HANDOFF_INFO_TABLE)))
{
return NULL;
}
BlHob.Raw = GetNextGuidHob (&gLoaderBootTimeReservationGuid, BlHob.Raw);
if (BlHob.Raw == NULL) {
return NULL;
}
BootTimeRes = (LOADER_BOOT_TIME_RESERVATION *)GET_GUID_HOB_DATA (BlHob.Raw);
DataSize = GET_GUID_HOB_DATA_SIZE (BlHob.Raw);
if ((DataSize < sizeof (LOADER_BOOT_TIME_RESERVATION)) ||
(BootTimeRes->Revision != 1))
{
return NULL;
}
*Count = MIN (
(UINTN)BootTimeRes->Count,
(DataSize - sizeof (LOADER_BOOT_TIME_RESERVATION)) /
sizeof (LOADER_BOOT_TIME_RESERVATION_ENTRY)
);
return BootTimeRes->Entry;
}
/**
Locate the payload FV base and size from the bootloader's ExtraData
HOB, if it published one.
@param[in] BootloaderParameter Bootloader-provided argument.
@param[out] FvBase ExtraData "uefi_fv" entry base.
@param[out] FvSize ExtraData "uefi_fv" entry size.
@retval TRUE The bootloader supplied an ExtraData FV location.
@retval FALSE No usable ExtraData "uefi_fv" entry.
**/
STATIC
BOOLEAN
FindPayloadFvFromBootloader (
IN UINTN BootloaderParameter,
OUT UINTN *FvBase,
OUT UINTN *FvSize
)
{
UNIVERSAL_PAYLOAD_EXTRA_DATA *ExtraData;
UINTN Count;
UINTN Index;
Count = 0;
ExtraData = FindBootloaderExtraDataHob (BootloaderParameter, &Count);
if (ExtraData == NULL) {
return FALSE;
}
for (Index = 0; Index < Count; Index++) {
if (AsciiStrnCmp (
ExtraData->Entry[Index].Identifier,
"uefi_fv",
sizeof (ExtraData->Entry[Index].Identifier)
) == 0)
{
if (ExtraData->Entry[Index].Size == 0) {
DEBUG ((DEBUG_ERROR, "%a: uefi_fv ExtraData entry has zero size\n", __func__));
return FALSE;
}
*FvBase = (UINTN)ExtraData->Entry[Index].Base;
*FvSize = (UINTN)ExtraData->Entry[Index].Size;
return TRUE;
}
}
return FALSE;
}
/**
This function will build the HOBs that every payload needs regardless of
which bootloader it was launched from: the payload FV reservation and the
CPU HOB, plus the Local APIC range on x86.
@param[in] PayloadFvBase Base of the payload FV to reserve.
@param[in] PayloadFvSize Size of the payload FV to reserve.
**/
VOID
BuildGenericHob (
VOID
IN UINTN PayloadFvBase,
IN UINTN PayloadFvSize
)
{
UINT8 PhysicalAddressBits;
UINT8 PhysicalAddressBits;
UINTN Index;
#if defined (MDE_CPU_IA32) || defined (MDE_CPU_X64)
EFI_RESOURCE_ATTRIBUTE_TYPE ResourceAttribute;
#endif
// The UEFI payload FV
BuildMemoryAllocationHob (PcdGet32 (PcdPayloadFdMemBase), PcdGet32 (PcdPayloadFdMemSize), EfiBootServicesData);
BuildMemoryAllocationHob (PayloadFvBase, PayloadFvSize, EfiBootServicesData);
//
// Pin every launcher boot-time reservation with an
// EfiBootServicesData memory-allocation HOB so that DXE never
// allocates over the launcher's HOB list, initial stack or (on
// AArch64) live translation tables. The FV was pinned just above,
// so the entry naming it is skipped. A launcher that emitted no
// gLoaderBootTimeReservationGuid HOB leaves the count at 0.
//
for (Index = 0; Index < mBootTimeReservationCount; Index++) {
if (mBootTimeReservation[Index].Base == PayloadFvBase) {
continue;
}
BuildMemoryAllocationHob (
mBootTimeReservation[Index].Base,
mBootTimeReservation[Index].Size,
EfiBootServicesData
);
DEBUG ((
DEBUG_INFO,
"boot-time reservation: base = 0x%lx, size = 0x%lx\n",
mBootTimeReservation[Index].Base,
mBootTimeReservation[Index].Size
));
}
PhysicalAddressBits = ArchGetPhysicalAddressBits ();
BuildCpuHob (PhysicalAddressBits, 16);
#if defined (MDE_CPU_IA32) || defined (MDE_CPU_X64)
//
// Report Local APIC range, cause sbl HOB to be NULL, comment now
// Report Local APIC range (x86-only)
//
ResourceAttribute = (
EFI_RESOURCE_ATTRIBUTE_PRESENT |
@ -511,6 +867,7 @@ BuildGenericHob (
);
BuildResourceDescriptorHob (EFI_RESOURCE_MEMORY_MAPPED_IO, ResourceAttribute, 0xFEC80000, SIZE_512KB);
BuildMemoryAllocationHob (0xFEC80000, SIZE_512KB, EfiMemoryMappedIO);
#endif
}
/**
@ -535,6 +892,12 @@ _ModuleEntryPoint (
SERIAL_PORT_INFO SerialPortInfo;
UNIVERSAL_PAYLOAD_SERIAL_PORT_INFO *UniversalSerialPort;
EFI_HOB_HANDOFF_INFO_TABLE *HobInfo;
UNIVERSAL_PAYLOAD_EXTRA_DATA *ExtraData;
UNIVERSAL_PAYLOAD_EXTRA_DATA *NewExtraData;
UINTN ExtraDataSize;
UINTN ExtraDataCount;
UINTN PayloadFvBase;
UINTN PayloadFvSize;
Status = PcdSet64S (PcdBootloaderParameter, BootloaderParameter);
ASSERT_EFI_ERROR (Status);
@ -542,8 +905,29 @@ _ModuleEntryPoint (
// Initialize floating point operating environment to be compliant with UEFI spec.
InitializeFloatingPointUnits ();
//
// Determine the payload FV location. If the bootloader relocated the
// FV and published an ExtraData HOB, honour that; otherwise fall back
// to the build-time PCD.
//
if (!FindPayloadFvFromBootloader (BootloaderParameter, &PayloadFvBase, &PayloadFvSize)) {
PayloadFvBase = PcdGet32 (PcdPayloadFdMemBase);
PayloadFvSize = PcdGet32 (PcdPayloadFdMemSize);
}
//
// If the launcher published boot-time reservations, cache them so
// FindFreeMemForHobCallback() below excludes those ranges from the
// HOB-memory search. A launcher that did not (Slim Bootloader,
// coreboot) leaves the count at 0 and nothing changes.
//
mBootTimeReservation = FindBootloaderBootTimeReservationHob (
BootloaderParameter,
&mBootTimeReservationCount
);
// HOB region is used for HOB and memory allocation for this module
MemBase = PcdGet32 (PcdPayloadFdMemBase);
MemBase = PayloadFvBase;
HobMemBase = 0;
//
@ -553,7 +937,7 @@ _ModuleEntryPoint (
ASSERT (HobMemBase != 0);
if (HobMemBase == 0) {
HobMemBase = ALIGN_VALUE (MemBase + PcdGet32 (PcdPayloadFdMemSize), SIZE_1MB);
HobMemBase = ALIGN_VALUE (MemBase + PayloadFvSize, SIZE_1MB);
}
HobMemTop = HobMemBase + FixedPcdGet32 (PcdSystemMemoryUefiRegionSize);
@ -596,8 +980,39 @@ _ModuleEntryPoint (
return Status;
}
//
// Republish the ExtraData HOB in the new HOB list so that DXE-phase
// consumers can locate the relocated payload FV. LoadDxeCore() does
// not read it: the FV was resolved once, above.
//
ExtraDataCount = 0;
ExtraData = FindBootloaderExtraDataHob (BootloaderParameter, &ExtraDataCount);
if (ExtraData != NULL) {
ExtraDataSize = sizeof (UNIVERSAL_PAYLOAD_EXTRA_DATA) +
ExtraDataCount * sizeof (UNIVERSAL_PAYLOAD_EXTRA_DATA_ENTRY);
NewExtraData = BuildGuidHob (&gUniversalPayloadExtraDataGuid, ExtraDataSize);
ASSERT (NewExtraData != NULL);
if (NewExtraData == NULL) {
//
// Not fatal: the FV was already resolved and reserved. Only
// DXE-phase consumers of the HOB lose out, so say so and go on.
//
DEBUG ((
DEBUG_ERROR,
"%a: failed to build ExtraData HOB of 0x%x bytes\n",
__func__,
(UINT32)ExtraDataSize
));
} else {
CopyMem (NewExtraData, ExtraData, ExtraDataSize);
NewExtraData->Count = (UINT32)ExtraDataCount;
NewExtraData->Header.Length = (UINT16)ExtraDataSize;
DEBUG ((DEBUG_INFO, "Copied ExtraData HOB with %u entries\n", (UINT32)ExtraDataCount));
}
}
// Build other HOBs required by DXE
BuildGenericHob ();
BuildGenericHob (PayloadFvBase, PayloadFvSize);
//
// Create Memory Type Information HOB
@ -609,7 +1024,7 @@ _ModuleEntryPoint (
);
// Load the DXE Core
Status = LoadDxeCore (&DxeCoreEntryPoint);
Status = LoadDxeCore ((EFI_FIRMWARE_VOLUME_HEADER *)PayloadFvBase, &DxeCoreEntryPoint);
ASSERT_EFI_ERROR (Status);
DEBUG ((DEBUG_INFO, "DxeCoreEntryPoint = 0x%lx\n", DxeCoreEntryPoint));
@ -621,11 +1036,13 @@ _ModuleEntryPoint (
HobInfo->BootMode = BOOT_ON_FLASH_UPDATE;
}
#if defined (MDE_CPU_IA32) || defined (MDE_CPU_X64)
//
// Mask off all legacy 8259 interrupt sources
//
IoWrite8 (LEGACY_8259_MASK_REGISTER_MASTER, 0xFF);
IoWrite8 (LEGACY_8259_MASK_REGISTER_SLAVE, 0xFF);
#endif
Hob.HandoffInformationTable = (EFI_HOB_HANDOFF_INFO_TABLE *)GetFirstHob (EFI_HOB_TYPE_HANDOFF);
HandOffToDxeCore (DxeCoreEntryPoint, Hob);

View file

@ -10,6 +10,7 @@
#include <PiPei.h>
#include <Library/AcpiTableWalkLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
@ -110,8 +111,9 @@ HobConstructor (
);
/**
Find DXE core from FV and build DXE core HOBs.
Find DXE core from the payload FV and build DXE core HOBs.
@param[in] PayloadFv The payload FV that contains the DXE FV.
@param[out] DxeCoreEntryPoint DXE core entry point
@retval EFI_SUCCESS If it completed successfully.
@ -119,7 +121,8 @@ HobConstructor (
**/
EFI_STATUS
LoadDxeCore (
OUT PHYSICAL_ADDRESS *DxeCoreEntryPoint
IN EFI_FIRMWARE_VOLUME_HEADER *PayloadFv,
OUT PHYSICAL_ADDRESS *DxeCoreEntryPoint
);
/**

View file

@ -47,6 +47,7 @@
UefiPayloadPkg/UefiPayloadPkg.dec
[LibraryClasses]
AcpiTableWalkLib
BaseMemoryLib
DebugLib
BaseLib
@ -61,6 +62,10 @@
UefiCpuBaseArchSupportLib
MemoryAllocationLib
[LibraryClasses.AARCH64]
ArmMmuLib
ArmLib
[Guids]
gEfiMemoryTypeInformationGuid
gEfiFirmwareFileSystem2Guid
@ -70,16 +75,20 @@
gUniversalPayloadSmbiosTableGuid
gUniversalPayloadAcpiTableGuid
gUniversalPayloadSerialPortInfoGuid
gUniversalPayloadExtraDataGuid
gLoaderBootTimeReservationGuid
gEfiFirmwareInfoHobGuid
gEfiSmmStoreInfoHobGuid
[FeaturePcd]
gUefiPayloadPkgTokenSpaceGuid.PcdPublishMcfgAsReservedMemory ## CONSUMES
[FeaturePcd.IA32]
gEfiMdeModulePkgTokenSpaceGuid.PcdDxeIplSwitchToLongMode ## CONSUMES
[FeaturePcd.X64]
gEfiMdeModulePkgTokenSpaceGuid.PcdDxeIplBuildPageTables ## CONSUMES
[Pcd.IA32,Pcd.X64,Pcd.AARCH64]
gEfiMdeModulePkgTokenSpaceGuid.PcdUse1GPageTable ## SOMETIMES_CONSUMES
gEfiMdeModulePkgTokenSpaceGuid.PcdPteMemoryEncryptionAddressOrMask ## CONSUMES

View file

@ -39,6 +39,7 @@
UefiCpuPkg/UefiCpuPkg.dec
UefiPayloadPkg/UefiPayloadPkg.dec
[LibraryClasses]
AcpiTableWalkLib
BaseMemoryLib
DebugLib
BaseLib
@ -52,6 +53,10 @@
UefiCpuBaseArchSupportLib
MemoryAllocationLib
[LibraryClasses.AARCH64]
ArmMmuLib
ArmLib
[Guids]
gEfiMemoryTypeInformationGuid
gEfiFirmwareFileSystem2Guid

View file

@ -21,6 +21,7 @@
],
## Both file path and directory path are accepted.
"IgnoreFiles": [
"ChainloadApp/EmbeddedPayloadStub.h",
"Tools/",
"Include/Coreboot.h",
"Library/CbParseLib/CbParseLib.c",
@ -85,7 +86,12 @@
"SpellCheck": {
"AuditOnly": True, # Fails right now with over 270 errors
"IgnoreFiles": [], # use gitignore syntax to ignore errors in matching files
"ExtendWords": [], # words to extend to the dictionary for this package
"ExtendWords": [ # words to extend to the dictionary for this package
"lapic",
"mcopy",
"mmconfig",
"tolud"
],
"IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignore
"AdditionalIncludePaths": [] # Additional paths to spell check (wildcards supported)
},

View file

@ -18,6 +18,8 @@
Include
[LibraryClasses]
## @libraryclass Locate ACPI tables by signature via a bootloader-supplied RSDP.
AcpiTableWalkLib|Include/Library/AcpiTableWalkLib.h
PayloadEntryHelperLib|Include/Library/PayloadEntryHelperLib.h
[Guids]
@ -38,6 +40,7 @@
gUefiAcpiBoardInfoGuid = {0xad3d31b, 0xb3d8, 0x4506, {0xae, 0x71, 0x2e, 0xf1, 0x10, 0x6, 0xd9, 0xf}}
gUefiSerialPortInfoGuid = { 0x6c6872fe, 0x56a9, 0x4403, { 0xbb, 0x98, 0x95, 0x8d, 0x62, 0xde, 0x87, 0xf1 } }
gLoaderMemoryMapInfoGuid = { 0xa1ff7424, 0x7a1a, 0x478e, { 0xa9, 0xe4, 0x92, 0xf3, 0x57, 0xd1, 0x28, 0x32 } }
gLoaderBootTimeReservationGuid = { 0x058e371c, 0x3486, 0x46b1, { 0xac, 0xf6, 0x2a, 0x3f, 0x26, 0x9c, 0xe2, 0x69 } }
gEdkiiPayloadCommandLineGuid = {0xb5aeb34f, 0x3047, 0x4955, {0xb8, 0x80, 0xad, 0xd3, 0x6d, 0x86, 0xdc, 0x0f}}
# SMM variable support
@ -116,3 +119,23 @@ gUefiPayloadPkgTokenSpaceGuid.PcdUseUniversalPayloadSerialPort|TRUE|BOOLEAN|0x00
## Indicates whether allows PCI Root Bridge to allocate DMA memory resource above 4G
gUefiPayloadPkgTokenSpaceGuid.PcdPciAllocateMemoryAbove4GB|FALSE|BOOLEAN|0x0000002E
[PcdsFeatureFlag]
## Publish the MCFG ECAM window as EFI_RESOURCE_MEMORY_RESERVED rather
# than EFI_RESOURCE_MEMORY_MAPPED_IO, so that it appears in the UEFI
# memory map as a Reserved entry.
#
# Linux only uses MMCONFIG for extended PCIe configuration space if the
# window passes its is_mmconf_reserved() check, which requires the range
# to be covered by a Reserved entry in the firmware-provided memory map.
# A plain MMIO resource does not qualify: CoreGetMemoryMap() reports an
# MMIO range only when it also carries EFI_MEMORY_RUNTIME, which the ECAM
# window does not. The window is still device memory, so it is published
# advertising UNCACHEABLE only, and a consumer that builds page tables
# from these HOBs must not map configuration space write-back.
#
# Declared as a feature flag rather than alongside the package's other
# boolean knobs in [PcdsFixedAtBuild, PcdsPatchableInModule] so that the
# publication code is eliminated outright on platforms that leave it off.
# @Prompt Publish the MCFG ECAM window as Reserved memory.
gUefiPayloadPkgTokenSpaceGuid.PcdPublishMcfgAsReservedMemory|FALSE|BOOLEAN|0x0000002F

View file

@ -45,6 +45,7 @@
DEFINE USE_CBMEM_FOR_CONSOLE = FALSE
DEFINE BOOTSPLASH_IMAGE = FALSE
DEFINE NVME_ENABLE = TRUE
DEFINE VIRTIO_ENABLE = FALSE
DEFINE LOCKBOX_SUPPORT = FALSE
DEFINE LOAD_OPTION_ROMS = FALSE
@ -72,6 +73,17 @@
DEFINE UNIVERSAL_PAYLOAD = FALSE
DEFINE UNIVERSAL_PAYLOAD_FORMAT = ELF
#
# Enable defaults suited to the ChainloadApp frontend:
# MCFG published as reserved memory, endpoint PCI BARs left at zero
# by the outer firmware programmed before PciBusDxe runs, full
# INIT-SIPI-SIPI for the first AP wakeup, a larger UEFI region, and
# on AArch64 the HOB-driven SerialPortLib plus MADT-derived GIC
# bases. Left FALSE so that coreboot/Slim Bootloader users are
# unaffected.
#
DEFINE CHAINLOAD_DEFAULTS = FALSE
#
# NULL: NullMemoryTestDxe
# GENERIC: GenericMemoryTestDxe
@ -192,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
@ -407,6 +421,7 @@
BmpSupportLib|MdeModulePkg/Library/BaseBmpSupportLib/BaseBmpSupportLib.inf
!endif
UefiCpuBaseArchSupportLib|UefiCpuPkg/Library/BaseArchSupportLib/BaseArchSupportLib.inf
AcpiTableWalkLib|UefiPayloadPkg/Library/AcpiTableWalkLib/AcpiTableWalkLib.inf
[LibraryClasses.X64]
#
@ -424,7 +439,27 @@
CpuExceptionHandlerLib|UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeCpuExceptionHandlerLib.inf
CpuPageTableLib|UefiCpuPkg/Library/CpuPageTableLib/CpuPageTableLib.inf
!if $(VIRTIO_ENABLE) == TRUE
#
# Virtio driver dependencies
#
PciCapLib|OvmfPkg/Library/BasePciCapLib/BasePciCapLib.inf
PciCapPciIoLib|OvmfPkg/Library/UefiPciCapPciIoLib/UefiPciCapPciIoLib.inf
OrderedCollectionLib|MdePkg/Library/BaseOrderedCollectionRedBlackTreeLib/BaseOrderedCollectionRedBlackTreeLib.inf
VirtioLib|OvmfPkg/Library/VirtioLib/VirtioLib.inf
!endif
[LibraryClasses.AARCH64]
#
# BaseIoLibIntrinsic.inf uses plain C volatile pointer dereferences for
# MmioRead*/MmioWrite*, which the compiler may fuse with adjacent pointer
# arithmetic into post-indexed AArch64 loads/stores. Post-indexed accesses
# produce a stage-2 data abort with ESR_EL2.ISV=0, so KVM cannot decode the
# access as MMIO and returns KVM_EXIT_ARM_NISV instead of KVM_EXIT_MMIO.
# Use the ArmVirt variant, which emits plain register-offset accesses in
# hand-written assembly, matching what ArmVirtPkg does.
#
IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsicArmVirt.inf
ArmHvcLib|ArmPkg/Library/ArmHvcLib/ArmHvcLib.inf
ArmLib|MdePkg/Library/ArmLib/ArmBaseLib.inf
ArmMmuLib|UefiCpuPkg/Library/ArmMmuLib/ArmMmuBaseLib.inf
@ -436,7 +471,19 @@
ResetSystemLib|ArmPkg/Library/ArmPsciResetSystemLib/ArmPsciResetSystemLib.inf
PL011UartLib|ArmPlatformPkg/Library/PL011UartLib/PL011UartLib.inf
PL011UartClockLib|ArmPlatformPkg/Library/PL011UartClockLib/PL011UartClockLib.inf
!if $(CHAINLOAD_DEFAULTS) == TRUE
#
# ChainloadApp emits a gUniversalPayloadSerialPortInfoGuid HOB from
# the ACPI SPCR, so use the HOB-driven 16550 SerialPortLib. The
# PL011 instance below is fixed to PcdSerialRegisterBase, which is
# only correct on QEMU virt. DXE-phase modules use the DxeHobLib-
# backed instance; SEC (below) uses the PayloadEntryHobLib-backed
# one.
#
SerialPortLib|UefiPayloadPkg/Library/BaseSerialPortLibHob/DxeBaseSerialPortLibHob.inf
!else
SerialPortLib|ArmPlatformPkg/Library/PL011SerialPortLib/PL011SerialPortLib.inf
!endif
QemuFwCfgLib|OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgMmioDxeLib.inf
QemuFwCfgS3Lib|OvmfPkg/Library/QemuFwCfgS3Lib/BaseQemuFwCfgS3LibNull.inf
@ -474,6 +521,11 @@
VirtioMmioDeviceLib|OvmfPkg/Library/VirtioMmioDeviceLib/VirtioMmioDeviceLib.inf
VirtioLib|OvmfPkg/Library/VirtioLib/VirtioLib.inf
[LibraryClasses.AARCH64.SEC]
!if $(CHAINLOAD_DEFAULTS) == TRUE
SerialPortLib|UefiPayloadPkg/Library/BaseSerialPortLibHob/BaseSerialPortLibHob.inf
!endif
[LibraryClasses.common.SEC]
HobLib|UefiPayloadPkg/Library/PayloadEntryHobLib/HobLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
@ -593,6 +645,10 @@
## Whether capsules are allowed to persist across reset.
gEfiMdeModulePkgTokenSpaceGuid.PcdSupportUpdateCapsuleReset|$(CAPSULE_SUPPORT)
!if $(CHAINLOAD_DEFAULTS) == TRUE
gUefiPayloadPkgTokenSpaceGuid.PcdPublishMcfgAsReservedMemory|TRUE
!endif
[PcdsFeatureFlag.X64]
gEfiMdeModulePkgTokenSpaceGuid.PcdDxeIplSwitchToLongMode|TRUE
gUefiCpuPkgTokenSpaceGuid.PcdCpuSmmEnableBspElection|FALSE
@ -668,6 +724,14 @@
## Whether allows PCI RB to allocate DMA memory above 4GB
gUefiPayloadPkgTokenSpaceGuid.PcdPciAllocateMemoryAbove4GB|FALSE
!if $(CHAINLOAD_DEFAULTS) == TRUE
# Note: gArmPlatformTokenSpaceGuid.PcdSystemMemoryUefiRegionSize is a
# different PCD with the same name; UefiPayloadEntry consumes only the
# gUefiPayloadPkgTokenSpaceGuid one below.
gUefiPayloadPkgTokenSpaceGuid.PcdSystemMemoryUefiRegionSize|0x10000000
# Only consumed by UefiCpuPkg MpInitLib on IA32/X64; harmless on AArch64.
gUefiCpuPkgTokenSpaceGuid.PcdFirstTimeWakeUpAPsBySipi|FALSE
!endif
[PcdsFixedAtBuild.AARCH64]
# System Memory Base -- fixed at 0x4000_0000
@ -680,9 +744,11 @@
gArmPlatformTokenSpaceGuid.PcdSystemMemoryUefiRegionSize|0x04000000
# ARM General Interrupt Controller
!if $(CHAINLOAD_DEFAULTS) == FALSE
gArmTokenSpaceGuid.PcdGicDistributorBase|0x8000000
gArmTokenSpaceGuid.PcdGicRedistributorsBase|0x80a0000
gArmTokenSpaceGuid.PcdGicInterruptInterfaceBase|0x8080000
!endif
# Enable NX memory protection for all non-code regions, including OEM and OS
# reserved ones, with the exception of LoaderData regions, of which OS loaders
@ -851,6 +917,19 @@
[PcdsDynamicExDefault.AARCH64]
!if $(CHAINLOAD_DEFAULTS) == TRUE
#
# ChainloadApp hands over ACPI tables from the outer firmware.
# AcpiGicPcdLib parses the MADT for the GICD/GICR/GICC bases and
# overrides these before ArmGicDxe reads them. QEMU-virt
# defaults are kept as the fallback for a bootloader that does
# not supply a MADT.
#
gArmTokenSpaceGuid.PcdGicDistributorBase|0x8000000
gArmTokenSpaceGuid.PcdGicRedistributorsBase|0x80a0000
gArmTokenSpaceGuid.PcdGicInterruptInterfaceBase|0x8080000
!endif
gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwSpareBase | 0
gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwSpareBase64 | 0
gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageVariableBase64 | 0
@ -861,8 +940,8 @@
# Timer IRQs
gArmTokenSpaceGuid.PcdArmArchTimerSecIntrNum|29
gArmTokenSpaceGuid.PcdArmArchTimerIntrNum|30
# Not used in QEMU platform
gArmTokenSpaceGuid.PcdArmArchTimerVirtIntrNum|0
# QEMU virt: EL1 virtual timer PPI 27
gArmTokenSpaceGuid.PcdArmArchTimerVirtIntrNum|27
gArmTokenSpaceGuid.PcdArmArchTimerHypIntrNum|26
gArmTokenSpaceGuid.PcdArmArchTimerHypVirtIntrNum|0x0
@ -1216,6 +1295,17 @@
!error "Invalid TIMER_SUPPORT"
!endif
!if $(VIRTIO_ENABLE) == TRUE
#
# Virtio bus and class drivers
#
OvmfPkg/Virtio10Dxe/Virtio10.inf
OvmfPkg/VirtioPciDeviceDxe/VirtioPciDeviceDxe.inf
OvmfPkg/VirtioBlkDxe/VirtioBlk.inf
OvmfPkg/VirtioScsiDxe/VirtioScsi.inf
OvmfPkg/VirtioNetDxe/VirtioNet.inf
!endif
[Components.AARCH64]
ArmPkg/Drivers/ArmPciCpuIo2Dxe/ArmPciCpuIo2Dxe.inf
ArmPkg/Drivers/CpuDxe/CpuDxe.inf
@ -1224,7 +1314,14 @@
EmbeddedPkg/RealTimeClockRuntimeDxe/RealTimeClockRuntimeDxe.inf
EmbeddedPkg/MetronomeDxe/MetronomeDxe.inf
!if $(CHAINLOAD_DEFAULTS) == TRUE
ArmPkg/Drivers/ArmGicDxe/ArmGicDxe.inf {
<LibraryClasses>
NULL|UefiPayloadPkg/Library/AcpiGicPcdLib/AcpiGicPcdLib.inf
}
!else
ArmPkg/Drivers/ArmGicDxe/ArmGicDxe.inf
!endif
ArmPkg/Drivers/TimerDxe/TimerDxe.inf
OvmfPkg/VirtNorFlashDxe/VirtNorFlashDxe.inf {
<LibraryClasses>
@ -1339,3 +1436,30 @@
}
!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
#
# ArmMmuLib allocates translation-table pages via
# MemoryAllocationLib::AllocatePages(). Redirect those to
# EfiReservedMemoryType so they stay isolated in the outer
# memory-map snapshot and cannot be selected as HOB memory,
# and record each one so ChainloadApp can hand the payload an
# explicit boot-time reservation list to publish as
# EfiBootServicesData.
#
MemoryAllocationLib|UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.inf
}

View file

@ -13,7 +13,16 @@
DEFINE FD_BASE = 0x00800000
DEFINE FD_BLOCK_SIZE = 0x00001000
!if "AARCH64" IN "$(ARCH)"
# ArmMmuLib and friends add ~2 MiB over the X64 build.
!if $(TARGET) == "NOOPT"
DEFINE FD_SIZE = 0x00950000
DEFINE NUM_BLOCKS = 0x950
!else
DEFINE FD_SIZE = 0x00900000
DEFINE NUM_BLOCKS = 0x900
!endif
!elseif $(TARGET) == "NOOPT"
DEFINE FD_SIZE = 0x00850000
DEFINE NUM_BLOCKS = 0x850
!else
@ -170,6 +179,19 @@ INF MdeModulePkg/Universal/StatusCodeHandler/RuntimeDxe/StatusCodeHandlerRuntime
!elseif $(TIMER_SUPPORT) == "LAPIC"
INF OvmfPkg/LocalApicTimerDxe/LocalApicTimerDxe.inf
!endif
!if $(VIRTIO_ENABLE) == TRUE
#
# Virtio: modern + legacy PCI transport, blk / scsi / net class
# drivers. Enables boot from a virtio-blk root disk on X64
# payloads chainloaded inside a virtual machine (e.g. QEMU) that
# exposes a virtio-pci endpoint.
#
INF OvmfPkg/Virtio10Dxe/Virtio10.inf
INF OvmfPkg/VirtioPciDeviceDxe/VirtioPciDeviceDxe.inf
INF OvmfPkg/VirtioBlkDxe/VirtioBlk.inf
INF OvmfPkg/VirtioScsiDxe/VirtioScsi.inf
INF OvmfPkg/VirtioNetDxe/VirtioNet.inf
!endif
!elseif "AARCH64" IN "$(ARCH)"
INF ArmPkg/Drivers/CpuDxe/CpuDxe.inf
INF ArmPkg/Drivers/ArmGicDxe/ArmGicDxe.inf