]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/CpuHotplugSmm/QemuCpuhp.c
OvmfPkg/CpuHotplugSmm: add function for collecting CPUs with events
[mirror_edk2.git] / OvmfPkg / CpuHotplugSmm / QemuCpuhp.c
index 31e46f51934aa1e6d67bca8161f1f00d24a23806..8d4a6693c8d63c1ef224a3debb8bf58bf9f29e1f 100644 (file)
@@ -1,8 +1,8 @@
 /** @file\r
-  Simple wrapper functions that access QEMU's modern CPU hotplug register\r
-  block.\r
+  Simple wrapper functions and utility functions that access QEMU's modern CPU\r
+  hotplug register block.\r
 \r
-  These functions thinly wrap some of the registers described in\r
+  These functions manipulate some of the registers described in\r
   "docs/specs/acpi_cpu_hotplug.txt" in the QEMU source. IO Ports are accessed\r
   via EFI_MM_CPU_IO_PROTOCOL. If a protocol call fails, these functions don't\r
   return.\r
@@ -134,3 +134,168 @@ QemuCpuhpWriteCommand (
     CpuDeadLoop ();\r
   }\r
 }\r
+\r
+/**\r
+  Collect the APIC IDs of\r
+  - the CPUs that have been hot-plugged,\r
+  - the CPUs that are about to be hot-unplugged.\r
+\r
+  This function only scans for events -- it does not modify them -- in the\r
+  hotplug registers.\r
+\r
+  On error, the contents of the output parameters are undefined.\r
+\r
+  @param[in] MmCpuIo           The EFI_MM_CPU_IO_PROTOCOL instance for\r
+                               accessing IO Ports.\r
+\r
+  @param[in] PossibleCpuCount  The number of possible CPUs in the system. Must\r
+                               be positive.\r
+\r
+  @param[in] ApicIdCount       The number of elements each one of the\r
+                               PluggedApicIds and ToUnplugApicIds arrays can\r
+                               accommodate. Must be positive.\r
+\r
+  @param[out] PluggedApicIds   The APIC IDs of the CPUs that have been\r
+                               hot-plugged.\r
+\r
+  @param[out] PluggedCount     The number of filled-in APIC IDs in\r
+                               PluggedApicIds.\r
+\r
+  @param[out] ToUnplugApicIds  The APIC IDs of the CPUs that are about to be\r
+                               hot-unplugged.\r
+\r
+  @param[out] ToUnplugCount    The number of filled-in APIC IDs in\r
+                               ToUnplugApicIds.\r
+\r
+  @retval EFI_INVALID_PARAMETER  PossibleCpuCount is zero, or ApicIdCount is\r
+                                 zero.\r
+\r
+  @retval EFI_PROTOCOL_ERROR     Invalid bitmap detected in the\r
+                                 QEMU_CPUHP_R_CPU_STAT register.\r
+\r
+  @retval EFI_BUFFER_TOO_SMALL   There was an attempt to place more than\r
+                                 ApicIdCount APIC IDs into one of the\r
+                                 PluggedApicIds and ToUnplugApicIds arrays.\r
+\r
+  @retval EFI_SUCCESS            Output parameters have been set successfully.\r
+**/\r
+EFI_STATUS\r
+QemuCpuhpCollectApicIds (\r
+  IN  CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,\r
+  IN  UINT32                       PossibleCpuCount,\r
+  IN  UINT32                       ApicIdCount,\r
+  OUT APIC_ID                      *PluggedApicIds,\r
+  OUT UINT32                       *PluggedCount,\r
+  OUT APIC_ID                      *ToUnplugApicIds,\r
+  OUT UINT32                       *ToUnplugCount\r
+  )\r
+{\r
+  UINT32 CurrentSelector;\r
+\r
+  if (PossibleCpuCount == 0 || ApicIdCount == 0) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  *PluggedCount = 0;\r
+  *ToUnplugCount = 0;\r
+\r
+  CurrentSelector = 0;\r
+  do {\r
+    UINT32  PendingSelector;\r
+    UINT8   CpuStatus;\r
+    APIC_ID *ExtendIds;\r
+    UINT32  *ExtendCount;\r
+    APIC_ID NewApicId;\r
+\r
+    //\r
+    // Write CurrentSelector (which is valid) to the CPU selector register.\r
+    // Consequences:\r
+    //\r
+    // - Other register accesses will be permitted.\r
+    //\r
+    // - The QEMU_CPUHP_CMD_GET_PENDING command will start scanning for a CPU\r
+    //   with pending events at CurrentSelector (inclusive).\r
+    //\r
+    QemuCpuhpWriteCpuSelector (MmCpuIo, CurrentSelector);\r
+    //\r
+    // Write the QEMU_CPUHP_CMD_GET_PENDING command. Consequences\r
+    // (independently of each other):\r
+    //\r
+    // - If there is a CPU with pending events, starting at CurrentSelector\r
+    //   (inclusive), the CPU selector will be updated to that CPU. Note that\r
+    //   the scanning in QEMU may wrap around, because we must never clear the\r
+    //   event bits.\r
+    //\r
+    // - The QEMU_CPUHP_RW_CMD_DATA register will return the (possibly updated)\r
+    //   CPU selector value.\r
+    //\r
+    QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);\r
+    PendingSelector = QemuCpuhpReadCommandData (MmCpuIo);\r
+    if (PendingSelector < CurrentSelector) {\r
+      DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u PendingSelector=%u: "\r
+        "wrap-around\n", __FUNCTION__, CurrentSelector, PendingSelector));\r
+      break;\r
+    }\r
+    CurrentSelector = PendingSelector;\r
+\r
+    //\r
+    // Check the known status / event bits for the currently selected CPU.\r
+    //\r
+    CpuStatus = QemuCpuhpReadCpuStatus (MmCpuIo);\r
+    if ((CpuStatus & QEMU_CPUHP_STAT_INSERT) != 0) {\r
+      //\r
+      // The "insert" event guarantees the "enabled" status; plus it excludes\r
+      // the "remove" event.\r
+      //\r
+      if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0 ||\r
+          (CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) {\r
+        DEBUG ((DEBUG_ERROR, "%a: CurrentSelector=%u CpuStatus=0x%x: "\r
+          "inconsistent CPU status\n", __FUNCTION__, CurrentSelector,\r
+          CpuStatus));\r
+        return EFI_PROTOCOL_ERROR;\r
+      }\r
+\r
+      DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: insert\n", __FUNCTION__,\r
+        CurrentSelector));\r
+\r
+      ExtendIds   = PluggedApicIds;\r
+      ExtendCount = PluggedCount;\r
+    } else if ((CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) {\r
+      DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: remove\n", __FUNCTION__,\r
+        CurrentSelector));\r
+\r
+      ExtendIds   = ToUnplugApicIds;\r
+      ExtendCount = ToUnplugCount;\r
+    } else {\r
+      DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: no event\n",\r
+        __FUNCTION__, CurrentSelector));\r
+      break;\r
+    }\r
+\r
+    //\r
+    // Save the APIC ID of the CPU with the pending event, to the corresponding\r
+    // APIC ID array.\r
+    //\r
+    if (*ExtendCount == ApicIdCount) {\r
+      DEBUG ((DEBUG_ERROR, "%a: APIC ID array too small\n", __FUNCTION__));\r
+      return EFI_BUFFER_TOO_SMALL;\r
+    }\r
+    QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_ARCH_ID);\r
+    NewApicId = QemuCpuhpReadCommandData (MmCpuIo);\r
+    DEBUG ((DEBUG_VERBOSE, "%a: ApicId=" FMT_APIC_ID "\n", __FUNCTION__,\r
+      NewApicId));\r
+    ExtendIds[(*ExtendCount)++] = NewApicId;\r
+\r
+    //\r
+    // We've processed the CPU with (known) pending events, but we must never\r
+    // clear events. Therefore we need to advance past this CPU manually;\r
+    // otherwise, QEMU_CPUHP_CMD_GET_PENDING would stick to the currently\r
+    // selected CPU.\r
+    //\r
+    CurrentSelector++;\r
+  } while (CurrentSelector < PossibleCpuCount);\r
+\r
+  DEBUG ((DEBUG_VERBOSE, "%a: PluggedCount=%u ToUnplugCount=%u\n",\r
+    __FUNCTION__, *PluggedCount, *ToUnplugCount));\r
+  return EFI_SUCCESS;\r
+}\r