2 Root SMI handler for VCPU hotplug SMIs.
4 Copyright (c) 2020, Red Hat, Inc.
6 SPDX-License-Identifier: BSD-2-Clause-Patent
9 #include <CpuHotPlugData.h> // CPU_HOT_PLUG_DATA
10 #include <IndustryStandard/Q35MchIch9.h> // ICH9_APM_CNT
11 #include <IndustryStandard/QemuCpuHotplug.h> // QEMU_CPUHP_CMD_GET_PENDING
12 #include <Library/BaseLib.h> // CpuDeadLoop()
13 #include <Library/DebugLib.h> // ASSERT()
14 #include <Library/MmServicesTableLib.h> // gMmst
15 #include <Library/PcdLib.h> // PcdGetBool()
16 #include <Library/SafeIntLib.h> // SafeUintnSub()
17 #include <Protocol/MmCpuIo.h> // EFI_MM_CPU_IO_PROTOCOL
18 #include <Protocol/SmmCpuService.h> // EFI_SMM_CPU_SERVICE_PROTOCOL
19 #include <Uefi/UefiBaseType.h> // EFI_STATUS
21 #include "ApicId.h" // APIC_ID
22 #include "QemuCpuhp.h" // QemuCpuhpWriteCpuSelector()
23 #include "Smbase.h" // SmbaseAllocatePostSmmPen()
26 // We use this protocol for accessing IO Ports.
28 STATIC EFI_MM_CPU_IO_PROTOCOL
*mMmCpuIo
;
30 // The following protocol is used to report the addition or removal of a CPU to
31 // the SMM CPU driver (PiSmmCpuDxeSmm).
33 STATIC EFI_SMM_CPU_SERVICE_PROTOCOL
*mMmCpuService
;
35 // This structure is a communication side-channel between the
36 // EFI_SMM_CPU_SERVICE_PROTOCOL consumer (i.e., this driver) and provider
37 // (i.e., PiSmmCpuDxeSmm).
39 STATIC CPU_HOT_PLUG_DATA
*mCpuHotPlugData
;
41 // SMRAM arrays for fetching the APIC IDs of processors with pending events (of
42 // known event types), for the time of just one MMI.
44 // The lifetimes of these arrays match that of this driver only because we
45 // don't want to allocate SMRAM at OS runtime, and potentially fail (or
46 // fragment the SMRAM map).
48 // The first array stores APIC IDs for hot-plug events, the second and the
49 // third store APIC IDs and QEMU CPU Selectors (both indexed similarly) for
50 // hot-unplug events. All of these provide room for "possible CPU count" minus
51 // one elements as we don't expect every possible CPU to appear, or disappear,
52 // in a single MMI. The numbers of used (populated) elements in the arrays are
53 // determined on every MMI separately.
55 STATIC APIC_ID
*mPluggedApicIds
;
56 STATIC APIC_ID
*mToUnplugApicIds
;
57 STATIC UINT32
*mToUnplugSelectors
;
59 // Address of the non-SMRAM reserved memory page that contains the Post-SMM Pen
60 // for hot-added CPUs.
62 STATIC UINT32 mPostSmmPenAddress
;
64 // Represents the registration of the CPU Hotplug MMI handler.
66 STATIC EFI_HANDLE mDispatchHandle
;
69 Process CPUs that have been hot-added, per QemuCpuhpCollectApicIds().
71 For each such CPU, relocate the SMBASE, and report the CPU to PiSmmCpuDxeSmm
72 via EFI_SMM_CPU_SERVICE_PROTOCOL. If the supposedly hot-added CPU is already
73 known, skip it silently.
75 @param[in] PluggedApicIds The APIC IDs of the CPUs that have been
78 @param[in] PluggedCount The number of filled-in APIC IDs in
81 @retval EFI_SUCCESS CPUs corresponding to all the APIC IDs are
84 @retval EFI_OUT_OF_RESOURCES Out of APIC ID space in "mCpuHotPlugData".
86 @return Error codes propagated from SmbaseRelocate()
87 and mMmCpuService->AddProcessor().
92 IN APIC_ID
*PluggedApicIds
,
93 IN UINT32 PluggedCount
101 // The Post-SMM Pen need not be reinstalled multiple times within a single
102 // root MMI handling. Even reinstalling once per root MMI is only prudence;
103 // in theory installing the pen in the driver's entry point function should
106 SmbaseReinstallPostSmmPen (mPostSmmPenAddress
);
110 while (PluggedIdx
< PluggedCount
) {
113 UINTN NewProcessorNumberByProtocol
;
115 NewApicId
= PluggedApicIds
[PluggedIdx
];
118 // Check if the supposedly hot-added CPU is already known to us.
121 CheckSlot
< mCpuHotPlugData
->ArrayLength
;
123 if (mCpuHotPlugData
->ApicId
[CheckSlot
] == NewApicId
) {
127 if (CheckSlot
< mCpuHotPlugData
->ArrayLength
) {
128 DEBUG ((DEBUG_VERBOSE
, "%a: APIC ID " FMT_APIC_ID
" was hot-plugged "
129 "before; ignoring it\n", __FUNCTION__
, NewApicId
));
135 // Find the first empty slot in CPU_HOT_PLUG_DATA.
137 while (NewSlot
< mCpuHotPlugData
->ArrayLength
&&
138 mCpuHotPlugData
->ApicId
[NewSlot
] != MAX_UINT64
) {
141 if (NewSlot
== mCpuHotPlugData
->ArrayLength
) {
142 DEBUG ((DEBUG_ERROR
, "%a: no room for APIC ID " FMT_APIC_ID
"\n",
143 __FUNCTION__
, NewApicId
));
144 return EFI_OUT_OF_RESOURCES
;
148 // Store the APIC ID of the new processor to the slot.
150 mCpuHotPlugData
->ApicId
[NewSlot
] = NewApicId
;
153 // Relocate the SMBASE of the new CPU.
155 Status
= SmbaseRelocate (NewApicId
, mCpuHotPlugData
->SmBase
[NewSlot
],
157 if (EFI_ERROR (Status
)) {
162 // Add the new CPU with EFI_SMM_CPU_SERVICE_PROTOCOL.
164 Status
= mMmCpuService
->AddProcessor (mMmCpuService
, NewApicId
,
165 &NewProcessorNumberByProtocol
);
166 if (EFI_ERROR (Status
)) {
167 DEBUG ((DEBUG_ERROR
, "%a: AddProcessor(" FMT_APIC_ID
"): %r\n",
168 __FUNCTION__
, NewApicId
, Status
));
172 DEBUG ((DEBUG_INFO
, "%a: hot-added APIC ID " FMT_APIC_ID
", SMBASE 0x%Lx, "
173 "EFI_SMM_CPU_SERVICE_PROTOCOL assigned number %Lu\n", __FUNCTION__
,
174 NewApicId
, (UINT64
)mCpuHotPlugData
->SmBase
[NewSlot
],
175 (UINT64
)NewProcessorNumberByProtocol
));
182 // We've processed this batch of hot-added CPUs.
187 mCpuHotPlugData
->ApicId
[NewSlot
] = MAX_UINT64
;
193 CPU Hotplug MMI handler function.
195 This is a root MMI handler.
197 @param[in] DispatchHandle The unique handle assigned to this handler by
198 EFI_MM_SYSTEM_TABLE.MmiHandlerRegister().
200 @param[in] Context Context passed in by
201 EFI_MM_SYSTEM_TABLE.MmiManage(). Due to
202 CpuHotplugMmi() being a root MMI handler,
203 Context is ASSERT()ed to be NULL.
205 @param[in,out] CommBuffer Ignored, due to CpuHotplugMmi() being a root
208 @param[in,out] CommBufferSize Ignored, due to CpuHotplugMmi() being a root
211 @retval EFI_SUCCESS The MMI was handled and the MMI
212 source was quiesced. When returned
213 by a non-root MMI handler,
214 EFI_SUCCESS terminates the
215 processing of MMI handlers in
216 EFI_MM_SYSTEM_TABLE.MmiManage().
217 For a root MMI handler (i.e., for
218 the present function too),
219 EFI_SUCCESS behaves identically to
220 EFI_WARN_INTERRUPT_SOURCE_QUIESCED,
221 as further root MMI handlers are
222 going to be called by
223 EFI_MM_SYSTEM_TABLE.MmiManage()
226 @retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The MMI source has been quiesced,
227 but other handlers should still
230 @retval EFI_WARN_INTERRUPT_SOURCE_PENDING The MMI source is still pending,
231 and other handlers should still
234 @retval EFI_INTERRUPT_PENDING The MMI source could not be
241 IN EFI_HANDLE DispatchHandle
,
242 IN CONST VOID
*Context OPTIONAL
,
243 IN OUT VOID
*CommBuffer OPTIONAL
,
244 IN OUT UINTN
*CommBufferSize OPTIONAL
250 UINT32 ToUnplugCount
;
253 // Assert that we are entering this function due to our root MMI handler
256 ASSERT (DispatchHandle
== mDispatchHandle
);
258 // When MmiManage() is invoked to process root MMI handlers, the caller (the
259 // MM Core) is expected to pass in a NULL Context. MmiManage() then passes
260 // the same NULL Context to individual handlers.
262 ASSERT (Context
== NULL
);
264 // Read the MMI command value from the APM Control Port, to see if this is an
265 // MMI we should care about.
267 Status
= mMmCpuIo
->Io
.Read (mMmCpuIo
, MM_IO_UINT8
, ICH9_APM_CNT
, 1,
269 if (EFI_ERROR (Status
)) {
270 DEBUG ((DEBUG_ERROR
, "%a: failed to read ICH9_APM_CNT: %r\n", __FUNCTION__
,
273 // We couldn't even determine if the MMI was for us or not.
278 if (ApmControl
!= ICH9_APM_CNT_CPU_HOTPLUG
) {
280 // The MMI is not for us.
282 return EFI_WARN_INTERRUPT_SOURCE_QUIESCED
;
286 // Collect the CPUs with pending events.
288 Status
= QemuCpuhpCollectApicIds (
290 mCpuHotPlugData
->ArrayLength
, // PossibleCpuCount
291 mCpuHotPlugData
->ArrayLength
- 1, // ApicIdCount
298 if (EFI_ERROR (Status
)) {
301 if (ToUnplugCount
> 0) {
302 DEBUG ((DEBUG_ERROR
, "%a: hot-unplug is not supported yet\n",
307 if (PluggedCount
> 0) {
308 Status
= ProcessHotAddedCpus (mPluggedApicIds
, PluggedCount
);
309 if (EFI_ERROR (Status
)) {
315 // We've handled this MMI.
323 // We couldn't handle this MMI.
325 return EFI_INTERRUPT_PENDING
;
330 // Entry point function of this driver.
335 IN EFI_HANDLE ImageHandle
,
336 IN EFI_SYSTEM_TABLE
*SystemTable
345 // This module should only be included when SMM support is required.
347 ASSERT (FeaturePcdGet (PcdSmmSmramRequire
));
349 // This driver depends on the dynamically detected "SMRAM at default SMBASE"
352 if (!PcdGetBool (PcdQ35SmramAtDefaultSmbase
)) {
353 return EFI_UNSUPPORTED
;
357 // Errors from here on are fatal; we cannot allow the boot to proceed if we
358 // can't set up this driver to handle CPU hotplug.
360 // First, collect the protocols needed later. All of these protocols are
361 // listed in our module DEPEX.
363 Status
= gMmst
->MmLocateProtocol (&gEfiMmCpuIoProtocolGuid
,
364 NULL
/* Registration */, (VOID
**)&mMmCpuIo
);
365 if (EFI_ERROR (Status
)) {
366 DEBUG ((DEBUG_ERROR
, "%a: locate MmCpuIo: %r\n", __FUNCTION__
, Status
));
369 Status
= gMmst
->MmLocateProtocol (&gEfiSmmCpuServiceProtocolGuid
,
370 NULL
/* Registration */, (VOID
**)&mMmCpuService
);
371 if (EFI_ERROR (Status
)) {
372 DEBUG ((DEBUG_ERROR
, "%a: locate MmCpuService: %r\n", __FUNCTION__
,
378 // Our DEPEX on EFI_SMM_CPU_SERVICE_PROTOCOL guarantees that PiSmmCpuDxeSmm
379 // has pointed PcdCpuHotPlugDataAddress to CPU_HOT_PLUG_DATA in SMRAM.
381 mCpuHotPlugData
= (VOID
*)(UINTN
)PcdGet64 (PcdCpuHotPlugDataAddress
);
382 if (mCpuHotPlugData
== NULL
) {
383 Status
= EFI_NOT_FOUND
;
384 DEBUG ((DEBUG_ERROR
, "%a: CPU_HOT_PLUG_DATA: %r\n", __FUNCTION__
, Status
));
388 // If the possible CPU count is 1, there's nothing for this driver to do.
390 if (mCpuHotPlugData
->ArrayLength
== 1) {
391 return EFI_UNSUPPORTED
;
394 // Allocate the data structures that depend on the possible CPU count.
396 if (RETURN_ERROR (SafeUintnSub (mCpuHotPlugData
->ArrayLength
, 1, &Len
)) ||
397 RETURN_ERROR (SafeUintnMult (sizeof (APIC_ID
), Len
, &Size
)) ||
398 RETURN_ERROR (SafeUintnMult (sizeof (UINT32
), Len
, &SizeSel
))) {
399 Status
= EFI_ABORTED
;
400 DEBUG ((DEBUG_ERROR
, "%a: invalid CPU_HOT_PLUG_DATA\n", __FUNCTION__
));
403 Status
= gMmst
->MmAllocatePool (EfiRuntimeServicesData
, Size
,
404 (VOID
**)&mPluggedApicIds
);
405 if (EFI_ERROR (Status
)) {
406 DEBUG ((DEBUG_ERROR
, "%a: MmAllocatePool(): %r\n", __FUNCTION__
, Status
));
409 Status
= gMmst
->MmAllocatePool (EfiRuntimeServicesData
, Size
,
410 (VOID
**)&mToUnplugApicIds
);
411 if (EFI_ERROR (Status
)) {
412 DEBUG ((DEBUG_ERROR
, "%a: MmAllocatePool(): %r\n", __FUNCTION__
, Status
));
413 goto ReleasePluggedApicIds
;
415 Status
= gMmst
->MmAllocatePool (EfiRuntimeServicesData
, SizeSel
,
416 (VOID
**)&mToUnplugSelectors
);
417 if (EFI_ERROR (Status
)) {
418 DEBUG ((DEBUG_ERROR
, "%a: MmAllocatePool(): %r\n", __FUNCTION__
, Status
));
419 goto ReleaseToUnplugApicIds
;
423 // Allocate the Post-SMM Pen for hot-added CPUs.
425 Status
= SmbaseAllocatePostSmmPen (&mPostSmmPenAddress
,
426 SystemTable
->BootServices
);
427 if (EFI_ERROR (Status
)) {
428 goto ReleaseToUnplugSelectors
;
432 // Sanity-check the CPU hotplug interface.
434 // Both of the following features are part of QEMU 5.0, introduced primarily
435 // in commit range 3e08b2b9cb64..3a61c8db9d25:
437 // (a) the QEMU_CPUHP_CMD_GET_ARCH_ID command of the modern CPU hotplug
440 // (b) the "SMRAM at default SMBASE" feature.
442 // From these, (b) is restricted to 5.0+ machine type versions, while (a)
443 // does not depend on machine type version. Because we ensured the stricter
444 // condition (b) through PcdQ35SmramAtDefaultSmbase above, the (a)
445 // QEMU_CPUHP_CMD_GET_ARCH_ID command must now be available too. While we
446 // can't verify the presence of precisely that command, we can still verify
447 // (sanity-check) that the modern interface is active, at least.
449 // Consult the "Typical usecases | Detecting and enabling modern CPU hotplug
450 // interface" section in QEMU's "docs/specs/acpi_cpu_hotplug.txt", on the
453 QemuCpuhpWriteCpuSelector (mMmCpuIo
, 0);
454 QemuCpuhpWriteCpuSelector (mMmCpuIo
, 0);
455 QemuCpuhpWriteCommand (mMmCpuIo
, QEMU_CPUHP_CMD_GET_PENDING
);
456 if (QemuCpuhpReadCommandData2 (mMmCpuIo
) != 0) {
457 Status
= EFI_NOT_FOUND
;
458 DEBUG ((DEBUG_ERROR
, "%a: modern CPU hotplug interface: %r\n",
459 __FUNCTION__
, Status
));
460 goto ReleasePostSmmPen
;
464 // Register the handler for the CPU Hotplug MMI.
466 Status
= gMmst
->MmiHandlerRegister (
468 NULL
, // HandlerType: root MMI handler
471 if (EFI_ERROR (Status
)) {
472 DEBUG ((DEBUG_ERROR
, "%a: MmiHandlerRegister(): %r\n", __FUNCTION__
,
474 goto ReleasePostSmmPen
;
478 // Install the handler for the hot-added CPUs' first SMI.
480 SmbaseInstallFirstSmiHandler ();
485 SmbaseReleasePostSmmPen (mPostSmmPenAddress
, SystemTable
->BootServices
);
486 mPostSmmPenAddress
= 0;
488 ReleaseToUnplugSelectors
:
489 gMmst
->MmFreePool (mToUnplugSelectors
);
490 mToUnplugSelectors
= NULL
;
492 ReleaseToUnplugApicIds
:
493 gMmst
->MmFreePool (mToUnplugApicIds
);
494 mToUnplugApicIds
= NULL
;
496 ReleasePluggedApicIds
:
497 gMmst
->MmFreePool (mPluggedApicIds
);
498 mPluggedApicIds
= NULL
;