mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-11-27 12:15:19 +01:00
1511 lines
45 KiB
C
1511 lines
45 KiB
C
/** @file
|
|
Enable SMM profile.
|
|
|
|
Copyright (c) 2012 - 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"
|
|
#include "SmmProfileInternal.h"
|
|
|
|
UINT32 mSmmProfileCr3;
|
|
|
|
SMM_PROFILE_HEADER *mSmmProfileBase;
|
|
MSR_DS_AREA_STRUCT *mMsrDsAreaBase;
|
|
//
|
|
// The buffer to store SMM profile data.
|
|
//
|
|
UINTN mSmmProfileSize;
|
|
|
|
//
|
|
// The buffer to enable branch trace store.
|
|
//
|
|
UINTN mMsrDsAreaSize = SMM_PROFILE_DTS_SIZE;
|
|
|
|
//
|
|
// The flag indicates if execute-disable is supported by processor.
|
|
//
|
|
BOOLEAN mXdSupported = TRUE;
|
|
|
|
//
|
|
// The flag indicates if execute-disable is enabled on processor.
|
|
//
|
|
BOOLEAN mXdEnabled = FALSE;
|
|
|
|
//
|
|
// The flag indicates if BTS is supported by processor.
|
|
//
|
|
BOOLEAN mBtsSupported = TRUE;
|
|
|
|
//
|
|
// The flag indicates if SMM profile starts to record data.
|
|
//
|
|
BOOLEAN mSmmProfileStart = FALSE;
|
|
|
|
//
|
|
// The flag indicates if #DB will be setup in #PF handler.
|
|
//
|
|
BOOLEAN mSetupDebugTrap = FALSE;
|
|
|
|
//
|
|
// Record the page fault exception count for one instruction execution.
|
|
//
|
|
UINTN *mPFEntryCount;
|
|
|
|
UINT64 (*mLastPFEntryValue)[MAX_PF_ENTRY_COUNT];
|
|
UINT64 *(*mLastPFEntryPointer)[MAX_PF_ENTRY_COUNT];
|
|
|
|
MSR_DS_AREA_STRUCT **mMsrDsArea;
|
|
BRANCH_TRACE_RECORD **mMsrBTSRecord;
|
|
UINTN mBTSRecordNumber;
|
|
PEBS_RECORD **mMsrPEBSRecord;
|
|
|
|
//
|
|
// These memory ranges are always present, they does not generate the access type of page fault exception,
|
|
// but they possibly generate instruction fetch type of page fault exception.
|
|
//
|
|
MEMORY_PROTECTION_RANGE *mProtectionMemRange = NULL;
|
|
UINTN mProtectionMemRangeCount = 0;
|
|
|
|
//
|
|
// Some predefined memory ranges.
|
|
//
|
|
MEMORY_PROTECTION_RANGE mProtectionMemRangeTemplate[] = {
|
|
//
|
|
// SMRAM range (to be fixed in runtime).
|
|
// It is always present and instruction fetches are allowed.
|
|
//
|
|
{{0x00000000, 0x00000000},TRUE,FALSE},
|
|
|
|
//
|
|
// SMM profile data range( to be fixed in runtime).
|
|
// It is always present and instruction fetches are not allowed.
|
|
//
|
|
{{0x00000000, 0x00000000},TRUE,TRUE},
|
|
|
|
//
|
|
// SMRAM ranges not covered by mCpuHotPlugData.SmrrBase/mCpuHotPlugData.SmrrSiz (to be fixed in runtime).
|
|
// It is always present and instruction fetches are allowed.
|
|
// {{0x00000000, 0x00000000},TRUE,FALSE},
|
|
//
|
|
|
|
//
|
|
// Future extended range could be added here.
|
|
//
|
|
|
|
//
|
|
// PCI MMIO ranges (to be added in runtime).
|
|
// They are always present and instruction fetches are not allowed.
|
|
//
|
|
};
|
|
|
|
//
|
|
// These memory ranges are mapped by 4KB-page instead of 2MB-page.
|
|
//
|
|
MEMORY_RANGE *mSplitMemRange = NULL;
|
|
UINTN mSplitMemRangeCount = 0;
|
|
|
|
//
|
|
// SMI command port.
|
|
//
|
|
UINT32 mSmiCommandPort;
|
|
|
|
/**
|
|
Disable branch trace store.
|
|
|
|
**/
|
|
VOID
|
|
DisableBTS (
|
|
VOID
|
|
)
|
|
{
|
|
AsmMsrAnd64 (MSR_DEBUG_CTL, ~((UINT64)(MSR_DEBUG_CTL_BTS | MSR_DEBUG_CTL_TR)));
|
|
}
|
|
|
|
/**
|
|
Enable branch trace store.
|
|
|
|
**/
|
|
VOID
|
|
EnableBTS (
|
|
VOID
|
|
)
|
|
{
|
|
AsmMsrOr64 (MSR_DEBUG_CTL, (MSR_DEBUG_CTL_BTS | MSR_DEBUG_CTL_TR));
|
|
}
|
|
|
|
/**
|
|
Get CPU Index from APIC ID.
|
|
|
|
**/
|
|
UINTN
|
|
GetCpuIndex (
|
|
VOID
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINT32 ApicId;
|
|
|
|
ApicId = GetApicId ();
|
|
|
|
for (Index = 0; Index < mMaxNumberOfCpus; Index++) {
|
|
if (gSmmCpuPrivate->ProcessorInfo[Index].ProcessorId == ApicId) {
|
|
return Index;
|
|
}
|
|
}
|
|
ASSERT (FALSE);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Get the source of IP after execute-disable exception is triggered.
|
|
|
|
@param CpuIndex The index of CPU.
|
|
@param DestinationIP The destination address.
|
|
|
|
**/
|
|
UINT64
|
|
GetSourceFromDestinationOnBts (
|
|
UINTN CpuIndex,
|
|
UINT64 DestinationIP
|
|
)
|
|
{
|
|
BRANCH_TRACE_RECORD *CurrentBTSRecord;
|
|
UINTN Index;
|
|
BOOLEAN FirstMatch;
|
|
|
|
FirstMatch = FALSE;
|
|
|
|
CurrentBTSRecord = (BRANCH_TRACE_RECORD *)mMsrDsArea[CpuIndex]->BTSIndex;
|
|
for (Index = 0; Index < mBTSRecordNumber; Index++) {
|
|
if ((UINTN)CurrentBTSRecord < (UINTN)mMsrBTSRecord[CpuIndex]) {
|
|
//
|
|
// Underflow
|
|
//
|
|
CurrentBTSRecord = (BRANCH_TRACE_RECORD *)((UINTN)mMsrDsArea[CpuIndex]->BTSAbsoluteMaximum - 1);
|
|
CurrentBTSRecord --;
|
|
}
|
|
if (CurrentBTSRecord->LastBranchTo == DestinationIP) {
|
|
//
|
|
// Good! find 1st one, then find 2nd one.
|
|
//
|
|
if (!FirstMatch) {
|
|
//
|
|
// The first one is DEBUG exception
|
|
//
|
|
FirstMatch = TRUE;
|
|
} else {
|
|
//
|
|
// Good find proper one.
|
|
//
|
|
return CurrentBTSRecord->LastBranchFrom;
|
|
}
|
|
}
|
|
CurrentBTSRecord--;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
SMM profile specific INT 1 (single-step) exception handler.
|
|
|
|
@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
|
|
DebugExceptionHandler (
|
|
IN EFI_EXCEPTION_TYPE InterruptType,
|
|
IN EFI_SYSTEM_CONTEXT SystemContext
|
|
)
|
|
{
|
|
UINTN CpuIndex;
|
|
UINTN PFEntry;
|
|
|
|
if (!mSmmProfileStart &&
|
|
!HEAP_GUARD_NONSTOP_MODE &&
|
|
!NULL_DETECTION_NONSTOP_MODE) {
|
|
return;
|
|
}
|
|
CpuIndex = GetCpuIndex ();
|
|
|
|
//
|
|
// Clear last PF entries
|
|
//
|
|
for (PFEntry = 0; PFEntry < mPFEntryCount[CpuIndex]; PFEntry++) {
|
|
*mLastPFEntryPointer[CpuIndex][PFEntry] = mLastPFEntryValue[CpuIndex][PFEntry];
|
|
}
|
|
|
|
//
|
|
// Reset page fault exception count for next page fault.
|
|
//
|
|
mPFEntryCount[CpuIndex] = 0;
|
|
|
|
//
|
|
// Flush TLB
|
|
//
|
|
CpuFlushTlb ();
|
|
|
|
//
|
|
// Clear TF in EFLAGS
|
|
//
|
|
ClearTrapFlag (SystemContext);
|
|
}
|
|
|
|
/**
|
|
Check if the input address is in SMM ranges.
|
|
|
|
@param[in] Address The input address.
|
|
|
|
@retval TRUE The input address is in SMM.
|
|
@retval FALSE The input address is not in SMM.
|
|
**/
|
|
BOOLEAN
|
|
IsInSmmRanges (
|
|
IN EFI_PHYSICAL_ADDRESS Address
|
|
)
|
|
{
|
|
UINTN Index;
|
|
|
|
if ((Address >= mCpuHotPlugData.SmrrBase) && (Address < mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize)) {
|
|
return TRUE;
|
|
}
|
|
for (Index = 0; Index < mSmmCpuSmramRangeCount; Index++) {
|
|
if (Address >= mSmmCpuSmramRanges[Index].CpuStart &&
|
|
Address < mSmmCpuSmramRanges[Index].CpuStart + mSmmCpuSmramRanges[Index].PhysicalSize) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
Check if the memory address will be mapped by 4KB-page.
|
|
|
|
@param Address The address of Memory.
|
|
@param Nx The flag indicates if the memory is execute-disable.
|
|
|
|
**/
|
|
BOOLEAN
|
|
IsAddressValid (
|
|
IN EFI_PHYSICAL_ADDRESS Address,
|
|
IN BOOLEAN *Nx
|
|
)
|
|
{
|
|
UINTN Index;
|
|
|
|
if (FeaturePcdGet (PcdCpuSmmProfileEnable)) {
|
|
//
|
|
// Check configuration
|
|
//
|
|
for (Index = 0; Index < mProtectionMemRangeCount; Index++) {
|
|
if ((Address >= mProtectionMemRange[Index].Range.Base) && (Address < mProtectionMemRange[Index].Range.Top)) {
|
|
*Nx = mProtectionMemRange[Index].Nx;
|
|
return mProtectionMemRange[Index].Present;
|
|
}
|
|
}
|
|
*Nx = TRUE;
|
|
return FALSE;
|
|
|
|
} else {
|
|
*Nx = TRUE;
|
|
if (IsInSmmRanges (Address)) {
|
|
*Nx = FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Check if the memory address will be mapped by 4KB-page.
|
|
|
|
@param Address The address of Memory.
|
|
|
|
**/
|
|
BOOLEAN
|
|
IsAddressSplit (
|
|
IN EFI_PHYSICAL_ADDRESS Address
|
|
)
|
|
{
|
|
UINTN Index;
|
|
|
|
if (FeaturePcdGet (PcdCpuSmmProfileEnable)) {
|
|
//
|
|
// Check configuration
|
|
//
|
|
for (Index = 0; Index < mSplitMemRangeCount; Index++) {
|
|
if ((Address >= mSplitMemRange[Index].Base) && (Address < mSplitMemRange[Index].Top)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
if (Address < mCpuHotPlugData.SmrrBase) {
|
|
if ((mCpuHotPlugData.SmrrBase - Address) < BASE_2MB) {
|
|
return TRUE;
|
|
}
|
|
} else if (Address > (mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize - BASE_2MB)) {
|
|
if ((Address - (mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize - BASE_2MB)) < BASE_2MB) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Return default
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
Initialize the protected memory ranges and the 4KB-page mapped memory ranges.
|
|
|
|
**/
|
|
VOID
|
|
InitProtectedMemRange (
|
|
VOID
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN NumberOfDescriptors;
|
|
UINTN NumberOfAddedDescriptors;
|
|
UINTN NumberOfProtectRange;
|
|
UINTN NumberOfSpliteRange;
|
|
EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap;
|
|
UINTN TotalSize;
|
|
EFI_PHYSICAL_ADDRESS ProtectBaseAddress;
|
|
EFI_PHYSICAL_ADDRESS ProtectEndAddress;
|
|
EFI_PHYSICAL_ADDRESS Top2MBAlignedAddress;
|
|
EFI_PHYSICAL_ADDRESS Base2MBAlignedAddress;
|
|
UINT64 High4KBPageSize;
|
|
UINT64 Low4KBPageSize;
|
|
|
|
NumberOfDescriptors = 0;
|
|
NumberOfAddedDescriptors = mSmmCpuSmramRangeCount;
|
|
NumberOfSpliteRange = 0;
|
|
MemorySpaceMap = NULL;
|
|
|
|
//
|
|
// Get MMIO ranges from GCD and add them into protected memory ranges.
|
|
//
|
|
gDS->GetMemorySpaceMap (
|
|
&NumberOfDescriptors,
|
|
&MemorySpaceMap
|
|
);
|
|
for (Index = 0; Index < NumberOfDescriptors; Index++) {
|
|
if (MemorySpaceMap[Index].GcdMemoryType == EfiGcdMemoryTypeMemoryMappedIo) {
|
|
NumberOfAddedDescriptors++;
|
|
}
|
|
}
|
|
|
|
if (NumberOfAddedDescriptors != 0) {
|
|
TotalSize = NumberOfAddedDescriptors * sizeof (MEMORY_PROTECTION_RANGE) + sizeof (mProtectionMemRangeTemplate);
|
|
mProtectionMemRange = (MEMORY_PROTECTION_RANGE *) AllocateZeroPool (TotalSize);
|
|
ASSERT (mProtectionMemRange != NULL);
|
|
mProtectionMemRangeCount = TotalSize / sizeof (MEMORY_PROTECTION_RANGE);
|
|
|
|
//
|
|
// Copy existing ranges.
|
|
//
|
|
CopyMem (mProtectionMemRange, mProtectionMemRangeTemplate, sizeof (mProtectionMemRangeTemplate));
|
|
|
|
//
|
|
// Create split ranges which come from protected ranges.
|
|
//
|
|
TotalSize = (TotalSize / sizeof (MEMORY_PROTECTION_RANGE)) * sizeof (MEMORY_RANGE);
|
|
mSplitMemRange = (MEMORY_RANGE *) AllocateZeroPool (TotalSize);
|
|
ASSERT (mSplitMemRange != NULL);
|
|
|
|
//
|
|
// Create SMM ranges which are set to present and execution-enable.
|
|
//
|
|
NumberOfProtectRange = sizeof (mProtectionMemRangeTemplate) / sizeof (MEMORY_PROTECTION_RANGE);
|
|
for (Index = 0; Index < mSmmCpuSmramRangeCount; Index++) {
|
|
if (mSmmCpuSmramRanges[Index].CpuStart >= mProtectionMemRange[0].Range.Base &&
|
|
mSmmCpuSmramRanges[Index].CpuStart + mSmmCpuSmramRanges[Index].PhysicalSize < mProtectionMemRange[0].Range.Top) {
|
|
//
|
|
// If the address have been already covered by mCpuHotPlugData.SmrrBase/mCpuHotPlugData.SmrrSiz
|
|
//
|
|
break;
|
|
}
|
|
mProtectionMemRange[NumberOfProtectRange].Range.Base = mSmmCpuSmramRanges[Index].CpuStart;
|
|
mProtectionMemRange[NumberOfProtectRange].Range.Top = mSmmCpuSmramRanges[Index].CpuStart + mSmmCpuSmramRanges[Index].PhysicalSize;
|
|
mProtectionMemRange[NumberOfProtectRange].Present = TRUE;
|
|
mProtectionMemRange[NumberOfProtectRange].Nx = FALSE;
|
|
NumberOfProtectRange++;
|
|
}
|
|
|
|
//
|
|
// Create MMIO ranges which are set to present and execution-disable.
|
|
//
|
|
for (Index = 0; Index < NumberOfDescriptors; Index++) {
|
|
if (MemorySpaceMap[Index].GcdMemoryType != EfiGcdMemoryTypeMemoryMappedIo) {
|
|
continue;
|
|
}
|
|
mProtectionMemRange[NumberOfProtectRange].Range.Base = MemorySpaceMap[Index].BaseAddress;
|
|
mProtectionMemRange[NumberOfProtectRange].Range.Top = MemorySpaceMap[Index].BaseAddress + MemorySpaceMap[Index].Length;
|
|
mProtectionMemRange[NumberOfProtectRange].Present = TRUE;
|
|
mProtectionMemRange[NumberOfProtectRange].Nx = TRUE;
|
|
NumberOfProtectRange++;
|
|
}
|
|
|
|
//
|
|
// Check and updated actual protected memory ranges count
|
|
//
|
|
ASSERT (NumberOfProtectRange <= mProtectionMemRangeCount);
|
|
mProtectionMemRangeCount = NumberOfProtectRange;
|
|
}
|
|
|
|
//
|
|
// According to protected ranges, create the ranges which will be mapped by 2KB page.
|
|
//
|
|
NumberOfSpliteRange = 0;
|
|
NumberOfProtectRange = mProtectionMemRangeCount;
|
|
for (Index = 0; Index < NumberOfProtectRange; Index++) {
|
|
//
|
|
// If MMIO base address is not 2MB alignment, make 2MB alignment for create 4KB page in page table.
|
|
//
|
|
ProtectBaseAddress = mProtectionMemRange[Index].Range.Base;
|
|
ProtectEndAddress = mProtectionMemRange[Index].Range.Top;
|
|
if (((ProtectBaseAddress & (SIZE_2MB - 1)) != 0) || ((ProtectEndAddress & (SIZE_2MB - 1)) != 0)) {
|
|
//
|
|
// Check if it is possible to create 4KB-page for not 2MB-aligned range and to create 2MB-page for 2MB-aligned range.
|
|
// A mix of 4KB and 2MB page could save SMRAM space.
|
|
//
|
|
Top2MBAlignedAddress = ProtectEndAddress & ~(SIZE_2MB - 1);
|
|
Base2MBAlignedAddress = (ProtectBaseAddress + SIZE_2MB - 1) & ~(SIZE_2MB - 1);
|
|
if ((Top2MBAlignedAddress > Base2MBAlignedAddress) &&
|
|
((Top2MBAlignedAddress - Base2MBAlignedAddress) >= SIZE_2MB)) {
|
|
//
|
|
// There is an range which could be mapped by 2MB-page.
|
|
//
|
|
High4KBPageSize = ((ProtectEndAddress + SIZE_2MB - 1) & ~(SIZE_2MB - 1)) - (ProtectEndAddress & ~(SIZE_2MB - 1));
|
|
Low4KBPageSize = ((ProtectBaseAddress + SIZE_2MB - 1) & ~(SIZE_2MB - 1)) - (ProtectBaseAddress & ~(SIZE_2MB - 1));
|
|
if (High4KBPageSize != 0) {
|
|
//
|
|
// Add not 2MB-aligned range to be mapped by 4KB-page.
|
|
//
|
|
mSplitMemRange[NumberOfSpliteRange].Base = ProtectEndAddress & ~(SIZE_2MB - 1);
|
|
mSplitMemRange[NumberOfSpliteRange].Top = (ProtectEndAddress + SIZE_2MB - 1) & ~(SIZE_2MB - 1);
|
|
NumberOfSpliteRange++;
|
|
}
|
|
if (Low4KBPageSize != 0) {
|
|
//
|
|
// Add not 2MB-aligned range to be mapped by 4KB-page.
|
|
//
|
|
mSplitMemRange[NumberOfSpliteRange].Base = ProtectBaseAddress & ~(SIZE_2MB - 1);
|
|
mSplitMemRange[NumberOfSpliteRange].Top = (ProtectBaseAddress + SIZE_2MB - 1) & ~(SIZE_2MB - 1);
|
|
NumberOfSpliteRange++;
|
|
}
|
|
} else {
|
|
//
|
|
// The range could only be mapped by 4KB-page.
|
|
//
|
|
mSplitMemRange[NumberOfSpliteRange].Base = ProtectBaseAddress & ~(SIZE_2MB - 1);
|
|
mSplitMemRange[NumberOfSpliteRange].Top = (ProtectEndAddress + SIZE_2MB - 1) & ~(SIZE_2MB - 1);
|
|
NumberOfSpliteRange++;
|
|
}
|
|
}
|
|
}
|
|
|
|
mSplitMemRangeCount = NumberOfSpliteRange;
|
|
|
|
DEBUG ((EFI_D_INFO, "SMM Profile Memory Ranges:\n"));
|
|
for (Index = 0; Index < mProtectionMemRangeCount; Index++) {
|
|
DEBUG ((EFI_D_INFO, "mProtectionMemRange[%d].Base = %lx\n", Index, mProtectionMemRange[Index].Range.Base));
|
|
DEBUG ((EFI_D_INFO, "mProtectionMemRange[%d].Top = %lx\n", Index, mProtectionMemRange[Index].Range.Top));
|
|
}
|
|
for (Index = 0; Index < mSplitMemRangeCount; Index++) {
|
|
DEBUG ((EFI_D_INFO, "mSplitMemRange[%d].Base = %lx\n", Index, mSplitMemRange[Index].Base));
|
|
DEBUG ((EFI_D_INFO, "mSplitMemRange[%d].Top = %lx\n", Index, mSplitMemRange[Index].Top));
|
|
}
|
|
}
|
|
|
|
/**
|
|
Update page table according to protected memory ranges and the 4KB-page mapped memory ranges.
|
|
|
|
**/
|
|
VOID
|
|
InitPaging (
|
|
VOID
|
|
)
|
|
{
|
|
UINT64 Pml5Entry;
|
|
UINT64 Pml4Entry;
|
|
UINT64 *Pml5;
|
|
UINT64 *Pml4;
|
|
UINT64 *Pdpt;
|
|
UINT64 *Pd;
|
|
UINT64 *Pt;
|
|
UINTN Address;
|
|
UINTN Pml5Index;
|
|
UINTN Pml4Index;
|
|
UINTN PdptIndex;
|
|
UINTN PdIndex;
|
|
UINTN PtIndex;
|
|
UINTN NumberOfPdptEntries;
|
|
UINTN NumberOfPml4Entries;
|
|
UINTN NumberOfPml5Entries;
|
|
UINTN SizeOfMemorySpace;
|
|
BOOLEAN Nx;
|
|
IA32_CR4 Cr4;
|
|
BOOLEAN Enable5LevelPaging;
|
|
|
|
Cr4.UintN = AsmReadCr4 ();
|
|
Enable5LevelPaging = (BOOLEAN) (Cr4.Bits.LA57 == 1);
|
|
|
|
if (sizeof (UINTN) == sizeof (UINT64)) {
|
|
if (!Enable5LevelPaging) {
|
|
Pml5Entry = (UINTN) mSmmProfileCr3 | IA32_PG_P;
|
|
Pml5 = &Pml5Entry;
|
|
} else {
|
|
Pml5 = (UINT64*) (UINTN) mSmmProfileCr3;
|
|
}
|
|
SizeOfMemorySpace = HighBitSet64 (gPhyMask) + 1;
|
|
//
|
|
// Calculate the table entries of PML4E and PDPTE.
|
|
//
|
|
NumberOfPml5Entries = 1;
|
|
if (SizeOfMemorySpace > 48) {
|
|
NumberOfPml5Entries = (UINTN) LShiftU64 (1, SizeOfMemorySpace - 48);
|
|
SizeOfMemorySpace = 48;
|
|
}
|
|
|
|
NumberOfPml4Entries = 1;
|
|
if (SizeOfMemorySpace > 39) {
|
|
NumberOfPml4Entries = (UINTN) LShiftU64 (1, SizeOfMemorySpace - 39);
|
|
SizeOfMemorySpace = 39;
|
|
}
|
|
|
|
NumberOfPdptEntries = 1;
|
|
ASSERT (SizeOfMemorySpace > 30);
|
|
NumberOfPdptEntries = (UINTN) LShiftU64 (1, SizeOfMemorySpace - 30);
|
|
} else {
|
|
Pml4Entry = (UINTN) mSmmProfileCr3 | IA32_PG_P;
|
|
Pml4 = &Pml4Entry;
|
|
Pml5Entry = (UINTN) Pml4 | IA32_PG_P;
|
|
Pml5 = &Pml5Entry;
|
|
NumberOfPml5Entries = 1;
|
|
NumberOfPml4Entries = 1;
|
|
NumberOfPdptEntries = 4;
|
|
}
|
|
|
|
//
|
|
// Go through page table and change 2MB-page into 4KB-page.
|
|
//
|
|
for (Pml5Index = 0; Pml5Index < NumberOfPml5Entries; Pml5Index++) {
|
|
if ((Pml5[Pml5Index] & IA32_PG_P) == 0) {
|
|
//
|
|
// If PML5 entry does not exist, skip it
|
|
//
|
|
continue;
|
|
}
|
|
Pml4 = (UINT64 *) (UINTN) (Pml5[Pml5Index] & PHYSICAL_ADDRESS_MASK);
|
|
for (Pml4Index = 0; Pml4Index < NumberOfPml4Entries; Pml4Index++) {
|
|
if ((Pml4[Pml4Index] & IA32_PG_P) == 0) {
|
|
//
|
|
// If PML4 entry does not exist, skip it
|
|
//
|
|
continue;
|
|
}
|
|
Pdpt = (UINT64 *)(UINTN)(Pml4[Pml4Index] & ~mAddressEncMask & PHYSICAL_ADDRESS_MASK);
|
|
for (PdptIndex = 0; PdptIndex < NumberOfPdptEntries; PdptIndex++, Pdpt++) {
|
|
if ((*Pdpt & IA32_PG_P) == 0) {
|
|
//
|
|
// If PDPT entry does not exist, skip it
|
|
//
|
|
continue;
|
|
}
|
|
if ((*Pdpt & IA32_PG_PS) != 0) {
|
|
//
|
|
// This is 1G entry, skip it
|
|
//
|
|
continue;
|
|
}
|
|
Pd = (UINT64 *)(UINTN)(*Pdpt & ~mAddressEncMask & PHYSICAL_ADDRESS_MASK);
|
|
if (Pd == 0) {
|
|
continue;
|
|
}
|
|
for (PdIndex = 0; PdIndex < SIZE_4KB / sizeof (*Pd); PdIndex++, Pd++) {
|
|
if ((*Pd & IA32_PG_P) == 0) {
|
|
//
|
|
// If PD entry does not exist, skip it
|
|
//
|
|
continue;
|
|
}
|
|
Address = (UINTN) LShiftU64 (
|
|
LShiftU64 (
|
|
LShiftU64 ((Pml5Index << 9) + Pml4Index, 9) + PdptIndex,
|
|
9
|
|
) + PdIndex,
|
|
21
|
|
);
|
|
|
|
//
|
|
// If it is 2M page, check IsAddressSplit()
|
|
//
|
|
if (((*Pd & IA32_PG_PS) != 0) && IsAddressSplit (Address)) {
|
|
//
|
|
// Based on current page table, create 4KB page table for split area.
|
|
//
|
|
ASSERT (Address == (*Pd & PHYSICAL_ADDRESS_MASK));
|
|
|
|
Pt = AllocatePageTableMemory (1);
|
|
ASSERT (Pt != NULL);
|
|
|
|
// Split it
|
|
for (PtIndex = 0; PtIndex < SIZE_4KB / sizeof(*Pt); PtIndex++) {
|
|
Pt[PtIndex] = Address + ((PtIndex << 12) | mAddressEncMask | PAGE_ATTRIBUTE_BITS);
|
|
} // end for PT
|
|
*Pd = (UINT64)(UINTN)Pt | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
|
|
} // end if IsAddressSplit
|
|
} // end for PD
|
|
} // end for PDPT
|
|
} // end for PML4
|
|
} // end for PML5
|
|
|
|
//
|
|
// Go through page table and set several page table entries to absent or execute-disable.
|
|
//
|
|
DEBUG ((EFI_D_INFO, "Patch page table start ...\n"));
|
|
for (Pml5Index = 0; Pml5Index < NumberOfPml5Entries; Pml5Index++) {
|
|
if ((Pml5[Pml5Index] & IA32_PG_P) == 0) {
|
|
//
|
|
// If PML5 entry does not exist, skip it
|
|
//
|
|
continue;
|
|
}
|
|
Pml4 = (UINT64 *) (UINTN) (Pml5[Pml5Index] & PHYSICAL_ADDRESS_MASK);
|
|
for (Pml4Index = 0; Pml4Index < NumberOfPml4Entries; Pml4Index++) {
|
|
if ((Pml4[Pml4Index] & IA32_PG_P) == 0) {
|
|
//
|
|
// If PML4 entry does not exist, skip it
|
|
//
|
|
continue;
|
|
}
|
|
Pdpt = (UINT64 *)(UINTN)(Pml4[Pml4Index] & ~mAddressEncMask & PHYSICAL_ADDRESS_MASK);
|
|
for (PdptIndex = 0; PdptIndex < NumberOfPdptEntries; PdptIndex++, Pdpt++) {
|
|
if ((*Pdpt & IA32_PG_P) == 0) {
|
|
//
|
|
// If PDPT entry does not exist, skip it
|
|
//
|
|
continue;
|
|
}
|
|
if ((*Pdpt & IA32_PG_PS) != 0) {
|
|
//
|
|
// This is 1G entry, set NX bit and skip it
|
|
//
|
|
if (mXdSupported) {
|
|
*Pdpt = *Pdpt | IA32_PG_NX;
|
|
}
|
|
continue;
|
|
}
|
|
Pd = (UINT64 *)(UINTN)(*Pdpt & ~mAddressEncMask & PHYSICAL_ADDRESS_MASK);
|
|
if (Pd == 0) {
|
|
continue;
|
|
}
|
|
for (PdIndex = 0; PdIndex < SIZE_4KB / sizeof (*Pd); PdIndex++, Pd++) {
|
|
if ((*Pd & IA32_PG_P) == 0) {
|
|
//
|
|
// If PD entry does not exist, skip it
|
|
//
|
|
continue;
|
|
}
|
|
Address = (UINTN) LShiftU64 (
|
|
LShiftU64 (
|
|
LShiftU64 ((Pml5Index << 9) + Pml4Index, 9) + PdptIndex,
|
|
9
|
|
) + PdIndex,
|
|
21
|
|
);
|
|
|
|
if ((*Pd & IA32_PG_PS) != 0) {
|
|
// 2MB page
|
|
|
|
if (!IsAddressValid (Address, &Nx)) {
|
|
//
|
|
// Patch to remove Present flag and RW flag
|
|
//
|
|
*Pd = *Pd & (INTN)(INT32)(~PAGE_ATTRIBUTE_BITS);
|
|
}
|
|
if (Nx && mXdSupported) {
|
|
*Pd = *Pd | IA32_PG_NX;
|
|
}
|
|
} else {
|
|
// 4KB page
|
|
Pt = (UINT64 *)(UINTN)(*Pd & ~mAddressEncMask & PHYSICAL_ADDRESS_MASK);
|
|
if (Pt == 0) {
|
|
continue;
|
|
}
|
|
for (PtIndex = 0; PtIndex < SIZE_4KB / sizeof(*Pt); PtIndex++, Pt++) {
|
|
if (!IsAddressValid (Address, &Nx)) {
|
|
*Pt = *Pt & (INTN)(INT32)(~PAGE_ATTRIBUTE_BITS);
|
|
}
|
|
if (Nx && mXdSupported) {
|
|
*Pt = *Pt | IA32_PG_NX;
|
|
}
|
|
Address += SIZE_4KB;
|
|
} // end for PT
|
|
} // end if PS
|
|
} // end for PD
|
|
} // end for PDPT
|
|
} // end for PML4
|
|
} // end for PML5
|
|
|
|
//
|
|
// Flush TLB
|
|
//
|
|
CpuFlushTlb ();
|
|
DEBUG ((EFI_D_INFO, "Patch page table done!\n"));
|
|
//
|
|
// Set execute-disable flag
|
|
//
|
|
mXdEnabled = TRUE;
|
|
|
|
return ;
|
|
}
|
|
|
|
/**
|
|
To get system port address of the SMI Command Port in FADT table.
|
|
|
|
**/
|
|
VOID
|
|
GetSmiCommandPort (
|
|
VOID
|
|
)
|
|
{
|
|
EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *Fadt;
|
|
|
|
Fadt = (EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *) EfiLocateFirstAcpiTable (
|
|
EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE
|
|
);
|
|
ASSERT (Fadt != NULL);
|
|
|
|
mSmiCommandPort = Fadt->SmiCmd;
|
|
DEBUG ((EFI_D_INFO, "mSmiCommandPort = %x\n", mSmiCommandPort));
|
|
}
|
|
|
|
/**
|
|
Updates page table to make some memory ranges (like system memory) absent
|
|
and make some memory ranges (like MMIO) present and execute disable. It also
|
|
update 2MB-page to 4KB-page for some memory ranges.
|
|
|
|
**/
|
|
VOID
|
|
SmmProfileStart (
|
|
VOID
|
|
)
|
|
{
|
|
//
|
|
// The flag indicates SMM profile starts to work.
|
|
//
|
|
mSmmProfileStart = TRUE;
|
|
}
|
|
|
|
/**
|
|
Initialize SMM profile in SmmReadyToLock protocol callback function.
|
|
|
|
@param Protocol Points to the protocol's unique identifier.
|
|
@param Interface Points to the interface instance.
|
|
@param Handle The handle on which the interface was installed.
|
|
|
|
@retval EFI_SUCCESS SmmReadyToLock protocol callback runs successfully.
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
InitSmmProfileCallBack (
|
|
IN CONST EFI_GUID *Protocol,
|
|
IN VOID *Interface,
|
|
IN EFI_HANDLE Handle
|
|
)
|
|
{
|
|
//
|
|
// Save to variable so that SMM profile data can be found.
|
|
//
|
|
gRT->SetVariable (
|
|
SMM_PROFILE_NAME,
|
|
&gEfiCallerIdGuid,
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
|
|
sizeof(mSmmProfileBase),
|
|
&mSmmProfileBase
|
|
);
|
|
|
|
//
|
|
// Get Software SMI from FADT
|
|
//
|
|
GetSmiCommandPort ();
|
|
|
|
//
|
|
// Initialize protected memory range for patching page table later.
|
|
//
|
|
InitProtectedMemRange ();
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Initialize SMM profile data structures.
|
|
|
|
**/
|
|
VOID
|
|
InitSmmProfileInternal (
|
|
VOID
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_PHYSICAL_ADDRESS Base;
|
|
VOID *Registration;
|
|
UINTN Index;
|
|
UINTN MsrDsAreaSizePerCpu;
|
|
UINTN TotalSize;
|
|
|
|
mPFEntryCount = (UINTN *)AllocateZeroPool (sizeof (UINTN) * mMaxNumberOfCpus);
|
|
ASSERT (mPFEntryCount != NULL);
|
|
mLastPFEntryValue = (UINT64 (*)[MAX_PF_ENTRY_COUNT])AllocateZeroPool (
|
|
sizeof (mLastPFEntryValue[0]) * mMaxNumberOfCpus);
|
|
ASSERT (mLastPFEntryValue != NULL);
|
|
mLastPFEntryPointer = (UINT64 *(*)[MAX_PF_ENTRY_COUNT])AllocateZeroPool (
|
|
sizeof (mLastPFEntryPointer[0]) * mMaxNumberOfCpus);
|
|
ASSERT (mLastPFEntryPointer != NULL);
|
|
|
|
//
|
|
// Allocate memory for SmmProfile below 4GB.
|
|
// The base address
|
|
//
|
|
mSmmProfileSize = PcdGet32 (PcdCpuSmmProfileSize);
|
|
ASSERT ((mSmmProfileSize & 0xFFF) == 0);
|
|
|
|
if (mBtsSupported) {
|
|
TotalSize = mSmmProfileSize + mMsrDsAreaSize;
|
|
} else {
|
|
TotalSize = mSmmProfileSize;
|
|
}
|
|
|
|
Base = 0xFFFFFFFF;
|
|
Status = gBS->AllocatePages (
|
|
AllocateMaxAddress,
|
|
EfiReservedMemoryType,
|
|
EFI_SIZE_TO_PAGES (TotalSize),
|
|
&Base
|
|
);
|
|
ASSERT_EFI_ERROR (Status);
|
|
ZeroMem ((VOID *)(UINTN)Base, TotalSize);
|
|
mSmmProfileBase = (SMM_PROFILE_HEADER *)(UINTN)Base;
|
|
|
|
//
|
|
// Initialize SMM profile data header.
|
|
//
|
|
mSmmProfileBase->HeaderSize = sizeof (SMM_PROFILE_HEADER);
|
|
mSmmProfileBase->MaxDataEntries = (UINT64)((mSmmProfileSize - sizeof(SMM_PROFILE_HEADER)) / sizeof (SMM_PROFILE_ENTRY));
|
|
mSmmProfileBase->MaxDataSize = MultU64x64 (mSmmProfileBase->MaxDataEntries, sizeof(SMM_PROFILE_ENTRY));
|
|
mSmmProfileBase->CurDataEntries = 0;
|
|
mSmmProfileBase->CurDataSize = 0;
|
|
mSmmProfileBase->TsegStart = mCpuHotPlugData.SmrrBase;
|
|
mSmmProfileBase->TsegSize = mCpuHotPlugData.SmrrSize;
|
|
mSmmProfileBase->NumSmis = 0;
|
|
mSmmProfileBase->NumCpus = gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus;
|
|
|
|
if (mBtsSupported) {
|
|
mMsrDsArea = (MSR_DS_AREA_STRUCT **)AllocateZeroPool (sizeof (MSR_DS_AREA_STRUCT *) * mMaxNumberOfCpus);
|
|
ASSERT (mMsrDsArea != NULL);
|
|
mMsrBTSRecord = (BRANCH_TRACE_RECORD **)AllocateZeroPool (sizeof (BRANCH_TRACE_RECORD *) * mMaxNumberOfCpus);
|
|
ASSERT (mMsrBTSRecord != NULL);
|
|
mMsrPEBSRecord = (PEBS_RECORD **)AllocateZeroPool (sizeof (PEBS_RECORD *) * mMaxNumberOfCpus);
|
|
ASSERT (mMsrPEBSRecord != NULL);
|
|
|
|
mMsrDsAreaBase = (MSR_DS_AREA_STRUCT *)((UINTN)Base + mSmmProfileSize);
|
|
MsrDsAreaSizePerCpu = mMsrDsAreaSize / mMaxNumberOfCpus;
|
|
mBTSRecordNumber = (MsrDsAreaSizePerCpu - sizeof(PEBS_RECORD) * PEBS_RECORD_NUMBER - sizeof(MSR_DS_AREA_STRUCT)) / sizeof(BRANCH_TRACE_RECORD);
|
|
for (Index = 0; Index < mMaxNumberOfCpus; Index++) {
|
|
mMsrDsArea[Index] = (MSR_DS_AREA_STRUCT *)((UINTN)mMsrDsAreaBase + MsrDsAreaSizePerCpu * Index);
|
|
mMsrBTSRecord[Index] = (BRANCH_TRACE_RECORD *)((UINTN)mMsrDsArea[Index] + sizeof(MSR_DS_AREA_STRUCT));
|
|
mMsrPEBSRecord[Index] = (PEBS_RECORD *)((UINTN)mMsrDsArea[Index] + MsrDsAreaSizePerCpu - sizeof(PEBS_RECORD) * PEBS_RECORD_NUMBER);
|
|
|
|
mMsrDsArea[Index]->BTSBufferBase = (UINTN)mMsrBTSRecord[Index];
|
|
mMsrDsArea[Index]->BTSIndex = mMsrDsArea[Index]->BTSBufferBase;
|
|
mMsrDsArea[Index]->BTSAbsoluteMaximum = mMsrDsArea[Index]->BTSBufferBase + mBTSRecordNumber * sizeof(BRANCH_TRACE_RECORD) + 1;
|
|
mMsrDsArea[Index]->BTSInterruptThreshold = mMsrDsArea[Index]->BTSAbsoluteMaximum + 1;
|
|
|
|
mMsrDsArea[Index]->PEBSBufferBase = (UINTN)mMsrPEBSRecord[Index];
|
|
mMsrDsArea[Index]->PEBSIndex = mMsrDsArea[Index]->PEBSBufferBase;
|
|
mMsrDsArea[Index]->PEBSAbsoluteMaximum = mMsrDsArea[Index]->PEBSBufferBase + PEBS_RECORD_NUMBER * sizeof(PEBS_RECORD) + 1;
|
|
mMsrDsArea[Index]->PEBSInterruptThreshold = mMsrDsArea[Index]->PEBSAbsoluteMaximum + 1;
|
|
}
|
|
}
|
|
|
|
mProtectionMemRange = mProtectionMemRangeTemplate;
|
|
mProtectionMemRangeCount = sizeof (mProtectionMemRangeTemplate) / sizeof (MEMORY_PROTECTION_RANGE);
|
|
|
|
//
|
|
// Update TSeg entry.
|
|
//
|
|
mProtectionMemRange[0].Range.Base = mCpuHotPlugData.SmrrBase;
|
|
mProtectionMemRange[0].Range.Top = mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize;
|
|
|
|
//
|
|
// Update SMM profile entry.
|
|
//
|
|
mProtectionMemRange[1].Range.Base = (EFI_PHYSICAL_ADDRESS)(UINTN)mSmmProfileBase;
|
|
mProtectionMemRange[1].Range.Top = (EFI_PHYSICAL_ADDRESS)(UINTN)mSmmProfileBase + TotalSize;
|
|
|
|
//
|
|
// Allocate memory reserved for creating 4KB pages.
|
|
//
|
|
InitPagesForPFHandler ();
|
|
|
|
//
|
|
// Start SMM profile when SmmReadyToLock protocol is installed.
|
|
//
|
|
Status = gSmst->SmmRegisterProtocolNotify (
|
|
&gEfiSmmReadyToLockProtocolGuid,
|
|
InitSmmProfileCallBack,
|
|
&Registration
|
|
);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
return ;
|
|
}
|
|
|
|
/**
|
|
Check if feature is supported by a processor.
|
|
|
|
**/
|
|
VOID
|
|
CheckFeatureSupported (
|
|
VOID
|
|
)
|
|
{
|
|
UINT32 RegEax;
|
|
UINT32 RegEcx;
|
|
UINT32 RegEdx;
|
|
MSR_IA32_MISC_ENABLE_REGISTER MiscEnableMsr;
|
|
|
|
if ((PcdGet32 (PcdControlFlowEnforcementPropertyMask) != 0) && mCetSupported) {
|
|
AsmCpuid (CPUID_EXTENDED_FUNCTION, &RegEax, NULL, NULL, NULL);
|
|
if (RegEax <= CPUID_EXTENDED_FUNCTION) {
|
|
mCetSupported = FALSE;
|
|
PatchInstructionX86 (mPatchCetSupported, mCetSupported, 1);
|
|
}
|
|
AsmCpuidEx (CPUID_STRUCTURED_EXTENDED_FEATURE_FLAGS, CPUID_STRUCTURED_EXTENDED_FEATURE_FLAGS_SUB_LEAF_INFO, NULL, NULL, &RegEcx, NULL);
|
|
if ((RegEcx & CPUID_CET_SS) == 0) {
|
|
mCetSupported = FALSE;
|
|
PatchInstructionX86 (mPatchCetSupported, mCetSupported, 1);
|
|
}
|
|
}
|
|
|
|
if (mXdSupported) {
|
|
AsmCpuid (CPUID_EXTENDED_FUNCTION, &RegEax, NULL, NULL, NULL);
|
|
if (RegEax <= CPUID_EXTENDED_FUNCTION) {
|
|
//
|
|
// Extended CPUID functions are not supported on this processor.
|
|
//
|
|
mXdSupported = FALSE;
|
|
PatchInstructionX86 (gPatchXdSupported, mXdSupported, 1);
|
|
}
|
|
|
|
AsmCpuid (CPUID_EXTENDED_CPU_SIG, NULL, NULL, NULL, &RegEdx);
|
|
if ((RegEdx & CPUID1_EDX_XD_SUPPORT) == 0) {
|
|
//
|
|
// Execute Disable Bit feature is not supported on this processor.
|
|
//
|
|
mXdSupported = FALSE;
|
|
PatchInstructionX86 (gPatchXdSupported, mXdSupported, 1);
|
|
}
|
|
}
|
|
|
|
if (mBtsSupported) {
|
|
AsmCpuid (CPUID_VERSION_INFO, NULL, NULL, NULL, &RegEdx);
|
|
if ((RegEdx & CPUID1_EDX_BTS_AVAILABLE) != 0) {
|
|
//
|
|
// Per IA32 manuals:
|
|
// When CPUID.1:EDX[21] is set, the following BTS facilities are available:
|
|
// 1. The BTS_UNAVAILABLE flag in the IA32_MISC_ENABLE MSR indicates the
|
|
// availability of the BTS facilities, including the ability to set the BTS and
|
|
// BTINT bits in the MSR_DEBUGCTLA MSR.
|
|
// 2. The IA32_DS_AREA MSR can be programmed to point to the DS save area.
|
|
//
|
|
MiscEnableMsr.Uint64 = AsmReadMsr64 (MSR_IA32_MISC_ENABLE);
|
|
if (MiscEnableMsr.Bits.BTS == 1) {
|
|
//
|
|
// BTS facilities is not supported if MSR_IA32_MISC_ENABLE.BTS bit is set.
|
|
//
|
|
mBtsSupported = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Enable single step.
|
|
|
|
**/
|
|
VOID
|
|
ActivateSingleStepDB (
|
|
VOID
|
|
)
|
|
{
|
|
UINTN Dr6;
|
|
|
|
Dr6 = AsmReadDr6 ();
|
|
if ((Dr6 & DR6_SINGLE_STEP) != 0) {
|
|
return;
|
|
}
|
|
Dr6 |= DR6_SINGLE_STEP;
|
|
AsmWriteDr6 (Dr6);
|
|
}
|
|
|
|
/**
|
|
Enable last branch.
|
|
|
|
**/
|
|
VOID
|
|
ActivateLBR (
|
|
VOID
|
|
)
|
|
{
|
|
UINT64 DebugCtl;
|
|
|
|
DebugCtl = AsmReadMsr64 (MSR_DEBUG_CTL);
|
|
if ((DebugCtl & MSR_DEBUG_CTL_LBR) != 0) {
|
|
return ;
|
|
}
|
|
DebugCtl |= MSR_DEBUG_CTL_LBR;
|
|
AsmWriteMsr64 (MSR_DEBUG_CTL, DebugCtl);
|
|
}
|
|
|
|
/**
|
|
Enable branch trace store.
|
|
|
|
@param CpuIndex The index of the processor.
|
|
|
|
**/
|
|
VOID
|
|
ActivateBTS (
|
|
IN UINTN CpuIndex
|
|
)
|
|
{
|
|
UINT64 DebugCtl;
|
|
|
|
DebugCtl = AsmReadMsr64 (MSR_DEBUG_CTL);
|
|
if ((DebugCtl & MSR_DEBUG_CTL_BTS) != 0) {
|
|
return ;
|
|
}
|
|
|
|
AsmWriteMsr64 (MSR_DS_AREA, (UINT64)(UINTN)mMsrDsArea[CpuIndex]);
|
|
DebugCtl |= (UINT64)(MSR_DEBUG_CTL_BTS | MSR_DEBUG_CTL_TR);
|
|
DebugCtl &= ~((UINT64)MSR_DEBUG_CTL_BTINT);
|
|
AsmWriteMsr64 (MSR_DEBUG_CTL, DebugCtl);
|
|
}
|
|
|
|
/**
|
|
Increase SMI number in each SMI entry.
|
|
|
|
**/
|
|
VOID
|
|
SmmProfileRecordSmiNum (
|
|
VOID
|
|
)
|
|
{
|
|
if (mSmmProfileStart) {
|
|
mSmmProfileBase->NumSmis++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Initialize processor environment for SMM profile.
|
|
|
|
@param CpuIndex The index of the processor.
|
|
|
|
**/
|
|
VOID
|
|
ActivateSmmProfile (
|
|
IN UINTN CpuIndex
|
|
)
|
|
{
|
|
//
|
|
// Enable Single Step DB#
|
|
//
|
|
ActivateSingleStepDB ();
|
|
|
|
if (mBtsSupported) {
|
|
//
|
|
// We can not get useful information from LER, so we have to use BTS.
|
|
//
|
|
ActivateLBR ();
|
|
|
|
//
|
|
// Enable BTS
|
|
//
|
|
ActivateBTS (CpuIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Initialize SMM profile in SMM CPU entry point.
|
|
|
|
@param[in] Cr3 The base address of the page tables to use in SMM.
|
|
|
|
**/
|
|
VOID
|
|
InitSmmProfile (
|
|
UINT32 Cr3
|
|
)
|
|
{
|
|
//
|
|
// Save Cr3
|
|
//
|
|
mSmmProfileCr3 = Cr3;
|
|
|
|
//
|
|
// Skip SMM profile initialization if feature is disabled
|
|
//
|
|
if (!FeaturePcdGet (PcdCpuSmmProfileEnable) &&
|
|
!HEAP_GUARD_NONSTOP_MODE &&
|
|
!NULL_DETECTION_NONSTOP_MODE) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Initialize SmmProfile here
|
|
//
|
|
InitSmmProfileInternal ();
|
|
|
|
//
|
|
// Initialize profile IDT.
|
|
//
|
|
InitIdtr ();
|
|
|
|
//
|
|
// Tell #PF handler to prepare a #DB subsequently.
|
|
//
|
|
mSetupDebugTrap = TRUE;
|
|
}
|
|
|
|
/**
|
|
Update page table to map the memory correctly in order to make the instruction
|
|
which caused page fault execute successfully. And it also save the original page
|
|
table to be restored in single-step exception.
|
|
|
|
@param PageTable PageTable Address.
|
|
@param PFAddress The memory address which caused page fault exception.
|
|
@param CpuIndex The index of the processor.
|
|
@param ErrorCode The Error code of exception.
|
|
|
|
**/
|
|
VOID
|
|
RestorePageTableBelow4G (
|
|
UINT64 *PageTable,
|
|
UINT64 PFAddress,
|
|
UINTN CpuIndex,
|
|
UINTN ErrorCode
|
|
)
|
|
{
|
|
UINTN PTIndex;
|
|
UINTN PFIndex;
|
|
IA32_CR4 Cr4;
|
|
BOOLEAN Enable5LevelPaging;
|
|
|
|
Cr4.UintN = AsmReadCr4 ();
|
|
Enable5LevelPaging = (BOOLEAN) (Cr4.Bits.LA57 == 1);
|
|
|
|
//
|
|
// PML5
|
|
//
|
|
if (Enable5LevelPaging) {
|
|
PTIndex = (UINTN)BitFieldRead64 (PFAddress, 48, 56);
|
|
ASSERT (PageTable[PTIndex] != 0);
|
|
PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
|
|
}
|
|
|
|
//
|
|
// PML4
|
|
//
|
|
if (sizeof(UINT64) == sizeof(UINTN)) {
|
|
PTIndex = (UINTN)BitFieldRead64 (PFAddress, 39, 47);
|
|
ASSERT (PageTable[PTIndex] != 0);
|
|
PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
|
|
}
|
|
|
|
//
|
|
// PDPTE
|
|
//
|
|
PTIndex = (UINTN)BitFieldRead64 (PFAddress, 30, 38);
|
|
ASSERT (PageTable[PTIndex] != 0);
|
|
PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
|
|
|
|
//
|
|
// PD
|
|
//
|
|
PTIndex = (UINTN)BitFieldRead64 (PFAddress, 21, 29);
|
|
if ((PageTable[PTIndex] & IA32_PG_PS) != 0) {
|
|
//
|
|
// Large page
|
|
//
|
|
|
|
//
|
|
// Record old entries with non-present status
|
|
// Old entries include the memory which instruction is at and the memory which instruction access.
|
|
//
|
|
//
|
|
ASSERT (mPFEntryCount[CpuIndex] < MAX_PF_ENTRY_COUNT);
|
|
if (mPFEntryCount[CpuIndex] < MAX_PF_ENTRY_COUNT) {
|
|
PFIndex = mPFEntryCount[CpuIndex];
|
|
mLastPFEntryValue[CpuIndex][PFIndex] = PageTable[PTIndex];
|
|
mLastPFEntryPointer[CpuIndex][PFIndex] = &PageTable[PTIndex];
|
|
mPFEntryCount[CpuIndex]++;
|
|
}
|
|
|
|
//
|
|
// Set new entry
|
|
//
|
|
PageTable[PTIndex] = (PFAddress & ~((1ull << 21) - 1));
|
|
PageTable[PTIndex] |= (UINT64)IA32_PG_PS;
|
|
PageTable[PTIndex] |= (UINT64)PAGE_ATTRIBUTE_BITS;
|
|
if ((ErrorCode & IA32_PF_EC_ID) != 0) {
|
|
PageTable[PTIndex] &= ~IA32_PG_NX;
|
|
}
|
|
} else {
|
|
//
|
|
// Small page
|
|
//
|
|
ASSERT (PageTable[PTIndex] != 0);
|
|
PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
|
|
|
|
//
|
|
// 4K PTE
|
|
//
|
|
PTIndex = (UINTN)BitFieldRead64 (PFAddress, 12, 20);
|
|
|
|
//
|
|
// Record old entries with non-present status
|
|
// Old entries include the memory which instruction is at and the memory which instruction access.
|
|
//
|
|
//
|
|
ASSERT (mPFEntryCount[CpuIndex] < MAX_PF_ENTRY_COUNT);
|
|
if (mPFEntryCount[CpuIndex] < MAX_PF_ENTRY_COUNT) {
|
|
PFIndex = mPFEntryCount[CpuIndex];
|
|
mLastPFEntryValue[CpuIndex][PFIndex] = PageTable[PTIndex];
|
|
mLastPFEntryPointer[CpuIndex][PFIndex] = &PageTable[PTIndex];
|
|
mPFEntryCount[CpuIndex]++;
|
|
}
|
|
|
|
//
|
|
// Set new entry
|
|
//
|
|
PageTable[PTIndex] = (PFAddress & ~((1ull << 12) - 1));
|
|
PageTable[PTIndex] |= (UINT64)PAGE_ATTRIBUTE_BITS;
|
|
if ((ErrorCode & IA32_PF_EC_ID) != 0) {
|
|
PageTable[PTIndex] &= ~IA32_PG_NX;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Handler for Page Fault triggered by Guard page.
|
|
|
|
@param ErrorCode The Error code of exception.
|
|
|
|
**/
|
|
VOID
|
|
GuardPagePFHandler (
|
|
UINTN ErrorCode
|
|
)
|
|
{
|
|
UINT64 *PageTable;
|
|
UINT64 PFAddress;
|
|
UINT64 RestoreAddress;
|
|
UINTN RestorePageNumber;
|
|
UINTN CpuIndex;
|
|
|
|
PageTable = (UINT64 *)AsmReadCr3 ();
|
|
PFAddress = AsmReadCr2 ();
|
|
CpuIndex = GetCpuIndex ();
|
|
|
|
//
|
|
// Memory operation cross pages, like "rep mov" instruction, will cause
|
|
// infinite loop between this and Debug Trap handler. We have to make sure
|
|
// that current page and the page followed are both in PRESENT state.
|
|
//
|
|
RestorePageNumber = 2;
|
|
RestoreAddress = PFAddress;
|
|
while (RestorePageNumber > 0) {
|
|
RestorePageTableBelow4G (PageTable, RestoreAddress, CpuIndex, ErrorCode);
|
|
RestoreAddress += EFI_PAGE_SIZE;
|
|
RestorePageNumber--;
|
|
}
|
|
|
|
//
|
|
// Flush TLB
|
|
//
|
|
CpuFlushTlb ();
|
|
}
|
|
|
|
/**
|
|
The Page fault handler to save SMM profile data.
|
|
|
|
@param Rip The RIP when exception happens.
|
|
@param ErrorCode The Error code of exception.
|
|
|
|
**/
|
|
VOID
|
|
SmmProfilePFHandler (
|
|
UINTN Rip,
|
|
UINTN ErrorCode
|
|
)
|
|
{
|
|
UINT64 *PageTable;
|
|
UINT64 PFAddress;
|
|
UINT64 RestoreAddress;
|
|
UINTN RestorePageNumber;
|
|
UINTN CpuIndex;
|
|
UINTN Index;
|
|
UINT64 InstructionAddress;
|
|
UINTN MaxEntryNumber;
|
|
UINTN CurrentEntryNumber;
|
|
BOOLEAN IsValidPFAddress;
|
|
SMM_PROFILE_ENTRY *SmmProfileEntry;
|
|
UINT64 SmiCommand;
|
|
EFI_STATUS Status;
|
|
UINT8 SoftSmiValue;
|
|
EFI_SMM_SAVE_STATE_IO_INFO IoInfo;
|
|
|
|
if (!mSmmProfileStart) {
|
|
//
|
|
// If SMM profile does not start, call original page fault handler.
|
|
//
|
|
SmiDefaultPFHandler ();
|
|
return;
|
|
}
|
|
|
|
if (mBtsSupported) {
|
|
DisableBTS ();
|
|
}
|
|
|
|
IsValidPFAddress = FALSE;
|
|
PageTable = (UINT64 *)AsmReadCr3 ();
|
|
PFAddress = AsmReadCr2 ();
|
|
CpuIndex = GetCpuIndex ();
|
|
|
|
//
|
|
// Memory operation cross pages, like "rep mov" instruction, will cause
|
|
// infinite loop between this and Debug Trap handler. We have to make sure
|
|
// that current page and the page followed are both in PRESENT state.
|
|
//
|
|
RestorePageNumber = 2;
|
|
RestoreAddress = PFAddress;
|
|
while (RestorePageNumber > 0) {
|
|
if (RestoreAddress <= 0xFFFFFFFF) {
|
|
RestorePageTableBelow4G (PageTable, RestoreAddress, CpuIndex, ErrorCode);
|
|
} else {
|
|
RestorePageTableAbove4G (PageTable, RestoreAddress, CpuIndex, ErrorCode, &IsValidPFAddress);
|
|
}
|
|
RestoreAddress += EFI_PAGE_SIZE;
|
|
RestorePageNumber--;
|
|
}
|
|
|
|
if (!IsValidPFAddress) {
|
|
InstructionAddress = Rip;
|
|
if ((ErrorCode & IA32_PF_EC_ID) != 0 && (mBtsSupported)) {
|
|
//
|
|
// If it is instruction fetch failure, get the correct IP from BTS.
|
|
//
|
|
InstructionAddress = GetSourceFromDestinationOnBts (CpuIndex, Rip);
|
|
if (InstructionAddress == 0) {
|
|
//
|
|
// It indicates the instruction which caused page fault is not a jump instruction,
|
|
// set instruction address same as the page fault address.
|
|
//
|
|
InstructionAddress = PFAddress;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Indicate it is not software SMI
|
|
//
|
|
SmiCommand = 0xFFFFFFFFFFFFFFFFULL;
|
|
for (Index = 0; Index < gSmst->NumberOfCpus; Index++) {
|
|
Status = SmmReadSaveState(&mSmmCpu, sizeof(IoInfo), EFI_SMM_SAVE_STATE_REGISTER_IO, Index, &IoInfo);
|
|
if (EFI_ERROR (Status)) {
|
|
continue;
|
|
}
|
|
if (IoInfo.IoPort == mSmiCommandPort) {
|
|
//
|
|
// A software SMI triggered by SMI command port has been found, get SmiCommand from SMI command port.
|
|
//
|
|
SoftSmiValue = IoRead8 (mSmiCommandPort);
|
|
SmiCommand = (UINT64)SoftSmiValue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
SmmProfileEntry = (SMM_PROFILE_ENTRY *)(UINTN)(mSmmProfileBase + 1);
|
|
//
|
|
// Check if there is already a same entry in profile data.
|
|
//
|
|
for (Index = 0; Index < (UINTN) mSmmProfileBase->CurDataEntries; Index++) {
|
|
if ((SmmProfileEntry[Index].ErrorCode == (UINT64)ErrorCode) &&
|
|
(SmmProfileEntry[Index].Address == PFAddress) &&
|
|
(SmmProfileEntry[Index].CpuNum == (UINT64)CpuIndex) &&
|
|
(SmmProfileEntry[Index].Instruction == InstructionAddress) &&
|
|
(SmmProfileEntry[Index].SmiCmd == SmiCommand)) {
|
|
//
|
|
// Same record exist, need not save again.
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
if (Index == mSmmProfileBase->CurDataEntries) {
|
|
CurrentEntryNumber = (UINTN) mSmmProfileBase->CurDataEntries;
|
|
MaxEntryNumber = (UINTN) mSmmProfileBase->MaxDataEntries;
|
|
if (FeaturePcdGet (PcdCpuSmmProfileRingBuffer)) {
|
|
CurrentEntryNumber = CurrentEntryNumber % MaxEntryNumber;
|
|
}
|
|
if (CurrentEntryNumber < MaxEntryNumber) {
|
|
//
|
|
// Log the new entry
|
|
//
|
|
SmmProfileEntry[CurrentEntryNumber].SmiNum = mSmmProfileBase->NumSmis;
|
|
SmmProfileEntry[CurrentEntryNumber].ErrorCode = (UINT64)ErrorCode;
|
|
SmmProfileEntry[CurrentEntryNumber].ApicId = (UINT64)GetApicId ();
|
|
SmmProfileEntry[CurrentEntryNumber].CpuNum = (UINT64)CpuIndex;
|
|
SmmProfileEntry[CurrentEntryNumber].Address = PFAddress;
|
|
SmmProfileEntry[CurrentEntryNumber].Instruction = InstructionAddress;
|
|
SmmProfileEntry[CurrentEntryNumber].SmiCmd = SmiCommand;
|
|
//
|
|
// Update current entry index and data size in the header.
|
|
//
|
|
mSmmProfileBase->CurDataEntries++;
|
|
mSmmProfileBase->CurDataSize = MultU64x64 (mSmmProfileBase->CurDataEntries, sizeof (SMM_PROFILE_ENTRY));
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Flush TLB
|
|
//
|
|
CpuFlushTlb ();
|
|
|
|
if (mBtsSupported) {
|
|
EnableBTS ();
|
|
}
|
|
}
|
|
|
|
/**
|
|
Replace INT1 exception handler to restore page table to absent/execute-disable state
|
|
in order to trigger page fault again to save SMM profile data..
|
|
|
|
**/
|
|
VOID
|
|
InitIdtr (
|
|
VOID
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
Status = SmmRegisterExceptionHandler (&mSmmCpuService, EXCEPT_IA32_DEBUG, DebugExceptionHandler);
|
|
ASSERT_EFI_ERROR (Status);
|
|
}
|