QemuCpuhp.c 12 KB

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