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 // These arrays provide room for ("possible CPU count" minus one) APIC IDs
49 // each, as we don't expect every possible CPU to appear, or disappear, in a
50 // single MMI. The numbers of used (populated) elements in the arrays are
51 // determined on every MMI separately.
53 STATIC APIC_ID
*mPluggedApicIds
;
54 STATIC APIC_ID
*mToUnplugApicIds
;
56 // Address of the non-SMRAM reserved memory page that contains the Post-SMM Pen
57 // for hot-added CPUs.
59 STATIC UINT32 mPostSmmPenAddress
;
61 // Represents the registration of the CPU Hotplug MMI handler.
63 STATIC EFI_HANDLE mDispatchHandle
;
67 CPU Hotplug MMI handler function.
69 This is a root MMI handler.
71 @param[in] DispatchHandle The unique handle assigned to this handler by
72 EFI_MM_SYSTEM_TABLE.MmiHandlerRegister().
74 @param[in] Context Context passed in by
75 EFI_MM_SYSTEM_TABLE.MmiManage(). Due to
76 CpuHotplugMmi() being a root MMI handler,
77 Context is ASSERT()ed to be NULL.
79 @param[in,out] CommBuffer Ignored, due to CpuHotplugMmi() being a root
82 @param[in,out] CommBufferSize Ignored, due to CpuHotplugMmi() being a root
85 @retval EFI_SUCCESS The MMI was handled and the MMI
86 source was quiesced. When returned
87 by a non-root MMI handler,
88 EFI_SUCCESS terminates the
89 processing of MMI handlers in
90 EFI_MM_SYSTEM_TABLE.MmiManage().
91 For a root MMI handler (i.e., for
92 the present function too),
93 EFI_SUCCESS behaves identically to
94 EFI_WARN_INTERRUPT_SOURCE_QUIESCED,
95 as further root MMI handlers are
97 EFI_MM_SYSTEM_TABLE.MmiManage()
100 @retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The MMI source has been quiesced,
101 but other handlers should still
104 @retval EFI_WARN_INTERRUPT_SOURCE_PENDING The MMI source is still pending,
105 and other handlers should still
108 @retval EFI_INTERRUPT_PENDING The MMI source could not be
115 IN EFI_HANDLE DispatchHandle
,
116 IN CONST VOID
*Context OPTIONAL
,
117 IN OUT VOID
*CommBuffer OPTIONAL
,
118 IN OUT UINTN
*CommBufferSize OPTIONAL
124 UINT32 ToUnplugCount
;
129 // Assert that we are entering this function due to our root MMI handler
132 ASSERT (DispatchHandle
== mDispatchHandle
);
134 // When MmiManage() is invoked to process root MMI handlers, the caller (the
135 // MM Core) is expected to pass in a NULL Context. MmiManage() then passes
136 // the same NULL Context to individual handlers.
138 ASSERT (Context
== NULL
);
140 // Read the MMI command value from the APM Control Port, to see if this is an
141 // MMI we should care about.
143 Status
= mMmCpuIo
->Io
.Read (mMmCpuIo
, MM_IO_UINT8
, ICH9_APM_CNT
, 1,
145 if (EFI_ERROR (Status
)) {
146 DEBUG ((DEBUG_ERROR
, "%a: failed to read ICH9_APM_CNT: %r\n", __FUNCTION__
,
149 // We couldn't even determine if the MMI was for us or not.
154 if (ApmControl
!= ICH9_APM_CNT_CPU_HOTPLUG
) {
156 // The MMI is not for us.
158 return EFI_WARN_INTERRUPT_SOURCE_QUIESCED
;
162 // Collect the CPUs with pending events.
164 Status
= QemuCpuhpCollectApicIds (
166 mCpuHotPlugData
->ArrayLength
, // PossibleCpuCount
167 mCpuHotPlugData
->ArrayLength
- 1, // ApicIdCount
173 if (EFI_ERROR (Status
)) {
176 if (ToUnplugCount
> 0) {
177 DEBUG ((DEBUG_ERROR
, "%a: hot-unplug is not supported yet\n",
183 // Process hot-added CPUs.
185 // The Post-SMM Pen need not be reinstalled multiple times within a single
186 // root MMI handling. Even reinstalling once per root MMI is only prudence;
187 // in theory installing the pen in the driver's entry point function should
190 SmbaseReinstallPostSmmPen (mPostSmmPenAddress
);
194 while (PluggedIdx
< PluggedCount
) {
196 UINTN NewProcessorNumberByProtocol
;
198 NewApicId
= mPluggedApicIds
[PluggedIdx
];
200 // Find the first empty slot in CPU_HOT_PLUG_DATA.
202 while (NewSlot
< mCpuHotPlugData
->ArrayLength
&&
203 mCpuHotPlugData
->ApicId
[NewSlot
] != MAX_UINT64
) {
206 if (NewSlot
== mCpuHotPlugData
->ArrayLength
) {
207 DEBUG ((DEBUG_ERROR
, "%a: no room for APIC ID " FMT_APIC_ID
"\n",
208 __FUNCTION__
, NewApicId
));
213 // Store the APIC ID of the new processor to the slot.
215 mCpuHotPlugData
->ApicId
[NewSlot
] = NewApicId
;
218 // Relocate the SMBASE of the new CPU.
220 Status
= SmbaseRelocate (NewApicId
, mCpuHotPlugData
->SmBase
[NewSlot
],
222 if (EFI_ERROR (Status
)) {
227 // Add the new CPU with EFI_SMM_CPU_SERVICE_PROTOCOL.
229 Status
= mMmCpuService
->AddProcessor (mMmCpuService
, NewApicId
,
230 &NewProcessorNumberByProtocol
);
231 if (EFI_ERROR (Status
)) {
232 DEBUG ((DEBUG_ERROR
, "%a: AddProcessor(" FMT_APIC_ID
"): %r\n",
233 __FUNCTION__
, NewApicId
, Status
));
237 DEBUG ((DEBUG_INFO
, "%a: hot-added APIC ID " FMT_APIC_ID
", SMBASE 0x%Lx, "
238 "EFI_SMM_CPU_SERVICE_PROTOCOL assigned number %Lu\n", __FUNCTION__
,
239 NewApicId
, (UINT64
)mCpuHotPlugData
->SmBase
[NewSlot
],
240 (UINT64
)NewProcessorNumberByProtocol
));
247 // We've handled this MMI.
252 mCpuHotPlugData
->ApicId
[NewSlot
] = MAX_UINT64
;
258 // We couldn't handle this MMI.
260 return EFI_INTERRUPT_PENDING
;
265 // Entry point function of this driver.
270 IN EFI_HANDLE ImageHandle
,
271 IN EFI_SYSTEM_TABLE
*SystemTable
278 // This module should only be included when SMM support is required.
280 ASSERT (FeaturePcdGet (PcdSmmSmramRequire
));
282 // This driver depends on the dynamically detected "SMRAM at default SMBASE"
285 if (!PcdGetBool (PcdQ35SmramAtDefaultSmbase
)) {
286 return EFI_UNSUPPORTED
;
290 // Errors from here on are fatal; we cannot allow the boot to proceed if we
291 // can't set up this driver to handle CPU hotplug.
293 // First, collect the protocols needed later. All of these protocols are
294 // listed in our module DEPEX.
296 Status
= gMmst
->MmLocateProtocol (&gEfiMmCpuIoProtocolGuid
,
297 NULL
/* Registration */, (VOID
**)&mMmCpuIo
);
298 if (EFI_ERROR (Status
)) {
299 DEBUG ((DEBUG_ERROR
, "%a: locate MmCpuIo: %r\n", __FUNCTION__
, Status
));
302 Status
= gMmst
->MmLocateProtocol (&gEfiSmmCpuServiceProtocolGuid
,
303 NULL
/* Registration */, (VOID
**)&mMmCpuService
);
304 if (EFI_ERROR (Status
)) {
305 DEBUG ((DEBUG_ERROR
, "%a: locate MmCpuService: %r\n", __FUNCTION__
,
311 // Our DEPEX on EFI_SMM_CPU_SERVICE_PROTOCOL guarantees that PiSmmCpuDxeSmm
312 // has pointed PcdCpuHotPlugDataAddress to CPU_HOT_PLUG_DATA in SMRAM.
314 mCpuHotPlugData
= (VOID
*)(UINTN
)PcdGet64 (PcdCpuHotPlugDataAddress
);
315 if (mCpuHotPlugData
== NULL
) {
316 Status
= EFI_NOT_FOUND
;
317 DEBUG ((DEBUG_ERROR
, "%a: CPU_HOT_PLUG_DATA: %r\n", __FUNCTION__
, Status
));
321 // If the possible CPU count is 1, there's nothing for this driver to do.
323 if (mCpuHotPlugData
->ArrayLength
== 1) {
324 return EFI_UNSUPPORTED
;
327 // Allocate the data structures that depend on the possible CPU count.
329 if (RETURN_ERROR (SafeUintnSub (mCpuHotPlugData
->ArrayLength
, 1, &Size
)) ||
330 RETURN_ERROR (SafeUintnMult (sizeof (APIC_ID
), Size
, &Size
))) {
331 Status
= EFI_ABORTED
;
332 DEBUG ((DEBUG_ERROR
, "%a: invalid CPU_HOT_PLUG_DATA\n", __FUNCTION__
));
335 Status
= gMmst
->MmAllocatePool (EfiRuntimeServicesData
, Size
,
336 (VOID
**)&mPluggedApicIds
);
337 if (EFI_ERROR (Status
)) {
338 DEBUG ((DEBUG_ERROR
, "%a: MmAllocatePool(): %r\n", __FUNCTION__
, Status
));
341 Status
= gMmst
->MmAllocatePool (EfiRuntimeServicesData
, Size
,
342 (VOID
**)&mToUnplugApicIds
);
343 if (EFI_ERROR (Status
)) {
344 DEBUG ((DEBUG_ERROR
, "%a: MmAllocatePool(): %r\n", __FUNCTION__
, Status
));
345 goto ReleasePluggedApicIds
;
349 // Allocate the Post-SMM Pen for hot-added CPUs.
351 Status
= SmbaseAllocatePostSmmPen (&mPostSmmPenAddress
,
352 SystemTable
->BootServices
);
353 if (EFI_ERROR (Status
)) {
354 goto ReleaseToUnplugApicIds
;
358 // Sanity-check the CPU hotplug interface.
360 // Both of the following features are part of QEMU 5.0, introduced primarily
361 // in commit range 3e08b2b9cb64..3a61c8db9d25:
363 // (a) the QEMU_CPUHP_CMD_GET_ARCH_ID command of the modern CPU hotplug
366 // (b) the "SMRAM at default SMBASE" feature.
368 // From these, (b) is restricted to 5.0+ machine type versions, while (a)
369 // does not depend on machine type version. Because we ensured the stricter
370 // condition (b) through PcdQ35SmramAtDefaultSmbase above, the (a)
371 // QEMU_CPUHP_CMD_GET_ARCH_ID command must now be available too. While we
372 // can't verify the presence of precisely that command, we can still verify
373 // (sanity-check) that the modern interface is active, at least.
375 // Consult the "Typical usecases | Detecting and enabling modern CPU hotplug
376 // interface" section in QEMU's "docs/specs/acpi_cpu_hotplug.txt", on the
379 QemuCpuhpWriteCpuSelector (mMmCpuIo
, 0);
380 QemuCpuhpWriteCpuSelector (mMmCpuIo
, 0);
381 QemuCpuhpWriteCommand (mMmCpuIo
, QEMU_CPUHP_CMD_GET_PENDING
);
382 if (QemuCpuhpReadCommandData2 (mMmCpuIo
) != 0) {
383 Status
= EFI_NOT_FOUND
;
384 DEBUG ((DEBUG_ERROR
, "%a: modern CPU hotplug interface: %r\n",
385 __FUNCTION__
, Status
));
386 goto ReleasePostSmmPen
;
390 // Register the handler for the CPU Hotplug MMI.
392 Status
= gMmst
->MmiHandlerRegister (
394 NULL
, // HandlerType: root MMI handler
397 if (EFI_ERROR (Status
)) {
398 DEBUG ((DEBUG_ERROR
, "%a: MmiHandlerRegister(): %r\n", __FUNCTION__
,
400 goto ReleasePostSmmPen
;
404 // Install the handler for the hot-added CPUs' first SMI.
406 SmbaseInstallFirstSmiHandler ();
411 SmbaseReleasePostSmmPen (mPostSmmPenAddress
, SystemTable
->BootServices
);
412 mPostSmmPenAddress
= 0;
414 ReleaseToUnplugApicIds
:
415 gMmst
->MmFreePool (mToUnplugApicIds
);
416 mToUnplugApicIds
= NULL
;
418 ReleasePluggedApicIds
:
419 gMmst
->MmFreePool (mPluggedApicIds
);
420 mPluggedApicIds
= NULL
;