diff --git a/kernel/src/cpu/idt/common.rs b/kernel/src/cpu/idt/common.rs index cbd6d7b97..03fff09ac 100644 --- a/kernel/src/cpu/idt/common.rs +++ b/kernel/src/cpu/idt/common.rs @@ -41,6 +41,7 @@ pub const MF_VECTOR: usize = 16; pub const AC_VECTOR: usize = 17; pub const MCE_VECTOR: usize = 18; pub const XF_VECTOR: usize = 19; +pub const VE_VECTOR: usize = 20; pub const CP_VECTOR: usize = 21; pub const HV_VECTOR: usize = 28; pub const VC_VECTOR: usize = 29; diff --git a/kernel/src/cpu/idt/entry.S b/kernel/src/cpu/idt/entry.S index 5e7f2434b..6fe08d972 100644 --- a/kernel/src/cpu/idt/entry.S +++ b/kernel/src/cpu/idt/entry.S @@ -398,7 +398,8 @@ default_entry_no_ist name=mce handler=panic error_code=0 vector=18 // #XF SIMD Floating-Point Exception (Vector 19) default_entry_no_ist name=xf handler=panic error_code=0 vector=19 -// Vector 20 not defined +// #VE Virtualization Exception (Vector 20) +default_entry_no_ist name=ve handler=ve error_code=0 vector=20 // #CP Control-Protection Exception (Vector 21) .if CFG_SHADOW_STACKS diff --git a/kernel/src/cpu/idt/svsm.rs b/kernel/src/cpu/idt/svsm.rs index 6a799533e..23b4f584c 100644 --- a/kernel/src/cpu/idt/svsm.rs +++ b/kernel/src/cpu/idt/svsm.rs @@ -13,7 +13,7 @@ use super::common::{ idt_mut, user_mode, IdtEntry, IdtEventType, PageFaultError, AC_VECTOR, BP_VECTOR, BR_VECTOR, CP_VECTOR, DB_VECTOR, DE_VECTOR, DF_VECTOR, GP_VECTOR, HV_VECTOR, INT_INJ_VECTOR, IPI_VECTOR, MCE_VECTOR, MF_VECTOR, NMI_VECTOR, NM_VECTOR, NP_VECTOR, OF_VECTOR, PF_VECTOR, SS_VECTOR, - SX_VECTOR, TS_VECTOR, UD_VECTOR, VC_VECTOR, XF_VECTOR, + SX_VECTOR, TS_VECTOR, UD_VECTOR, VC_VECTOR, VE_VECTOR, XF_VECTOR, }; use crate::address::VirtAddr; use crate::cpu::irq_state::{raw_get_tpr, raw_set_tpr, tpr_from_vector}; @@ -24,6 +24,7 @@ use crate::debug::gdbstub::svsm_gdbstub::handle_debug_exception; use crate::mm::GuestPtr; use crate::platform::SVSM_PLATFORM; use crate::task::{is_task_fault, terminate}; +use crate::tdx::ve::handle_virtualization_exception; use core::arch::global_asm; use crate::syscall::*; @@ -50,6 +51,7 @@ extern "C" { fn asm_entry_ac(); fn asm_entry_mce(); fn asm_entry_xf(); + fn asm_entry_ve(); fn asm_entry_cp(); fn asm_entry_hv(); fn asm_entry_vc(); @@ -85,6 +87,7 @@ pub fn early_idt_init() { idt.set_entry(AC_VECTOR, IdtEntry::entry(asm_entry_ac)); idt.set_entry(MCE_VECTOR, IdtEntry::entry(asm_entry_mce)); idt.set_entry(XF_VECTOR, IdtEntry::entry(asm_entry_xf)); + idt.set_entry(VE_VECTOR, IdtEntry::entry(asm_entry_ve)); idt.set_entry(CP_VECTOR, IdtEntry::entry(asm_entry_cp)); idt.set_entry(HV_VECTOR, IdtEntry::entry(asm_entry_hv)); idt.set_entry(VC_VECTOR, IdtEntry::entry(asm_entry_vc)); @@ -262,6 +265,26 @@ extern "C" fn ex_handler_control_protection(ctxt: &mut X86ExceptionContext, _vec } } +// Virtualization Exception handler +#[no_mangle] +extern "C" fn ex_handler_ve(ctxt: &mut X86ExceptionContext) { + let rip = ctxt.frame.rip; + let code = ctxt.error_code; + + if let Err(err) = handle_virtualization_exception(ctxt) { + log::error!("#VE handling error: {:?}", err); + if user_mode(ctxt) { + log::error!("Failed to handle #VE from user-mode at RIP {:#018x} code: {:#018x} - Terminating task", rip, code); + terminate(); + } else { + panic!( + "Failed to handle #VE from kernel-mode at RIP {:#018x} code: {:#018x}", + rip, code + ); + } + } +} + // VMM Communication handler #[no_mangle] extern "C" fn ex_handler_vmm_communication(ctxt: &mut X86ExceptionContext, vector: usize) { diff --git a/kernel/src/tdx/error.rs b/kernel/src/tdx/error.rs index 9706b69b5..e6047c94f 100644 --- a/kernel/src/tdx/error.rs +++ b/kernel/src/tdx/error.rs @@ -15,6 +15,7 @@ pub enum TdxSuccess { #[derive(Clone, Copy, Debug, PartialEq)] pub enum TdxError { + NoVeInfo, PageSizeMismatch, Unimplemented, Vmcall(TdVmcallError), @@ -43,6 +44,7 @@ pub fn tdx_result(err: u64) -> Result { } } else { match code { + 0xC000_0704 => Err(TdxError::NoVeInfo), 0xC000_0B0B => Err(TdxError::PageSizeMismatch), _ => Err(TdxError::Unknown(err)), } diff --git a/kernel/src/tdx/mod.rs b/kernel/src/tdx/mod.rs index 705b4b418..e2f6dabc0 100644 --- a/kernel/src/tdx/mod.rs +++ b/kernel/src/tdx/mod.rs @@ -6,5 +6,6 @@ pub mod error; pub mod tdcall; +pub mod ve; pub use error::TdxError; diff --git a/kernel/src/tdx/tdcall.rs b/kernel/src/tdx/tdcall.rs index 92bacb0db..19a65440e 100644 --- a/kernel/src/tdx/tdcall.rs +++ b/kernel/src/tdx/tdcall.rs @@ -6,6 +6,7 @@ use super::error::{tdvmcall_result, tdx_recoverable_error, tdx_result, TdxError, TdxSuccess}; use crate::address::{Address, PhysAddr, VirtAddr}; +use crate::cpu::cpuid::CpuidResult; use crate::error::SvsmError; use crate::mm::pagetable::PageFrame; use crate::mm::{virt_to_frame, PerCPUPageMappingGuard}; @@ -16,11 +17,24 @@ use bitfield_struct::bitfield; use core::arch::asm; const TDG_VP_TDVMCALL: u32 = 0; +const TDG_VP_VEINFO_GET: u32 = 3; const TDG_MEM_PAGE_ACCEPT: u32 = 6; +const TDVMCALL_CPUID: u32 = 10; const TDVMCALL_HLT: u32 = 12; const TDVMCALL_IO: u32 = 30; +/// Virtualization exception information +#[derive(Clone, Copy, Debug)] +pub struct TdVeInfo { + pub exit_reason: u32, + pub exit_qualification: u64, + pub gla: u64, + pub gpa: u64, + pub exit_instruction_length: u32, + pub exit_instruction_info: u32, +} + #[bitfield(u64)] struct EptMappingInfo { #[bits(12)] @@ -244,6 +258,79 @@ pub unsafe fn td_accept_virtual_memory(region: MemoryRegion) -> Result Ok(()) } +pub fn tdcall_get_ve_info() -> Option { + let mut out_rcx: u64; + let mut out_rdx: u64; + let mut out_r8: u64; + let mut out_r9: u64; + let mut out_r10: u64; + // SAFETY: executing TDCALL requires the use of assembly. + let err = unsafe { + let mut ret: u64; + asm!("tdcall", + in("rax") TDG_VP_VEINFO_GET, + lateout("rax") ret, + out("rcx") out_rcx, + out("rdx") out_rdx, + out("r8") out_r8, + out("r9") out_r9, + out("r10") out_r10, + options(att_syntax)); + ret + }; + match tdx_result(err) { + Ok(_) => Some(TdVeInfo { + exit_reason: out_rcx as u32, + exit_qualification: out_rdx, + gla: out_r8, + gpa: out_r9, + exit_instruction_length: out_r10 as u32, + exit_instruction_info: ((out_r10 >> 32) as u32), + }), + Err(TdxError::NoVeInfo) => None, + Err(e) => panic!("Unknown TD error: {e:?}"), + } +} + +pub fn tdvmcall_cpuid(cpuid_fn: u32, cpuid_subfn: u32) -> CpuidResult { + let pass_regs = (1 << 10) | (1 << 11) | (1 << 12) | (1 << 13) | (1 << 14) | (1 << 15); + let mut ret: u64; + let mut vmcall_ret: u64; + let mut result_eax: u32; + let mut result_ebx: u32; + let mut result_ecx: u32; + let mut result_edx: u32; + // SAFETY: executing TDCALL requires the use of assembly. + unsafe { + asm!("tdcall", + in("rax") TDG_VP_TDVMCALL, + in("rcx") pass_regs, + in("r10") 0, + in("r11") TDVMCALL_CPUID, + in("r12") cpuid_fn, + in("r13") cpuid_subfn, + lateout("rax") ret, + lateout("r10") vmcall_ret, + lateout("r11") _, + lateout("r12") result_eax, + lateout("r13") result_ebx, + lateout("r14") result_ecx, + lateout("r15") result_edx, + options(att_syntax)); + } + // r10 is expected to be TDG.VP.VMCALL_SUCCESS per the GHCI spec + // Make sure the result matches the expectation + debug_assert!(tdvmcall_result(vmcall_ret).is_ok()); + debug_assert!(tdx_result(ret).is_ok()); + + CpuidResult { + eax: result_eax, + ebx: result_ebx, + ecx: result_ecx, + edx: result_edx, + } +} + pub fn tdvmcall_halt() { let pass_regs = (1 << 10) | (1 << 11) | (1 << 12); let mut ret: u64; diff --git a/kernel/src/tdx/ve.rs b/kernel/src/tdx/ve.rs new file mode 100644 index 000000000..7a15bfc93 --- /dev/null +++ b/kernel/src/tdx/ve.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2025 Intel Corporation. +// +// Author: Chuanxiao Dong + +use super::tdcall::{tdcall_get_ve_info, tdvmcall_cpuid}; +use super::TdxError; +use crate::cpu::idt::common::X86ExceptionContext; +use crate::error::SvsmError; + +const VMX_EXIT_REASON_CPUID: u32 = 10; + +pub fn handle_virtualization_exception(ctx: &mut X86ExceptionContext) -> Result<(), SvsmError> { + let veinfo = tdcall_get_ve_info().expect("Failed to get #VE info"); + + match veinfo.exit_reason { + VMX_EXIT_REASON_CPUID => handle_cpuid(ctx), + _ => Err(TdxError::Unknown(veinfo.exit_reason.into()).into()), + }?; + + let new_rip = ctx.frame.rip + veinfo.exit_instruction_length as usize; + // SAFETY: we are advancing the instruction pointer by the size of the exit + // instruction. + unsafe { + ctx.set_rip(new_rip); + } + Ok(()) +} + +fn handle_cpuid(ctx: &mut X86ExceptionContext) -> Result<(), SvsmError> { + let cpuidinfo = tdvmcall_cpuid(ctx.regs.rax as u32, ctx.regs.rcx as u32); + ctx.regs.rax = cpuidinfo.eax as usize; + ctx.regs.rbx = cpuidinfo.ebx as usize; + ctx.regs.rcx = cpuidinfo.ecx as usize; + ctx.regs.rdx = cpuidinfo.edx as usize; + Ok(()) +}