aboutsummaryrefslogtreecommitdiffstats
path: root/roms/edk2/OvmfPkg/CpuHotplugSmm/CpuHotplug.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/edk2/OvmfPkg/CpuHotplugSmm/CpuHotplug.c')
-rw-r--r--roms/edk2/OvmfPkg/CpuHotplugSmm/CpuHotplug.c445
1 files changed, 445 insertions, 0 deletions
diff --git a/roms/edk2/OvmfPkg/CpuHotplugSmm/CpuHotplug.c b/roms/edk2/OvmfPkg/CpuHotplugSmm/CpuHotplug.c
new file mode 100644
index 000000000..cfe698ed2
--- /dev/null
+++ b/roms/edk2/OvmfPkg/CpuHotplugSmm/CpuHotplug.c
@@ -0,0 +1,445 @@
+/** @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/DebugLib.h> // ASSERT()
+#include <Library/MmServicesTableLib.h> // gMmst
+#include <Library/PcdLib.h> // PcdGetBool()
+#include <Library/SafeIntLib.h> // SafeUintnSub()
+#include <Protocol/MmCpuIo.h> // EFI_MM_CPU_IO_PROTOCOL
+#include <Protocol/SmmCpuService.h> // EFI_SMM_CPU_SERVICE_PROTOCOL
+#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;
+//
+// This structure is a communication side-channel between the
+// EFI_SMM_CPU_SERVICE_PROTOCOL consumer (i.e., this driver) and provider
+// (i.e., PiSmmCpuDxeSmm).
+//
+STATIC CPU_HOT_PLUG_DATA *mCpuHotPlugData;
+//
+// 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).
+//
+// These arrays provide room for ("possible CPU count" minus one) APIC IDs
+// each, 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;
+//
+// 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;
+
+
+/**
+ 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;
+ UINT32 PluggedIdx;
+ UINT32 NewSlot;
+
+ //
+ // 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", __FUNCTION__,
+ 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,
+ &ToUnplugCount
+ );
+ if (EFI_ERROR (Status)) {
+ goto Fatal;
+ }
+ if (ToUnplugCount > 0) {
+ DEBUG ((DEBUG_ERROR, "%a: hot-unplug is not supported yet\n",
+ __FUNCTION__));
+ goto Fatal;
+ }
+
+ //
+ // Process hot-added CPUs.
+ //
+ // 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 = mPluggedApicIds[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", __FUNCTION__, 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",
+ __FUNCTION__, NewApicId));
+ goto Fatal;
+ }
+
+ //
+ // 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",
+ __FUNCTION__, 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", __FUNCTION__,
+ NewApicId, (UINT64)mCpuHotPlugData->SmBase[NewSlot],
+ (UINT64)NewProcessorNumberByProtocol));
+
+ NewSlot++;
+ PluggedIdx++;
+ }
+
+ //
+ // We've handled this MMI.
+ //
+ return EFI_SUCCESS;
+
+RevokeNewSlot:
+ mCpuHotPlugData->ApicId[NewSlot] = MAX_UINT64;
+
+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 Size;
+
+ //
+ // 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", __FUNCTION__, Status));
+ goto Fatal;
+ }
+ Status = gMmst->MmLocateProtocol (&gEfiSmmCpuServiceProtocolGuid,
+ NULL /* Registration */, (VOID **)&mMmCpuService);
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: locate MmCpuService: %r\n", __FUNCTION__,
+ Status));
+ goto Fatal;
+ }
+
+ //
+ // Our DEPEX on EFI_SMM_CPU_SERVICE_PROTOCOL guarantees that PiSmmCpuDxeSmm
+ // has pointed PcdCpuHotPlugDataAddress to CPU_HOT_PLUG_DATA in SMRAM.
+ //
+ mCpuHotPlugData = (VOID *)(UINTN)PcdGet64 (PcdCpuHotPlugDataAddress);
+ if (mCpuHotPlugData == NULL) {
+ Status = EFI_NOT_FOUND;
+ DEBUG ((DEBUG_ERROR, "%a: CPU_HOT_PLUG_DATA: %r\n", __FUNCTION__, 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;
+ }
+ //
+ // Allocate the data structures that depend on the possible CPU count.
+ //
+ if (RETURN_ERROR (SafeUintnSub (mCpuHotPlugData->ArrayLength, 1, &Size)) ||
+ RETURN_ERROR (SafeUintnMult (sizeof (APIC_ID), Size, &Size))) {
+ Status = EFI_ABORTED;
+ DEBUG ((DEBUG_ERROR, "%a: invalid CPU_HOT_PLUG_DATA\n", __FUNCTION__));
+ goto Fatal;
+ }
+ Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, Size,
+ (VOID **)&mPluggedApicIds);
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));
+ goto Fatal;
+ }
+ Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, Size,
+ (VOID **)&mToUnplugApicIds);
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));
+ goto ReleasePluggedApicIds;
+ }
+
+ //
+ // Allocate the Post-SMM Pen for hot-added CPUs.
+ //
+ Status = SmbaseAllocatePostSmmPen (&mPostSmmPenAddress,
+ SystemTable->BootServices);
+ if (EFI_ERROR (Status)) {
+ goto ReleaseToUnplugApicIds;
+ }
+
+ //
+ // 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",
+ __FUNCTION__, 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", __FUNCTION__,
+ Status));
+ goto ReleasePostSmmPen;
+ }
+
+ //
+ // Install the handler for the hot-added CPUs' first SMI.
+ //
+ SmbaseInstallFirstSmiHandler ();
+
+ return EFI_SUCCESS;
+
+ReleasePostSmmPen:
+ SmbaseReleasePostSmmPen (mPostSmmPenAddress, SystemTable->BootServices);
+ mPostSmmPenAddress = 0;
+
+ReleaseToUnplugApicIds:
+ gMmst->MmFreePool (mToUnplugApicIds);
+ mToUnplugApicIds = NULL;
+
+ReleasePluggedApicIds:
+ gMmst->MmFreePool (mPluggedApicIds);
+ mPluggedApicIds = NULL;
+
+Fatal:
+ ASSERT (FALSE);
+ CpuDeadLoop ();
+ return Status;
+}