]> git.proxmox.com Git - mirror_edk2.git/blame - OvmfPkg/CpuHotplugSmm/Smbase.c
OvmfPkg/CpuHotplugSmm: fix CPU hotplug race just after SMI broadcast
[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
21extern CONST UINT8 mPostSmmPen[];\r
22extern CONST UINT16 mPostSmmPenSize;\r
51a6fb41
LE
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
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
91VOID\r
92SmbaseReinstallPostSmmPen (\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
111VOID\r
112SmbaseReleasePostSmmPen (\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
131VOID\r
132SmbaseInstallFirstSmiHandler (\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
185EFI_STATUS\r
186SmbaseRelocate (\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