PAGE_READ_WRITE + \\r
PAGE_PRESENT)\r
\r
+;\r
+; SEV-ES #VC exception handler support\r
+;\r
+; #VC handler local variable locations\r
+;\r
+%define VC_CPUID_RESULT_EAX 0\r
+%define VC_CPUID_RESULT_EBX 4\r
+%define VC_CPUID_RESULT_ECX 8\r
+%define VC_CPUID_RESULT_EDX 12\r
+%define VC_GHCB_MSR_EDX 16\r
+%define VC_GHCB_MSR_EAX 20\r
+%define VC_CPUID_REQUEST_REGISTER 24\r
+%define VC_CPUID_FUNCTION 28\r
+\r
+; #VC handler total local variable size\r
+;\r
+%define VC_VARIABLE_SIZE 32\r
+\r
+; #VC handler GHCB CPUID request/response protocol values\r
+;\r
+%define GHCB_CPUID_REQUEST 4\r
+%define GHCB_CPUID_RESPONSE 5\r
+%define GHCB_CPUID_REGISTER_SHIFT 30\r
+%define CPUID_INSN_LEN 2\r
+\r
+\r
; Check if Secure Encrypted Virtualization (SEV) feature is enabled\r
;\r
-; If SEV is enabled then EAX will be at least 32\r
+; Modified: EAX, EBX, ECX, EDX, ESP\r
+;\r
+; If SEV is enabled then EAX will be at least 32.\r
; If SEV is disabled then EAX will be zero.\r
;\r
CheckSevFeature:\r
+ ; Set the first byte of the workarea to zero to communicate to the SEC\r
+ ; phase that SEV-ES is not enabled. If SEV-ES is enabled, the CPUID\r
+ ; instruction will trigger a #VC exception where the first byte of the\r
+ ; workarea will be set to one.\r
+ mov byte[SEV_ES_WORK_AREA], 0\r
+\r
+ ;\r
+ ; Set up exception handlers to check for SEV-ES\r
+ ; Load temporary RAM stack based on PCDs (see SevEsIdtVmmComm for\r
+ ; stack usage)\r
+ ; Establish exception handlers\r
+ ;\r
+ mov esp, SEV_ES_VC_TOP_OF_STACK\r
+ mov eax, ADDR_OF(Idtr)\r
+ lidt [cs:eax]\r
+\r
; Check if we have a valid (0x8000_001F) CPUID leaf\r
+ ; CPUID raises a #VC exception if running as an SEV-ES guest\r
mov eax, 0x80000000\r
cpuid\r
\r
jl NoSev\r
\r
; Check for memory encryption feature:\r
- ; CPUID Fn8000_001F[EAX] - Bit 1\r
- ;\r
+ ; CPUID Fn8000_001F[EAX] - Bit 1\r
+ ; CPUID raises a #VC exception if running as an SEV-ES guest\r
mov eax, 0x8000001f\r
cpuid\r
bt eax, 1\r
xor eax, eax\r
\r
SevExit:\r
+ ;\r
+ ; Clear exception handlers and stack\r
+ ;\r
+ push eax\r
+ mov eax, ADDR_OF(IdtrClear)\r
+ lidt [cs:eax]\r
+ pop eax\r
+ mov esp, 0\r
+\r
OneTimeCallRet CheckSevFeature\r
\r
; Check if Secure Encrypted Virtualization - Encrypted State (SEV-ES) feature\r
mov cr3, eax\r
\r
OneTimeCallRet SetCr3ForPageTables64\r
+\r
+;\r
+; Start of #VC exception handling routines\r
+;\r
+\r
+SevEsIdtNotCpuid:\r
+ ;\r
+ ; Use VMGEXIT to request termination.\r
+ ; 1 - #VC was not for CPUID\r
+ ;\r
+ mov eax, 1\r
+ jmp SevEsIdtTerminate\r
+\r
+SevEsIdtNoCpuidResponse:\r
+ ;\r
+ ; Use VMGEXIT to request termination.\r
+ ; 2 - GHCB_CPUID_RESPONSE not received\r
+ ;\r
+ mov eax, 2\r
+\r
+SevEsIdtTerminate:\r
+ ;\r
+ ; Use VMGEXIT to request termination. At this point the reason code is\r
+ ; located in EAX, so shift it left 16 bits to the proper location.\r
+ ;\r
+ ; EAX[11:0] => 0x100 - request termination\r
+ ; EAX[15:12] => 0x1 - OVMF\r
+ ; EAX[23:16] => 0xXX - REASON CODE\r
+ ;\r
+ shl eax, 16\r
+ or eax, 0x1100\r
+ xor edx, edx\r
+ mov ecx, 0xc0010130\r
+ wrmsr\r
+ ;\r
+ ; Issue VMGEXIT - NASM doesn't support the vmmcall instruction in 32-bit\r
+ ; mode, so work around this by temporarily switching to 64-bit mode.\r
+ ;\r
+BITS 64\r
+ rep vmmcall\r
+BITS 32\r
+\r
+ ;\r
+ ; We shouldn't come back from the VMGEXIT, but if we do, just loop.\r
+ ;\r
+SevEsIdtHlt:\r
+ hlt\r
+ jmp SevEsIdtHlt\r
+ iret\r
+\r
+ ;\r
+ ; Total stack usage for the #VC handler is 44 bytes:\r
+ ; - 12 bytes for the exception IRET (after popping error code)\r
+ ; - 32 bytes for the local variables.\r
+ ;\r
+SevEsIdtVmmComm:\r
+ ;\r
+ ; If we're here, then we are an SEV-ES guest and this\r
+ ; was triggered by a CPUID instruction\r
+ ;\r
+ ; Set the first byte of the workarea to one to communicate to the SEC\r
+ ; phase that SEV-ES is enabled.\r
+ mov byte[SEV_ES_WORK_AREA], 1\r
+\r
+ pop ecx ; Error code\r
+ cmp ecx, 0x72 ; Be sure it was CPUID\r
+ jne SevEsIdtNotCpuid\r
+\r
+ ; Set up local variable room on the stack\r
+ ; CPUID function : + 28\r
+ ; CPUID request register : + 24\r
+ ; GHCB MSR (EAX) : + 20\r
+ ; GHCB MSR (EDX) : + 16\r
+ ; CPUID result (EDX) : + 12\r
+ ; CPUID result (ECX) : + 8\r
+ ; CPUID result (EBX) : + 4\r
+ ; CPUID result (EAX) : + 0\r
+ sub esp, VC_VARIABLE_SIZE\r
+\r
+ ; Save the CPUID function being requested\r
+ mov [esp + VC_CPUID_FUNCTION], eax\r
+\r
+ ; The GHCB CPUID protocol uses the following mapping to request\r
+ ; a specific register:\r
+ ; 0 => EAX, 1 => EBX, 2 => ECX, 3 => EDX\r
+ ;\r
+ ; Set EAX as the first register to request. This will also be used as a\r
+ ; loop variable to request all register values (EAX to EDX).\r
+ xor eax, eax\r
+ mov [esp + VC_CPUID_REQUEST_REGISTER], eax\r
+\r
+ ; Save current GHCB MSR value\r
+ mov ecx, 0xc0010130\r
+ rdmsr\r
+ mov [esp + VC_GHCB_MSR_EAX], eax\r
+ mov [esp + VC_GHCB_MSR_EDX], edx\r
+\r
+NextReg:\r
+ ;\r
+ ; Setup GHCB MSR\r
+ ; GHCB_MSR[63:32] = CPUID function\r
+ ; GHCB_MSR[31:30] = CPUID register\r
+ ; GHCB_MSR[11:0] = CPUID request protocol\r
+ ;\r
+ mov eax, [esp + VC_CPUID_REQUEST_REGISTER]\r
+ cmp eax, 4\r
+ jge VmmDone\r
+\r
+ shl eax, GHCB_CPUID_REGISTER_SHIFT\r
+ or eax, GHCB_CPUID_REQUEST\r
+ mov edx, [esp + VC_CPUID_FUNCTION]\r
+ mov ecx, 0xc0010130\r
+ wrmsr\r
+\r
+ ;\r
+ ; Issue VMGEXIT - NASM doesn't support the vmmcall instruction in 32-bit\r
+ ; mode, so work around this by temporarily switching to 64-bit mode.\r
+ ;\r
+BITS 64\r
+ rep vmmcall\r
+BITS 32\r
+\r
+ ;\r
+ ; Read GHCB MSR\r
+ ; GHCB_MSR[63:32] = CPUID register value\r
+ ; GHCB_MSR[31:30] = CPUID register\r
+ ; GHCB_MSR[11:0] = CPUID response protocol\r
+ ;\r
+ mov ecx, 0xc0010130\r
+ rdmsr\r
+ mov ecx, eax\r
+ and ecx, 0xfff\r
+ cmp ecx, GHCB_CPUID_RESPONSE\r
+ jne SevEsIdtNoCpuidResponse\r
+\r
+ ; Save returned value\r
+ shr eax, GHCB_CPUID_REGISTER_SHIFT\r
+ mov [esp + eax * 4], edx\r
+\r
+ ; Next register\r
+ inc word [esp + VC_CPUID_REQUEST_REGISTER]\r
+\r
+ jmp NextReg\r
+\r
+VmmDone:\r
+ ;\r
+ ; At this point we have all CPUID register values. Restore the GHCB MSR,\r
+ ; set the return register values and return.\r
+ ;\r
+ mov eax, [esp + VC_GHCB_MSR_EAX]\r
+ mov edx, [esp + VC_GHCB_MSR_EDX]\r
+ mov ecx, 0xc0010130\r
+ wrmsr\r
+\r
+ mov eax, [esp + VC_CPUID_RESULT_EAX]\r
+ mov ebx, [esp + VC_CPUID_RESULT_EBX]\r
+ mov ecx, [esp + VC_CPUID_RESULT_ECX]\r
+ mov edx, [esp + VC_CPUID_RESULT_EDX]\r
+\r
+ add esp, VC_VARIABLE_SIZE\r
+\r
+ ; Update the EIP value to skip over the now handled CPUID instruction\r
+ ; (the CPUID instruction has a length of 2)\r
+ add word [esp], CPUID_INSN_LEN\r
+ iret\r
+\r
+ALIGN 2\r
+\r
+Idtr:\r
+ dw IDT_END - IDT_BASE - 1 ; Limit\r
+ dd ADDR_OF(IDT_BASE) ; Base\r
+\r
+IdtrClear:\r
+ dw 0 ; Limit\r
+ dd 0 ; Base\r
+\r
+ALIGN 16\r
+\r
+;\r
+; The Interrupt Descriptor Table (IDT)\r
+; This will be used to determine if SEV-ES is enabled. Upon execution\r
+; of the CPUID instruction, a VMM Communication Exception will occur.\r
+; This will tell us if SEV-ES is enabled. We can use the current value\r
+; of the GHCB MSR to determine the SEV attributes.\r
+;\r
+IDT_BASE:\r
+;\r
+; Vectors 0 - 28 (No handlers)\r
+;\r
+%rep 29\r
+ dw 0 ; Offset low bits 15..0\r
+ dw 0x10 ; Selector\r
+ db 0 ; Reserved\r
+ db 0x8E ; Gate Type (IA32_IDT_GATE_TYPE_INTERRUPT_32)\r
+ dw 0 ; Offset high bits 31..16\r
+%endrep\r
+;\r
+; Vector 29 (VMM Communication Exception)\r
+;\r
+ dw (ADDR_OF(SevEsIdtVmmComm) & 0xffff) ; Offset low bits 15..0\r
+ dw 0x10 ; Selector\r
+ db 0 ; Reserved\r
+ db 0x8E ; Gate Type (IA32_IDT_GATE_TYPE_INTERRUPT_32)\r
+ dw (ADDR_OF(SevEsIdtVmmComm) >> 16) ; Offset high bits 31..16\r
+;\r
+; Vectors 30 - 31 (No handlers)\r
+;\r
+%rep 2\r
+ dw 0 ; Offset low bits 15..0\r
+ dw 0x10 ; Selector\r
+ db 0 ; Reserved\r
+ db 0x8E ; Gate Type (IA32_IDT_GATE_TYPE_INTERRUPT_32)\r
+ dw 0 ; Offset high bits 31..16\r
+%endrep\r
+IDT_END:\r