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