| /** @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; | |
| } |