/** @file
SMM MP service implementation
Copyright (c) 2009 - 2019, Intel Corporation. All rights reserved.
Copyright (c) 2017, AMD Incorporated. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "PiSmmCpuDxeSmm.h"
//
// Slots for all MTRR( FIXED MTRR + VARIABLE MTRR + MTRR_LIB_IA32_MTRR_DEF_TYPE)
//
MTRR_SETTINGS gSmiMtrrs;
UINT64 gPhyMask;
SMM_DISPATCHER_MP_SYNC_DATA *mSmmMpSyncData = NULL;
UINTN mSmmMpSyncDataSize;
SMM_CPU_SEMAPHORES mSmmCpuSemaphores;
UINTN mSemaphoreSize;
SPIN_LOCK *mPFLock = NULL;
SMM_CPU_SYNC_MODE mCpuSmmSyncMode;
BOOLEAN mMachineCheckSupported = FALSE;
/**
Performs an atomic compare exchange operation to get semaphore.
The compare exchange operation must be performed using
MP safe mechanisms.
@param Sem IN: 32-bit unsigned integer
OUT: original integer - 1
@return Original integer - 1
**/
UINT32
WaitForSemaphore (
IN OUT volatile UINT32 *Sem
)
{
UINT32 Value;
do {
Value = *Sem;
} while (Value == 0 ||
InterlockedCompareExchange32 (
(UINT32*)Sem,
Value,
Value - 1
) != Value);
return Value - 1;
}
/**
Performs an atomic compare exchange operation to release semaphore.
The compare exchange operation must be performed using
MP safe mechanisms.
@param Sem IN: 32-bit unsigned integer
OUT: original integer + 1
@return Original integer + 1
**/
UINT32
ReleaseSemaphore (
IN OUT volatile UINT32 *Sem
)
{
UINT32 Value;
do {
Value = *Sem;
} while (Value + 1 != 0 &&
InterlockedCompareExchange32 (
(UINT32*)Sem,
Value,
Value + 1
) != Value);
return Value + 1;
}
/**
Performs an atomic compare exchange operation to lock semaphore.
The compare exchange operation must be performed using
MP safe mechanisms.
@param Sem IN: 32-bit unsigned integer
OUT: -1
@return Original integer
**/
UINT32
LockdownSemaphore (
IN OUT volatile UINT32 *Sem
)
{
UINT32 Value;
do {
Value = *Sem;
} while (InterlockedCompareExchange32 (
(UINT32*)Sem,
Value, (UINT32)-1
) != Value);
return Value;
}
/**
Wait all APs to performs an atomic compare exchange operation to release semaphore.
@param NumberOfAPs AP number
**/
VOID
WaitForAllAPs (
IN UINTN NumberOfAPs
)
{
UINTN BspIndex;
BspIndex = mSmmMpSyncData->BspIndex;
while (NumberOfAPs-- > 0) {
WaitForSemaphore (mSmmMpSyncData->CpuData[BspIndex].Run);
}
}
/**
Performs an atomic compare exchange operation to release semaphore
for each AP.
**/
VOID
ReleaseAllAPs (
VOID
)
{
UINTN Index;
UINTN BspIndex;
BspIndex = mSmmMpSyncData->BspIndex;
for (Index = mMaxNumberOfCpus; Index-- > 0;) {
if (Index != BspIndex && *(mSmmMpSyncData->CpuData[Index].Present)) {
ReleaseSemaphore (mSmmMpSyncData->CpuData[Index].Run);
}
}
}
/**
Checks if all CPUs (with certain exceptions) have checked in for this SMI run
@param Exceptions CPU Arrival exception flags.
@retval TRUE if all CPUs the have checked in.
@retval FALSE if at least one Normal AP hasn't checked in.
**/
BOOLEAN
AllCpusInSmmWithExceptions (
SMM_CPU_ARRIVAL_EXCEPTIONS Exceptions
)
{
UINTN Index;
SMM_CPU_DATA_BLOCK *CpuData;
EFI_PROCESSOR_INFORMATION *ProcessorInfo;
ASSERT (*mSmmMpSyncData->Counter <= mNumberOfCpus);
if (*mSmmMpSyncData->Counter == mNumberOfCpus) {
return TRUE;
}
CpuData = mSmmMpSyncData->CpuData;
ProcessorInfo = gSmmCpuPrivate->ProcessorInfo;
for (Index = mMaxNumberOfCpus; Index-- > 0;) {
if (!(*(CpuData[Index].Present)) && ProcessorInfo[Index].ProcessorId != INVALID_APIC_ID) {
if (((Exceptions & ARRIVAL_EXCEPTION_DELAYED) != 0) && SmmCpuFeaturesGetSmmRegister (Index, SmmRegSmmDelayed) != 0) {
continue;
}
if (((Exceptions & ARRIVAL_EXCEPTION_BLOCKED) != 0) && SmmCpuFeaturesGetSmmRegister (Index, SmmRegSmmBlocked) != 0) {
continue;
}
if (((Exceptions & ARRIVAL_EXCEPTION_SMI_DISABLED) != 0) && SmmCpuFeaturesGetSmmRegister (Index, SmmRegSmmEnable) != 0) {
continue;
}
return FALSE;
}
}
return TRUE;
}
/**
Has OS enabled Lmce in the MSR_IA32_MCG_EXT_CTL
@retval TRUE Os enable lmce.
@retval FALSE Os not enable lmce.
**/
BOOLEAN
IsLmceOsEnabled (
VOID
)
{
MSR_IA32_MCG_CAP_REGISTER McgCap;
MSR_IA32_FEATURE_CONTROL_REGISTER FeatureCtrl;
MSR_IA32_MCG_EXT_CTL_REGISTER McgExtCtrl;
McgCap.Uint64 = AsmReadMsr64 (MSR_IA32_MCG_CAP);
if (McgCap.Bits.MCG_LMCE_P == 0) {
return FALSE;
}
FeatureCtrl.Uint64 = AsmReadMsr64 (MSR_IA32_FEATURE_CONTROL);
if (FeatureCtrl.Bits.LmceOn == 0) {
return FALSE;
}
McgExtCtrl.Uint64 = AsmReadMsr64 (MSR_IA32_MCG_EXT_CTL);
return (BOOLEAN) (McgExtCtrl.Bits.LMCE_EN == 1);
}
/**
Return if Local machine check exception signaled.
Indicates (when set) that a local machine check exception was generated. This indicates that the current machine-check event was
delivered to only the logical processor.
@retval TRUE LMCE was signaled.
@retval FALSE LMCE was not signaled.
**/
BOOLEAN
IsLmceSignaled (
VOID
)
{
MSR_IA32_MCG_STATUS_REGISTER McgStatus;
McgStatus.Uint64 = AsmReadMsr64 (MSR_IA32_MCG_STATUS);
return (BOOLEAN) (McgStatus.Bits.LMCE_S == 1);
}
/**
Given timeout constraint, wait for all APs to arrive, and insure when this function returns, no AP will execute normal mode code before
entering SMM, except SMI disabled APs.
**/
VOID
SmmWaitForApArrival (
VOID
)
{
UINT64 Timer;
UINTN Index;
BOOLEAN LmceEn;
BOOLEAN LmceSignal;
ASSERT (*mSmmMpSyncData->Counter <= mNumberOfCpus);
LmceEn = FALSE;
LmceSignal = FALSE;
if (mMachineCheckSupported) {
LmceEn = IsLmceOsEnabled ();
LmceSignal = IsLmceSignaled();
}
//
// Platform implementor should choose a timeout value appropriately:
// - The timeout value should balance the SMM time constrains and the likelihood that delayed CPUs are excluded in the SMM run. Note
// the SMI Handlers must ALWAYS take into account the cases that not all APs are available in an SMI run.
// - The timeout value must, in the case of 2nd timeout, be at least long enough to give time for all APs to receive the SMI IPI
// and either enter SMM or buffer the SMI, to insure there is no CPU running normal mode code when SMI handling starts. This will
// be TRUE even if a blocked CPU is brought out of the blocked state by a normal mode CPU (before the normal mode CPU received the
// SMI IPI), because with a buffered SMI, and CPU will enter SMM immediately after it is brought out of the blocked state.
// - The timeout value must be longer than longest possible IO operation in the system
//
//
// Sync with APs 1st timeout
//
for (Timer = StartSyncTimer ();
!IsSyncTimerTimeout (Timer) && !(LmceEn && LmceSignal) &&
!AllCpusInSmmWithExceptions (ARRIVAL_EXCEPTION_BLOCKED | ARRIVAL_EXCEPTION_SMI_DISABLED );
) {
CpuPause ();
}
//
// Not all APs have arrived, so we need 2nd round of timeout. IPIs should be sent to ALL none present APs,
// because:
// a) Delayed AP may have just come out of the delayed state. Blocked AP may have just been brought out of blocked state by some AP running
// normal mode code. These APs need to be guaranteed to have an SMI pending to insure that once they are out of delayed / blocked state, they
// enter SMI immediately without executing instructions in normal mode. Note traditional flow requires there are no APs doing normal mode
// work while SMI handling is on-going.
// b) As a consequence of SMI IPI sending, (spurious) SMI may occur after this SMM run.
// c) ** NOTE **: Use SMI disabling feature VERY CAREFULLY (if at all) for traditional flow, because a processor in SMI-disabled state
// will execute normal mode code, which breaks the traditional SMI handlers' assumption that no APs are doing normal
// mode work while SMI handling is on-going.
// d) We don't add code to check SMI disabling status to skip sending IPI to SMI disabled APs, because:
// - In traditional flow, SMI disabling is discouraged.
// - In relaxed flow, CheckApArrival() will check SMI disabling status before calling this function.
// In both cases, adding SMI-disabling checking code increases overhead.
//
if (*mSmmMpSyncData->Counter < mNumberOfCpus) {
//
// Send SMI IPIs to bring outside processors in
//
for (Index = mMaxNumberOfCpus; Index-- > 0;) {
if (!(*(mSmmMpSyncData->CpuData[Index].Present)) && gSmmCpuPrivate->ProcessorInfo[Index].ProcessorId != INVALID_APIC_ID) {
SendSmiIpi ((UINT32)gSmmCpuPrivate->ProcessorInfo[Index].ProcessorId);
}
}
//
// Sync with APs 2nd timeout.
//
for (Timer = StartSyncTimer ();
!IsSyncTimerTimeout (Timer) &&
!AllCpusInSmmWithExceptions (ARRIVAL_EXCEPTION_BLOCKED | ARRIVAL_EXCEPTION_SMI_DISABLED );
) {
CpuPause ();
}
}
return;
}
/**
Replace OS MTRR's with SMI MTRR's.
@param CpuIndex Processor Index
**/
VOID
ReplaceOSMtrrs (
IN UINTN CpuIndex
)
{
SmmCpuFeaturesDisableSmrr ();
//
// Replace all MTRRs registers
//
MtrrSetAllMtrrs (&gSmiMtrrs);
}
/**
SMI handler for BSP.
@param CpuIndex BSP processor Index
@param SyncMode SMM MP sync mode
**/
VOID
BSPHandler (
IN UINTN CpuIndex,
IN SMM_CPU_SYNC_MODE SyncMode
)
{
UINTN Index;
MTRR_SETTINGS Mtrrs;
UINTN ApCount;
BOOLEAN ClearTopLevelSmiResult;
UINTN PresentCount;
ASSERT (CpuIndex == mSmmMpSyncData->BspIndex);
ApCount = 0;
//
// Flag BSP's presence
//
*mSmmMpSyncData->InsideSmm = TRUE;
//
// Initialize Debug Agent to start source level debug in BSP handler
//
InitializeDebugAgent (DEBUG_AGENT_INIT_ENTER_SMI, NULL, NULL);
//
// Mark this processor's presence
//
*(mSmmMpSyncData->CpuData[CpuIndex].Present) = TRUE;
//
// Clear platform top level SMI status bit before calling SMI handlers. If
// we cleared it after SMI handlers are run, we would miss the SMI that
// occurs after SMI handlers are done and before SMI status bit is cleared.
//
ClearTopLevelSmiResult = ClearTopLevelSmiStatus();
ASSERT (ClearTopLevelSmiResult == TRUE);
//
// Set running processor index
//
gSmmCpuPrivate->SmmCoreEntryContext.CurrentlyExecutingCpu = CpuIndex;
//
// If Traditional Sync Mode or need to configure MTRRs: gather all available APs.
//
if (SyncMode == SmmCpuSyncModeTradition || SmmCpuFeaturesNeedConfigureMtrrs()) {
//
// Wait for APs to arrive
//
SmmWaitForApArrival();
//
// Lock the counter down and retrieve the number of APs
//
*mSmmMpSyncData->AllCpusInSync = TRUE;
ApCount = LockdownSemaphore (mSmmMpSyncData->Counter) - 1;
//
// Wait for all APs to get ready for programming MTRRs
//
WaitForAllAPs (ApCount);
if (SmmCpuFeaturesNeedConfigureMtrrs()) {
//
// Signal all APs it's time for backup MTRRs
//
ReleaseAllAPs ();
//
// WaitForSemaphore() may wait for ever if an AP happens to enter SMM at
// exactly this point. Please make sure PcdCpuSmmMaxSyncLoops has been set
// to a large enough value to avoid this situation.
// Note: For HT capable CPUs, threads within a core share the same set of MTRRs.
// We do the backup first and then set MTRR to avoid race condition for threads
// in the same core.
//
MtrrGetAllMtrrs(&Mtrrs);
//
// Wait for all APs to complete their MTRR saving
//
WaitForAllAPs (ApCount);
//
// Let all processors program SMM MTRRs together
//
ReleaseAllAPs ();
//
// WaitForSemaphore() may wait for ever if an AP happens to enter SMM at
// exactly this point. Please make sure PcdCpuSmmMaxSyncLoops has been set
// to a large enough value to avoid this situation.
//
ReplaceOSMtrrs (CpuIndex);
//
// Wait for all APs to complete their MTRR programming
//
WaitForAllAPs (ApCount);
}
}
//
// The BUSY lock is initialized to Acquired state
//
AcquireSpinLock (mSmmMpSyncData->CpuData[CpuIndex].Busy);
//
// Perform the pre tasks
//
PerformPreTasks ();
//
// Invoke SMM Foundation EntryPoint with the processor information context.
//
gSmmCpuPrivate->SmmCoreEntry (&gSmmCpuPrivate->SmmCoreEntryContext);
//
// Make sure all APs have completed their pending none-block tasks
//
for (Index = mMaxNumberOfCpus; Index-- > 0;) {
if (Index != CpuIndex && *(mSmmMpSyncData->CpuData[Index].Present)) {
AcquireSpinLock (mSmmMpSyncData->CpuData[Index].Busy);
ReleaseSpinLock (mSmmMpSyncData->CpuData[Index].Busy);
}
}
//
// Perform the remaining tasks
//
PerformRemainingTasks ();
//
// If Relaxed-AP Sync Mode: gather all available APs after BSP SMM handlers are done, and
// make those APs to exit SMI synchronously. APs which arrive later will be excluded and
// will run through freely.
//
if (SyncMode != SmmCpuSyncModeTradition && !SmmCpuFeaturesNeedConfigureMtrrs()) {
//
// Lock the counter down and retrieve the number of APs
//
*mSmmMpSyncData->AllCpusInSync = TRUE;
ApCount = LockdownSemaphore (mSmmMpSyncData->Counter) - 1;
//
// Make sure all APs have their Present flag set
//
while (TRUE) {
PresentCount = 0;
for (Index = mMaxNumberOfCpus; Index-- > 0;) {
if (*(mSmmMpSyncData->CpuData[Index].Present)) {
PresentCount ++;
}
}
if (PresentCount > ApCount) {
break;
}
}
}
//
// Notify all APs to exit
//
*mSmmMpSyncData->InsideSmm = FALSE;
ReleaseAllAPs ();
//
// Wait for all APs to complete their pending tasks
//
WaitForAllAPs (ApCount);
if (SmmCpuFeaturesNeedConfigureMtrrs()) {
//
// Signal APs to restore MTRRs
//
ReleaseAllAPs ();
//
// Restore OS MTRRs
//
SmmCpuFeaturesReenableSmrr ();
MtrrSetAllMtrrs(&Mtrrs);
//
// Wait for all APs to complete MTRR programming
//
WaitForAllAPs (ApCount);
}
//
// Stop source level debug in BSP handler, the code below will not be
// debugged.
//
InitializeDebugAgent (DEBUG_AGENT_INIT_EXIT_SMI, NULL, NULL);
//
// Signal APs to Reset states/semaphore for this processor
//
ReleaseAllAPs ();
//
// Perform pending operations for hot-plug
//
SmmCpuUpdate ();
//
// Clear the Present flag of BSP
//
*(mSmmMpSyncData->CpuData[CpuIndex].Present) = FALSE;
//
// Gather APs to exit SMM synchronously. Note the Present flag is cleared by now but
// WaitForAllAps does not depend on the Present flag.
//
WaitForAllAPs (ApCount);
//
// Reset BspIndex to -1, meaning BSP has not been elected.
//
if (FeaturePcdGet (PcdCpuSmmEnableBspElection)) {
mSmmMpSyncData->BspIndex = (UINT32)-1;
}
//
// Allow APs to check in from this point on
//
*mSmmMpSyncData->Counter = 0;
*mSmmMpSyncData->AllCpusInSync = FALSE;
}
/**
SMI handler for AP.
@param CpuIndex AP processor Index.
@param ValidSmi Indicates that current SMI is a valid SMI or not.
@param SyncMode SMM MP sync mode.
**/
VOID
APHandler (
IN UINTN CpuIndex,
IN BOOLEAN ValidSmi,
IN SMM_CPU_SYNC_MODE SyncMode
)
{
UINT64 Timer;
UINTN BspIndex;
MTRR_SETTINGS Mtrrs;
//
// Timeout BSP
//
for (Timer = StartSyncTimer ();
!IsSyncTimerTimeout (Timer) &&
!(*mSmmMpSyncData->InsideSmm);
) {
CpuPause ();
}
if (!(*mSmmMpSyncData->InsideSmm)) {
//
// BSP timeout in the first round
//
if (mSmmMpSyncData->BspIndex != -1) {
//
// BSP Index is known
//
BspIndex = mSmmMpSyncData->BspIndex;
ASSERT (CpuIndex != BspIndex);
//
// Send SMI IPI to bring BSP in
//
SendSmiIpi ((UINT32)gSmmCpuPrivate->ProcessorInfo[BspIndex].ProcessorId);
//
// Now clock BSP for the 2nd time
//
for (Timer = StartSyncTimer ();
!IsSyncTimerTimeout (Timer) &&
!(*mSmmMpSyncData->InsideSmm);
) {
CpuPause ();
}
if (!(*mSmmMpSyncData->InsideSmm)) {
//
// Give up since BSP is unable to enter SMM
// and signal the completion of this AP
WaitForSemaphore (mSmmMpSyncData->Counter);
return;
}
} else {
//
// Don't know BSP index. Give up without sending IPI to BSP.
//
WaitForSemaphore (mSmmMpSyncData->Counter);
return;
}
}
//
// BSP is available
//
BspIndex = mSmmMpSyncData->BspIndex;
ASSERT (CpuIndex != BspIndex);
//
// Mark this processor's presence
//
*(mSmmMpSyncData->CpuData[CpuIndex].Present) = TRUE;
if (SyncMode == SmmCpuSyncModeTradition || SmmCpuFeaturesNeedConfigureMtrrs()) {
//
// Notify BSP of arrival at this point
//
ReleaseSemaphore (mSmmMpSyncData->CpuData[BspIndex].Run);
}
if (SmmCpuFeaturesNeedConfigureMtrrs()) {
//
// Wait for the signal from BSP to backup MTRRs
//
WaitForSemaphore (mSmmMpSyncData->CpuData[CpuIndex].Run);
//
// Backup OS MTRRs
//
MtrrGetAllMtrrs(&Mtrrs);
//
// Signal BSP the completion of this AP
//
ReleaseSemaphore (mSmmMpSyncData->CpuData[BspIndex].Run);
//
// Wait for BSP's signal to program MTRRs
//
WaitForSemaphore (mSmmMpSyncData->CpuData[CpuIndex].Run);
//
// Replace OS MTRRs with SMI MTRRs
//
ReplaceOSMtrrs (CpuIndex);
//
// Signal BSP the completion of this AP
//
ReleaseSemaphore (mSmmMpSyncData->CpuData[BspIndex].Run);
}
while (TRUE) {
//
// Wait for something to happen
//
WaitForSemaphore (mSmmMpSyncData->CpuData[CpuIndex].Run);
//
// Check if BSP wants to exit SMM
//
if (!(*mSmmMpSyncData->InsideSmm)) {
break;
}
//
// BUSY should be acquired by SmmStartupThisAp()
//
ASSERT (
!AcquireSpinLockOrFail (mSmmMpSyncData->CpuData[CpuIndex].Busy)
);
//
// Invoke the scheduled procedure
//
(*mSmmMpSyncData->CpuData[CpuIndex].Procedure) (
(VOID*)mSmmMpSyncData->CpuData[CpuIndex].Parameter
);
//
// Release BUSY
//
ReleaseSpinLock (mSmmMpSyncData->CpuData[CpuIndex].Busy);
}
if (SmmCpuFeaturesNeedConfigureMtrrs()) {
//
// Notify BSP the readiness of this AP to program MTRRs
//
ReleaseSemaphore (mSmmMpSyncData->CpuData[BspIndex].Run);
//
// Wait for the signal from BSP to program MTRRs
//
WaitForSemaphore (mSmmMpSyncData->CpuData[CpuIndex].Run);
//
// Restore OS MTRRs
//
SmmCpuFeaturesReenableSmrr ();
MtrrSetAllMtrrs(&Mtrrs);
}
//
// Notify BSP the readiness of this AP to Reset states/semaphore for this processor
//
ReleaseSemaphore (mSmmMpSyncData->CpuData[BspIndex].Run);
//
// Wait for the signal from BSP to Reset states/semaphore for this processor
//
WaitForSemaphore (mSmmMpSyncData->CpuData[CpuIndex].Run);
//
// Reset states/semaphore for this processor
//
*(mSmmMpSyncData->CpuData[CpuIndex].Present) = FALSE;
//
// Notify BSP the readiness of this AP to exit SMM
//
ReleaseSemaphore (mSmmMpSyncData->CpuData[BspIndex].Run);
}
/**
Create 4G PageTable in SMRAM.
@param[in] Is32BitPageTable Whether the page table is 32-bit PAE
@return PageTable Address
**/
UINT32
Gen4GPageTable (
IN BOOLEAN Is32BitPageTable
)
{
VOID *PageTable;
UINTN Index;
UINT64 *Pte;
UINTN PagesNeeded;
UINTN Low2MBoundary;
UINTN High2MBoundary;
UINTN Pages;
UINTN GuardPage;
UINT64 *Pdpte;
UINTN PageIndex;
UINTN PageAddress;
Low2MBoundary = 0;
High2MBoundary = 0;
PagesNeeded = 0;
if (FeaturePcdGet (PcdCpuSmmStackGuard)) {
//
// Add one more page for known good stack, then find the lower 2MB aligned address.
//
Low2MBoundary = (mSmmStackArrayBase + EFI_PAGE_SIZE) & ~(SIZE_2MB-1);
//
// Add two more pages for known good stack and stack guard page,
// then find the lower 2MB aligned address.
//
High2MBoundary = (mSmmStackArrayEnd - mSmmStackSize + EFI_PAGE_SIZE * 2) & ~(SIZE_2MB-1);
PagesNeeded = ((High2MBoundary - Low2MBoundary) / SIZE_2MB) + 1;
}
//
// Allocate the page table
//
PageTable = AllocatePageTableMemory (5 + PagesNeeded);
ASSERT (PageTable != NULL);
PageTable = (VOID *)((UINTN)PageTable);
Pte = (UINT64*)PageTable;
//
// Zero out all page table entries first
//
ZeroMem (Pte, EFI_PAGES_TO_SIZE (1));
//
// Set Page Directory Pointers
//
for (Index = 0; Index < 4; Index++) {
Pte[Index] = ((UINTN)PageTable + EFI_PAGE_SIZE * (Index + 1)) | mAddressEncMask |
(Is32BitPageTable ? IA32_PAE_PDPTE_ATTRIBUTE_BITS : PAGE_ATTRIBUTE_BITS);
}
Pte += EFI_PAGE_SIZE / sizeof (*Pte);
//
// Fill in Page Directory Entries
//
for (Index = 0; Index < EFI_PAGE_SIZE * 4 / sizeof (*Pte); Index++) {
Pte[Index] = (Index << 21) | mAddressEncMask | IA32_PG_PS | PAGE_ATTRIBUTE_BITS;
}
Pdpte = (UINT64*)PageTable;
if (FeaturePcdGet (PcdCpuSmmStackGuard)) {
Pages = (UINTN)PageTable + EFI_PAGES_TO_SIZE (5);
GuardPage = mSmmStackArrayBase + EFI_PAGE_SIZE;
for (PageIndex = Low2MBoundary; PageIndex <= High2MBoundary; PageIndex += SIZE_2MB) {
Pte = (UINT64*)(UINTN)(Pdpte[BitFieldRead32 ((UINT32)PageIndex, 30, 31)] & ~mAddressEncMask & ~(EFI_PAGE_SIZE - 1));
Pte[BitFieldRead32 ((UINT32)PageIndex, 21, 29)] = (UINT64)Pages | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
//
// Fill in Page Table Entries
//
Pte = (UINT64*)Pages;
PageAddress = PageIndex;
for (Index = 0; Index < EFI_PAGE_SIZE / sizeof (*Pte); Index++) {
if (PageAddress == GuardPage) {
//
// Mark the guard page as non-present
//
Pte[Index] = PageAddress | mAddressEncMask;
GuardPage += mSmmStackSize;
if (GuardPage > mSmmStackArrayEnd) {
GuardPage = 0;
}
} else {
Pte[Index] = PageAddress | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
}
PageAddress+= EFI_PAGE_SIZE;
}
Pages += EFI_PAGE_SIZE;
}
}
if ((PcdGet8 (PcdNullPointerDetectionPropertyMask) & BIT1) != 0) {
Pte = (UINT64*)(UINTN)(Pdpte[0] & ~mAddressEncMask & ~(EFI_PAGE_SIZE - 1));
if ((Pte[0] & IA32_PG_PS) == 0) {
// 4K-page entries are already mapped. Just hide the first one anyway.
Pte = (UINT64*)(UINTN)(Pte[0] & ~mAddressEncMask & ~(EFI_PAGE_SIZE - 1));
Pte[0] &= ~(UINT64)IA32_PG_P; // Hide page 0
} else {
// Create 4K-page entries
Pages = (UINTN)AllocatePageTableMemory (1);
ASSERT (Pages != 0);
Pte[0] = (UINT64)(Pages | mAddressEncMask | PAGE_ATTRIBUTE_BITS);
Pte = (UINT64*)Pages;
PageAddress = 0;
Pte[0] = PageAddress | mAddressEncMask; // Hide page 0 but present left
for (Index = 1; Index < EFI_PAGE_SIZE / sizeof (*Pte); Index++) {
PageAddress += EFI_PAGE_SIZE;
Pte[Index] = PageAddress | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
}
}
}
return (UINT32)(UINTN)PageTable;
}
/**
Schedule a procedure to run on the specified CPU.
@param[in] Procedure The address of the procedure to run
@param[in] CpuIndex Target CPU Index
@param[in, out] ProcArguments The parameter to pass to the procedure
@param[in] BlockingMode Startup AP in blocking mode or not
@retval EFI_INVALID_PARAMETER CpuNumber not valid
@retval EFI_INVALID_PARAMETER CpuNumber specifying BSP
@retval EFI_INVALID_PARAMETER The AP specified by CpuNumber did not enter SMM
@retval EFI_INVALID_PARAMETER The AP specified by CpuNumber is busy
@retval EFI_SUCCESS The procedure has been successfully scheduled
**/
EFI_STATUS
InternalSmmStartupThisAp (
IN EFI_AP_PROCEDURE Procedure,
IN UINTN CpuIndex,
IN OUT VOID *ProcArguments OPTIONAL,
IN BOOLEAN BlockingMode
)
{
if (CpuIndex >= gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus) {
DEBUG((DEBUG_ERROR, "CpuIndex(%d) >= gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus(%d)\n", CpuIndex, gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus));
return EFI_INVALID_PARAMETER;
}
if (CpuIndex == gSmmCpuPrivate->SmmCoreEntryContext.CurrentlyExecutingCpu) {
DEBUG((DEBUG_ERROR, "CpuIndex(%d) == gSmmCpuPrivate->SmmCoreEntryContext.CurrentlyExecutingCpu\n", CpuIndex));
return EFI_INVALID_PARAMETER;
}
if (gSmmCpuPrivate->ProcessorInfo[CpuIndex].ProcessorId == INVALID_APIC_ID) {
return EFI_INVALID_PARAMETER;
}
if (!(*(mSmmMpSyncData->CpuData[CpuIndex].Present))) {
if (mSmmMpSyncData->EffectiveSyncMode == SmmCpuSyncModeTradition) {
DEBUG((DEBUG_ERROR, "!mSmmMpSyncData->CpuData[%d].Present\n", CpuIndex));
}
return EFI_INVALID_PARAMETER;
}
if (gSmmCpuPrivate->Operation[CpuIndex] == SmmCpuRemove) {
if (!FeaturePcdGet (PcdCpuHotPlugSupport)) {
DEBUG((DEBUG_ERROR, "gSmmCpuPrivate->Operation[%d] == SmmCpuRemove\n", CpuIndex));
}
return EFI_INVALID_PARAMETER;
}
if (BlockingMode) {
AcquireSpinLock (mSmmMpSyncData->CpuData[CpuIndex].Busy);
} else {
if (!AcquireSpinLockOrFail (mSmmMpSyncData->CpuData[CpuIndex].Busy)) {
DEBUG((DEBUG_ERROR, "mSmmMpSyncData->CpuData[%d].Busy\n", CpuIndex));
return EFI_INVALID_PARAMETER;
}
}
mSmmMpSyncData->CpuData[CpuIndex].Procedure = Procedure;
mSmmMpSyncData->CpuData[CpuIndex].Parameter = ProcArguments;
ReleaseSemaphore (mSmmMpSyncData->CpuData[CpuIndex].Run);
if (BlockingMode) {
AcquireSpinLock (mSmmMpSyncData->CpuData[CpuIndex].Busy);
ReleaseSpinLock (mSmmMpSyncData->CpuData[CpuIndex].Busy);
}
return EFI_SUCCESS;
}
/**
Schedule a procedure to run on the specified CPU in blocking mode.
@param[in] Procedure The address of the procedure to run
@param[in] CpuIndex Target CPU Index
@param[in, out] ProcArguments The parameter to pass to the procedure
@retval EFI_INVALID_PARAMETER CpuNumber not valid
@retval EFI_INVALID_PARAMETER CpuNumber specifying BSP
@retval EFI_INVALID_PARAMETER The AP specified by CpuNumber did not enter SMM
@retval EFI_INVALID_PARAMETER The AP specified by CpuNumber is busy
@retval EFI_SUCCESS The procedure has been successfully scheduled
**/
EFI_STATUS
EFIAPI
SmmBlockingStartupThisAp (
IN EFI_AP_PROCEDURE Procedure,
IN UINTN CpuIndex,
IN OUT VOID *ProcArguments OPTIONAL
)
{
return InternalSmmStartupThisAp(Procedure, CpuIndex, ProcArguments, TRUE);
}
/**
Schedule a procedure to run on the specified CPU.
@param Procedure The address of the procedure to run
@param CpuIndex Target CPU Index
@param ProcArguments The parameter to pass to the procedure
@retval EFI_INVALID_PARAMETER CpuNumber not valid
@retval EFI_INVALID_PARAMETER CpuNumber specifying BSP
@retval EFI_INVALID_PARAMETER The AP specified by CpuNumber did not enter SMM
@retval EFI_INVALID_PARAMETER The AP specified by CpuNumber is busy
@retval EFI_SUCCESS The procedure has been successfully scheduled
**/
EFI_STATUS
EFIAPI
SmmStartupThisAp (
IN EFI_AP_PROCEDURE Procedure,
IN UINTN CpuIndex,
IN OUT VOID *ProcArguments OPTIONAL
)
{
return InternalSmmStartupThisAp(Procedure, CpuIndex, ProcArguments, FeaturePcdGet (PcdCpuSmmBlockStartupThisAp));
}
/**
This function sets DR6 & DR7 according to SMM save state, before running SMM C code.
They are useful when you want to enable hardware breakpoints in SMM without entry SMM mode.
NOTE: It might not be appreciated in runtime since it might
conflict with OS debugging facilities. Turn them off in RELEASE.
@param CpuIndex CPU Index
**/
VOID
EFIAPI
CpuSmmDebugEntry (
IN UINTN CpuIndex
)
{
SMRAM_SAVE_STATE_MAP *CpuSaveState;
if (FeaturePcdGet (PcdCpuSmmDebug)) {
ASSERT(CpuIndex < mMaxNumberOfCpus);
CpuSaveState = (SMRAM_SAVE_STATE_MAP *)gSmmCpuPrivate->CpuSaveState[CpuIndex];
if (mSmmSaveStateRegisterLma == EFI_SMM_SAVE_STATE_REGISTER_LMA_32BIT) {
AsmWriteDr6 (CpuSaveState->x86._DR6);
AsmWriteDr7 (CpuSaveState->x86._DR7);
} else {
AsmWriteDr6 ((UINTN)CpuSaveState->x64._DR6);
AsmWriteDr7 ((UINTN)CpuSaveState->x64._DR7);
}
}
}
/**
This function restores DR6 & DR7 to SMM save state.
NOTE: It might not be appreciated in runtime since it might
conflict with OS debugging facilities. Turn them off in RELEASE.
@param CpuIndex CPU Index
**/
VOID
EFIAPI
CpuSmmDebugExit (
IN UINTN CpuIndex
)
{
SMRAM_SAVE_STATE_MAP *CpuSaveState;
if (FeaturePcdGet (PcdCpuSmmDebug)) {
ASSERT(CpuIndex < mMaxNumberOfCpus);
CpuSaveState = (SMRAM_SAVE_STATE_MAP *)gSmmCpuPrivate->CpuSaveState[CpuIndex];
if (mSmmSaveStateRegisterLma == EFI_SMM_SAVE_STATE_REGISTER_LMA_32BIT) {
CpuSaveState->x86._DR7 = (UINT32)AsmReadDr7 ();
CpuSaveState->x86._DR6 = (UINT32)AsmReadDr6 ();
} else {
CpuSaveState->x64._DR7 = AsmReadDr7 ();
CpuSaveState->x64._DR6 = AsmReadDr6 ();
}
}
}
/**
C function for SMI entry, each processor comes here upon SMI trigger.
@param CpuIndex CPU Index
**/
VOID
EFIAPI
SmiRendezvous (
IN UINTN CpuIndex
)
{
EFI_STATUS Status;
BOOLEAN ValidSmi;
BOOLEAN IsBsp;
BOOLEAN BspInProgress;
UINTN Index;
UINTN Cr2;
ASSERT(CpuIndex < mMaxNumberOfCpus);
//
// Save Cr2 because Page Fault exception in SMM may override its value,
// when using on-demand paging for above 4G memory.
//
Cr2 = 0;
SaveCr2 (&Cr2);
//
// Perform CPU specific entry hooks
//
SmmCpuFeaturesRendezvousEntry (CpuIndex);
//
// Determine if this is a valid SMI
//
ValidSmi = PlatformValidSmi();
//
// Determine if BSP has been already in progress. Note this must be checked after
// ValidSmi because BSP may clear a valid SMI source after checking in.
//
BspInProgress = *mSmmMpSyncData->InsideSmm;
if (!BspInProgress && !ValidSmi) {
//
// If we reach here, it means when we sampled the ValidSmi flag, SMI status had not
// been cleared by BSP in a new SMI run (so we have a truly invalid SMI), or SMI
// status had been cleared by BSP and an existing SMI run has almost ended. (Note
// we sampled ValidSmi flag BEFORE judging BSP-in-progress status.) In both cases, there
// is nothing we need to do.
//
goto Exit;
} else {
//
// Signal presence of this processor
//
if (ReleaseSemaphore (mSmmMpSyncData->Counter) == 0) {
//
// BSP has already ended the synchronization, so QUIT!!!
//
//
// Wait for BSP's signal to finish SMI
//
while (*mSmmMpSyncData->AllCpusInSync) {
CpuPause ();
}
goto Exit;
} else {
//
// The BUSY lock is initialized to Released state.
// This needs to be done early enough to be ready for BSP's SmmStartupThisAp() call.
// E.g., with Relaxed AP flow, SmmStartupThisAp() may be called immediately
// after AP's present flag is detected.
//
InitializeSpinLock (mSmmMpSyncData->CpuData[CpuIndex].Busy);
}
if (FeaturePcdGet (PcdCpuSmmProfileEnable)) {
ActivateSmmProfile (CpuIndex);
}
if (BspInProgress) {
//
// BSP has been elected. Follow AP path, regardless of ValidSmi flag
// as BSP may have cleared the SMI status
//
APHandler (CpuIndex, ValidSmi, mSmmMpSyncData->EffectiveSyncMode);
} else {
//
// We have a valid SMI
//
//
// Elect BSP
//
IsBsp = FALSE;
if (FeaturePcdGet (PcdCpuSmmEnableBspElection)) {
if (!mSmmMpSyncData->SwitchBsp || mSmmMpSyncData->CandidateBsp[CpuIndex]) {
//
// Call platform hook to do BSP election
//
Status = PlatformSmmBspElection (&IsBsp);
if (EFI_SUCCESS == Status) {
//
// Platform hook determines successfully
//
if (IsBsp) {
mSmmMpSyncData->BspIndex = (UINT32)CpuIndex;
}
} else {
//
// Platform hook fails to determine, use default BSP election method
//
InterlockedCompareExchange32 (
(UINT32*)&mSmmMpSyncData->BspIndex,
(UINT32)-1,
(UINT32)CpuIndex
);
}
}
}
//
// "mSmmMpSyncData->BspIndex == CpuIndex" means this is the BSP
//
if (mSmmMpSyncData->BspIndex == CpuIndex) {
//
// Clear last request for SwitchBsp.
//
if (mSmmMpSyncData->SwitchBsp) {
mSmmMpSyncData->SwitchBsp = FALSE;
for (Index = 0; Index < mMaxNumberOfCpus; Index++) {
mSmmMpSyncData->CandidateBsp[Index] = FALSE;
}
}
if (FeaturePcdGet (PcdCpuSmmProfileEnable)) {
SmmProfileRecordSmiNum ();
}
//
// BSP Handler is always called with a ValidSmi == TRUE
//
BSPHandler (CpuIndex, mSmmMpSyncData->EffectiveSyncMode);
} else {
APHandler (CpuIndex, ValidSmi, mSmmMpSyncData->EffectiveSyncMode);
}
}
ASSERT (*mSmmMpSyncData->CpuData[CpuIndex].Run == 0);
//
// Wait for BSP's signal to exit SMI
//
while (*mSmmMpSyncData->AllCpusInSync) {
CpuPause ();
}
}
Exit:
SmmCpuFeaturesRendezvousExit (CpuIndex);
//
// Restore Cr2
//
RestoreCr2 (Cr2);
}
/**
Allocate buffer for all semaphores and spin locks.
**/
VOID
InitializeSmmCpuSemaphores (
VOID
)
{
UINTN ProcessorCount;
UINTN TotalSize;
UINTN GlobalSemaphoresSize;
UINTN CpuSemaphoresSize;
UINTN SemaphoreSize;
UINTN Pages;
UINTN *SemaphoreBlock;
UINTN SemaphoreAddr;
SemaphoreSize = GetSpinLockProperties ();
ProcessorCount = gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus;
GlobalSemaphoresSize = (sizeof (SMM_CPU_SEMAPHORE_GLOBAL) / sizeof (VOID *)) * SemaphoreSize;
CpuSemaphoresSize = (sizeof (SMM_CPU_SEMAPHORE_CPU) / sizeof (VOID *)) * ProcessorCount * SemaphoreSize;
TotalSize = GlobalSemaphoresSize + CpuSemaphoresSize;
DEBUG((EFI_D_INFO, "One Semaphore Size = 0x%x\n", SemaphoreSize));
DEBUG((EFI_D_INFO, "Total Semaphores Size = 0x%x\n", TotalSize));
Pages = EFI_SIZE_TO_PAGES (TotalSize);
SemaphoreBlock = AllocatePages (Pages);
ASSERT (SemaphoreBlock != NULL);
ZeroMem (SemaphoreBlock, TotalSize);
SemaphoreAddr = (UINTN)SemaphoreBlock;
mSmmCpuSemaphores.SemaphoreGlobal.Counter = (UINT32 *)SemaphoreAddr;
SemaphoreAddr += SemaphoreSize;
mSmmCpuSemaphores.SemaphoreGlobal.InsideSmm = (BOOLEAN *)SemaphoreAddr;
SemaphoreAddr += SemaphoreSize;
mSmmCpuSemaphores.SemaphoreGlobal.AllCpusInSync = (BOOLEAN *)SemaphoreAddr;
SemaphoreAddr += SemaphoreSize;
mSmmCpuSemaphores.SemaphoreGlobal.PFLock = (SPIN_LOCK *)SemaphoreAddr;
SemaphoreAddr += SemaphoreSize;
mSmmCpuSemaphores.SemaphoreGlobal.CodeAccessCheckLock
= (SPIN_LOCK *)SemaphoreAddr;
SemaphoreAddr += SemaphoreSize;
SemaphoreAddr = (UINTN)SemaphoreBlock + GlobalSemaphoresSize;
mSmmCpuSemaphores.SemaphoreCpu.Busy = (SPIN_LOCK *)SemaphoreAddr;
SemaphoreAddr += ProcessorCount * SemaphoreSize;
mSmmCpuSemaphores.SemaphoreCpu.Run = (UINT32 *)SemaphoreAddr;
SemaphoreAddr += ProcessorCount * SemaphoreSize;
mSmmCpuSemaphores.SemaphoreCpu.Present = (BOOLEAN *)SemaphoreAddr;
mPFLock = mSmmCpuSemaphores.SemaphoreGlobal.PFLock;
mConfigSmmCodeAccessCheckLock = mSmmCpuSemaphores.SemaphoreGlobal.CodeAccessCheckLock;
mSemaphoreSize = SemaphoreSize;
}
/**
Initialize un-cacheable data.
**/
VOID
EFIAPI
InitializeMpSyncData (
VOID
)
{
UINTN CpuIndex;
if (mSmmMpSyncData != NULL) {
//
// mSmmMpSyncDataSize includes one structure of SMM_DISPATCHER_MP_SYNC_DATA, one
// CpuData array of SMM_CPU_DATA_BLOCK and one CandidateBsp array of BOOLEAN.
//
ZeroMem (mSmmMpSyncData, mSmmMpSyncDataSize);
mSmmMpSyncData->CpuData = (SMM_CPU_DATA_BLOCK *)((UINT8 *)mSmmMpSyncData + sizeof (SMM_DISPATCHER_MP_SYNC_DATA));
mSmmMpSyncData->CandidateBsp = (BOOLEAN *)(mSmmMpSyncData->CpuData + gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus);
if (FeaturePcdGet (PcdCpuSmmEnableBspElection)) {
//
// Enable BSP election by setting BspIndex to -1
//
mSmmMpSyncData->BspIndex = (UINT32)-1;
}
mSmmMpSyncData->EffectiveSyncMode = mCpuSmmSyncMode;
mSmmMpSyncData->Counter = mSmmCpuSemaphores.SemaphoreGlobal.Counter;
mSmmMpSyncData->InsideSmm = mSmmCpuSemaphores.SemaphoreGlobal.InsideSmm;
mSmmMpSyncData->AllCpusInSync = mSmmCpuSemaphores.SemaphoreGlobal.AllCpusInSync;
ASSERT (mSmmMpSyncData->Counter != NULL && mSmmMpSyncData->InsideSmm != NULL &&
mSmmMpSyncData->AllCpusInSync != NULL);
*mSmmMpSyncData->Counter = 0;
*mSmmMpSyncData->InsideSmm = FALSE;
*mSmmMpSyncData->AllCpusInSync = FALSE;
for (CpuIndex = 0; CpuIndex < gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus; CpuIndex ++) {
mSmmMpSyncData->CpuData[CpuIndex].Busy =
(SPIN_LOCK *)((UINTN)mSmmCpuSemaphores.SemaphoreCpu.Busy + mSemaphoreSize * CpuIndex);
mSmmMpSyncData->CpuData[CpuIndex].Run =
(UINT32 *)((UINTN)mSmmCpuSemaphores.SemaphoreCpu.Run + mSemaphoreSize * CpuIndex);
mSmmMpSyncData->CpuData[CpuIndex].Present =
(BOOLEAN *)((UINTN)mSmmCpuSemaphores.SemaphoreCpu.Present + mSemaphoreSize * CpuIndex);
*(mSmmMpSyncData->CpuData[CpuIndex].Busy) = 0;
*(mSmmMpSyncData->CpuData[CpuIndex].Run) = 0;
*(mSmmMpSyncData->CpuData[CpuIndex].Present) = FALSE;
}
}
}
/**
Initialize global data for MP synchronization.
@param Stacks Base address of SMI stack buffer for all processors.
@param StackSize Stack size for each processor in SMM.
@param ShadowStackSize Shadow Stack size for each processor in SMM.
**/
UINT32
InitializeMpServiceData (
IN VOID *Stacks,
IN UINTN StackSize,
IN UINTN ShadowStackSize
)
{
UINT32 Cr3;
UINTN Index;
UINT8 *GdtTssTables;
UINTN GdtTableStepSize;
CPUID_VERSION_INFO_EDX RegEdx;
//
// Determine if this CPU supports machine check
//
AsmCpuid (CPUID_VERSION_INFO, NULL, NULL, NULL, &RegEdx.Uint32);
mMachineCheckSupported = (BOOLEAN)(RegEdx.Bits.MCA == 1);
//
// Allocate memory for all locks and semaphores
//
InitializeSmmCpuSemaphores ();
//
// Initialize mSmmMpSyncData
//
mSmmMpSyncDataSize = sizeof (SMM_DISPATCHER_MP_SYNC_DATA) +
(sizeof (SMM_CPU_DATA_BLOCK) + sizeof (BOOLEAN)) * gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus;
mSmmMpSyncData = (SMM_DISPATCHER_MP_SYNC_DATA*) AllocatePages (EFI_SIZE_TO_PAGES (mSmmMpSyncDataSize));
ASSERT (mSmmMpSyncData != NULL);
mCpuSmmSyncMode = (SMM_CPU_SYNC_MODE)PcdGet8 (PcdCpuSmmSyncMode);
InitializeMpSyncData ();
//
// Initialize physical address mask
// NOTE: Physical memory above virtual address limit is not supported !!!
//
AsmCpuid (0x80000008, (UINT32*)&Index, NULL, NULL, NULL);
gPhyMask = LShiftU64 (1, (UINT8)Index) - 1;
gPhyMask &= (1ull << 48) - EFI_PAGE_SIZE;
//
// Create page tables
//
Cr3 = SmmInitPageTable ();
GdtTssTables = InitGdt (Cr3, &GdtTableStepSize);
//
// Install SMI handler for each CPU
//
for (Index = 0; Index < mMaxNumberOfCpus; Index++) {
InstallSmiHandler (
Index,
(UINT32)mCpuHotPlugData.SmBase[Index],
(VOID*)((UINTN)Stacks + (StackSize + ShadowStackSize) * Index),
StackSize,
(UINTN)(GdtTssTables + GdtTableStepSize * Index),
gcSmiGdtr.Limit + 1,
gcSmiIdtr.Base,
gcSmiIdtr.Limit + 1,
Cr3
);
}
//
// Record current MTRR settings
//
ZeroMem (&gSmiMtrrs, sizeof (gSmiMtrrs));
MtrrGetAllMtrrs (&gSmiMtrrs);
return Cr3;
}
/**
Register the SMM Foundation entry point.
@param This Pointer to EFI_SMM_CONFIGURATION_PROTOCOL instance
@param SmmEntryPoint SMM Foundation EntryPoint
@retval EFI_SUCCESS Successfully to register SMM foundation entry point
**/
EFI_STATUS
EFIAPI
RegisterSmmEntry (
IN CONST EFI_SMM_CONFIGURATION_PROTOCOL *This,
IN EFI_SMM_ENTRY_POINT SmmEntryPoint
)
{
//
// Record SMM Foundation EntryPoint, later invoke it on SMI entry vector.
//
gSmmCpuPrivate->SmmCoreEntry = SmmEntryPoint;
return EFI_SUCCESS;
}