audk/OvmfPkg/CpuHotplugSmm/QemuCpuhp.c

302 lines
9.5 KiB
C

/** @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;
}