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