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

Reply via email to