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