/** @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 | |
QemuCpuhpWriteCpuStatus ( | |
IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo, | |
IN UINT8 CpuStatus | |
) | |
{ | |
EFI_STATUS Status; | |
Status = MmCpuIo->Io.Write ( | |
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 (); | |
} | |
} | |
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] ToUnplugSelectors The QEMU Selectors 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 *ToUnplugSelectors, | |
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 *ExtendSels; | |
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 "fw_remove" event. | |
// | |
if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0 || | |
(CpuStatus & QEMU_CPUHP_STAT_FW_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; | |
ExtendSels = NULL; | |
ExtendCount = PluggedCount; | |
} else if ((CpuStatus & QEMU_CPUHP_STAT_FW_REMOVE) != 0) { | |
// | |
// "fw_remove" event guarantees "enabled". | |
// | |
if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 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: fw_remove\n", | |
__FUNCTION__, CurrentSelector)); | |
ExtendIds = ToUnplugApicIds; | |
ExtendSels = ToUnplugSelectors; | |
ExtendCount = ToUnplugCount; | |
} else if ((CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) { | |
// | |
// Let the OSPM deal with the "remove" event. | |
// | |
DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: remove (ignored)\n", | |
__FUNCTION__, CurrentSelector)); | |
ExtendIds = NULL; | |
ExtendSels = NULL; | |
ExtendCount = NULL; | |
} else { | |
DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: no event\n", | |
__FUNCTION__, CurrentSelector)); | |
break; | |
} | |
ASSERT ((ExtendIds == NULL) == (ExtendCount == NULL)); | |
ASSERT ((ExtendSels == NULL) || (ExtendIds != NULL)); | |
if (ExtendIds != NULL) { | |
// | |
// Save the APIC ID of the CPU with the pending event, to the | |
// corresponding APIC ID array. | |
// For unplug events, also save the CurrentSelector. | |
// | |
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)); | |
if (ExtendSels != NULL) { | |
ExtendSels[(*ExtendCount)] = CurrentSelector; | |
} | |
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; | |
} |