]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/CpuHotplugSmm/QemuCpuhp.c
OvmfPkg: add library class BlobVerifierLib with null implementation
[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 return CommandData2;
44 }
45
46 UINT8
47 QemuCpuhpReadCpuStatus (
48 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
49 )
50 {
51 UINT8 CpuStatus;
52 EFI_STATUS Status;
53
54 CpuStatus = 0;
55 Status = MmCpuIo->Io.Read (
56 MmCpuIo,
57 MM_IO_UINT8,
58 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CPU_STAT,
59 1,
60 &CpuStatus
61 );
62 if (EFI_ERROR (Status)) {
63 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
64 ASSERT (FALSE);
65 CpuDeadLoop ();
66 }
67 return CpuStatus;
68 }
69
70 UINT32
71 QemuCpuhpReadCommandData (
72 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo
73 )
74 {
75 UINT32 CommandData;
76 EFI_STATUS Status;
77
78 CommandData = 0;
79 Status = MmCpuIo->Io.Read (
80 MmCpuIo,
81 MM_IO_UINT32,
82 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_RW_CMD_DATA,
83 1,
84 &CommandData
85 );
86 if (EFI_ERROR (Status)) {
87 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
88 ASSERT (FALSE);
89 CpuDeadLoop ();
90 }
91 return CommandData;
92 }
93
94 VOID
95 QemuCpuhpWriteCpuSelector (
96 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
97 IN UINT32 Selector
98 )
99 {
100 EFI_STATUS Status;
101
102 Status = MmCpuIo->Io.Write (
103 MmCpuIo,
104 MM_IO_UINT32,
105 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL,
106 1,
107 &Selector
108 );
109 if (EFI_ERROR (Status)) {
110 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
111 ASSERT (FALSE);
112 CpuDeadLoop ();
113 }
114 }
115
116 VOID
117 QemuCpuhpWriteCpuStatus (
118 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
119 IN UINT8 CpuStatus
120 )
121 {
122 EFI_STATUS Status;
123
124 Status = MmCpuIo->Io.Write (
125 MmCpuIo,
126 MM_IO_UINT8,
127 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_R_CPU_STAT,
128 1,
129 &CpuStatus
130 );
131 if (EFI_ERROR (Status)) {
132 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
133 ASSERT (FALSE);
134 CpuDeadLoop ();
135 }
136 }
137
138 VOID
139 QemuCpuhpWriteCommand (
140 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
141 IN UINT8 Command
142 )
143 {
144 EFI_STATUS Status;
145
146 Status = MmCpuIo->Io.Write (
147 MmCpuIo,
148 MM_IO_UINT8,
149 ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CMD,
150 1,
151 &Command
152 );
153 if (EFI_ERROR (Status)) {
154 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
155 ASSERT (FALSE);
156 CpuDeadLoop ();
157 }
158 }
159
160 /**
161 Collect the APIC IDs of
162 - the CPUs that have been hot-plugged,
163 - the CPUs that are about to be hot-unplugged.
164
165 This function only scans for events -- it does not modify them -- in the
166 hotplug registers.
167
168 On error, the contents of the output parameters are undefined.
169
170 @param[in] MmCpuIo The EFI_MM_CPU_IO_PROTOCOL instance for
171 accessing IO Ports.
172
173 @param[in] PossibleCpuCount The number of possible CPUs in the system. Must
174 be positive.
175
176 @param[in] ApicIdCount The number of elements each one of the
177 PluggedApicIds and ToUnplugApicIds arrays can
178 accommodate. Must be positive.
179
180 @param[out] PluggedApicIds The APIC IDs of the CPUs that have been
181 hot-plugged.
182
183 @param[out] PluggedCount The number of filled-in APIC IDs in
184 PluggedApicIds.
185
186 @param[out] ToUnplugApicIds The APIC IDs of the CPUs that are about to be
187 hot-unplugged.
188
189 @param[out] ToUnplugSelectors The QEMU Selectors of the CPUs that are about
190 to be hot-unplugged.
191
192 @param[out] ToUnplugCount The number of filled-in APIC IDs in
193 ToUnplugApicIds.
194
195 @retval EFI_INVALID_PARAMETER PossibleCpuCount is zero, or ApicIdCount is
196 zero.
197
198 @retval EFI_PROTOCOL_ERROR Invalid bitmap detected in the
199 QEMU_CPUHP_R_CPU_STAT register.
200
201 @retval EFI_BUFFER_TOO_SMALL There was an attempt to place more than
202 ApicIdCount APIC IDs into one of the
203 PluggedApicIds and ToUnplugApicIds arrays.
204
205 @retval EFI_SUCCESS Output parameters have been set successfully.
206 **/
207 EFI_STATUS
208 QemuCpuhpCollectApicIds (
209 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
210 IN UINT32 PossibleCpuCount,
211 IN UINT32 ApicIdCount,
212 OUT APIC_ID *PluggedApicIds,
213 OUT UINT32 *PluggedCount,
214 OUT APIC_ID *ToUnplugApicIds,
215 OUT UINT32 *ToUnplugSelectors,
216 OUT UINT32 *ToUnplugCount
217 )
218 {
219 UINT32 CurrentSelector;
220
221 if (PossibleCpuCount == 0 || ApicIdCount == 0) {
222 return EFI_INVALID_PARAMETER;
223 }
224
225 *PluggedCount = 0;
226 *ToUnplugCount = 0;
227
228 CurrentSelector = 0;
229 do {
230 UINT32 PendingSelector;
231 UINT8 CpuStatus;
232 APIC_ID *ExtendIds;
233 UINT32 *ExtendSels;
234 UINT32 *ExtendCount;
235 APIC_ID NewApicId;
236
237 //
238 // Write CurrentSelector (which is valid) to the CPU selector register.
239 // Consequences:
240 //
241 // - Other register accesses will be permitted.
242 //
243 // - The QEMU_CPUHP_CMD_GET_PENDING command will start scanning for a CPU
244 // with pending events at CurrentSelector (inclusive).
245 //
246 QemuCpuhpWriteCpuSelector (MmCpuIo, CurrentSelector);
247 //
248 // Write the QEMU_CPUHP_CMD_GET_PENDING command. Consequences
249 // (independently of each other):
250 //
251 // - If there is a CPU with pending events, starting at CurrentSelector
252 // (inclusive), the CPU selector will be updated to that CPU. Note that
253 // the scanning in QEMU may wrap around, because we must never clear the
254 // event bits.
255 //
256 // - The QEMU_CPUHP_RW_CMD_DATA register will return the (possibly updated)
257 // CPU selector value.
258 //
259 QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);
260 PendingSelector = QemuCpuhpReadCommandData (MmCpuIo);
261 if (PendingSelector < CurrentSelector) {
262 DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u PendingSelector=%u: "
263 "wrap-around\n", __FUNCTION__, CurrentSelector, PendingSelector));
264 break;
265 }
266 CurrentSelector = PendingSelector;
267
268 //
269 // Check the known status / event bits for the currently selected CPU.
270 //
271 CpuStatus = QemuCpuhpReadCpuStatus (MmCpuIo);
272 if ((CpuStatus & QEMU_CPUHP_STAT_INSERT) != 0) {
273 //
274 // The "insert" event guarantees the "enabled" status; plus it excludes
275 // the "fw_remove" event.
276 //
277 if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0 ||
278 (CpuStatus & QEMU_CPUHP_STAT_FW_REMOVE) != 0) {
279 DEBUG ((DEBUG_ERROR, "%a: CurrentSelector=%u CpuStatus=0x%x: "
280 "inconsistent CPU status\n", __FUNCTION__, CurrentSelector,
281 CpuStatus));
282 return EFI_PROTOCOL_ERROR;
283 }
284
285 DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: insert\n", __FUNCTION__,
286 CurrentSelector));
287
288 ExtendIds = PluggedApicIds;
289 ExtendSels = NULL;
290 ExtendCount = PluggedCount;
291 } else if ((CpuStatus & QEMU_CPUHP_STAT_FW_REMOVE) != 0) {
292 //
293 // "fw_remove" event guarantees "enabled".
294 //
295 if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0) {
296 DEBUG ((DEBUG_ERROR, "%a: CurrentSelector=%u CpuStatus=0x%x: "
297 "inconsistent CPU status\n", __FUNCTION__, CurrentSelector,
298 CpuStatus));
299 return EFI_PROTOCOL_ERROR;
300 }
301
302 DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: fw_remove\n",
303 __FUNCTION__, CurrentSelector));
304
305 ExtendIds = ToUnplugApicIds;
306 ExtendSels = ToUnplugSelectors;
307 ExtendCount = ToUnplugCount;
308 } else if ((CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) {
309 //
310 // Let the OSPM deal with the "remove" event.
311 //
312 DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: remove (ignored)\n",
313 __FUNCTION__, CurrentSelector));
314
315 ExtendIds = NULL;
316 ExtendSels = NULL;
317 ExtendCount = NULL;
318 } else {
319 DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: no event\n",
320 __FUNCTION__, CurrentSelector));
321 break;
322 }
323
324 ASSERT ((ExtendIds == NULL) == (ExtendCount == NULL));
325 ASSERT ((ExtendSels == NULL) || (ExtendIds != NULL));
326
327 if (ExtendIds != NULL) {
328 //
329 // Save the APIC ID of the CPU with the pending event, to the
330 // corresponding APIC ID array.
331 // For unplug events, also save the CurrentSelector.
332 //
333 if (*ExtendCount == ApicIdCount) {
334 DEBUG ((DEBUG_ERROR, "%a: APIC ID array too small\n", __FUNCTION__));
335 return EFI_BUFFER_TOO_SMALL;
336 }
337 QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_ARCH_ID);
338 NewApicId = QemuCpuhpReadCommandData (MmCpuIo);
339 DEBUG ((DEBUG_VERBOSE, "%a: ApicId=" FMT_APIC_ID "\n", __FUNCTION__,
340 NewApicId));
341 if (ExtendSels != NULL) {
342 ExtendSels[(*ExtendCount)] = CurrentSelector;
343 }
344 ExtendIds[(*ExtendCount)++] = NewApicId;
345 }
346 //
347 // We've processed the CPU with (known) pending events, but we must never
348 // clear events. Therefore we need to advance past this CPU manually;
349 // otherwise, QEMU_CPUHP_CMD_GET_PENDING would stick to the currently
350 // selected CPU.
351 //
352 CurrentSelector++;
353 } while (CurrentSelector < PossibleCpuCount);
354
355 DEBUG ((DEBUG_VERBOSE, "%a: PluggedCount=%u ToUnplugCount=%u\n",
356 __FUNCTION__, *PluggedCount, *ToUnplugCount));
357 return EFI_SUCCESS;
358 }