VirtualBox

source: vbox/trunk/src/VBox/Devices/EFI/FirmwareNew/OvmfPkg/CpuHotplugSmm/CpuHotplug.c@ 109019

Last change on this file since 109019 was 108794, checked in by vboxsync, 4 weeks ago

Devices/EFI/FirmwareNew: Merge edk2-stable202502 from the vendor branch and make it build for the important platforms, bugref:4643

  • Property svn:eol-style set to native
File size: 28.0 KB
Line 
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//
31STATIC 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//
36STATIC 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//
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
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**/
93STATIC
94EFI_STATUS
95ProcessHotAddedCpus (
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
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**/
236STATIC
237BOOLEAN
238CheckIfBsp (
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**/
267VOID
268EFIAPI
269EjectCpu (
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 ( ; ;) {
374 DisableInterrupts ();
375 CpuSleep ();
376 }
377}
378
379/**
380 Process to be hot-unplugged CPUs, per QemuCpuhpCollectApicIds().
381
382 For each such CPU, report the CPU to PiSmmCpuDxeSmm via
383 EFI_SMM_CPU_SERVICE_PROTOCOL and stash the QEMU Cpu Selectors for later
384 ejection. If the to be hot-unplugged CPU is unknown, skip it silently.
385
386 Additonally, if we do stash any Cpu Selectors, also install a CPU eject
387 handler which would handle the ejection.
388
389 @param[in] ToUnplugApicIds The APIC IDs of the CPUs that are about to be
390 hot-unplugged.
391
392 @param[in] ToUnplugSelectors The QEMU Selectors of the CPUs that are about to
393 be hot-unplugged.
394
395 @param[in] ToUnplugCount The number of filled-in APIC IDs in
396 ToUnplugApicIds.
397
398 @retval EFI_ALREADY_STARTED For the ProcessorNum that
399 EFI_SMM_CPU_SERVICE_PROTOCOL had assigned to
400 one of the APIC IDs in ToUnplugApicIds,
401 mCpuHotEjectData->QemuSelectorMap already has
402 the QemuSelector value stashed. (This should
403 never happen.)
404
405 @retval EFI_SUCCESS Known APIC IDs have been removed from SMM data
406 structures.
407
408 @return Error codes propagated from
409 mMmCpuService->RemoveProcessor().
410**/
411STATIC
412EFI_STATUS
413UnplugCpus (
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
557/**
558 CPU Hotplug MMI handler function.
559
560 This is a root MMI handler.
561
562 @param[in] DispatchHandle The unique handle assigned to this handler by
563 EFI_MM_SYSTEM_TABLE.MmiHandlerRegister().
564
565 @param[in] Context Context passed in by
566 EFI_MM_SYSTEM_TABLE.MmiManage(). Due to
567 CpuHotplugMmi() being a root MMI handler,
568 Context is ASSERT()ed to be NULL.
569
570 @param[in,out] CommBuffer Ignored, due to CpuHotplugMmi() being a root
571 MMI handler.
572
573 @param[in,out] CommBufferSize Ignored, due to CpuHotplugMmi() being a root
574 MMI handler.
575
576 @retval EFI_SUCCESS The MMI was handled and the MMI
577 source was quiesced. When returned
578 by a non-root MMI handler,
579 EFI_SUCCESS terminates the
580 processing of MMI handlers in
581 EFI_MM_SYSTEM_TABLE.MmiManage().
582 For a root MMI handler (i.e., for
583 the present function too),
584 EFI_SUCCESS behaves identically to
585 EFI_WARN_INTERRUPT_SOURCE_QUIESCED,
586 as further root MMI handlers are
587 going to be called by
588 EFI_MM_SYSTEM_TABLE.MmiManage()
589 anyway.
590
591 @retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The MMI source has been quiesced,
592 but other handlers should still
593 be called.
594
595 @retval EFI_WARN_INTERRUPT_SOURCE_PENDING The MMI source is still pending,
596 and other handlers should still
597 be called.
598
599 @retval EFI_INTERRUPT_PENDING The MMI source could not be
600 quiesced.
601**/
602STATIC
603EFI_STATUS
604EFIAPI
605CpuHotplugMmi (
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 //
701 return EFI_INTERRUPT_PENDING;
702}
703
704//
705// Entry point function of this driver.
706//
707EFI_STATUS
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 (
812 EfiRuntimeServicesData,
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 (
822 EfiRuntimeServicesData,
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 (
832 EfiRuntimeServicesData,
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 (
892 CpuHotplugMmi,
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 //
909 SmbaseInstallFirstSmiHandler ();
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}
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette