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