;------------------------------------------------------------------------------
; @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)
