2 Simple wrapper functions and utility functions that access QEMU's modern CPU
3 hotplug register block.
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
10 Copyright (c) 2020, Red Hat, Inc.
12 SPDX-License-Identifier: BSD-2-Clause-Patent
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()
20 #include "QemuCpuhp.h"
23 QemuCpuhpReadCommandData2 (
24 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
31 Status
= MmCpuIo
->Io
.Read (
34 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_R_CMD_DATA2
,
38 if (EFI_ERROR (Status
)) {
39 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
48 QemuCpuhpReadCpuStatus (
49 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
56 Status
= MmCpuIo
->Io
.Read (
59 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_R_CPU_STAT
,
63 if (EFI_ERROR (Status
)) {
64 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
73 QemuCpuhpReadCommandData (
74 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
81 Status
= MmCpuIo
->Io
.Read (
84 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_RW_CMD_DATA
,
88 if (EFI_ERROR (Status
)) {
89 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
98 QemuCpuhpWriteCpuSelector (
99 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
,
105 Status
= MmCpuIo
->Io
.Write (
108 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_W_CPU_SEL
,
112 if (EFI_ERROR (Status
)) {
113 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
120 QemuCpuhpWriteCpuStatus (
121 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
,
127 Status
= MmCpuIo
->Io
.Write (
130 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_R_CPU_STAT
,
134 if (EFI_ERROR (Status
)) {
135 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
142 QemuCpuhpWriteCommand (
143 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
,
149 Status
= MmCpuIo
->Io
.Write (
152 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_W_CMD
,
156 if (EFI_ERROR (Status
)) {
157 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
164 Collect the APIC IDs of
165 - the CPUs that have been hot-plugged,
166 - the CPUs that are about to be hot-unplugged.
168 This function only scans for events -- it does not modify them -- in the
171 On error, the contents of the output parameters are undefined.
173 @param[in] MmCpuIo The EFI_MM_CPU_IO_PROTOCOL instance for
176 @param[in] PossibleCpuCount The number of possible CPUs in the system. Must
179 @param[in] ApicIdCount The number of elements each one of the
180 PluggedApicIds and ToUnplugApicIds arrays can
181 accommodate. Must be positive.
183 @param[out] PluggedApicIds The APIC IDs of the CPUs that have been
186 @param[out] PluggedCount The number of filled-in APIC IDs in
189 @param[out] ToUnplugApicIds The APIC IDs of the CPUs that are about to be
192 @param[out] ToUnplugSelectors The QEMU Selectors of the CPUs that are about
195 @param[out] ToUnplugCount The number of filled-in APIC IDs in
198 @retval EFI_INVALID_PARAMETER PossibleCpuCount is zero, or ApicIdCount is
201 @retval EFI_PROTOCOL_ERROR Invalid bitmap detected in the
202 QEMU_CPUHP_R_CPU_STAT register.
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.
208 @retval EFI_SUCCESS Output parameters have been set successfully.
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
222 UINT32 CurrentSelector
;
224 if ((PossibleCpuCount
== 0) || (ApicIdCount
== 0)) {
225 return EFI_INVALID_PARAMETER
;
233 UINT32 PendingSelector
;
241 // Write CurrentSelector (which is valid) to the CPU selector register.
244 // - Other register accesses will be permitted.
246 // - The QEMU_CPUHP_CMD_GET_PENDING command will start scanning for a CPU
247 // with pending events at CurrentSelector (inclusive).
249 QemuCpuhpWriteCpuSelector (MmCpuIo
, CurrentSelector
);
251 // Write the QEMU_CPUHP_CMD_GET_PENDING command. Consequences
252 // (independently of each other):
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
259 // - The QEMU_CPUHP_RW_CMD_DATA register will return the (possibly updated)
260 // CPU selector value.
262 QemuCpuhpWriteCommand (MmCpuIo
, QEMU_CPUHP_CMD_GET_PENDING
);
263 PendingSelector
= QemuCpuhpReadCommandData (MmCpuIo
);
264 if (PendingSelector
< CurrentSelector
) {
267 "%a: CurrentSelector=%u PendingSelector=%u: "
276 CurrentSelector
= PendingSelector
;
279 // Check the known status / event bits for the currently selected CPU.
281 CpuStatus
= QemuCpuhpReadCpuStatus (MmCpuIo
);
282 if ((CpuStatus
& QEMU_CPUHP_STAT_INSERT
) != 0) {
284 // The "insert" event guarantees the "enabled" status; plus it excludes
285 // the "fw_remove" event.
287 if (((CpuStatus
& QEMU_CPUHP_STAT_ENABLED
) == 0) ||
288 ((CpuStatus
& QEMU_CPUHP_STAT_FW_REMOVE
) != 0))
292 "%a: CurrentSelector=%u CpuStatus=0x%x: "
293 "inconsistent CPU status\n",
298 return EFI_PROTOCOL_ERROR
;
303 "%a: CurrentSelector=%u: insert\n",
308 ExtendIds
= PluggedApicIds
;
310 ExtendCount
= PluggedCount
;
311 } else if ((CpuStatus
& QEMU_CPUHP_STAT_FW_REMOVE
) != 0) {
313 // "fw_remove" event guarantees "enabled".
315 if ((CpuStatus
& QEMU_CPUHP_STAT_ENABLED
) == 0) {
318 "%a: CurrentSelector=%u CpuStatus=0x%x: "
319 "inconsistent CPU status\n",
324 return EFI_PROTOCOL_ERROR
;
329 "%a: CurrentSelector=%u: fw_remove\n",
334 ExtendIds
= ToUnplugApicIds
;
335 ExtendSels
= ToUnplugSelectors
;
336 ExtendCount
= ToUnplugCount
;
337 } else if ((CpuStatus
& QEMU_CPUHP_STAT_REMOVE
) != 0) {
339 // Let the OSPM deal with the "remove" event.
343 "%a: CurrentSelector=%u: remove (ignored)\n",
354 "%a: CurrentSelector=%u: no event\n",
361 ASSERT ((ExtendIds
== NULL
) == (ExtendCount
== NULL
));
362 ASSERT ((ExtendSels
== NULL
) || (ExtendIds
!= NULL
));
364 if (ExtendIds
!= NULL
) {
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.
370 if (*ExtendCount
== ApicIdCount
) {
371 DEBUG ((DEBUG_ERROR
, "%a: APIC ID array too small\n", __FUNCTION__
));
372 return EFI_BUFFER_TOO_SMALL
;
375 QemuCpuhpWriteCommand (MmCpuIo
, QEMU_CPUHP_CMD_GET_ARCH_ID
);
376 NewApicId
= QemuCpuhpReadCommandData (MmCpuIo
);
379 "%a: ApicId=" FMT_APIC_ID
"\n",
383 if (ExtendSels
!= NULL
) {
384 ExtendSels
[(*ExtendCount
)] = CurrentSelector
;
387 ExtendIds
[(*ExtendCount
)++] = NewApicId
;
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
397 } while (CurrentSelector
< PossibleCpuCount
);
401 "%a: PluggedCount=%u ToUnplugCount=%u\n",