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