aboutsummaryrefslogtreecommitdiffstats
path: root/roms/edk2/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm
diff options
context:
space:
mode:
Diffstat (limited to 'roms/edk2/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm')
-rw-r--r--roms/edk2/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm154
1 files changed, 154 insertions, 0 deletions
diff --git a/roms/edk2/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm b/roms/edk2/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm
new file mode 100644
index 000000000..5399b5fa4
--- /dev/null
+++ b/roms/edk2/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm
@@ -0,0 +1,154 @@
+;------------------------------------------------------------------------------
+; @file
+; Relocate the SMBASE on a hot-added CPU when it services its first SMI.
+;
+; Copyright (c) 2020, Red Hat, Inc.
+;
+; SPDX-License-Identifier: BSD-2-Clause-Patent
+;
+; The routine runs on the hot-added CPU in the following "big real mode",
+; 16-bit environment; per "SMI HANDLER EXECUTION ENVIRONMENT" in the Intel SDM
+; (table "Processor Register Initialization in SMM"):
+;
+; - CS selector: 0x3000 (most significant 16 bits of SMM_DEFAULT_SMBASE).
+;
+; - CS limit: 0xFFFF_FFFF.
+;
+; - CS base: SMM_DEFAULT_SMBASE (0x3_0000).
+;
+; - IP: SMM_HANDLER_OFFSET (0x8000).
+;
+; - ES, SS, DS, FS, GS selectors: 0.
+;
+; - ES, SS, DS, FS, GS limits: 0xFFFF_FFFF.
+;
+; - ES, SS, DS, FS, GS bases: 0.
+;
+; - Operand-size and address-size override prefixes can be used to access the
+; address space beyond 1MB.
+;------------------------------------------------------------------------------
+
+SECTION .data
+BITS 16
+
+;
+; Bring in SMM_DEFAULT_SMBASE from
+; "MdePkg/Include/Register/Intel/SmramSaveStateMap.h".
+;
+SMM_DEFAULT_SMBASE: equ 0x3_0000
+
+;
+; Field offsets in FIRST_SMI_HANDLER_CONTEXT, which resides at
+; SMM_DEFAULT_SMBASE.
+;
+ApicIdGate: equ 0 ; UINT64
+NewSmbase: equ 8 ; UINT32
+AboutToLeaveSmm: equ 12 ; UINT8
+
+;
+; SMRAM Save State Map field offsets, per the AMD (not Intel) layout that QEMU
+; implements. Relative to SMM_DEFAULT_SMBASE.
+;
+SaveStateRevId: equ 0xFEFC ; UINT32
+SaveStateSmbase: equ 0xFEF8 ; UINT32
+SaveStateSmbase64: equ 0xFF00 ; UINT32
+
+;
+; CPUID constants, from "MdePkg/Include/Register/Intel/Cpuid.h".
+;
+CPUID_SIGNATURE: equ 0x00
+CPUID_EXTENDED_TOPOLOGY: equ 0x0B
+CPUID_VERSION_INFO: equ 0x01
+
+GLOBAL ASM_PFX (mFirstSmiHandler) ; UINT8[]
+GLOBAL ASM_PFX (mFirstSmiHandlerSize) ; UINT16
+
+ASM_PFX (mFirstSmiHandler):
+ ;
+ ; Get our own APIC ID first, so we can contend for ApicIdGate.
+ ;
+ ; This basically reimplements GetInitialApicId() from
+ ; "UefiCpuPkg/Library/BaseXApicLib/BaseXApicLib.c".
+ ;
+ mov eax, CPUID_SIGNATURE
+ cpuid
+ cmp eax, CPUID_EXTENDED_TOPOLOGY
+ jb GetApicIdFromVersionInfo
+
+ mov eax, CPUID_EXTENDED_TOPOLOGY
+ mov ecx, 0
+ cpuid
+ test ebx, 0xFFFF
+ jz GetApicIdFromVersionInfo
+
+ ;
+ ; EDX has the APIC ID, save it to ESI.
+ ;
+ mov esi, edx
+ jmp KnockOnGate
+
+GetApicIdFromVersionInfo:
+ mov eax, CPUID_VERSION_INFO
+ cpuid
+ shr ebx, 24
+ ;
+ ; EBX has the APIC ID, save it to ESI.
+ ;
+ mov esi, ebx
+
+KnockOnGate:
+ ;
+ ; See if ApicIdGate shows our own APIC ID. If so, swap it to MAX_UINT64
+ ; (close the gate), and advance. Otherwise, keep knocking.
+ ;
+ ; InterlockedCompareExchange64():
+ ; - Value := &FIRST_SMI_HANDLER_CONTEXT.ApicIdGate
+ ; - CompareValue (EDX:EAX) := APIC ID (from ESI)
+ ; - ExchangeValue (ECX:EBX) := MAX_UINT64
+ ;
+ mov edx, 0
+ mov eax, esi
+ mov ecx, 0xFFFF_FFFF
+ mov ebx, 0xFFFF_FFFF
+ lock cmpxchg8b [ds : dword (SMM_DEFAULT_SMBASE + ApicIdGate)]
+ jz ApicIdMatch
+ pause
+ jmp KnockOnGate
+
+ApicIdMatch:
+ ;
+ ; Update the SMBASE field in the SMRAM Save State Map.
+ ;
+ ; First, calculate the address of the SMBASE field, based on the SMM Revision
+ ; ID; store the result in EBX.
+ ;
+ mov eax, dword [ds : dword (SMM_DEFAULT_SMBASE + SaveStateRevId)]
+ test eax, 0xFFFF
+ jz LegacySaveStateMap
+
+ mov ebx, SMM_DEFAULT_SMBASE + SaveStateSmbase64
+ jmp UpdateSmbase
+
+LegacySaveStateMap:
+ mov ebx, SMM_DEFAULT_SMBASE + SaveStateSmbase
+
+UpdateSmbase:
+ ;
+ ; Load the new SMBASE value into EAX.
+ ;
+ mov eax, dword [ds : dword (SMM_DEFAULT_SMBASE + NewSmbase)]
+ ;
+ ; Save it to the SMBASE field whose address we calculated in EBX.
+ ;
+ mov dword [ds : dword ebx], eax
+ ;
+ ; Set AboutToLeaveSmm.
+ ;
+ mov byte [ds : dword (SMM_DEFAULT_SMBASE + AboutToLeaveSmm)], 1
+ ;
+ ; We're done; leave SMM and continue to the pen.
+ ;
+ rsm
+
+ASM_PFX (mFirstSmiHandlerSize):
+ dw $ - ASM_PFX (mFirstSmiHandler)