/** @file X64 #VC Exception Handler functon. Copyright (C) 2020, Advanced Micro Devices, Inc. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include // // Instruction execution mode definition // typedef enum { LongMode64Bit = 0, LongModeCompat32Bit, LongModeCompat16Bit, } SEV_ES_INSTRUCTION_MODE; // // Instruction size definition (for operand and address) // typedef enum { Size8Bits = 0, Size16Bits, Size32Bits, Size64Bits, } SEV_ES_INSTRUCTION_SIZE; // // Intruction segment definition // typedef enum { SegmentEs = 0, SegmentCs, SegmentSs, SegmentDs, SegmentFs, SegmentGs, } SEV_ES_INSTRUCTION_SEGMENT; // // Instruction rep function definition // typedef enum { RepNone = 0, RepZ, RepNZ, } SEV_ES_INSTRUCTION_REP; typedef struct { UINT8 Rm; UINT8 Reg; UINT8 Mod; } SEV_ES_INSTRUCTION_MODRM_EXT; typedef struct { UINT8 Base; UINT8 Index; UINT8 Scale; } SEV_ES_INSTRUCTION_SIB_EXT; // // Instruction opcode definition // typedef struct { SEV_ES_INSTRUCTION_MODRM_EXT ModRm; SEV_ES_INSTRUCTION_SIB_EXT Sib; UINTN RegData; UINTN RmData; } SEV_ES_INSTRUCTION_OPCODE_EXT; // // Instruction parsing context definition // typedef struct { GHCB *Ghcb; SEV_ES_INSTRUCTION_MODE Mode; SEV_ES_INSTRUCTION_SIZE DataSize; SEV_ES_INSTRUCTION_SIZE AddrSize; BOOLEAN SegmentSpecified; SEV_ES_INSTRUCTION_SEGMENT Segment; SEV_ES_INSTRUCTION_REP RepMode; UINT8 *Begin; UINT8 *End; UINT8 *Prefixes; UINT8 *OpCodes; UINT8 *Displacement; UINT8 *Immediate; INSTRUCTION_REX_PREFIX RexPrefix; BOOLEAN ModRmPresent; INSTRUCTION_MODRM ModRm; BOOLEAN SibPresent; INSTRUCTION_SIB Sib; UINTN PrefixSize; UINTN OpCodeSize; UINTN DisplacementSize; UINTN ImmediateSize; SEV_ES_INSTRUCTION_OPCODE_EXT Ext; } SEV_ES_INSTRUCTION_DATA; // // Non-automatic Exit function prototype // typedef UINT64 (*NAE_EXIT) ( GHCB *Ghcb, EFI_SYSTEM_CONTEXT_X64 *Regs, SEV_ES_INSTRUCTION_DATA *InstructionData ); // // Per-CPU data mapping structure // typedef struct { BOOLEAN Dr7Cached; UINT64 Dr7; } SEV_ES_PER_CPU_DATA; /** Return a pointer to the contents of the specified register. Based upon the input register, return a pointer to the registers contents in the x86 processor context. @param[in] Regs x64 processor context @param[in] Register Register to obtain pointer for @return Pointer to the contents of the requested register **/ STATIC UINT64 * GetRegisterPointer ( IN EFI_SYSTEM_CONTEXT_X64 *Regs, IN UINT8 Register ) { UINT64 *Reg; switch (Register) { case 0: Reg = &Regs->Rax; break; case 1: Reg = &Regs->Rcx; break; case 2: Reg = &Regs->Rdx; break; case 3: Reg = &Regs->Rbx; break; case 4: Reg = &Regs->Rsp; break; case 5: Reg = &Regs->Rbp; break; case 6: Reg = &Regs->Rsi; break; case 7: Reg = &Regs->Rdi; break; case 8: Reg = &Regs->R8; break; case 9: Reg = &Regs->R9; break; case 10: Reg = &Regs->R10; break; case 11: Reg = &Regs->R11; break; case 12: Reg = &Regs->R12; break; case 13: Reg = &Regs->R13; break; case 14: Reg = &Regs->R14; break; case 15: Reg = &Regs->R15; break; default: Reg = NULL; } ASSERT (Reg != NULL); return Reg; } /** Update the instruction parsing context for displacement bytes. @param[in, out] InstructionData Instruction parsing context @param[in] Size The instruction displacement size **/ STATIC VOID UpdateForDisplacement ( IN OUT SEV_ES_INSTRUCTION_DATA *InstructionData, IN UINTN Size ) { InstructionData->DisplacementSize = Size; InstructionData->Immediate += Size; InstructionData->End += Size; } /** Determine if an instruction address if RIP relative. Examine the instruction parsing context to determine if the address offset is relative to the instruction pointer. @param[in] InstructionData Instruction parsing context @retval TRUE Instruction addressing is RIP relative @retval FALSE Instruction addressing is not RIP relative **/ STATIC BOOLEAN IsRipRelative ( IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { SEV_ES_INSTRUCTION_OPCODE_EXT *Ext; Ext = &InstructionData->Ext; return ((InstructionData->Mode == LongMode64Bit) && (Ext->ModRm.Mod == 0) && (Ext->ModRm.Rm == 5) && (InstructionData->SibPresent == FALSE)); } /** Return the effective address of a memory operand. Examine the instruction parsing context to obtain the effective memory address of a memory operand. @param[in] Regs x64 processor context @param[in] InstructionData Instruction parsing context @return The memory operand effective address **/ STATIC UINT64 GetEffectiveMemoryAddress ( IN EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { SEV_ES_INSTRUCTION_OPCODE_EXT *Ext; UINT64 EffectiveAddress; Ext = &InstructionData->Ext; EffectiveAddress = 0; if (IsRipRelative (InstructionData)) { // // RIP-relative displacement is a 32-bit signed value // INT32 RipRelative; RipRelative = *(INT32 *) InstructionData->Displacement; UpdateForDisplacement (InstructionData, 4); // // Negative displacement is handled by standard UINT64 wrap-around. // return Regs->Rip + (UINT64) RipRelative; } switch (Ext->ModRm.Mod) { case 1: UpdateForDisplacement (InstructionData, 1); EffectiveAddress += (UINT64) (*(INT8 *) (InstructionData->Displacement)); break; case 2: switch (InstructionData->AddrSize) { case Size16Bits: UpdateForDisplacement (InstructionData, 2); EffectiveAddress += (UINT64) (*(INT16 *) (InstructionData->Displacement)); break; default: UpdateForDisplacement (InstructionData, 4); EffectiveAddress += (UINT64) (*(INT32 *) (InstructionData->Displacement)); break; } break; } if (InstructionData->SibPresent) { INT64 Displacement; if (Ext->Sib.Index != 4) { CopyMem ( &Displacement, GetRegisterPointer (Regs, Ext->Sib.Index), sizeof (Displacement) ); Displacement *= (INT64)(1 << Ext->Sib.Scale); // // Negative displacement is handled by standard UINT64 wrap-around. // EffectiveAddress += (UINT64) Displacement; } if ((Ext->Sib.Base != 5) || Ext->ModRm.Mod) { EffectiveAddress += *GetRegisterPointer (Regs, Ext->Sib.Base); } else { UpdateForDisplacement (InstructionData, 4); EffectiveAddress += (UINT64) (*(INT32 *) (InstructionData->Displacement)); } } else { EffectiveAddress += *GetRegisterPointer (Regs, Ext->ModRm.Rm); } return EffectiveAddress; } /** Decode a ModRM byte. Examine the instruction parsing context to decode a ModRM byte and the SIB byte, if present. @param[in] Regs x64 processor context @param[in, out] InstructionData Instruction parsing context **/ STATIC VOID DecodeModRm ( IN EFI_SYSTEM_CONTEXT_X64 *Regs, IN OUT SEV_ES_INSTRUCTION_DATA *InstructionData ) { SEV_ES_INSTRUCTION_OPCODE_EXT *Ext; INSTRUCTION_REX_PREFIX *RexPrefix; INSTRUCTION_MODRM *ModRm; INSTRUCTION_SIB *Sib; RexPrefix = &InstructionData->RexPrefix; Ext = &InstructionData->Ext; ModRm = &InstructionData->ModRm; Sib = &InstructionData->Sib; InstructionData->ModRmPresent = TRUE; ModRm->Uint8 = *(InstructionData->End); InstructionData->Displacement++; InstructionData->Immediate++; InstructionData->End++; Ext->ModRm.Mod = ModRm->Bits.Mod; Ext->ModRm.Reg = (RexPrefix->Bits.BitR << 3) | ModRm->Bits.Reg; Ext->ModRm.Rm = (RexPrefix->Bits.BitB << 3) | ModRm->Bits.Rm; Ext->RegData = *GetRegisterPointer (Regs, Ext->ModRm.Reg); if (Ext->ModRm.Mod == 3) { Ext->RmData = *GetRegisterPointer (Regs, Ext->ModRm.Rm); } else { if (ModRm->Bits.Rm == 4) { InstructionData->SibPresent = TRUE; Sib->Uint8 = *(InstructionData->End); InstructionData->Displacement++; InstructionData->Immediate++; InstructionData->End++; Ext->Sib.Scale = Sib->Bits.Scale; Ext->Sib.Index = (RexPrefix->Bits.BitX << 3) | Sib->Bits.Index; Ext->Sib.Base = (RexPrefix->Bits.BitB << 3) | Sib->Bits.Base; } Ext->RmData = GetEffectiveMemoryAddress (Regs, InstructionData); } } /** Decode instruction prefixes. Parse the instruction data to track the instruction prefixes that have been used. @param[in] Regs x64 processor context @param[in, out] InstructionData Instruction parsing context **/ STATIC VOID DecodePrefixes ( IN EFI_SYSTEM_CONTEXT_X64 *Regs, IN OUT SEV_ES_INSTRUCTION_DATA *InstructionData ) { SEV_ES_INSTRUCTION_MODE Mode; SEV_ES_INSTRUCTION_SIZE ModeDataSize; SEV_ES_INSTRUCTION_SIZE ModeAddrSize; UINT8 *Byte; // // Always in 64-bit mode // Mode = LongMode64Bit; ModeDataSize = Size32Bits; ModeAddrSize = Size64Bits; InstructionData->Mode = Mode; InstructionData->DataSize = ModeDataSize; InstructionData->AddrSize = ModeAddrSize; InstructionData->Prefixes = InstructionData->Begin; Byte = InstructionData->Prefixes; for ( ; ; Byte++, InstructionData->PrefixSize++) { // // Check the 0x40 to 0x4F range using an if statement here since some // compilers don't like the "case 0x40 ... 0x4F:" syntax. This avoids // 16 case statements below. // if ((*Byte >= REX_PREFIX_START) && (*Byte <= REX_PREFIX_STOP)) { InstructionData->RexPrefix.Uint8 = *Byte; if ((*Byte & REX_64BIT_OPERAND_SIZE_MASK) != 0) { InstructionData->DataSize = Size64Bits; } continue; } switch (*Byte) { case OVERRIDE_SEGMENT_CS: case OVERRIDE_SEGMENT_DS: case OVERRIDE_SEGMENT_ES: case OVERRIDE_SEGMENT_SS: if (Mode != LongMode64Bit) { InstructionData->SegmentSpecified = TRUE; InstructionData->Segment = (*Byte >> 3) & 3; } break; case OVERRIDE_SEGMENT_FS: case OVERRIDE_SEGMENT_GS: InstructionData->SegmentSpecified = TRUE; InstructionData->Segment = *Byte & 7; break; case OVERRIDE_OPERAND_SIZE: if (InstructionData->RexPrefix.Uint8 == 0) { InstructionData->DataSize = (Mode == LongMode64Bit) ? Size16Bits : (Mode == LongModeCompat32Bit) ? Size16Bits : (Mode == LongModeCompat16Bit) ? Size32Bits : 0; } break; case OVERRIDE_ADDRESS_SIZE: InstructionData->AddrSize = (Mode == LongMode64Bit) ? Size32Bits : (Mode == LongModeCompat32Bit) ? Size16Bits : (Mode == LongModeCompat16Bit) ? Size32Bits : 0; break; case LOCK_PREFIX: break; case REPZ_PREFIX: InstructionData->RepMode = RepZ; break; case REPNZ_PREFIX: InstructionData->RepMode = RepNZ; break; default: InstructionData->OpCodes = Byte; InstructionData->OpCodeSize = (*Byte == TWO_BYTE_OPCODE_ESCAPE) ? 2 : 1; InstructionData->End = Byte + InstructionData->OpCodeSize; InstructionData->Displacement = InstructionData->End; InstructionData->Immediate = InstructionData->End; return; } } } /** Determine instruction length Return the total length of the parsed instruction. @param[in] InstructionData Instruction parsing context @return Length of parsed instruction **/ STATIC UINT64 InstructionLength ( IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { return (UINT64) (InstructionData->End - InstructionData->Begin); } /** Initialize the instruction parsing context. Initialize the instruction parsing context, which includes decoding the instruction prefixes. @param[in, out] InstructionData Instruction parsing context @param[in] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in] Regs x64 processor context **/ STATIC VOID InitInstructionData ( IN OUT SEV_ES_INSTRUCTION_DATA *InstructionData, IN GHCB *Ghcb, IN EFI_SYSTEM_CONTEXT_X64 *Regs ) { SetMem (InstructionData, sizeof (*InstructionData), 0); InstructionData->Ghcb = Ghcb; InstructionData->Begin = (UINT8 *) Regs->Rip; InstructionData->End = (UINT8 *) Regs->Rip; DecodePrefixes (Regs, InstructionData); } /** Report an unsupported event to the hypervisor Use the VMGEXIT support to report an unsupported event to the hypervisor. @param[in] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in] Regs x64 processor context @param[in] InstructionData Instruction parsing context @return New exception value to propagate **/ STATIC UINT64 UnsupportedExit ( IN GHCB *Ghcb, IN EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 Status; Status = VmgExit (Ghcb, SVM_EXIT_UNSUPPORTED, Regs->ExceptionData, 0); if (Status == 0) { GHCB_EVENT_INJECTION Event; Event.Uint64 = 0; Event.Elements.Vector = GP_EXCEPTION; Event.Elements.Type = GHCB_EVENT_INJECTION_TYPE_EXCEPTION; Event.Elements.Valid = 1; Status = Event.Uint64; } return Status; } /** Handle an MMIO event. Use the VMGEXIT instruction to handle either an MMIO read or an MMIO write. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in, out] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 MmioExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN OUT SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 ExitInfo1, ExitInfo2, Status; UINTN Bytes; UINT64 *Register; UINT8 OpCode, SignByte; Bytes = 0; OpCode = *(InstructionData->OpCodes); if (OpCode == TWO_BYTE_OPCODE_ESCAPE) { OpCode = *(InstructionData->OpCodes + 1); } switch (OpCode) { // // MMIO write (MOV reg/memX, regX) // case 0x88: Bytes = 1; // // fall through // case 0x89: DecodeModRm (Regs, InstructionData); Bytes = ((Bytes != 0) ? Bytes : (InstructionData->DataSize == Size16Bits) ? 2 : (InstructionData->DataSize == Size32Bits) ? 4 : (InstructionData->DataSize == Size64Bits) ? 8 : 0); if (InstructionData->Ext.ModRm.Mod == 3) { // // NPF on two register operands??? // return UnsupportedExit (Ghcb, Regs, InstructionData); } ExitInfo1 = InstructionData->Ext.RmData; ExitInfo2 = Bytes; CopyMem (Ghcb->SharedBuffer, &InstructionData->Ext.RegData, Bytes); Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer; VmgSetOffsetValid (Ghcb, GhcbSwScratch); Status = VmgExit (Ghcb, SVM_EXIT_MMIO_WRITE, ExitInfo1, ExitInfo2); if (Status != 0) { return Status; } break; // // MMIO write (MOV reg/memX, immX) // case 0xC6: Bytes = 1; // // fall through // case 0xC7: DecodeModRm (Regs, InstructionData); Bytes = ((Bytes != 0) ? Bytes : (InstructionData->DataSize == Size16Bits) ? 2 : (InstructionData->DataSize == Size32Bits) ? 4 : 0); InstructionData->ImmediateSize = Bytes; InstructionData->End += Bytes; ExitInfo1 = InstructionData->Ext.RmData; ExitInfo2 = Bytes; CopyMem (Ghcb->SharedBuffer, InstructionData->Immediate, Bytes); Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer; VmgSetOffsetValid (Ghcb, GhcbSwScratch); Status = VmgExit (Ghcb, SVM_EXIT_MMIO_WRITE, ExitInfo1, ExitInfo2); if (Status != 0) { return Status; } break; // // MMIO read (MOV regX, reg/memX) // case 0x8A: Bytes = 1; // // fall through // case 0x8B: DecodeModRm (Regs, InstructionData); Bytes = ((Bytes != 0) ? Bytes : (InstructionData->DataSize == Size16Bits) ? 2 : (InstructionData->DataSize == Size32Bits) ? 4 : (InstructionData->DataSize == Size64Bits) ? 8 : 0); if (InstructionData->Ext.ModRm.Mod == 3) { // // NPF on two register operands??? // return UnsupportedExit (Ghcb, Regs, InstructionData); } ExitInfo1 = InstructionData->Ext.RmData; ExitInfo2 = Bytes; Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer; VmgSetOffsetValid (Ghcb, GhcbSwScratch); Status = VmgExit (Ghcb, SVM_EXIT_MMIO_READ, ExitInfo1, ExitInfo2); if (Status != 0) { return Status; } Register = GetRegisterPointer (Regs, InstructionData->Ext.ModRm.Reg); if (Bytes == 4) { // // Zero-extend for 32-bit operation // *Register = 0; } CopyMem (Register, Ghcb->SharedBuffer, Bytes); break; // // MMIO read w/ zero-extension ((MOVZX regX, reg/memX) // case 0xB6: Bytes = 1; // // fall through // case 0xB7: Bytes = (Bytes != 0) ? Bytes : 2; ExitInfo1 = InstructionData->Ext.RmData; ExitInfo2 = Bytes; Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer; VmgSetOffsetValid (Ghcb, GhcbSwScratch); Status = VmgExit (Ghcb, SVM_EXIT_MMIO_READ, ExitInfo1, ExitInfo2); if (Status != 0) { return Status; } Register = GetRegisterPointer (Regs, InstructionData->Ext.ModRm.Reg); SetMem (Register, InstructionData->DataSize, 0); CopyMem (Register, Ghcb->SharedBuffer, Bytes); break; // // MMIO read w/ sign-extension (MOVSX regX, reg/memX) // case 0xBE: Bytes = 1; // // fall through // case 0xBF: Bytes = (Bytes != 0) ? Bytes : 2; ExitInfo1 = InstructionData->Ext.RmData; ExitInfo2 = Bytes; Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer; VmgSetOffsetValid (Ghcb, GhcbSwScratch); Status = VmgExit (Ghcb, SVM_EXIT_MMIO_READ, ExitInfo1, ExitInfo2); if (Status != 0) { return Status; } if (Bytes == 1) { UINT8 *Data; Data = (UINT8 *) Ghcb->SharedBuffer; SignByte = ((*Data & BIT7) != 0) ? 0xFF : 0x00; } else { UINT16 *Data; Data = (UINT16 *) Ghcb->SharedBuffer; SignByte = ((*Data & BIT15) != 0) ? 0xFF : 0x00; } Register = GetRegisterPointer (Regs, InstructionData->Ext.ModRm.Reg); SetMem (Register, InstructionData->DataSize, SignByte); CopyMem (Register, Ghcb->SharedBuffer, Bytes); break; default: Status = GP_EXCEPTION; ASSERT (FALSE); } return Status; } /** Handle a MWAIT event. Use the VMGEXIT instruction to handle a MWAIT event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 MwaitExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { DecodeModRm (Regs, InstructionData); Ghcb->SaveArea.Rax = Regs->Rax; VmgSetOffsetValid (Ghcb, GhcbRax); Ghcb->SaveArea.Rcx = Regs->Rcx; VmgSetOffsetValid (Ghcb, GhcbRcx); return VmgExit (Ghcb, SVM_EXIT_MWAIT, 0, 0); } /** Handle a MONITOR event. Use the VMGEXIT instruction to handle a MONITOR event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 MonitorExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { DecodeModRm (Regs, InstructionData); Ghcb->SaveArea.Rax = Regs->Rax; // Identity mapped, so VA = PA VmgSetOffsetValid (Ghcb, GhcbRax); Ghcb->SaveArea.Rcx = Regs->Rcx; VmgSetOffsetValid (Ghcb, GhcbRcx); Ghcb->SaveArea.Rdx = Regs->Rdx; VmgSetOffsetValid (Ghcb, GhcbRdx); return VmgExit (Ghcb, SVM_EXIT_MONITOR, 0, 0); } /** Handle a WBINVD event. Use the VMGEXIT instruction to handle a WBINVD event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 WbinvdExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { return VmgExit (Ghcb, SVM_EXIT_WBINVD, 0, 0); } /** Handle a RDTSCP event. Use the VMGEXIT instruction to handle a RDTSCP event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 RdtscpExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 Status; DecodeModRm (Regs, InstructionData); Status = VmgExit (Ghcb, SVM_EXIT_RDTSCP, 0, 0); if (Status != 0) { return Status; } if (!VmgIsOffsetValid (Ghcb, GhcbRax) || !VmgIsOffsetValid (Ghcb, GhcbRcx) || !VmgIsOffsetValid (Ghcb, GhcbRdx)) { return UnsupportedExit (Ghcb, Regs, InstructionData); } Regs->Rax = Ghcb->SaveArea.Rax; Regs->Rcx = Ghcb->SaveArea.Rcx; Regs->Rdx = Ghcb->SaveArea.Rdx; return 0; } /** Handle a VMMCALL event. Use the VMGEXIT instruction to handle a VMMCALL event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 VmmCallExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 Status; DecodeModRm (Regs, InstructionData); Ghcb->SaveArea.Rax = Regs->Rax; VmgSetOffsetValid (Ghcb, GhcbRax); Ghcb->SaveArea.Cpl = (UINT8) (Regs->Cs & 0x3); VmgSetOffsetValid (Ghcb, GhcbCpl); Status = VmgExit (Ghcb, SVM_EXIT_VMMCALL, 0, 0); if (Status != 0) { return Status; } if (!VmgIsOffsetValid (Ghcb, GhcbRax)) { return UnsupportedExit (Ghcb, Regs, InstructionData); } Regs->Rax = Ghcb->SaveArea.Rax; return 0; } /** Handle an MSR event. Use the VMGEXIT instruction to handle either a RDMSR or WRMSR event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 MsrExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 ExitInfo1, Status; ExitInfo1 = 0; switch (*(InstructionData->OpCodes + 1)) { case 0x30: // WRMSR ExitInfo1 = 1; Ghcb->SaveArea.Rax = Regs->Rax; VmgSetOffsetValid (Ghcb, GhcbRax); Ghcb->SaveArea.Rdx = Regs->Rdx; VmgSetOffsetValid (Ghcb, GhcbRdx); // // fall through // case 0x32: // RDMSR Ghcb->SaveArea.Rcx = Regs->Rcx; VmgSetOffsetValid (Ghcb, GhcbRcx); break; default: return UnsupportedExit (Ghcb, Regs, InstructionData); } Status = VmgExit (Ghcb, SVM_EXIT_MSR, ExitInfo1, 0); if (Status != 0) { return Status; } if (ExitInfo1 == 0) { if (!VmgIsOffsetValid (Ghcb, GhcbRax) || !VmgIsOffsetValid (Ghcb, GhcbRdx)) { return UnsupportedExit (Ghcb, Regs, InstructionData); } Regs->Rax = Ghcb->SaveArea.Rax; Regs->Rdx = Ghcb->SaveArea.Rdx; } return 0; } /** Build the IOIO event information. The IOIO event information identifies the type of IO operation to be performed by the hypervisor. Build this information based on the instruction data. @param[in] Regs x64 processor context @param[in, out] InstructionData Instruction parsing context @return IOIO event information value **/ STATIC UINT64 IoioExitInfo ( IN EFI_SYSTEM_CONTEXT_X64 *Regs, IN OUT SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 ExitInfo; ExitInfo = 0; switch (*(InstructionData->OpCodes)) { // // INS opcodes // case 0x6C: case 0x6D: ExitInfo |= IOIO_TYPE_INS; ExitInfo |= IOIO_SEG_ES; ExitInfo |= ((Regs->Rdx & 0xffff) << 16); break; // // OUTS opcodes // case 0x6E: case 0x6F: ExitInfo |= IOIO_TYPE_OUTS; ExitInfo |= IOIO_SEG_DS; ExitInfo |= ((Regs->Rdx & 0xffff) << 16); break; // // IN immediate opcodes // case 0xE4: case 0xE5: InstructionData->ImmediateSize = 1; InstructionData->End++; ExitInfo |= IOIO_TYPE_IN; ExitInfo |= ((*(InstructionData->OpCodes + 1)) << 16); break; // // OUT immediate opcodes // case 0xE6: case 0xE7: InstructionData->ImmediateSize = 1; InstructionData->End++; ExitInfo |= IOIO_TYPE_OUT; ExitInfo |= ((*(InstructionData->OpCodes + 1)) << 16) | IOIO_TYPE_OUT; break; // // IN register opcodes // case 0xEC: case 0xED: ExitInfo |= IOIO_TYPE_IN; ExitInfo |= ((Regs->Rdx & 0xffff) << 16); break; // // OUT register opcodes // case 0xEE: case 0xEF: ExitInfo |= IOIO_TYPE_OUT; ExitInfo |= ((Regs->Rdx & 0xffff) << 16); break; default: return 0; } switch (*(InstructionData->OpCodes)) { // // Single-byte opcodes // case 0x6C: case 0x6E: case 0xE4: case 0xE6: case 0xEC: case 0xEE: ExitInfo |= IOIO_DATA_8; break; // // Length determined by instruction parsing // default: ExitInfo |= (InstructionData->DataSize == Size16Bits) ? IOIO_DATA_16 : IOIO_DATA_32; } switch (InstructionData->AddrSize) { case Size16Bits: ExitInfo |= IOIO_ADDR_16; break; case Size32Bits: ExitInfo |= IOIO_ADDR_32; break; case Size64Bits: ExitInfo |= IOIO_ADDR_64; break; default: break; } if (InstructionData->RepMode != 0) { ExitInfo |= IOIO_REP; } return ExitInfo; } /** Handle an IOIO event. Use the VMGEXIT instruction to handle an IOIO event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 IoioExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 ExitInfo1, ExitInfo2, Status; BOOLEAN IsString; ExitInfo1 = IoioExitInfo (Regs, InstructionData); if (ExitInfo1 == 0) { return UnsupportedExit (Ghcb, Regs, InstructionData); } IsString = ((ExitInfo1 & IOIO_TYPE_STR) != 0) ? TRUE : FALSE; if (IsString) { UINTN IoBytes, VmgExitBytes; UINTN GhcbCount, OpCount; Status = 0; IoBytes = IOIO_DATA_BYTES (ExitInfo1); GhcbCount = sizeof (Ghcb->SharedBuffer) / IoBytes; OpCount = ((ExitInfo1 & IOIO_REP) != 0) ? Regs->Rcx : 1; while (OpCount != 0) { ExitInfo2 = MIN (OpCount, GhcbCount); VmgExitBytes = ExitInfo2 * IoBytes; if ((ExitInfo1 & IOIO_TYPE_IN) == 0) { CopyMem (Ghcb->SharedBuffer, (VOID *) Regs->Rsi, VmgExitBytes); Regs->Rsi += VmgExitBytes; } Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer; VmgSetOffsetValid (Ghcb, GhcbSwScratch); Status = VmgExit (Ghcb, SVM_EXIT_IOIO_PROT, ExitInfo1, ExitInfo2); if (Status != 0) { return Status; } if ((ExitInfo1 & IOIO_TYPE_IN) != 0) { CopyMem ((VOID *) Regs->Rdi, Ghcb->SharedBuffer, VmgExitBytes); Regs->Rdi += VmgExitBytes; } if ((ExitInfo1 & IOIO_REP) != 0) { Regs->Rcx -= ExitInfo2; } OpCount -= ExitInfo2; } } else { if ((ExitInfo1 & IOIO_TYPE_IN) != 0) { Ghcb->SaveArea.Rax = 0; } else { CopyMem (&Ghcb->SaveArea.Rax, &Regs->Rax, IOIO_DATA_BYTES (ExitInfo1)); } VmgSetOffsetValid (Ghcb, GhcbRax); Status = VmgExit (Ghcb, SVM_EXIT_IOIO_PROT, ExitInfo1, 0); if (Status != 0) { return Status; } if ((ExitInfo1 & IOIO_TYPE_IN) != 0) { if (!VmgIsOffsetValid (Ghcb, GhcbRax)) { return UnsupportedExit (Ghcb, Regs, InstructionData); } CopyMem (&Regs->Rax, &Ghcb->SaveArea.Rax, IOIO_DATA_BYTES (ExitInfo1)); } } return 0; } /** Handle a INVD event. Use the VMGEXIT instruction to handle a INVD event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 InvdExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { return VmgExit (Ghcb, SVM_EXIT_INVD, 0, 0); } /** Handle a CPUID event. Use the VMGEXIT instruction to handle a CPUID event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 CpuidExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 Status; Ghcb->SaveArea.Rax = Regs->Rax; VmgSetOffsetValid (Ghcb, GhcbRax); Ghcb->SaveArea.Rcx = Regs->Rcx; VmgSetOffsetValid (Ghcb, GhcbRcx); if (Regs->Rax == CPUID_EXTENDED_STATE) { IA32_CR4 Cr4; Cr4.UintN = AsmReadCr4 (); Ghcb->SaveArea.XCr0 = (Cr4.Bits.OSXSAVE == 1) ? AsmXGetBv (0) : 1; VmgSetOffsetValid (Ghcb, GhcbXCr0); } Status = VmgExit (Ghcb, SVM_EXIT_CPUID, 0, 0); if (Status != 0) { return Status; } if (!VmgIsOffsetValid (Ghcb, GhcbRax) || !VmgIsOffsetValid (Ghcb, GhcbRbx) || !VmgIsOffsetValid (Ghcb, GhcbRcx) || !VmgIsOffsetValid (Ghcb, GhcbRdx)) { return UnsupportedExit (Ghcb, Regs, InstructionData); } Regs->Rax = Ghcb->SaveArea.Rax; Regs->Rbx = Ghcb->SaveArea.Rbx; Regs->Rcx = Ghcb->SaveArea.Rcx; Regs->Rdx = Ghcb->SaveArea.Rdx; return 0; } /** Handle a RDPMC event. Use the VMGEXIT instruction to handle a RDPMC event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 RdpmcExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 Status; Ghcb->SaveArea.Rcx = Regs->Rcx; VmgSetOffsetValid (Ghcb, GhcbRcx); Status = VmgExit (Ghcb, SVM_EXIT_RDPMC, 0, 0); if (Status != 0) { return Status; } if (!VmgIsOffsetValid (Ghcb, GhcbRax) || !VmgIsOffsetValid (Ghcb, GhcbRdx)) { return UnsupportedExit (Ghcb, Regs, InstructionData); } Regs->Rax = Ghcb->SaveArea.Rax; Regs->Rdx = Ghcb->SaveArea.Rdx; return 0; } /** Handle a RDTSC event. Use the VMGEXIT instruction to handle a RDTSC event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 RdtscExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { UINT64 Status; Status = VmgExit (Ghcb, SVM_EXIT_RDTSC, 0, 0); if (Status != 0) { return Status; } if (!VmgIsOffsetValid (Ghcb, GhcbRax) || !VmgIsOffsetValid (Ghcb, GhcbRdx)) { return UnsupportedExit (Ghcb, Regs, InstructionData); } Regs->Rax = Ghcb->SaveArea.Rax; Regs->Rdx = Ghcb->SaveArea.Rdx; return 0; } /** Handle a DR7 register write event. Use the VMGEXIT instruction to handle a DR7 write event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully @return New exception value to propagate **/ STATIC UINT64 Dr7WriteExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { SEV_ES_INSTRUCTION_OPCODE_EXT *Ext; SEV_ES_PER_CPU_DATA *SevEsData; UINT64 *Register; UINT64 Status; Ext = &InstructionData->Ext; SevEsData = (SEV_ES_PER_CPU_DATA *) (Ghcb + 1); DecodeModRm (Regs, InstructionData); // // MOV DRn always treats MOD == 3 no matter how encoded // Register = GetRegisterPointer (Regs, Ext->ModRm.Rm); // // Using a value of 0 for ExitInfo1 means RAX holds the value // Ghcb->SaveArea.Rax = *Register; VmgSetOffsetValid (Ghcb, GhcbRax); Status = VmgExit (Ghcb, SVM_EXIT_DR7_WRITE, 0, 0); if (Status != 0) { return Status; } SevEsData->Dr7 = *Register; SevEsData->Dr7Cached = TRUE; return 0; } /** Handle a DR7 register read event. Use the VMGEXIT instruction to handle a DR7 read event. @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block @param[in, out] Regs x64 processor context @param[in] InstructionData Instruction parsing context @retval 0 Event handled successfully **/ STATIC UINT64 Dr7ReadExit ( IN OUT GHCB *Ghcb, IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, IN SEV_ES_INSTRUCTION_DATA *InstructionData ) { SEV_ES_INSTRUCTION_OPCODE_EXT *Ext; SEV_ES_PER_CPU_DATA *SevEsData; UINT64 *Register; Ext = &InstructionData->Ext; SevEsData = (SEV_ES_PER_CPU_DATA *) (Ghcb + 1); DecodeModRm (Regs, InstructionData); // // MOV DRn always treats MOD == 3 no matter how encoded // Register = GetRegisterPointer (Regs, Ext->ModRm.Rm); // // If there is a cached valued for DR7, return that. Otherwise return the // DR7 standard reset value of 0x400 (no debug breakpoints set). // *Register = (SevEsData->Dr7Cached) ? SevEsData->Dr7 : 0x400; return 0; } /** Handle a #VC exception. Performs the necessary processing to handle a #VC 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 #VC not supported, (new) exception value to propagate provided @retval EFI_PROTOCOL_ERROR #VC handling failed, (new) exception value to propagate provided **/ EFI_STATUS EFIAPI VmgExitHandleVc ( IN OUT EFI_EXCEPTION_TYPE *ExceptionType, IN OUT EFI_SYSTEM_CONTEXT SystemContext ) { MSR_SEV_ES_GHCB_REGISTER Msr; EFI_SYSTEM_CONTEXT_X64 *Regs; GHCB *Ghcb; NAE_EXIT NaeExit; SEV_ES_INSTRUCTION_DATA InstructionData; UINT64 ExitCode, Status; EFI_STATUS VcRet; BOOLEAN InterruptState; VcRet = EFI_SUCCESS; Msr.GhcbPhysicalAddress = AsmReadMsr64 (MSR_SEV_ES_GHCB); ASSERT (Msr.GhcbInfo.Function == 0); ASSERT (Msr.Ghcb != 0); Regs = SystemContext.SystemContextX64; Ghcb = Msr.Ghcb; VmgInit (Ghcb, &InterruptState); ExitCode = Regs->ExceptionData; switch (ExitCode) { case SVM_EXIT_DR7_READ: NaeExit = Dr7ReadExit; break; case SVM_EXIT_DR7_WRITE: NaeExit = Dr7WriteExit; break; case SVM_EXIT_RDTSC: NaeExit = RdtscExit; break; case SVM_EXIT_RDPMC: NaeExit = RdpmcExit; break; case SVM_EXIT_CPUID: NaeExit = CpuidExit; break; case SVM_EXIT_INVD: NaeExit = InvdExit; break; case SVM_EXIT_IOIO_PROT: NaeExit = IoioExit; break; case SVM_EXIT_MSR: NaeExit = MsrExit; break; case SVM_EXIT_VMMCALL: NaeExit = VmmCallExit; break; case SVM_EXIT_RDTSCP: NaeExit = RdtscpExit; break; case SVM_EXIT_WBINVD: NaeExit = WbinvdExit; break; case SVM_EXIT_MONITOR: NaeExit = MonitorExit; break; case SVM_EXIT_MWAIT: NaeExit = MwaitExit; break; case SVM_EXIT_NPF: NaeExit = MmioExit; break; default: NaeExit = UnsupportedExit; } InitInstructionData (&InstructionData, Ghcb, Regs); Status = NaeExit (Ghcb, Regs, &InstructionData); if (Status == 0) { Regs->Rip += InstructionLength (&InstructionData); } else { GHCB_EVENT_INJECTION Event; Event.Uint64 = Status; if (Event.Elements.ErrorCodeValid != 0) { Regs->ExceptionData = Event.Elements.ErrorCode; } else { Regs->ExceptionData = 0; } *ExceptionType = Event.Elements.Vector; VcRet = EFI_PROTOCOL_ERROR; } VmgDone (Ghcb, InterruptState); return VcRet; }