]> git.proxmox.com Git - mirror_edk2.git/commitdiff
OvmfPkg/CpuHotplugSmm: add function for collecting CPUs with events
authorLaszlo Ersek <lersek@redhat.com>
Wed, 26 Feb 2020 22:11:49 +0000 (23:11 +0100)
committermergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Wed, 4 Mar 2020 12:22:07 +0000 (12:22 +0000)
Add a function that collects the APIC IDs of CPUs that have just been
hot-plugged, or are about to be hot-unplugged.

Pending events are only located and never cleared; QEMU's AML needs the
firmware to leave the status bits intact in the hotplug register block.

Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Igor Mammedov <imammedo@redhat.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Michael Kinney <michael.d.kinney@intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=1512
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20200226221156.29589-10-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Reviewed-by: Philippe Mathieu-Daude <philmd@redhat.com>
Tested-by: Boris Ostrovsky <boris.ostrovsky@oracle.com>
OvmfPkg/CpuHotplugSmm/ApicId.h [new file with mode: 0644]
OvmfPkg/CpuHotplugSmm/CpuHotplugSmm.inf
OvmfPkg/CpuHotplugSmm/QemuCpuhp.c
OvmfPkg/CpuHotplugSmm/QemuCpuhp.h
OvmfPkg/Include/IndustryStandard/QemuCpuHotplug.h

diff --git a/OvmfPkg/CpuHotplugSmm/ApicId.h b/OvmfPkg/CpuHotplugSmm/ApicId.h
new file mode 100644 (file)
index 0000000..3c36514
--- /dev/null
@@ -0,0 +1,23 @@
+/** @file\r
+  Type and macro definitions for representing and printing APIC IDs, compatibly\r
+  with the LocalApicLib and PrintLib classes, respectively.\r
+\r
+  Copyright (c) 2020, Red Hat, Inc.\r
+\r
+  SPDX-License-Identifier: BSD-2-Clause-Patent\r
+**/\r
+\r
+#ifndef APIC_ID_H_\r
+#define APIC_ID_H_\r
+\r
+//\r
+// The type that LocalApicLib represents an APIC ID with.\r
+//\r
+typedef UINT32 APIC_ID;\r
+\r
+//\r
+// The PrintLib conversion specification for formatting an APIC_ID.\r
+//\r
+#define FMT_APIC_ID "0x%08x"\r
+\r
+#endif // APIC_ID_H_\r
index ac4ca4c1f4f2c0eb39168e28d20ff4995321677c..ab690a9e5e205cb4d601391381239ade5b9bca27 100644 (file)
@@ -22,6 +22,7 @@
 #\r
 \r
 [Sources]\r
+  ApicId.h\r
   CpuHotplug.c\r
   QemuCpuhp.c\r
   QemuCpuhp.h\r
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
index 82f88f0b73bbe7dcb991108b79bdac6b06a9bd33..8adaa0ad91f0bac8530c7213212f05395e591f3e 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
@@ -16,6 +16,9 @@
 #define QEMU_CPUHP_H_\r
 \r
 #include <Protocol/MmCpuIo.h>  // EFI_MM_CPU_IO_PROTOCOL\r
+#include <Uefi/UefiBaseType.h> // EFI_STATUS\r
+\r
+#include "ApicId.h"            // APIC_ID\r
 \r
 UINT32\r
 QemuCpuhpReadCommandData2 (\r
@@ -44,4 +47,15 @@ QemuCpuhpWriteCommand (
   IN UINT8                        Command\r
   );\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
 #endif // QEMU_CPUHP_H_\r
index 3d013633501b3ee3892c95b0d2b8ded3c17c98ae..a34a6d3fae61b0cdd70f81d893a3e6a2082db9d7 100644 (file)
@@ -32,6 +32,8 @@
 \r
 #define QEMU_CPUHP_R_CPU_STAT                0x4\r
 #define QEMU_CPUHP_STAT_ENABLED                BIT0\r
+#define QEMU_CPUHP_STAT_INSERT                 BIT1\r
+#define QEMU_CPUHP_STAT_REMOVE                 BIT2\r
 \r
 #define QEMU_CPUHP_RW_CMD_DATA               0x8\r
 \r