Smbase.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /** @file
  2. SMBASE relocation for hot-plugged CPUs.
  3. Copyright (c) 2020, Red Hat, Inc.
  4. SPDX-License-Identifier: BSD-2-Clause-Patent
  5. **/
  6. #include <Base.h> // BASE_1MB
  7. #include <Library/BaseLib.h> // CpuPause()
  8. #include <Library/BaseMemoryLib.h> // CopyMem()
  9. #include <Library/DebugLib.h> // DEBUG()
  10. #include <Library/LocalApicLib.h> // SendInitSipiSipi()
  11. #include <Library/SynchronizationLib.h> // InterlockedCompareExchange64()
  12. #include <Register/Intel/SmramSaveStateMap.h> // SMM_DEFAULT_SMBASE
  13. #include "FirstSmiHandlerContext.h" // FIRST_SMI_HANDLER_CONTEXT
  14. #include "Smbase.h"
  15. extern CONST UINT8 mPostSmmPen[];
  16. extern CONST UINT16 mPostSmmPenSize;
  17. extern CONST UINT8 mFirstSmiHandler[];
  18. extern CONST UINT16 mFirstSmiHandlerSize;
  19. /**
  20. Allocate a non-SMRAM reserved memory page for the Post-SMM Pen for hot-added
  21. CPUs.
  22. This function may only be called from the entry point function of the driver.
  23. @param[out] PenAddress The address of the allocated (normal RAM) reserved
  24. page.
  25. @param[in] BootServices Pointer to the UEFI boot services table. Used for
  26. allocating the normal RAM (not SMRAM) reserved page.
  27. @retval EFI_SUCCESS Allocation successful.
  28. @retval EFI_BAD_BUFFER_SIZE The Post-SMM Pen template is not smaller than
  29. EFI_PAGE_SIZE.
  30. @return Error codes propagated from underlying services.
  31. DEBUG_ERROR messages have been logged. No
  32. resources have been allocated.
  33. **/
  34. EFI_STATUS
  35. SmbaseAllocatePostSmmPen (
  36. OUT UINT32 *PenAddress,
  37. IN CONST EFI_BOOT_SERVICES *BootServices
  38. )
  39. {
  40. EFI_STATUS Status;
  41. EFI_PHYSICAL_ADDRESS Address;
  42. //
  43. // The pen code must fit in one page, and the last byte must remain free for
  44. // signaling the SMM Monarch.
  45. //
  46. if (mPostSmmPenSize >= EFI_PAGE_SIZE) {
  47. Status = EFI_BAD_BUFFER_SIZE;
  48. DEBUG ((
  49. DEBUG_ERROR,
  50. "%a: mPostSmmPenSize=%u: %r\n",
  51. __FUNCTION__,
  52. mPostSmmPenSize,
  53. Status
  54. ));
  55. return Status;
  56. }
  57. Address = BASE_1MB - 1;
  58. Status = BootServices->AllocatePages (
  59. AllocateMaxAddress,
  60. EfiReservedMemoryType,
  61. 1,
  62. &Address
  63. );
  64. if (EFI_ERROR (Status)) {
  65. DEBUG ((DEBUG_ERROR, "%a: AllocatePages(): %r\n", __FUNCTION__, Status));
  66. return Status;
  67. }
  68. DEBUG ((DEBUG_INFO, "%a: Post-SMM Pen at 0x%Lx\n", __FUNCTION__, Address));
  69. *PenAddress = (UINT32)Address;
  70. return EFI_SUCCESS;
  71. }
  72. /**
  73. Copy the Post-SMM Pen template code into the reserved page allocated with
  74. SmbaseAllocatePostSmmPen().
  75. Note that this effects an "SMRAM to normal RAM" copy.
  76. The SMM Monarch is supposed to call this function from the root MMI handler.
  77. @param[in] PenAddress The allocation address returned by
  78. SmbaseAllocatePostSmmPen().
  79. **/
  80. VOID
  81. SmbaseReinstallPostSmmPen (
  82. IN UINT32 PenAddress
  83. )
  84. {
  85. CopyMem ((VOID *)(UINTN)PenAddress, mPostSmmPen, mPostSmmPenSize);
  86. }
  87. /**
  88. Release the reserved page allocated with SmbaseAllocatePostSmmPen().
  89. This function may only be called from the entry point function of the driver,
  90. on the error path.
  91. @param[in] PenAddress The allocation address returned by
  92. SmbaseAllocatePostSmmPen().
  93. @param[in] BootServices Pointer to the UEFI boot services table. Used for
  94. releasing the normal RAM (not SMRAM) reserved page.
  95. **/
  96. VOID
  97. SmbaseReleasePostSmmPen (
  98. IN UINT32 PenAddress,
  99. IN CONST EFI_BOOT_SERVICES *BootServices
  100. )
  101. {
  102. BootServices->FreePages (PenAddress, 1);
  103. }
  104. /**
  105. Place the handler routine for the first SMIs of hot-added CPUs at
  106. (SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET).
  107. Note that this effects an "SMRAM to SMRAM" copy.
  108. Additionally, shut the APIC ID gate in FIRST_SMI_HANDLER_CONTEXT.
  109. This function may only be called from the entry point function of the driver,
  110. and only after PcdQ35SmramAtDefaultSmbase has been determined to be TRUE.
  111. **/
  112. VOID
  113. SmbaseInstallFirstSmiHandler (
  114. VOID
  115. )
  116. {
  117. FIRST_SMI_HANDLER_CONTEXT *Context;
  118. CopyMem (
  119. (VOID *)(UINTN)(SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET),
  120. mFirstSmiHandler,
  121. mFirstSmiHandlerSize
  122. );
  123. Context = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;
  124. Context->ApicIdGate = MAX_UINT64;
  125. }
  126. /**
  127. Relocate the SMBASE on a hot-added CPU. Then pen the hot-added CPU in the
  128. normal RAM reserved memory page, set up earlier with
  129. SmbaseAllocatePostSmmPen() and SmbaseReinstallPostSmmPen().
  130. The SMM Monarch is supposed to call this function from the root MMI handler.
  131. The SMM Monarch is responsible for calling SmbaseInstallFirstSmiHandler(),
  132. SmbaseAllocatePostSmmPen(), and SmbaseReinstallPostSmmPen() before calling
  133. this function.
  134. If the OS maliciously boots the hot-added CPU ahead of letting the ACPI CPU
  135. hotplug event handler broadcast the CPU hotplug MMI, then the hot-added CPU
  136. returns to the OS rather than to the pen, upon RSM. In that case, this
  137. function will hang forever (unless the OS happens to signal back through the
  138. last byte of the pen page).
  139. @param[in] ApicId The APIC ID of the hot-added CPU whose SMBASE should
  140. be relocated.
  141. @param[in] Smbase The new SMBASE address. The root MMI handler is
  142. responsible for passing in a free ("unoccupied")
  143. SMBASE address that was pre-configured by
  144. PiSmmCpuDxeSmm in CPU_HOT_PLUG_DATA.
  145. @param[in] PenAddress The address of the Post-SMM Pen for hot-added CPUs, as
  146. returned by SmbaseAllocatePostSmmPen(), and installed
  147. by SmbaseReinstallPostSmmPen().
  148. @retval EFI_SUCCESS The SMBASE of the hot-added CPU with APIC ID
  149. ApicId has been relocated to Smbase. The
  150. hot-added CPU has reported back about leaving
  151. SMM.
  152. @retval EFI_PROTOCOL_ERROR Synchronization bug encountered around
  153. FIRST_SMI_HANDLER_CONTEXT.ApicIdGate.
  154. @retval EFI_INVALID_PARAMETER Smbase does not fit in 32 bits. No relocation
  155. has been attempted.
  156. **/
  157. EFI_STATUS
  158. SmbaseRelocate (
  159. IN APIC_ID ApicId,
  160. IN UINTN Smbase,
  161. IN UINT32 PenAddress
  162. )
  163. {
  164. EFI_STATUS Status;
  165. volatile UINT8 *SmmVacated;
  166. volatile FIRST_SMI_HANDLER_CONTEXT *Context;
  167. UINT64 ExchangeResult;
  168. if (Smbase > MAX_UINT32) {
  169. Status = EFI_INVALID_PARAMETER;
  170. DEBUG ((
  171. DEBUG_ERROR,
  172. "%a: ApicId=" FMT_APIC_ID " Smbase=0x%Lx: %r\n",
  173. __FUNCTION__,
  174. ApicId,
  175. (UINT64)Smbase,
  176. Status
  177. ));
  178. return Status;
  179. }
  180. SmmVacated = (UINT8 *)(UINTN)PenAddress + (EFI_PAGE_SIZE - 1);
  181. Context = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;
  182. //
  183. // Clear AboutToLeaveSmm, so we notice when the hot-added CPU is just about
  184. // to reach RSM, and we can proceed to polling the last byte of the reserved
  185. // page (which could be attacked by the OS).
  186. //
  187. Context->AboutToLeaveSmm = 0;
  188. //
  189. // Clear the last byte of the reserved page, so we notice when the hot-added
  190. // CPU checks back in from the pen.
  191. //
  192. *SmmVacated = 0;
  193. //
  194. // Boot the hot-added CPU.
  195. //
  196. // There are 2*2 cases to consider:
  197. //
  198. // (1) The CPU was hot-added before the SMI was broadcast.
  199. //
  200. // (1.1) The OS is benign.
  201. //
  202. // The hot-added CPU is in RESET state, with the broadcast SMI pending
  203. // for it. The directed SMI below will be ignored (it's idempotent),
  204. // and the INIT-SIPI-SIPI will launch the CPU directly into SMM.
  205. //
  206. // (1.2) The OS is malicious.
  207. //
  208. // The hot-added CPU has been booted, by the OS. Thus, the hot-added
  209. // CPU is spinning on the APIC ID gate. In that case, both the SMI and
  210. // the INIT-SIPI-SIPI below will be ignored.
  211. //
  212. // (2) The CPU was hot-added after the SMI was broadcast.
  213. //
  214. // (2.1) The OS is benign.
  215. //
  216. // The hot-added CPU is in RESET state, with no SMI pending for it. The
  217. // directed SMI will latch the SMI for the CPU. Then the INIT-SIPI-SIPI
  218. // will launch the CPU into SMM.
  219. //
  220. // (2.2) The OS is malicious.
  221. //
  222. // The hot-added CPU is executing OS code. The directed SMI will pull
  223. // the hot-added CPU into SMM, where it will start spinning on the APIC
  224. // ID gate. The INIT-SIPI-SIPI will be ignored.
  225. //
  226. SendSmiIpi (ApicId);
  227. SendInitSipiSipi (ApicId, PenAddress);
  228. //
  229. // Expose the desired new SMBASE value to the hot-added CPU.
  230. //
  231. Context->NewSmbase = (UINT32)Smbase;
  232. //
  233. // Un-gate SMBASE relocation for the hot-added CPU whose APIC ID is ApicId.
  234. //
  235. ExchangeResult = InterlockedCompareExchange64 (
  236. &Context->ApicIdGate,
  237. MAX_UINT64,
  238. ApicId
  239. );
  240. if (ExchangeResult != MAX_UINT64) {
  241. Status = EFI_PROTOCOL_ERROR;
  242. DEBUG ((
  243. DEBUG_ERROR,
  244. "%a: ApicId=" FMT_APIC_ID " ApicIdGate=0x%Lx: %r\n",
  245. __FUNCTION__,
  246. ApicId,
  247. ExchangeResult,
  248. Status
  249. ));
  250. return Status;
  251. }
  252. //
  253. // Wait until the hot-added CPU is just about to execute RSM.
  254. //
  255. while (Context->AboutToLeaveSmm == 0) {
  256. CpuPause ();
  257. }
  258. //
  259. // Now wait until the hot-added CPU reports back from the pen (or the OS
  260. // attacks the last byte of the reserved page).
  261. //
  262. while (*SmmVacated == 0) {
  263. CpuPause ();
  264. }
  265. Status = EFI_SUCCESS;
  266. return Status;
  267. }