mirror of https://github.com/acidanthera/audk.git
1286 lines
40 KiB
C
1286 lines
40 KiB
C
/** @file
|
|
Page Fault (#PF) handler for X64 processors
|
|
|
|
Copyright (c) 2009 - 2019, Intel Corporation. All rights reserved.<BR>
|
|
Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR>
|
|
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
**/
|
|
|
|
#include "PiSmmCpuDxeSmm.h"
|
|
|
|
#define PAGE_TABLE_PAGES 8
|
|
#define ACC_MAX_BIT BIT3
|
|
|
|
LIST_ENTRY mPagePool = INITIALIZE_LIST_HEAD_VARIABLE (mPagePool);
|
|
BOOLEAN m1GPageTableSupport = FALSE;
|
|
BOOLEAN mCpuSmmRestrictedMemoryAccess;
|
|
BOOLEAN m5LevelPagingNeeded;
|
|
X86_ASSEMBLY_PATCH_LABEL gPatch5LevelPagingNeeded;
|
|
|
|
/**
|
|
Disable CET.
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
DisableCet (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Enable CET.
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
EnableCet (
|
|
VOID
|
|
);
|
|
|
|
/**
|
|
Check if 1-GByte pages is supported by processor or not.
|
|
|
|
@retval TRUE 1-GByte pages is supported.
|
|
@retval FALSE 1-GByte pages is not supported.
|
|
|
|
**/
|
|
BOOLEAN
|
|
Is1GPageSupport (
|
|
VOID
|
|
)
|
|
{
|
|
UINT32 RegEax;
|
|
UINT32 RegEdx;
|
|
|
|
AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL);
|
|
if (RegEax >= 0x80000001) {
|
|
AsmCpuid (0x80000001, NULL, NULL, NULL, &RegEdx);
|
|
if ((RegEdx & BIT26) != 0) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
The routine returns TRUE when CPU supports it (CPUID[7,0].ECX.BIT[16] is set) and
|
|
the max physical address bits is bigger than 48. Because 4-level paging can support
|
|
to address physical address up to 2^48 - 1, there is no need to enable 5-level paging
|
|
with max physical address bits <= 48.
|
|
|
|
@retval TRUE 5-level paging enabling is needed.
|
|
@retval FALSE 5-level paging enabling is not needed.
|
|
**/
|
|
BOOLEAN
|
|
Is5LevelPagingNeeded (
|
|
VOID
|
|
)
|
|
{
|
|
CPUID_VIR_PHY_ADDRESS_SIZE_EAX VirPhyAddressSize;
|
|
CPUID_STRUCTURED_EXTENDED_FEATURE_FLAGS_ECX ExtFeatureEcx;
|
|
UINT32 MaxExtendedFunctionId;
|
|
|
|
AsmCpuid (CPUID_EXTENDED_FUNCTION, &MaxExtendedFunctionId, NULL, NULL, NULL);
|
|
if (MaxExtendedFunctionId >= CPUID_VIR_PHY_ADDRESS_SIZE) {
|
|
AsmCpuid (CPUID_VIR_PHY_ADDRESS_SIZE, &VirPhyAddressSize.Uint32, NULL, NULL, NULL);
|
|
} else {
|
|
VirPhyAddressSize.Bits.PhysicalAddressBits = 36;
|
|
}
|
|
AsmCpuidEx (
|
|
CPUID_STRUCTURED_EXTENDED_FEATURE_FLAGS,
|
|
CPUID_STRUCTURED_EXTENDED_FEATURE_FLAGS_SUB_LEAF_INFO,
|
|
NULL, NULL, &ExtFeatureEcx.Uint32, NULL
|
|
);
|
|
DEBUG ((
|
|
DEBUG_INFO, "PhysicalAddressBits = %d, 5LPageTable = %d.\n",
|
|
VirPhyAddressSize.Bits.PhysicalAddressBits, ExtFeatureEcx.Bits.FiveLevelPage
|
|
));
|
|
|
|
if (VirPhyAddressSize.Bits.PhysicalAddressBits > 4 * 9 + 12) {
|
|
ASSERT (ExtFeatureEcx.Bits.FiveLevelPage == 1);
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Set sub-entries number in entry.
|
|
|
|
@param[in, out] Entry Pointer to entry
|
|
@param[in] SubEntryNum Sub-entries number based on 0:
|
|
0 means there is 1 sub-entry under this entry
|
|
0x1ff means there is 512 sub-entries under this entry
|
|
|
|
**/
|
|
VOID
|
|
SetSubEntriesNum (
|
|
IN OUT UINT64 *Entry,
|
|
IN UINT64 SubEntryNum
|
|
)
|
|
{
|
|
//
|
|
// Sub-entries number is saved in BIT52 to BIT60 (reserved field) in Entry
|
|
//
|
|
*Entry = BitFieldWrite64 (*Entry, 52, 60, SubEntryNum);
|
|
}
|
|
|
|
/**
|
|
Return sub-entries number in entry.
|
|
|
|
@param[in] Entry Pointer to entry
|
|
|
|
@return Sub-entries number based on 0:
|
|
0 means there is 1 sub-entry under this entry
|
|
0x1ff means there is 512 sub-entries under this entry
|
|
**/
|
|
UINT64
|
|
GetSubEntriesNum (
|
|
IN UINT64 *Entry
|
|
)
|
|
{
|
|
//
|
|
// Sub-entries number is saved in BIT52 to BIT60 (reserved field) in Entry
|
|
//
|
|
return BitFieldRead64 (*Entry, 52, 60);
|
|
}
|
|
|
|
/**
|
|
Calculate the maximum support address.
|
|
|
|
@return the maximum support address.
|
|
**/
|
|
UINT8
|
|
CalculateMaximumSupportAddress (
|
|
VOID
|
|
)
|
|
{
|
|
UINT32 RegEax;
|
|
UINT8 PhysicalAddressBits;
|
|
VOID *Hob;
|
|
|
|
//
|
|
// Get physical address bits supported.
|
|
//
|
|
Hob = GetFirstHob (EFI_HOB_TYPE_CPU);
|
|
if (Hob != NULL) {
|
|
PhysicalAddressBits = ((EFI_HOB_CPU *) Hob)->SizeOfMemorySpace;
|
|
} else {
|
|
AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL);
|
|
if (RegEax >= 0x80000008) {
|
|
AsmCpuid (0x80000008, &RegEax, NULL, NULL, NULL);
|
|
PhysicalAddressBits = (UINT8) RegEax;
|
|
} else {
|
|
PhysicalAddressBits = 36;
|
|
}
|
|
}
|
|
return PhysicalAddressBits;
|
|
}
|
|
|
|
/**
|
|
Set static page table.
|
|
|
|
@param[in] PageTable Address of page table.
|
|
**/
|
|
VOID
|
|
SetStaticPageTable (
|
|
IN UINTN PageTable
|
|
)
|
|
{
|
|
UINT64 PageAddress;
|
|
UINTN NumberOfPml5EntriesNeeded;
|
|
UINTN NumberOfPml4EntriesNeeded;
|
|
UINTN NumberOfPdpEntriesNeeded;
|
|
UINTN IndexOfPml5Entries;
|
|
UINTN IndexOfPml4Entries;
|
|
UINTN IndexOfPdpEntries;
|
|
UINTN IndexOfPageDirectoryEntries;
|
|
UINT64 *PageMapLevel5Entry;
|
|
UINT64 *PageMapLevel4Entry;
|
|
UINT64 *PageMap;
|
|
UINT64 *PageDirectoryPointerEntry;
|
|
UINT64 *PageDirectory1GEntry;
|
|
UINT64 *PageDirectoryEntry;
|
|
|
|
//
|
|
// IA-32e paging translates 48-bit linear addresses to 52-bit physical addresses
|
|
// when 5-Level Paging is disabled.
|
|
//
|
|
ASSERT (mPhysicalAddressBits <= 52);
|
|
if (!m5LevelPagingNeeded && mPhysicalAddressBits > 48) {
|
|
mPhysicalAddressBits = 48;
|
|
}
|
|
|
|
NumberOfPml5EntriesNeeded = 1;
|
|
if (mPhysicalAddressBits > 48) {
|
|
NumberOfPml5EntriesNeeded = (UINTN) LShiftU64 (1, mPhysicalAddressBits - 48);
|
|
mPhysicalAddressBits = 48;
|
|
}
|
|
|
|
NumberOfPml4EntriesNeeded = 1;
|
|
if (mPhysicalAddressBits > 39) {
|
|
NumberOfPml4EntriesNeeded = (UINTN) LShiftU64 (1, mPhysicalAddressBits - 39);
|
|
mPhysicalAddressBits = 39;
|
|
}
|
|
|
|
NumberOfPdpEntriesNeeded = 1;
|
|
ASSERT (mPhysicalAddressBits > 30);
|
|
NumberOfPdpEntriesNeeded = (UINTN) LShiftU64 (1, mPhysicalAddressBits - 30);
|
|
|
|
//
|
|
// By architecture only one PageMapLevel4 exists - so lets allocate storage for it.
|
|
//
|
|
PageMap = (VOID *) PageTable;
|
|
|
|
PageMapLevel4Entry = PageMap;
|
|
PageMapLevel5Entry = NULL;
|
|
if (m5LevelPagingNeeded) {
|
|
//
|
|
// By architecture only one PageMapLevel5 exists - so lets allocate storage for it.
|
|
//
|
|
PageMapLevel5Entry = PageMap;
|
|
}
|
|
PageAddress = 0;
|
|
|
|
for ( IndexOfPml5Entries = 0
|
|
; IndexOfPml5Entries < NumberOfPml5EntriesNeeded
|
|
; IndexOfPml5Entries++, PageMapLevel5Entry++) {
|
|
//
|
|
// Each PML5 entry points to a page of PML4 entires.
|
|
// So lets allocate space for them and fill them in in the IndexOfPml4Entries loop.
|
|
// When 5-Level Paging is disabled, below allocation happens only once.
|
|
//
|
|
if (m5LevelPagingNeeded) {
|
|
PageMapLevel4Entry = (UINT64 *) ((*PageMapLevel5Entry) & ~mAddressEncMask & gPhyMask);
|
|
if (PageMapLevel4Entry == NULL) {
|
|
PageMapLevel4Entry = AllocatePageTableMemory (1);
|
|
ASSERT(PageMapLevel4Entry != NULL);
|
|
ZeroMem (PageMapLevel4Entry, EFI_PAGES_TO_SIZE(1));
|
|
|
|
*PageMapLevel5Entry = (UINT64)(UINTN)PageMapLevel4Entry | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
|
|
}
|
|
}
|
|
|
|
for (IndexOfPml4Entries = 0; IndexOfPml4Entries < (NumberOfPml5EntriesNeeded == 1 ? NumberOfPml4EntriesNeeded : 512); IndexOfPml4Entries++, PageMapLevel4Entry++) {
|
|
//
|
|
// Each PML4 entry points to a page of Page Directory Pointer entries.
|
|
//
|
|
PageDirectoryPointerEntry = (UINT64 *) ((*PageMapLevel4Entry) & ~mAddressEncMask & gPhyMask);
|
|
if (PageDirectoryPointerEntry == NULL) {
|
|
PageDirectoryPointerEntry = AllocatePageTableMemory (1);
|
|
ASSERT(PageDirectoryPointerEntry != NULL);
|
|
ZeroMem (PageDirectoryPointerEntry, EFI_PAGES_TO_SIZE(1));
|
|
|
|
*PageMapLevel4Entry = (UINT64)(UINTN)PageDirectoryPointerEntry | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
|
|
}
|
|
|
|
if (m1GPageTableSupport) {
|
|
PageDirectory1GEntry = PageDirectoryPointerEntry;
|
|
for (IndexOfPageDirectoryEntries = 0; IndexOfPageDirectoryEntries < 512; IndexOfPageDirectoryEntries++, PageDirectory1GEntry++, PageAddress += SIZE_1GB) {
|
|
if (IndexOfPml4Entries == 0 && IndexOfPageDirectoryEntries < 4) {
|
|
//
|
|
// Skip the < 4G entries
|
|
//
|
|
continue;
|
|
}
|
|
//
|
|
// Fill in the Page Directory entries
|
|
//
|
|
*PageDirectory1GEntry = PageAddress | mAddressEncMask | IA32_PG_PS | PAGE_ATTRIBUTE_BITS;
|
|
}
|
|
} else {
|
|
PageAddress = BASE_4GB;
|
|
for (IndexOfPdpEntries = 0; IndexOfPdpEntries < (NumberOfPml4EntriesNeeded == 1 ? NumberOfPdpEntriesNeeded : 512); IndexOfPdpEntries++, PageDirectoryPointerEntry++) {
|
|
if (IndexOfPml4Entries == 0 && IndexOfPdpEntries < 4) {
|
|
//
|
|
// Skip the < 4G entries
|
|
//
|
|
continue;
|
|
}
|
|
//
|
|
// Each Directory Pointer entries points to a page of Page Directory entires.
|
|
// So allocate space for them and fill them in in the IndexOfPageDirectoryEntries loop.
|
|
//
|
|
PageDirectoryEntry = (UINT64 *) ((*PageDirectoryPointerEntry) & ~mAddressEncMask & gPhyMask);
|
|
if (PageDirectoryEntry == NULL) {
|
|
PageDirectoryEntry = AllocatePageTableMemory (1);
|
|
ASSERT(PageDirectoryEntry != NULL);
|
|
ZeroMem (PageDirectoryEntry, EFI_PAGES_TO_SIZE(1));
|
|
|
|
//
|
|
// Fill in a Page Directory Pointer Entries
|
|
//
|
|
*PageDirectoryPointerEntry = (UINT64)(UINTN)PageDirectoryEntry | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
|
|
}
|
|
|
|
for (IndexOfPageDirectoryEntries = 0; IndexOfPageDirectoryEntries < 512; IndexOfPageDirectoryEntries++, PageDirectoryEntry++, PageAddress += SIZE_2MB) {
|
|
//
|
|
// Fill in the Page Directory entries
|
|
//
|
|
*PageDirectoryEntry = PageAddress | mAddressEncMask | IA32_PG_PS | PAGE_ATTRIBUTE_BITS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Create PageTable for SMM use.
|
|
|
|
@return The address of PML4 (to set CR3).
|
|
|
|
**/
|
|
UINT32
|
|
SmmInitPageTable (
|
|
VOID
|
|
)
|
|
{
|
|
EFI_PHYSICAL_ADDRESS Pages;
|
|
UINT64 *PTEntry;
|
|
LIST_ENTRY *FreePage;
|
|
UINTN Index;
|
|
UINTN PageFaultHandlerHookAddress;
|
|
IA32_IDT_GATE_DESCRIPTOR *IdtEntry;
|
|
EFI_STATUS Status;
|
|
UINT64 *Pml4Entry;
|
|
UINT64 *Pml5Entry;
|
|
|
|
//
|
|
// Initialize spin lock
|
|
//
|
|
InitializeSpinLock (mPFLock);
|
|
|
|
mCpuSmmRestrictedMemoryAccess = PcdGetBool (PcdCpuSmmRestrictedMemoryAccess);
|
|
m1GPageTableSupport = Is1GPageSupport ();
|
|
m5LevelPagingNeeded = Is5LevelPagingNeeded ();
|
|
mPhysicalAddressBits = CalculateMaximumSupportAddress ();
|
|
PatchInstructionX86 (gPatch5LevelPagingNeeded, m5LevelPagingNeeded, 1);
|
|
DEBUG ((DEBUG_INFO, "5LevelPaging Needed - %d\n", m5LevelPagingNeeded));
|
|
DEBUG ((DEBUG_INFO, "1GPageTable Support - %d\n", m1GPageTableSupport));
|
|
DEBUG ((DEBUG_INFO, "PcdCpuSmmRestrictedMemoryAccess - %d\n", mCpuSmmRestrictedMemoryAccess));
|
|
DEBUG ((DEBUG_INFO, "PhysicalAddressBits - %d\n", mPhysicalAddressBits));
|
|
//
|
|
// Generate PAE page table for the first 4GB memory space
|
|
//
|
|
Pages = Gen4GPageTable (FALSE);
|
|
|
|
//
|
|
// Set IA32_PG_PMNT bit to mask this entry
|
|
//
|
|
PTEntry = (UINT64*)(UINTN)Pages;
|
|
for (Index = 0; Index < 4; Index++) {
|
|
PTEntry[Index] |= IA32_PG_PMNT;
|
|
}
|
|
|
|
//
|
|
// Fill Page-Table-Level4 (PML4) entry
|
|
//
|
|
Pml4Entry = (UINT64*)AllocatePageTableMemory (1);
|
|
ASSERT (Pml4Entry != NULL);
|
|
*Pml4Entry = Pages | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
|
|
ZeroMem (Pml4Entry + 1, EFI_PAGE_SIZE - sizeof (*Pml4Entry));
|
|
|
|
//
|
|
// Set sub-entries number
|
|
//
|
|
SetSubEntriesNum (Pml4Entry, 3);
|
|
PTEntry = Pml4Entry;
|
|
|
|
if (m5LevelPagingNeeded) {
|
|
//
|
|
// Fill PML5 entry
|
|
//
|
|
Pml5Entry = (UINT64*)AllocatePageTableMemory (1);
|
|
ASSERT (Pml5Entry != NULL);
|
|
*Pml5Entry = (UINTN) Pml4Entry | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
|
|
ZeroMem (Pml5Entry + 1, EFI_PAGE_SIZE - sizeof (*Pml5Entry));
|
|
//
|
|
// Set sub-entries number
|
|
//
|
|
SetSubEntriesNum (Pml5Entry, 1);
|
|
PTEntry = Pml5Entry;
|
|
}
|
|
|
|
if (mCpuSmmRestrictedMemoryAccess) {
|
|
//
|
|
// When access to non-SMRAM memory is restricted, create page table
|
|
// that covers all memory space.
|
|
//
|
|
SetStaticPageTable ((UINTN)PTEntry);
|
|
} else {
|
|
//
|
|
// Add pages to page pool
|
|
//
|
|
FreePage = (LIST_ENTRY*)AllocatePageTableMemory (PAGE_TABLE_PAGES);
|
|
ASSERT (FreePage != NULL);
|
|
for (Index = 0; Index < PAGE_TABLE_PAGES; Index++) {
|
|
InsertTailList (&mPagePool, FreePage);
|
|
FreePage += EFI_PAGE_SIZE / sizeof (*FreePage);
|
|
}
|
|
}
|
|
|
|
if (FeaturePcdGet (PcdCpuSmmProfileEnable) ||
|
|
HEAP_GUARD_NONSTOP_MODE ||
|
|
NULL_DETECTION_NONSTOP_MODE) {
|
|
//
|
|
// Set own Page Fault entry instead of the default one, because SMM Profile
|
|
// feature depends on IRET instruction to do Single Step
|
|
//
|
|
PageFaultHandlerHookAddress = (UINTN)PageFaultIdtHandlerSmmProfile;
|
|
IdtEntry = (IA32_IDT_GATE_DESCRIPTOR *) gcSmiIdtr.Base;
|
|
IdtEntry += EXCEPT_IA32_PAGE_FAULT;
|
|
IdtEntry->Bits.OffsetLow = (UINT16)PageFaultHandlerHookAddress;
|
|
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;
|
|
} else {
|
|
//
|
|
// Register Smm Page Fault Handler
|
|
//
|
|
Status = SmmRegisterExceptionHandler (&mSmmCpuService, EXCEPT_IA32_PAGE_FAULT, SmiPFHandler);
|
|
ASSERT_EFI_ERROR (Status);
|
|
}
|
|
|
|
//
|
|
// Additional SMM IDT initialization for SMM stack guard
|
|
//
|
|
if (FeaturePcdGet (PcdCpuSmmStackGuard)) {
|
|
InitializeIDTSmmStackGuard ();
|
|
}
|
|
|
|
//
|
|
// Return the address of PML4/PML5 (to set CR3)
|
|
//
|
|
return (UINT32)(UINTN)PTEntry;
|
|
}
|
|
|
|
/**
|
|
Set access record in entry.
|
|
|
|
@param[in, out] Entry Pointer to entry
|
|
@param[in] Acc Access record value
|
|
|
|
**/
|
|
VOID
|
|
SetAccNum (
|
|
IN OUT UINT64 *Entry,
|
|
IN UINT64 Acc
|
|
)
|
|
{
|
|
//
|
|
// Access record is saved in BIT9 to BIT11 (reserved field) in Entry
|
|
//
|
|
*Entry = BitFieldWrite64 (*Entry, 9, 11, Acc);
|
|
}
|
|
|
|
/**
|
|
Return access record in entry.
|
|
|
|
@param[in] Entry Pointer to entry
|
|
|
|
@return Access record value.
|
|
|
|
**/
|
|
UINT64
|
|
GetAccNum (
|
|
IN UINT64 *Entry
|
|
)
|
|
{
|
|
//
|
|
// Access record is saved in BIT9 to BIT11 (reserved field) in Entry
|
|
//
|
|
return BitFieldRead64 (*Entry, 9, 11);
|
|
}
|
|
|
|
/**
|
|
Return and update the access record in entry.
|
|
|
|
@param[in, out] Entry Pointer to entry
|
|
|
|
@return Access record value.
|
|
|
|
**/
|
|
UINT64
|
|
GetAndUpdateAccNum (
|
|
IN OUT UINT64 *Entry
|
|
)
|
|
{
|
|
UINT64 Acc;
|
|
|
|
Acc = GetAccNum (Entry);
|
|
if ((*Entry & IA32_PG_A) != 0) {
|
|
//
|
|
// If this entry has been accessed, clear access flag in Entry and update access record
|
|
// to the initial value 7, adding ACC_MAX_BIT is to make it larger than others
|
|
//
|
|
*Entry &= ~(UINT64)(UINTN)IA32_PG_A;
|
|
SetAccNum (Entry, 0x7);
|
|
return (0x7 + ACC_MAX_BIT);
|
|
} else {
|
|
if (Acc != 0) {
|
|
//
|
|
// If the access record is not the smallest value 0, minus 1 and update the access record field
|
|
//
|
|
SetAccNum (Entry, Acc - 1);
|
|
}
|
|
}
|
|
return Acc;
|
|
}
|
|
|
|
/**
|
|
Reclaim free pages for PageFault handler.
|
|
|
|
Search the whole entries tree to find the leaf entry that has the smallest
|
|
access record value. Insert the page pointed by this leaf entry into the
|
|
page pool. And check its upper entries if need to be inserted into the page
|
|
pool or not.
|
|
|
|
**/
|
|
VOID
|
|
ReclaimPages (
|
|
VOID
|
|
)
|
|
{
|
|
UINT64 Pml5Entry;
|
|
UINT64 *Pml5;
|
|
UINT64 *Pml4;
|
|
UINT64 *Pdpt;
|
|
UINT64 *Pdt;
|
|
UINTN Pml5Index;
|
|
UINTN Pml4Index;
|
|
UINTN PdptIndex;
|
|
UINTN PdtIndex;
|
|
UINTN MinPml5;
|
|
UINTN MinPml4;
|
|
UINTN MinPdpt;
|
|
UINTN MinPdt;
|
|
UINT64 MinAcc;
|
|
UINT64 Acc;
|
|
UINT64 SubEntriesNum;
|
|
BOOLEAN PML4EIgnore;
|
|
BOOLEAN PDPTEIgnore;
|
|
UINT64 *ReleasePageAddress;
|
|
IA32_CR4 Cr4;
|
|
BOOLEAN Enable5LevelPaging;
|
|
UINT64 PFAddress;
|
|
UINT64 PFAddressPml5Index;
|
|
UINT64 PFAddressPml4Index;
|
|
UINT64 PFAddressPdptIndex;
|
|
UINT64 PFAddressPdtIndex;
|
|
|
|
Pml4 = NULL;
|
|
Pdpt = NULL;
|
|
Pdt = NULL;
|
|
MinAcc = (UINT64)-1;
|
|
MinPml4 = (UINTN)-1;
|
|
MinPml5 = (UINTN)-1;
|
|
MinPdpt = (UINTN)-1;
|
|
MinPdt = (UINTN)-1;
|
|
Acc = 0;
|
|
ReleasePageAddress = 0;
|
|
PFAddress = AsmReadCr2 ();
|
|
PFAddressPml5Index = BitFieldRead64 (PFAddress, 48, 48 + 8);
|
|
PFAddressPml4Index = BitFieldRead64 (PFAddress, 39, 39 + 8);
|
|
PFAddressPdptIndex = BitFieldRead64 (PFAddress, 30, 30 + 8);
|
|
PFAddressPdtIndex = BitFieldRead64 (PFAddress, 21, 21 + 8);
|
|
|
|
Cr4.UintN = AsmReadCr4 ();
|
|
Enable5LevelPaging = (BOOLEAN) (Cr4.Bits.LA57 == 1);
|
|
Pml5 = (UINT64*)(UINTN)(AsmReadCr3 () & gPhyMask);
|
|
|
|
if (!Enable5LevelPaging) {
|
|
//
|
|
// Create one fake PML5 entry for 4-Level Paging
|
|
// so that the page table parsing logic only handles 5-Level page structure.
|
|
//
|
|
Pml5Entry = (UINTN) Pml5 | IA32_PG_P;
|
|
Pml5 = &Pml5Entry;
|
|
}
|
|
|
|
//
|
|
// First, find the leaf entry has the smallest access record value
|
|
//
|
|
for (Pml5Index = 0; Pml5Index < (Enable5LevelPaging ? (EFI_PAGE_SIZE / sizeof (*Pml4)) : 1); Pml5Index++) {
|
|
if ((Pml5[Pml5Index] & IA32_PG_P) == 0 || (Pml5[Pml5Index] & IA32_PG_PMNT) != 0) {
|
|
//
|
|
// If the PML5 entry is not present or is masked, skip it
|
|
//
|
|
continue;
|
|
}
|
|
Pml4 = (UINT64*)(UINTN)(Pml5[Pml5Index] & gPhyMask);
|
|
for (Pml4Index = 0; Pml4Index < EFI_PAGE_SIZE / sizeof (*Pml4); Pml4Index++) {
|
|
if ((Pml4[Pml4Index] & IA32_PG_P) == 0 || (Pml4[Pml4Index] & IA32_PG_PMNT) != 0) {
|
|
//
|
|
// If the PML4 entry is not present or is masked, skip it
|
|
//
|
|
continue;
|
|
}
|
|
Pdpt = (UINT64*)(UINTN)(Pml4[Pml4Index] & ~mAddressEncMask & gPhyMask);
|
|
PML4EIgnore = FALSE;
|
|
for (PdptIndex = 0; PdptIndex < EFI_PAGE_SIZE / sizeof (*Pdpt); PdptIndex++) {
|
|
if ((Pdpt[PdptIndex] & IA32_PG_P) == 0 || (Pdpt[PdptIndex] & IA32_PG_PMNT) != 0) {
|
|
//
|
|
// If the PDPT entry is not present or is masked, skip it
|
|
//
|
|
if ((Pdpt[PdptIndex] & IA32_PG_PMNT) != 0) {
|
|
//
|
|
// If the PDPT entry is masked, we will ignore checking the PML4 entry
|
|
//
|
|
PML4EIgnore = TRUE;
|
|
}
|
|
continue;
|
|
}
|
|
if ((Pdpt[PdptIndex] & IA32_PG_PS) == 0) {
|
|
//
|
|
// It's not 1-GByte pages entry, it should be a PDPT entry,
|
|
// we will not check PML4 entry more
|
|
//
|
|
PML4EIgnore = TRUE;
|
|
Pdt = (UINT64*)(UINTN)(Pdpt[PdptIndex] & ~mAddressEncMask & gPhyMask);
|
|
PDPTEIgnore = FALSE;
|
|
for (PdtIndex = 0; PdtIndex < EFI_PAGE_SIZE / sizeof(*Pdt); PdtIndex++) {
|
|
if ((Pdt[PdtIndex] & IA32_PG_P) == 0 || (Pdt[PdtIndex] & IA32_PG_PMNT) != 0) {
|
|
//
|
|
// If the PD entry is not present or is masked, skip it
|
|
//
|
|
if ((Pdt[PdtIndex] & IA32_PG_PMNT) != 0) {
|
|
//
|
|
// If the PD entry is masked, we will not PDPT entry more
|
|
//
|
|
PDPTEIgnore = TRUE;
|
|
}
|
|
continue;
|
|
}
|
|
if ((Pdt[PdtIndex] & IA32_PG_PS) == 0) {
|
|
//
|
|
// It's not 2 MByte page table entry, it should be PD entry
|
|
// we will find the entry has the smallest access record value
|
|
//
|
|
PDPTEIgnore = TRUE;
|
|
if (PdtIndex != PFAddressPdtIndex || PdptIndex != PFAddressPdptIndex ||
|
|
Pml4Index != PFAddressPml4Index || Pml5Index != PFAddressPml5Index) {
|
|
Acc = GetAndUpdateAccNum (Pdt + PdtIndex);
|
|
if (Acc < MinAcc) {
|
|
//
|
|
// If the PD entry has the smallest access record value,
|
|
// save the Page address to be released
|
|
//
|
|
MinAcc = Acc;
|
|
MinPml5 = Pml5Index;
|
|
MinPml4 = Pml4Index;
|
|
MinPdpt = PdptIndex;
|
|
MinPdt = PdtIndex;
|
|
ReleasePageAddress = Pdt + PdtIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!PDPTEIgnore) {
|
|
//
|
|
// If this PDPT entry has no PDT entries pointer to 4 KByte pages,
|
|
// it should only has the entries point to 2 MByte Pages
|
|
//
|
|
if (PdptIndex != PFAddressPdptIndex || Pml4Index != PFAddressPml4Index ||
|
|
Pml5Index != PFAddressPml5Index) {
|
|
Acc = GetAndUpdateAccNum (Pdpt + PdptIndex);
|
|
if (Acc < MinAcc) {
|
|
//
|
|
// If the PDPT entry has the smallest access record value,
|
|
// save the Page address to be released
|
|
//
|
|
MinAcc = Acc;
|
|
MinPml5 = Pml5Index;
|
|
MinPml4 = Pml4Index;
|
|
MinPdpt = PdptIndex;
|
|
MinPdt = (UINTN)-1;
|
|
ReleasePageAddress = Pdpt + PdptIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!PML4EIgnore) {
|
|
//
|
|
// If PML4 entry has no the PDPT entry pointer to 2 MByte pages,
|
|
// it should only has the entries point to 1 GByte Pages
|
|
//
|
|
if (Pml4Index != PFAddressPml4Index || Pml5Index != PFAddressPml5Index) {
|
|
Acc = GetAndUpdateAccNum (Pml4 + Pml4Index);
|
|
if (Acc < MinAcc) {
|
|
//
|
|
// If the PML4 entry has the smallest access record value,
|
|
// save the Page address to be released
|
|
//
|
|
MinAcc = Acc;
|
|
MinPml5 = Pml5Index;
|
|
MinPml4 = Pml4Index;
|
|
MinPdpt = (UINTN)-1;
|
|
MinPdt = (UINTN)-1;
|
|
ReleasePageAddress = Pml4 + Pml4Index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Make sure one PML4/PDPT/PD entry is selected
|
|
//
|
|
ASSERT (MinAcc != (UINT64)-1);
|
|
|
|
//
|
|
// Secondly, insert the page pointed by this entry into page pool and clear this entry
|
|
//
|
|
InsertTailList (&mPagePool, (LIST_ENTRY*)(UINTN)(*ReleasePageAddress & ~mAddressEncMask & gPhyMask));
|
|
*ReleasePageAddress = 0;
|
|
|
|
//
|
|
// Lastly, check this entry's upper entries if need to be inserted into page pool
|
|
// or not
|
|
//
|
|
while (TRUE) {
|
|
if (MinPdt != (UINTN)-1) {
|
|
//
|
|
// If 4 KByte Page Table is released, check the PDPT entry
|
|
//
|
|
Pml4 = (UINT64 *) (UINTN) (Pml5[MinPml5] & gPhyMask);
|
|
Pdpt = (UINT64*)(UINTN)(Pml4[MinPml4] & ~mAddressEncMask & gPhyMask);
|
|
SubEntriesNum = GetSubEntriesNum(Pdpt + MinPdpt);
|
|
if (SubEntriesNum == 0 &&
|
|
(MinPdpt != PFAddressPdptIndex || MinPml4 != PFAddressPml4Index || MinPml5 != PFAddressPml5Index)) {
|
|
//
|
|
// Release the empty Page Directory table if there was no more 4 KByte Page Table entry
|
|
// clear the Page directory entry
|
|
//
|
|
InsertTailList (&mPagePool, (LIST_ENTRY*)(UINTN)(Pdpt[MinPdpt] & ~mAddressEncMask & gPhyMask));
|
|
Pdpt[MinPdpt] = 0;
|
|
//
|
|
// Go on checking the PML4 table
|
|
//
|
|
MinPdt = (UINTN)-1;
|
|
continue;
|
|
}
|
|
//
|
|
// Update the sub-entries filed in PDPT entry and exit
|
|
//
|
|
SetSubEntriesNum (Pdpt + MinPdpt, (SubEntriesNum - 1) & 0x1FF);
|
|
break;
|
|
}
|
|
if (MinPdpt != (UINTN)-1) {
|
|
//
|
|
// One 2MB Page Table is released or Page Directory table is released, check the PML4 entry
|
|
//
|
|
SubEntriesNum = GetSubEntriesNum (Pml4 + MinPml4);
|
|
if (SubEntriesNum == 0 && (MinPml4 != PFAddressPml4Index || MinPml5 != PFAddressPml5Index)) {
|
|
//
|
|
// Release the empty PML4 table if there was no more 1G KByte Page Table entry
|
|
// clear the Page directory entry
|
|
//
|
|
InsertTailList (&mPagePool, (LIST_ENTRY*)(UINTN)(Pml4[MinPml4] & ~mAddressEncMask & gPhyMask));
|
|
Pml4[MinPml4] = 0;
|
|
MinPdpt = (UINTN)-1;
|
|
continue;
|
|
}
|
|
//
|
|
// Update the sub-entries filed in PML4 entry and exit
|
|
//
|
|
SetSubEntriesNum (Pml4 + MinPml4, (SubEntriesNum - 1) & 0x1FF);
|
|
break;
|
|
}
|
|
//
|
|
// PLM4 table has been released before, exit it
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Allocate free Page for PageFault handler use.
|
|
|
|
@return Page address.
|
|
|
|
**/
|
|
UINT64
|
|
AllocPage (
|
|
VOID
|
|
)
|
|
{
|
|
UINT64 RetVal;
|
|
|
|
if (IsListEmpty (&mPagePool)) {
|
|
//
|
|
// If page pool is empty, reclaim the used pages and insert one into page pool
|
|
//
|
|
ReclaimPages ();
|
|
}
|
|
|
|
//
|
|
// Get one free page and remove it from page pool
|
|
//
|
|
RetVal = (UINT64)(UINTN)mPagePool.ForwardLink;
|
|
RemoveEntryList (mPagePool.ForwardLink);
|
|
//
|
|
// Clean this page and return
|
|
//
|
|
ZeroMem ((VOID*)(UINTN)RetVal, EFI_PAGE_SIZE);
|
|
return RetVal;
|
|
}
|
|
|
|
/**
|
|
Page Fault handler for SMM use.
|
|
|
|
**/
|
|
VOID
|
|
SmiDefaultPFHandler (
|
|
VOID
|
|
)
|
|
{
|
|
UINT64 *PageTable;
|
|
UINT64 *PageTableTop;
|
|
UINT64 PFAddress;
|
|
UINTN StartBit;
|
|
UINTN EndBit;
|
|
UINT64 PTIndex;
|
|
UINTN Index;
|
|
SMM_PAGE_SIZE_TYPE PageSize;
|
|
UINTN NumOfPages;
|
|
UINTN PageAttribute;
|
|
EFI_STATUS Status;
|
|
UINT64 *UpperEntry;
|
|
BOOLEAN Enable5LevelPaging;
|
|
IA32_CR4 Cr4;
|
|
|
|
//
|
|
// Set default SMM page attribute
|
|
//
|
|
PageSize = SmmPageSize2M;
|
|
NumOfPages = 1;
|
|
PageAttribute = 0;
|
|
|
|
EndBit = 0;
|
|
PageTableTop = (UINT64*)(AsmReadCr3 () & gPhyMask);
|
|
PFAddress = AsmReadCr2 ();
|
|
|
|
Cr4.UintN = AsmReadCr4 ();
|
|
Enable5LevelPaging = (BOOLEAN) (Cr4.Bits.LA57 != 0);
|
|
|
|
Status = GetPlatformPageTableAttribute (PFAddress, &PageSize, &NumOfPages, &PageAttribute);
|
|
//
|
|
// If platform not support page table attribute, set default SMM page attribute
|
|
//
|
|
if (Status != EFI_SUCCESS) {
|
|
PageSize = SmmPageSize2M;
|
|
NumOfPages = 1;
|
|
PageAttribute = 0;
|
|
}
|
|
if (PageSize >= MaxSmmPageSizeType) {
|
|
PageSize = SmmPageSize2M;
|
|
}
|
|
if (NumOfPages > 512) {
|
|
NumOfPages = 512;
|
|
}
|
|
|
|
switch (PageSize) {
|
|
case SmmPageSize4K:
|
|
//
|
|
// BIT12 to BIT20 is Page Table index
|
|
//
|
|
EndBit = 12;
|
|
break;
|
|
case SmmPageSize2M:
|
|
//
|
|
// BIT21 to BIT29 is Page Directory index
|
|
//
|
|
EndBit = 21;
|
|
PageAttribute |= (UINTN)IA32_PG_PS;
|
|
break;
|
|
case SmmPageSize1G:
|
|
if (!m1GPageTableSupport) {
|
|
DEBUG ((DEBUG_ERROR, "1-GByte pages is not supported!"));
|
|
ASSERT (FALSE);
|
|
}
|
|
//
|
|
// BIT30 to BIT38 is Page Directory Pointer Table index
|
|
//
|
|
EndBit = 30;
|
|
PageAttribute |= (UINTN)IA32_PG_PS;
|
|
break;
|
|
default:
|
|
ASSERT (FALSE);
|
|
}
|
|
|
|
//
|
|
// If execute-disable is enabled, set NX bit
|
|
//
|
|
if (mXdEnabled) {
|
|
PageAttribute |= IA32_PG_NX;
|
|
}
|
|
|
|
for (Index = 0; Index < NumOfPages; Index++) {
|
|
PageTable = PageTableTop;
|
|
UpperEntry = NULL;
|
|
for (StartBit = Enable5LevelPaging ? 48 : 39; StartBit > EndBit; StartBit -= 9) {
|
|
PTIndex = BitFieldRead64 (PFAddress, StartBit, StartBit + 8);
|
|
if ((PageTable[PTIndex] & IA32_PG_P) == 0) {
|
|
//
|
|
// If the entry is not present, allocate one page from page pool for it
|
|
//
|
|
PageTable[PTIndex] = AllocPage () | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
|
|
} else {
|
|
//
|
|
// Save the upper entry address
|
|
//
|
|
UpperEntry = PageTable + PTIndex;
|
|
}
|
|
//
|
|
// BIT9 to BIT11 of entry is used to save access record,
|
|
// initialize value is 7
|
|
//
|
|
PageTable[PTIndex] |= (UINT64)IA32_PG_A;
|
|
SetAccNum (PageTable + PTIndex, 7);
|
|
PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & ~mAddressEncMask & gPhyMask);
|
|
}
|
|
|
|
PTIndex = BitFieldRead64 (PFAddress, StartBit, StartBit + 8);
|
|
if ((PageTable[PTIndex] & IA32_PG_P) != 0) {
|
|
//
|
|
// Check if the entry has already existed, this issue may occur when the different
|
|
// size page entries created under the same entry
|
|
//
|
|
DEBUG ((DEBUG_ERROR, "PageTable = %lx, PTIndex = %x, PageTable[PTIndex] = %lx\n", PageTable, PTIndex, PageTable[PTIndex]));
|
|
DEBUG ((DEBUG_ERROR, "New page table overlapped with old page table!\n"));
|
|
ASSERT (FALSE);
|
|
}
|
|
//
|
|
// Fill the new entry
|
|
//
|
|
PageTable[PTIndex] = ((PFAddress | mAddressEncMask) & gPhyMask & ~((1ull << EndBit) - 1)) |
|
|
PageAttribute | IA32_PG_A | PAGE_ATTRIBUTE_BITS;
|
|
if (UpperEntry != NULL) {
|
|
SetSubEntriesNum (UpperEntry, (GetSubEntriesNum (UpperEntry) + 1) & 0x1FF);
|
|
}
|
|
//
|
|
// Get the next page address if we need to create more page tables
|
|
//
|
|
PFAddress += (1ull << EndBit);
|
|
}
|
|
}
|
|
|
|
/**
|
|
ThePage Fault handler wrapper for SMM use.
|
|
|
|
@param InterruptType Defines the type of interrupt or exception that
|
|
occurred on the processor.This parameter is processor architecture specific.
|
|
@param SystemContext A pointer to the processor context when
|
|
the interrupt occurred on the processor.
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
SmiPFHandler (
|
|
IN EFI_EXCEPTION_TYPE InterruptType,
|
|
IN EFI_SYSTEM_CONTEXT SystemContext
|
|
)
|
|
{
|
|
UINTN PFAddress;
|
|
UINTN GuardPageAddress;
|
|
UINTN CpuIndex;
|
|
|
|
ASSERT (InterruptType == EXCEPT_IA32_PAGE_FAULT);
|
|
|
|
AcquireSpinLock (mPFLock);
|
|
|
|
PFAddress = AsmReadCr2 ();
|
|
|
|
if (mCpuSmmRestrictedMemoryAccess && (PFAddress >= LShiftU64 (1, (mPhysicalAddressBits - 1)))) {
|
|
DumpCpuContext (InterruptType, SystemContext);
|
|
DEBUG ((DEBUG_ERROR, "Do not support address 0x%lx by processor!\n", PFAddress));
|
|
CpuDeadLoop ();
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// If a page fault occurs in SMRAM range, it might be in a SMM stack guard page,
|
|
// or SMM page protection violation.
|
|
//
|
|
if ((PFAddress >= mCpuHotPlugData.SmrrBase) &&
|
|
(PFAddress < (mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize))) {
|
|
DumpCpuContext (InterruptType, SystemContext);
|
|
CpuIndex = GetCpuIndex ();
|
|
GuardPageAddress = (mSmmStackArrayBase + EFI_PAGE_SIZE + CpuIndex * mSmmStackSize);
|
|
if ((FeaturePcdGet (PcdCpuSmmStackGuard)) &&
|
|
(PFAddress >= GuardPageAddress) &&
|
|
(PFAddress < (GuardPageAddress + EFI_PAGE_SIZE))) {
|
|
DEBUG ((DEBUG_ERROR, "SMM stack overflow!\n"));
|
|
} else {
|
|
if ((SystemContext.SystemContextX64->ExceptionData & IA32_PF_EC_ID) != 0) {
|
|
DEBUG ((DEBUG_ERROR, "SMM exception at execution (0x%lx)\n", PFAddress));
|
|
DEBUG_CODE (
|
|
DumpModuleInfoByIp (*(UINTN *)(UINTN)SystemContext.SystemContextX64->Rsp);
|
|
);
|
|
} else {
|
|
DEBUG ((DEBUG_ERROR, "SMM exception at access (0x%lx)\n", PFAddress));
|
|
DEBUG_CODE (
|
|
DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextX64->Rip);
|
|
);
|
|
}
|
|
|
|
if (HEAP_GUARD_NONSTOP_MODE) {
|
|
GuardPagePFHandler (SystemContext.SystemContextX64->ExceptionData);
|
|
goto Exit;
|
|
}
|
|
}
|
|
CpuDeadLoop ();
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// If a page fault occurs in non-SMRAM range.
|
|
//
|
|
if ((PFAddress < mCpuHotPlugData.SmrrBase) ||
|
|
(PFAddress >= mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize)) {
|
|
if ((SystemContext.SystemContextX64->ExceptionData & IA32_PF_EC_ID) != 0) {
|
|
DumpCpuContext (InterruptType, SystemContext);
|
|
DEBUG ((DEBUG_ERROR, "Code executed on IP(0x%lx) out of SMM range after SMM is locked!\n", PFAddress));
|
|
DEBUG_CODE (
|
|
DumpModuleInfoByIp (*(UINTN *)(UINTN)SystemContext.SystemContextX64->Rsp);
|
|
);
|
|
CpuDeadLoop ();
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// If NULL pointer was just accessed
|
|
//
|
|
if ((PcdGet8 (PcdNullPointerDetectionPropertyMask) & BIT1) != 0 &&
|
|
(PFAddress < EFI_PAGE_SIZE)) {
|
|
DumpCpuContext (InterruptType, SystemContext);
|
|
DEBUG ((DEBUG_ERROR, "!!! NULL pointer access !!!\n"));
|
|
DEBUG_CODE (
|
|
DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextX64->Rip);
|
|
);
|
|
|
|
if (NULL_DETECTION_NONSTOP_MODE) {
|
|
GuardPagePFHandler (SystemContext.SystemContextX64->ExceptionData);
|
|
goto Exit;
|
|
}
|
|
|
|
CpuDeadLoop ();
|
|
goto Exit;
|
|
}
|
|
|
|
if (mCpuSmmRestrictedMemoryAccess && IsSmmCommBufferForbiddenAddress (PFAddress)) {
|
|
DumpCpuContext (InterruptType, SystemContext);
|
|
DEBUG ((DEBUG_ERROR, "Access SMM communication forbidden address (0x%lx)!\n", PFAddress));
|
|
DEBUG_CODE (
|
|
DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextX64->Rip);
|
|
);
|
|
CpuDeadLoop ();
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (FeaturePcdGet (PcdCpuSmmProfileEnable)) {
|
|
SmmProfilePFHandler (
|
|
SystemContext.SystemContextX64->Rip,
|
|
SystemContext.SystemContextX64->ExceptionData
|
|
);
|
|
} else {
|
|
SmiDefaultPFHandler ();
|
|
}
|
|
|
|
Exit:
|
|
ReleaseSpinLock (mPFLock);
|
|
}
|
|
|
|
/**
|
|
This function sets memory attribute for page table.
|
|
**/
|
|
VOID
|
|
SetPageTableAttributes (
|
|
VOID
|
|
)
|
|
{
|
|
UINTN Index2;
|
|
UINTN Index3;
|
|
UINTN Index4;
|
|
UINTN Index5;
|
|
UINT64 *L1PageTable;
|
|
UINT64 *L2PageTable;
|
|
UINT64 *L3PageTable;
|
|
UINT64 *L4PageTable;
|
|
UINT64 *L5PageTable;
|
|
BOOLEAN IsSplitted;
|
|
BOOLEAN PageTableSplitted;
|
|
BOOLEAN CetEnabled;
|
|
IA32_CR4 Cr4;
|
|
BOOLEAN Enable5LevelPaging;
|
|
|
|
Cr4.UintN = AsmReadCr4 ();
|
|
Enable5LevelPaging = (BOOLEAN) (Cr4.Bits.LA57 == 1);
|
|
|
|
//
|
|
// Don't mark page table memory as read-only if
|
|
// - no restriction on access to non-SMRAM memory; or
|
|
// - SMM heap guard feature enabled; or
|
|
// BIT2: SMM page guard enabled
|
|
// BIT3: SMM pool guard enabled
|
|
// - SMM profile feature enabled
|
|
//
|
|
if (!mCpuSmmRestrictedMemoryAccess ||
|
|
((PcdGet8 (PcdHeapGuardPropertyMask) & (BIT3 | BIT2)) != 0) ||
|
|
FeaturePcdGet (PcdCpuSmmProfileEnable)) {
|
|
//
|
|
// Restriction on access to non-SMRAM memory and heap guard could not be enabled at the same time.
|
|
//
|
|
ASSERT (!(mCpuSmmRestrictedMemoryAccess &&
|
|
(PcdGet8 (PcdHeapGuardPropertyMask) & (BIT3 | BIT2)) != 0));
|
|
|
|
//
|
|
// Restriction on access to non-SMRAM memory and SMM profile could not be enabled at the same time.
|
|
//
|
|
ASSERT (!(mCpuSmmRestrictedMemoryAccess && FeaturePcdGet (PcdCpuSmmProfileEnable)));
|
|
return ;
|
|
}
|
|
|
|
DEBUG ((DEBUG_INFO, "SetPageTableAttributes\n"));
|
|
|
|
//
|
|
// Disable write protection, because we need mark page table to be write protected.
|
|
// We need *write* page table memory, to mark itself to be *read only*.
|
|
//
|
|
CetEnabled = ((AsmReadCr4() & CR4_CET_ENABLE) != 0) ? TRUE : FALSE;
|
|
if (CetEnabled) {
|
|
//
|
|
// CET must be disabled if WP is disabled.
|
|
//
|
|
DisableCet();
|
|
}
|
|
AsmWriteCr0 (AsmReadCr0() & ~CR0_WP);
|
|
|
|
do {
|
|
DEBUG ((DEBUG_INFO, "Start...\n"));
|
|
PageTableSplitted = FALSE;
|
|
L5PageTable = NULL;
|
|
if (Enable5LevelPaging) {
|
|
L5PageTable = (UINT64 *)GetPageTableBase ();
|
|
SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L5PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted);
|
|
PageTableSplitted = (PageTableSplitted || IsSplitted);
|
|
}
|
|
|
|
for (Index5 = 0; Index5 < (Enable5LevelPaging ? SIZE_4KB/sizeof(UINT64) : 1); Index5++) {
|
|
if (Enable5LevelPaging) {
|
|
L4PageTable = (UINT64 *)(UINTN)(L5PageTable[Index5] & ~mAddressEncMask & PAGING_4K_ADDRESS_MASK_64);
|
|
if (L4PageTable == NULL) {
|
|
continue;
|
|
}
|
|
} else {
|
|
L4PageTable = (UINT64 *)GetPageTableBase ();
|
|
}
|
|
SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L4PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted);
|
|
PageTableSplitted = (PageTableSplitted || IsSplitted);
|
|
|
|
for (Index4 = 0; Index4 < SIZE_4KB/sizeof(UINT64); Index4++) {
|
|
L3PageTable = (UINT64 *)(UINTN)(L4PageTable[Index4] & ~mAddressEncMask & PAGING_4K_ADDRESS_MASK_64);
|
|
if (L3PageTable == NULL) {
|
|
continue;
|
|
}
|
|
|
|
SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L3PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted);
|
|
PageTableSplitted = (PageTableSplitted || IsSplitted);
|
|
|
|
for (Index3 = 0; Index3 < SIZE_4KB/sizeof(UINT64); Index3++) {
|
|
if ((L3PageTable[Index3] & IA32_PG_PS) != 0) {
|
|
// 1G
|
|
continue;
|
|
}
|
|
L2PageTable = (UINT64 *)(UINTN)(L3PageTable[Index3] & ~mAddressEncMask & PAGING_4K_ADDRESS_MASK_64);
|
|
if (L2PageTable == NULL) {
|
|
continue;
|
|
}
|
|
|
|
SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L2PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted);
|
|
PageTableSplitted = (PageTableSplitted || IsSplitted);
|
|
|
|
for (Index2 = 0; Index2 < SIZE_4KB/sizeof(UINT64); Index2++) {
|
|
if ((L2PageTable[Index2] & IA32_PG_PS) != 0) {
|
|
// 2M
|
|
continue;
|
|
}
|
|
L1PageTable = (UINT64 *)(UINTN)(L2PageTable[Index2] & ~mAddressEncMask & PAGING_4K_ADDRESS_MASK_64);
|
|
if (L1PageTable == NULL) {
|
|
continue;
|
|
}
|
|
SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L1PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted);
|
|
PageTableSplitted = (PageTableSplitted || IsSplitted);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (PageTableSplitted);
|
|
|
|
//
|
|
// Enable write protection, after page table updated.
|
|
//
|
|
AsmWriteCr0 (AsmReadCr0() | CR0_WP);
|
|
if (CetEnabled) {
|
|
//
|
|
// re-enable CET.
|
|
//
|
|
EnableCet();
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
/**
|
|
This function reads CR2 register when on-demand paging is enabled.
|
|
|
|
@param[out] *Cr2 Pointer to variable to hold CR2 register value.
|
|
**/
|
|
VOID
|
|
SaveCr2 (
|
|
OUT UINTN *Cr2
|
|
)
|
|
{
|
|
if (!mCpuSmmRestrictedMemoryAccess) {
|
|
//
|
|
// On-demand paging is enabled when access to non-SMRAM is not restricted.
|
|
//
|
|
*Cr2 = AsmReadCr2 ();
|
|
}
|
|
}
|
|
|
|
/**
|
|
This function restores CR2 register when on-demand paging is enabled.
|
|
|
|
@param[in] Cr2 Value to write into CR2 register.
|
|
**/
|
|
VOID
|
|
RestoreCr2 (
|
|
IN UINTN Cr2
|
|
)
|
|
{
|
|
if (!mCpuSmmRestrictedMemoryAccess) {
|
|
//
|
|
// On-demand paging is enabled when access to non-SMRAM is not restricted.
|
|
//
|
|
AsmWriteCr2 (Cr2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Return whether access to non-SMRAM is restricted.
|
|
|
|
@retval TRUE Access to non-SMRAM is restricted.
|
|
@retval FALSE Access to non-SMRAM is not restricted.
|
|
**/
|
|
BOOLEAN
|
|
IsRestrictedMemoryAccess (
|
|
VOID
|
|
)
|
|
{
|
|
return mCpuSmmRestrictedMemoryAccess;
|
|
}
|