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