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