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
;
62 DEBUG ((DEBUG_ERROR
, "%a: mPostSmmPenSize=%u: %r\n", __FUNCTION__
,
63 mPostSmmPenSize
, Status
));
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
));
75 DEBUG ((DEBUG_INFO
, "%a: Post-SMM Pen at 0x%Lx\n", __FUNCTION__
, Address
));
76 *PenAddress
= (UINT32
)Address
;
81 Copy the Post-SMM Pen template code into the reserved page allocated with
82 SmbaseAllocatePostSmmPen().
84 Note that this effects an "SMRAM to normal RAM" copy.
86 The SMM Monarch is supposed to call this function from the root MMI handler.
88 @param[in] PenAddress The allocation address returned by
89 SmbaseAllocatePostSmmPen().
92 SmbaseReinstallPostSmmPen (
96 CopyMem ((VOID
*)(UINTN
)PenAddress
, mPostSmmPen
, mPostSmmPenSize
);
100 Release the reserved page allocated with SmbaseAllocatePostSmmPen().
102 This function may only be called from the entry point function of the driver,
105 @param[in] PenAddress The allocation address returned by
106 SmbaseAllocatePostSmmPen().
108 @param[in] BootServices Pointer to the UEFI boot services table. Used for
109 releasing the normal RAM (not SMRAM) reserved page.
112 SmbaseReleasePostSmmPen (
113 IN UINT32 PenAddress
,
114 IN CONST EFI_BOOT_SERVICES
*BootServices
117 BootServices
->FreePages (PenAddress
, 1);
121 Place the handler routine for the first SMIs of hot-added CPUs at
122 (SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET).
124 Note that this effects an "SMRAM to SMRAM" copy.
126 Additionally, shut the APIC ID gate in FIRST_SMI_HANDLER_CONTEXT.
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.
132 SmbaseInstallFirstSmiHandler (
136 FIRST_SMI_HANDLER_CONTEXT
*Context
;
138 CopyMem ((VOID
*)(UINTN
)(SMM_DEFAULT_SMBASE
+ SMM_HANDLER_OFFSET
),
139 mFirstSmiHandler
, mFirstSmiHandlerSize
);
141 Context
= (VOID
*)(UINTN
)SMM_DEFAULT_SMBASE
;
142 Context
->ApicIdGate
= MAX_UINT64
;
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().
150 The SMM Monarch is supposed to call this function from the root MMI handler.
152 The SMM Monarch is responsible for calling SmbaseInstallFirstSmiHandler(),
153 SmbaseAllocatePostSmmPen(), and SmbaseReinstallPostSmmPen() before calling
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).
162 @param[in] ApicId The APIC ID of the hot-added CPU whose SMBASE should
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.
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().
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
179 @retval EFI_PROTOCOL_ERROR Synchronization bug encountered around
180 FIRST_SMI_HANDLER_CONTEXT.ApicIdGate.
182 @retval EFI_INVALID_PARAMETER Smbase does not fit in 32 bits. No relocation
193 volatile UINT8
*SmmVacated
;
194 volatile FIRST_SMI_HANDLER_CONTEXT
*Context
;
195 UINT64 ExchangeResult
;
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
));
204 SmmVacated
= (UINT8
*)(UINTN
)PenAddress
+ (EFI_PAGE_SIZE
- 1);
205 Context
= (VOID
*)(UINTN
)SMM_DEFAULT_SMBASE
;
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).
212 Context
->AboutToLeaveSmm
= 0;
215 // Clear the last byte of the reserved page, so we notice when the hot-added
216 // CPU checks back in from the pen.
221 // Boot the hot-added CPU.
223 // There are 2*2 cases to consider:
225 // (1) The CPU was hot-added before the SMI was broadcast.
227 // (1.1) The OS is benign.
229 // The hot-added CPU is in RESET state, with the broadcast SMI pending
230 // for it. The directed SMI below will be ignored (it's idempotent),
231 // and the INIT-SIPI-SIPI will launch the CPU directly into SMM.
233 // (1.2) The OS is malicious.
235 // The hot-added CPU has been booted, by the OS. Thus, the hot-added
236 // CPU is spinning on the APIC ID gate. In that case, both the SMI and
237 // the INIT-SIPI-SIPI below will be ignored.
239 // (2) The CPU was hot-added after the SMI was broadcast.
241 // (2.1) The OS is benign.
243 // The hot-added CPU is in RESET state, with no SMI pending for it. The
244 // directed SMI will latch the SMI for the CPU. Then the INIT-SIPI-SIPI
245 // will launch the CPU into SMM.
247 // (2.2) The OS is malicious.
249 // The hot-added CPU is executing OS code. The directed SMI will pull
250 // the hot-added CPU into SMM, where it will start spinning on the APIC
251 // ID gate. The INIT-SIPI-SIPI will be ignored.
254 SendInitSipiSipi (ApicId
, PenAddress
);
257 // Expose the desired new SMBASE value to the hot-added CPU.
259 Context
->NewSmbase
= (UINT32
)Smbase
;
262 // Un-gate SMBASE relocation for the hot-added CPU whose APIC ID is ApicId.
264 ExchangeResult
= InterlockedCompareExchange64 (&Context
->ApicIdGate
,
266 if (ExchangeResult
!= MAX_UINT64
) {
267 Status
= EFI_PROTOCOL_ERROR
;
268 DEBUG ((DEBUG_ERROR
, "%a: ApicId=" FMT_APIC_ID
" ApicIdGate=0x%Lx: %r\n",
269 __FUNCTION__
, ApicId
, ExchangeResult
, Status
));
274 // Wait until the hot-added CPU is just about to execute RSM.
276 while (Context
->AboutToLeaveSmm
== 0) {
281 // Now wait until the hot-added CPU reports back from the pen (or the OS
282 // attacks the last byte of the reserved page).
284 while (*SmmVacated
== 0) {
288 Status
= EFI_SUCCESS
;