/** @file
The X64 entrypoint is used to process capsule in long mode.
Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.
Copyright (c) 2017, AMD Incorporated. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include "CommonHeader.h"
#define EXCEPTION_VECTOR_NUMBER 0x22
#define IA32_PG_P BIT0
#define IA32_PG_RW BIT1
#define IA32_PG_PS BIT7
typedef struct _PAGE_FAULT_CONTEXT {
BOOLEAN Page1GSupport;
UINT64 PhyMask;
UINTN PageFaultBuffer;
UINTN PageFaultIndex;
UINT64 AddressEncMask;
//
// Store the uplink information for each page being used.
//
UINT64 *PageFaultUplink[EXTRA_PAGE_TABLE_PAGES];
VOID *OriginalHandler;
} PAGE_FAULT_CONTEXT;
typedef struct _PAGE_FAULT_IDT_TABLE {
PAGE_FAULT_CONTEXT PageFaultContext;
IA32_IDT_GATE_DESCRIPTOR IdtEntryTable[EXCEPTION_VECTOR_NUMBER];
} PAGE_FAULT_IDT_TABLE;
/**
Page fault handler.
**/
VOID
EFIAPI
PageFaultHandlerHook (
VOID
);
/**
Hook IDT with our page fault handler so that the on-demand paging works on page fault.
@param[in, out] IdtEntry Pointer to IDT entry.
@param[in, out] PageFaultContext Pointer to page fault context.
**/
VOID
HookPageFaultHandler (
IN OUT IA32_IDT_GATE_DESCRIPTOR *IdtEntry,
IN OUT PAGE_FAULT_CONTEXT *PageFaultContext
)
{
UINT32 RegEax;
UINT8 PhysicalAddressBits;
UINTN PageFaultHandlerHookAddress;
AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL);
if (RegEax >= 0x80000008) {
AsmCpuid (0x80000008, &RegEax, NULL, NULL, NULL);
PhysicalAddressBits = (UINT8)RegEax;
} else {
PhysicalAddressBits = 36;
}
PageFaultContext->PhyMask = LShiftU64 (1, PhysicalAddressBits) - 1;
PageFaultContext->PhyMask &= (1ull << 48) - SIZE_4KB;
//
// Set Page Fault entry to catch >4G access
//
PageFaultHandlerHookAddress = (UINTN)PageFaultHandlerHook;
PageFaultContext->OriginalHandler = (VOID *)(UINTN)(LShiftU64 (IdtEntry->Bits.OffsetUpper, 32) + IdtEntry->Bits.OffsetLow + (IdtEntry->Bits.OffsetHigh << 16));
IdtEntry->Bits.OffsetLow = (UINT16)PageFaultHandlerHookAddress;
IdtEntry->Bits.Selector = (UINT16)AsmReadCs ();
IdtEntry->Bits.Reserved_0 = 0;
IdtEntry->Bits.GateType = IA32_IDT_GATE_TYPE_INTERRUPT_32;
IdtEntry->Bits.OffsetHigh = (UINT16)(PageFaultHandlerHookAddress >> 16);
IdtEntry->Bits.OffsetUpper = (UINT32)(PageFaultHandlerHookAddress >> 32);
IdtEntry->Bits.Reserved_1 = 0;
if (PageFaultContext->Page1GSupport) {
PageFaultContext->PageFaultBuffer = (UINTN)(AsmReadCr3 () & PageFaultContext->PhyMask) + EFI_PAGES_TO_SIZE (2);
} else {
PageFaultContext->PageFaultBuffer = (UINTN)(AsmReadCr3 () & PageFaultContext->PhyMask) + EFI_PAGES_TO_SIZE (6);
}
PageFaultContext->PageFaultIndex = 0;
ZeroMem (PageFaultContext->PageFaultUplink, sizeof (PageFaultContext->PageFaultUplink));
}
/**
Acquire page for page fault.
@param[in, out] PageFaultContext Pointer to page fault context.
@param[in, out] Uplink Pointer to up page table entry.
**/
VOID
AcquirePage (
IN OUT PAGE_FAULT_CONTEXT *PageFaultContext,
IN OUT UINT64 *Uplink
)
{
UINTN Address;
UINT64 AddressEncMask;
Address = PageFaultContext->PageFaultBuffer + EFI_PAGES_TO_SIZE (PageFaultContext->PageFaultIndex);
ZeroMem ((VOID *)Address, EFI_PAGES_TO_SIZE (1));
AddressEncMask = PageFaultContext->AddressEncMask;
//
// Cut the previous uplink if it exists and wasn't overwritten.
//
if ((PageFaultContext->PageFaultUplink[PageFaultContext->PageFaultIndex] != NULL) &&
((*PageFaultContext->PageFaultUplink[PageFaultContext->PageFaultIndex] & ~AddressEncMask & PageFaultContext->PhyMask) == Address))
{
*PageFaultContext->PageFaultUplink[PageFaultContext->PageFaultIndex] = 0;
}
//
// Link & Record the current uplink.
//
*Uplink = Address | AddressEncMask | IA32_PG_P | IA32_PG_RW;
PageFaultContext->PageFaultUplink[PageFaultContext->PageFaultIndex] = Uplink;
PageFaultContext->PageFaultIndex = (PageFaultContext->PageFaultIndex + 1) % EXTRA_PAGE_TABLE_PAGES;
}
/**
The page fault handler that on-demand read >4G memory/MMIO.
@retval NULL The page fault is correctly handled.
@retval OriginalHandler The page fault is not handled and is passed through to original handler.
**/
VOID *
EFIAPI
PageFaultHandler (
VOID
)
{
IA32_DESCRIPTOR Idtr;
PAGE_FAULT_CONTEXT *PageFaultContext;
UINT64 PhyMask;
UINT64 *PageTable;
UINT64 PFAddress;
UINTN PTIndex;
UINT64 AddressEncMask;
//
// Get the IDT Descriptor.
//
AsmReadIdtr ((IA32_DESCRIPTOR *)&Idtr);
//
// Then get page fault context by IDT Descriptor.
//
PageFaultContext = (PAGE_FAULT_CONTEXT *)(UINTN)(Idtr.Base - sizeof (PAGE_FAULT_CONTEXT));
PhyMask = PageFaultContext->PhyMask;
AddressEncMask = PageFaultContext->AddressEncMask;
PFAddress = AsmReadCr2 ();
DEBUG ((DEBUG_ERROR, "CapsuleX64 - PageFaultHandler: Cr2 - %lx\n", PFAddress));
if (PFAddress >= PhyMask + SIZE_4KB) {
return PageFaultContext->OriginalHandler;
}
PFAddress &= PhyMask;
PageTable = (UINT64 *)(UINTN)(AsmReadCr3 () & PhyMask);
PTIndex = BitFieldRead64 (PFAddress, 39, 47);
// PML4E
if ((PageTable[PTIndex] & IA32_PG_P) == 0) {
AcquirePage (PageFaultContext, &PageTable[PTIndex]);
}
PageTable = (UINT64 *)(UINTN)(PageTable[PTIndex] & ~AddressEncMask & PhyMask);
PTIndex = BitFieldRead64 (PFAddress, 30, 38);
// PDPTE
if (PageFaultContext->Page1GSupport) {
PageTable[PTIndex] = ((PFAddress | AddressEncMask) & ~((1ull << 30) - 1)) | IA32_PG_P | IA32_PG_RW | IA32_PG_PS;
} else {
if ((PageTable[PTIndex] & IA32_PG_P) == 0) {
AcquirePage (PageFaultContext, &PageTable[PTIndex]);
}
PageTable = (UINT64 *)(UINTN)(PageTable[PTIndex] & ~AddressEncMask & PhyMask);
PTIndex = BitFieldRead64 (PFAddress, 21, 29);
// PD
PageTable[PTIndex] = ((PFAddress | AddressEncMask) & ~((1ull << 21) - 1)) | IA32_PG_P | IA32_PG_RW | IA32_PG_PS;
}
return NULL;
}
/**
The X64 entrypoint is used to process capsule in long mode then
return to 32-bit protected mode.
@param EntrypointContext Pointer to the context of long mode.
@param ReturnContext Pointer to the context of 32-bit protected mode.
@retval This function should never return actually.
**/
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
SWITCH_32_TO_64_CONTEXT *EntrypointContext,
SWITCH_64_TO_32_CONTEXT *ReturnContext
)
{
EFI_STATUS Status;
IA32_DESCRIPTOR Ia32Idtr;
IA32_DESCRIPTOR X64Idtr;
PAGE_FAULT_IDT_TABLE PageFaultIdtTable;
IA32_IDT_GATE_DESCRIPTOR *IdtEntry;
//
// Save the IA32 IDT Descriptor
//
AsmReadIdtr ((IA32_DESCRIPTOR *)&Ia32Idtr);
//
// Setup X64 IDT table
//
ZeroMem (PageFaultIdtTable.IdtEntryTable, sizeof (IA32_IDT_GATE_DESCRIPTOR) * EXCEPTION_VECTOR_NUMBER);
X64Idtr.Base = (UINTN)PageFaultIdtTable.IdtEntryTable;
X64Idtr.Limit = (UINT16)(sizeof (IA32_IDT_GATE_DESCRIPTOR) * EXCEPTION_VECTOR_NUMBER - 1);
AsmWriteIdtr ((IA32_DESCRIPTOR *)&X64Idtr);
//
// Setup the default CPU exception handlers
//
Status = InitializeCpuExceptionHandlers (NULL);
ASSERT_EFI_ERROR (Status);
//
// Hook page fault handler to handle >4G request.
//
PageFaultIdtTable.PageFaultContext.Page1GSupport = EntrypointContext->Page1GSupport;
PageFaultIdtTable.PageFaultContext.AddressEncMask = EntrypointContext->AddressEncMask;
IdtEntry = (IA32_IDT_GATE_DESCRIPTOR *)(X64Idtr.Base + (14 * sizeof (IA32_IDT_GATE_DESCRIPTOR)));
HookPageFaultHandler (IdtEntry, &(PageFaultIdtTable.PageFaultContext));
//
// Initialize Debug Agent to support source level debug
//
InitializeDebugAgent (DEBUG_AGENT_INIT_THUNK_PEI_IA32TOX64, (VOID *)&Ia32Idtr, NULL);
//
// Call CapsuleDataCoalesce to process capsule.
//
Status = CapsuleDataCoalesce (
NULL,
(EFI_PHYSICAL_ADDRESS *)(UINTN)EntrypointContext->BlockListAddr,
(MEMORY_RESOURCE_DESCRIPTOR *)(UINTN)EntrypointContext->MemoryResource,
(VOID **)(UINTN)EntrypointContext->MemoryBase64Ptr,
(UINTN *)(UINTN)EntrypointContext->MemorySize64Ptr
);
ReturnContext->ReturnStatus = Status;
DEBUG ((
DEBUG_INFO,
"%a() Stack Base: 0x%lx, Stack Size: 0x%lx\n",
__func__,
EntrypointContext->StackBufferBase,
EntrypointContext->StackBufferLength
));
//
// Disable interrupt of Debug timer, since the new IDT table cannot work in long mode
//
SaveAndSetDebugTimerInterrupt (FALSE);
//
// Restore IA32 IDT table
//
AsmWriteIdtr ((IA32_DESCRIPTOR *)&Ia32Idtr);
//
// Finish to coalesce capsule, and return to 32-bit mode.
//
AsmDisablePaging64 (
ReturnContext->ReturnCs,
(UINT32)ReturnContext->ReturnEntryPoint,
(UINT32)(UINTN)EntrypointContext,
(UINT32)(UINTN)ReturnContext,
(UINT32)(EntrypointContext->StackBufferBase + EntrypointContext->StackBufferLength)
);
//
// Should never be here.
//
ASSERT (FALSE);
return EFI_SUCCESS;
}