]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm
OvmfPkg/CpuHotplugSmm: introduce First SMI Handler for hot-added CPUs
[mirror_edk2.git] / OvmfPkg / CpuHotplugSmm / FirstSmiHandler.nasm
diff --git a/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm b/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm
new file mode 100644 (file)
index 0000000..5399b5f
--- /dev/null
@@ -0,0 +1,154 @@
+;------------------------------------------------------------------------------\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