OvmfPkg: Extend VmgExitLib to handle #VE exception

RFC: https://bugzilla.tianocore.org/show_bug.cgi?id=3429

The base VmgExitLib library provides a default limited interface to
handle #VE exception. To provide full support, the OVMF version of
VmgExitLib is extended to provide full support of #VE handler.

Cc: Ard Biesheuvel <ardb+tianocore@kernel.org>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Cc: Erdem Aktas <erdemaktas@google.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
Reviewed-by: Jiewen Yao <jiewen.yao@intel.com>
Signed-off-by: Min Xu <min.m.xu@intel.com>
This commit is contained in:
Min Xu 2021-10-30 14:30:15 +08:00 committed by mergify[bot]
parent eddcba40b5
commit daf8f642f3
5 changed files with 741 additions and 1 deletions

View File

@ -25,6 +25,8 @@
VmgExitVcHandler.c
VmgExitVcHandler.h
SecVmgExitVcHandler.c
VmTdExitVeHandler.c
X64/TdVmcallCpuid.nasm
[Packages]
MdePkg/MdePkg.dec
@ -44,4 +46,3 @@
gUefiOvmfPkgTokenSpaceGuid.PcdOvmfSecGhcbBackupSize
gUefiOvmfPkgTokenSpaceGuid.PcdOvmfCpuidBase
gUefiOvmfPkgTokenSpaceGuid.PcdOvmfCpuidSize

View File

@ -0,0 +1,32 @@
/** @file
Copyright (c) 2020 - 2021, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#ifndef VMTD_EXIT_HANDLER_H_
#define VMTD_EXIT_HANDLER_H_
#include <Base.h>
#include <Uefi.h>
/**
This function enable the TD guest to request the VMM to emulate CPUID
operation, especially for non-architectural, CPUID leaves.
@param[in] Eax Main leaf of the CPUID
@param[in] Ecx Sub-leaf of the CPUID
@param[out] Results Returned result of CPUID operation
@return EFI_SUCCESS
**/
EFI_STATUS
EFIAPI
TdVmCallCpuid (
IN UINT64 Eax,
IN UINT64 Ecx,
OUT VOID *Results
);
#endif

View File

@ -0,0 +1,559 @@
/** @file
Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include "VmTdExitHandler.h"
#include <Library/VmgExitLib.h>
#include <Library/BaseMemoryLib.h>
#include <IndustryStandard/Tdx.h>
#include <IndustryStandard/InstructionParsing.h>
typedef union {
struct {
UINT32 Eax;
UINT32 Edx;
} Regs;
UINT64 Val;
} MSR_DATA;
typedef union {
UINT8 Val;
struct {
UINT8 B : 1;
UINT8 X : 1;
UINT8 R : 1;
UINT8 W : 1;
} Bits;
} REX;
typedef union {
UINT8 Val;
struct {
UINT8 Rm : 3;
UINT8 Reg : 3;
UINT8 Mod : 2;
} Bits;
} MODRM;
typedef struct {
UINT64 Regs[4];
} CPUID_DATA;
/**
Handle an CPUID event.
Use the TDVMCALL instruction to handle cpuid #ve
@param[in, out] Regs x64 processor context
@param[in] Veinfo VE Info
@retval 0 Event handled successfully
@return New exception value to propagate
**/
STATIC
UINT64
EFIAPI
CpuIdExit (
IN EFI_SYSTEM_CONTEXT_X64 *Regs,
IN TDCALL_VEINFO_RETURN_DATA *Veinfo
)
{
CPUID_DATA CpuIdData;
UINT64 Status;
Status = TdVmCallCpuid (Regs->Rax, Regs->Rcx, &CpuIdData);
if (Status == 0) {
Regs->Rax = CpuIdData.Regs[0];
Regs->Rbx = CpuIdData.Regs[1];
Regs->Rcx = CpuIdData.Regs[2];
Regs->Rdx = CpuIdData.Regs[3];
}
return Status;
}
/**
Handle an IO event.
Use the TDVMCALL instruction to handle either an IO read or an IO write.
@param[in, out] Regs x64 processor context
@param[in] Veinfo VE Info
@retval 0 Event handled successfully
@return New exception value to propagate
**/
STATIC
UINT64
EFIAPI
IoExit (
IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs,
IN TDCALL_VEINFO_RETURN_DATA *Veinfo
)
{
BOOLEAN Write;
UINTN Size;
UINTN Port;
UINT64 Val;
UINT64 RepCnt;
UINT64 Status;
Val = 0;
Write = Veinfo->ExitQualification.Io.Direction ? FALSE : TRUE;
Size = Veinfo->ExitQualification.Io.Size + 1;
Port = Veinfo->ExitQualification.Io.Port;
if (Veinfo->ExitQualification.Io.String) {
//
// If REP is set, get rep-cnt from Rcx
//
RepCnt = Veinfo->ExitQualification.Io.Rep ? Regs->Rcx : 1;
while (RepCnt) {
Val = 0;
if (Write == TRUE) {
CopyMem (&Val, (VOID *)Regs->Rsi, Size);
Regs->Rsi += Size;
}
Status = TdVmCall (EXIT_REASON_IO_INSTRUCTION, Size, Write, Port, Val, (Write ? NULL : &Val));
if (Status != 0) {
break;
}
if (Write == FALSE) {
CopyMem ((VOID *)Regs->Rdi, &Val, Size);
Regs->Rdi += Size;
}
if (Veinfo->ExitQualification.Io.Rep) {
Regs->Rcx -= 1;
}
RepCnt -= 1;
}
} else {
if (Write == TRUE) {
CopyMem (&Val, (VOID *)&Regs->Rax, Size);
}
Status = TdVmCall (EXIT_REASON_IO_INSTRUCTION, Size, Write, Port, Val, (Write ? NULL : &Val));
if ((Status == 0) && (Write == FALSE)) {
CopyMem ((VOID *)&Regs->Rax, &Val, Size);
}
}
return Status;
}
/**
Handle an READ MSR event.
Use the TDVMCALL instruction to handle msr read
@param[in, out] Regs x64 processor context
@param[in] Veinfo VE Info
@retval 0 Event handled successfully
@return New exception value to propagate
**/
STATIC
UINT64
ReadMsrExit (
IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs,
IN TDCALL_VEINFO_RETURN_DATA *Veinfo
)
{
MSR_DATA Data;
UINT64 Status;
Status = TdVmCall (EXIT_REASON_MSR_READ, Regs->Rcx, 0, 0, 0, &Data);
if (Status == 0) {
Regs->Rax = Data.Regs.Eax;
Regs->Rdx = Data.Regs.Edx;
}
return Status;
}
/**
Handle an WRITE MSR event.
Use the TDVMCALL instruction to handle msr write
@param[in, out] Regs x64 processor context
@param[in] Veinfo VE Info
@retval 0 Event handled successfully
@return New exception value to propagate
**/
STATIC
UINT64
WriteMsrExit (
IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs,
IN TDCALL_VEINFO_RETURN_DATA *Veinfo
)
{
UINT64 Status;
MSR_DATA Data;
Data.Regs.Eax = (UINT32)Regs->Rax;
Data.Regs.Edx = (UINT32)Regs->Rdx;
Status = TdVmCall (EXIT_REASON_MSR_WRITE, Regs->Rcx, Data.Val, 0, 0, NULL);
return Status;
}
STATIC
VOID
EFIAPI
TdxDecodeInstruction (
IN UINT8 *Rip
)
{
UINTN i;
DEBUG ((DEBUG_INFO, "TDX: #TD[EPT] instruction (%p):", Rip));
for (i = 0; i < 15; i++) {
DEBUG ((DEBUG_INFO, "%02x:", Rip[i]));
}
DEBUG ((DEBUG_INFO, "\n"));
}
#define TDX_DECODER_BUG_ON(x) \
if ((x)) { \
TdxDecodeInstruction(Rip); \
TdVmCall(TDVMCALL_HALT, 0, 0, 0, 0, 0); \
}
STATIC
UINT64 *
EFIAPI
GetRegFromContext (
IN EFI_SYSTEM_CONTEXT_X64 *Regs,
IN UINTN RegIndex
)
{
switch (RegIndex) {
case 0: return &Regs->Rax;
break;
case 1: return &Regs->Rcx;
break;
case 2: return &Regs->Rdx;
break;
case 3: return &Regs->Rbx;
break;
case 4: return &Regs->Rsp;
break;
case 5: return &Regs->Rbp;
break;
case 6: return &Regs->Rsi;
break;
case 7: return &Regs->Rdi;
break;
case 8: return &Regs->R8;
break;
case 9: return &Regs->R9;
break;
case 10: return &Regs->R10;
break;
case 11: return &Regs->R11;
break;
case 12: return &Regs->R12;
break;
case 13: return &Regs->R13;
break;
case 14: return &Regs->R14;
break;
case 15: return &Regs->R15;
break;
}
return NULL;
}
/**
Handle an MMIO event.
Use the TDVMCALL instruction to handle either an mmio read or an mmio write.
@param[in, out] Regs x64 processor context
@param[in] Veinfo VE Info
@retval 0 Event handled successfully
@return New exception value to propagate
**/
STATIC
INTN
EFIAPI
MmioExit (
IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs,
IN TDCALL_VEINFO_RETURN_DATA *Veinfo
)
{
UINT64 Status;
UINT32 MmioSize;
UINT32 RegSize;
UINT8 OpCode;
BOOLEAN SeenRex;
UINT64 *Reg;
UINT8 *Rip;
UINT64 Val;
UINT32 OpSize;
MODRM ModRm;
REX Rex;
Rip = (UINT8 *)Regs->Rip;
Val = 0;
Rex.Val = 0;
SeenRex = FALSE;
//
// Default to 32bit transfer
//
OpSize = 4;
do {
OpCode = *Rip++;
if (OpCode == 0x66) {
OpSize = 2;
} else if ((OpCode == 0x64) || (OpCode == 0x65) || (OpCode == 0x67)) {
continue;
} else if ((OpCode >= 0x40) && (OpCode <= 0x4f)) {
SeenRex = TRUE;
Rex.Val = OpCode;
} else {
break;
}
} while (TRUE);
//
// We need to have at least 2 more bytes for this instruction
//
TDX_DECODER_BUG_ON (((UINT64)Rip - Regs->Rip) > 13);
OpCode = *Rip++;
//
// Two-byte opecode, get next byte
//
if (OpCode == 0x0F) {
OpCode = *Rip++;
}
switch (OpCode) {
case 0x88:
case 0x8A:
case 0xB6:
MmioSize = 1;
break;
case 0xB7:
MmioSize = 2;
break;
default:
MmioSize = Rex.Bits.W ? 8 : OpSize;
break;
}
/* Punt on AH/BH/CH/DH unless it shows up. */
ModRm.Val = *Rip++;
TDX_DECODER_BUG_ON (MmioSize == 1 && ModRm.Bits.Reg > 4 && !SeenRex && OpCode != 0xB6);
Reg = GetRegFromContext (Regs, ModRm.Bits.Reg | ((int)Rex.Bits.R << 3));
TDX_DECODER_BUG_ON (!Reg);
if (ModRm.Bits.Rm == 4) {
++Rip; /* SIB byte */
}
if ((ModRm.Bits.Mod == 2) || ((ModRm.Bits.Mod == 0) && (ModRm.Bits.Rm == 5))) {
Rip += 4; /* DISP32 */
} else if (ModRm.Bits.Mod == 1) {
++Rip; /* DISP8 */
}
switch (OpCode) {
case 0x88:
case 0x89:
CopyMem ((void *)&Val, Reg, MmioSize);
Status = TdVmCall (TDVMCALL_MMIO, MmioSize, 1, Veinfo->GuestPA, Val, 0);
break;
case 0xC7:
CopyMem ((void *)&Val, Rip, OpSize);
Status = TdVmCall (TDVMCALL_MMIO, MmioSize, 1, Veinfo->GuestPA, Val, 0);
Rip += OpSize;
default:
//
// 32-bit write registers are zero extended to the full register
// Hence 'MOVZX r[32/64], r/m16' is
// hardcoded to reg size 8, and the straight MOV case has a reg
// size of 8 in the 32-bit read case.
//
switch (OpCode) {
case 0xB6:
RegSize = Rex.Bits.W ? 8 : OpSize;
break;
case 0xB7:
RegSize = 8;
break;
default:
RegSize = MmioSize == 4 ? 8 : MmioSize;
break;
}
Status = TdVmCall (TDVMCALL_MMIO, MmioSize, 0, Veinfo->GuestPA, 0, &Val);
if (Status == 0) {
ZeroMem (Reg, RegSize);
CopyMem (Reg, (void *)&Val, MmioSize);
}
}
if (Status == 0) {
TDX_DECODER_BUG_ON (((UINT64)Rip - Regs->Rip) > 15);
//
// We change instruction length to reflect true size so handler can
// bump rip
//
Veinfo->ExitInstructionLength = (UINT32)((UINT64)Rip - Regs->Rip);
}
return Status;
}
/**
Handle a #VE exception.
Performs the necessary processing to handle a #VE exception.
@param[in, out] ExceptionType Pointer to an EFI_EXCEPTION_TYPE to be set
as value to use on error.
@param[in, out] SystemContext Pointer to EFI_SYSTEM_CONTEXT
@retval EFI_SUCCESS Exception handled
@retval EFI_UNSUPPORTED #VE not supported, (new) exception value to
propagate provided
@retval EFI_PROTOCOL_ERROR #VE handling failed, (new) exception value to
propagate provided
**/
EFI_STATUS
EFIAPI
VmTdExitHandleVe (
IN OUT EFI_EXCEPTION_TYPE *ExceptionType,
IN OUT EFI_SYSTEM_CONTEXT SystemContext
)
{
UINT64 Status;
TD_RETURN_DATA ReturnData;
EFI_SYSTEM_CONTEXT_X64 *Regs;
Regs = SystemContext.SystemContextX64;
Status = TdCall (TDCALL_TDGETVEINFO, 0, 0, 0, &ReturnData);
ASSERT (Status == 0);
if (Status != 0) {
DEBUG ((DEBUG_ERROR, "#VE happened. TDGETVEINFO failed with Status = 0x%llx\n", Status));
TdVmCall (TDVMCALL_HALT, 0, 0, 0, 0, 0);
}
switch (ReturnData.VeInfo.ExitReason) {
case EXIT_REASON_CPUID:
Status = CpuIdExit (Regs, &ReturnData.VeInfo);
DEBUG ((
DEBUG_VERBOSE,
"CPUID #VE happened, ExitReasion is %d, ExitQualification = 0x%x.\n",
ReturnData.VeInfo.ExitReason,
ReturnData.VeInfo.ExitQualification.Val
));
break;
case EXIT_REASON_HLT:
Status = TdVmCall (EXIT_REASON_HLT, 0, 0, 0, 0, 0);
break;
case EXIT_REASON_IO_INSTRUCTION:
Status = IoExit (Regs, &ReturnData.VeInfo);
DEBUG ((
DEBUG_VERBOSE,
"IO_Instruction #VE happened, ExitReasion is %d, ExitQualification = 0x%x.\n",
ReturnData.VeInfo.ExitReason,
ReturnData.VeInfo.ExitQualification.Val
));
break;
case EXIT_REASON_MSR_READ:
Status = ReadMsrExit (Regs, &ReturnData.VeInfo);
DEBUG ((
DEBUG_VERBOSE,
"RDMSR #VE happened, ExitReasion is %d, ExitQualification = 0x%x. Regs->Rcx=0x%llx, Status = 0x%llx\n",
ReturnData.VeInfo.ExitReason,
ReturnData.VeInfo.ExitQualification.Val,
Regs->Rcx,
Status
));
break;
case EXIT_REASON_MSR_WRITE:
Status = WriteMsrExit (Regs, &ReturnData.VeInfo);
DEBUG ((
DEBUG_VERBOSE,
"WRMSR #VE happened, ExitReasion is %d, ExitQualification = 0x%x. Regs->Rcx=0x%llx, Status = 0x%llx\n",
ReturnData.VeInfo.ExitReason,
ReturnData.VeInfo.ExitQualification.Val,
Regs->Rcx,
Status
));
break;
case EXIT_REASON_EPT_VIOLATION:
Status = MmioExit (Regs, &ReturnData.VeInfo);
DEBUG ((
DEBUG_VERBOSE,
"MMIO #VE happened, ExitReasion is %d, ExitQualification = 0x%x.\n",
ReturnData.VeInfo.ExitReason,
ReturnData.VeInfo.ExitQualification.Val
));
break;
case EXIT_REASON_VMCALL:
case EXIT_REASON_MWAIT_INSTRUCTION:
case EXIT_REASON_MONITOR_INSTRUCTION:
case EXIT_REASON_WBINVD:
case EXIT_REASON_RDPMC:
/* Handle as nops. */
break;
default:
DEBUG ((
DEBUG_ERROR,
"Unsupported #VE happened, ExitReason is %d, ExitQualification = 0x%x.\n",
ReturnData.VeInfo.ExitReason,
ReturnData.VeInfo.ExitQualification.Val
));
ASSERT (FALSE);
CpuDeadLoop ();
}
if (Status) {
DEBUG ((
DEBUG_ERROR,
"#VE Error (0x%llx) returned from host, ExitReason is %d, ExitQualification = 0x%x.\n",
Status,
ReturnData.VeInfo.ExitReason,
ReturnData.VeInfo.ExitQualification.Val
));
TdVmCall (TDVMCALL_HALT, 0, 0, 0, 0, 0);
}
SystemContext.SystemContextX64->Rip += ReturnData.VeInfo.ExitInstructionLength;
return EFI_SUCCESS;
}

View File

@ -25,6 +25,8 @@
VmgExitVcHandler.c
VmgExitVcHandler.h
PeiDxeVmgExitVcHandler.c
VmTdExitVeHandler.c
X64/TdVmcallCpuid.nasm
[Packages]
MdePkg/MdePkg.dec

View File

@ -0,0 +1,146 @@
;------------------------------------------------------------------------------
;*
;* Copyright (c) 2020 - 2021, Intel Corporation. All rights reserved.<BR>
;* SPDX-License-Identifier: BSD-2-Clause-Patent
;*
;*
;------------------------------------------------------------------------------
DEFAULT REL
SECTION .text
%define TDVMCALL_EXPOSE_REGS_MASK 0xffec
%define TDVMCALL 0x0
%define EXIT_REASON_CPUID 0xa
%macro tdcall 0
db 0x66,0x0f,0x01,0xcc
%endmacro
%macro tdcall_push_regs 0
push rbp
mov rbp, rsp
push r15
push r14
push r13
push r12
push rbx
push rsi
push rdi
%endmacro
%macro tdcall_pop_regs 0
pop rdi
pop rsi
pop rbx
pop r12
pop r13
pop r14
pop r15
pop rbp
%endmacro
%define number_of_regs_pushed 8
%define number_of_parameters 4
;
; Keep these in sync for push_regs/pop_regs, code below
; uses them to find 5th or greater parameters
;
%define first_variable_on_stack_offset \
((number_of_regs_pushed * 8) + (number_of_parameters * 8) + 8)
%define second_variable_on_stack_offset \
((first_variable_on_stack_offset) + 8)
%macro tdcall_regs_preamble 2
mov rax, %1
xor rcx, rcx
mov ecx, %2
; R10 = 0 (standard TDVMCALL)
xor r10d, r10d
; Zero out unused (for standard TDVMCALL) registers to avoid leaking
; secrets to the VMM.
xor ebx, ebx
xor esi, esi
xor edi, edi
xor edx, edx
xor ebp, ebp
xor r8d, r8d
xor r9d, r9d
xor r14, r14
xor r15, r15
%endmacro
%macro tdcall_regs_postamble 0
xor ebx, ebx
xor esi, esi
xor edi, edi
xor ecx, ecx
xor edx, edx
xor r8d, r8d
xor r9d, r9d
xor r10d, r10d
xor r11d, r11d
%endmacro
;------------------------------------------------------------------------------
; 0 => RAX = TDCALL leaf / TDVMCALL
; M => RCX = TDVMCALL register behavior
; 0xa => R11 = TDVMCALL function / CPUID
; RCX => R12 = p1
; RDX => R13 = p2
;
; UINT64
; EFIAPI
; TdVmCallCpuid (
; UINT64 EaxIn, // Rcx
; UINT64 EcxIn, // Rdx
; UINT64 *Results // R8
; )
global ASM_PFX(TdVmCallCpuid)
ASM_PFX(TdVmCallCpuid):
tdcall_push_regs
mov r11, EXIT_REASON_CPUID
mov r12, rcx
mov r13, rdx
; Save *results pointers
push r8
tdcall_regs_preamble TDVMCALL, TDVMCALL_EXPOSE_REGS_MASK
tdcall
; ignore return data if TDCALL reports failure.
test rax, rax
jnz .no_return_data
; Propagate TDVMCALL success/failure to return value.
mov rax, r10
test rax, rax
jnz .no_return_data
; Retrieve *Results
pop r8
test r8, r8
jz .no_return_data
; Caller pass in buffer so store results r12-r15 contains eax-edx
mov [r8 + 0], r12
mov [r8 + 8], r13
mov [r8 + 16], r14
mov [r8 + 24], r15
.no_return_data:
tdcall_regs_postamble
tdcall_pop_regs
ret