summaryrefslogtreecommitdiff
path: root/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm
blob: 5399b5fa4387d75b8e9018df5b147bcd1e681c59 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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)