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