TianoCore EDK2 master
Loading...
Searching...
No Matches
CpuHotplug.c
Go to the documentation of this file.
1
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//
32//
33// The following protocol is used to report the addition or removal of a CPU to
34// the SMM CPU driver (PiSmmCpuDxeSmm).
35//
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//
42STATIC CPU_HOT_PLUG_DATA *mCpuHotPlugData;
43STATIC 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//
59STATIC APIC_ID *mPluggedApicIds;
60STATIC APIC_ID *mToUnplugApicIds;
61STATIC UINT32 *mToUnplugSelectors;
62//
63// Address of the non-SMRAM reserved memory page that contains the Post-SMM Pen
64// for hot-added CPUs.
65//
66STATIC UINT32 mPostSmmPenAddress;
67//
68// Represents the registration of the CPU Hotplug MMI handler.
69//
70STATIC EFI_HANDLE mDispatchHandle;
71
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 __func__,
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 __func__,
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 __func__,
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 __func__,
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
219RevokeNewSlot:
220 mCpuHotPlugData->ApicId[NewSlot] = MAX_UINT64;
221
222 return Status;
223}
224
236STATIC
237BOOLEAN
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
267VOID
268EFIAPI
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 __func__,
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 /* wait until BSP is done */
359 while (mCpuHotEjectData->Handler != NULL) {
360 CpuPause ();
361 }
362
363 return;
364 }
365
366 //
367 // APs being unplugged get here from SmmCpuFeaturesRendezvousExit()
368 // after having been cleared to exit the SMI and so have no SMM
369 // processing remaining.
370 //
371 // Keep them penned here until the BSP tells QEMU to eject them.
372 //
373 for ( ; ;) {
375 CpuSleep ();
376 }
377}
378
411STATIC
414 IN APIC_ID *ToUnplugApicIds,
415 IN UINT32 *ToUnplugSelectors,
416 IN UINT32 ToUnplugCount
417 )
418{
419 EFI_STATUS Status;
420 UINT32 ToUnplugIdx;
421 UINT32 EjectCount;
422 UINTN ProcessorNum;
423
424 ToUnplugIdx = 0;
425 EjectCount = 0;
426 while (ToUnplugIdx < ToUnplugCount) {
427 APIC_ID RemoveApicId;
428 UINT32 QemuSelector;
429
430 RemoveApicId = ToUnplugApicIds[ToUnplugIdx];
431 QemuSelector = ToUnplugSelectors[ToUnplugIdx];
432
433 //
434 // mCpuHotPlugData->ApicId maps ProcessorNum -> ApicId. Use RemoveApicId
435 // to find the corresponding ProcessorNum for the CPU to be removed.
436 //
437 // With this we can establish a 3 way mapping:
438 // APIC_ID -- ProcessorNum -- QemuSelector
439 //
440 // We stash the ProcessorNum -> QemuSelector mapping so it can later be
441 // used for CPU hot-eject in SmmCpuFeaturesRendezvousExit() context (where
442 // we only have ProcessorNum available.)
443 //
444
445 for (ProcessorNum = 0;
446 ProcessorNum < mCpuHotPlugData->ArrayLength;
447 ProcessorNum++)
448 {
449 if (mCpuHotPlugData->ApicId[ProcessorNum] == RemoveApicId) {
450 break;
451 }
452 }
453
454 //
455 // Ignore the unplug if APIC ID not found
456 //
457 if (ProcessorNum == mCpuHotPlugData->ArrayLength) {
458 DEBUG ((
459 DEBUG_VERBOSE,
460 "%a: did not find APIC ID " FMT_APIC_ID
461 " to unplug\n",
462 __func__,
463 RemoveApicId
464 ));
465 ToUnplugIdx++;
466 continue;
467 }
468
469 //
470 // Mark ProcessorNum for removal from SMM data structures
471 //
472 Status = mMmCpuService->RemoveProcessor (mMmCpuService, ProcessorNum);
473 if (EFI_ERROR (Status)) {
474 DEBUG ((
475 DEBUG_ERROR,
476 "%a: RemoveProcessor(" FMT_APIC_ID "): %r\n",
477 __func__,
478 RemoveApicId,
479 Status
480 ));
481 return Status;
482 }
483
484 if (mCpuHotEjectData->QemuSelectorMap[ProcessorNum] !=
485 CPU_EJECT_QEMU_SELECTOR_INVALID)
486 {
487 //
488 // mCpuHotEjectData->QemuSelectorMap[ProcessorNum] is set to
489 // CPU_EJECT_QEMU_SELECTOR_INVALID when mCpuHotEjectData->QemuSelectorMap
490 // is allocated, and once the subject processsor is ejected.
491 //
492 // Additionally, mMmCpuService->RemoveProcessor(ProcessorNum) invalidates
493 // mCpuHotPlugData->ApicId[ProcessorNum], so a given ProcessorNum can
494 // never match more than one APIC ID -- nor, by transitivity, designate
495 // more than one QemuSelector -- in a single invocation of UnplugCpus().
496 //
497 DEBUG ((
498 DEBUG_ERROR,
499 "%a: ProcessorNum %Lu maps to QemuSelector %Lu, "
500 "cannot also map to %u\n",
501 __func__,
502 (UINT64)ProcessorNum,
503 mCpuHotEjectData->QemuSelectorMap[ProcessorNum],
504 QemuSelector
505 ));
506
507 return EFI_ALREADY_STARTED;
508 }
509
510 //
511 // Stash the QemuSelector so we can do the actual ejection later.
512 //
513 mCpuHotEjectData->QemuSelectorMap[ProcessorNum] = (UINT64)QemuSelector;
514
515 DEBUG ((
516 DEBUG_INFO,
517 "%a: Started hot-unplug on ProcessorNum %Lu, APIC ID "
518 FMT_APIC_ID ", QemuSelector %u\n",
519 __func__,
520 (UINT64)ProcessorNum,
521 RemoveApicId,
522 QemuSelector
523 ));
524
525 EjectCount++;
526 ToUnplugIdx++;
527 }
528
529 if (EjectCount != 0) {
530 //
531 // We have processors to be ejected; install the handler.
532 //
533 mCpuHotEjectData->Handler = EjectCpu;
534
535 //
536 // The BSP and APs load mCpuHotEjectData->Handler, and
537 // mCpuHotEjectData->QemuSelectorMap[] in SmmCpuFeaturesRendezvousExit()
538 // and EjectCpu().
539 //
540 // The comment in SmmCpuFeaturesRendezvousExit() details how we use
541 // the AllCpusInSync control-dependency to ensure that any loads are
542 // ordered-after the stores above.
543 //
544 // Ensure that the stores above are ordered-before the AllCpusInSync store
545 // by using a MemoryFence() with release semantics.
546 //
547 MemoryFence ();
548 }
549
550 //
551 // We've removed this set of APIC IDs from SMM data structures and
552 // have installed an ejection handler if needed.
553 //
554 return EFI_SUCCESS;
555}
556
602STATIC
604EFIAPI
606 IN EFI_HANDLE DispatchHandle,
607 IN CONST VOID *Context OPTIONAL,
608 IN OUT VOID *CommBuffer OPTIONAL,
609 IN OUT UINTN *CommBufferSize OPTIONAL
610 )
611{
612 EFI_STATUS Status;
613 UINT8 ApmControl;
614 UINT32 PluggedCount;
615 UINT32 ToUnplugCount;
616
617 //
618 // Assert that we are entering this function due to our root MMI handler
619 // registration.
620 //
621 ASSERT (DispatchHandle == mDispatchHandle);
622 //
623 // When MmiManage() is invoked to process root MMI handlers, the caller (the
624 // MM Core) is expected to pass in a NULL Context. MmiManage() then passes
625 // the same NULL Context to individual handlers.
626 //
627 ASSERT (Context == NULL);
628 //
629 // Read the MMI command value from the APM Control Port, to see if this is an
630 // MMI we should care about.
631 //
632 Status = mMmCpuIo->Io.Read (
633 mMmCpuIo,
634 MM_IO_UINT8,
635 ICH9_APM_CNT,
636 1,
637 &ApmControl
638 );
639 if (EFI_ERROR (Status)) {
640 DEBUG ((
641 DEBUG_ERROR,
642 "%a: failed to read ICH9_APM_CNT: %r\n",
643 __func__,
644 Status
645 ));
646 //
647 // We couldn't even determine if the MMI was for us or not.
648 //
649 goto Fatal;
650 }
651
652 if (ApmControl != ICH9_APM_CNT_CPU_HOTPLUG) {
653 //
654 // The MMI is not for us.
655 //
656 return EFI_WARN_INTERRUPT_SOURCE_QUIESCED;
657 }
658
659 //
660 // Collect the CPUs with pending events.
661 //
662 Status = QemuCpuhpCollectApicIds (
663 mMmCpuIo,
664 mCpuHotPlugData->ArrayLength, // PossibleCpuCount
665 mCpuHotPlugData->ArrayLength - 1, // ApicIdCount
666 mPluggedApicIds,
667 &PluggedCount,
668 mToUnplugApicIds,
669 mToUnplugSelectors,
670 &ToUnplugCount
671 );
672 if (EFI_ERROR (Status)) {
673 goto Fatal;
674 }
675
676 if (PluggedCount > 0) {
677 Status = ProcessHotAddedCpus (mPluggedApicIds, PluggedCount);
678 if (EFI_ERROR (Status)) {
679 goto Fatal;
680 }
681 }
682
683 if (ToUnplugCount > 0) {
684 Status = UnplugCpus (mToUnplugApicIds, mToUnplugSelectors, ToUnplugCount);
685 if (EFI_ERROR (Status)) {
686 goto Fatal;
687 }
688 }
689
690 //
691 // We've handled this MMI.
692 //
693 return EFI_SUCCESS;
694
695Fatal:
696 ASSERT (FALSE);
697 CpuDeadLoop ();
698 //
699 // We couldn't handle this MMI.
700 //
702}
703
704//
705// Entry point function of this driver.
706//
708EFIAPI
709CpuHotplugEntry (
710 IN EFI_HANDLE ImageHandle,
711 IN EFI_SYSTEM_TABLE *SystemTable
712 )
713{
714 EFI_STATUS Status;
715 UINTN Len;
716 UINTN Size;
717 UINTN SizeSel;
718
719 //
720 // This module should only be included when SMM support is required.
721 //
722 ASSERT (FeaturePcdGet (PcdSmmSmramRequire));
723 //
724 // This driver depends on the dynamically detected "SMRAM at default SMBASE"
725 // feature.
726 //
727 if (!PcdGetBool (PcdQ35SmramAtDefaultSmbase)) {
728 return EFI_UNSUPPORTED;
729 }
730
731 //
732 // Errors from here on are fatal; we cannot allow the boot to proceed if we
733 // can't set up this driver to handle CPU hotplug.
734 //
735 // First, collect the protocols needed later. All of these protocols are
736 // listed in our module DEPEX.
737 //
738 Status = gMmst->MmLocateProtocol (
739 &gEfiMmCpuIoProtocolGuid,
740 NULL /* Registration */,
741 (VOID **)&mMmCpuIo
742 );
743 if (EFI_ERROR (Status)) {
744 DEBUG ((DEBUG_ERROR, "%a: locate MmCpuIo: %r\n", __func__, Status));
745 goto Fatal;
746 }
747
748 Status = gMmst->MmLocateProtocol (
749 &gEfiSmmCpuServiceProtocolGuid,
750 NULL /* Registration */,
751 (VOID **)&mMmCpuService
752 );
753 if (EFI_ERROR (Status)) {
754 DEBUG ((
755 DEBUG_ERROR,
756 "%a: locate MmCpuService: %r\n",
757 __func__,
758 Status
759 ));
760 goto Fatal;
761 }
762
763 //
764 // Our DEPEX on EFI_SMM_CPU_SERVICE_PROTOCOL guarantees that PiSmmCpuDxeSmm
765 // has pointed:
766 // - PcdCpuHotPlugDataAddress to CPU_HOT_PLUG_DATA in SMRAM,
767 // - PcdCpuHotEjectDataAddress to CPU_HOT_EJECT_DATA in SMRAM, if the
768 // possible CPU count is greater than 1.
769 //
770 mCpuHotPlugData = (VOID *)(UINTN)PcdGet64 (PcdCpuHotPlugDataAddress);
771 mCpuHotEjectData = (VOID *)(UINTN)PcdGet64 (PcdCpuHotEjectDataAddress);
772
773 if (mCpuHotPlugData == NULL) {
774 Status = EFI_NOT_FOUND;
775 DEBUG ((DEBUG_ERROR, "%a: CPU_HOT_PLUG_DATA: %r\n", __func__, Status));
776 goto Fatal;
777 }
778
779 //
780 // If the possible CPU count is 1, there's nothing for this driver to do.
781 //
782 if (mCpuHotPlugData->ArrayLength == 1) {
783 return EFI_UNSUPPORTED;
784 }
785
786 if (mCpuHotEjectData == NULL) {
787 Status = EFI_NOT_FOUND;
788 } else if (mCpuHotPlugData->ArrayLength != mCpuHotEjectData->ArrayLength) {
789 Status = EFI_INVALID_PARAMETER;
790 } else {
791 Status = EFI_SUCCESS;
792 }
793
794 if (EFI_ERROR (Status)) {
795 DEBUG ((DEBUG_ERROR, "%a: CPU_HOT_EJECT_DATA: %r\n", __func__, Status));
796 goto Fatal;
797 }
798
799 //
800 // Allocate the data structures that depend on the possible CPU count.
801 //
802 if (RETURN_ERROR (SafeUintnSub (mCpuHotPlugData->ArrayLength, 1, &Len)) ||
803 RETURN_ERROR (SafeUintnMult (sizeof (APIC_ID), Len, &Size)) ||
804 RETURN_ERROR (SafeUintnMult (sizeof (UINT32), Len, &SizeSel)))
805 {
806 Status = EFI_ABORTED;
807 DEBUG ((DEBUG_ERROR, "%a: invalid CPU_HOT_PLUG_DATA\n", __func__));
808 goto Fatal;
809 }
810
811 Status = gMmst->MmAllocatePool (
813 Size,
814 (VOID **)&mPluggedApicIds
815 );
816 if (EFI_ERROR (Status)) {
817 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __func__, Status));
818 goto Fatal;
819 }
820
821 Status = gMmst->MmAllocatePool (
823 Size,
824 (VOID **)&mToUnplugApicIds
825 );
826 if (EFI_ERROR (Status)) {
827 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __func__, Status));
828 goto ReleasePluggedApicIds;
829 }
830
831 Status = gMmst->MmAllocatePool (
833 SizeSel,
834 (VOID **)&mToUnplugSelectors
835 );
836 if (EFI_ERROR (Status)) {
837 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __func__, Status));
838 goto ReleaseToUnplugApicIds;
839 }
840
841 //
842 // Allocate the Post-SMM Pen for hot-added CPUs.
843 //
844 Status = SmbaseAllocatePostSmmPen (
845 &mPostSmmPenAddress,
846 SystemTable->BootServices
847 );
848 if (EFI_ERROR (Status)) {
849 goto ReleaseToUnplugSelectors;
850 }
851
852 //
853 // Sanity-check the CPU hotplug interface.
854 //
855 // Both of the following features are part of QEMU 5.0, introduced primarily
856 // in commit range 3e08b2b9cb64..3a61c8db9d25:
857 //
858 // (a) the QEMU_CPUHP_CMD_GET_ARCH_ID command of the modern CPU hotplug
859 // interface,
860 //
861 // (b) the "SMRAM at default SMBASE" feature.
862 //
863 // From these, (b) is restricted to 5.0+ machine type versions, while (a)
864 // does not depend on machine type version. Because we ensured the stricter
865 // condition (b) through PcdQ35SmramAtDefaultSmbase above, the (a)
866 // QEMU_CPUHP_CMD_GET_ARCH_ID command must now be available too. While we
867 // can't verify the presence of precisely that command, we can still verify
868 // (sanity-check) that the modern interface is active, at least.
869 //
870 // Consult the "Typical usecases | Detecting and enabling modern CPU hotplug
871 // interface" section in QEMU's "docs/specs/acpi_cpu_hotplug.txt", on the
872 // following.
873 //
874 QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);
875 QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);
876 QemuCpuhpWriteCommand (mMmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);
877 if (QemuCpuhpReadCommandData2 (mMmCpuIo) != 0) {
878 Status = EFI_NOT_FOUND;
879 DEBUG ((
880 DEBUG_ERROR,
881 "%a: modern CPU hotplug interface: %r\n",
882 __func__,
883 Status
884 ));
885 goto ReleasePostSmmPen;
886 }
887
888 //
889 // Register the handler for the CPU Hotplug MMI.
890 //
891 Status = gMmst->MmiHandlerRegister (
893 NULL, // HandlerType: root MMI handler
894 &mDispatchHandle
895 );
896 if (EFI_ERROR (Status)) {
897 DEBUG ((
898 DEBUG_ERROR,
899 "%a: MmiHandlerRegister(): %r\n",
900 __func__,
901 Status
902 ));
903 goto ReleasePostSmmPen;
904 }
905
906 //
907 // Install the handler for the hot-added CPUs' first SMI.
908 //
910
911 return EFI_SUCCESS;
912
913ReleasePostSmmPen:
914 SmbaseReleasePostSmmPen (mPostSmmPenAddress, SystemTable->BootServices);
915 mPostSmmPenAddress = 0;
916
917ReleaseToUnplugSelectors:
918 gMmst->MmFreePool (mToUnplugSelectors);
919 mToUnplugSelectors = NULL;
920
921ReleaseToUnplugApicIds:
922 gMmst->MmFreePool (mToUnplugApicIds);
923 mToUnplugApicIds = NULL;
924
925ReleasePluggedApicIds:
926 gMmst->MmFreePool (mPluggedApicIds);
927 mPluggedApicIds = NULL;
928
929Fatal:
930 ASSERT (FALSE);
931 CpuDeadLoop ();
932 return Status;
933}
UINT64 UINTN
VOID EFIAPI CpuDeadLoop(VOID)
Definition: CpuDeadLoop.c:25
VOID EFIAPI MemoryFence(VOID)
Definition: CpuBreakpoint.c:42
VOID EFIAPI CpuPause(VOID)
VOID EFIAPI DisableInterrupts(VOID)
Definition: CpuBreakpoint.c:54
VOID EFIAPI EjectCpu(IN UINTN ProcessorNum)
Definition: CpuHotplug.c:269
STATIC EFI_STATUS EFIAPI CpuHotplugMmi(IN EFI_HANDLE DispatchHandle, IN CONST VOID *Context OPTIONAL, IN OUT VOID *CommBuffer OPTIONAL, IN OUT UINTN *CommBufferSize OPTIONAL)
Definition: CpuHotplug.c:605
STATIC EFI_STATUS ProcessHotAddedCpus(IN APIC_ID *PluggedApicIds, IN UINT32 PluggedCount)
Definition: CpuHotplug.c:95
STATIC EFI_STATUS UnplugCpus(IN APIC_ID *ToUnplugApicIds, IN UINT32 *ToUnplugSelectors, IN UINT32 ToUnplugCount)
Definition: CpuHotplug.c:413
STATIC BOOLEAN CheckIfBsp(VOID)
Definition: CpuHotplug.c:238
VOID EFIAPI CpuSleep(VOID)
UINT64 EFIAPI AsmReadMsr64(IN UINT32 Index)
Definition: GccInlinePriv.c:60
#define NULL
Definition: Base.h:319
#define CONST
Definition: Base.h:259
#define STATIC
Definition: Base.h:264
#define RETURN_ERROR(StatusCode)
Definition: Base.h:1061
#define FALSE
Definition: Base.h:307
#define IN
Definition: Base.h:279
#define OUT
Definition: Base.h:284
#define DEBUG(Expression)
Definition: DebugLib.h:434
#define MSR_IA32_APIC_BASE
#define PcdGet64(TokenName)
Definition: PcdLib.h:375
#define PcdGetBool(TokenName)
Definition: PcdLib.h:401
#define FeaturePcdGet(TokenName)
Definition: PcdLib.h:50
#define EFI_INTERRUPT_PENDING
Definition: PiMultiPhase.h:73
EFI_STATUS QemuCpuhpCollectApicIds(IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo, IN UINT32 PossibleCpuCount, IN UINT32 ApicIdCount, OUT APIC_ID *PluggedApicIds, OUT UINT32 *PluggedCount, OUT APIC_ID *ToUnplugApicIds, OUT UINT32 *ToUnplugSelectors, OUT UINT32 *ToUnplugCount)
Definition: QemuCpuhp.c:211
RETURN_STATUS EFIAPI SafeUintnMult(IN UINTN Multiplicand, IN UINTN Multiplier, OUT UINTN *Result)
Definition: SafeIntLib32.c:430
RETURN_STATUS EFIAPI SafeUintnSub(IN UINTN Minuend, IN UINTN Subtrahend, OUT UINTN *Result)
Definition: SafeIntLib32.c:384
EFI_STATUS SmbaseAllocatePostSmmPen(OUT UINT32 *PenAddress, IN CONST EFI_BOOT_SERVICES *BootServices)
Definition: Smbase.c:48
VOID SmbaseInstallFirstSmiHandler(VOID)
Definition: Smbase.c:141
VOID SmbaseReinstallPostSmmPen(IN UINT32 PenAddress)
Definition: Smbase.c:101
VOID SmbaseReleasePostSmmPen(IN UINT32 PenAddress, IN CONST EFI_BOOT_SERVICES *BootServices)
Definition: Smbase.c:121
EFI_STATUS SmbaseRelocate(IN APIC_ID ApicId, IN UINTN Smbase, IN UINT32 PenAddress)
Definition: Smbase.c:198
RETURN_STATUS EFI_STATUS
Definition: UefiBaseType.h:29
VOID * EFI_HANDLE
Definition: UefiBaseType.h:33
#define EFI_SUCCESS
Definition: UefiBaseType.h:112
@ EfiRuntimeServicesData
EFI_MM_IO_ACCESS Io
Definition: MmCpuIo.h:85
EFI_ALLOCATE_POOL MmAllocatePool
Definition: PiMmCis.h:274
EFI_MM_CPU_IO Read
Definition: MmCpuIo.h:67
struct MSR_IA32_APIC_BASE_REGISTER::@627 Bits