OvmfPkg/CpuHotplugSmm: add function for collecting CPUs with events

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>
This commit is contained in:
Laszlo Ersek 2020-02-26 23:11:49 +01:00 committed by mergify[bot]
parent f668e78871
commit 763840c9ab
5 changed files with 211 additions and 6 deletions

View File

@ -0,0 +1,23 @@
/** @file
Type and macro definitions for representing and printing APIC IDs, compatibly
with the LocalApicLib and PrintLib classes, respectively.
Copyright (c) 2020, Red Hat, Inc.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#ifndef APIC_ID_H_
#define APIC_ID_H_
//
// The type that LocalApicLib represents an APIC ID with.
//
typedef UINT32 APIC_ID;
//
// The PrintLib conversion specification for formatting an APIC_ID.
//
#define FMT_APIC_ID "0x%08x"
#endif // APIC_ID_H_

View File

@ -22,6 +22,7 @@
# #
[Sources] [Sources]
ApicId.h
CpuHotplug.c CpuHotplug.c
QemuCpuhp.c QemuCpuhp.c
QemuCpuhp.h QemuCpuhp.h

View File

@ -1,8 +1,8 @@
/** @file /** @file
Simple wrapper functions that access QEMU's modern CPU hotplug register Simple wrapper functions and utility functions that access QEMU's modern CPU
block. hotplug register block.
These functions thinly wrap some of the registers described in These functions manipulate some of the registers described in
"docs/specs/acpi_cpu_hotplug.txt" in the QEMU source. IO Ports are accessed "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 via EFI_MM_CPU_IO_PROTOCOL. If a protocol call fails, these functions don't
return. return.
@ -134,3 +134,168 @@ QemuCpuhpWriteCommand (
CpuDeadLoop (); 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;
}

View File

@ -1,8 +1,8 @@
/** @file /** @file
Simple wrapper functions that access QEMU's modern CPU hotplug register Simple wrapper functions and utility functions that access QEMU's modern CPU
block. hotplug register block.
These functions thinly wrap some of the registers described in These functions manipulate some of the registers described in
"docs/specs/acpi_cpu_hotplug.txt" in the QEMU source. IO Ports are accessed "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 via EFI_MM_CPU_IO_PROTOCOL. If a protocol call fails, these functions don't
return. return.
@ -16,6 +16,9 @@
#define QEMU_CPUHP_H_ #define QEMU_CPUHP_H_
#include <Protocol/MmCpuIo.h> // EFI_MM_CPU_IO_PROTOCOL #include <Protocol/MmCpuIo.h> // EFI_MM_CPU_IO_PROTOCOL
#include <Uefi/UefiBaseType.h> // EFI_STATUS
#include "ApicId.h" // APIC_ID
UINT32 UINT32
QemuCpuhpReadCommandData2 ( QemuCpuhpReadCommandData2 (
@ -44,4 +47,15 @@ QemuCpuhpWriteCommand (
IN UINT8 Command IN UINT8 Command
); );
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
);
#endif // QEMU_CPUHP_H_ #endif // QEMU_CPUHP_H_

View File

@ -32,6 +32,8 @@
#define QEMU_CPUHP_R_CPU_STAT 0x4 #define QEMU_CPUHP_R_CPU_STAT 0x4
#define QEMU_CPUHP_STAT_ENABLED BIT0 #define QEMU_CPUHP_STAT_ENABLED BIT0
#define QEMU_CPUHP_STAT_INSERT BIT1
#define QEMU_CPUHP_STAT_REMOVE BIT2
#define QEMU_CPUHP_RW_CMD_DATA 0x8 #define QEMU_CPUHP_RW_CMD_DATA 0x8