]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/CpuHotplugSmm/QemuCpuhp.c
UefiCpuPkg: Move AsmRelocateApLoopStart from Mpfuncs.nasm to AmdSev.nasm
[mirror_edk2.git] / OvmfPkg / CpuHotplugSmm / QemuCpuhp.c
1 /** @file
2 Simple wrapper functions and utility functions that access QEMU's modern CPU
3 hotplug register block.
4
5 These functions manipulate some of the registers described in
6 "docs/specs/acpi_cpu_hotplug.txt" in the QEMU source. IO Ports are accessed
7 via EFI_MM_CPU_IO_PROTOCOL. If a protocol call fails, these functions don't
8 return.
9
10 Copyright (c) 2020, Red Hat, Inc.
11
12 SPDX-License-Identifier: BSD-2-Clause-Patent
13 **/
14
15 #include <IndustryStandard/Q35MchIch9.h> // ICH9_CPU_HOTPLUG_BASE
16 #include <IndustryStandard/QemuCpuHotplug.h> // QEMU_CPUHP_R_CMD_DATA2
17 #include <Library/BaseLib.h> // CpuDeadLoop()
18 #include <Library/DebugLib.h> // DEBUG()
19
20 #include "QemuCpuhp.h"
21
22 UINT32
23 QemuCpuhpReadCommandData2 (
24 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
25 )
26 {
27 UINT32 CommandData2;
28 EFI_STATUS Status;
29
30 CommandData2 = 0;
31 Status = MmCpuIo->Io.Read (
32 MmCpuIo,
33 MM_IO_UINT32,
34 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CMD_DATA2,
35 1,
36 &CommandData2
37 );
38 if (EFI_ERROR (Status)) {
39 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
40 ASSERT (FALSE);
41 CpuDeadLoop ();
42 }
43
44 return CommandData2;
45 }
46
47 UINT8
48 QemuCpuhpReadCpuStatus (
49 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
50 )
51 {
52 UINT8 CpuStatus;
53 EFI_STATUS Status;
54
55 CpuStatus = 0;
56 Status = MmCpuIo->Io.Read (
57 MmCpuIo,
58 MM_IO_UINT8,
59 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CPU_STAT,
60 1,
61 &CpuStatus
62 );
63 if (EFI_ERROR (Status)) {
64 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
65 ASSERT (FALSE);
66 CpuDeadLoop ();
67 }
68
69 return CpuStatus;
70 }
71
72 UINT32
73 QemuCpuhpReadCommandData (
74 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
75 )
76 {
77 UINT32 CommandData;
78 EFI_STATUS Status;
79
80 CommandData = 0;
81 Status = MmCpuIo->Io.Read (
82 MmCpuIo,
83 MM_IO_UINT32,
84 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_RW_CMD_DATA,
85 1,
86 &CommandData
87 );
88 if (EFI_ERROR (Status)) {
89 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
90 ASSERT (FALSE);
91 CpuDeadLoop ();
92 }
93
94 return CommandData;
95 }
96
97 VOID
98 QemuCpuhpWriteCpuSelector (
99 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
100 IN UINT32 Selector
101 )
102 {
103 EFI_STATUS Status;
104
105 Status = MmCpuIo->Io.Write (
106 MmCpuIo,
107 MM_IO_UINT32,
108 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL,
109 1,
110 &Selector
111 );
112 if (EFI_ERROR (Status)) {
113 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
114 ASSERT (FALSE);
115 CpuDeadLoop ();
116 }
117 }
118
119 VOID
120 QemuCpuhpWriteCpuStatus (
121 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
122 IN UINT8 CpuStatus
123 )
124 {
125 EFI_STATUS Status;
126
127 Status = MmCpuIo->Io.Write (
128 MmCpuIo,
129 MM_IO_UINT8,
130 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CPU_STAT,
131 1,
132 &CpuStatus
133 );
134 if (EFI_ERROR (Status)) {
135 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
136 ASSERT (FALSE);
137 CpuDeadLoop ();
138 }
139 }
140
141 VOID
142 QemuCpuhpWriteCommand (
143 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
144 IN UINT8 Command
145 )
146 {
147 EFI_STATUS Status;
148
149 Status = MmCpuIo->Io.Write (
150 MmCpuIo,
151 MM_IO_UINT8,
152 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CMD,
153 1,
154 &Command
155 );
156 if (EFI_ERROR (Status)) {
157 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
158 ASSERT (FALSE);
159 CpuDeadLoop ();
160 }
161 }
162
163 /**
164 Collect the APIC IDs of
165 - the CPUs that have been hot-plugged,
166 - the CPUs that are about to be hot-unplugged.
167
168 This function only scans for events -- it does not modify them -- in the
169 hotplug registers.
170
171 On error, the contents of the output parameters are undefined.
172
173 @param[in] MmCpuIo The EFI_MM_CPU_IO_PROTOCOL instance for
174 accessing IO Ports.
175
176 @param[in] PossibleCpuCount The number of possible CPUs in the system. Must
177 be positive.
178
179 @param[in] ApicIdCount The number of elements each one of the
180 PluggedApicIds and ToUnplugApicIds arrays can
181 accommodate. Must be positive.
182
183 @param[out] PluggedApicIds The APIC IDs of the CPUs that have been
184 hot-plugged.
185
186 @param[out] PluggedCount The number of filled-in APIC IDs in
187 PluggedApicIds.
188
189 @param[out] ToUnplugApicIds The APIC IDs of the CPUs that are about to be
190 hot-unplugged.
191
192 @param[out] ToUnplugSelectors The QEMU Selectors of the CPUs that are about
193 to be hot-unplugged.
194
195 @param[out] ToUnplugCount The number of filled-in APIC IDs in
196 ToUnplugApicIds.
197
198 @retval EFI_INVALID_PARAMETER PossibleCpuCount is zero, or ApicIdCount is
199 zero.
200
201 @retval EFI_PROTOCOL_ERROR Invalid bitmap detected in the
202 QEMU_CPUHP_R_CPU_STAT register.
203
204 @retval EFI_BUFFER_TOO_SMALL There was an attempt to place more than
205 ApicIdCount APIC IDs into one of the
206 PluggedApicIds and ToUnplugApicIds arrays.
207
208 @retval EFI_SUCCESS Output parameters have been set successfully.
209 **/
210 EFI_STATUS
211 QemuCpuhpCollectApicIds (
212 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
213 IN UINT32 PossibleCpuCount,
214 IN UINT32 ApicIdCount,
215 OUT APIC_ID *PluggedApicIds,
216 OUT UINT32 *PluggedCount,
217 OUT APIC_ID *ToUnplugApicIds,
218 OUT UINT32 *ToUnplugSelectors,
219 OUT UINT32 *ToUnplugCount
220 )
221 {
222 UINT32 CurrentSelector;
223
224 if ((PossibleCpuCount == 0) || (ApicIdCount == 0)) {
225 return EFI_INVALID_PARAMETER;
226 }
227
228 *PluggedCount = 0;
229 *ToUnplugCount = 0;
230
231 CurrentSelector = 0;
232 do {
233 UINT32 PendingSelector;
234 UINT8 CpuStatus;
235 APIC_ID *ExtendIds;
236 UINT32 *ExtendSels;
237 UINT32 *ExtendCount;
238 APIC_ID NewApicId;
239
240 //
241 // Write CurrentSelector (which is valid) to the CPU selector register.
242 // Consequences:
243 //
244 // - Other register accesses will be permitted.
245 //
246 // - The QEMU_CPUHP_CMD_GET_PENDING command will start scanning for a CPU
247 // with pending events at CurrentSelector (inclusive).
248 //
249 QemuCpuhpWriteCpuSelector (MmCpuIo, CurrentSelector);
250 //
251 // Write the QEMU_CPUHP_CMD_GET_PENDING command. Consequences
252 // (independently of each other):
253 //
254 // - If there is a CPU with pending events, starting at CurrentSelector
255 // (inclusive), the CPU selector will be updated to that CPU. Note that
256 // the scanning in QEMU may wrap around, because we must never clear the
257 // event bits.
258 //
259 // - The QEMU_CPUHP_RW_CMD_DATA register will return the (possibly updated)
260 // CPU selector value.
261 //
262 QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);
263 PendingSelector = QemuCpuhpReadCommandData (MmCpuIo);
264 if (PendingSelector < CurrentSelector) {
265 DEBUG ((
266 DEBUG_VERBOSE,
267 "%a: CurrentSelector=%u PendingSelector=%u: "
268 "wrap-around\n",
269 __FUNCTION__,
270 CurrentSelector,
271 PendingSelector
272 ));
273 break;
274 }
275
276 CurrentSelector = PendingSelector;
277
278 //
279 // Check the known status / event bits for the currently selected CPU.
280 //
281 CpuStatus = QemuCpuhpReadCpuStatus (MmCpuIo);
282 if ((CpuStatus & QEMU_CPUHP_STAT_INSERT) != 0) {
283 //
284 // The "insert" event guarantees the "enabled" status; plus it excludes
285 // the "fw_remove" event.
286 //
287 if (((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0) ||
288 ((CpuStatus & QEMU_CPUHP_STAT_FW_REMOVE) != 0))
289 {
290 DEBUG ((
291 DEBUG_ERROR,
292 "%a: CurrentSelector=%u CpuStatus=0x%x: "
293 "inconsistent CPU status\n",
294 __FUNCTION__,
295 CurrentSelector,
296 CpuStatus
297 ));
298 return EFI_PROTOCOL_ERROR;
299 }
300
301 DEBUG ((
302 DEBUG_VERBOSE,
303 "%a: CurrentSelector=%u: insert\n",
304 __FUNCTION__,
305 CurrentSelector
306 ));
307
308 ExtendIds = PluggedApicIds;
309 ExtendSels = NULL;
310 ExtendCount = PluggedCount;
311 } else if ((CpuStatus & QEMU_CPUHP_STAT_FW_REMOVE) != 0) {
312 //
313 // "fw_remove" event guarantees "enabled".
314 //
315 if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0) {
316 DEBUG ((
317 DEBUG_ERROR,
318 "%a: CurrentSelector=%u CpuStatus=0x%x: "
319 "inconsistent CPU status\n",
320 __FUNCTION__,
321 CurrentSelector,
322 CpuStatus
323 ));
324 return EFI_PROTOCOL_ERROR;
325 }
326
327 DEBUG ((
328 DEBUG_VERBOSE,
329 "%a: CurrentSelector=%u: fw_remove\n",
330 __FUNCTION__,
331 CurrentSelector
332 ));
333
334 ExtendIds = ToUnplugApicIds;
335 ExtendSels = ToUnplugSelectors;
336 ExtendCount = ToUnplugCount;
337 } else if ((CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) {
338 //
339 // Let the OSPM deal with the "remove" event.
340 //
341 DEBUG ((
342 DEBUG_VERBOSE,
343 "%a: CurrentSelector=%u: remove (ignored)\n",
344 __FUNCTION__,
345 CurrentSelector
346 ));
347
348 ExtendIds = NULL;
349 ExtendSels = NULL;
350 ExtendCount = NULL;
351 } else {
352 DEBUG ((
353 DEBUG_VERBOSE,
354 "%a: CurrentSelector=%u: no event\n",
355 __FUNCTION__,
356 CurrentSelector
357 ));
358 break;
359 }
360
361 ASSERT ((ExtendIds == NULL) == (ExtendCount == NULL));
362 ASSERT ((ExtendSels == NULL) || (ExtendIds != NULL));
363
364 if (ExtendIds != NULL) {
365 //
366 // Save the APIC ID of the CPU with the pending event, to the
367 // corresponding APIC ID array.
368 // For unplug events, also save the CurrentSelector.
369 //
370 if (*ExtendCount == ApicIdCount) {
371 DEBUG ((DEBUG_ERROR, "%a: APIC ID array too small\n", __FUNCTION__));
372 return EFI_BUFFER_TOO_SMALL;
373 }
374
375 QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_ARCH_ID);
376 NewApicId = QemuCpuhpReadCommandData (MmCpuIo);
377 DEBUG ((
378 DEBUG_VERBOSE,
379 "%a: ApicId=" FMT_APIC_ID "\n",
380 __FUNCTION__,
381 NewApicId
382 ));
383 if (ExtendSels != NULL) {
384 ExtendSels[(*ExtendCount)] = CurrentSelector;
385 }
386
387 ExtendIds[(*ExtendCount)++] = NewApicId;
388 }
389
390 //
391 // We've processed the CPU with (known) pending events, but we must never
392 // clear events. Therefore we need to advance past this CPU manually;
393 // otherwise, QEMU_CPUHP_CMD_GET_PENDING would stick to the currently
394 // selected CPU.
395 //
396 CurrentSelector++;
397 } while (CurrentSelector < PossibleCpuCount);
398
399 DEBUG ((
400 DEBUG_VERBOSE,
401 "%a: PluggedCount=%u ToUnplugCount=%u\n",
402 __FUNCTION__,
403 *PluggedCount,
404 *ToUnplugCount
405 ));
406 return EFI_SUCCESS;
407 }