| /** @file | |
| X64 #VC Exception Handler functon. | |
| Copyright (C) 2020, 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 <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. MMIO to the APIC page is always allowed. | |
| @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; | |
| UINTN Address; | |
| // | |
| // Allow APIC accesses (which will have the encryption bit set during | |
| // SEC and PEI phases). | |
| // | |
| Address = MemoryAddress & ~(SIZE_4KB - 1); | |
| if (Address == GetLocalApicBaseAddress ()) { | |
| return 0; | |
| } | |
| 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 | |
| ) | |
| { | |
| CcDecodeModRm (Regs, 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 | |
| ) | |
| { | |
| CcDecodeModRm (Regs, 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; | |
| CcDecodeModRm (Regs, InstructionData); | |
| 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 | |
| ) | |
| { | |
| UINT64 ExitInfo1, Status; | |
| ExitInfo1 = 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); | |
| } | |
| 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); | |
| CcDecodeModRm (Regs, InstructionData); | |
| // | |
| // 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); | |
| CcDecodeModRm (Regs, InstructionData); | |
| // | |
| // 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; | |
| } | |
| /** | |
| 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 = 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 (); | |
| } |