The AArch64 build has refused to hand off with EFI_UNSUPPORTED since
the launcher was added, because entering the payload needs translation
tables the launcher owns. Enter the payload with the MMU and caches
enabled, on tables ChainloadApp built, and drop that refusal. Two
alternatives were rejected:

  1) Keeping the outer firmware's tables live. They are
     EfiBootServicesData, which the payload sees as free RAM once
     ExitBootServices() has been called, so DXE could allocate over the
     live hierarchy, and they typically map the payload FV as XN under
     PcdDxeNxMemoryProtectionPolicy, so branching into it faults.

  2) Disabling the MMU and caches at the branch. The payload then
     writes DRAM with the D-cache off at addresses the outer firmware
     may have left dirty; no bounded set of by-VA maintenance covers
     those lines and they can clobber the payload's writes on
     eviction.

While boot services are still available, BuildOwnPageTables() derives an
ARM_MEMORY_REGION_DESCRIPTOR list from the outer firmware's GCD
memory-space map, a non-overlapping partition of the whole physical
address space, and calls ArmConfigureMmu() to build a fresh hierarchy.
System memory and Reserved are mapped WRITE_BACK, which carries no XN,
so the FV is executable in the new translation; MMIO is mapped Device.

ArmConfigureMmu() writes TCR before it allocates the root, and we call
it with the outer translation live, so between the TCR write and the
TTBR0 swap the outer TTBR0 is interpreted under the new TCR and
gBS->AllocatePages() runs under it for every intermediate table page.
BuildOwnPageTables() therefore verifies before touching any register
that neither TCR nor MAIR will change, duplicating ArmConfigureMmu()'s
TCR construction to do so, and refuses to launch on any difference. An
ArmMmuLib helper that returned TCR and MAIR instead of writing them
would remove both the duplication and that window.

Once ArmConfigureMmu() returns, the outer TCR/MAIR/TTBR0 are put back so
boot-services code keeps running on the outer translation and the new
tables do not have to cover every region the outer firmware might touch.
ArmGetTTBR0BaseAddress() masks off CnP, ASID and (on LPA2) OA[51:48], so
we open-code the mrs and write back exactly what we read. The new TCR,
MAIR and root reach the CPU only after ExitBootServices(), followed by
ArmInvalidateTlb().

ArmMmuLib allocates every table page through MemoryAllocationLib, so
resolving it here brings the per-module override added earlier in this
series into play: the pages are EfiReservedMemoryType in the outer
snapshot, DXE cannot allocate over the live hierarchy, and the
launcher's boot-time reservation list hands them back to the OS.
ArmMmuLib lives in UefiCpuPkg, so the module gains a [Packages.AARCH64]
section for UefiCpuPkg.dec.

There is no cache-off window, so no by-VA data-cache maintenance is
done. The FV is invalidated from the instruction cache all the same: it
was written through the D-cache and is about to be executed, and ARMv8-A
does not require the I-cache to be coherent with the D-cache.

Cc: Benjamin Doron <[email protected]>
Cc: Gua Guo <[email protected]>
Cc: Guo Dong <[email protected]>
Cc: James Lu <[email protected]>
Cc: Sean Rhodes <[email protected]>
Cc: Shuo Liu <[email protected]>
Cc: Ard Biesheuvel <[email protected]>
Cc: Leif Lindholm <[email protected]>
Cc: Sami Mujawar <[email protected]>
Cc: Vishal Oliyil Kunnnil <[email protected]>
Assisted-by: claude-opus-5
Signed-off-by: Alexander Graf <[email protected]>
---
 UefiPayloadPkg/ChainloadApp/ChainloadApp.c   | 479 ++++++++++++++++++-
 UefiPayloadPkg/ChainloadApp/ChainloadApp.inf |   4 +
 2 files changed, 474 insertions(+), 9 deletions(-)

diff --git a/UefiPayloadPkg/ChainloadApp/ChainloadApp.c 
b/UefiPayloadPkg/ChainloadApp/ChainloadApp.c
index 6373ca531e..1c04de3f2c 100644
--- a/UefiPayloadPkg/ChainloadApp/ChainloadApp.c
+++ b/UefiPayloadPkg/ChainloadApp/ChainloadApp.c
@@ -6,6 +6,27 @@
   map, ACPI, SMBIOS and PCIe ECAM information, calls ExitBootServices(),

   and transfers control to the payload entry point on a fresh stack.

 

+  On AArch64 the MMU and caches stay enabled across the handoff, but on

+  this application's own translation tables rather than the outer

+  firmware's.  The outer tables cannot be carried over: they are

+  EfiBootServicesData, which the payload reports as free RAM once

+  ExitBootServices() has been called, so DXE could allocate over the live

+  hierarchy; and they typically map the payload FV as XN under

+  PcdDxeNxMemoryProtectionPolicy, so branching into it would fault.

+  BuildOwnPageTables() therefore derives a mapping from the outer GCD

+  memory space map into EfiReservedMemoryType pages while boot services

+  are still available, and TCR/MAIR/TTBR0 are pointed at it after

+  ExitBootServices() returns.  The table pages are handed to the

+  payload as boot-time reservations so it publishes them as

+  EfiBootServicesData: a normal ArmMmuLib-based boot allocates its

+  translation tables that way, and the OS reclaims them once it has

+  installed its own translation.  Because there is no cache-off window at

+  any point, no data-cache maintenance is needed; only the instruction

+  cache is invalidated over the FV, for I/D coherency with the payload

+  image this application just wrote through the data cache.  The payload

+  finds the MMU already enabled and adopts the live translation instead

+  of building tables from its resource-descriptor HOBs.

+

   Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All Rights 
Reserved.<BR>

   SPDX-License-Identifier: BSD-2-Clause-Patent

 **/

@@ -59,6 +80,8 @@ FixupUnassignedBars (
 

 #if defined (MDE_CPU_AARCH64)

   #include <Library/ArmLib.h>

+  #include <Library/ArmMmuLib.h>

+  #include <AArch64/AArch64.h>

 #endif

 

 //

@@ -84,6 +107,40 @@ FixupUnassignedBars (
 #define PAYLOAD_STACK_PAGES  4

 #define PAYLOAD_STACK_SIZE   (EFI_PAGES_TO_SIZE (PAYLOAD_STACK_PAGES))

 

+#if defined (MDE_CPU_AARCH64)

+//

+// The MAIR value ArmConfigureMmu() writes.  Compared against the outer

+// firmware's MAIR before the switch, so a mismatch is refused rather

+// than acted on.  See BuildOwnPageTables().

+//

+#define CHAINLOAD_ARM_MMU_LIB_MAIR                                             
          \

+  (MAIR_ATTR (TT_ATTR_INDX_DEVICE_MEMORY, MAIR_ATTR_DEVICE_MEMORY)             
         | \

+   MAIR_ATTR (TT_ATTR_INDX_MEMORY_NON_CACHEABLE, 
MAIR_ATTR_NORMAL_MEMORY_NON_CACHEABLE) | \

+   MAIR_ATTR (TT_ATTR_INDX_MEMORY_WRITE_THROUGH, 
MAIR_ATTR_NORMAL_MEMORY_WRITE_THROUGH) | \

+   MAIR_ATTR (TT_ATTR_INDX_MEMORY_WRITE_BACK, 
MAIR_ATTR_NORMAL_MEMORY_WRITE_BACK))

+

+//

+// Root translation table returned by ArmConfigureMmu(), and the

+// TCR/MAIR values it wrote, held here between BuildOwnPageTables()

+// and the post-ExitBootServices() install.  BuildPayloadHobList()

+// asserts the memory-map snapshot reports the root as Reserved,

+// alongside the FV, HOB list and payload stack.

+//

+STATIC VOID   *mTranslationTableRoot;

+STATIC UINTN  mTranslationTableRootSize;

+STATIC UINTN  mOwnTcr;

+STATIC UINTN  mOwnMair;

+

+//

+// Reserved page allocations tracked by ReservedUefiMemoryAllocationLib

+// (the AArch64 translation-table pages).  ChainloadApp reads them

+// after BuildOwnPageTables().

+//

+extern EFI_PHYSICAL_ADDRESS  gReservedPageAllocBase[];

+extern UINTN                 gReservedPageAllocPages[];

+extern UINTN                 gReservedPageAllocCount;

+#endif

+

 //

 // Boot-time-only reservations: the FV copy, the HOB list buffer, the

 // payload's initial stack and, on AArch64, every translation-table

@@ -1013,6 +1070,16 @@ BuildPayloadHobList (
            StackBase,

            StackSize

            )

+ #if defined (MDE_CPU_AARCH64)

+     || ((mTranslationTableRoot != NULL) &&

+         !IsReservedInMemoryMap (

+            MemoryMap,

+            MemoryMapSize,

+            DescriptorSize,

+            (EFI_PHYSICAL_ADDRESS)(UINTN)mTranslationTableRoot,

+            ALIGN_VALUE (mTranslationTableRootSize, EFI_PAGE_SIZE)

+            ))

+ #endif

         )

   {

     ASSERT (FALSE);

@@ -1403,6 +1470,338 @@ FindPeInFv (
   return NULL;

 }

 

+#if defined (MDE_CPU_AARCH64)

+

+/**

+  Read the raw TTBR0_ELx value at the current EL.

+

+  ArmLib's ArmGetTTBR0BaseAddress() masks off bits [63:48] and bit 0

+  (CnP), which loses ASID, CnP and, on LPA2, output-address bits

+  [51:48] carried in TTBR0[5:2].  Restoring the outer translation

+  needs the exact register value, not the base address, so open-code

+  the read here.  Adding a raw getter to ArmLib would be the cleaner

+  fix, and belongs in ArmPkg rather than here.

+

+  @return  Raw TTBR0_EL1 or TTBR0_EL2, whichever is current.

+**/

+STATIC

+UINTN

+ChainloadReadRawTtbr0 (

+  VOID

+  )

+{

+  UINTN  Ttbr0;

+

+  if (ArmReadCurrentEL () == AARCH64_EL2) {

+    __asm__ __volatile__ ("mrs %0, ttbr0_el2" : "=r" (Ttbr0));

+  } else {

+    __asm__ __volatile__ ("mrs %0, ttbr0_el1" : "=r" (Ttbr0));

+  }

+

+  return Ttbr0;

+}

+

+/**

+  Compute the TCR value ArmConfigureMmu() will write.

+

+  This deliberately duplicates the TCR construction in

+  UefiCpuPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c

+  ArmConfigureMmu(): T0SZ from ArmGetPhysicalAddressBits() and

+  ArmHas52BitTgran4(), TG0 4 KiB, PS/IPS and DS from PARange, EPD1/TG1

+  (dual regime) or the RES1 bits [31] and [23] (EL2 non-VHE), and the

+  SH0/IRGN0/ORGN0 walker attributes.  BuildOwnPageTables() compares

+  the result against the outer TCR before ArmConfigureMmu() is

+  called, and refuses to launch if any bit differs.

+

+  The duplication is intentional and load-bearing: any change to

+  ArmConfigureMmu()'s TCR recipe must be mirrored here or the compare

+  will fail closed on an outer firmware that would otherwise be safe.

+  An ArmMmuLib helper that returned TCR/MAIR from a build-only path

+  (rather than writing them) would remove both the duplication and

+  the dangerous instant this whole check exists for.

+

+  @return  The TCR value ArmConfigureMmu() will write, or 0 if the

+           physical address size is beyond what ArmMmuLib supports.

+**/

+STATIC

+UINTN

+ComputeArmMmuLibTcr (

+  VOID

+  )

+{

+  UINTN    Tcr;

+  UINTN    MaxAddressBits;

+  UINT64   MaxAddress;

+  BOOLEAN  DualRegime;

+

+  if (ArmHas52BitTgran4 ()) {

+    MaxAddressBits = MIN (ArmGetPhysicalAddressBits (), 52);

+  } else {

+    MaxAddressBits = MIN (ArmGetPhysicalAddressBits (), 48);

+  }

+

+  MaxAddress = LShiftU64 (1ULL, MaxAddressBits) - 1;

+

+  DualRegime = (ArmReadCurrentEL () != AARCH64_EL2) ||

+               ((ArmReadHcr () & ARM_HCR_E2H) != 0);

+

+  if (!DualRegime) {

+    // TCR_EL2 non-VHE: bits [31] and [23] are RES1.

+    Tcr = (64 - MaxAddressBits) | (1UL << 31) | (1UL << 23) | TCR_TG0_4KB;

+

+    if (MaxAddress < SIZE_4GB) {

+      Tcr |= TCR_PS_4GB;

+    } else if (MaxAddress < SIZE_64GB) {

+      Tcr |= TCR_PS_64GB;

+    } else if (MaxAddress < SIZE_1TB) {

+      Tcr |= TCR_PS_1TB;

+    } else if (MaxAddress < SIZE_4TB) {

+      Tcr |= TCR_PS_4TB;

+    } else if (MaxAddress < SIZE_16TB) {

+      Tcr |= TCR_PS_16TB;

+    } else if (MaxAddress < SIZE_256TB) {

+      Tcr |= TCR_PS_256TB;

+    } else if ((MaxAddress < SIZE_4PB) && ArmHas52BitTgran4 ()) {

+      Tcr |= TCR_PS_4PB | TCR_DS_NVHE;

+    } else {

+      return 0;

+    }

+  } else {

+    // Cortex-A57 erratum #822227: TG1[1] must be 1 regardless of EPD1.

+    Tcr = (64 - MaxAddressBits) | TCR_TG0_4KB | TCR_TG1_4KB | TCR_EPD1;

+

+    if (MaxAddress < SIZE_4GB) {

+      Tcr |= TCR_IPS_4GB;

+    } else if (MaxAddress < SIZE_64GB) {

+      Tcr |= TCR_IPS_64GB;

+    } else if (MaxAddress < SIZE_1TB) {

+      Tcr |= TCR_IPS_1TB;

+    } else if (MaxAddress < SIZE_4TB) {

+      Tcr |= TCR_IPS_4TB;

+    } else if (MaxAddress < SIZE_16TB) {

+      Tcr |= TCR_IPS_16TB;

+    } else if (MaxAddress < SIZE_256TB) {

+      Tcr |= TCR_IPS_256TB;

+    } else if ((MaxAddress < SIZE_4PB) && ArmHas52BitTgran4 ()) {

+      Tcr |= TCR_IPS_4PB | TCR_DS;

+    } else {

+      return 0;

+    }

+  }

+

+  Tcr |= TCR_SH_INNER_SHAREABLE |

+         TCR_RGN_OUTER_WRITE_BACK_ALLOC |

+         TCR_RGN_INNER_WRITE_BACK_ALLOC;

+

+  return Tcr;

+}

+

+/**

+  Build AArch64 translation tables owned by ChainloadApp, in

+  EfiReservedMemoryType pages, without leaving them installed.

+

+  ArmConfigureMmu() is called with the outer firmware's translation

+  live: it writes TCR, allocates and populates a fresh table hierarchy

+  through MemoryAllocationLib::AllocatePages() (which for this module is

+  ReservedUefiMemoryAllocationLib, so every page is EfiReservedMemoryType),

+  writes MAIR, then TTBR0.  Once it returns the outer TCR/MAIR/TTBR0 are

+  put back, so boot-services code keeps running on the outer

+  translation and this function does not need to have covered every

+  region the outer firmware might touch.  The new TCR/MAIR and root are

+  saved in module state; the caller writes them to the CPU after

+  ExitBootServices(), when no outer-firmware code runs any more.

+

+  The dangerous instant is between ArmConfigureMmu()'s TCR write and

+  its TTBR0 swap: the outer TTBR0 is briefly interpreted under the new

+  TCR, and gBS->AllocatePages() -- arbitrary outer-firmware code --

+  runs under the new TCR for every intermediate table page.

+  ArmConfigureMmu() rewrites all of TCR: T0SZ, TG0, IPS/PS, DS,

+  SH0/IRGN0/ORGN0, and (in a dual regime) EPD1/TG1.  If any of those

+  differed from what the outer firmware set, the outer translation

+  could be misinterpreted (T0SZ/TG0/DS change the entry format), the

+  page-table walker would use different cacheability for the outer

+  tables (SH0/IRGN0/ORGN0), or the outer TTBR1 would be silently

+  disabled (EPD1).  This function verifies before touching any

+  register that no bit of TCR will change: ComputeArmMmuLibTcr()

+  duplicates ArmConfigureMmu()'s recipe and the outer TCR is compared

+  against it in full.  MAIR is likewise a fixed ArmMmuLib constant.

+  On any outer firmware built from ArmMmuLib both match; on any other

+  outer firmware the launch is refused, so the guarantee this

+  establishes is precisely "the outer firmware built its tables the

+  way ArmMmuLib does".

+

+  ArmMmuBaseLib's constructor calls GetFirstGuidHob() for

+  gArmMmuReplaceLiveTranslationEntryFuncGuid, and in this DSC HobLib

+  for a UEFI application resolves to DxeHobLib, which reads the

+  outer firmware's HOB list from gEfiHobListGuid in its

+  configuration table.  A match there would adopt an

+  outer-firmware-owned mReplaceLiveEntryFunc.  That helper is only

+  ever called with TableIsLive == TRUE, and FillTranslationTable()

+  passes FALSE, so it is unreachable during ArmConfigureMmu() below;

+  noted here so nobody assumes the constructor is a no-op under an

+  outer firmware that publishes the GUID.

+

+  The descriptor list is derived from the outer firmware's GCD memory

+  space map, which is by construction a non-overlapping partition of

+  the whole physical address space.  System memory and Reserved are

+  mapped WRITE_BACK (which carries no XN, so the FV is executable in the

+  new translation, which is what disabling the MMU was originally there

+  to achieve), MMIO is DEVICE, NonExistent is left unmapped.  This does

+  not reuse the interval-splitting helper the payload's

+  ConfigureMmuFromHobs() applies to resource-descriptor HOBs: that

+  helper exists to resolve HOB overlaps, and the GCD map has none.

+

+  The alternative -- keeping the outer firmware's tables live -- cannot

+  be relied on: they are EfiBootServicesData, which the payload reports

+  as free RAM after ExitBootServices(), so DXE could allocate over the

+  live hierarchy.  The alternative of an MMU-off handoff leaves a

+  window in which the payload writes DRAM with the D-cache off; dirty

+  lines the outer firmware left there are not covered by any bounded

+  set of by-VA maintenance and can clobber those writes on eviction.

+

+  @param[in]  GcdMap        Outer firmware's GCD memory space map.

+  @param[in]  GcdMapCount   Number of entries in GcdMap.

+

+  @retval EFI_SUCCESS           New tables built; outer translation left live.

+  @retval EFI_UNSUPPORTED       Outer TCR/MAIR would change; no launch.

+  @retval EFI_OUT_OF_RESOURCES  Descriptor pool exhausted.

+  @retval other                 ArmConfigureMmu() failure.

+**/

+STATIC

+EFI_STATUS

+BuildOwnPageTables (

+  IN EFI_GCD_MEMORY_SPACE_DESCRIPTOR  *GcdMap,

+  IN UINTN                            GcdMapCount

+  )

+{

+  ARM_MEMORY_REGION_DESCRIPTOR  *Region;

+  ARM_MEMORY_REGION_ATTRIBUTES  Attr;

+  EFI_STATUS                    Status;

+  UINTN                         OuterTcr;

+  UINTN                         OuterMair;

+  UINTN                         OuterTtbr0;

+  UINTN                         OurTcr;

+  UINTN                         Count;

+  UINTN                         Index;

+

+  OuterTcr   = ArmGetTCR ();

+  OuterMair  = ArmGetMAIR ();

+  OuterTtbr0 = ChainloadReadRawTtbr0 ();

+  OurTcr     = ComputeArmMmuLibTcr ();

+

+  Print (

+    L"ChainloadApp: outer TCR=0x%lx MAIR=0x%lx TTBR0=0x%lx\n",

+    (UINT64)OuterTcr,

+    (UINT64)OuterMair,

+    (UINT64)OuterTtbr0

+    );

+

+  if ((OuterTcr != OurTcr) ||

+      (OuterMair != CHAINLOAD_ARM_MMU_LIB_MAIR))

+  {

+    //

+    // ArmConfigureMmu() would change TCR or MAIR against a live

+    // TTBR0.  There is no way to build our tables safely and no way to

+    // fall back to an MMU-off handoff without reintroducing the

+    // dirty-line hazard, so refuse to launch.

+    //

+    Print (

+      L"ChainloadApp: outer TCR=0x%lx MAIR=0x%lx does not match "

+      L"ArmMmuLib TCR=0x%lx MAIR=0x%lx; refusing to launch\n",

+      (UINT64)OuterTcr,

+      (UINT64)OuterMair,

+      (UINT64)OurTcr,

+      (UINT64)CHAINLOAD_ARM_MMU_LIB_MAIR

+      );

+    return EFI_UNSUPPORTED;

+  }

+

+  //

+  // One descriptor per mappable GCD entry, plus the zero-length

+  // terminator ArmConfigureMmu() looks for.

+  //

+  Region = AllocatePool ((GcdMapCount + 1) * sizeof (*Region));

+  if (Region == NULL) {

+    return EFI_OUT_OF_RESOURCES;

+  }

+

+  Count = 0;

+  for (Index = 0; Index < GcdMapCount; Index++) {

+    if (GcdMap[Index].Length == 0) {

+      continue;

+    }

+

+    switch (GcdMap[Index].GcdMemoryType) {

+      case EfiGcdMemoryTypeSystemMemory:

+      case EfiGcdMemoryTypeMoreReliable:

+      case EfiGcdMemoryTypeReserved:

+      case EfiGcdMemoryTypePersistent:

+        //

+        // WRITE_BACK carries no XN in ArmMemoryAttributeToPageAttribute(),

+        // so the FV (Reserved inside a SystemMemory range) is executable

+        // in the new translation.

+        //

+        Attr = ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK;

+        break;

+      case EfiGcdMemoryTypeMemoryMappedIo:

+        Attr = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE;

+        break;

+      default:

+        //

+        // NonExistent: leave unmapped.

+        //

+        continue;

+    }

+

+    Region[Count].PhysicalBase = GcdMap[Index].BaseAddress;

+    Region[Count].VirtualBase  = GcdMap[Index].BaseAddress;

+    Region[Count].Length       = GcdMap[Index].Length;

+    Region[Count].Attributes   = Attr;

+    Count++;

+  }

+

+  ZeroMem (&Region[Count], sizeof (Region[Count]));

+

+  Status = ArmConfigureMmu (

+             Region,

+             &mTranslationTableRoot,

+             &mTranslationTableRootSize

+             );

+

+  //

+  // Put the outer translation back so boot-services code keeps working

+  // on the tables it expects.  ArmConfigureMmu() left our TCR/MAIR/TTBR0

+  // in place; save the TCR/MAIR it wrote for the post-EBS install and

+  // then restore all three.  ArmSetTTBR0() ends with an ISB but performs

+  // no TLB invalidation, so any entry cached from our tables between the

+  // TTBR0 swap and here is dropped explicitly.

+  //

+  mOwnTcr  = ArmGetTCR ();

+  mOwnMair = ArmGetMAIR ();

+  ArmSetTCR (OuterTcr);

+  ArmSetMAIR (OuterMair);

+  ArmSetTTBR0 ((VOID *)OuterTtbr0);

+  ArmInvalidateTlb ();

+

+  FreePool (Region);

+

+  if (EFI_ERROR (Status)) {

+    Print (L"ChainloadApp: ArmConfigureMmu failed: %r\n", Status);

+    return Status;

+  }

+

+  Print (

+    L"ChainloadApp: own page tables at 0x%lx (%lu GCD region%s mapped)\n",

+    (UINT64)(UINTN)mTranslationTableRoot,

+    (UINT64)Count,

+    (Count == 1) ? L"" : L"s"

+    );

+

+  return EFI_SUCCESS;

+}

+

+#endif

+

 /**

   Application entry point.  Locates the embedded FV, allocates reserved

   memory for the FV copy, HOB list and payload stack, builds the HOB

@@ -1461,15 +1860,6 @@ ChainloadEntry (
     return EFI_NOT_FOUND;

   }

 

- #if defined (MDE_CPU_AARCH64)

-  //

-  // The AArch64 translation-table handover is added by a later

-  // change; refuse cleanly rather than jump without it.

-  //

-  Print (L"ChainloadApp: AArch64 handover not yet supported\n");

-  return EFI_UNSUPPORTED;

- #endif

-

   EmbeddedFv = FindFvInPayload ((VOID *)mPayloadData, mPayloadSize);

   if (EmbeddedFv == NULL) {

     Print (L"ChainloadApp: no FV header found in embedded payload\n");

@@ -1601,6 +1991,40 @@ ChainloadEntry (
     Print (L"ChainloadApp: GCD map %lu entries, %lu MMIO\n", 
(UINT64)GcdMapCount, (UINT64)MmioCount);

   }

 

+ #if defined (MDE_CPU_AARCH64)

+  //

+  // Build our own translation tables now, while boot services (and

+  // therefore ArmMmuLib's AllocatePages()) are still available.

+  // BuildOwnPageTables() puts the outer TCR/MAIR/TTBR0 back before it

+  // returns; the new tables are made live only after

+  // ExitBootServices(), when no outer-firmware code runs any more.

+  // This runs before the memory-map snapshot below so that the

+  // snapshot -- and hence the SBL memory-map HOB -- reports the table

+  // pages as EfiReservedMemoryType.

+  //

+  Status = BuildOwnPageTables (GcdMap, GcdMapCount);

+  if (EFI_ERROR (Status)) {

+    goto FreeReserved;

+  }

+

+  //

+  // ArmMmuLib allocated the root and every intermediate translation-

+  // table page through ReservedUefiMemoryAllocationLib, which

+  // recorded each one.  Fold them into the boot-time reservation

+  // list so the payload publishes them as EfiBootServicesData: a

+  // normal edk2 boot allocates the same tables via the stock

+  // UefiMemoryAllocationLib as EfiBootServicesData, and arm64 Linux

+  // reclaims them after installing its own translation.

+  //

+  for (Index = 0; Index < gReservedPageAllocCount; Index++) {

+    RecordBootTimeReservation (

+      gReservedPageAllocBase[Index],

+      EFI_PAGES_TO_SIZE (gReservedPageAllocPages[Index])

+      );

+  }

+

+ #endif

+

   //

   // Snapshot the memory map for HOB construction.

   //

@@ -1748,6 +2172,10 @@ ChainloadEntry (
     goto FreeReserved;

   }

 

+ #if defined (MDE_CPU_AARCH64)

+  Print (L"ChainloadApp: MMU stays enabled at branch; own page tables 
installed after ExitBootServices\n");

+ #endif

+

   Print (L"ChainloadApp: calling ExitBootServices\n");

 

   Status = EFI_INVALID_PARAMETER;

@@ -1793,6 +2221,39 @@ ChainloadEntry (
     EnableSpcrPciSerialDecode (AcpiRsdp);

     );

 

+ #if defined (MDE_CPU_AARCH64)

+  //

+  // Install our own translation tables now that no outer-firmware

+  // code runs any more.  BuildOwnPageTables() built them earlier and

+  // put the outer TCR/MAIR/TTBR0 back; here TCR and MAIR are the

+  // ArmMmuLib values (which the refusal check above verified match the

+  // outer values), TTBR0 is our root, and the TLB is invalidated so no

+  // stale entry from the outer tables survives.  ArmSetTTBR0() ends

+  // with an ISB but performs no TLB invalidation itself.

+  //

+  ArmSetTCR (mOwnTcr);

+  ArmSetMAIR (mOwnMair);

+  ArmSetTTBR0 (mTranslationTableRoot);

+  ArmInvalidateTlb ();

+

+  //

+  // The MMU and D-cache stay enabled across the branch, so no by-VA

+  // data-cache maintenance is needed: every write above went through

+  // the D-cache and the payload reads through the same D-cache with

+  // the same WRITE_BACK attribute.

+  //

+  // The instruction cache is a different observer.  The FV was written

+  // through the D-cache and is about to be executed, and the ARMv8-A

+  // architecture does not require the I-cache to be coherent with the

+  // D-cache.  Invalidate the FV to the point of unification so no

+  // stale I-line can shadow the code just copied there.  Do this after

+  // ExitBootServices() so no boot-services code can speculatively

+  // refill the I-cache between the invalidate and the branch.  Cache

+  // maintenance is not a boot service, so it is legal post-EBS.

+  //

+  InvalidateInstructionCacheRange ((VOID *)(UINTN)FvAddress, EFI_PAGES_TO_SIZE 
(FvPages));

+ #endif

+

   JumpToPayload (

     (UINTN)StackAddress + PAYLOAD_STACK_SIZE,

     (UINTN)HobList,

diff --git a/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf 
b/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf
index 7eb5f31a06..0030e6e7c2 100644
--- a/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf
+++ b/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf
@@ -38,6 +38,9 @@
   MdeModulePkg/MdeModulePkg.dec

   UefiPayloadPkg/UefiPayloadPkg.dec

 

+[Packages.AARCH64]

+  UefiCpuPkg/UefiCpuPkg.dec

+

 [LibraryClasses]

   UefiApplicationEntryPoint

   UefiBootServicesTableLib

@@ -55,6 +58,7 @@
 

 [LibraryClasses.AARCH64]

   ArmLib

+  ArmMmuLib

 

 [Guids]

   gEfiAcpiTableGuid

-- 
2.47.3



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#122104): https://edk2.groups.io/g/devel/message/122104
Mute This Topic: https://groups.io/mt/120797311/21656
Group Owner: [email protected]
Unsubscribe: https://edk2.groups.io/g/devel/unsub [[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-


Reply via email to