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
));
47 QemuCpuhpReadCpuStatus (
48 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
55 Status
= MmCpuIo
->Io
.Read (
58 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_R_CPU_STAT
,
62 if (EFI_ERROR (Status
)) {
63 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
71 QemuCpuhpReadCommandData (
72 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
79 Status
= MmCpuIo
->Io
.Read (
82 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_RW_CMD_DATA
,
86 if (EFI_ERROR (Status
)) {
87 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
95 QemuCpuhpWriteCpuSelector (
96 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
,
102 Status
= MmCpuIo
->Io
.Write (
105 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_W_CPU_SEL
,
109 if (EFI_ERROR (Status
)) {
110 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
117 QemuCpuhpWriteCpuStatus (
118 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
,
124 Status
= MmCpuIo
->Io
.Write (
127 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_R_CPU_STAT
,
131 if (EFI_ERROR (Status
)) {
132 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
139 QemuCpuhpWriteCommand (
140 IN CONST EFI_MM_CPU_IO_PROTOCOL
*MmCpuIo
,
146 Status
= MmCpuIo
->Io
.Write (
149 ICH9_CPU_HOTPLUG_BASE
+ QEMU_CPUHP_W_CMD
,
153 if (EFI_ERROR (Status
)) {
154 DEBUG ((DEBUG_ERROR
, "%a: %r\n", __FUNCTION__
, Status
));
161 Collect the APIC IDs of
162 - the CPUs that have been hot-plugged,
163 - the CPUs that are about to be hot-unplugged.
165 This function only scans for events -- it does not modify them -- in the
168 On error, the contents of the output parameters are undefined.
170 @param[in] MmCpuIo The EFI_MM_CPU_IO_PROTOCOL instance for
173 @param[in] PossibleCpuCount The number of possible CPUs in the system. Must
176 @param[in] ApicIdCount The number of elements each one of the
177 PluggedApicIds and ToUnplugApicIds arrays can
178 accommodate. Must be positive.
180 @param[out] PluggedApicIds The APIC IDs of the CPUs that have been
183 @param[out] PluggedCount The number of filled-in APIC IDs in
186 @param[out] ToUnplugApicIds The APIC IDs of the CPUs that are about to be
189 @param[out] ToUnplugSelectors The QEMU Selectors of the CPUs that are about
192 @param[out] ToUnplugCount The number of filled-in APIC IDs in
195 @retval EFI_INVALID_PARAMETER PossibleCpuCount is zero, or ApicIdCount is
198 @retval EFI_PROTOCOL_ERROR Invalid bitmap detected in the
199 QEMU_CPUHP_R_CPU_STAT register.
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.
205 @retval EFI_SUCCESS Output parameters have been set successfully.
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
219 UINT32 CurrentSelector
;
221 if (PossibleCpuCount
== 0 || ApicIdCount
== 0) {
222 return EFI_INVALID_PARAMETER
;
230 UINT32 PendingSelector
;
238 // Write CurrentSelector (which is valid) to the CPU selector register.
241 // - Other register accesses will be permitted.
243 // - The QEMU_CPUHP_CMD_GET_PENDING command will start scanning for a CPU
244 // with pending events at CurrentSelector (inclusive).
246 QemuCpuhpWriteCpuSelector (MmCpuIo
, CurrentSelector
);
248 // Write the QEMU_CPUHP_CMD_GET_PENDING command. Consequences
249 // (independently of each other):
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
256 // - The QEMU_CPUHP_RW_CMD_DATA register will return the (possibly updated)
257 // CPU selector value.
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
));
266 CurrentSelector
= PendingSelector
;
269 // Check the known status / event bits for the currently selected CPU.
271 CpuStatus
= QemuCpuhpReadCpuStatus (MmCpuIo
);
272 if ((CpuStatus
& QEMU_CPUHP_STAT_INSERT
) != 0) {
274 // The "insert" event guarantees the "enabled" status; plus it excludes
275 // the "fw_remove" event.
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
,
282 return EFI_PROTOCOL_ERROR
;
285 DEBUG ((DEBUG_VERBOSE
, "%a: CurrentSelector=%u: insert\n", __FUNCTION__
,
288 ExtendIds
= PluggedApicIds
;
290 ExtendCount
= PluggedCount
;
291 } else if ((CpuStatus
& QEMU_CPUHP_STAT_FW_REMOVE
) != 0) {
293 // "fw_remove" event guarantees "enabled".
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
,
299 return EFI_PROTOCOL_ERROR
;
302 DEBUG ((DEBUG_VERBOSE
, "%a: CurrentSelector=%u: fw_remove\n",
303 __FUNCTION__
, CurrentSelector
));
305 ExtendIds
= ToUnplugApicIds
;
306 ExtendSels
= ToUnplugSelectors
;
307 ExtendCount
= ToUnplugCount
;
308 } else if ((CpuStatus
& QEMU_CPUHP_STAT_REMOVE
) != 0) {
310 // Let the OSPM deal with the "remove" event.
312 DEBUG ((DEBUG_VERBOSE
, "%a: CurrentSelector=%u: remove (ignored)\n",
313 __FUNCTION__
, CurrentSelector
));
319 DEBUG ((DEBUG_VERBOSE
, "%a: CurrentSelector=%u: no event\n",
320 __FUNCTION__
, CurrentSelector
));
324 ASSERT ((ExtendIds
== NULL
) == (ExtendCount
== NULL
));
325 ASSERT ((ExtendSels
== NULL
) || (ExtendIds
!= NULL
));
327 if (ExtendIds
!= NULL
) {
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.
333 if (*ExtendCount
== ApicIdCount
) {
334 DEBUG ((DEBUG_ERROR
, "%a: APIC ID array too small\n", __FUNCTION__
));
335 return EFI_BUFFER_TOO_SMALL
;
337 QemuCpuhpWriteCommand (MmCpuIo
, QEMU_CPUHP_CMD_GET_ARCH_ID
);
338 NewApicId
= QemuCpuhpReadCommandData (MmCpuIo
);
339 DEBUG ((DEBUG_VERBOSE
, "%a: ApicId=" FMT_APIC_ID
"\n", __FUNCTION__
,
341 if (ExtendSels
!= NULL
) {
342 ExtendSels
[(*ExtendCount
)] = CurrentSelector
;
344 ExtendIds
[(*ExtendCount
)++] = NewApicId
;
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
353 } while (CurrentSelector
< PossibleCpuCount
);
355 DEBUG ((DEBUG_VERBOSE
, "%a: PluggedCount=%u ToUnplugCount=%u\n",
356 __FUNCTION__
, *PluggedCount
, *ToUnplugCount
));