]> git.proxmox.com Git - mirror_edk2.git/commitdiff
OvmfPkg/CpuHotplugSmm: do actual CPU hot-eject
authorAnkur Arora <ankur.a.arora@oracle.com>
Fri, 12 Mar 2021 06:26:55 +0000 (22:26 -0800)
committermergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Tue, 16 Mar 2021 13:21:46 +0000 (13:21 +0000)
Add logic in EjectCpu() to do the actual the CPU ejection.

On the BSP, ejection happens by first selecting the CPU via
its QemuSelector and then sending the QEMU "eject" command.
QEMU in-turn signals the remote VCPU thread which context-switches
the CPU out of the SMI handler.

Meanwhile the CPU being ejected, waits around in its holding
area until it is context-switched out. Note that it is possible
that a slow CPU gets ejected before it reaches the wait loop.
However, this would never happen before it has executed the
"AllCpusInSync" loop in SmiRendezvous().
It can mean that an ejected CPU does not execute code after
that point but given that the CPU state will be destroyed by
QEMU, the missed cleanup is no great loss.

Cc: Laszlo Ersek <lersek@redhat.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Ard Biesheuvel <ard.biesheuvel@arm.com>
Cc: Igor Mammedov <imammedo@redhat.com>
Cc: Boris Ostrovsky <boris.ostrovsky@oracle.com>
Cc: Aaron Young <aaron.young@oracle.com>
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=3132
Signed-off-by: Ankur Arora <ankur.a.arora@oracle.com>
Message-Id: <20210312062656.2477515-10-ankur.a.arora@oracle.com>
Reviewed-by: Laszlo Ersek <lersek@redhat.com>
[lersek@redhat.com: unneeded inner QemuSelector declaration in EjectCpu()
 triggers VS warning #4456 (local variable shadowed); remove it]

OvmfPkg/CpuHotplugSmm/CpuHotplug.c
OvmfPkg/Include/IndustryStandard/QemuCpuHotplug.h

index 2eeb4567a26258e8e84d82b9abeca1c56c383c28..2c768f89f1ee0121a53125fc7557d1ea8b71eec5 100644 (file)
@@ -18,6 +18,7 @@
 #include <Pcd/CpuHotEjectData.h>             // CPU_HOT_EJECT_DATA\r
 #include <Protocol/MmCpuIo.h>                // EFI_MM_CPU_IO_PROTOCOL\r
 #include <Protocol/SmmCpuService.h>          // EFI_SMM_CPU_SERVICE_PROTOCOL\r
+#include <Register/Intel/ArchitecturalMsr.h> // MSR_IA32_APIC_BASE_REGISTER\r
 #include <Uefi/UefiBaseType.h>               // EFI_STATUS\r
 \r
 #include "ApicId.h"                          // APIC_ID\r
@@ -192,13 +193,41 @@ RevokeNewSlot:
   return Status;\r
 }\r
 \r
+/**\r
+  EjectCpu needs to know the BSP at SMI exit at a point when\r
+  some of the EFI_SMM_CPU_SERVICE_PROTOCOL state has been torn\r
+  down.\r
+  Reuse the logic from OvmfPkg::PlatformSmmBspElection() to\r
+  do that.\r
+\r
+  @retval TRUE   If the CPU executing this function is the BSP.\r
+\r
+  @retval FALSE  If the CPU executing this function is an AP.\r
+**/\r
+STATIC\r
+BOOLEAN\r
+CheckIfBsp (\r
+  VOID\r
+  )\r
+{\r
+  MSR_IA32_APIC_BASE_REGISTER ApicBaseMsr;\r
+  BOOLEAN                     IsBsp;\r
+\r
+  ApicBaseMsr.Uint64 = AsmReadMsr64 (MSR_IA32_APIC_BASE);\r
+  IsBsp = (BOOLEAN)(ApicBaseMsr.Bits.BSP == 1);\r
+  return IsBsp;\r
+}\r
+\r
 /**\r
   CPU Hot-eject handler, called from SmmCpuFeaturesRendezvousExit()\r
   on each CPU at exit from SMM.\r
 \r
-  If, the executing CPU is not being ejected, nothing to be done.\r
+  If, the executing CPU is neither the BSP, nor being ejected, nothing\r
+  to be done.\r
   If, the executing CPU is being ejected, wait in a halted loop\r
   until ejected.\r
+  If, the executing CPU is the BSP, set QEMU CPU status to eject\r
+  for CPUs being ejected.\r
 \r
   @param[in] ProcessorNum      ProcessorNum denotes the CPU exiting SMM,\r
                                and will be used as an index into\r
@@ -214,6 +243,81 @@ EjectCpu (
 {\r
   UINT64 QemuSelector;\r
 \r
+  if (CheckIfBsp ()) {\r
+    UINT32 Idx;\r
+\r
+    for (Idx = 0; Idx < mCpuHotEjectData->ArrayLength; Idx++) {\r
+      QemuSelector = mCpuHotEjectData->QemuSelectorMap[Idx];\r
+\r
+      if (QemuSelector != CPU_EJECT_QEMU_SELECTOR_INVALID) {\r
+        //\r
+        // This to-be-ejected-CPU has already received the BSP's SMI exit\r
+        // signal and will execute SmmCpuFeaturesRendezvousExit()\r
+        // followed by this callback or is already penned in the\r
+        // CpuSleep() loop below.\r
+        //\r
+        // Tell QEMU to context-switch it out.\r
+        //\r
+        QemuCpuhpWriteCpuSelector (mMmCpuIo, (UINT32) QemuSelector);\r
+        QemuCpuhpWriteCpuStatus (mMmCpuIo, QEMU_CPUHP_STAT_EJECT);\r
+\r
+        //\r
+        // Now that we've ejected the CPU corresponding to QemuSelectorMap[Idx],\r
+        // clear its eject status to ensure that an invalid future SMI does\r
+        // not end up trying a spurious eject or a newly hotplugged CPU does\r
+        // not get penned in the CpuSleep() loop.\r
+        //\r
+        // Note that the QemuCpuhpWriteCpuStatus() command above is a write to\r
+        // a different address space and uses the EFI_MM_CPU_IO_PROTOCOL.\r
+        //\r
+        // This means that we are guaranteed that the following assignment\r
+        // will not be reordered before the eject. And, so we can safely\r
+        // do this write here.\r
+        //\r
+        mCpuHotEjectData->QemuSelectorMap[Idx] =\r
+          CPU_EJECT_QEMU_SELECTOR_INVALID;\r
+\r
+        DEBUG ((DEBUG_INFO, "%a: Unplugged ProcessorNum %u, "\r
+          "QemuSelector %Lu\n", __FUNCTION__, Idx, QemuSelector));\r
+      }\r
+    }\r
+\r
+    //\r
+    // We are done until the next hot-unplug; clear the handler.\r
+    //\r
+    // mCpuHotEjectData->Handler is a NOP for any CPU not under ejection.\r
+    // So, once we are done with all the ejections, we can safely reset it\r
+    // here since any CPU dereferencing it would only see either the old\r
+    // or the new value (since it is aligned at a natural boundary.)\r
+    //\r
+    mCpuHotEjectData->Handler = NULL;\r
+    return;\r
+  }\r
+\r
+  //\r
+  // Reached only on APs\r
+  //\r
+\r
+  //\r
+  // mCpuHotEjectData->QemuSelectorMap[ProcessorNum] is updated\r
+  // on the BSP in the ongoing SMI at two places:\r
+  //\r
+  // - UnplugCpus() where the BSP determines if a CPU is under ejection\r
+  //   or not. As a comment in UnplugCpus() at set-up, and in\r
+  //   SmmCpuFeaturesRendezvousExit() where it is dereferenced describe,\r
+  //   any such updates are guaranteed to be ordered-before the\r
+  //   dereference below.\r
+  //\r
+  // - EjectCpu() on the BSP (above) updates QemuSelectorMap[ProcessorNum]\r
+  //   for a CPU once it's ejected.\r
+  //\r
+  //   The CPU under ejection: might be executing anywhere between the\r
+  //   AllCpusInSync loop in SmiRendezvous(), to about to dereference\r
+  //   QemuSelectorMap[ProcessorNum].\r
+  //   As described in the comment above where we do the reset, this\r
+  //   is not a problem since the ejected CPU never sees the after value.\r
+  //   CPUs not-under ejection: never see any changes so they are fine.\r
+  //\r
   QemuSelector = mCpuHotEjectData->QemuSelectorMap[ProcessorNum];\r
   if (QemuSelector == CPU_EJECT_QEMU_SELECTOR_INVALID) {\r
     return;\r
@@ -495,11 +599,6 @@ CpuHotplugMmi (
   if (EFI_ERROR (Status)) {\r
     goto Fatal;\r
   }\r
-  if (ToUnplugCount > 0) {\r
-    DEBUG ((DEBUG_ERROR, "%a: hot-unplug is not supported yet\n",\r
-      __FUNCTION__));\r
-    goto Fatal;\r
-  }\r
 \r
   if (PluggedCount > 0) {\r
     Status = ProcessHotAddedCpus (mPluggedApicIds, PluggedCount);\r
index 2ec7a107a64d20be3fd7c7b3dc3561a9bfab56d8..d0e83102c13fc62c33340bace2449c5ad4baa49c 100644 (file)
@@ -34,6 +34,7 @@
 #define QEMU_CPUHP_STAT_ENABLED                BIT0\r
 #define QEMU_CPUHP_STAT_INSERT                 BIT1\r
 #define QEMU_CPUHP_STAT_REMOVE                 BIT2\r
+#define QEMU_CPUHP_STAT_EJECT                  BIT3\r
 #define QEMU_CPUHP_STAT_FW_REMOVE              BIT4\r
 \r
 #define QEMU_CPUHP_RW_CMD_DATA               0x8\r