]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/ResetVector/Ia32/AmdSev.asm
OvmfPkg/ResetVector: move the GHCB page setup in AmdSev.asm
[mirror_edk2.git] / OvmfPkg / ResetVector / Ia32 / AmdSev.asm
1 ;------------------------------------------------------------------------------
2 ; @file
3 ; Provide the functions to check whether SEV and SEV-ES is enabled.
4 ;
5 ; Copyright (c) 2017 - 2021, Advanced Micro Devices, Inc. All rights reserved.<BR>
6 ; SPDX-License-Identifier: BSD-2-Clause-Patent
7 ;
8 ;------------------------------------------------------------------------------
9
10 BITS 32
11
12 ;
13 ; SEV-ES #VC exception handler support
14 ;
15 ; #VC handler local variable locations
16 ;
17 %define VC_CPUID_RESULT_EAX 0
18 %define VC_CPUID_RESULT_EBX 4
19 %define VC_CPUID_RESULT_ECX 8
20 %define VC_CPUID_RESULT_EDX 12
21 %define VC_GHCB_MSR_EDX 16
22 %define VC_GHCB_MSR_EAX 20
23 %define VC_CPUID_REQUEST_REGISTER 24
24 %define VC_CPUID_FUNCTION 28
25
26 ; #VC handler total local variable size
27 ;
28 %define VC_VARIABLE_SIZE 32
29
30 ; #VC handler GHCB CPUID request/response protocol values
31 ;
32 %define GHCB_CPUID_REQUEST 4
33 %define GHCB_CPUID_RESPONSE 5
34 %define GHCB_CPUID_REGISTER_SHIFT 30
35 %define CPUID_INSN_LEN 2
36
37
38 %define SEV_GHCB_MSR 0xc0010130
39 %define SEV_STATUS_MSR 0xc0010131
40
41 ; The #VC was not for CPUID
42 %define TERM_VC_NOT_CPUID 1
43
44 ; The unexpected response code
45 %define TERM_UNEXPECTED_RESP_CODE 2
46
47 %define PAGE_PRESENT 0x01
48 %define PAGE_READ_WRITE 0x02
49 %define PAGE_USER_SUPERVISOR 0x04
50 %define PAGE_WRITE_THROUGH 0x08
51 %define PAGE_CACHE_DISABLE 0x010
52 %define PAGE_ACCESSED 0x020
53 %define PAGE_DIRTY 0x040
54 %define PAGE_PAT 0x080
55 %define PAGE_GLOBAL 0x0100
56 %define PAGE_2M_MBO 0x080
57 %define PAGE_2M_PAT 0x01000
58
59 %define PAGE_4K_PDE_ATTR (PAGE_ACCESSED + \
60 PAGE_DIRTY + \
61 PAGE_READ_WRITE + \
62 PAGE_PRESENT)
63
64 %define PAGE_PDP_ATTR (PAGE_ACCESSED + \
65 PAGE_READ_WRITE + \
66 PAGE_PRESENT)
67
68
69 ; Macro is used to issue the MSR protocol based VMGEXIT. The caller is
70 ; responsible to populate values in the EDX:EAX registers. After the vmmcall
71 ; returns, it verifies that the response code matches with the expected
72 ; code. If it does not match then terminate the guest. The result of request
73 ; is returned in the EDX:EAX.
74 ;
75 ; args 1:Request code, 2: Response code
76 %macro VmgExit 2
77 ;
78 ; Add request code:
79 ; GHCB_MSR[11:0] = Request code
80 or eax, %1
81
82 mov ecx, SEV_GHCB_MSR
83 wrmsr
84
85 ; Issue VMGEXIT - NASM doesn't support the vmmcall instruction in 32-bit
86 ; mode, so work around this by temporarily switching to 64-bit mode.
87 ;
88 BITS 64
89 rep vmmcall
90 BITS 32
91
92 mov ecx, SEV_GHCB_MSR
93 rdmsr
94
95 ;
96 ; Verify the reponse code, if it does not match then request to terminate
97 ; GHCB_MSR[11:0] = Response code
98 mov ecx, eax
99 and ecx, 0xfff
100 cmp ecx, %2
101 jne SevEsUnexpectedRespTerminate
102 %endmacro
103
104 ; Macro to terminate the guest using the VMGEXIT.
105 ; arg 1: reason code
106 %macro TerminateVmgExit 1
107 mov eax, %1
108 ;
109 ; Use VMGEXIT to request termination. At this point the reason code is
110 ; located in EAX, so shift it left 16 bits to the proper location.
111 ;
112 ; EAX[11:0] => 0x100 - request termination
113 ; EAX[15:12] => 0x1 - OVMF
114 ; EAX[23:16] => 0xXX - REASON CODE
115 ;
116 shl eax, 16
117 or eax, 0x1100
118 xor edx, edx
119 mov ecx, SEV_GHCB_MSR
120 wrmsr
121 ;
122 ; Issue VMGEXIT - NASM doesn't support the vmmcall instruction in 32-bit
123 ; mode, so work around this by temporarily switching to 64-bit mode.
124 ;
125 BITS 64
126 rep vmmcall
127 BITS 32
128
129 ;
130 ; We shouldn't come back from the VMGEXIT, but if we do, just loop.
131 ;
132 %%TerminateHlt:
133 hlt
134 jmp %%TerminateHlt
135 %endmacro
136
137 ; Terminate the guest due to unexpected response code.
138 SevEsUnexpectedRespTerminate:
139 TerminateVmgExit TERM_UNEXPECTED_RESP_CODE
140
141 ; If SEV-ES is enabled then initialize and make the GHCB page shared
142 SevClearPageEncMaskForGhcbPage:
143 ; Check if SEV is enabled
144 cmp byte[WORK_AREA_GUEST_TYPE], 1
145 jnz SevClearPageEncMaskForGhcbPageExit
146
147 ; Check if SEV-ES is enabled
148 cmp byte[SEV_ES_WORK_AREA], 1
149 jnz SevClearPageEncMaskForGhcbPageExit
150
151 ;
152 ; The initial GHCB will live at GHCB_BASE and needs to be un-encrypted.
153 ; This requires the 2MB page for this range be broken down into 512 4KB
154 ; pages. All will be marked encrypted, except for the GHCB.
155 ;
156 mov ecx, (GHCB_BASE >> 21)
157 mov eax, GHCB_PT_ADDR + PAGE_PDP_ATTR
158 mov [ecx * 8 + PT_ADDR (0x2000)], eax
159
160 ;
161 ; Page Table Entries (512 * 4KB entries => 2MB)
162 ;
163 mov ecx, 512
164 pageTableEntries4kLoop:
165 mov eax, ecx
166 dec eax
167 shl eax, 12
168 add eax, GHCB_BASE & 0xFFE0_0000
169 add eax, PAGE_4K_PDE_ATTR
170 mov [ecx * 8 + GHCB_PT_ADDR - 8], eax
171 mov [(ecx * 8 + GHCB_PT_ADDR - 8) + 4], edx
172 loop pageTableEntries4kLoop
173
174 ;
175 ; Clear the encryption bit from the GHCB entry
176 ;
177 mov ecx, (GHCB_BASE & 0x1F_FFFF) >> 12
178 mov [ecx * 8 + GHCB_PT_ADDR + 4], strict dword 0
179
180 mov ecx, GHCB_SIZE / 4
181 xor eax, eax
182 clearGhcbMemoryLoop:
183 mov dword[ecx * 4 + GHCB_BASE - 4], eax
184 loop clearGhcbMemoryLoop
185
186 SevClearPageEncMaskForGhcbPageExit:
187 OneTimeCallRet SevClearPageEncMaskForGhcbPage
188
189 ; Check if SEV is enabled, and get the C-bit mask above 31.
190 ; Modified: EDX
191 ;
192 ; The value is returned in the EDX
193 GetSevCBitMaskAbove31:
194 xor edx, edx
195
196 ; Check if SEV is enabled
197 cmp byte[WORK_AREA_GUEST_TYPE], 1
198 jnz GetSevCBitMaskAbove31Exit
199
200 mov edx, dword[SEV_ES_WORK_AREA_ENC_MASK + 4]
201
202 GetSevCBitMaskAbove31Exit:
203 OneTimeCallRet GetSevCBitMaskAbove31
204
205 ; Check if Secure Encrypted Virtualization (SEV) features are enabled.
206 ;
207 ; Register usage is tight in this routine, so multiple calls for the
208 ; same CPUID and MSR data are performed to keep things simple.
209 ;
210 ; Modified: EAX, EBX, ECX, EDX, ESP
211 ;
212 ; If SEV is enabled then EAX will be at least 32.
213 ; If SEV is disabled then EAX will be zero.
214 ;
215 CheckSevFeatures:
216 ; Set the first byte of the workarea to zero to communicate to the SEC
217 ; phase that SEV-ES is not enabled. If SEV-ES is enabled, the CPUID
218 ; instruction will trigger a #VC exception where the first byte of the
219 ; workarea will be set to one or, if CPUID is not being intercepted,
220 ; the MSR check below will set the first byte of the workarea to one.
221 mov byte[SEV_ES_WORK_AREA], 0
222
223 ;
224 ; Set up exception handlers to check for SEV-ES
225 ; Load temporary RAM stack based on PCDs (see SevEsIdtVmmComm for
226 ; stack usage)
227 ; Establish exception handlers
228 ;
229 mov esp, SEV_ES_VC_TOP_OF_STACK
230 mov eax, ADDR_OF(Idtr)
231 lidt [cs:eax]
232
233 ; Check if we have a valid (0x8000_001F) CPUID leaf
234 ; CPUID raises a #VC exception if running as an SEV-ES guest
235 mov eax, 0x80000000
236 cpuid
237
238 ; This check should fail on Intel or Non SEV AMD CPUs. In future if
239 ; Intel CPUs supports this CPUID leaf then we are guranteed to have exact
240 ; same bit definition.
241 cmp eax, 0x8000001f
242 jl NoSev
243
244 ; Check for SEV memory encryption feature:
245 ; CPUID Fn8000_001F[EAX] - Bit 1
246 ; CPUID raises a #VC exception if running as an SEV-ES guest
247 mov eax, 0x8000001f
248 cpuid
249 bt eax, 1
250 jnc NoSev
251
252 ; Check if SEV memory encryption is enabled
253 ; MSR_0xC0010131 - Bit 0 (SEV enabled)
254 mov ecx, SEV_STATUS_MSR
255 rdmsr
256 bt eax, 0
257 jnc NoSev
258
259 ; Set the work area header to indicate that the SEV is enabled
260 mov byte[WORK_AREA_GUEST_TYPE], 1
261
262 ; Check for SEV-ES memory encryption feature:
263 ; CPUID Fn8000_001F[EAX] - Bit 3
264 ; CPUID raises a #VC exception if running as an SEV-ES guest
265 mov eax, 0x8000001f
266 cpuid
267 bt eax, 3
268 jnc GetSevEncBit
269
270 ; Check if SEV-ES is enabled
271 ; MSR_0xC0010131 - Bit 1 (SEV-ES enabled)
272 mov ecx, SEV_STATUS_MSR
273 rdmsr
274 bt eax, 1
275 jnc GetSevEncBit
276
277 ; Set the first byte of the workarea to one to communicate to the SEC
278 ; phase that SEV-ES is enabled.
279 mov byte[SEV_ES_WORK_AREA], 1
280
281 GetSevEncBit:
282 ; Get pte bit position to enable memory encryption
283 ; CPUID Fn8000_001F[EBX] - Bits 5:0
284 ;
285 and ebx, 0x3f
286 mov eax, ebx
287
288 ; The encryption bit position is always above 31
289 sub ebx, 32
290 jns SevSaveMask
291
292 ; Encryption bit was reported as 31 or below, enter a HLT loop
293 SevEncBitLowHlt:
294 cli
295 hlt
296 jmp SevEncBitLowHlt
297
298 SevSaveMask:
299 xor edx, edx
300 bts edx, ebx
301
302 mov dword[SEV_ES_WORK_AREA_ENC_MASK], 0
303 mov dword[SEV_ES_WORK_AREA_ENC_MASK + 4], edx
304 jmp SevExit
305
306 NoSev:
307 ;
308 ; Perform an SEV-ES sanity check by seeing if a #VC exception occurred.
309 ;
310 cmp byte[SEV_ES_WORK_AREA], 0
311 jz NoSevPass
312
313 ;
314 ; A #VC was received, yet CPUID indicates no SEV-ES support, something
315 ; isn't right.
316 ;
317 NoSevEsVcHlt:
318 cli
319 hlt
320 jmp NoSevEsVcHlt
321
322 NoSevPass:
323 xor eax, eax
324
325 SevExit:
326 ;
327 ; Clear exception handlers and stack
328 ;
329 push eax
330 mov eax, ADDR_OF(IdtrClear)
331 lidt [cs:eax]
332 pop eax
333 mov esp, 0
334
335 OneTimeCallRet CheckSevFeatures
336
337 ; Start of #VC exception handling routines
338 ;
339
340 SevEsIdtNotCpuid:
341 TerminateVmgExit TERM_VC_NOT_CPUID
342 iret
343
344 ;
345 ; Total stack usage for the #VC handler is 44 bytes:
346 ; - 12 bytes for the exception IRET (after popping error code)
347 ; - 32 bytes for the local variables.
348 ;
349 SevEsIdtVmmComm:
350 ;
351 ; If we're here, then we are an SEV-ES guest and this
352 ; was triggered by a CPUID instruction
353 ;
354 ; Set the first byte of the workarea to one to communicate that
355 ; a #VC was taken.
356 mov byte[SEV_ES_WORK_AREA], 1
357
358 pop ecx ; Error code
359 cmp ecx, 0x72 ; Be sure it was CPUID
360 jne SevEsIdtNotCpuid
361
362 ; Set up local variable room on the stack
363 ; CPUID function : + 28
364 ; CPUID request register : + 24
365 ; GHCB MSR (EAX) : + 20
366 ; GHCB MSR (EDX) : + 16
367 ; CPUID result (EDX) : + 12
368 ; CPUID result (ECX) : + 8
369 ; CPUID result (EBX) : + 4
370 ; CPUID result (EAX) : + 0
371 sub esp, VC_VARIABLE_SIZE
372
373 ; Save the CPUID function being requested
374 mov [esp + VC_CPUID_FUNCTION], eax
375
376 ; The GHCB CPUID protocol uses the following mapping to request
377 ; a specific register:
378 ; 0 => EAX, 1 => EBX, 2 => ECX, 3 => EDX
379 ;
380 ; Set EAX as the first register to request. This will also be used as a
381 ; loop variable to request all register values (EAX to EDX).
382 xor eax, eax
383 mov [esp + VC_CPUID_REQUEST_REGISTER], eax
384
385 ; Save current GHCB MSR value
386 mov ecx, SEV_GHCB_MSR
387 rdmsr
388 mov [esp + VC_GHCB_MSR_EAX], eax
389 mov [esp + VC_GHCB_MSR_EDX], edx
390
391 NextReg:
392 ;
393 ; Setup GHCB MSR
394 ; GHCB_MSR[63:32] = CPUID function
395 ; GHCB_MSR[31:30] = CPUID register
396 ; GHCB_MSR[11:0] = CPUID request protocol
397 ;
398 mov eax, [esp + VC_CPUID_REQUEST_REGISTER]
399 cmp eax, 4
400 jge VmmDone
401
402 shl eax, GHCB_CPUID_REGISTER_SHIFT
403 mov edx, [esp + VC_CPUID_FUNCTION]
404
405 VmgExit GHCB_CPUID_REQUEST, GHCB_CPUID_RESPONSE
406
407 ;
408 ; Response GHCB MSR
409 ; GHCB_MSR[63:32] = CPUID register value
410 ; GHCB_MSR[31:30] = CPUID register
411 ; GHCB_MSR[11:0] = CPUID response protocol
412 ;
413
414 ; Save returned value
415 shr eax, GHCB_CPUID_REGISTER_SHIFT
416 mov [esp + eax * 4], edx
417
418 ; Next register
419 inc word [esp + VC_CPUID_REQUEST_REGISTER]
420
421 jmp NextReg
422
423 VmmDone:
424 ;
425 ; At this point we have all CPUID register values. Restore the GHCB MSR,
426 ; set the return register values and return.
427 ;
428 mov eax, [esp + VC_GHCB_MSR_EAX]
429 mov edx, [esp + VC_GHCB_MSR_EDX]
430 mov ecx, SEV_GHCB_MSR
431 wrmsr
432
433 mov eax, [esp + VC_CPUID_RESULT_EAX]
434 mov ebx, [esp + VC_CPUID_RESULT_EBX]
435 mov ecx, [esp + VC_CPUID_RESULT_ECX]
436 mov edx, [esp + VC_CPUID_RESULT_EDX]
437
438 add esp, VC_VARIABLE_SIZE
439
440 ; Update the EIP value to skip over the now handled CPUID instruction
441 ; (the CPUID instruction has a length of 2)
442 add word [esp], CPUID_INSN_LEN
443 iret
444
445 ALIGN 2
446
447 Idtr:
448 dw IDT_END - IDT_BASE - 1 ; Limit
449 dd ADDR_OF(IDT_BASE) ; Base
450
451 IdtrClear:
452 dw 0 ; Limit
453 dd 0 ; Base
454
455 ALIGN 16
456
457 ;
458 ; The Interrupt Descriptor Table (IDT)
459 ; This will be used to determine if SEV-ES is enabled. Upon execution
460 ; of the CPUID instruction, a VMM Communication Exception will occur.
461 ; This will tell us if SEV-ES is enabled. We can use the current value
462 ; of the GHCB MSR to determine the SEV attributes.
463 ;
464 IDT_BASE:
465 ;
466 ; Vectors 0 - 28 (No handlers)
467 ;
468 %rep 29
469 dw 0 ; Offset low bits 15..0
470 dw 0x10 ; Selector
471 db 0 ; Reserved
472 db 0x8E ; Gate Type (IA32_IDT_GATE_TYPE_INTERRUPT_32)
473 dw 0 ; Offset high bits 31..16
474 %endrep
475 ;
476 ; Vector 29 (VMM Communication Exception)
477 ;
478 dw (ADDR_OF(SevEsIdtVmmComm) & 0xffff) ; Offset low bits 15..0
479 dw 0x10 ; Selector
480 db 0 ; Reserved
481 db 0x8E ; Gate Type (IA32_IDT_GATE_TYPE_INTERRUPT_32)
482 dw (ADDR_OF(SevEsIdtVmmComm) >> 16) ; Offset high bits 31..16
483 ;
484 ; Vectors 30 - 31 (No handlers)
485 ;
486 %rep 2
487 dw 0 ; Offset low bits 15..0
488 dw 0x10 ; Selector
489 db 0 ; Reserved
490 db 0x8E ; Gate Type (IA32_IDT_GATE_TYPE_INTERRUPT_32)
491 dw 0 ; Offset high bits 31..16
492 %endrep
493 IDT_END: