diff options
Diffstat (limited to 'roms/edk2/OvmfPkg/CpuHotplugSmm/QemuCpuhp.c')
-rw-r--r-- | roms/edk2/OvmfPkg/CpuHotplugSmm/QemuCpuhp.c | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/roms/edk2/OvmfPkg/CpuHotplugSmm/QemuCpuhp.c b/roms/edk2/OvmfPkg/CpuHotplugSmm/QemuCpuhp.c new file mode 100644 index 000000000..8d4a6693c --- /dev/null +++ b/roms/edk2/OvmfPkg/CpuHotplugSmm/QemuCpuhp.c @@ -0,0 +1,301 @@ +/** @file
+ Simple wrapper functions and utility functions that access QEMU's modern CPU
+ hotplug register block.
+
+ These functions manipulate some of the registers described in
+ "docs/specs/acpi_cpu_hotplug.txt" in the QEMU source. IO Ports are accessed
+ via EFI_MM_CPU_IO_PROTOCOL. If a protocol call fails, these functions don't
+ return.
+
+ Copyright (c) 2020, Red Hat, Inc.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include <IndustryStandard/Q35MchIch9.h> // ICH9_CPU_HOTPLUG_BASE
+#include <IndustryStandard/QemuCpuHotplug.h> // QEMU_CPUHP_R_CMD_DATA2
+#include <Library/BaseLib.h> // CpuDeadLoop()
+#include <Library/DebugLib.h> // DEBUG()
+
+#include "QemuCpuhp.h"
+
+UINT32
+QemuCpuhpReadCommandData2 (
+ IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
+ )
+{
+ UINT32 CommandData2;
+ EFI_STATUS Status;
+
+ CommandData2 = 0;
+ Status = MmCpuIo->Io.Read (
+ MmCpuIo,
+ MM_IO_UINT32,
+ ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CMD_DATA2,
+ 1,
+ &CommandData2
+ );
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
+ ASSERT (FALSE);
+ CpuDeadLoop ();
+ }
+ return CommandData2;
+}
+
+UINT8
+QemuCpuhpReadCpuStatus (
+ IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
+ )
+{
+ UINT8 CpuStatus;
+ EFI_STATUS Status;
+
+ CpuStatus = 0;
+ Status = MmCpuIo->Io.Read (
+ MmCpuIo,
+ MM_IO_UINT8,
+ ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CPU_STAT,
+ 1,
+ &CpuStatus
+ );
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
+ ASSERT (FALSE);
+ CpuDeadLoop ();
+ }
+ return CpuStatus;
+}
+
+UINT32
+QemuCpuhpReadCommandData (
+ IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
+ )
+{
+ UINT32 CommandData;
+ EFI_STATUS Status;
+
+ CommandData = 0;
+ Status = MmCpuIo->Io.Read (
+ MmCpuIo,
+ MM_IO_UINT32,
+ ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_RW_CMD_DATA,
+ 1,
+ &CommandData
+ );
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
+ ASSERT (FALSE);
+ CpuDeadLoop ();
+ }
+ return CommandData;
+}
+
+VOID
+QemuCpuhpWriteCpuSelector (
+ IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
+ IN UINT32 Selector
+ )
+{
+ EFI_STATUS Status;
+
+ Status = MmCpuIo->Io.Write (
+ MmCpuIo,
+ MM_IO_UINT32,
+ ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL,
+ 1,
+ &Selector
+ );
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
+ ASSERT (FALSE);
+ CpuDeadLoop ();
+ }
+}
+
+VOID
+QemuCpuhpWriteCommand (
+ IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
+ IN UINT8 Command
+ )
+{
+ EFI_STATUS Status;
+
+ Status = MmCpuIo->Io.Write (
+ MmCpuIo,
+ MM_IO_UINT8,
+ ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CMD,
+ 1,
+ &Command
+ );
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
+ ASSERT (FALSE);
+ CpuDeadLoop ();
+ }
+}
+
+/**
+ Collect the APIC IDs of
+ - the CPUs that have been hot-plugged,
+ - the CPUs that are about to be hot-unplugged.
+
+ This function only scans for events -- it does not modify them -- in the
+ hotplug registers.
+
+ On error, the contents of the output parameters are undefined.
+
+ @param[in] MmCpuIo The EFI_MM_CPU_IO_PROTOCOL instance for
+ accessing IO Ports.
+
+ @param[in] PossibleCpuCount The number of possible CPUs in the system. Must
+ be positive.
+
+ @param[in] ApicIdCount The number of elements each one of the
+ PluggedApicIds and ToUnplugApicIds arrays can
+ accommodate. Must be positive.
+
+ @param[out] PluggedApicIds The APIC IDs of the CPUs that have been
+ hot-plugged.
+
+ @param[out] PluggedCount The number of filled-in APIC IDs in
+ PluggedApicIds.
+
+ @param[out] ToUnplugApicIds The APIC IDs of the CPUs that are about to be
+ hot-unplugged.
+
+ @param[out] ToUnplugCount The number of filled-in APIC IDs in
+ ToUnplugApicIds.
+
+ @retval EFI_INVALID_PARAMETER PossibleCpuCount is zero, or ApicIdCount is
+ zero.
+
+ @retval EFI_PROTOCOL_ERROR Invalid bitmap detected in the
+ QEMU_CPUHP_R_CPU_STAT register.
+
+ @retval EFI_BUFFER_TOO_SMALL There was an attempt to place more than
+ ApicIdCount APIC IDs into one of the
+ PluggedApicIds and ToUnplugApicIds arrays.
+
+ @retval EFI_SUCCESS Output parameters have been set successfully.
+**/
+EFI_STATUS
+QemuCpuhpCollectApicIds (
+ IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
+ IN UINT32 PossibleCpuCount,
+ IN UINT32 ApicIdCount,
+ OUT APIC_ID *PluggedApicIds,
+ OUT UINT32 *PluggedCount,
+ OUT APIC_ID *ToUnplugApicIds,
+ OUT UINT32 *ToUnplugCount
+ )
+{
+ UINT32 CurrentSelector;
+
+ if (PossibleCpuCount == 0 || ApicIdCount == 0) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ *PluggedCount = 0;
+ *ToUnplugCount = 0;
+
+ CurrentSelector = 0;
+ do {
+ UINT32 PendingSelector;
+ UINT8 CpuStatus;
+ APIC_ID *ExtendIds;
+ UINT32 *ExtendCount;
+ APIC_ID NewApicId;
+
+ //
+ // Write CurrentSelector (which is valid) to the CPU selector register.
+ // Consequences:
+ //
+ // - Other register accesses will be permitted.
+ //
+ // - The QEMU_CPUHP_CMD_GET_PENDING command will start scanning for a CPU
+ // with pending events at CurrentSelector (inclusive).
+ //
+ QemuCpuhpWriteCpuSelector (MmCpuIo, CurrentSelector);
+ //
+ // Write the QEMU_CPUHP_CMD_GET_PENDING command. Consequences
+ // (independently of each other):
+ //
+ // - If there is a CPU with pending events, starting at CurrentSelector
+ // (inclusive), the CPU selector will be updated to that CPU. Note that
+ // the scanning in QEMU may wrap around, because we must never clear the
+ // event bits.
+ //
+ // - The QEMU_CPUHP_RW_CMD_DATA register will return the (possibly updated)
+ // CPU selector value.
+ //
+ QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);
+ PendingSelector = QemuCpuhpReadCommandData (MmCpuIo);
+ if (PendingSelector < CurrentSelector) {
+ DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u PendingSelector=%u: "
+ "wrap-around\n", __FUNCTION__, CurrentSelector, PendingSelector));
+ break;
+ }
+ CurrentSelector = PendingSelector;
+
+ //
+ // Check the known status / event bits for the currently selected CPU.
+ //
+ CpuStatus = QemuCpuhpReadCpuStatus (MmCpuIo);
+ if ((CpuStatus & QEMU_CPUHP_STAT_INSERT) != 0) {
+ //
+ // The "insert" event guarantees the "enabled" status; plus it excludes
+ // the "remove" event.
+ //
+ if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0 ||
+ (CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) {
+ DEBUG ((DEBUG_ERROR, "%a: CurrentSelector=%u CpuStatus=0x%x: "
+ "inconsistent CPU status\n", __FUNCTION__, CurrentSelector,
+ CpuStatus));
+ return EFI_PROTOCOL_ERROR;
+ }
+
+ DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: insert\n", __FUNCTION__,
+ CurrentSelector));
+
+ ExtendIds = PluggedApicIds;
+ ExtendCount = PluggedCount;
+ } else if ((CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) {
+ DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: remove\n", __FUNCTION__,
+ CurrentSelector));
+
+ ExtendIds = ToUnplugApicIds;
+ ExtendCount = ToUnplugCount;
+ } else {
+ DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: no event\n",
+ __FUNCTION__, CurrentSelector));
+ break;
+ }
+
+ //
+ // Save the APIC ID of the CPU with the pending event, to the corresponding
+ // APIC ID array.
+ //
+ if (*ExtendCount == ApicIdCount) {
+ DEBUG ((DEBUG_ERROR, "%a: APIC ID array too small\n", __FUNCTION__));
+ return EFI_BUFFER_TOO_SMALL;
+ }
+ QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_ARCH_ID);
+ NewApicId = QemuCpuhpReadCommandData (MmCpuIo);
+ DEBUG ((DEBUG_VERBOSE, "%a: ApicId=" FMT_APIC_ID "\n", __FUNCTION__,
+ NewApicId));
+ ExtendIds[(*ExtendCount)++] = NewApicId;
+
+ //
+ // We've processed the CPU with (known) pending events, but we must never
+ // clear events. Therefore we need to advance past this CPU manually;
+ // otherwise, QEMU_CPUHP_CMD_GET_PENDING would stick to the currently
+ // selected CPU.
+ //
+ CurrentSelector++;
+ } while (CurrentSelector < PossibleCpuCount);
+
+ DEBUG ((DEBUG_VERBOSE, "%a: PluggedCount=%u ToUnplugCount=%u\n",
+ __FUNCTION__, *PluggedCount, *ToUnplugCount));
+ return EFI_SUCCESS;
+}
|