]> git.proxmox.com Git - mirror_edk2.git/blame - OvmfPkg/CpuHotplugSmm/CpuHotplug.c
OvmfPkg/CpuHotplugSmm: add Qemu Cpu Status helper
[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
LE
12#include <Library/BaseLib.h> // CpuDeadLoop()\r
13#include <Library/DebugLib.h> // ASSERT()\r
14#include <Library/MmServicesTableLib.h> // gMmst\r
15#include <Library/PcdLib.h> // PcdGetBool()\r
17cb8ddb 16#include <Library/SafeIntLib.h> // SafeUintnSub()\r
17efae27 17#include <Protocol/MmCpuIo.h> // EFI_MM_CPU_IO_PROTOCOL\r
17cb8ddb 18#include <Protocol/SmmCpuService.h> // EFI_SMM_CPU_SERVICE_PROTOCOL\r
17efae27
LE
19#include <Uefi/UefiBaseType.h> // EFI_STATUS\r
20\r
17cb8ddb 21#include "ApicId.h" // APIC_ID\r
f668e788 22#include "QemuCpuhp.h" // QemuCpuhpWriteCpuSelector()\r
bc498ac4 23#include "Smbase.h" // SmbaseAllocatePostSmmPen()\r
f668e788 24\r
17efae27
LE
25//\r
26// We use this protocol for accessing IO Ports.\r
27//\r
28STATIC EFI_MM_CPU_IO_PROTOCOL *mMmCpuIo;\r
29//\r
17cb8ddb
LE
30// The following protocol is used to report the addition or removal of a CPU to\r
31// the SMM CPU driver (PiSmmCpuDxeSmm).\r
32//\r
33STATIC EFI_SMM_CPU_SERVICE_PROTOCOL *mMmCpuService;\r
34//\r
35// This structure is a communication side-channel between the\r
36// EFI_SMM_CPU_SERVICE_PROTOCOL consumer (i.e., this driver) and provider\r
37// (i.e., PiSmmCpuDxeSmm).\r
38//\r
39STATIC CPU_HOT_PLUG_DATA *mCpuHotPlugData;\r
40//\r
41// SMRAM arrays for fetching the APIC IDs of processors with pending events (of\r
42// known event types), for the time of just one MMI.\r
43//\r
44// The lifetimes of these arrays match that of this driver only because we\r
45// don't want to allocate SMRAM at OS runtime, and potentially fail (or\r
46// fragment the SMRAM map).\r
47//\r
a752dd07
AA
48// The first array stores APIC IDs for hot-plug events, the second and the\r
49// third store APIC IDs and QEMU CPU Selectors (both indexed similarly) for\r
50// hot-unplug events. All of these provide room for "possible CPU count" minus\r
51// one elements as we don't expect every possible CPU to appear, or disappear,\r
52// in a single MMI. The numbers of used (populated) elements in the arrays are\r
17cb8ddb
LE
53// determined on every MMI separately.\r
54//\r
55STATIC APIC_ID *mPluggedApicIds;\r
56STATIC APIC_ID *mToUnplugApicIds;\r
a752dd07 57STATIC UINT32 *mToUnplugSelectors;\r
17cb8ddb 58//\r
bc498ac4
LE
59// Address of the non-SMRAM reserved memory page that contains the Post-SMM Pen\r
60// for hot-added CPUs.\r
61//\r
62STATIC UINT32 mPostSmmPenAddress;\r
63//\r
17efae27
LE
64// Represents the registration of the CPU Hotplug MMI handler.\r
65//\r
66STATIC EFI_HANDLE mDispatchHandle;\r
67\r
0cb242e3
AA
68/**\r
69 Process CPUs that have been hot-added, per QemuCpuhpCollectApicIds().\r
70\r
71 For each such CPU, relocate the SMBASE, and report the CPU to PiSmmCpuDxeSmm\r
72 via EFI_SMM_CPU_SERVICE_PROTOCOL. If the supposedly hot-added CPU is already\r
73 known, skip it silently.\r
74\r
75 @param[in] PluggedApicIds The APIC IDs of the CPUs that have been\r
76 hot-plugged.\r
77\r
78 @param[in] PluggedCount The number of filled-in APIC IDs in\r
79 PluggedApicIds.\r
80\r
81 @retval EFI_SUCCESS CPUs corresponding to all the APIC IDs are\r
82 populated.\r
83\r
84 @retval EFI_OUT_OF_RESOURCES Out of APIC ID space in "mCpuHotPlugData".\r
85\r
86 @return Error codes propagated from SmbaseRelocate()\r
87 and mMmCpuService->AddProcessor().\r
88**/\r
89STATIC\r
90EFI_STATUS\r
91ProcessHotAddedCpus (\r
92 IN APIC_ID *PluggedApicIds,\r
93 IN UINT32 PluggedCount\r
94 )\r
95{\r
96 EFI_STATUS Status;\r
97 UINT32 PluggedIdx;\r
98 UINT32 NewSlot;\r
99\r
100 //\r
101 // The Post-SMM Pen need not be reinstalled multiple times within a single\r
102 // root MMI handling. Even reinstalling once per root MMI is only prudence;\r
103 // in theory installing the pen in the driver's entry point function should\r
104 // suffice.\r
105 //\r
106 SmbaseReinstallPostSmmPen (mPostSmmPenAddress);\r
107\r
108 PluggedIdx = 0;\r
109 NewSlot = 0;\r
110 while (PluggedIdx < PluggedCount) {\r
111 APIC_ID NewApicId;\r
112 UINT32 CheckSlot;\r
113 UINTN NewProcessorNumberByProtocol;\r
114\r
115 NewApicId = PluggedApicIds[PluggedIdx];\r
116\r
117 //\r
118 // Check if the supposedly hot-added CPU is already known to us.\r
119 //\r
120 for (CheckSlot = 0;\r
121 CheckSlot < mCpuHotPlugData->ArrayLength;\r
122 CheckSlot++) {\r
123 if (mCpuHotPlugData->ApicId[CheckSlot] == NewApicId) {\r
124 break;\r
125 }\r
126 }\r
127 if (CheckSlot < mCpuHotPlugData->ArrayLength) {\r
128 DEBUG ((DEBUG_VERBOSE, "%a: APIC ID " FMT_APIC_ID " was hot-plugged "\r
129 "before; ignoring it\n", __FUNCTION__, NewApicId));\r
130 PluggedIdx++;\r
131 continue;\r
132 }\r
133\r
134 //\r
135 // Find the first empty slot in CPU_HOT_PLUG_DATA.\r
136 //\r
137 while (NewSlot < mCpuHotPlugData->ArrayLength &&\r
138 mCpuHotPlugData->ApicId[NewSlot] != MAX_UINT64) {\r
139 NewSlot++;\r
140 }\r
141 if (NewSlot == mCpuHotPlugData->ArrayLength) {\r
142 DEBUG ((DEBUG_ERROR, "%a: no room for APIC ID " FMT_APIC_ID "\n",\r
143 __FUNCTION__, NewApicId));\r
144 return EFI_OUT_OF_RESOURCES;\r
145 }\r
146\r
147 //\r
148 // Store the APIC ID of the new processor to the slot.\r
149 //\r
150 mCpuHotPlugData->ApicId[NewSlot] = NewApicId;\r
151\r
152 //\r
153 // Relocate the SMBASE of the new CPU.\r
154 //\r
155 Status = SmbaseRelocate (NewApicId, mCpuHotPlugData->SmBase[NewSlot],\r
156 mPostSmmPenAddress);\r
157 if (EFI_ERROR (Status)) {\r
158 goto RevokeNewSlot;\r
159 }\r
160\r
161 //\r
162 // Add the new CPU with EFI_SMM_CPU_SERVICE_PROTOCOL.\r
163 //\r
164 Status = mMmCpuService->AddProcessor (mMmCpuService, NewApicId,\r
165 &NewProcessorNumberByProtocol);\r
166 if (EFI_ERROR (Status)) {\r
167 DEBUG ((DEBUG_ERROR, "%a: AddProcessor(" FMT_APIC_ID "): %r\n",\r
168 __FUNCTION__, NewApicId, Status));\r
169 goto RevokeNewSlot;\r
170 }\r
171\r
172 DEBUG ((DEBUG_INFO, "%a: hot-added APIC ID " FMT_APIC_ID ", SMBASE 0x%Lx, "\r
173 "EFI_SMM_CPU_SERVICE_PROTOCOL assigned number %Lu\n", __FUNCTION__,\r
174 NewApicId, (UINT64)mCpuHotPlugData->SmBase[NewSlot],\r
175 (UINT64)NewProcessorNumberByProtocol));\r
176\r
177 NewSlot++;\r
178 PluggedIdx++;\r
179 }\r
180\r
181 //\r
182 // We've processed this batch of hot-added CPUs.\r
183 //\r
184 return EFI_SUCCESS;\r
185\r
186RevokeNewSlot:\r
187 mCpuHotPlugData->ApicId[NewSlot] = MAX_UINT64;\r
188\r
189 return Status;\r
190}\r
17efae27
LE
191\r
192/**\r
193 CPU Hotplug MMI handler function.\r
194\r
195 This is a root MMI handler.\r
196\r
197 @param[in] DispatchHandle The unique handle assigned to this handler by\r
198 EFI_MM_SYSTEM_TABLE.MmiHandlerRegister().\r
199\r
200 @param[in] Context Context passed in by\r
201 EFI_MM_SYSTEM_TABLE.MmiManage(). Due to\r
202 CpuHotplugMmi() being a root MMI handler,\r
203 Context is ASSERT()ed to be NULL.\r
204\r
205 @param[in,out] CommBuffer Ignored, due to CpuHotplugMmi() being a root\r
206 MMI handler.\r
207\r
208 @param[in,out] CommBufferSize Ignored, due to CpuHotplugMmi() being a root\r
209 MMI handler.\r
210\r
211 @retval EFI_SUCCESS The MMI was handled and the MMI\r
212 source was quiesced. When returned\r
213 by a non-root MMI handler,\r
214 EFI_SUCCESS terminates the\r
215 processing of MMI handlers in\r
216 EFI_MM_SYSTEM_TABLE.MmiManage().\r
217 For a root MMI handler (i.e., for\r
218 the present function too),\r
219 EFI_SUCCESS behaves identically to\r
220 EFI_WARN_INTERRUPT_SOURCE_QUIESCED,\r
221 as further root MMI handlers are\r
222 going to be called by\r
223 EFI_MM_SYSTEM_TABLE.MmiManage()\r
224 anyway.\r
225\r
226 @retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The MMI source has been quiesced,\r
227 but other handlers should still\r
228 be called.\r
229\r
230 @retval EFI_WARN_INTERRUPT_SOURCE_PENDING The MMI source is still pending,\r
231 and other handlers should still\r
232 be called.\r
233\r
234 @retval EFI_INTERRUPT_PENDING The MMI source could not be\r
235 quiesced.\r
236**/\r
237STATIC\r
238EFI_STATUS\r
239EFIAPI\r
240CpuHotplugMmi (\r
241 IN EFI_HANDLE DispatchHandle,\r
242 IN CONST VOID *Context OPTIONAL,\r
243 IN OUT VOID *CommBuffer OPTIONAL,\r
244 IN OUT UINTN *CommBufferSize OPTIONAL\r
245 )\r
246{\r
247 EFI_STATUS Status;\r
248 UINT8 ApmControl;\r
17cb8ddb
LE
249 UINT32 PluggedCount;\r
250 UINT32 ToUnplugCount;\r
17efae27
LE
251\r
252 //\r
253 // Assert that we are entering this function due to our root MMI handler\r
254 // registration.\r
255 //\r
256 ASSERT (DispatchHandle == mDispatchHandle);\r
257 //\r
258 // When MmiManage() is invoked to process root MMI handlers, the caller (the\r
259 // MM Core) is expected to pass in a NULL Context. MmiManage() then passes\r
260 // the same NULL Context to individual handlers.\r
261 //\r
262 ASSERT (Context == NULL);\r
263 //\r
264 // Read the MMI command value from the APM Control Port, to see if this is an\r
265 // MMI we should care about.\r
266 //\r
267 Status = mMmCpuIo->Io.Read (mMmCpuIo, MM_IO_UINT8, ICH9_APM_CNT, 1,\r
268 &ApmControl);\r
269 if (EFI_ERROR (Status)) {\r
270 DEBUG ((DEBUG_ERROR, "%a: failed to read ICH9_APM_CNT: %r\n", __FUNCTION__,\r
271 Status));\r
272 //\r
273 // We couldn't even determine if the MMI was for us or not.\r
274 //\r
275 goto Fatal;\r
276 }\r
277\r
278 if (ApmControl != ICH9_APM_CNT_CPU_HOTPLUG) {\r
279 //\r
280 // The MMI is not for us.\r
281 //\r
282 return EFI_WARN_INTERRUPT_SOURCE_QUIESCED;\r
283 }\r
284\r
17cb8ddb
LE
285 //\r
286 // Collect the CPUs with pending events.\r
287 //\r
288 Status = QemuCpuhpCollectApicIds (\r
289 mMmCpuIo,\r
290 mCpuHotPlugData->ArrayLength, // PossibleCpuCount\r
291 mCpuHotPlugData->ArrayLength - 1, // ApicIdCount\r
292 mPluggedApicIds,\r
293 &PluggedCount,\r
294 mToUnplugApicIds,\r
a752dd07 295 mToUnplugSelectors,\r
17cb8ddb
LE
296 &ToUnplugCount\r
297 );\r
298 if (EFI_ERROR (Status)) {\r
299 goto Fatal;\r
300 }\r
301 if (ToUnplugCount > 0) {\r
302 DEBUG ((DEBUG_ERROR, "%a: hot-unplug is not supported yet\n",\r
303 __FUNCTION__));\r
304 goto Fatal;\r
305 }\r
306\r
0cb242e3
AA
307 if (PluggedCount > 0) {\r
308 Status = ProcessHotAddedCpus (mPluggedApicIds, PluggedCount);\r
bc498ac4 309 if (EFI_ERROR (Status)) {\r
0cb242e3 310 goto Fatal;\r
bc498ac4 311 }\r
bc498ac4
LE
312 }\r
313\r
17efae27
LE
314 //\r
315 // We've handled this MMI.\r
316 //\r
317 return EFI_SUCCESS;\r
318\r
319Fatal:\r
320 ASSERT (FALSE);\r
321 CpuDeadLoop ();\r
322 //\r
323 // We couldn't handle this MMI.\r
324 //\r
325 return EFI_INTERRUPT_PENDING;\r
326}\r
327\r
328\r
329//\r
330// Entry point function of this driver.\r
331//\r
332EFI_STATUS\r
333EFIAPI\r
334CpuHotplugEntry (\r
335 IN EFI_HANDLE ImageHandle,\r
336 IN EFI_SYSTEM_TABLE *SystemTable\r
337 )\r
338{\r
339 EFI_STATUS Status;\r
a752dd07 340 UINTN Len;\r
17cb8ddb 341 UINTN Size;\r
a752dd07 342 UINTN SizeSel;\r
17efae27
LE
343\r
344 //\r
345 // This module should only be included when SMM support is required.\r
346 //\r
347 ASSERT (FeaturePcdGet (PcdSmmSmramRequire));\r
348 //\r
349 // This driver depends on the dynamically detected "SMRAM at default SMBASE"\r
350 // feature.\r
351 //\r
352 if (!PcdGetBool (PcdQ35SmramAtDefaultSmbase)) {\r
353 return EFI_UNSUPPORTED;\r
354 }\r
355\r
356 //\r
357 // Errors from here on are fatal; we cannot allow the boot to proceed if we\r
358 // can't set up this driver to handle CPU hotplug.\r
359 //\r
360 // First, collect the protocols needed later. All of these protocols are\r
361 // listed in our module DEPEX.\r
362 //\r
363 Status = gMmst->MmLocateProtocol (&gEfiMmCpuIoProtocolGuid,\r
364 NULL /* Registration */, (VOID **)&mMmCpuIo);\r
365 if (EFI_ERROR (Status)) {\r
366 DEBUG ((DEBUG_ERROR, "%a: locate MmCpuIo: %r\n", __FUNCTION__, Status));\r
367 goto Fatal;\r
368 }\r
17cb8ddb
LE
369 Status = gMmst->MmLocateProtocol (&gEfiSmmCpuServiceProtocolGuid,\r
370 NULL /* Registration */, (VOID **)&mMmCpuService);\r
371 if (EFI_ERROR (Status)) {\r
372 DEBUG ((DEBUG_ERROR, "%a: locate MmCpuService: %r\n", __FUNCTION__,\r
373 Status));\r
374 goto Fatal;\r
375 }\r
376\r
377 //\r
378 // Our DEPEX on EFI_SMM_CPU_SERVICE_PROTOCOL guarantees that PiSmmCpuDxeSmm\r
379 // has pointed PcdCpuHotPlugDataAddress to CPU_HOT_PLUG_DATA in SMRAM.\r
380 //\r
381 mCpuHotPlugData = (VOID *)(UINTN)PcdGet64 (PcdCpuHotPlugDataAddress);\r
382 if (mCpuHotPlugData == NULL) {\r
383 Status = EFI_NOT_FOUND;\r
384 DEBUG ((DEBUG_ERROR, "%a: CPU_HOT_PLUG_DATA: %r\n", __FUNCTION__, Status));\r
385 goto Fatal;\r
386 }\r
387 //\r
388 // If the possible CPU count is 1, there's nothing for this driver to do.\r
389 //\r
390 if (mCpuHotPlugData->ArrayLength == 1) {\r
391 return EFI_UNSUPPORTED;\r
392 }\r
393 //\r
394 // Allocate the data structures that depend on the possible CPU count.\r
395 //\r
a752dd07
AA
396 if (RETURN_ERROR (SafeUintnSub (mCpuHotPlugData->ArrayLength, 1, &Len)) ||\r
397 RETURN_ERROR (SafeUintnMult (sizeof (APIC_ID), Len, &Size)) ||\r
398 RETURN_ERROR (SafeUintnMult (sizeof (UINT32), Len, &SizeSel))) {\r
17cb8ddb
LE
399 Status = EFI_ABORTED;\r
400 DEBUG ((DEBUG_ERROR, "%a: invalid CPU_HOT_PLUG_DATA\n", __FUNCTION__));\r
401 goto Fatal;\r
402 }\r
403 Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, Size,\r
404 (VOID **)&mPluggedApicIds);\r
405 if (EFI_ERROR (Status)) {\r
406 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));\r
407 goto Fatal;\r
408 }\r
409 Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, Size,\r
410 (VOID **)&mToUnplugApicIds);\r
411 if (EFI_ERROR (Status)) {\r
412 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));\r
413 goto ReleasePluggedApicIds;\r
414 }\r
a752dd07
AA
415 Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, SizeSel,\r
416 (VOID **)&mToUnplugSelectors);\r
417 if (EFI_ERROR (Status)) {\r
418 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));\r
419 goto ReleaseToUnplugApicIds;\r
420 }\r
17efae27 421\r
bc498ac4
LE
422 //\r
423 // Allocate the Post-SMM Pen for hot-added CPUs.\r
424 //\r
425 Status = SmbaseAllocatePostSmmPen (&mPostSmmPenAddress,\r
426 SystemTable->BootServices);\r
427 if (EFI_ERROR (Status)) {\r
a752dd07 428 goto ReleaseToUnplugSelectors;\r
bc498ac4
LE
429 }\r
430\r
f668e788
LE
431 //\r
432 // Sanity-check the CPU hotplug interface.\r
433 //\r
434 // Both of the following features are part of QEMU 5.0, introduced primarily\r
435 // in commit range 3e08b2b9cb64..3a61c8db9d25:\r
436 //\r
437 // (a) the QEMU_CPUHP_CMD_GET_ARCH_ID command of the modern CPU hotplug\r
438 // interface,\r
439 //\r
440 // (b) the "SMRAM at default SMBASE" feature.\r
441 //\r
442 // From these, (b) is restricted to 5.0+ machine type versions, while (a)\r
443 // does not depend on machine type version. Because we ensured the stricter\r
444 // condition (b) through PcdQ35SmramAtDefaultSmbase above, the (a)\r
445 // QEMU_CPUHP_CMD_GET_ARCH_ID command must now be available too. While we\r
446 // can't verify the presence of precisely that command, we can still verify\r
447 // (sanity-check) that the modern interface is active, at least.\r
448 //\r
449 // Consult the "Typical usecases | Detecting and enabling modern CPU hotplug\r
450 // interface" section in QEMU's "docs/specs/acpi_cpu_hotplug.txt", on the\r
451 // following.\r
452 //\r
453 QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);\r
454 QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);\r
455 QemuCpuhpWriteCommand (mMmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);\r
456 if (QemuCpuhpReadCommandData2 (mMmCpuIo) != 0) {\r
457 Status = EFI_NOT_FOUND;\r
458 DEBUG ((DEBUG_ERROR, "%a: modern CPU hotplug interface: %r\n",\r
459 __FUNCTION__, Status));\r
bc498ac4 460 goto ReleasePostSmmPen;\r
f668e788
LE
461 }\r
462\r
17efae27
LE
463 //\r
464 // Register the handler for the CPU Hotplug MMI.\r
465 //\r
466 Status = gMmst->MmiHandlerRegister (\r
467 CpuHotplugMmi,\r
468 NULL, // HandlerType: root MMI handler\r
469 &mDispatchHandle\r
470 );\r
471 if (EFI_ERROR (Status)) {\r
472 DEBUG ((DEBUG_ERROR, "%a: MmiHandlerRegister(): %r\n", __FUNCTION__,\r
473 Status));\r
bc498ac4 474 goto ReleasePostSmmPen;\r
17efae27
LE
475 }\r
476\r
bc498ac4
LE
477 //\r
478 // Install the handler for the hot-added CPUs' first SMI.\r
479 //\r
480 SmbaseInstallFirstSmiHandler ();\r
481\r
17efae27
LE
482 return EFI_SUCCESS;\r
483\r
bc498ac4
LE
484ReleasePostSmmPen:\r
485 SmbaseReleasePostSmmPen (mPostSmmPenAddress, SystemTable->BootServices);\r
486 mPostSmmPenAddress = 0;\r
487\r
a752dd07
AA
488ReleaseToUnplugSelectors:\r
489 gMmst->MmFreePool (mToUnplugSelectors);\r
490 mToUnplugSelectors = NULL;\r
491\r
17cb8ddb
LE
492ReleaseToUnplugApicIds:\r
493 gMmst->MmFreePool (mToUnplugApicIds);\r
494 mToUnplugApicIds = NULL;\r
495\r
496ReleasePluggedApicIds:\r
497 gMmst->MmFreePool (mPluggedApicIds);\r
498 mPluggedApicIds = NULL;\r
499\r
17efae27
LE
500Fatal:\r
501 ASSERT (FALSE);\r
502 CpuDeadLoop ();\r
503 return Status;\r
504}\r