2 SMBASE relocation for hot-plugged CPUs.
4 Copyright (c) 2020, Red Hat, Inc.
6 SPDX-License-Identifier: BSD-2-Clause-Patent
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
17 #include "FirstSmiHandlerContext.h" // FIRST_SMI_HANDLER_CONTEXT
21 extern CONST UINT8 mPostSmmPen
[];
22 extern CONST UINT16 mPostSmmPenSize
;
23 extern CONST UINT8 mFirstSmiHandler
[];
24 extern CONST UINT16 mFirstSmiHandlerSize
;
27 Allocate a non-SMRAM reserved memory page for the Post-SMM Pen for hot-added
30 This function may only be called from the entry point function of the driver.
32 @param[out] PenAddress The address of the allocated (normal RAM) reserved
35 @param[in] BootServices Pointer to the UEFI boot services table. Used for
36 allocating the normal RAM (not SMRAM) reserved page.
38 @retval EFI_SUCCESS Allocation successful.
40 @retval EFI_BAD_BUFFER_SIZE The Post-SMM Pen template is not smaller than
43 @return Error codes propagated from underlying services.
44 DEBUG_ERROR messages have been logged. No
45 resources have been allocated.
48 SmbaseAllocatePostSmmPen (
49 OUT UINT32
*PenAddress
,
50 IN CONST EFI_BOOT_SERVICES
*BootServices
54 EFI_PHYSICAL_ADDRESS Address
;
57 // The pen code must fit in one page, and the last byte must remain free for
58 // signaling the SMM Monarch.
60 if (mPostSmmPenSize
>= EFI_PAGE_SIZE
) {
61 Status
= EFI_BAD_BUFFER_SIZE
;
64 "%a: mPostSmmPenSize=%u: %r\n",
72 Address
= BASE_1MB
- 1;
73 Status
= BootServices
->AllocatePages (
75 EfiReservedMemoryType
,
79 if (EFI_ERROR (Status
)) {
80 DEBUG ((DEBUG_ERROR
, "%a: AllocatePages(): %r\n", __FUNCTION__
, Status
));
84 DEBUG ((DEBUG_INFO
, "%a: Post-SMM Pen at 0x%Lx\n", __FUNCTION__
, Address
));
85 *PenAddress
= (UINT32
)Address
;
90 Copy the Post-SMM Pen template code into the reserved page allocated with
91 SmbaseAllocatePostSmmPen().
93 Note that this effects an "SMRAM to normal RAM" copy.
95 The SMM Monarch is supposed to call this function from the root MMI handler.
97 @param[in] PenAddress The allocation address returned by
98 SmbaseAllocatePostSmmPen().
101 SmbaseReinstallPostSmmPen (
105 CopyMem ((VOID
*)(UINTN
)PenAddress
, mPostSmmPen
, mPostSmmPenSize
);
109 Release the reserved page allocated with SmbaseAllocatePostSmmPen().
111 This function may only be called from the entry point function of the driver,
114 @param[in] PenAddress The allocation address returned by
115 SmbaseAllocatePostSmmPen().
117 @param[in] BootServices Pointer to the UEFI boot services table. Used for
118 releasing the normal RAM (not SMRAM) reserved page.
121 SmbaseReleasePostSmmPen (
122 IN UINT32 PenAddress
,
123 IN CONST EFI_BOOT_SERVICES
*BootServices
126 BootServices
->FreePages (PenAddress
, 1);
130 Place the handler routine for the first SMIs of hot-added CPUs at
131 (SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET).
133 Note that this effects an "SMRAM to SMRAM" copy.
135 Additionally, shut the APIC ID gate in FIRST_SMI_HANDLER_CONTEXT.
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.
141 SmbaseInstallFirstSmiHandler (
145 FIRST_SMI_HANDLER_CONTEXT
*Context
;
148 (VOID
*)(UINTN
)(SMM_DEFAULT_SMBASE
+ SMM_HANDLER_OFFSET
),
153 Context
= (VOID
*)(UINTN
)SMM_DEFAULT_SMBASE
;
154 Context
->ApicIdGate
= MAX_UINT64
;
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().
162 The SMM Monarch is supposed to call this function from the root MMI handler.
164 The SMM Monarch is responsible for calling SmbaseInstallFirstSmiHandler(),
165 SmbaseAllocatePostSmmPen(), and SmbaseReinstallPostSmmPen() before calling
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).
174 @param[in] ApicId The APIC ID of the hot-added CPU whose SMBASE should
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.
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().
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
191 @retval EFI_PROTOCOL_ERROR Synchronization bug encountered around
192 FIRST_SMI_HANDLER_CONTEXT.ApicIdGate.
194 @retval EFI_INVALID_PARAMETER Smbase does not fit in 32 bits. No relocation
205 volatile UINT8
*SmmVacated
;
206 volatile FIRST_SMI_HANDLER_CONTEXT
*Context
;
207 UINT64 ExchangeResult
;
209 if (Smbase
> MAX_UINT32
) {
210 Status
= EFI_INVALID_PARAMETER
;
213 "%a: ApicId=" FMT_APIC_ID
" Smbase=0x%Lx: %r\n",
222 SmmVacated
= (UINT8
*)(UINTN
)PenAddress
+ (EFI_PAGE_SIZE
- 1);
223 Context
= (VOID
*)(UINTN
)SMM_DEFAULT_SMBASE
;
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).
230 Context
->AboutToLeaveSmm
= 0;
233 // Clear the last byte of the reserved page, so we notice when the hot-added
234 // CPU checks back in from the pen.
239 // Boot the hot-added CPU.
241 // There are 2*2 cases to consider:
243 // (1) The CPU was hot-added before the SMI was broadcast.
245 // (1.1) The OS is benign.
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.
251 // (1.2) The OS is malicious.
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.
257 // (2) The CPU was hot-added after the SMI was broadcast.
259 // (2.1) The OS is benign.
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.
265 // (2.2) The OS is malicious.
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.
272 SendInitSipiSipi (ApicId
, PenAddress
);
275 // Expose the desired new SMBASE value to the hot-added CPU.
277 Context
->NewSmbase
= (UINT32
)Smbase
;
280 // Un-gate SMBASE relocation for the hot-added CPU whose APIC ID is ApicId.
282 ExchangeResult
= InterlockedCompareExchange64 (
283 &Context
->ApicIdGate
,
287 if (ExchangeResult
!= MAX_UINT64
) {
288 Status
= EFI_PROTOCOL_ERROR
;
291 "%a: ApicId=" FMT_APIC_ID
" ApicIdGate=0x%Lx: %r\n",
301 // Wait until the hot-added CPU is just about to execute RSM.
303 while (Context
->AboutToLeaveSmm
== 0) {
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).
311 while (*SmmVacated
== 0) {
315 Status
= EFI_SUCCESS
;