]> 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 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 QemuCpuhpWriteCommand (
118 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
119 IN UINT8 Command
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_W_CMD,
128 1,
129 &Command
130 );
131 if (EFI_ERROR (Status)) {
132 DEBUG ((DEBUG_ERROR, "%a: %r\n", __FUNCTION__, Status));
133 ASSERT (FALSE);
134 CpuDeadLoop ();
135 }
136 }
137
138 /**
139 Collect the APIC IDs of
140 - the CPUs that have been hot-plugged,
141 - the CPUs that are about to be hot-unplugged.
142
143 This function only scans for events -- it does not modify them -- in the
144 hotplug registers.
145
146 On error, the contents of the output parameters are undefined.
147
148 @param[in] MmCpuIo The EFI_MM_CPU_IO_PROTOCOL instance for
149 accessing IO Ports.
150
151 @param[in] PossibleCpuCount The number of possible CPUs in the system. Must
152 be positive.
153
154 @param[in] ApicIdCount The number of elements each one of the
155 PluggedApicIds and ToUnplugApicIds arrays can
156 accommodate. Must be positive.
157
158 @param[out] PluggedApicIds The APIC IDs of the CPUs that have been
159 hot-plugged.
160
161 @param[out] PluggedCount The number of filled-in APIC IDs in
162 PluggedApicIds.
163
164 @param[out] ToUnplugApicIds The APIC IDs of the CPUs that are about to be
165 hot-unplugged.
166
167 @param[out] ToUnplugCount The number of filled-in APIC IDs in
168 ToUnplugApicIds.
169
170 @retval EFI_INVALID_PARAMETER PossibleCpuCount is zero, or ApicIdCount is
171 zero.
172
173 @retval EFI_PROTOCOL_ERROR Invalid bitmap detected in the
174 QEMU_CPUHP_R_CPU_STAT register.
175
176 @retval EFI_BUFFER_TOO_SMALL There was an attempt to place more than
177 ApicIdCount APIC IDs into one of the
178 PluggedApicIds and ToUnplugApicIds arrays.
179
180 @retval EFI_SUCCESS Output parameters have been set successfully.
181 **/
182 EFI_STATUS
183 QemuCpuhpCollectApicIds (
184 IN CONST EFI_MM_CPU_IO_PROTOCOL *MmCpuIo,
185 IN UINT32 PossibleCpuCount,
186 IN UINT32 ApicIdCount,
187 OUT APIC_ID *PluggedApicIds,
188 OUT UINT32 *PluggedCount,
189 OUT APIC_ID *ToUnplugApicIds,
190 OUT UINT32 *ToUnplugCount
191 )
192 {
193 UINT32 CurrentSelector;
194
195 if (PossibleCpuCount == 0 || ApicIdCount == 0) {
196 return EFI_INVALID_PARAMETER;
197 }
198
199 *PluggedCount = 0;
200 *ToUnplugCount = 0;
201
202 CurrentSelector = 0;
203 do {
204 UINT32 PendingSelector;
205 UINT8 CpuStatus;
206 APIC_ID *ExtendIds;
207 UINT32 *ExtendCount;
208 APIC_ID NewApicId;
209
210 //
211 // Write CurrentSelector (which is valid) to the CPU selector register.
212 // Consequences:
213 //
214 // - Other register accesses will be permitted.
215 //
216 // - The QEMU_CPUHP_CMD_GET_PENDING command will start scanning for a CPU
217 // with pending events at CurrentSelector (inclusive).
218 //
219 QemuCpuhpWriteCpuSelector (MmCpuIo, CurrentSelector);
220 //
221 // Write the QEMU_CPUHP_CMD_GET_PENDING command. Consequences
222 // (independently of each other):
223 //
224 // - If there is a CPU with pending events, starting at CurrentSelector
225 // (inclusive), the CPU selector will be updated to that CPU. Note that
226 // the scanning in QEMU may wrap around, because we must never clear the
227 // event bits.
228 //
229 // - The QEMU_CPUHP_RW_CMD_DATA register will return the (possibly updated)
230 // CPU selector value.
231 //
232 QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_PENDING);
233 PendingSelector = QemuCpuhpReadCommandData (MmCpuIo);
234 if (PendingSelector < CurrentSelector) {
235 DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u PendingSelector=%u: "
236 "wrap-around\n", __FUNCTION__, CurrentSelector, PendingSelector));
237 break;
238 }
239 CurrentSelector = PendingSelector;
240
241 //
242 // Check the known status / event bits for the currently selected CPU.
243 //
244 CpuStatus = QemuCpuhpReadCpuStatus (MmCpuIo);
245 if ((CpuStatus & QEMU_CPUHP_STAT_INSERT) != 0) {
246 //
247 // The "insert" event guarantees the "enabled" status; plus it excludes
248 // the "remove" event.
249 //
250 if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) == 0 ||
251 (CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) {
252 DEBUG ((DEBUG_ERROR, "%a: CurrentSelector=%u CpuStatus=0x%x: "
253 "inconsistent CPU status\n", __FUNCTION__, CurrentSelector,
254 CpuStatus));
255 return EFI_PROTOCOL_ERROR;
256 }
257
258 DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: insert\n", __FUNCTION__,
259 CurrentSelector));
260
261 ExtendIds = PluggedApicIds;
262 ExtendCount = PluggedCount;
263 } else if ((CpuStatus & QEMU_CPUHP_STAT_REMOVE) != 0) {
264 DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: remove\n", __FUNCTION__,
265 CurrentSelector));
266
267 ExtendIds = ToUnplugApicIds;
268 ExtendCount = ToUnplugCount;
269 } else {
270 DEBUG ((DEBUG_VERBOSE, "%a: CurrentSelector=%u: no event\n",
271 __FUNCTION__, CurrentSelector));
272 break;
273 }
274
275 //
276 // Save the APIC ID of the CPU with the pending event, to the corresponding
277 // APIC ID array.
278 //
279 if (*ExtendCount == ApicIdCount) {
280 DEBUG ((DEBUG_ERROR, "%a: APIC ID array too small\n", __FUNCTION__));
281 return EFI_BUFFER_TOO_SMALL;
282 }
283 QemuCpuhpWriteCommand (MmCpuIo, QEMU_CPUHP_CMD_GET_ARCH_ID);
284 NewApicId = QemuCpuhpReadCommandData (MmCpuIo);
285 DEBUG ((DEBUG_VERBOSE, "%a: ApicId=" FMT_APIC_ID "\n", __FUNCTION__,
286 NewApicId));
287 ExtendIds[(*ExtendCount)++] = NewApicId;
288
289 //
290 // We've processed the CPU with (known) pending events, but we must never
291 // clear events. Therefore we need to advance past this CPU manually;
292 // otherwise, QEMU_CPUHP_CMD_GET_PENDING would stick to the currently
293 // selected CPU.
294 //
295 CurrentSelector++;
296 } while (CurrentSelector < PossibleCpuCount);
297
298 DEBUG ((DEBUG_VERBOSE, "%a: PluggedCount=%u ToUnplugCount=%u\n",
299 __FUNCTION__, *PluggedCount, *ToUnplugCount));
300 return EFI_SUCCESS;
301 }