]> git.proxmox.com Git - mirror_edk2.git/blame - OvmfPkg/CpuHotplugSmm/CpuHotplug.c
OvmfPkg/CpuHotplugSmm: introduce First SMI Handler for hot-added CPUs
[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
LE
22#include "QemuCpuhp.h" // QemuCpuhpWriteCpuSelector()\r
23\r
17efae27
LE
24//\r
25// We use this protocol for accessing IO Ports.\r
26//\r
27STATIC EFI_MM_CPU_IO_PROTOCOL *mMmCpuIo;\r
28//\r
17cb8ddb
LE
29// The following protocol is used to report the addition or removal of a CPU to\r
30// the SMM CPU driver (PiSmmCpuDxeSmm).\r
31//\r
32STATIC EFI_SMM_CPU_SERVICE_PROTOCOL *mMmCpuService;\r
33//\r
34// This structure is a communication side-channel between the\r
35// EFI_SMM_CPU_SERVICE_PROTOCOL consumer (i.e., this driver) and provider\r
36// (i.e., PiSmmCpuDxeSmm).\r
37//\r
38STATIC CPU_HOT_PLUG_DATA *mCpuHotPlugData;\r
39//\r
40// SMRAM arrays for fetching the APIC IDs of processors with pending events (of\r
41// known event types), for the time of just one MMI.\r
42//\r
43// The lifetimes of these arrays match that of this driver only because we\r
44// don't want to allocate SMRAM at OS runtime, and potentially fail (or\r
45// fragment the SMRAM map).\r
46//\r
47// These arrays provide room for ("possible CPU count" minus one) APIC IDs\r
48// each, as we don't expect every possible CPU to appear, or disappear, in a\r
49// single MMI. The numbers of used (populated) elements in the arrays are\r
50// determined on every MMI separately.\r
51//\r
52STATIC APIC_ID *mPluggedApicIds;\r
53STATIC APIC_ID *mToUnplugApicIds;\r
54//\r
17efae27
LE
55// Represents the registration of the CPU Hotplug MMI handler.\r
56//\r
57STATIC EFI_HANDLE mDispatchHandle;\r
58\r
59\r
60/**\r
61 CPU Hotplug MMI handler function.\r
62\r
63 This is a root MMI handler.\r
64\r
65 @param[in] DispatchHandle The unique handle assigned to this handler by\r
66 EFI_MM_SYSTEM_TABLE.MmiHandlerRegister().\r
67\r
68 @param[in] Context Context passed in by\r
69 EFI_MM_SYSTEM_TABLE.MmiManage(). Due to\r
70 CpuHotplugMmi() being a root MMI handler,\r
71 Context is ASSERT()ed to be NULL.\r
72\r
73 @param[in,out] CommBuffer Ignored, due to CpuHotplugMmi() being a root\r
74 MMI handler.\r
75\r
76 @param[in,out] CommBufferSize Ignored, due to CpuHotplugMmi() being a root\r
77 MMI handler.\r
78\r
79 @retval EFI_SUCCESS The MMI was handled and the MMI\r
80 source was quiesced. When returned\r
81 by a non-root MMI handler,\r
82 EFI_SUCCESS terminates the\r
83 processing of MMI handlers in\r
84 EFI_MM_SYSTEM_TABLE.MmiManage().\r
85 For a root MMI handler (i.e., for\r
86 the present function too),\r
87 EFI_SUCCESS behaves identically to\r
88 EFI_WARN_INTERRUPT_SOURCE_QUIESCED,\r
89 as further root MMI handlers are\r
90 going to be called by\r
91 EFI_MM_SYSTEM_TABLE.MmiManage()\r
92 anyway.\r
93\r
94 @retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The MMI source has been quiesced,\r
95 but other handlers should still\r
96 be called.\r
97\r
98 @retval EFI_WARN_INTERRUPT_SOURCE_PENDING The MMI source is still pending,\r
99 and other handlers should still\r
100 be called.\r
101\r
102 @retval EFI_INTERRUPT_PENDING The MMI source could not be\r
103 quiesced.\r
104**/\r
105STATIC\r
106EFI_STATUS\r
107EFIAPI\r
108CpuHotplugMmi (\r
109 IN EFI_HANDLE DispatchHandle,\r
110 IN CONST VOID *Context OPTIONAL,\r
111 IN OUT VOID *CommBuffer OPTIONAL,\r
112 IN OUT UINTN *CommBufferSize OPTIONAL\r
113 )\r
114{\r
115 EFI_STATUS Status;\r
116 UINT8 ApmControl;\r
17cb8ddb
LE
117 UINT32 PluggedCount;\r
118 UINT32 ToUnplugCount;\r
17efae27
LE
119\r
120 //\r
121 // Assert that we are entering this function due to our root MMI handler\r
122 // registration.\r
123 //\r
124 ASSERT (DispatchHandle == mDispatchHandle);\r
125 //\r
126 // When MmiManage() is invoked to process root MMI handlers, the caller (the\r
127 // MM Core) is expected to pass in a NULL Context. MmiManage() then passes\r
128 // the same NULL Context to individual handlers.\r
129 //\r
130 ASSERT (Context == NULL);\r
131 //\r
132 // Read the MMI command value from the APM Control Port, to see if this is an\r
133 // MMI we should care about.\r
134 //\r
135 Status = mMmCpuIo->Io.Read (mMmCpuIo, MM_IO_UINT8, ICH9_APM_CNT, 1,\r
136 &ApmControl);\r
137 if (EFI_ERROR (Status)) {\r
138 DEBUG ((DEBUG_ERROR, "%a: failed to read ICH9_APM_CNT: %r\n", __FUNCTION__,\r
139 Status));\r
140 //\r
141 // We couldn't even determine if the MMI was for us or not.\r
142 //\r
143 goto Fatal;\r
144 }\r
145\r
146 if (ApmControl != ICH9_APM_CNT_CPU_HOTPLUG) {\r
147 //\r
148 // The MMI is not for us.\r
149 //\r
150 return EFI_WARN_INTERRUPT_SOURCE_QUIESCED;\r
151 }\r
152\r
17cb8ddb
LE
153 //\r
154 // Collect the CPUs with pending events.\r
155 //\r
156 Status = QemuCpuhpCollectApicIds (\r
157 mMmCpuIo,\r
158 mCpuHotPlugData->ArrayLength, // PossibleCpuCount\r
159 mCpuHotPlugData->ArrayLength - 1, // ApicIdCount\r
160 mPluggedApicIds,\r
161 &PluggedCount,\r
162 mToUnplugApicIds,\r
163 &ToUnplugCount\r
164 );\r
165 if (EFI_ERROR (Status)) {\r
166 goto Fatal;\r
167 }\r
168 if (ToUnplugCount > 0) {\r
169 DEBUG ((DEBUG_ERROR, "%a: hot-unplug is not supported yet\n",\r
170 __FUNCTION__));\r
171 goto Fatal;\r
172 }\r
173\r
17efae27
LE
174 //\r
175 // We've handled this MMI.\r
176 //\r
177 return EFI_SUCCESS;\r
178\r
179Fatal:\r
180 ASSERT (FALSE);\r
181 CpuDeadLoop ();\r
182 //\r
183 // We couldn't handle this MMI.\r
184 //\r
185 return EFI_INTERRUPT_PENDING;\r
186}\r
187\r
188\r
189//\r
190// Entry point function of this driver.\r
191//\r
192EFI_STATUS\r
193EFIAPI\r
194CpuHotplugEntry (\r
195 IN EFI_HANDLE ImageHandle,\r
196 IN EFI_SYSTEM_TABLE *SystemTable\r
197 )\r
198{\r
199 EFI_STATUS Status;\r
17cb8ddb 200 UINTN Size;\r
17efae27
LE
201\r
202 //\r
203 // This module should only be included when SMM support is required.\r
204 //\r
205 ASSERT (FeaturePcdGet (PcdSmmSmramRequire));\r
206 //\r
207 // This driver depends on the dynamically detected "SMRAM at default SMBASE"\r
208 // feature.\r
209 //\r
210 if (!PcdGetBool (PcdQ35SmramAtDefaultSmbase)) {\r
211 return EFI_UNSUPPORTED;\r
212 }\r
213\r
214 //\r
215 // Errors from here on are fatal; we cannot allow the boot to proceed if we\r
216 // can't set up this driver to handle CPU hotplug.\r
217 //\r
218 // First, collect the protocols needed later. All of these protocols are\r
219 // listed in our module DEPEX.\r
220 //\r
221 Status = gMmst->MmLocateProtocol (&gEfiMmCpuIoProtocolGuid,\r
222 NULL /* Registration */, (VOID **)&mMmCpuIo);\r
223 if (EFI_ERROR (Status)) {\r
224 DEBUG ((DEBUG_ERROR, "%a: locate MmCpuIo: %r\n", __FUNCTION__, Status));\r
225 goto Fatal;\r
226 }\r
17cb8ddb
LE
227 Status = gMmst->MmLocateProtocol (&gEfiSmmCpuServiceProtocolGuid,\r
228 NULL /* Registration */, (VOID **)&mMmCpuService);\r
229 if (EFI_ERROR (Status)) {\r
230 DEBUG ((DEBUG_ERROR, "%a: locate MmCpuService: %r\n", __FUNCTION__,\r
231 Status));\r
232 goto Fatal;\r
233 }\r
234\r
235 //\r
236 // Our DEPEX on EFI_SMM_CPU_SERVICE_PROTOCOL guarantees that PiSmmCpuDxeSmm\r
237 // has pointed PcdCpuHotPlugDataAddress to CPU_HOT_PLUG_DATA in SMRAM.\r
238 //\r
239 mCpuHotPlugData = (VOID *)(UINTN)PcdGet64 (PcdCpuHotPlugDataAddress);\r
240 if (mCpuHotPlugData == NULL) {\r
241 Status = EFI_NOT_FOUND;\r
242 DEBUG ((DEBUG_ERROR, "%a: CPU_HOT_PLUG_DATA: %r\n", __FUNCTION__, Status));\r
243 goto Fatal;\r
244 }\r
245 //\r
246 // If the possible CPU count is 1, there's nothing for this driver to do.\r
247 //\r
248 if (mCpuHotPlugData->ArrayLength == 1) {\r
249 return EFI_UNSUPPORTED;\r
250 }\r
251 //\r
252 // Allocate the data structures that depend on the possible CPU count.\r
253 //\r
254 if (RETURN_ERROR (SafeUintnSub (mCpuHotPlugData->ArrayLength, 1, &Size)) ||\r
255 RETURN_ERROR (SafeUintnMult (sizeof (APIC_ID), Size, &Size))) {\r
256 Status = EFI_ABORTED;\r
257 DEBUG ((DEBUG_ERROR, "%a: invalid CPU_HOT_PLUG_DATA\n", __FUNCTION__));\r
258 goto Fatal;\r
259 }\r
260 Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, Size,\r
261 (VOID **)&mPluggedApicIds);\r
262 if (EFI_ERROR (Status)) {\r
263 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));\r
264 goto Fatal;\r
265 }\r
266 Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, Size,\r
267 (VOID **)&mToUnplugApicIds);\r
268 if (EFI_ERROR (Status)) {\r
269 DEBUG ((DEBUG_ERROR, "%a: MmAllocatePool(): %r\n", __FUNCTION__, Status));\r
270 goto ReleasePluggedApicIds;\r
271 }\r
17efae27 272\r
f668e788
LE
273 //\r
274 // Sanity-check the CPU hotplug interface.\r
275 //\r
276 // Both of the following features are part of QEMU 5.0, introduced primarily\r
277 // in commit range 3e08b2b9cb64..3a61c8db9d25:\r
278 //\r
279 // (a) the QEMU_CPUHP_CMD_GET_ARCH_ID command of the modern CPU hotplug\r
280 // interface,\r
281 //\r
282 // (b) the "SMRAM at default SMBASE" feature.\r
283 //\r
284 // From these, (b) is restricted to 5.0+ machine type versions, while (a)\r
285 // does not depend on machine type version. Because we ensured the stricter\r
286 // condition (b) through PcdQ35SmramAtDefaultSmbase above, the (a)\r
287 // QEMU_CPUHP_CMD_GET_ARCH_ID command must now be available too. While we\r
288 // can't verify the presence of precisely that command, we can still verify\r
289 // (sanity-check) that the modern interface is active, at least.\r
290 //\r
291 // Consult the "Typical usecases | Detecting and enabling modern CPU hotplug\r
292 // interface" section in QEMU's "docs/specs/acpi_cpu_hotplug.txt", on the\r
293 // following.\r
294 //\r
295 QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);\r
296 QemuCpuhpWriteCpuSelector (mMmCpuIo, 0);\r
297 QemuCpuhpWriteCommand (mMmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);\r
298 if (QemuCpuhpReadCommandData2 (mMmCpuIo) != 0) {\r
299 Status = EFI_NOT_FOUND;\r
300 DEBUG ((DEBUG_ERROR, "%a: modern CPU hotplug interface: %r\n",\r
301 __FUNCTION__, Status));\r
17cb8ddb 302 goto ReleaseToUnplugApicIds;\r
f668e788
LE
303 }\r
304\r
17efae27
LE
305 //\r
306 // Register the handler for the CPU Hotplug MMI.\r
307 //\r
308 Status = gMmst->MmiHandlerRegister (\r
309 CpuHotplugMmi,\r
310 NULL, // HandlerType: root MMI handler\r
311 &mDispatchHandle\r
312 );\r
313 if (EFI_ERROR (Status)) {\r
314 DEBUG ((DEBUG_ERROR, "%a: MmiHandlerRegister(): %r\n", __FUNCTION__,\r
315 Status));\r
17cb8ddb 316 goto ReleaseToUnplugApicIds;\r
17efae27
LE
317 }\r
318\r
319 return EFI_SUCCESS;\r
320\r
17cb8ddb
LE
321ReleaseToUnplugApicIds:\r
322 gMmst->MmFreePool (mToUnplugApicIds);\r
323 mToUnplugApicIds = NULL;\r
324\r
325ReleasePluggedApicIds:\r
326 gMmst->MmFreePool (mPluggedApicIds);\r
327 mPluggedApicIds = NULL;\r
328\r
17efae27
LE
329Fatal:\r
330 ASSERT (FALSE);\r
331 CpuDeadLoop ();\r
332 return Status;\r
333}\r