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