Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Virtualization Exception handler #629

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kernel/src/cpu/idt/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion kernel/src/cpu/idt/entry.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 24 additions & 1 deletion kernel/src/cpu/idt/svsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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::*;
Expand All @@ -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();
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions kernel/src/tdx/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub enum TdxSuccess {

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TdxError {
NoVeInfo,
PageSizeMismatch,
Unimplemented,
Vmcall(TdVmcallError),
Expand Down Expand Up @@ -43,6 +44,7 @@ pub fn tdx_result(err: u64) -> Result<TdxSuccess, TdxError> {
}
} else {
match code {
0xC000_0704 => Err(TdxError::NoVeInfo),
0xC000_0B0B => Err(TdxError::PageSizeMismatch),
_ => Err(TdxError::Unknown(err)),
}
Expand Down
1 change: 1 addition & 0 deletions kernel/src/tdx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@

pub mod error;
pub mod tdcall;
pub mod ve;

pub use error::TdxError;
87 changes: 87 additions & 0 deletions kernel/src/tdx/tdcall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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)]
Expand Down Expand Up @@ -244,6 +258,79 @@ pub unsafe fn td_accept_virtual_memory(region: MemoryRegion<VirtAddr>) -> Result
Ok(())
}

pub fn tdcall_get_ve_info() -> Option<TdVeInfo> {
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;
Expand Down
38 changes: 38 additions & 0 deletions kernel/src/tdx/ve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2025 Intel Corporation.
//
// Author: Chuanxiao Dong <chuanxiao.dong@intel.com>

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(())
}
Loading