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