/** @file | |
X64 #VC Exception Handler functon. | |
Copyright (C) 2020 - 2024, Advanced Micro Devices, Inc. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Base.h> | |
#include <Uefi.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/LocalApicLib.h> | |
#include <Library/MemEncryptSevLib.h> | |
#include <Library/CcExitLib.h> | |
#include <Library/AmdSvsmLib.h> | |
#include <Register/Amd/Msr.h> | |
#include <Register/Intel/Cpuid.h> | |
#include <IndustryStandard/InstructionParsing.h> | |
#include "CcExitVcHandler.h" | |
#include "CcInstruction.h" | |
// | |
// Non-automatic Exit function prototype | |
// | |
typedef | |
UINT64 | |
(*NAE_EXIT) ( | |
GHCB *Ghcb, | |
EFI_SYSTEM_CONTEXT_X64 *Regs, | |
CC_INSTRUCTION_DATA *InstructionData | |
); | |
// | |
// SEV-SNP Cpuid table entry/function | |
// | |
typedef PACKED struct { | |
UINT32 EaxIn; | |
UINT32 EcxIn; | |
UINT64 Unused; | |
UINT64 Unused2; | |
UINT32 Eax; | |
UINT32 Ebx; | |
UINT32 Ecx; | |
UINT32 Edx; | |
UINT64 Reserved; | |
} SEV_SNP_CPUID_FUNCTION; | |
// | |
// SEV-SNP Cpuid page format | |
// | |
typedef PACKED struct { | |
UINT32 Count; | |
UINT32 Reserved1; | |
UINT64 Reserved2; | |
SEV_SNP_CPUID_FUNCTION function[0]; | |
} SEV_SNP_CPUID_INFO; | |
/** | |
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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
UINT64 Status; | |
Status = CcExitVmgExit (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; | |
} | |
/** | |
Validate that the MMIO memory access is not to encrypted memory. | |
Examine the pagetable entry for the memory specified. MMIO should not be | |
performed against encrypted memory. | |
@param[in] Ghcb Pointer to the Guest-Hypervisor Communication Block | |
@param[in] MemoryAddress Memory address to validate | |
@param[in] MemoryLength Memory length to validate | |
@retval 0 Memory is not encrypted | |
@return New exception value to propogate | |
**/ | |
STATIC | |
UINT64 | |
ValidateMmioMemory ( | |
IN GHCB *Ghcb, | |
IN UINTN MemoryAddress, | |
IN UINTN MemoryLength | |
) | |
{ | |
MEM_ENCRYPT_SEV_ADDRESS_RANGE_STATE State; | |
GHCB_EVENT_INJECTION GpEvent; | |
State = MemEncryptSevGetAddressRangeState ( | |
0, | |
MemoryAddress, | |
MemoryLength | |
); | |
if (State == MemEncryptSevAddressRangeUnencrypted) { | |
return 0; | |
} | |
// | |
// Any state other than unencrypted is an error, issue a #GP. | |
// | |
DEBUG (( | |
DEBUG_ERROR, | |
"MMIO using encrypted memory: %lx\n", | |
(UINT64)MemoryAddress | |
)); | |
GpEvent.Uint64 = 0; | |
GpEvent.Elements.Vector = GP_EXCEPTION; | |
GpEvent.Elements.Type = GHCB_EVENT_INJECTION_TYPE_EXCEPTION; | |
GpEvent.Elements.Valid = 1; | |
return GpEvent.Uint64; | |
} | |
/** | |
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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
UINT64 ExitInfo1, ExitInfo2, Status; | |
UINTN Bytes; | |
UINT64 *Register; | |
UINT8 OpCode, SignByte; | |
UINTN Address; | |
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: | |
CcDecodeModRm (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); | |
} | |
Status = ValidateMmioMemory (Ghcb, InstructionData->Ext.RmData, Bytes); | |
if (Status != 0) { | |
return Status; | |
} | |
ExitInfo1 = InstructionData->Ext.RmData; | |
ExitInfo2 = Bytes; | |
CopyMem (Ghcb->SharedBuffer, &InstructionData->Ext.RegData, Bytes); | |
Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch); | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_MMIO_WRITE, ExitInfo1, ExitInfo2); | |
if (Status != 0) { | |
return Status; | |
} | |
break; | |
// | |
// MMIO write (MOV moffsetX, aX) | |
// | |
case 0xA2: | |
Bytes = 1; | |
// | |
// fall through | |
// | |
case 0xA3: | |
Bytes = ((Bytes != 0) ? Bytes : | |
(InstructionData->DataSize == Size16Bits) ? 2 : | |
(InstructionData->DataSize == Size32Bits) ? 4 : | |
(InstructionData->DataSize == Size64Bits) ? 8 : | |
0); | |
InstructionData->ImmediateSize = (UINTN)(1 << InstructionData->AddrSize); | |
InstructionData->End += InstructionData->ImmediateSize; | |
// | |
// This code is X64 only, so a possible 8-byte copy to a UINTN is ok. | |
// Use a STATIC_ASSERT to be certain the code is being built as X64. | |
// | |
STATIC_ASSERT ( | |
sizeof (UINTN) == sizeof (UINT64), | |
"sizeof (UINTN) != sizeof (UINT64), this file must be built as X64" | |
); | |
Address = 0; | |
CopyMem ( | |
&Address, | |
InstructionData->Immediate, | |
InstructionData->ImmediateSize | |
); | |
Status = ValidateMmioMemory (Ghcb, Address, Bytes); | |
if (Status != 0) { | |
return Status; | |
} | |
ExitInfo1 = Address; | |
ExitInfo2 = Bytes; | |
CopyMem (Ghcb->SharedBuffer, &Regs->Rax, Bytes); | |
Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch); | |
Status = CcExitVmgExit (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: | |
CcDecodeModRm (Regs, InstructionData); | |
Bytes = ((Bytes != 0) ? Bytes : | |
(InstructionData->DataSize == Size16Bits) ? 2 : | |
(InstructionData->DataSize == Size32Bits) ? 4 : | |
0); | |
InstructionData->ImmediateSize = Bytes; | |
InstructionData->End += Bytes; | |
Status = ValidateMmioMemory (Ghcb, InstructionData->Ext.RmData, Bytes); | |
if (Status != 0) { | |
return Status; | |
} | |
ExitInfo1 = InstructionData->Ext.RmData; | |
ExitInfo2 = Bytes; | |
CopyMem (Ghcb->SharedBuffer, InstructionData->Immediate, Bytes); | |
Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch); | |
Status = CcExitVmgExit (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: | |
CcDecodeModRm (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); | |
} | |
Status = ValidateMmioMemory (Ghcb, InstructionData->Ext.RmData, Bytes); | |
if (Status != 0) { | |
return Status; | |
} | |
ExitInfo1 = InstructionData->Ext.RmData; | |
ExitInfo2 = Bytes; | |
Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch); | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_MMIO_READ, ExitInfo1, ExitInfo2); | |
if (Status != 0) { | |
return Status; | |
} | |
Register = CcGetRegisterPointer (Regs, InstructionData->Ext.ModRm.Reg); | |
if (Bytes == 4) { | |
// | |
// Zero-extend for 32-bit operation | |
// | |
*Register = 0; | |
} | |
CopyMem (Register, Ghcb->SharedBuffer, Bytes); | |
break; | |
// | |
// MMIO read (MOV aX, moffsetX) | |
// | |
case 0xA0: | |
Bytes = 1; | |
// | |
// fall through | |
// | |
case 0xA1: | |
Bytes = ((Bytes != 0) ? Bytes : | |
(InstructionData->DataSize == Size16Bits) ? 2 : | |
(InstructionData->DataSize == Size32Bits) ? 4 : | |
(InstructionData->DataSize == Size64Bits) ? 8 : | |
0); | |
InstructionData->ImmediateSize = (UINTN)(1 << InstructionData->AddrSize); | |
InstructionData->End += InstructionData->ImmediateSize; | |
// | |
// This code is X64 only, so a possible 8-byte copy to a UINTN is ok. | |
// Use a STATIC_ASSERT to be certain the code is being built as X64. | |
// | |
STATIC_ASSERT ( | |
sizeof (UINTN) == sizeof (UINT64), | |
"sizeof (UINTN) != sizeof (UINT64), this file must be built as X64" | |
); | |
Address = 0; | |
CopyMem ( | |
&Address, | |
InstructionData->Immediate, | |
InstructionData->ImmediateSize | |
); | |
Status = ValidateMmioMemory (Ghcb, Address, Bytes); | |
if (Status != 0) { | |
return Status; | |
} | |
ExitInfo1 = Address; | |
ExitInfo2 = Bytes; | |
Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch); | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_MMIO_READ, ExitInfo1, ExitInfo2); | |
if (Status != 0) { | |
return Status; | |
} | |
if (Bytes == 4) { | |
// | |
// Zero-extend for 32-bit operation | |
// | |
Regs->Rax = 0; | |
} | |
CopyMem (&Regs->Rax, Ghcb->SharedBuffer, Bytes); | |
break; | |
// | |
// MMIO read w/ zero-extension ((MOVZX regX, reg/memX) | |
// | |
case 0xB6: | |
Bytes = 1; | |
// | |
// fall through | |
// | |
case 0xB7: | |
CcDecodeModRm (Regs, InstructionData); | |
Bytes = (Bytes != 0) ? Bytes : 2; | |
Status = ValidateMmioMemory (Ghcb, InstructionData->Ext.RmData, Bytes); | |
if (Status != 0) { | |
return Status; | |
} | |
ExitInfo1 = InstructionData->Ext.RmData; | |
ExitInfo2 = Bytes; | |
Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch); | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_MMIO_READ, ExitInfo1, ExitInfo2); | |
if (Status != 0) { | |
return Status; | |
} | |
Register = CcGetRegisterPointer (Regs, InstructionData->Ext.ModRm.Reg); | |
SetMem (Register, (UINTN)(1 << 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: | |
CcDecodeModRm (Regs, InstructionData); | |
Bytes = (Bytes != 0) ? Bytes : 2; | |
Status = ValidateMmioMemory (Ghcb, InstructionData->Ext.RmData, Bytes); | |
if (Status != 0) { | |
return Status; | |
} | |
ExitInfo1 = InstructionData->Ext.RmData; | |
ExitInfo2 = Bytes; | |
Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch); | |
Status = CcExitVmgExit (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 = CcGetRegisterPointer (Regs, InstructionData->Ext.ModRm.Reg); | |
SetMem (Register, (UINTN)(1 << InstructionData->DataSize), SignByte); | |
CopyMem (Register, Ghcb->SharedBuffer, Bytes); | |
break; | |
default: | |
DEBUG ((DEBUG_ERROR, "Invalid MMIO opcode (%x)\n", OpCode)); | |
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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
Ghcb->SaveArea.Rax = Regs->Rax; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRax); | |
Ghcb->SaveArea.Rcx = Regs->Rcx; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRcx); | |
return CcExitVmgExit (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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
Ghcb->SaveArea.Rax = Regs->Rax; // Identity mapped, so VA = PA | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRax); | |
Ghcb->SaveArea.Rcx = Regs->Rcx; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRcx); | |
Ghcb->SaveArea.Rdx = Regs->Rdx; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRdx); | |
return CcExitVmgExit (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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
return CcExitVmgExit (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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
UINT64 Status; | |
CcDecodeModRm (Regs, InstructionData); | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_RDTSCP, 0, 0); | |
if (Status != 0) { | |
return Status; | |
} | |
if (!CcExitVmgIsOffsetValid (Ghcb, GhcbRax) || | |
!CcExitVmgIsOffsetValid (Ghcb, GhcbRcx) || | |
!CcExitVmgIsOffsetValid (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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
UINT64 Status; | |
Ghcb->SaveArea.Rax = Regs->Rax; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRax); | |
Ghcb->SaveArea.Cpl = (UINT8)(Regs->Cs & 0x3); | |
CcExitVmgSetOffsetValid (Ghcb, GhcbCpl); | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_VMMCALL, 0, 0); | |
if (Status != 0) { | |
return Status; | |
} | |
if (!CcExitVmgIsOffsetValid (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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
MSR_SVSM_CAA_REGISTER Msr; | |
UINT64 ExitInfo1; | |
UINT64 Status; | |
ExitInfo1 = 0; | |
// | |
// The SVSM CAA MSR is a software implemented MSR and not supported | |
// by the hardware, handle it directly. | |
// | |
if (Regs->Rax == MSR_SVSM_CAA) { | |
// Writes to the SVSM CAA MSR are ignored | |
if (*(InstructionData->OpCodes + 1) == 0x30) { | |
return 0; | |
} | |
Msr.Uint64 = AmdSvsmSnpGetCaa (); | |
Regs->Rax = Msr.Bits.Lower32Bits; | |
Regs->Rdx = Msr.Bits.Upper32Bits; | |
return 0; | |
} | |
switch (*(InstructionData->OpCodes + 1)) { | |
case 0x30: // WRMSR | |
ExitInfo1 = 1; | |
Ghcb->SaveArea.Rax = Regs->Rax; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRax); | |
Ghcb->SaveArea.Rdx = Regs->Rdx; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRdx); | |
// | |
// fall through | |
// | |
case 0x32: // RDMSR | |
Ghcb->SaveArea.Rcx = Regs->Rcx; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRcx); | |
break; | |
default: | |
return UnsupportedExit (Ghcb, Regs, InstructionData); | |
} | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_MSR, ExitInfo1, 0); | |
if (Status != 0) { | |
return Status; | |
} | |
if (ExitInfo1 == 0) { | |
if (!CcExitVmgIsOffsetValid (Ghcb, GhcbRax) || | |
!CcExitVmgIsOffsetValid (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 CC_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 CC_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; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch); | |
Status = CcExitVmgExit (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)); | |
} | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRax); | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_IOIO_PROT, ExitInfo1, 0); | |
if (Status != 0) { | |
return Status; | |
} | |
if ((ExitInfo1 & IOIO_TYPE_IN) != 0) { | |
if (!CcExitVmgIsOffsetValid (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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
return CcExitVmgExit (Ghcb, SVM_EXIT_INVD, 0, 0); | |
} | |
/** | |
Fetch CPUID leaf/function via hypervisor/VMGEXIT. | |
@param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication | |
Block | |
@param[in] EaxIn EAX input for cpuid instruction | |
@param[in] EcxIn ECX input for cpuid instruction | |
@param[in] Xcr0In XCR0 at time of cpuid instruction | |
@param[in, out] Eax Pointer to store leaf's EAX value | |
@param[in, out] Ebx Pointer to store leaf's EBX value | |
@param[in, out] Ecx Pointer to store leaf's ECX value | |
@param[in, out] Edx Pointer to store leaf's EDX value | |
@param[in, out] Status Pointer to store status from VMGEXIT (always 0 | |
unless return value indicates failure) | |
@param[in, out] Unsupported Pointer to store indication of unsupported | |
VMGEXIT (always false unless return value | |
indicates failure) | |
@retval TRUE CPUID leaf fetch successfully. | |
@retval FALSE Error occurred while fetching CPUID leaf. Callers | |
should Status and Unsupported and handle | |
accordingly if they indicate a more precise | |
error condition. | |
**/ | |
STATIC | |
BOOLEAN | |
GetCpuidHyp ( | |
IN OUT GHCB *Ghcb, | |
IN UINT32 EaxIn, | |
IN UINT32 EcxIn, | |
IN UINT64 XCr0, | |
IN OUT UINT32 *Eax, | |
IN OUT UINT32 *Ebx, | |
IN OUT UINT32 *Ecx, | |
IN OUT UINT32 *Edx, | |
IN OUT UINT64 *Status, | |
IN OUT BOOLEAN *UnsupportedExit | |
) | |
{ | |
*UnsupportedExit = FALSE; | |
Ghcb->SaveArea.Rax = EaxIn; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRax); | |
Ghcb->SaveArea.Rcx = EcxIn; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRcx); | |
if (EaxIn == CPUID_EXTENDED_STATE) { | |
Ghcb->SaveArea.XCr0 = XCr0; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbXCr0); | |
} | |
*Status = CcExitVmgExit (Ghcb, SVM_EXIT_CPUID, 0, 0); | |
if (*Status != 0) { | |
return FALSE; | |
} | |
if (!CcExitVmgIsOffsetValid (Ghcb, GhcbRax) || | |
!CcExitVmgIsOffsetValid (Ghcb, GhcbRbx) || | |
!CcExitVmgIsOffsetValid (Ghcb, GhcbRcx) || | |
!CcExitVmgIsOffsetValid (Ghcb, GhcbRdx)) | |
{ | |
*UnsupportedExit = TRUE; | |
return FALSE; | |
} | |
if (Eax) { | |
*Eax = (UINT32)(UINTN)Ghcb->SaveArea.Rax; | |
} | |
if (Ebx) { | |
*Ebx = (UINT32)(UINTN)Ghcb->SaveArea.Rbx; | |
} | |
if (Ecx) { | |
*Ecx = (UINT32)(UINTN)Ghcb->SaveArea.Rcx; | |
} | |
if (Edx) { | |
*Edx = (UINT32)(UINTN)Ghcb->SaveArea.Rdx; | |
} | |
return TRUE; | |
} | |
/** | |
Check if SEV-SNP enabled. | |
@retval TRUE SEV-SNP is enabled. | |
@retval FALSE SEV-SNP is disabled. | |
**/ | |
STATIC | |
BOOLEAN | |
SnpEnabled ( | |
VOID | |
) | |
{ | |
MSR_SEV_STATUS_REGISTER Msr; | |
Msr.Uint32 = AsmReadMsr32 (MSR_SEV_STATUS); | |
return !!Msr.Bits.SevSnpBit; | |
} | |
/** | |
Calculate the total XSAVE area size for enabled XSAVE areas | |
@param[in] XFeaturesEnabled Bit-mask of enabled XSAVE features/areas as | |
indicated by XCR0/MSR_IA32_XSS bits | |
@param[in, out] XSaveSize Pointer to storage for calculated XSAVE area | |
size | |
@param[in] Compacted Whether or not the calculation is for the | |
normal XSAVE area size (leaf 0xD,0x0,EBX) or | |
compacted XSAVE area size (leaf 0xD,0x1,EBX) | |
@retval TRUE XSAVE size calculation was successful. | |
@retval FALSE XSAVE size calculation was unsuccessful. | |
**/ | |
STATIC | |
BOOLEAN | |
GetCpuidXSaveSize ( | |
IN UINT64 XFeaturesEnabled, | |
IN OUT UINT32 *XSaveSize, | |
IN BOOLEAN Compacted | |
) | |
{ | |
SEV_SNP_CPUID_INFO *CpuidInfo; | |
UINT64 XFeaturesFound = 0; | |
UINT32 Idx; | |
// | |
// The base/legacy XSave size is documented to be 0x240 in the APM. | |
// | |
*XSaveSize = 0x240; | |
CpuidInfo = (SEV_SNP_CPUID_INFO *)(UINT64)PcdGet32 (PcdOvmfCpuidBase); | |
for (Idx = 0; Idx < CpuidInfo->Count; Idx++) { | |
SEV_SNP_CPUID_FUNCTION *CpuidFn = &CpuidInfo->function[Idx]; | |
if (!((CpuidFn->EaxIn == 0xD) && (CpuidFn->EcxIn > 1))) { | |
continue; | |
} | |
if (XFeaturesFound & (1ULL << CpuidFn->EcxIn) || | |
!(XFeaturesEnabled & (1ULL << CpuidFn->EcxIn))) | |
{ | |
continue; | |
} | |
XFeaturesFound |= (1ULL << CpuidFn->EcxIn); | |
if (Compacted) { | |
*XSaveSize += CpuidFn->Eax; | |
} else { | |
*XSaveSize = MAX (*XSaveSize, CpuidFn->Eax + CpuidFn->Ebx); | |
} | |
} | |
/* | |
* Either the guest set unsupported XCR0/XSS bits, or the corresponding | |
* entries in the CPUID table were not present. This is an invalid state. | |
*/ | |
if (XFeaturesFound != (XFeaturesEnabled & ~3UL)) { | |
return FALSE; | |
} | |
return TRUE; | |
} | |
/** | |
Check if a CPUID leaf/function is indexed via ECX sub-leaf/sub-function | |
@param[in] EaxIn EAX input for cpuid instruction | |
@retval FALSE cpuid leaf/function is not indexed by ECX input | |
@retval TRUE cpuid leaf/function is indexed by ECX input | |
**/ | |
STATIC | |
BOOLEAN | |
IsFunctionIndexed ( | |
IN UINT32 EaxIn | |
) | |
{ | |
switch (EaxIn) { | |
case CPUID_CACHE_PARAMS: | |
case CPUID_STRUCTURED_EXTENDED_FEATURE_FLAGS: | |
case CPUID_EXTENDED_TOPOLOGY: | |
case CPUID_EXTENDED_STATE: | |
case CPUID_INTEL_RDT_MONITORING: | |
case CPUID_INTEL_RDT_ALLOCATION: | |
case CPUID_INTEL_SGX: | |
case CPUID_INTEL_PROCESSOR_TRACE: | |
case CPUID_DETERMINISTIC_ADDRESS_TRANSLATION_PARAMETERS: | |
case CPUID_V2_EXTENDED_TOPOLOGY: | |
case 0x8000001D: /* Cache Topology Information */ | |
return TRUE; | |
} | |
return FALSE; | |
} | |
/** | |
Fetch CPUID leaf/function via SEV-SNP CPUID table. | |
@param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication | |
Block | |
@param[in] EaxIn EAX input for cpuid instruction | |
@param[in] EcxIn ECX input for cpuid instruction | |
@param[in] Xcr0In XCR0 at time of cpuid instruction | |
@param[in, out] Eax Pointer to store leaf's EAX value | |
@param[in, out] Ebx Pointer to store leaf's EBX value | |
@param[in, out] Ecx Pointer to store leaf's ECX value | |
@param[in, out] Edx Pointer to store leaf's EDX value | |
@param[in, out] Status Pointer to store status from VMGEXIT (always 0 | |
unless return value indicates failure) | |
@param[in, out] Unsupported Pointer to store indication of unsupported | |
VMGEXIT (always false unless return value | |
indicates failure) | |
@retval TRUE CPUID leaf fetch successfully. | |
@retval FALSE Error occurred while fetching CPUID leaf. Callers | |
should Status and Unsupported and handle | |
accordingly if they indicate a more precise | |
error condition. | |
**/ | |
STATIC | |
BOOLEAN | |
GetCpuidFw ( | |
IN OUT GHCB *Ghcb, | |
IN UINT32 EaxIn, | |
IN UINT32 EcxIn, | |
IN UINT64 XCr0, | |
IN OUT UINT32 *Eax, | |
IN OUT UINT32 *Ebx, | |
IN OUT UINT32 *Ecx, | |
IN OUT UINT32 *Edx, | |
IN OUT UINT64 *Status, | |
IN OUT BOOLEAN *Unsupported | |
) | |
{ | |
SEV_SNP_CPUID_INFO *CpuidInfo; | |
BOOLEAN Found; | |
UINT32 Idx; | |
CpuidInfo = (SEV_SNP_CPUID_INFO *)(UINT64)PcdGet32 (PcdOvmfCpuidBase); | |
Found = FALSE; | |
for (Idx = 0; Idx < CpuidInfo->Count; Idx++) { | |
SEV_SNP_CPUID_FUNCTION *CpuidFn = &CpuidInfo->function[Idx]; | |
if (CpuidFn->EaxIn != EaxIn) { | |
continue; | |
} | |
if (IsFunctionIndexed (CpuidFn->EaxIn) && (CpuidFn->EcxIn != EcxIn)) { | |
continue; | |
} | |
*Eax = CpuidFn->Eax; | |
*Ebx = CpuidFn->Ebx; | |
*Ecx = CpuidFn->Ecx; | |
*Edx = CpuidFn->Edx; | |
Found = TRUE; | |
break; | |
} | |
if (!Found) { | |
*Eax = *Ebx = *Ecx = *Edx = 0; | |
goto Out; | |
} | |
if (EaxIn == CPUID_VERSION_INFO) { | |
IA32_CR4 Cr4; | |
UINT32 Ebx2; | |
UINT32 Edx2; | |
if (!GetCpuidHyp ( | |
Ghcb, | |
EaxIn, | |
EcxIn, | |
XCr0, | |
NULL, | |
&Ebx2, | |
NULL, | |
&Edx2, | |
Status, | |
Unsupported | |
)) | |
{ | |
return FALSE; | |
} | |
/* initial APIC ID */ | |
*Ebx = (*Ebx & 0x00FFFFFF) | (Ebx2 & 0xFF000000); | |
/* APIC enabled bit */ | |
*Edx = (*Edx & ~BIT9) | (Edx2 & BIT9); | |
/* OSXSAVE enabled bit */ | |
Cr4.UintN = AsmReadCr4 (); | |
*Ecx = (Cr4.Bits.OSXSAVE) ? (*Ecx & ~BIT27) | (*Ecx & BIT27) | |
: (*Ecx & ~BIT27); | |
} else if (EaxIn == CPUID_STRUCTURED_EXTENDED_FEATURE_FLAGS) { | |
IA32_CR4 Cr4; | |
Cr4.UintN = AsmReadCr4 (); | |
/* OSPKE enabled bit */ | |
*Ecx = (Cr4.Bits.PKE) ? (*Ecx | BIT4) : (*Ecx & ~BIT4); | |
} else if (EaxIn == CPUID_EXTENDED_TOPOLOGY) { | |
if (!GetCpuidHyp ( | |
Ghcb, | |
EaxIn, | |
EcxIn, | |
XCr0, | |
NULL, | |
NULL, | |
NULL, | |
Edx, | |
Status, | |
Unsupported | |
)) | |
{ | |
return FALSE; | |
} | |
} else if ((EaxIn == CPUID_EXTENDED_STATE) && ((EcxIn == 0) || (EcxIn == 1))) { | |
MSR_IA32_XSS_REGISTER XssMsr; | |
BOOLEAN Compacted; | |
UINT32 XSaveSize; | |
XssMsr.Uint64 = 0; | |
Compacted = FALSE; | |
if (EcxIn == 1) { | |
/* | |
* The PPR and APM aren't clear on what size should be encoded in | |
* 0xD:0x1:EBX when compaction is not enabled by either XSAVEC or | |
* XSAVES, as these are generally fixed to 1 on real CPUs. Report | |
* this undefined case as an error. | |
*/ | |
if (!(*Eax & (BIT3 | BIT1))) { | |
/* (XSAVES | XSAVEC) */ | |
return FALSE; | |
} | |
Compacted = TRUE; | |
XssMsr.Uint64 = AsmReadMsr64 (MSR_IA32_XSS); | |
} | |
if (!GetCpuidXSaveSize ( | |
XCr0 | XssMsr.Uint64, | |
&XSaveSize, | |
Compacted | |
)) | |
{ | |
return FALSE; | |
} | |
*Ebx = XSaveSize; | |
} else if (EaxIn == 0x8000001E) { | |
UINT32 Ebx2; | |
UINT32 Ecx2; | |
/* extended APIC ID */ | |
if (!GetCpuidHyp ( | |
Ghcb, | |
EaxIn, | |
EcxIn, | |
XCr0, | |
Eax, | |
&Ebx2, | |
&Ecx2, | |
NULL, | |
Status, | |
Unsupported | |
)) | |
{ | |
return FALSE; | |
} | |
/* compute ID */ | |
*Ebx = (*Ebx & 0xFFFFFF00) | (Ebx2 & 0x000000FF); | |
/* node ID */ | |
*Ecx = (*Ecx & 0xFFFFFF00) | (Ecx2 & 0x000000FF); | |
} else if (EaxIn == 0x8000001F) { | |
/* Set the SVSM feature bit if running under an SVSM */ | |
if (AmdSvsmIsSvsmPresent ()) { | |
*Eax |= BIT28; | |
} | |
} | |
Out: | |
*Status = 0; | |
*Unsupported = FALSE; | |
return TRUE; | |
} | |
/** | |
Handle a CPUID event. | |
Use VMGEXIT instruction or CPUID table 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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
BOOLEAN Unsupported; | |
UINT64 Status; | |
UINT32 EaxIn; | |
UINT32 EcxIn; | |
UINT64 XCr0; | |
UINT32 Eax; | |
UINT32 Ebx; | |
UINT32 Ecx; | |
UINT32 Edx; | |
EaxIn = (UINT32)(UINTN)Regs->Rax; | |
EcxIn = (UINT32)(UINTN)Regs->Rcx; | |
if (EaxIn == CPUID_EXTENDED_STATE) { | |
IA32_CR4 Cr4; | |
Cr4.UintN = AsmReadCr4 (); | |
Ghcb->SaveArea.XCr0 = (Cr4.Bits.OSXSAVE == 1) ? AsmXGetBv (0) : 1; | |
XCr0 = (Cr4.Bits.OSXSAVE == 1) ? AsmXGetBv (0) : 1; | |
} | |
if (SnpEnabled ()) { | |
if (!GetCpuidFw ( | |
Ghcb, | |
EaxIn, | |
EcxIn, | |
XCr0, | |
&Eax, | |
&Ebx, | |
&Ecx, | |
&Edx, | |
&Status, | |
&Unsupported | |
)) | |
{ | |
goto CpuidFail; | |
} | |
} else { | |
if (!GetCpuidHyp ( | |
Ghcb, | |
EaxIn, | |
EcxIn, | |
XCr0, | |
&Eax, | |
&Ebx, | |
&Ecx, | |
&Edx, | |
&Status, | |
&Unsupported | |
)) | |
{ | |
goto CpuidFail; | |
} | |
} | |
Regs->Rax = Eax; | |
Regs->Rbx = Ebx; | |
Regs->Rcx = Ecx; | |
Regs->Rdx = Edx; | |
return 0; | |
CpuidFail: | |
if (Unsupported) { | |
return UnsupportedExit (Ghcb, Regs, InstructionData); | |
} | |
return Status; | |
} | |
/** | |
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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
UINT64 Status; | |
Ghcb->SaveArea.Rcx = Regs->Rcx; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRcx); | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_RDPMC, 0, 0); | |
if (Status != 0) { | |
return Status; | |
} | |
if (!CcExitVmgIsOffsetValid (Ghcb, GhcbRax) || | |
!CcExitVmgIsOffsetValid (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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
UINT64 Status; | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_RDTSC, 0, 0); | |
if (Status != 0) { | |
return Status; | |
} | |
if (!CcExitVmgIsOffsetValid (Ghcb, GhcbRax) || | |
!CcExitVmgIsOffsetValid (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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
CC_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); | |
// | |
// MOV DRn always treats MOD == 3 no matter how encoded | |
// | |
Register = CcGetRegisterPointer (Regs, Ext->ModRm.Rm); | |
// | |
// Using a value of 0 for ExitInfo1 means RAX holds the value | |
// | |
Ghcb->SaveArea.Rax = *Register; | |
CcExitVmgSetOffsetValid (Ghcb, GhcbRax); | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_DR7_WRITE, 0, 0); | |
if (Status != 0) { | |
return Status; | |
} | |
SevEsData->Dr7 = *Register; | |
SevEsData->Dr7Cached = 1; | |
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 CC_INSTRUCTION_DATA *InstructionData | |
) | |
{ | |
CC_INSTRUCTION_OPCODE_EXT *Ext; | |
SEV_ES_PER_CPU_DATA *SevEsData; | |
UINT64 *Register; | |
Ext = &InstructionData->Ext; | |
SevEsData = (SEV_ES_PER_CPU_DATA *)(Ghcb + 1); | |
// | |
// MOV DRn always treats MOD == 3 no matter how encoded | |
// | |
Register = CcGetRegisterPointer (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 == 1) ? SevEsData->Dr7 : 0x400; | |
return 0; | |
} | |
/** | |
Check that the opcode matches the exit code for a #VC. | |
Each exit code should only be raised while executing certain instructions. | |
Verify that rIP points to a correct instruction based on the exit code to | |
protect against maliciously injected interrupts via the hypervisor. If it does | |
not, report an unsupported event to the hypervisor. | |
Decodes the ModRm byte into InstructionData if necessary. | |
@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 | |
@param[in] ExitCode Exit code given by #VC. | |
@retval 0 No problems detected. | |
@return New exception value to propagate | |
**/ | |
STATIC | |
UINT64 | |
VcCheckOpcodeBytes ( | |
IN OUT GHCB *Ghcb, | |
IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs, | |
IN OUT CC_INSTRUCTION_DATA *InstructionData, | |
IN UINT64 ExitCode | |
) | |
{ | |
UINT8 OpCode; | |
// | |
// Expected opcodes are either 1 or 2 bytes. If they are 2 bytes, they always | |
// start with TWO_BYTE_OPCODE_ESCAPE (0x0f), so skip over that. | |
// | |
OpCode = *(InstructionData->OpCodes); | |
if (OpCode == TWO_BYTE_OPCODE_ESCAPE) { | |
OpCode = *(InstructionData->OpCodes + 1); | |
} | |
switch (ExitCode) { | |
case SVM_EXIT_IOIO_PROT: | |
case SVM_EXIT_NPF: | |
/* handled separately */ | |
return 0; | |
case SVM_EXIT_CPUID: | |
if (OpCode == 0xa2) { | |
return 0; | |
} | |
break; | |
case SVM_EXIT_INVD: | |
if (OpCode == 0x08) { | |
return 0; | |
} | |
break; | |
case SVM_EXIT_MONITOR: | |
CcDecodeModRm (Regs, InstructionData); | |
if ((OpCode == 0x01) && | |
( (InstructionData->ModRm.Uint8 == 0xc8) /* MONITOR */ | |
|| (InstructionData->ModRm.Uint8 == 0xfa))) /* MONITORX */ | |
{ | |
return 0; | |
} | |
break; | |
case SVM_EXIT_MWAIT: | |
CcDecodeModRm (Regs, InstructionData); | |
if ((OpCode == 0x01) && | |
( (InstructionData->ModRm.Uint8 == 0xc9) /* MWAIT */ | |
|| (InstructionData->ModRm.Uint8 == 0xfb))) /* MWAITX */ | |
{ | |
return 0; | |
} | |
break; | |
case SVM_EXIT_MSR: | |
/* RDMSR */ | |
if ((OpCode == 0x32) || | |
/* WRMSR */ | |
(OpCode == 0x30)) | |
{ | |
return 0; | |
} | |
break; | |
case SVM_EXIT_RDPMC: | |
if (OpCode == 0x33) { | |
return 0; | |
} | |
break; | |
case SVM_EXIT_RDTSC: | |
if (OpCode == 0x31) { | |
return 0; | |
} | |
break; | |
case SVM_EXIT_RDTSCP: | |
CcDecodeModRm (Regs, InstructionData); | |
if ((OpCode == 0x01) && (InstructionData->ModRm.Uint8 == 0xf9)) { | |
return 0; | |
} | |
break; | |
case SVM_EXIT_DR7_READ: | |
CcDecodeModRm (Regs, InstructionData); | |
if ((OpCode == 0x21) && | |
(InstructionData->Ext.ModRm.Reg == 7)) | |
{ | |
return 0; | |
} | |
break; | |
case SVM_EXIT_VMMCALL: | |
CcDecodeModRm (Regs, InstructionData); | |
if ((OpCode == 0x01) && (InstructionData->ModRm.Uint8 == 0xd9)) { | |
return 0; | |
} | |
break; | |
case SVM_EXIT_DR7_WRITE: | |
CcDecodeModRm (Regs, InstructionData); | |
if ((OpCode == 0x23) && | |
(InstructionData->Ext.ModRm.Reg == 7)) | |
{ | |
return 0; | |
} | |
break; | |
case SVM_EXIT_WBINVD: | |
if (OpCode == 0x9) { | |
return 0; | |
} | |
break; | |
default: | |
break; | |
} | |
return UnsupportedExit (Ghcb, Regs, InstructionData); | |
} | |
/** | |
Handle a #VC exception. | |
Performs the necessary processing to handle a #VC exception. | |
@param[in, out] Ghcb Pointer to the GHCB | |
@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 | |
InternalVmgExitHandleVc ( | |
IN OUT GHCB *Ghcb, | |
IN OUT EFI_EXCEPTION_TYPE *ExceptionType, | |
IN OUT EFI_SYSTEM_CONTEXT SystemContext | |
) | |
{ | |
EFI_SYSTEM_CONTEXT_X64 *Regs; | |
NAE_EXIT NaeExit; | |
CC_INSTRUCTION_DATA InstructionData; | |
UINT64 ExitCode, Status; | |
EFI_STATUS VcRet; | |
BOOLEAN InterruptState; | |
VcRet = EFI_SUCCESS; | |
Regs = SystemContext.SystemContextX64; | |
CcExitVmgInit (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; | |
} | |
CcInitInstructionData (&InstructionData, Ghcb, Regs); | |
Status = VcCheckOpcodeBytes (Ghcb, Regs, &InstructionData, ExitCode); | |
// | |
// If the opcode does not match the exit code, do not process the exception | |
// | |
if (Status == 0) { | |
Status = NaeExit (Ghcb, Regs, &InstructionData); | |
} | |
if (Status == 0) { | |
Regs->Rip += CcInstructionLength (&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; | |
} | |
CcExitVmgDone (Ghcb, InterruptState); | |
return VcRet; | |
} | |
/** | |
Routine to allow ASSERT from within #VC. | |
@param[in, out] SevEsData Pointer to the per-CPU data | |
**/ | |
VOID | |
EFIAPI | |
VmgExitIssueAssert ( | |
IN OUT SEV_ES_PER_CPU_DATA *SevEsData | |
) | |
{ | |
// | |
// Progress will be halted, so set VcCount to allow for ASSERT output | |
// to be seen. | |
// | |
SevEsData->VcCount = 0; | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} |