Our current ExitBootServices() callback puts the APs in wait-for-SIPI
state, by sending them an INIT IPI. This works fine as long as the APs are
not woken between ExitBootServices() and the time the runtime OS boots the
APs.
However, if a platform includes PiSmmCpuDxeSmm, and bases the runtime UEFI
services (like the variable services) on SMM and SMIs, then two problem
scenarios can be experienced:
(a) On QEMU, the APs can be (spuriously?) woken by SMIs, between
ExitBootServices() and the runtime OS's AP boot; for example by
variable service calls. In this case the APs have no code to continue
executing after the RSM instruction, which causes them to re-set.
(b) On physical platforms, the APs might not wake from the wait-for-SIPI
state at all, causing performance (or functional) problems with
PiSmmCpuDxeSmm, dependent on the BSP/AP sync mode configured therein.
Instead, reboot all APs with a minimal real-mode stub when
ExitBootServices() is called. Each AP shall bump a shared counter
atomically at first, then spiral into an infinite HLT loop. (This occurs
regardless of what each AP has been doing at that moment.) APs can wake
from the HLT due to an SMI, and easily return to the loop upon RSM as
well. Finally, the BSP will be able to ensure in the ExitBootServices()
callback that all APs have moved on to the stub.
This patch introduces the CPU_AP_HLT_LOOP data structure only, with the
related accessor functions. The ExitBootServices() callback is not
modified yet.
Cc: Chen Fan <[email protected]>
Cc: Jeff Fan <[email protected]>
Cc: Jordan Justen <[email protected]>
Cc: Michael Kinney <[email protected]>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Laszlo Ersek <[email protected]>
---
Notes:
v2:
- capitalize first letter of subject after "UefiCpuPkg/CpuDxe" prefix
[Jeff]
- move C-based-asm to ApStartup.c [Jordan]
- in the template, replace 0x1234 with 0x0000 as placeholder of AX value
[Jordan]
- provide nasm source block as a comment in ApStartup.c [Jordan]
UefiCpuPkg/CpuDxe/CpuMp.h | 46 +++++-
UefiCpuPkg/CpuDxe/ApStartup.c | 148 +++++++++++++++++++-
UefiCpuPkg/CpuDxe/CpuMp.c | 14 +-
3 files changed, 187 insertions(+), 21 deletions(-)
diff --git a/UefiCpuPkg/CpuDxe/CpuMp.h b/UefiCpuPkg/CpuDxe/CpuMp.h
index 503f3ae..cf3a002 100644
--- a/UefiCpuPkg/CpuDxe/CpuMp.h
+++ b/UefiCpuPkg/CpuDxe/CpuMp.h
@@ -129,10 +129,17 @@ typedef struct {
BOOLEAN TimeoutActive;
EFI_EVENT CheckAllAPsEvent;
} MP_SYSTEM_DATA;
/**
+ Opaque data structure holding the binary code of the final HALT loop for the
+ APs.
+
+**/
+typedef struct _CPU_AP_HLT_LOOP CPU_AP_HLT_LOOP;
+
+/**
This function is called by all processors (both BSP and AP) once and
collects MP related data.
@param Bsp TRUE if the CPU is BSP
@param ProcessorNumber The specific processor number
@@ -606,29 +613,54 @@ VOID
ResetProcessorToIdleState (
IN CPU_DATA_BLOCK *CpuData
);
/**
- Prepares Startup Code for APs.
- This function prepares Startup Code for APs.
+ This function prepares the Startup Code and the final HALT loop for the APs.
- @retval EFI_SUCCESS The APs were started
- @retval EFI_OUT_OF_RESOURCES Cannot allocate memory to start APs
+ @param[out] CpuApHltLoopPointer On output, pointer to the HALT loop for the
+ APs, allocated in AcpiNVS type memory. The
+ numeric value of this pointer is less than 1
+ MB, and it is page-aligned.
+
+ @retval EFI_SUCCESS The routines have been prepared.
+ @retval EFI_OUT_OF_RESOURCES Cannot allocate memory for the routines.
**/
EFI_STATUS
PrepareAPStartupCode (
- VOID
+ OUT CPU_AP_HLT_LOOP **CpuApHltLoopPointer
);
/**
- Free the code buffer of startup AP.
+ Free the AP startup and HALT loop routines.
+
+ @param[in] CpuApHltLoopPointer The pointer to the HALT loop for the APs,
+ stored by the successful invocation of
+ PrepareAPStartupCode().
**/
VOID
FreeApStartupCode (
- VOID
+ IN CPU_AP_HLT_LOOP *CpuApHltLoopPointer OPTIONAL
+ );
+
+/**
+ Wait until all APs enter the final HALT loop.
+
+ @param[in] CpuApHltLoopPointer The pointer to the HALT loop for the APs,
+ stored by the successful invocation of
+ PrepareAPStartupCode().
+
+ @param[in] ApCount The number of APs that have been detected at
+ MP services initialization.
+
+**/
+VOID
+WaitForAllApsToEnterHltLoop (
+ IN CPU_AP_HLT_LOOP *CpuApHltLoopPointer,
+ IN UINT16 ApCount
);
/**
Resets the Application Processor and directs it to jump to the
specified routine.
diff --git a/UefiCpuPkg/CpuDxe/ApStartup.c b/UefiCpuPkg/CpuDxe/ApStartup.c
index 78fb26f..b6a1d2a 100644
--- a/UefiCpuPkg/CpuDxe/ApStartup.c
+++ b/UefiCpuPkg/CpuDxe/ApStartup.c
@@ -131,10 +131,23 @@ typedef struct {
#endif
UINT8 JmpToCpuDxeEntry[2];
} STARTUP_CODE;
+struct _CPU_AP_HLT_LOOP {
+ UINT8 MovAx;
+ UINT16 AxValue;
+ UINT8 MovDsAx[2];
+ UINT8 LockedWordIncrement[6];
+
+ UINT8 Cli;
+ UINT8 Hlt;
+ UINT8 JmpToCli[2];
+
+ UINT16 SharedWordCounter;
+};
+
#pragma pack()
/**
This .asm code used for translating processor from 16 bit real mode into
64 bit long mode. which help to create the mStartupCodeTemplate value.
@@ -319,10 +332,71 @@ STARTUP_CODE mStartupCodeTemplate = {
};
volatile STARTUP_CODE *StartupCode = NULL;
/**
+ This real mode assembly code is used by the ExitBootServices() callback for
+ rebooting all APs into a HALT loop. The BSP returns from the callback when
+ all APs have entered the code.
+
+ Assemble it with:
+ $ nasm -o HltLoop HltLoop.nasmb
+
+ Then disassemble it with:
+ $ ndisasm HltLoop
+
+ ;
+ ; Real mode code.
+ ;
+ BITS 16
+
+ ;
+ ; The binary should be stored at offset 0 of both the containing page
+ ; and the containing real mode segment.
+ ;
+ ORG 0
+
+ ;
+ ; Set DS to the page this code has been stored in.
+ ; The AX value will be patched by the C language initialization code.
+ ;
+ mov ax, 0x0000
+ mov ds, ax
+
+ ;
+ ; Let the BSP know that this AP has entered the code.
+ ;
+ lock inc word [ds:SharedWordCounter]
+
+ ;
+ ; The HALT loop.
+ ;
+ Loop:
+ cli
+ hlt
+ jmp Loop
+
+ ;
+ ; This counter is bumped by each AP, and checked by the BSP.
+ ;
+ SharedWordCounter:
+ dw 0x0000
+**/
+STATIC CONST CPU_AP_HLT_LOOP mCpuApHltLoopTemplate = {
+ 0xB8, 0x0000, // mov ax,0x0
+ { 0x8E, 0xD8 }, // mov ds,ax
+
+ { 0xF0, 0x3E, 0xFF, 0x06, 0x0F, 0x00 }, // lock inc word [ds:0xf]
+
+ 0xFA, // cli @ offset 0xb
+ 0xF4, // hlt
+ { 0xEB, 0xFC }, // jmp short 0xb
+
+ 0x0000 // SharedWordCounter @ offset 0xf
+};
+
+/**
The function will check if BSP Execute Disable is enabled.
DxeIpl may have enabled Execute Disable for BSP,
APs need to get the status and sync up the settings.
@retval TRUE BSP Execute Disable is enabled.
@@ -361,25 +435,31 @@ IsBspExecuteDisableEnabled (
return Enabled;
}
/**
- Prepares Startup Code for APs.
- This function prepares Startup Code for APs.
+ This function prepares the Startup Code and the final HALT loop for the APs.
- @retval EFI_SUCCESS The APs were started
- @retval EFI_OUT_OF_RESOURCES Cannot allocate memory to start APs
+ @param[out] CpuApHltLoopPointer On output, pointer to the HALT loop for the
+ APs, allocated in AcpiNVS type memory. The
+ numeric value of this pointer is less than 1
+ MB, and it is page-aligned.
+
+ @retval EFI_SUCCESS The routines have been prepared.
+ @retval EFI_OUT_OF_RESOURCES Cannot allocate memory for the routines.
**/
EFI_STATUS
PrepareAPStartupCode (
- VOID
+ OUT CPU_AP_HLT_LOOP **CpuApHltLoopPointer
)
{
EFI_STATUS Status;
IA32_DESCRIPTOR Gdtr;
EFI_PHYSICAL_ADDRESS StartAddress;
+ EFI_PHYSICAL_ADDRESS CpuApHltLoopAddress;
+ CPU_AP_HLT_LOOP *CpuApHltLoop;
StartAddress = BASE_1MB;
Status = gBS->AllocatePages (
AllocateMaxAddress,
EfiACPIMemoryNVS,
@@ -414,30 +494,84 @@ PrepareAPStartupCode (
StartupCode->LongJmpOffset += (UINT32) StartAddress;
#else
StartupCode->EnableExecuteDisable.Cr3Value = (UINT32) AsmReadCr3 ();
#endif
+ //
+ // Prepare the HALT loop for the ExitBootServices() callback.
+ //
+ CpuApHltLoopAddress = BASE_1MB - 1;
+ Status = gBS->AllocatePages (AllocateMaxAddress, EfiACPIMemoryNVS,
+ EFI_SIZE_TO_PAGES (sizeof *CpuApHltLoop),
+ &CpuApHltLoopAddress);
+ if (EFI_ERROR (Status)) {
+ gBS->FreePages (StartAddress, EFI_SIZE_TO_PAGES (sizeof *StartupCode));
+ return Status;
+ }
+
+ CpuApHltLoop = (VOID *)(UINTN)CpuApHltLoopAddress;
+ CopyMem (CpuApHltLoop, &mCpuApHltLoopTemplate, sizeof *CpuApHltLoop);
+ //
+ // Point DS via AX to the page where the routine has been allocated.
+ //
+ CpuApHltLoop->AxValue = (UINT16)((UINTN)CpuApHltLoopAddress >> 4);
+
+ *CpuApHltLoopPointer = CpuApHltLoop;
return EFI_SUCCESS;
}
/**
- Free the code buffer of startup AP.
+ Free the AP startup and HALT loop routines.
+
+ @param[in] CpuApHltLoopPointer The pointer to the HALT loop for the APs,
+ stored by the successful invocation of
+ PrepareAPStartupCode().
**/
VOID
FreeApStartupCode (
- VOID
+ IN CPU_AP_HLT_LOOP *CpuApHltLoopPointer OPTIONAL
)
{
if (StartupCode != NULL) {
gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)(VOID*) StartupCode,
EFI_SIZE_TO_PAGES (sizeof (*StartupCode)));
}
+
+ if (CpuApHltLoopPointer != NULL) {
+ gBS->FreePages ((UINTN)CpuApHltLoopPointer,
+ EFI_SIZE_TO_PAGES (sizeof *CpuApHltLoopPointer));
+ }
}
/**
+ Wait until all APs enter the final HALT loop.
+
+ @param[in] CpuApHltLoopPointer The pointer to the HALT loop for the APs,
+ stored by the successful invocation of
+ PrepareAPStartupCode().
+
+ @param[in] ApCount The number of APs that have been detected at
+ MP services initialization.
+
+**/
+VOID
+WaitForAllApsToEnterHltLoop (
+ IN CPU_AP_HLT_LOOP *CpuApHltLoopPointer,
+ IN UINT16 ApCount
+ )
+{
+ while (InterlockedCompareExchange16 (
+ &CpuApHltLoopPointer->SharedWordCounter,
+ ApCount,
+ ApCount) != ApCount) {
+ CpuPause();
+ }
+}
+
+/**
Starts the Application Processors and directs them to jump to the
specified routine.
The processor jumps to this code in flat mode, but the processor's
stack is not initialized.
diff --git a/UefiCpuPkg/CpuDxe/CpuMp.c b/UefiCpuPkg/CpuDxe/CpuMp.c
index b66f965..1aa2600 100644
--- a/UefiCpuPkg/CpuDxe/CpuMp.c
+++ b/UefiCpuPkg/CpuDxe/CpuMp.c
@@ -1691,23 +1691,23 @@ SetMtrrsFromBuffer (
VOID
InitializeMpSupport (
VOID
)
{
- EFI_STATUS Status;
- MTRR_SETTINGS MtrrSettings;
- UINTN Timeout;
+ EFI_STATUS Status;
+ MTRR_SETTINGS MtrrSettings;
+ UINTN Timeout;
+ CPU_AP_HLT_LOOP *CpuApHltLoop;
gMaxLogicalProcessorNumber = (UINTN) PcdGet32
(PcdCpuMaxLogicalProcessorNumber);
if (gMaxLogicalProcessorNumber < 1) {
DEBUG ((DEBUG_ERROR, "Setting PcdCpuMaxLogicalProcessorNumber should be
more than zero.\n"));
return;
}
-
-
InitMpSystemData ();
+ CpuApHltLoop = NULL;
//
// Only perform AP detection if PcdCpuMaxLogicalProcessorNumber is greater
than 1
//
if (gMaxLogicalProcessorNumber > 1) {
@@ -1724,18 +1724,18 @@ InitializeMpSupport (
//
mCommonStack = mApStackStart;
mTopOfApCommonStack = (UINT8*) mApStackStart + gApStackSize;
mApStackStart = mTopOfApCommonStack;
- PrepareAPStartupCode ();
+ PrepareAPStartupCode (&CpuApHltLoop);
StartApsStackless ();
}
DEBUG ((DEBUG_INFO, "Detect CPU count: %d\n",
mMpSystemData.NumberOfProcessors));
if (mMpSystemData.NumberOfProcessors == 1) {
- FreeApStartupCode ();
+ FreeApStartupCode (CpuApHltLoop);
if (mCommonStack != NULL) {
FreePages (mCommonStack, EFI_SIZE_TO_PAGES (gMaxLogicalProcessorNumber *
gApStackSize));
}
}
--
1.8.3.1
_______________________________________________
edk2-devel mailing list
[email protected]
https://lists.01.org/mailman/listinfo/edk2-devel