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