]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/CpuHotplugSmm/CpuHotplug.c
UefiCpuPkg: Move AsmRelocateApLoopStart from Mpfuncs.nasm to AmdSev.nasm
[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/CpuLib.h> // CpuSleep()
14 #include <Library/DebugLib.h> // ASSERT()
15 #include <Library/MmServicesTableLib.h> // gMmst
16 #include <Library/PcdLib.h> // PcdGetBool()
17 #include <Library/SafeIntLib.h> // SafeUintnSub()
18 #include <Pcd/CpuHotEjectData.h> // CPU_HOT_EJECT_DATA
19 #include <Protocol/MmCpuIo.h> // EFI_MM_CPU_IO_PROTOCOL
20 #include <Protocol/SmmCpuService.h> // EFI_SMM_CPU_SERVICE_PROTOCOL
21 #include <Register/Intel/ArchitecturalMsr.h> // MSR_IA32_APIC_BASE_REGISTER
22 #include <Uefi/UefiBaseType.h> // EFI_STATUS
23
24 #include "ApicId.h" // APIC_ID
25 #include "QemuCpuhp.h" // QemuCpuhpWriteCpuSelector()
26 #include "Smbase.h" // SmbaseAllocatePostSmmPen()
27
28 //
29 // We use this protocol for accessing IO Ports.
30 //
31 STATIC EFI_MM_CPU_IO_PROTOCOL *mMmCpuIo;
32 //
33 // The following protocol is used to report the addition or removal of a CPU to
34 // the SMM CPU driver (PiSmmCpuDxeSmm).
35 //
36 STATIC EFI_SMM_CPU_SERVICE_PROTOCOL *mMmCpuService;
37 //
38 // These structures serve as communication side-channels between the
39 // EFI_SMM_CPU_SERVICE_PROTOCOL consumer (i.e., this driver) and provider
40 // (i.e., PiSmmCpuDxeSmm).
41 //
42 STATIC CPU_HOT_PLUG_DATA *mCpuHotPlugData;
43 STATIC CPU_HOT_EJECT_DATA *mCpuHotEjectData;
44 //
45 // SMRAM arrays for fetching the APIC IDs of processors with pending events (of
46 // known event types), for the time of just one MMI.
47 //
48 // The lifetimes of these arrays match that of this driver only because we
49 // don't want to allocate SMRAM at OS runtime, and potentially fail (or
50 // fragment the SMRAM map).
51 //
52 // The first array stores APIC IDs for hot-plug events, the second and the
53 // third store APIC IDs and QEMU CPU Selectors (both indexed similarly) for
54 // hot-unplug events. All of these provide room for "possible CPU count" minus
55 // one elements as we don't expect every possible CPU to appear, or disappear,
56 // in a single MMI. The numbers of used (populated) elements in the arrays are
57 // determined on every MMI separately.
58 //
59 STATIC APIC_ID *mPluggedApicIds;
60 STATIC APIC_ID *mToUnplugApicIds;
61 STATIC UINT32 *mToUnplugSelectors;
62 //
63 // Address of the non-SMRAM reserved memory page that contains the Post-SMM Pen
64 // for hot-added CPUs.
65 //
66 STATIC UINT32 mPostSmmPenAddress;
67 //
68 // Represents the registration of the CPU Hotplug MMI handler.
69 //
70 STATIC EFI_HANDLE mDispatchHandle;
71
72 /**
73 Process CPUs that have been hot-added, per QemuCpuhpCollectApicIds().
74
75 For each such CPU, relocate the SMBASE, and report the CPU to PiSmmCpuDxeSmm
76 via EFI_SMM_CPU_SERVICE_PROTOCOL. If the supposedly hot-added CPU is already
77 known, skip it silently.
78
79 @param[in] PluggedApicIds The APIC IDs of the CPUs that have been
80 hot-plugged.
81
82 @param[in] PluggedCount The number of filled-in APIC IDs in
83 PluggedApicIds.
84
85 @retval EFI_SUCCESS CPUs corresponding to all the APIC IDs are
86 populated.
87
88 @retval EFI_OUT_OF_RESOURCES Out of APIC ID space in "mCpuHotPlugData".
89
90 @return Error codes propagated from SmbaseRelocate()
91 and mMmCpuService->AddProcessor().
92 **/
93 STATIC
94 EFI_STATUS
95 ProcessHotAddedCpus (
96 IN APIC_ID *PluggedApicIds,
97 IN UINT32 PluggedCount
98 )
99 {
100 EFI_STATUS Status;
101 UINT32 PluggedIdx;
102 UINT32 NewSlot;
103
104 //
105 // The Post-SMM Pen need not be reinstalled multiple times within a single
106 // root MMI handling. Even reinstalling once per root MMI is only prudence;
107 // in theory installing the pen in the driver's entry point function should
108 // suffice.
109 //
110 SmbaseReinstallPostSmmPen (mPostSmmPenAddress);
111
112 PluggedIdx = 0;
113 NewSlot = 0;
114 while (PluggedIdx < PluggedCount) {
115 APIC_ID NewApicId;
116 UINT32 CheckSlot;
117 UINTN NewProcessorNumberByProtocol;
118
119 NewApicId = PluggedApicIds[PluggedIdx];
120
121 //
122 // Check if the supposedly hot-added CPU is already known to us.
123 //
124 for (CheckSlot = 0;
125 CheckSlot < mCpuHotPlugData->ArrayLength;
126 CheckSlot++)
127 {
128 if (mCpuHotPlugData->ApicId[CheckSlot] == NewApicId) {
129 break;
130 }
131 }
132
133 if (CheckSlot < mCpuHotPlugData->ArrayLength) {
134 DEBUG ((
135 DEBUG_VERBOSE,
136 "%a: APIC ID " FMT_APIC_ID " was hot-plugged "
137 "before; ignoring it\n",
138 __FUNCTION__,
139 NewApicId
140 ));
141 PluggedIdx++;
142 continue;
143 }
144
145 //
146 // Find the first empty slot in CPU_HOT_PLUG_DATA.
147 //
148 while (NewSlot < mCpuHotPlugData->ArrayLength &&
149 mCpuHotPlugData->ApicId[NewSlot] != MAX_UINT64)
150 {
151 NewSlot++;
152 }
153
154 if (NewSlot == mCpuHotPlugData->ArrayLength) {
155 DEBUG ((
156 DEBUG_ERROR,
157 "%a: no room for APIC ID " FMT_APIC_ID "\n",
158 __FUNCTION__,
159 NewApicId
160 ));
161 return EFI_OUT_OF_RESOURCES;
162 }
163
164 //
165 // Store the APIC ID of the new processor to the slot.
166 //
167 mCpuHotPlugData->ApicId[NewSlot] = NewApicId;
168
169 //
170 // Relocate the SMBASE of the new CPU.
171 //
172 Status = SmbaseRelocate (
173 NewApicId,
174 mCpuHotPlugData->SmBase[NewSlot],
175 mPostSmmPenAddress
176 );
177 if (EFI_ERROR (Status)) {
178 goto RevokeNewSlot;
179 }
180
181 //
182 // Add the new CPU with EFI_SMM_CPU_SERVICE_PROTOCOL.
183 //
184 Status = mMmCpuService->AddProcessor (
185 mMmCpuService,
186 NewApicId,
187 &NewProcessorNumberByProtocol
188 );
189 if (EFI_ERROR (Status)) {
190 DEBUG ((
191 DEBUG_ERROR,
192 "%a: AddProcessor(" FMT_APIC_ID "): %r\n",
193 __FUNCTION__,
194 NewApicId,
195 Status
196 ));
197 goto RevokeNewSlot;
198 }
199
200 DEBUG ((
201 DEBUG_INFO,
202 "%a: hot-added APIC ID " FMT_APIC_ID ", SMBASE 0x%Lx, "
203 "EFI_SMM_CPU_SERVICE_PROTOCOL assigned number %Lu\n",
204 __FUNCTION__,
205 NewApicId,
206 (UINT64)mCpuHotPlugData->SmBase[NewSlot],
207 (UINT64)NewProcessorNumberByProtocol
208 ));
209
210 NewSlot++;
211 PluggedIdx++;
212 }
213
214 //
215 // We've processed this batch of hot-added CPUs.
216 //
217 return EFI_SUCCESS;
218
219 RevokeNewSlot:
220 mCpuHotPlugData->ApicId[NewSlot] = MAX_UINT64;
221
222 return Status;
223 }
224
225 /**
226 EjectCpu needs to know the BSP at SMI exit at a point when
227 some of the EFI_SMM_CPU_SERVICE_PROTOCOL state has been torn
228 down.
229 Reuse the logic from OvmfPkg::PlatformSmmBspElection() to
230 do that.
231
232 @retval TRUE If the CPU executing this function is the BSP.
233
234 @retval FALSE If the CPU executing this function is an AP.
235 **/
236 STATIC
237 BOOLEAN
238 CheckIfBsp (
239 VOID
240 )
241 {
242 MSR_IA32_APIC_BASE_REGISTER ApicBaseMsr;
243 BOOLEAN IsBsp;
244
245 ApicBaseMsr.Uint64 = AsmReadMsr64 (MSR_IA32_APIC_BASE);
246 IsBsp = (BOOLEAN)(ApicBaseMsr.Bits.BSP == 1);
247 return IsBsp;
248 }
249
250 /**
251 CPU Hot-eject handler, called from SmmCpuFeaturesRendezvousExit()
252 on each CPU at exit from SMM.
253
254 If, the executing CPU is neither the BSP, nor being ejected, nothing
255 to be done.
256 If, the executing CPU is being ejected, wait in a halted loop
257 until ejected.
258 If, the executing CPU is the BSP, set QEMU CPU status to eject
259 for CPUs being ejected.
260
261 @param[in] ProcessorNum ProcessorNum denotes the CPU exiting SMM,
262 and will be used as an index into
263 CPU_HOT_EJECT_DATA->QemuSelectorMap. It is
264 identical to the processor handle number in
265 EFI_SMM_CPU_SERVICE_PROTOCOL.
266 **/
267 VOID
268 EFIAPI
269 EjectCpu (
270 IN UINTN ProcessorNum
271 )
272 {
273 UINT64 QemuSelector;
274
275 if (CheckIfBsp ()) {
276 UINT32 Idx;
277
278 for (Idx = 0; Idx < mCpuHotEjectData->ArrayLength; Idx++) {
279 QemuSelector = mCpuHotEjectData->QemuSelectorMap[Idx];
280
281 if (QemuSelector != CPU_EJECT_QEMU_SELECTOR_INVALID) {
282 //
283 // This to-be-ejected-CPU has already received the BSP's SMI exit
284 // signal and will execute SmmCpuFeaturesRendezvousExit()
285 // followed by this callback or is already penned in the
286 // CpuSleep() loop below.
287 //
288 // Tell QEMU to context-switch it out.
289 //
290 QemuCpuhpWriteCpuSelector (mMmCpuIo, (UINT32)QemuSelector);
291 QemuCpuhpWriteCpuStatus (mMmCpuIo, QEMU_CPUHP_STAT_EJECT);
292
293 //
294 // Now that we've ejected the CPU corresponding to QemuSelectorMap[Idx],
295 // clear its eject status to ensure that an invalid future SMI does
296 // not end up trying a spurious eject or a newly hotplugged CPU does
297 // not get penned in the CpuSleep() loop.
298 //
299 // Note that the QemuCpuhpWriteCpuStatus() command above is a write to
300 // a different address space and uses the EFI_MM_CPU_IO_PROTOCOL.
301 //
302 // This means that we are guaranteed that the following assignment
303 // will not be reordered before the eject. And, so we can safely
304 // do this write here.
305 //
306 mCpuHotEjectData->QemuSelectorMap[Idx] =
307 CPU_EJECT_QEMU_SELECTOR_INVALID;
308
309 DEBUG ((
310 DEBUG_INFO,
311 "%a: Unplugged ProcessorNum %u, "
312 "QemuSelector %Lu\n",
313 __FUNCTION__,
314 Idx,
315 QemuSelector
316 ));
317 }
318 }
319
320 //
321 // We are done until the next hot-unplug; clear the handler.
322 //
323 // mCpuHotEjectData->Handler is a NOP for any CPU not under ejection.
324 // So, once we are done with all the ejections, we can safely reset it
325 // here since any CPU dereferencing it would only see either the old
326 // or the new value (since it is aligned at a natural boundary.)
327 //
328 mCpuHotEjectData->Handler = NULL;
329 return;
330 }
331
332 //
333 // Reached only on APs
334 //
335
336 //
337 // mCpuHotEjectData->QemuSelectorMap[ProcessorNum] is updated
338 // on the BSP in the ongoing SMI at two places:
339 //
340 // - UnplugCpus() where the BSP determines if a CPU is under ejection
341 // or not. As a comment in UnplugCpus() at set-up, and in
342 // SmmCpuFeaturesRendezvousExit() where it is dereferenced describe,
343 // any such updates are guaranteed to be ordered-before the
344 // dereference below.
345 //
346 // - EjectCpu() on the BSP (above) updates QemuSelectorMap[ProcessorNum]
347 // for a CPU once it's ejected.
348 //
349 // The CPU under ejection: might be executing anywhere between the
350 // AllCpusInSync loop in SmiRendezvous(), to about to dereference
351 // QemuSelectorMap[ProcessorNum].
352 // As described in the comment above where we do the reset, this
353 // is not a problem since the ejected CPU never sees the after value.
354 // CPUs not-under ejection: never see any changes so they are fine.
355 //
356 QemuSelector = mCpuHotEjectData->QemuSelectorMap[ProcessorNum];
357 if (QemuSelector == CPU_EJECT_QEMU_SELECTOR_INVALID) {
358 return;
359 }
360
361 //
362 // APs being unplugged get here from SmmCpuFeaturesRendezvousExit()
363 // after having been cleared to exit the SMI and so have no SMM
364 // processing remaining.
365 //
366 // Keep them penned here until the BSP tells QEMU to eject them.
367 //
368 for ( ; ;) {
369 DisableInterrupts ();
370 CpuSleep ();
371 }
372 }
373
374 /**
375 Process to be hot-unplugged CPUs, per QemuCpuhpCollectApicIds().
376
377 For each such CPU, report the CPU to PiSmmCpuDxeSmm via
378 EFI_SMM_CPU_SERVICE_PROTOCOL and stash the QEMU Cpu Selectors for later
379 ejection. If the to be hot-unplugged CPU is unknown, skip it silently.
380
381 Additonally, if we do stash any Cpu Selectors, also install a CPU eject
382 handler which would handle the ejection.
383
384 @param[in] ToUnplugApicIds The APIC IDs of the CPUs that are about to be
385 hot-unplugged.
386
387 @param[in] ToUnplugSelectors The QEMU Selectors of the CPUs that are about to
388 be hot-unplugged.
389
390 @param[in] ToUnplugCount The number of filled-in APIC IDs in
391 ToUnplugApicIds.
392
393 @retval EFI_ALREADY_STARTED For the ProcessorNum that
394 EFI_SMM_CPU_SERVICE_PROTOCOL had assigned to
395 one of the APIC IDs in ToUnplugApicIds,
396 mCpuHotEjectData->QemuSelectorMap already has
397 the QemuSelector value stashed. (This should
398 never happen.)
399
400 @retval EFI_SUCCESS Known APIC IDs have been removed from SMM data
401 structures.
402
403 @return Error codes propagated from
404 mMmCpuService->RemoveProcessor().
405 **/
406 STATIC
407 EFI_STATUS
408 UnplugCpus (
409 IN APIC_ID *ToUnplugApicIds,
410 IN UINT32 *ToUnplugSelectors,
411 IN UINT32 ToUnplugCount
412 )
413 {
414 EFI_STATUS Status;
415 UINT32 ToUnplugIdx;
416 UINT32 EjectCount;
417 UINTN ProcessorNum;
418
419 ToUnplugIdx = 0;
420 EjectCount = 0;
421 while (ToUnplugIdx < ToUnplugCount) {
422 APIC_ID RemoveApicId;
423 UINT32 QemuSelector;
424
425 RemoveApicId = ToUnplugApicIds[ToUnplugIdx];
426 QemuSelector = ToUnplugSelectors[ToUnplugIdx];
427
428 //
429 // mCpuHotPlugData->ApicId maps ProcessorNum -> ApicId. Use RemoveApicId
430 // to find the corresponding ProcessorNum for the CPU to be removed.
431 //
432 // With this we can establish a 3 way mapping:
433 // APIC_ID -- ProcessorNum -- QemuSelector
434 //
435 // We stash the ProcessorNum -> QemuSelector mapping so it can later be
436 // used for CPU hot-eject in SmmCpuFeaturesRendezvousExit() context (where
437 // we only have ProcessorNum available.)
438 //
439
440 for (ProcessorNum = 0;
441 ProcessorNum < mCpuHotPlugData->ArrayLength;
442 ProcessorNum++)
443 {
444 if (mCpuHotPlugData->ApicId[ProcessorNum] == RemoveApicId) {
445 break;
446 }
447 }
448
449 //
450 // Ignore the unplug if APIC ID not found
451 //
452 if (ProcessorNum == mCpuHotPlugData->ArrayLength) {
453 DEBUG ((
454 DEBUG_VERBOSE,
455 "%a: did not find APIC ID " FMT_APIC_ID
456 " to unplug\n",
457 __FUNCTION__,
458 RemoveApicId
459 ));
460 ToUnplugIdx++;
461 continue;
462 }
463
464 //
465 // Mark ProcessorNum for removal from SMM data structures
466 //
467 Status = mMmCpuService->RemoveProcessor (mMmCpuService, ProcessorNum);
468 if (EFI_ERROR (Status)) {
469 DEBUG ((
470 DEBUG_ERROR,
471 "%a: RemoveProcessor(" FMT_APIC_ID "): %r\n",
472 __FUNCTION__,
473 RemoveApicId,
474 Status
475 ));
476 return Status;
477 }
478
479 if (mCpuHotEjectData->QemuSelectorMap[ProcessorNum] !=
480 CPU_EJECT_QEMU_SELECTOR_INVALID)
481 {
482 //
483 // mCpuHotEjectData->QemuSelectorMap[ProcessorNum] is set to
484 // CPU_EJECT_QEMU_SELECTOR_INVALID when mCpuHotEjectData->QemuSelectorMap
485 // is allocated, and once the subject processsor is ejected.
486 //
487 // Additionally, mMmCpuService->RemoveProcessor(ProcessorNum) invalidates
488 // mCpuHotPlugData->ApicId[ProcessorNum], so a given ProcessorNum can
489 // never match more than one APIC ID -- nor, by transitivity, designate
490 // more than one QemuSelector -- in a single invocation of UnplugCpus().
491 //
492 DEBUG ((
493 DEBUG_ERROR,
494 "%a: ProcessorNum %Lu maps to QemuSelector %Lu, "
495 "cannot also map to %u\n",
496 __FUNCTION__,
497 (UINT64)ProcessorNum,
498 mCpuHotEjectData->QemuSelectorMap[ProcessorNum],
499 QemuSelector
500 ));
501
502 return EFI_ALREADY_STARTED;
503 }
504
505 //
506 // Stash the QemuSelector so we can do the actual ejection later.
507 //
508 mCpuHotEjectData->QemuSelectorMap[ProcessorNum] = (UINT64)QemuSelector;
509
510 DEBUG ((
511 DEBUG_INFO,
512 "%a: Started hot-unplug on ProcessorNum %Lu, APIC ID "
513 FMT_APIC_ID ", QemuSelector %u\n",
514 __FUNCTION__,
515 (UINT64)ProcessorNum,
516 RemoveApicId,
517 QemuSelector
518 ));
519
520 EjectCount++;
521 ToUnplugIdx++;
522 }
523
524 if (EjectCount != 0) {
525 //
526 // We have processors to be ejected; install the handler.
527 //
528 mCpuHotEjectData->Handler = EjectCpu;
529
530 //
531 // The BSP and APs load mCpuHotEjectData->Handler, and
532 // mCpuHotEjectData->QemuSelectorMap[] in SmmCpuFeaturesRendezvousExit()
533 // and EjectCpu().
534 //
535 // The comment in SmmCpuFeaturesRendezvousExit() details how we use
536 // the AllCpusInSync control-dependency to ensure that any loads are
537 // ordered-after the stores above.
538 //
539 // Ensure that the stores above are ordered-before the AllCpusInSync store
540 // by using a MemoryFence() with release semantics.
541 //
542 MemoryFence ();
543 }
544
545 //
546 // We've removed this set of APIC IDs from SMM data structures and
547 // have installed an ejection handler if needed.
548 //
549 return EFI_SUCCESS;
550 }
551
552 /**
553 CPU Hotplug MMI handler function.
554
555 This is a root MMI handler.
556
557 @param[in] DispatchHandle The unique handle assigned to this handler by
558 EFI_MM_SYSTEM_TABLE.MmiHandlerRegister().
559
560 @param[in] Context Context passed in by
561 EFI_MM_SYSTEM_TABLE.MmiManage(). Due to
562 CpuHotplugMmi() being a root MMI handler,
563 Context is ASSERT()ed to be NULL.
564
565 @param[in,out] CommBuffer Ignored, due to CpuHotplugMmi() being a root
566 MMI handler.
567
568 @param[in,out] CommBufferSize Ignored, due to CpuHotplugMmi() being a root
569 MMI handler.
570
571 @retval EFI_SUCCESS The MMI was handled and the MMI
572 source was quiesced. When returned
573 by a non-root MMI handler,
574 EFI_SUCCESS terminates the
575 processing of MMI handlers in
576 EFI_MM_SYSTEM_TABLE.MmiManage().
577 For a root MMI handler (i.e., for
578 the present function too),
579 EFI_SUCCESS behaves identically to
580 EFI_WARN_INTERRUPT_SOURCE_QUIESCED,
581 as further root MMI handlers are
582 going to be called by
583 EFI_MM_SYSTEM_TABLE.MmiManage()
584 anyway.
585
586 @retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The MMI source has been quiesced,
587 but other handlers should still
588 be called.
589
590 @retval EFI_WARN_INTERRUPT_SOURCE_PENDING The MMI source is still pending,
591 and other handlers should still
592 be called.
593
594 @retval EFI_INTERRUPT_PENDING The MMI source could not be
595 quiesced.
596 **/
597 STATIC
598 EFI_STATUS
599 EFIAPI
600 CpuHotplugMmi (
601 IN EFI_HANDLE DispatchHandle,
602 IN CONST VOID *Context OPTIONAL,
603 IN OUT VOID *CommBuffer OPTIONAL,
604 IN OUT UINTN *CommBufferSize OPTIONAL
605 )
606 {
607 EFI_STATUS Status;
608 UINT8 ApmControl;
609 UINT32 PluggedCount;
610 UINT32 ToUnplugCount;
611
612 //
613 // Assert that we are entering this function due to our root MMI handler
614 // registration.
615 //
616 ASSERT (DispatchHandle == mDispatchHandle);
617 //
618 // When MmiManage() is invoked to process root MMI handlers, the caller (the
619 // MM Core) is expected to pass in a NULL Context. MmiManage() then passes
620 // the same NULL Context to individual handlers.
621 //
622 ASSERT (Context == NULL);
623 //
624 // Read the MMI command value from the APM Control Port, to see if this is an
625 // MMI we should care about.
626 //
627 Status = mMmCpuIo->Io.Read (
628 mMmCpuIo,
629 MM_IO_UINT8,
630 ICH9_APM_CNT,
631 1,
632 &ApmControl
633 );
634 if (EFI_ERROR (Status)) {
635 DEBUG ((
636 DEBUG_ERROR,
637 "%a: failed to read ICH9_APM_CNT: %r\n",
638 __FUNCTION__,
639 Status
640 ));
641 //
642 // We couldn't even determine if the MMI was for us or not.
643 //
644 goto Fatal;
645 }
646
647 if (ApmControl != ICH9_APM_CNT_CPU_HOTPLUG) {
648 //
649 // The MMI is not for us.
650 //
651 return EFI_WARN_INTERRUPT_SOURCE_QUIESCED;
652 }
653
654 //
655 // Collect the CPUs with pending events.
656 //
657 Status = QemuCpuhpCollectApicIds (
658 mMmCpuIo,
659 mCpuHotPlugData->ArrayLength, // PossibleCpuCount
660 mCpuHotPlugData->ArrayLength - 1, // ApicIdCount
661 mPluggedApicIds,
662 &PluggedCount,
663 mToUnplugApicIds,
664 mToUnplugSelectors,
665 &ToUnplugCount
666 );
667 if (EFI_ERROR (Status)) {
668 goto Fatal;
669 }
670
671 if (PluggedCount > 0) {
672 Status = ProcessHotAddedCpus (mPluggedApicIds, PluggedCount);
673 if (EFI_ERROR (Status)) {
674 goto Fatal;
675 }
676 }
677
678 if (ToUnplugCount > 0) {
679 Status = UnplugCpus (mToUnplugApicIds, mToUnplugSelectors, ToUnplugCount);
680 if (EFI_ERROR (Status)) {
681 goto Fatal;
682 }
683 }
684
685 //
686 // We've handled this MMI.
687 //
688 return EFI_SUCCESS;
689
690 Fatal:
691 ASSERT (FALSE);
692 CpuDeadLoop ();
693 //
694 // We couldn't handle this MMI.
695 //
696 return EFI_INTERRUPT_PENDING;
697 }
698
699 //
700 // Entry point function of this driver.
701 //
702 EFI_STATUS
703 EFIAPI
704 CpuHotplugEntry (
705 IN EFI_HANDLE ImageHandle,
706 IN EFI_SYSTEM_TABLE *SystemTable
707 )
708 {
709 EFI_STATUS Status;
710 UINTN Len;
711 UINTN Size;
712 UINTN SizeSel;
713
714 //
715 // This module should only be included when SMM support is required.
716 //
717 ASSERT (FeaturePcdGet (PcdSmmSmramRequire));
718 //
719 // This driver depends on the dynamically detected "SMRAM at default SMBASE"
720 // feature.
721 //
722 if (!PcdGetBool (PcdQ35SmramAtDefaultSmbase)) {
723 return EFI_UNSUPPORTED;
724 }
725
726 //
727 // Errors from here on are fatal; we cannot allow the boot to proceed if we
728 // can't set up this driver to handle CPU hotplug.
729 //
730 // First, collect the protocols needed later. All of these protocols are
731 // listed in our module DEPEX.
732 //
733 Status = gMmst->MmLocateProtocol (
734 &gEfiMmCpuIoProtocolGuid,
735 NULL /* Registration */,
736 (VOID **)&mMmCpuIo
737 );
738 if (EFI_ERROR (Status)) {
739 DEBUG ((DEBUG_ERROR, "%a: locate MmCpuIo: %r\n", __FUNCTION__, Status));
740 goto Fatal;
741 }
742
743 Status = gMmst->MmLocateProtocol (
744 &gEfiSmmCpuServiceProtocolGuid,
745 NULL /* Registration */,
746 (VOID **)&mMmCpuService
747 );
748 if (EFI_ERROR (Status)) {
749 DEBUG ((
750 DEBUG_ERROR,
751 "%a: locate MmCpuService: %r\n",
752 __FUNCTION__,
753 Status
754 ));
755 goto Fatal;
756 }
757
758 //
759 // Our DEPEX on EFI_SMM_CPU_SERVICE_PROTOCOL guarantees that PiSmmCpuDxeSmm
760 // has pointed:
761 // - PcdCpuHotPlugDataAddress to CPU_HOT_PLUG_DATA in SMRAM,
762 // - PcdCpuHotEjectDataAddress to CPU_HOT_EJECT_DATA in SMRAM, if the
763 // possible CPU count is greater than 1.
764 //
765 mCpuHotPlugData = (VOID *)(UINTN)PcdGet64 (PcdCpuHotPlugDataAddress);
766 mCpuHotEjectData = (VOID *)(UINTN)PcdGet64 (PcdCpuHotEjectDataAddress);
767
768 if (mCpuHotPlugData == NULL) {
769 Status = EFI_NOT_FOUND;
770 DEBUG ((DEBUG_ERROR, "%a: CPU_HOT_PLUG_DATA: %r\n", __FUNCTION__, Status));
771 goto Fatal;
772 }
773
774 //
775 // If the possible CPU count is 1, there's nothing for this driver to do.
776 //
777 if (mCpuHotPlugData->ArrayLength == 1) {
778 return EFI_UNSUPPORTED;
779 }
780
781 if (mCpuHotEjectData == NULL) {
782 Status = EFI_NOT_FOUND;
783 } else if (mCpuHotPlugData->ArrayLength != mCpuHotEjectData->ArrayLength) {
784 Status = EFI_INVALID_PARAMETER;
785 } else {
786 Status = EFI_SUCCESS;
787 }
788
789 if (EFI_ERROR (Status)) {
790 DEBUG ((DEBUG_ERROR, "%a: CPU_HOT_EJECT_DATA: %r\n", __FUNCTION__, Status));
791 goto Fatal;
792 }
793
794 //
795 // Allocate the data structures that depend on the possible CPU count.
796 //
797 if (RETURN_ERROR (SafeUintnSub (mCpuHotPlugData->ArrayLength, 1, &Len)) ||
798 RETURN_ERROR (SafeUintnMult (sizeof (APIC_ID), Len, &Size)) ||
799 RETURN_ERROR (SafeUintnMult (sizeof (UINT32), Len, &SizeSel)))
800 {
801 Status = EFI_ABORTED;
802 DEBUG ((DEBUG_ERROR, "%a: invalid CPU_HOT_PLUG_DATA\n", __FUNCTION__));
803 goto Fatal;
804 }
805
806 Status = gMmst->MmAllocatePool (
807 EfiRuntimeServicesData,
808 Size,
809 (VOID **)&mPluggedApicIds
810 );
811 if (EFI_ERROR (Status)) {
812 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));
813 goto Fatal;
814 }
815
816 Status = gMmst->MmAllocatePool (
817 EfiRuntimeServicesData,
818 Size,
819 (VOID **)&mToUnplugApicIds
820 );
821 if (EFI_ERROR (Status)) {
822 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));
823 goto ReleasePluggedApicIds;
824 }
825
826 Status = gMmst->MmAllocatePool (
827 EfiRuntimeServicesData,
828 SizeSel,
829 (VOID **)&mToUnplugSelectors
830 );
831 if (EFI_ERROR (Status)) {
832 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));
833 goto ReleaseToUnplugApicIds;
834 }
835
836 //
837 // Allocate the Post-SMM Pen for hot-added CPUs.
838 //
839 Status = SmbaseAllocatePostSmmPen (
840 &mPostSmmPenAddress,
841 SystemTable->BootServices
842 );
843 if (EFI_ERROR (Status)) {
844 goto ReleaseToUnplugSelectors;
845 }
846
847 //
848 // Sanity-check the CPU hotplug interface.
849 //
850 // Both of the following features are part of QEMU 5.0, introduced primarily
851 // in commit range 3e08b2b9cb64..3a61c8db9d25:
852 //
853 // (a) the QEMU_CPUHP_CMD_GET_ARCH_ID command of the modern CPU hotplug
854 // interface,
855 //
856 // (b) the "SMRAM at default SMBASE" feature.
857 //
858 // From these, (b) is restricted to 5.0+ machine type versions, while (a)
859 // does not depend on machine type version. Because we ensured the stricter
860 // condition (b) through PcdQ35SmramAtDefaultSmbase above, the (a)
861 // QEMU_CPUHP_CMD_GET_ARCH_ID command must now be available too. While we
862 // can't verify the presence of precisely that command, we can still verify
863 // (sanity-check) that the modern interface is active, at least.
864 //
865 // Consult the "Typical usecases | Detecting and enabling modern CPU hotplug
866 // interface" section in QEMU's "docs/specs/acpi_cpu_hotplug.txt", on the
867 // following.
868 //
869 QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);
870 QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);
871 QemuCpuhpWriteCommand (mMmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);
872 if (QemuCpuhpReadCommandData2 (mMmCpuIo) != 0) {
873 Status = EFI_NOT_FOUND;
874 DEBUG ((
875 DEBUG_ERROR,
876 "%a: modern CPU hotplug interface: %r\n",
877 __FUNCTION__,
878 Status
879 ));
880 goto ReleasePostSmmPen;
881 }
882
883 //
884 // Register the handler for the CPU Hotplug MMI.
885 //
886 Status = gMmst->MmiHandlerRegister (
887 CpuHotplugMmi,
888 NULL, // HandlerType: root MMI handler
889 &mDispatchHandle
890 );
891 if (EFI_ERROR (Status)) {
892 DEBUG ((
893 DEBUG_ERROR,
894 "%a: MmiHandlerRegister(): %r\n",
895 __FUNCTION__,
896 Status
897 ));
898 goto ReleasePostSmmPen;
899 }
900
901 //
902 // Install the handler for the hot-added CPUs' first SMI.
903 //
904 SmbaseInstallFirstSmiHandler ();
905
906 return EFI_SUCCESS;
907
908 ReleasePostSmmPen:
909 SmbaseReleasePostSmmPen (mPostSmmPenAddress, SystemTable->BootServices);
910 mPostSmmPenAddress = 0;
911
912 ReleaseToUnplugSelectors:
913 gMmst->MmFreePool (mToUnplugSelectors);
914 mToUnplugSelectors = NULL;
915
916 ReleaseToUnplugApicIds:
917 gMmst->MmFreePool (mToUnplugApicIds);
918 mToUnplugApicIds = NULL;
919
920 ReleasePluggedApicIds:
921 gMmst->MmFreePool (mPluggedApicIds);
922 mPluggedApicIds = NULL;
923
924 Fatal:
925 ASSERT (FALSE);
926 CpuDeadLoop ();
927 return Status;
928 }