]>
Commit | Line | Data |
---|---|---|
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 |
21 | extern CONST UINT8 mPostSmmPen[];\r |
22 | extern CONST UINT16 mPostSmmPenSize;\r | |
23 | extern CONST UINT8 mFirstSmiHandler[];\r | |
24 | extern 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 | |
47 | EFI_STATUS\r | |
48 | SmbaseAllocatePostSmmPen (\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 | |
100 | VOID\r | |
101 | SmbaseReinstallPostSmmPen (\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 | |
120 | VOID\r | |
121 | SmbaseReleasePostSmmPen (\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 | |
140 | VOID\r | |
141 | SmbaseInstallFirstSmiHandler (\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 | |
197 | EFI_STATUS\r | |
198 | SmbaseRelocate (\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 |