blob: d50416302677ec8616615cae7f37997cf96013fc [file] [log] [blame]
/** @file
Root SMI handler for VCPU hotplug SMIs.
Copyright (c) 2020, Red Hat, Inc.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <CpuHotPlugData.h> // CPU_HOT_PLUG_DATA
#include <IndustryStandard/Q35MchIch9.h> // ICH9_APM_CNT
#include <IndustryStandard/QemuCpuHotplug.h> // QEMU_CPUHP_CMD_GET_PENDING
#include <Library/BaseLib.h> // CpuDeadLoop()
#include <Library/CpuLib.h> // CpuSleep()
#include <Library/DebugLib.h> // ASSERT()
#include <Library/MmServicesTableLib.h> // gMmst
#include <Library/PcdLib.h> // PcdGetBool()
#include <Library/SafeIntLib.h> // SafeUintnSub()
#include <Pcd/CpuHotEjectData.h> // CPU_HOT_EJECT_DATA
#include <Protocol/MmCpuIo.h> // EFI_MM_CPU_IO_PROTOCOL
#include <Protocol/SmmCpuService.h> // EFI_SMM_CPU_SERVICE_PROTOCOL
#include <Register/Intel/ArchitecturalMsr.h> // MSR_IA32_APIC_BASE_REGISTER
#include <Uefi/UefiBaseType.h> // EFI_STATUS
#include "ApicId.h" // APIC_ID
#include "QemuCpuhp.h" // QemuCpuhpWriteCpuSelector()
#include "Smbase.h" // SmbaseAllocatePostSmmPen()
//
// We use this protocol for accessing IO Ports.
//
STATIC EFI_MM_CPU_IO_PROTOCOL *mMmCpuIo;
//
// The following protocol is used to report the addition or removal of a CPU to
// the SMM CPU driver (PiSmmCpuDxeSmm).
//
STATIC EFI_SMM_CPU_SERVICE_PROTOCOL *mMmCpuService;
//
// These structures serve as communication side-channels between the
// EFI_SMM_CPU_SERVICE_PROTOCOL consumer (i.e., this driver) and provider
// (i.e., PiSmmCpuDxeSmm).
//
STATIC CPU_HOT_PLUG_DATA *mCpuHotPlugData;
STATIC CPU_HOT_EJECT_DATA *mCpuHotEjectData;
//
// SMRAM arrays for fetching the APIC IDs of processors with pending events (of
// known event types), for the time of just one MMI.
//
// The lifetimes of these arrays match that of this driver only because we
// don't want to allocate SMRAM at OS runtime, and potentially fail (or
// fragment the SMRAM map).
//
// The first array stores APIC IDs for hot-plug events, the second and the
// third store APIC IDs and QEMU CPU Selectors (both indexed similarly) for
// hot-unplug events. All of these provide room for "possible CPU count" minus
// one elements as we don't expect every possible CPU to appear, or disappear,
// in a single MMI. The numbers of used (populated) elements in the arrays are
// determined on every MMI separately.
//
STATIC APIC_ID *mPluggedApicIds;
STATIC APIC_ID *mToUnplugApicIds;
STATIC UINT32 *mToUnplugSelectors;
//
// Address of the non-SMRAM reserved memory page that contains the Post-SMM Pen
// for hot-added CPUs.
//
STATIC UINT32 mPostSmmPenAddress;
//
// Represents the registration of the CPU Hotplug MMI handler.
//
STATIC EFI_HANDLE mDispatchHandle;
/**
Process CPUs that have been hot-added, per QemuCpuhpCollectApicIds().
For each such CPU, relocate the SMBASE, and report the CPU to PiSmmCpuDxeSmm
via EFI_SMM_CPU_SERVICE_PROTOCOL. If the supposedly hot-added CPU is already
known, skip it silently.
@param[in] PluggedApicIds The APIC IDs of the CPUs that have been
hot-plugged.
@param[in] PluggedCount The number of filled-in APIC IDs in
PluggedApicIds.
@retval EFI_SUCCESS CPUs corresponding to all the APIC IDs are
populated.
@retval EFI_OUT_OF_RESOURCES Out of APIC ID space in "mCpuHotPlugData".
@return Error codes propagated from SmbaseRelocate()
and mMmCpuService->AddProcessor().
**/
STATIC
EFI_STATUS
ProcessHotAddedCpus (
IN APIC_ID *PluggedApicIds,
IN UINT32 PluggedCount
)
{
EFI_STATUS Status;
UINT32 PluggedIdx;
UINT32 NewSlot;
//
// The Post-SMM Pen need not be reinstalled multiple times within a single
// root MMI handling. Even reinstalling once per root MMI is only prudence;
// in theory installing the pen in the driver's entry point function should
// suffice.
//
SmbaseReinstallPostSmmPen (mPostSmmPenAddress);
PluggedIdx = 0;
NewSlot = 0;
while (PluggedIdx < PluggedCount) {
APIC_ID NewApicId;
UINT32 CheckSlot;
UINTN NewProcessorNumberByProtocol;
NewApicId = PluggedApicIds[PluggedIdx];
//
// Check if the supposedly hot-added CPU is already known to us.
//
for (CheckSlot = 0;
CheckSlot < mCpuHotPlugData->ArrayLength;
CheckSlot++)
{
if (mCpuHotPlugData->ApicId[CheckSlot] == NewApicId) {
break;
}
}
if (CheckSlot < mCpuHotPlugData->ArrayLength) {
DEBUG ((
DEBUG_VERBOSE,
"%a: APIC ID " FMT_APIC_ID " was hot-plugged "
"before; ignoring it\n",
__func__,
NewApicId
));
PluggedIdx++;
continue;
}
//
// Find the first empty slot in CPU_HOT_PLUG_DATA.
//
while (NewSlot < mCpuHotPlugData->ArrayLength &&
mCpuHotPlugData->ApicId[NewSlot] != MAX_UINT64)
{
NewSlot++;
}
if (NewSlot == mCpuHotPlugData->ArrayLength) {
DEBUG ((
DEBUG_ERROR,
"%a: no room for APIC ID " FMT_APIC_ID "\n",
__func__,
NewApicId
));
return EFI_OUT_OF_RESOURCES;
}
//
// Store the APIC ID of the new processor to the slot.
//
mCpuHotPlugData->ApicId[NewSlot] = NewApicId;
//
// Relocate the SMBASE of the new CPU.
//
Status = SmbaseRelocate (
NewApicId,
mCpuHotPlugData->SmBase[NewSlot],
mPostSmmPenAddress
);
if (EFI_ERROR (Status)) {
goto RevokeNewSlot;
}
//
// Add the new CPU with EFI_SMM_CPU_SERVICE_PROTOCOL.
//
Status = mMmCpuService->AddProcessor (
mMmCpuService,
NewApicId,
&NewProcessorNumberByProtocol
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: AddProcessor(" FMT_APIC_ID "): %r\n",
__func__,
NewApicId,
Status
));
goto RevokeNewSlot;
}
DEBUG ((
DEBUG_INFO,
"%a: hot-added APIC ID " FMT_APIC_ID ", SMBASE 0x%Lx, "
"EFI_SMM_CPU_SERVICE_PROTOCOL assigned number %Lu\n",
__func__,
NewApicId,
(UINT64)mCpuHotPlugData->SmBase[NewSlot],
(UINT64)NewProcessorNumberByProtocol
));
NewSlot++;
PluggedIdx++;
}
//
// We've processed this batch of hot-added CPUs.
//
return EFI_SUCCESS;
RevokeNewSlot:
mCpuHotPlugData->ApicId[NewSlot] = MAX_UINT64;
return Status;
}
/**
EjectCpu needs to know the BSP at SMI exit at a point when
some of the EFI_SMM_CPU_SERVICE_PROTOCOL state has been torn
down.
Reuse the logic from OvmfPkg::PlatformSmmBspElection() to
do that.
@retval TRUE If the CPU executing this function is the BSP.
@retval FALSE If the CPU executing this function is an AP.
**/
STATIC
BOOLEAN
CheckIfBsp (
VOID
)
{
MSR_IA32_APIC_BASE_REGISTER ApicBaseMsr;
BOOLEAN IsBsp;
ApicBaseMsr.Uint64 = AsmReadMsr64 (MSR_IA32_APIC_BASE);
IsBsp = (BOOLEAN)(ApicBaseMsr.Bits.BSP == 1);
return IsBsp;
}
/**
CPU Hot-eject handler, called from SmmCpuFeaturesRendezvousExit()
on each CPU at exit from SMM.
If, the executing CPU is neither the BSP, nor being ejected, nothing
to be done.
If, the executing CPU is being ejected, wait in a halted loop
until ejected.
If, the executing CPU is the BSP, set QEMU CPU status to eject
for CPUs being ejected.
@param[in] ProcessorNum ProcessorNum denotes the CPU exiting SMM,
and will be used as an index into
CPU_HOT_EJECT_DATA->QemuSelectorMap. It is
identical to the processor handle number in
EFI_SMM_CPU_SERVICE_PROTOCOL.
**/
VOID
EFIAPI
EjectCpu (
IN UINTN ProcessorNum
)
{
UINT64 QemuSelector;
if (CheckIfBsp ()) {
UINT32 Idx;
for (Idx = 0; Idx < mCpuHotEjectData->ArrayLength; Idx++) {
QemuSelector = mCpuHotEjectData->QemuSelectorMap[Idx];
if (QemuSelector != CPU_EJECT_QEMU_SELECTOR_INVALID) {
//
// This to-be-ejected-CPU has already received the BSP's SMI exit
// signal and will execute SmmCpuFeaturesRendezvousExit()
// followed by this callback or is already penned in the
// CpuSleep() loop below.
//
// Tell QEMU to context-switch it out.
//
QemuCpuhpWriteCpuSelector (mMmCpuIo, (UINT32)QemuSelector);
QemuCpuhpWriteCpuStatus (mMmCpuIo, QEMU_CPUHP_STAT_EJECT);
//
// Now that we've ejected the CPU corresponding to QemuSelectorMap[Idx],
// clear its eject status to ensure that an invalid future SMI does
// not end up trying a spurious eject or a newly hotplugged CPU does
// not get penned in the CpuSleep() loop.
//
// Note that the QemuCpuhpWriteCpuStatus() command above is a write to
// a different address space and uses the EFI_MM_CPU_IO_PROTOCOL.
//
// This means that we are guaranteed that the following assignment
// will not be reordered before the eject. And, so we can safely
// do this write here.
//
mCpuHotEjectData->QemuSelectorMap[Idx] =
CPU_EJECT_QEMU_SELECTOR_INVALID;
DEBUG ((
DEBUG_INFO,
"%a: Unplugged ProcessorNum %u, "
"QemuSelector %Lu\n",
__func__,
Idx,
QemuSelector
));
}
}
//
// We are done until the next hot-unplug; clear the handler.
//
// mCpuHotEjectData->Handler is a NOP for any CPU not under ejection.
// So, once we are done with all the ejections, we can safely reset it
// here since any CPU dereferencing it would only see either the old
// or the new value (since it is aligned at a natural boundary.)
//
mCpuHotEjectData->Handler = NULL;
return;
}
//
// Reached only on APs
//
//
// mCpuHotEjectData->QemuSelectorMap[ProcessorNum] is updated
// on the BSP in the ongoing SMI at two places:
//
// - UnplugCpus() where the BSP determines if a CPU is under ejection
// or not. As a comment in UnplugCpus() at set-up, and in
// SmmCpuFeaturesRendezvousExit() where it is dereferenced describe,
// any such updates are guaranteed to be ordered-before the
// dereference below.
//
// - EjectCpu() on the BSP (above) updates QemuSelectorMap[ProcessorNum]
// for a CPU once it's ejected.
//
// The CPU under ejection: might be executing anywhere between the
// AllCpusInSync loop in SmiRendezvous(), to about to dereference
// QemuSelectorMap[ProcessorNum].
// As described in the comment above where we do the reset, this
// is not a problem since the ejected CPU never sees the after value.
// CPUs not-under ejection: never see any changes so they are fine.
//
QemuSelector = mCpuHotEjectData->QemuSelectorMap[ProcessorNum];
if (QemuSelector == CPU_EJECT_QEMU_SELECTOR_INVALID) {
return;
}
//
// APs being unplugged get here from SmmCpuFeaturesRendezvousExit()
// after having been cleared to exit the SMI and so have no SMM
// processing remaining.
//
// Keep them penned here until the BSP tells QEMU to eject them.
//
for ( ; ;) {
DisableInterrupts ();
CpuSleep ();
}
}
/**
Process to be hot-unplugged CPUs, per QemuCpuhpCollectApicIds().
For each such CPU, report the CPU to PiSmmCpuDxeSmm via
EFI_SMM_CPU_SERVICE_PROTOCOL and stash the QEMU Cpu Selectors for later
ejection. If the to be hot-unplugged CPU is unknown, skip it silently.
Additonally, if we do stash any Cpu Selectors, also install a CPU eject
handler which would handle the ejection.
@param[in] ToUnplugApicIds The APIC IDs of the CPUs that are about to be
hot-unplugged.
@param[in] ToUnplugSelectors The QEMU Selectors of the CPUs that are about to
be hot-unplugged.
@param[in] ToUnplugCount The number of filled-in APIC IDs in
ToUnplugApicIds.
@retval EFI_ALREADY_STARTED For the ProcessorNum that
EFI_SMM_CPU_SERVICE_PROTOCOL had assigned to
one of the APIC IDs in ToUnplugApicIds,
mCpuHotEjectData->QemuSelectorMap already has
the QemuSelector value stashed. (This should
never happen.)
@retval EFI_SUCCESS Known APIC IDs have been removed from SMM data
structures.
@return Error codes propagated from
mMmCpuService->RemoveProcessor().
**/
STATIC
EFI_STATUS
UnplugCpus (
IN APIC_ID *ToUnplugApicIds,
IN UINT32 *ToUnplugSelectors,
IN UINT32 ToUnplugCount
)
{
EFI_STATUS Status;
UINT32 ToUnplugIdx;
UINT32 EjectCount;
UINTN ProcessorNum;
ToUnplugIdx = 0;
EjectCount = 0;
while (ToUnplugIdx < ToUnplugCount) {
APIC_ID RemoveApicId;
UINT32 QemuSelector;
RemoveApicId = ToUnplugApicIds[ToUnplugIdx];
QemuSelector = ToUnplugSelectors[ToUnplugIdx];
//
// mCpuHotPlugData->ApicId maps ProcessorNum -> ApicId. Use RemoveApicId
// to find the corresponding ProcessorNum for the CPU to be removed.
//
// With this we can establish a 3 way mapping:
// APIC_ID -- ProcessorNum -- QemuSelector
//
// We stash the ProcessorNum -> QemuSelector mapping so it can later be
// used for CPU hot-eject in SmmCpuFeaturesRendezvousExit() context (where
// we only have ProcessorNum available.)
//
for (ProcessorNum = 0;
ProcessorNum < mCpuHotPlugData->ArrayLength;
ProcessorNum++)
{
if (mCpuHotPlugData->ApicId[ProcessorNum] == RemoveApicId) {
break;
}
}
//
// Ignore the unplug if APIC ID not found
//
if (ProcessorNum == mCpuHotPlugData->ArrayLength) {
DEBUG ((
DEBUG_VERBOSE,
"%a: did not find APIC ID " FMT_APIC_ID
" to unplug\n",
__func__,
RemoveApicId
));
ToUnplugIdx++;
continue;
}
//
// Mark ProcessorNum for removal from SMM data structures
//
Status = mMmCpuService->RemoveProcessor (mMmCpuService, ProcessorNum);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: RemoveProcessor(" FMT_APIC_ID "): %r\n",
__func__,
RemoveApicId,
Status
));
return Status;
}
if (mCpuHotEjectData->QemuSelectorMap[ProcessorNum] !=
CPU_EJECT_QEMU_SELECTOR_INVALID)
{
//
// mCpuHotEjectData->QemuSelectorMap[ProcessorNum] is set to
// CPU_EJECT_QEMU_SELECTOR_INVALID when mCpuHotEjectData->QemuSelectorMap
// is allocated, and once the subject processsor is ejected.
//
// Additionally, mMmCpuService->RemoveProcessor(ProcessorNum) invalidates
// mCpuHotPlugData->ApicId[ProcessorNum], so a given ProcessorNum can
// never match more than one APIC ID -- nor, by transitivity, designate
// more than one QemuSelector -- in a single invocation of UnplugCpus().
//
DEBUG ((
DEBUG_ERROR,
"%a: ProcessorNum %Lu maps to QemuSelector %Lu, "
"cannot also map to %u\n",
__func__,
(UINT64)ProcessorNum,
mCpuHotEjectData->QemuSelectorMap[ProcessorNum],
QemuSelector
));
return EFI_ALREADY_STARTED;
}
//
// Stash the QemuSelector so we can do the actual ejection later.
//
mCpuHotEjectData->QemuSelectorMap[ProcessorNum] = (UINT64)QemuSelector;
DEBUG ((
DEBUG_INFO,
"%a: Started hot-unplug on ProcessorNum %Lu, APIC ID "
FMT_APIC_ID ", QemuSelector %u\n",
__func__,
(UINT64)ProcessorNum,
RemoveApicId,
QemuSelector
));
EjectCount++;
ToUnplugIdx++;
}
if (EjectCount != 0) {
//
// We have processors to be ejected; install the handler.
//
mCpuHotEjectData->Handler = EjectCpu;
//
// The BSP and APs load mCpuHotEjectData->Handler, and
// mCpuHotEjectData->QemuSelectorMap[] in SmmCpuFeaturesRendezvousExit()
// and EjectCpu().
//
// The comment in SmmCpuFeaturesRendezvousExit() details how we use
// the AllCpusInSync control-dependency to ensure that any loads are
// ordered-after the stores above.
//
// Ensure that the stores above are ordered-before the AllCpusInSync store
// by using a MemoryFence() with release semantics.
//
MemoryFence ();
}
//
// We've removed this set of APIC IDs from SMM data structures and
// have installed an ejection handler if needed.
//
return EFI_SUCCESS;
}
/**
CPU Hotplug MMI handler function.
This is a root MMI handler.
@param[in] DispatchHandle The unique handle assigned to this handler by
EFI_MM_SYSTEM_TABLE.MmiHandlerRegister().
@param[in] Context Context passed in by
EFI_MM_SYSTEM_TABLE.MmiManage(). Due to
CpuHotplugMmi() being a root MMI handler,
Context is ASSERT()ed to be NULL.
@param[in,out] CommBuffer Ignored, due to CpuHotplugMmi() being a root
MMI handler.
@param[in,out] CommBufferSize Ignored, due to CpuHotplugMmi() being a root
MMI handler.
@retval EFI_SUCCESS The MMI was handled and the MMI
source was quiesced. When returned
by a non-root MMI handler,
EFI_SUCCESS terminates the
processing of MMI handlers in
EFI_MM_SYSTEM_TABLE.MmiManage().
For a root MMI handler (i.e., for
the present function too),
EFI_SUCCESS behaves identically to
EFI_WARN_INTERRUPT_SOURCE_QUIESCED,
as further root MMI handlers are
going to be called by
EFI_MM_SYSTEM_TABLE.MmiManage()
anyway.
@retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The MMI source has been quiesced,
but other handlers should still
be called.
@retval EFI_WARN_INTERRUPT_SOURCE_PENDING The MMI source is still pending,
and other handlers should still
be called.
@retval EFI_INTERRUPT_PENDING The MMI source could not be
quiesced.
**/
STATIC
EFI_STATUS
EFIAPI
CpuHotplugMmi (
IN EFI_HANDLE DispatchHandle,
IN CONST VOID *Context OPTIONAL,
IN OUT VOID *CommBuffer OPTIONAL,
IN OUT UINTN *CommBufferSize OPTIONAL
)
{
EFI_STATUS Status;
UINT8 ApmControl;
UINT32 PluggedCount;
UINT32 ToUnplugCount;
//
// Assert that we are entering this function due to our root MMI handler
// registration.
//
ASSERT (DispatchHandle == mDispatchHandle);
//
// When MmiManage() is invoked to process root MMI handlers, the caller (the
// MM Core) is expected to pass in a NULL Context. MmiManage() then passes
// the same NULL Context to individual handlers.
//
ASSERT (Context == NULL);
//
// Read the MMI command value from the APM Control Port, to see if this is an
// MMI we should care about.
//
Status = mMmCpuIo->Io.Read (
mMmCpuIo,
MM_IO_UINT8,
ICH9_APM_CNT,
1,
&ApmControl
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: failed to read ICH9_APM_CNT: %r\n",
__func__,
Status
));
//
// We couldn't even determine if the MMI was for us or not.
//
goto Fatal;
}
if (ApmControl != ICH9_APM_CNT_CPU_HOTPLUG) {
//
// The MMI is not for us.
//
return EFI_WARN_INTERRUPT_SOURCE_QUIESCED;
}
//
// Collect the CPUs with pending events.
//
Status = QemuCpuhpCollectApicIds (
mMmCpuIo,
mCpuHotPlugData->ArrayLength, // PossibleCpuCount
mCpuHotPlugData->ArrayLength - 1, // ApicIdCount
mPluggedApicIds,
&PluggedCount,
mToUnplugApicIds,
mToUnplugSelectors,
&ToUnplugCount
);
if (EFI_ERROR (Status)) {
goto Fatal;
}
if (PluggedCount > 0) {
Status = ProcessHotAddedCpus (mPluggedApicIds, PluggedCount);
if (EFI_ERROR (Status)) {
goto Fatal;
}
}
if (ToUnplugCount > 0) {
Status = UnplugCpus (mToUnplugApicIds, mToUnplugSelectors, ToUnplugCount);
if (EFI_ERROR (Status)) {
goto Fatal;
}
}
//
// We've handled this MMI.
//
return EFI_SUCCESS;
Fatal:
ASSERT (FALSE);
CpuDeadLoop ();
//
// We couldn't handle this MMI.
//
return EFI_INTERRUPT_PENDING;
}
//
// Entry point function of this driver.
//
EFI_STATUS
EFIAPI
CpuHotplugEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINTN Len;
UINTN Size;
UINTN SizeSel;
//
// This module should only be included when SMM support is required.
//
ASSERT (FeaturePcdGet (PcdSmmSmramRequire));
//
// This driver depends on the dynamically detected "SMRAM at default SMBASE"
// feature.
//
if (!PcdGetBool (PcdQ35SmramAtDefaultSmbase)) {
return EFI_UNSUPPORTED;
}
//
// Errors from here on are fatal; we cannot allow the boot to proceed if we
// can't set up this driver to handle CPU hotplug.
//
// First, collect the protocols needed later. All of these protocols are
// listed in our module DEPEX.
//
Status = gMmst->MmLocateProtocol (
&gEfiMmCpuIoProtocolGuid,
NULL /* Registration */,
(VOID **)&mMmCpuIo
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "%a: locate MmCpuIo: %r\n", __func__, Status));
goto Fatal;
}
Status = gMmst->MmLocateProtocol (
&gEfiSmmCpuServiceProtocolGuid,
NULL /* Registration */,
(VOID **)&mMmCpuService
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: locate MmCpuService: %r\n",
__func__,
Status
));
goto Fatal;
}
//
// Our DEPEX on EFI_SMM_CPU_SERVICE_PROTOCOL guarantees that PiSmmCpuDxeSmm
// has pointed:
// - PcdCpuHotPlugDataAddress to CPU_HOT_PLUG_DATA in SMRAM,
// - PcdCpuHotEjectDataAddress to CPU_HOT_EJECT_DATA in SMRAM, if the
// possible CPU count is greater than 1.
//
mCpuHotPlugData = (VOID *)(UINTN)PcdGet64 (PcdCpuHotPlugDataAddress);
mCpuHotEjectData = (VOID *)(UINTN)PcdGet64 (PcdCpuHotEjectDataAddress);
if (mCpuHotPlugData == NULL) {
Status = EFI_NOT_FOUND;
DEBUG ((DEBUG_ERROR, "%a: CPU_HOT_PLUG_DATA: %r\n", __func__, Status));
goto Fatal;
}
//
// If the possible CPU count is 1, there's nothing for this driver to do.
//
if (mCpuHotPlugData->ArrayLength == 1) {
return EFI_UNSUPPORTED;
}
if (mCpuHotEjectData == NULL) {
Status = EFI_NOT_FOUND;
} else if (mCpuHotPlugData->ArrayLength != mCpuHotEjectData->ArrayLength) {
Status = EFI_INVALID_PARAMETER;
} else {
Status = EFI_SUCCESS;
}
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "%a: CPU_HOT_EJECT_DATA: %r\n", __func__, Status));
goto Fatal;
}
//
// Allocate the data structures that depend on the possible CPU count.
//
if (RETURN_ERROR (SafeUintnSub (mCpuHotPlugData->ArrayLength, 1, &Len)) ||
RETURN_ERROR (SafeUintnMult (sizeof (APIC_ID), Len, &Size)) ||
RETURN_ERROR (SafeUintnMult (sizeof (UINT32), Len, &SizeSel)))
{
Status = EFI_ABORTED;
DEBUG ((DEBUG_ERROR, "%a: invalid CPU_HOT_PLUG_DATA\n", __func__));
goto Fatal;
}
Status = gMmst->MmAllocatePool (
EfiRuntimeServicesData,
Size,
(VOID **)&mPluggedApicIds
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __func__, Status));
goto Fatal;
}
Status = gMmst->MmAllocatePool (
EfiRuntimeServicesData,
Size,
(VOID **)&mToUnplugApicIds
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __func__, Status));
goto ReleasePluggedApicIds;
}
Status = gMmst->MmAllocatePool (
EfiRuntimeServicesData,
SizeSel,
(VOID **)&mToUnplugSelectors
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __func__, Status));
goto ReleaseToUnplugApicIds;
}
//
// Allocate the Post-SMM Pen for hot-added CPUs.
//
Status = SmbaseAllocatePostSmmPen (
&mPostSmmPenAddress,
SystemTable->BootServices
);
if (EFI_ERROR (Status)) {
goto ReleaseToUnplugSelectors;
}
//
// Sanity-check the CPU hotplug interface.
//
// Both of the following features are part of QEMU 5.0, introduced primarily
// in commit range 3e08b2b9cb64..3a61c8db9d25:
//
// (a) the QEMU_CPUHP_CMD_GET_ARCH_ID command of the modern CPU hotplug
// interface,
//
// (b) the "SMRAM at default SMBASE" feature.
//
// From these, (b) is restricted to 5.0+ machine type versions, while (a)
// does not depend on machine type version. Because we ensured the stricter
// condition (b) through PcdQ35SmramAtDefaultSmbase above, the (a)
// QEMU_CPUHP_CMD_GET_ARCH_ID command must now be available too. While we
// can't verify the presence of precisely that command, we can still verify
// (sanity-check) that the modern interface is active, at least.
//
// Consult the "Typical usecases | Detecting and enabling modern CPU hotplug
// interface" section in QEMU's "docs/specs/acpi_cpu_hotplug.txt", on the
// following.
//
QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);
QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);
QemuCpuhpWriteCommand (mMmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);
if (QemuCpuhpReadCommandData2 (mMmCpuIo) != 0) {
Status = EFI_NOT_FOUND;
DEBUG ((
DEBUG_ERROR,
"%a: modern CPU hotplug interface: %r\n",
__func__,
Status
));
goto ReleasePostSmmPen;
}
//
// Register the handler for the CPU Hotplug MMI.
//
Status = gMmst->MmiHandlerRegister (
CpuHotplugMmi,
NULL, // HandlerType: root MMI handler
&mDispatchHandle
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: MmiHandlerRegister(): %r\n",
__func__,
Status
));
goto ReleasePostSmmPen;
}
//
// Install the handler for the hot-added CPUs' first SMI.
//
SmbaseInstallFirstSmiHandler ();
return EFI_SUCCESS;
ReleasePostSmmPen:
SmbaseReleasePostSmmPen (mPostSmmPenAddress, SystemTable->BootServices);
mPostSmmPenAddress = 0;
ReleaseToUnplugSelectors:
gMmst->MmFreePool (mToUnplugSelectors);
mToUnplugSelectors = NULL;
ReleaseToUnplugApicIds:
gMmst->MmFreePool (mToUnplugApicIds);
mToUnplugApicIds = NULL;
ReleasePluggedApicIds:
gMmst->MmFreePool (mPluggedApicIds);
mPluggedApicIds = NULL;
Fatal:
ASSERT (FALSE);
CpuDeadLoop ();
return Status;
}