mirror of https://github.com/acidanthera/audk.git
408 lines
12 KiB
C
408 lines
12 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
|
|
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;
|
|
}
|