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