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