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