mirror of
https://github.com/tianocore/edk2
synced 2026-08-27 00:23:19 -04:00
Merge 0c9a1f1488 into fb2ac429de
This commit is contained in:
commit
057ae7a3c3
36 changed files with 6300 additions and 134 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
125
UefiPayloadPkg/BuildChainloadEmbedded.sh
Executable file
125
UefiPayloadPkg/BuildChainloadEmbedded.sh
Executable 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
|
||||
32
UefiPayloadPkg/ChainloadApp/AArch64/PayloadEntry.S
Normal file
32
UefiPayloadPkg/ChainloadApp/AArch64/PayloadEntry.S
Normal 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
|
||||
2288
UefiPayloadPkg/ChainloadApp/ChainloadApp.c
Normal file
2288
UefiPayloadPkg/ChainloadApp/ChainloadApp.c
Normal file
File diff suppressed because it is too large
Load diff
77
UefiPayloadPkg/ChainloadApp/ChainloadApp.inf
Normal file
77
UefiPayloadPkg/ChainloadApp/ChainloadApp.inf
Normal 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
|
||||
17
UefiPayloadPkg/ChainloadApp/EmbeddedPayloadStub.h
Normal file
17
UefiPayloadPkg/ChainloadApp/EmbeddedPayloadStub.h
Normal 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;
|
||||
61
UefiPayloadPkg/ChainloadApp/GenPayloadHdr.py
Normal file
61
UefiPayloadPkg/ChainloadApp/GenPayloadHdr.py
Normal 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())
|
||||
844
UefiPayloadPkg/ChainloadApp/PciBarFixup.c
Normal file
844
UefiPayloadPkg/ChainloadApp/PciBarFixup.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
116
UefiPayloadPkg/ChainloadApp/README.md
Normal file
116
UefiPayloadPkg/ChainloadApp/README.md
Normal 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
|
||||
395
UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.c
Normal file
395
UefiPayloadPkg/ChainloadApp/ReservedUefiMemoryAllocationLib.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
37
UefiPayloadPkg/ChainloadApp/X64/PayloadEntry.nasm
Normal file
37
UefiPayloadPkg/ChainloadApp/X64/PayloadEntry.nasm
Normal 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
|
||||
50
UefiPayloadPkg/Include/Guid/BootTimeReservationGuid.h
Normal file
50
UefiPayloadPkg/Include/Guid/BootTimeReservationGuid.h
Normal 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()
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
40
UefiPayloadPkg/Include/Library/AcpiTableWalkLib.h
Normal file
40
UefiPayloadPkg/Include/Library/AcpiTableWalkLib.h
Normal 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
|
||||
);
|
||||
664
UefiPayloadPkg/Library/AcpiGicPcdLib/AcpiGicPcdLib.c
Normal file
664
UefiPayloadPkg/Library/AcpiGicPcdLib/AcpiGicPcdLib.c
Normal 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;
|
||||
}
|
||||
50
UefiPayloadPkg/Library/AcpiGicPcdLib/AcpiGicPcdLib.inf
Normal file
50
UefiPayloadPkg/Library/AcpiGicPcdLib/AcpiGicPcdLib.inf
Normal 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
|
||||
169
UefiPayloadPkg/Library/AcpiTableWalkLib/AcpiTableWalkLib.c
Normal file
169
UefiPayloadPkg/Library/AcpiTableWalkLib/AcpiTableWalkLib.c
Normal 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;
|
||||
}
|
||||
29
UefiPayloadPkg/Library/AcpiTableWalkLib/AcpiTableWalkLib.inf
Normal file
29
UefiPayloadPkg/Library/AcpiTableWalkLib/AcpiTableWalkLib.inf
Normal 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
UefiPayloadPkg/UefiPayloadPkg.dec
|
||||
|
||||
[LibraryClasses]
|
||||
AcpiTableWalkLib
|
||||
BaseMemoryLib
|
||||
DebugLib
|
||||
BaseLib
|
||||
|
|
|
|||
|
|
@ -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
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue