]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/CpuHotplugSmm/CpuHotplug.c
OvmfPkg/CpuHotplugSmm: collect hot-unplug events
[mirror_edk2.git] / OvmfPkg / CpuHotplugSmm / CpuHotplug.c
1 /** @file
2 Root SMI handler for VCPU hotplug SMIs.
3
4 Copyright (c) 2020, Red Hat, Inc.
5
6 SPDX-License-Identifier: BSD-2-Clause-Patent
7 **/
8
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
20
21 #include "ApicId.h" // APIC_ID
22 #include "QemuCpuhp.h" // QemuCpuhpWriteCpuSelector()
23 #include "Smbase.h" // SmbaseAllocatePostSmmPen()
24
25 //
26 // We use this protocol for accessing IO Ports.
27 //
28 STATIC EFI_MM_CPU_IO_PROTOCOL *mMmCpuIo;
29 //
30 // The following protocol is used to report the addition or removal of a CPU to
31 // the SMM CPU driver (PiSmmCpuDxeSmm).
32 //
33 STATIC EFI_SMM_CPU_SERVICE_PROTOCOL *mMmCpuService;
34 //
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).
38 //
39 STATIC CPU_HOT_PLUG_DATA *mCpuHotPlugData;
40 //
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.
43 //
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).
47 //
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.
54 //
55 STATIC APIC_ID *mPluggedApicIds;
56 STATIC APIC_ID *mToUnplugApicIds;
57 STATIC UINT32 *mToUnplugSelectors;
58 //
59 // Address of the non-SMRAM reserved memory page that contains the Post-SMM Pen
60 // for hot-added CPUs.
61 //
62 STATIC UINT32 mPostSmmPenAddress;
63 //
64 // Represents the registration of the CPU Hotplug MMI handler.
65 //
66 STATIC EFI_HANDLE mDispatchHandle;
67
68 /**
69 Process CPUs that have been hot-added, per QemuCpuhpCollectApicIds().
70
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.
74
75 @param[in] PluggedApicIds The APIC IDs of the CPUs that have been
76 hot-plugged.
77
78 @param[in] PluggedCount The number of filled-in APIC IDs in
79 PluggedApicIds.
80
81 @retval EFI_SUCCESS CPUs corresponding to all the APIC IDs are
82 populated.
83
84 @retval EFI_OUT_OF_RESOURCES Out of APIC ID space in "mCpuHotPlugData".
85
86 @return Error codes propagated from SmbaseRelocate()
87 and mMmCpuService->AddProcessor().
88 **/
89 STATIC
90 EFI_STATUS
91 ProcessHotAddedCpus (
92 IN APIC_ID *PluggedApicIds,
93 IN UINT32 PluggedCount
94 )
95 {
96 EFI_STATUS Status;
97 UINT32 PluggedIdx;
98 UINT32 NewSlot;
99
100 //
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
104 // suffice.
105 //
106 SmbaseReinstallPostSmmPen (mPostSmmPenAddress);
107
108 PluggedIdx = 0;
109 NewSlot = 0;
110 while (PluggedIdx < PluggedCount) {
111 APIC_ID NewApicId;
112 UINT32 CheckSlot;
113 UINTN NewProcessorNumberByProtocol;
114
115 NewApicId = PluggedApicIds[PluggedIdx];
116
117 //
118 // Check if the supposedly hot-added CPU is already known to us.
119 //
120 for (CheckSlot = 0;
121 CheckSlot < mCpuHotPlugData->ArrayLength;
122 CheckSlot++) {
123 if (mCpuHotPlugData->ApicId[CheckSlot] == NewApicId) {
124 break;
125 }
126 }
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));
130 PluggedIdx++;
131 continue;
132 }
133
134 //
135 // Find the first empty slot in CPU_HOT_PLUG_DATA.
136 //
137 while (NewSlot < mCpuHotPlugData->ArrayLength &&
138 mCpuHotPlugData->ApicId[NewSlot] != MAX_UINT64) {
139 NewSlot++;
140 }
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;
145 }
146
147 //
148 // Store the APIC ID of the new processor to the slot.
149 //
150 mCpuHotPlugData->ApicId[NewSlot] = NewApicId;
151
152 //
153 // Relocate the SMBASE of the new CPU.
154 //
155 Status = SmbaseRelocate (NewApicId, mCpuHotPlugData->SmBase[NewSlot],
156 mPostSmmPenAddress);
157 if (EFI_ERROR (Status)) {
158 goto RevokeNewSlot;
159 }
160
161 //
162 // Add the new CPU with EFI_SMM_CPU_SERVICE_PROTOCOL.
163 //
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));
169 goto RevokeNewSlot;
170 }
171
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));
176
177 NewSlot++;
178 PluggedIdx++;
179 }
180
181 //
182 // We've processed this batch of hot-added CPUs.
183 //
184 return EFI_SUCCESS;
185
186 RevokeNewSlot:
187 mCpuHotPlugData->ApicId[NewSlot] = MAX_UINT64;
188
189 return Status;
190 }
191
192 /**
193 CPU Hotplug MMI handler function.
194
195 This is a root MMI handler.
196
197 @param[in] DispatchHandle The unique handle assigned to this handler by
198 EFI_MM_SYSTEM_TABLE.MmiHandlerRegister().
199
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.
204
205 @param[in,out] CommBuffer Ignored, due to CpuHotplugMmi() being a root
206 MMI handler.
207
208 @param[in,out] CommBufferSize Ignored, due to CpuHotplugMmi() being a root
209 MMI handler.
210
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()
224 anyway.
225
226 @retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The MMI source has been quiesced,
227 but other handlers should still
228 be called.
229
230 @retval EFI_WARN_INTERRUPT_SOURCE_PENDING The MMI source is still pending,
231 and other handlers should still
232 be called.
233
234 @retval EFI_INTERRUPT_PENDING The MMI source could not be
235 quiesced.
236 **/
237 STATIC
238 EFI_STATUS
239 EFIAPI
240 CpuHotplugMmi (
241 IN EFI_HANDLE DispatchHandle,
242 IN CONST VOID *Context OPTIONAL,
243 IN OUT VOID *CommBuffer OPTIONAL,
244 IN OUT UINTN *CommBufferSize OPTIONAL
245 )
246 {
247 EFI_STATUS Status;
248 UINT8 ApmControl;
249 UINT32 PluggedCount;
250 UINT32 ToUnplugCount;
251
252 //
253 // Assert that we are entering this function due to our root MMI handler
254 // registration.
255 //
256 ASSERT (DispatchHandle == mDispatchHandle);
257 //
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.
261 //
262 ASSERT (Context == NULL);
263 //
264 // Read the MMI command value from the APM Control Port, to see if this is an
265 // MMI we should care about.
266 //
267 Status = mMmCpuIo->Io.Read (mMmCpuIo, MM_IO_UINT8, ICH9_APM_CNT, 1,
268 &ApmControl);
269 if (EFI_ERROR (Status)) {
270 DEBUG ((DEBUG_ERROR, "%a: failed to read ICH9_APM_CNT: %r\n", __FUNCTION__,
271 Status));
272 //
273 // We couldn't even determine if the MMI was for us or not.
274 //
275 goto Fatal;
276 }
277
278 if (ApmControl != ICH9_APM_CNT_CPU_HOTPLUG) {
279 //
280 // The MMI is not for us.
281 //
282 return EFI_WARN_INTERRUPT_SOURCE_QUIESCED;
283 }
284
285 //
286 // Collect the CPUs with pending events.
287 //
288 Status = QemuCpuhpCollectApicIds (
289 mMmCpuIo,
290 mCpuHotPlugData->ArrayLength, // PossibleCpuCount
291 mCpuHotPlugData->ArrayLength - 1, // ApicIdCount
292 mPluggedApicIds,
293 &PluggedCount,
294 mToUnplugApicIds,
295 mToUnplugSelectors,
296 &ToUnplugCount
297 );
298 if (EFI_ERROR (Status)) {
299 goto Fatal;
300 }
301 if (ToUnplugCount > 0) {
302 DEBUG ((DEBUG_ERROR, "%a: hot-unplug is not supported yet\n",
303 __FUNCTION__));
304 goto Fatal;
305 }
306
307 if (PluggedCount > 0) {
308 Status = ProcessHotAddedCpus (mPluggedApicIds, PluggedCount);
309 if (EFI_ERROR (Status)) {
310 goto Fatal;
311 }
312 }
313
314 //
315 // We've handled this MMI.
316 //
317 return EFI_SUCCESS;
318
319 Fatal:
320 ASSERT (FALSE);
321 CpuDeadLoop ();
322 //
323 // We couldn't handle this MMI.
324 //
325 return EFI_INTERRUPT_PENDING;
326 }
327
328
329 //
330 // Entry point function of this driver.
331 //
332 EFI_STATUS
333 EFIAPI
334 CpuHotplugEntry (
335 IN EFI_HANDLE ImageHandle,
336 IN EFI_SYSTEM_TABLE *SystemTable
337 )
338 {
339 EFI_STATUS Status;
340 UINTN Len;
341 UINTN Size;
342 UINTN SizeSel;
343
344 //
345 // This module should only be included when SMM support is required.
346 //
347 ASSERT (FeaturePcdGet (PcdSmmSmramRequire));
348 //
349 // This driver depends on the dynamically detected "SMRAM at default SMBASE"
350 // feature.
351 //
352 if (!PcdGetBool (PcdQ35SmramAtDefaultSmbase)) {
353 return EFI_UNSUPPORTED;
354 }
355
356 //
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.
359 //
360 // First, collect the protocols needed later. All of these protocols are
361 // listed in our module DEPEX.
362 //
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));
367 goto Fatal;
368 }
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__,
373 Status));
374 goto Fatal;
375 }
376
377 //
378 // Our DEPEX on EFI_SMM_CPU_SERVICE_PROTOCOL guarantees that PiSmmCpuDxeSmm
379 // has pointed PcdCpuHotPlugDataAddress to CPU_HOT_PLUG_DATA in SMRAM.
380 //
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));
385 goto Fatal;
386 }
387 //
388 // If the possible CPU count is 1, there's nothing for this driver to do.
389 //
390 if (mCpuHotPlugData->ArrayLength == 1) {
391 return EFI_UNSUPPORTED;
392 }
393 //
394 // Allocate the data structures that depend on the possible CPU count.
395 //
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__));
401 goto Fatal;
402 }
403 Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, Size,
404 (VOID **)&mPluggedApicIds);
405 if (EFI_ERROR (Status)) {
406 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));
407 goto Fatal;
408 }
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;
414 }
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;
420 }
421
422 //
423 // Allocate the Post-SMM Pen for hot-added CPUs.
424 //
425 Status = SmbaseAllocatePostSmmPen (&mPostSmmPenAddress,
426 SystemTable->BootServices);
427 if (EFI_ERROR (Status)) {
428 goto ReleaseToUnplugSelectors;
429 }
430
431 //
432 // Sanity-check the CPU hotplug interface.
433 //
434 // Both of the following features are part of QEMU 5.0, introduced primarily
435 // in commit range 3e08b2b9cb64..3a61c8db9d25:
436 //
437 // (a) the QEMU_CPUHP_CMD_GET_ARCH_ID command of the modern CPU hotplug
438 // interface,
439 //
440 // (b) the "SMRAM at default SMBASE" feature.
441 //
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.
448 //
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
451 // following.
452 //
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;
461 }
462
463 //
464 // Register the handler for the CPU Hotplug MMI.
465 //
466 Status = gMmst->MmiHandlerRegister (
467 CpuHotplugMmi,
468 NULL, // HandlerType: root MMI handler
469 &mDispatchHandle
470 );
471 if (EFI_ERROR (Status)) {
472 DEBUG ((DEBUG_ERROR, "%a: MmiHandlerRegister(): %r\n", __FUNCTION__,
473 Status));
474 goto ReleasePostSmmPen;
475 }
476
477 //
478 // Install the handler for the hot-added CPUs' first SMI.
479 //
480 SmbaseInstallFirstSmiHandler ();
481
482 return EFI_SUCCESS;
483
484 ReleasePostSmmPen:
485 SmbaseReleasePostSmmPen (mPostSmmPenAddress, SystemTable->BootServices);
486 mPostSmmPenAddress = 0;
487
488 ReleaseToUnplugSelectors:
489 gMmst->MmFreePool (mToUnplugSelectors);
490 mToUnplugSelectors = NULL;
491
492 ReleaseToUnplugApicIds:
493 gMmst->MmFreePool (mToUnplugApicIds);
494 mToUnplugApicIds = NULL;
495
496 ReleasePluggedApicIds:
497 gMmst->MmFreePool (mPluggedApicIds);
498 mPluggedApicIds = NULL;
499
500 Fatal:
501 ASSERT (FALSE);
502 CpuDeadLoop ();
503 return Status;
504 }