diff --git a/.gitignore b/.gitignore index 55c0d45..f7a0b62 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ rusty-tags.vi # We ignore Cargo.lock because `axvcpu` is just a library Cargo.lock + +tmp/ diff --git a/Cargo.toml b/Cargo.toml index c78d47c..54d5534 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,16 @@ axvcpu = "0.1.0" x86_vlapic = "0.1.0" axdevice_base = "0.1.0" axvisor_api = "0.1.0" +tock-registers = "0.10" spin = { version = "0.9", default-features = false } +[dev-dependencies] +memoffset = "0.9" + [features] -default = ["vmx"] +default = ["svm"] +# default = ["vmx"] tracing = [] vmx = [] svm = [] diff --git a/src/lib.rs b/src/lib.rs index 79d3e6c..71164bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] #![feature(doc_cfg)] +#![feature(concat_idents)] #![doc = include_str!("../README.md")] #[macro_use] @@ -14,18 +15,27 @@ pub(crate) mod msr; #[macro_use] pub(crate) mod regs; mod ept; +pub(crate) mod xstate; + +#[cfg(all(feature = "vmx", feature = "svm"))] +compile_error!("Features 'vmx' and 'svm' are mutually exclusive. Please enable only one of them."); cfg_if::cfg_if! { if #[cfg(feature = "vmx")] { mod vmx; - use vmx as vender; - pub use vmx::{VmxExitInfo, VmxExitReason, VmxInterruptInfo, VmxIoExitInfo}; - - pub use vender::VmxArchVCpu; - pub use vender::VmxArchPerCpuState; + use vmx as vendor; + // pub use vmx::{VmxExitInfo, VmxExitReason, VmxInterruptInfo, VmxIoExitInfo}; + pub use vendor::VmxArchVCpu as X86ArchVCpu; + pub use vendor::VmxArchPerCpuState as X86ArchPerCpuState; + } else if #[cfg(feature = "svm")] { + mod svm; + use svm as vendor; + pub use vendor::{ + SvmArchVCpu as X86ArchVCpu, SvmArchPerCpuState as X86ArchPerCpuState, + }; } } pub use ept::GuestPageWalkInfo; pub use regs::GeneralRegisters; -pub use vender::has_hardware_support; +pub use vendor::has_hardware_support; diff --git a/src/msr.rs b/src/msr.rs index a5a77db..813f0a2 100644 --- a/src/msr.rs +++ b/src/msr.rs @@ -7,6 +7,10 @@ use x86::msr::{rdmsr, wrmsr}; pub enum Msr { IA32_FEATURE_CONTROL = 0x3a, + IA32_SYSENTER_CS = 0x174, + IA32_SYSENTER_ESP = 0x175, + IA32_SYSENTER_EIP = 0x176, + IA32_PAT = 0x277, IA32_VMX_BASIC = 0x480, @@ -37,6 +41,18 @@ pub enum Msr { IA32_FS_BASE = 0xc000_0100, IA32_GS_BASE = 0xc000_0101, IA32_KERNEL_GSBASE = 0xc000_0102, + + // SVM Related MSRs: + VM_CR = 0xc001_0114, + IGNNE = 0xc001_0115, + VM_HSAVE_PA = 0xc001_0117, + + PERF_EVT_SEL0 = 0xc001_0200, + PERF_EVT_SEL1 = 0xc001_0202, + PERF_EVT_SEL2 = 0xc001_0204, + PERF_EVT_SEL3 = 0xc001_0206, + PERF_EVT_SEL4 = 0xc001_0208, + PERF_EVT_SEL5 = 0xc001_020a, } impl Msr { @@ -71,117 +87,3 @@ pub(super) trait MsrReadWrite { } } } - -#[cfg(test)] -mod tests { - use super::*; - use alloc::format; - - #[test] - fn test_msr_enum_values() { - // Test that MSR enum values match the expected constants - assert_eq!(Msr::IA32_FEATURE_CONTROL as u32, 0x3a); - assert_eq!(Msr::IA32_PAT as u32, 0x277); - assert_eq!(Msr::IA32_VMX_BASIC as u32, 0x480); - assert_eq!(Msr::IA32_EFER as u32, 0xc000_0080); - assert_eq!(Msr::IA32_LSTAR as u32, 0xc000_0082); - } - - #[test] - fn test_msr_debug() { - // Test that MSR implements Debug properly - let msr = Msr::IA32_VMX_BASIC; - let debug_str = format!("{:?}", msr); - assert!(!debug_str.is_empty()); - assert!(debug_str.contains("IA32_VMX_BASIC")); - } - - #[test] - fn test_msr_copy_clone() { - // Test that MSR implements Copy and Clone - let msr1 = Msr::IA32_EFER; - let msr2 = msr1; // Copy - let msr3 = msr1.clone(); // Clone - - assert_eq!(msr1 as u32, msr2 as u32); - assert_eq!(msr1 as u32, msr3 as u32); - } - - #[test] - fn test_vmx_msr_ranges() { - // Test VMX MSR values are in the correct range - assert!(Msr::IA32_VMX_BASIC as u32 >= 0x480); - assert!(Msr::IA32_VMX_TRUE_ENTRY_CTLS as u32 <= 0x490); - - // Test that VMX MSRs are consecutive where expected - assert_eq!( - Msr::IA32_VMX_BASIC as u32 + 1, - Msr::IA32_VMX_PINBASED_CTLS as u32 - ); - assert_eq!( - Msr::IA32_VMX_PINBASED_CTLS as u32 + 1, - Msr::IA32_VMX_PROCBASED_CTLS as u32 - ); - } - - #[test] - fn test_fs_gs_base_msr_values() { - // Test FS/GS base MSRs - assert_eq!(Msr::IA32_FS_BASE as u32, 0xc000_0100); - assert_eq!(Msr::IA32_GS_BASE as u32, 0xc000_0101); - assert_eq!(Msr::IA32_KERNEL_GSBASE as u32, 0xc000_0102); - - // These should be consecutive - assert_eq!(Msr::IA32_FS_BASE as u32 + 1, Msr::IA32_GS_BASE as u32); - assert_eq!(Msr::IA32_GS_BASE as u32 + 1, Msr::IA32_KERNEL_GSBASE as u32); - } - - #[test] - fn test_system_call_msr_values() { - // Test system call related MSRs - assert_eq!(Msr::IA32_STAR as u32, 0xc000_0081); - assert_eq!(Msr::IA32_LSTAR as u32, 0xc000_0082); - assert_eq!(Msr::IA32_CSTAR as u32, 0xc000_0083); - assert_eq!(Msr::IA32_FMASK as u32, 0xc000_0084); - - // These should be consecutive - assert_eq!(Msr::IA32_STAR as u32 + 1, Msr::IA32_LSTAR as u32); - assert_eq!(Msr::IA32_LSTAR as u32 + 1, Msr::IA32_CSTAR as u32); - assert_eq!(Msr::IA32_CSTAR as u32 + 1, Msr::IA32_FMASK as u32); - } - - // Note: We can't test the actual read/write methods without running on real hardware - // and having the appropriate privileges. Those would be integration tests. - - // Mock implementation for testing the MsrReadWrite trait - struct TestMsr; - - impl MsrReadWrite for TestMsr { - const MSR: Msr = Msr::IA32_PAT; - } - - #[test] - fn test_msr_read_write_trait() { - // Test that the trait compiles and has the expected methods - // We can't actually call read_raw() without MSR access - assert_eq!(TestMsr::MSR as u32, 0x277); - } - - #[test] - fn test_msr_as_u32_conversion() { - // Test that we can convert MSR enum to u32 properly - let msrs = [ - Msr::IA32_FEATURE_CONTROL, - Msr::IA32_VMX_BASIC, - Msr::IA32_EFER, - Msr::IA32_LSTAR, - ]; - - for msr in msrs.iter() { - let value = *msr as u32; - assert!(value > 0); - // Values should be reasonable MSR numbers - assert!(value < 0xffff_ffff); - } - } -} diff --git a/src/regs/mod.rs b/src/regs/mod.rs index 4f20052..d049f8c 100644 --- a/src/regs/mod.rs +++ b/src/regs/mod.rs @@ -83,6 +83,26 @@ macro_rules! save_regs_to_stack { push rcx push rax" }; + (norax) => { + " + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rbp + sub rsp, 8 + push rbx + push rdx + push rcx + sub rsp, 8 + " + }; } macro_rules! restore_regs_from_stack { @@ -105,4 +125,23 @@ macro_rules! restore_regs_from_stack { pop r14 pop r15" }; + (norax) => { + " + add rsp, 8 + pop rcx + pop rdx + pop rbx + add rsp, 8 + pop rbp + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15" + }; } diff --git a/src/svm/definitions.rs b/src/svm/definitions.rs new file mode 100644 index 0000000..9c452c7 --- /dev/null +++ b/src/svm/definitions.rs @@ -0,0 +1,207 @@ +use core::convert::TryFrom; + +#[derive(Debug, Clone, Copy)] +#[allow(non_camel_case_types)] +pub enum SvmExitCode { + CR_READ(u8), + CR_WRITE(u8), + DR_READ(u8), + DR_WRITE(u8), + EXCP(u8), + INTR, + NMI, + SMI, + INIT, + VINTR, + CR0_SEL_WRITE, + IDTR_READ, + GDTR_READ, + LDTR_READ, + TR_READ, + IDTR_WRITE, + GDTR_WRITE, + LDTR_WRITE, + TR_WRITE, + RDTSC, + RDPMC, + PUSHF, + POPF, + CPUID, + RSM, + IRET, + SWINT, + INVD, + PAUSE, + HLT, + INVLPG, + INVLPGA, + IOIO, + MSR, + TASK_SWITCH, + FERR_FREEZE, + SHUTDOWN, + VMRUN, + VMMCALL, + VMLOAD, + VMSAVE, + STGI, + CLGI, + SKINIT, + RDTSCP, + ICEBP, + WBINVD, + MONITOR, + MWAIT, + MWAIT_CONDITIONAL, + XSETBV, + RDPRU, + EFER_WRITE_TRAP, + CR_WRITE_TRAP(u8), + INVLPGB, + INVLPGB_ILLEGAL, + INVPCID, + MCOMMIT, + TLBSYNC, + NPF, + AVIC_INCOMPLETE_IPI, + AVIC_NOACCEL, + VMGEXIT, + INVALID, + BUSY, +} + +impl TryFrom for SvmExitCode { + type Error = u64; + fn try_from(val: u64) -> Result { + match val as i64 { + 0x00..=0x0F => Ok(Self::CR_READ(val as u8)), + 0x10..=0x1F => Ok(Self::CR_WRITE(val as u8 - 0x10)), + 0x20..=0x2F => Ok(Self::DR_READ(val as u8 - 0x20)), + 0x30..=0x3F => Ok(Self::DR_WRITE(val as u8 - 0x30)), + 0x40..=0x5F => Ok(Self::EXCP(val as u8 - 0x40)), + 0x60 => Ok(Self::INTR), + 0x61 => Ok(Self::NMI), + 0x62 => Ok(Self::SMI), + 0x63 => Ok(Self::INIT), + 0x64 => Ok(Self::VINTR), + 0x65 => Ok(Self::CR0_SEL_WRITE), + 0x66 => Ok(Self::IDTR_READ), + 0x67 => Ok(Self::GDTR_READ), + 0x68 => Ok(Self::LDTR_READ), + 0x69 => Ok(Self::TR_READ), + 0x6A => Ok(Self::IDTR_WRITE), + 0x6B => Ok(Self::GDTR_WRITE), + 0x6C => Ok(Self::LDTR_WRITE), + 0x6D => Ok(Self::TR_WRITE), + 0x6E => Ok(Self::RDTSC), + 0x6F => Ok(Self::RDPMC), + 0x70 => Ok(Self::PUSHF), + 0x71 => Ok(Self::POPF), + 0x72 => Ok(Self::CPUID), + 0x73 => Ok(Self::RSM), + 0x74 => Ok(Self::IRET), + 0x75 => Ok(Self::SWINT), + 0x76 => Ok(Self::INVD), + 0x77 => Ok(Self::PAUSE), + 0x78 => Ok(Self::HLT), + 0x79 => Ok(Self::INVLPG), + 0x7A => Ok(Self::INVLPGA), + 0x7B => Ok(Self::IOIO), + 0x7C => Ok(Self::MSR), + 0x7D => Ok(Self::TASK_SWITCH), + 0x7E => Ok(Self::FERR_FREEZE), + 0x7F => Ok(Self::SHUTDOWN), + 0x80 => Ok(Self::VMRUN), + 0x81 => Ok(Self::VMMCALL), + 0x82 => Ok(Self::VMLOAD), + 0x83 => Ok(Self::VMSAVE), + 0x84 => Ok(Self::STGI), + 0x85 => Ok(Self::CLGI), + 0x86 => Ok(Self::SKINIT), + 0x87 => Ok(Self::RDTSCP), + 0x88 => Ok(Self::ICEBP), + 0x89 => Ok(Self::WBINVD), + 0x8A => Ok(Self::MONITOR), + 0x8B => Ok(Self::MWAIT), + 0x8C => Ok(Self::MWAIT_CONDITIONAL), + 0x8D => Ok(Self::XSETBV), + 0x8E => Ok(Self::RDPRU), + 0x8F => Ok(Self::EFER_WRITE_TRAP), + 0x90..=0x9F => Ok(Self::CR_WRITE_TRAP(val as u8 - 0x90)), + 0xA0 => Ok(Self::INVLPGB), + 0xA1 => Ok(Self::INVLPGB_ILLEGAL), + 0xA2 => Ok(Self::INVPCID), + 0xA3 => Ok(Self::MCOMMIT), + 0xA4 => Ok(Self::TLBSYNC), + 0x400 => Ok(Self::NPF), + 0x401 => Ok(Self::AVIC_INCOMPLETE_IPI), + 0x402 => Ok(Self::AVIC_NOACCEL), + 0x403 => Ok(Self::VMGEXIT), + -1 => Ok(Self::INVALID), + -2 => Ok(Self::BUSY), + _ => Err(val), + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +#[allow(non_camel_case_types)] +pub enum SvmIntercept { + // 0x0C (vector 3) + INTR, + NMI, + SMI, + INIT, + VINTR, + CR0_SEL_WRITE, + IDTR_READ, + GDTR_READ, + LDTR_READ, + TR_READ, + IDTR_WRITE, + GDTR_WRITE, + LDTR_WRITE, + TR_WRITE, + RDTSC, + RDPMC, + PUSHF, + POPF, + CPUID, + RSM, + IRET, + SWINT, + INVD, + PAUSE, + HLT, + INVLPG, + INVLPGA, + IOIO_PROT, + MSR_PROT, + TASK_SWITCH, + FERR_FREEZE, + SHUTDOWN, + // 0x10 (vector 4) + VMRUN, + VMMCALL, + VMLOAD, + VMSAVE, + STGI, + CLGI, + SKINIT, + RDTSCP, + ICEBP, + WBINVD, + MONITOR, + MWAIT, + MWAIT_CONDITIONAL, + XSETBV, + RDPRU, + EFER_WRITE_TRAP, + // 0x14 (vector 5) + INVLPGB, + INVLPGB_ILLEGAL, + INVPCID, + MCOMMIT, + TLBSYNC, +} diff --git a/src/svm/flags.rs b/src/svm/flags.rs new file mode 100644 index 0000000..d57af8a --- /dev/null +++ b/src/svm/flags.rs @@ -0,0 +1,142 @@ +use bit_field::BitField; +use bitflags::bitflags; + +use crate::msr::{Msr, MsrReadWrite}; + +bitflags! { + /// VM_CR MSR flags. + pub struct VmCrFlags: u64 { + /// If set, disables HDT and certain internal debug features. + const DPD = 1 << 0; + /// If set, non-intercepted INIT signals are converted into an #SX + /// exception. + const R_INIT = 1 << 1; + /// If set, disables A20 masking. + const DIS_A20M = 1 << 2; + /// When this bit is set, writes to LOCK and SVMDIS are silently ignored. + /// When this bit is clear, VM_CR bits 3 and 4 can be written. Once set, + /// LOCK can only be cleared using the SVM_KEY MSR (See Section 15.31.) + /// This bit is not affected by INIT or SKINIT. + const LOCK = 1 << 3; + /// When this bit is set, writes to EFER treat the SVME bit as MBZ. When + /// this bit is clear, EFER.SVME can be written normally. This bit does + /// not prevent CPUID from reporting that SVM is available. Setting + /// SVMDIS while EFER.SVME is 1 generates a #GP fault, regardless of the + /// current state of VM_CR.LOCK. This bit is not affected by SKINIT. It + /// is cleared by INIT when LOCK is cleared to 0; otherwise, it is not + /// affected. + const SVMDIS = 1 << 4; + } +} + +/// The VM_CR MSR controls certain global aspects of SVM. +pub struct VmCr; + +impl MsrReadWrite for VmCr { + const MSR: Msr = Msr::VM_CR; +} + +impl VmCr { + pub fn read() -> VmCrFlags { + VmCrFlags::from_bits_truncate(Self::read_raw()) + } +} + +bitflags! { + /// The VMCB Clean field (VMCB offset 0C0h, bits 31:0) controls which guest + /// register values are loaded from the VMCB state cache on VMRUN. + pub struct VmcbCleanBits: u32 { + /// Intercepts: all the intercept vectors, TSC offset, Pause Filter Count + const I = 1 << 0; + /// IOMSRPM: IOPM_BASE, MSRPM_BASE + const IOPM = 1 << 1; + /// ASID + const ASID = 1 << 2; + /// V_TPR, V_IRQ, V_INTR_PRIO, V_IGN_TPR, V_INTR_MASKING, V_INTR_VECTOR + /// (Offset 60h–67h) + const TPR = 1 << 3; + /// Nested Paging: NCR3, G_PAT + const NP = 1 << 4; + /// CR0, CR3, CR4, EFER + const CR_X = 1 << 5; + /// DR6, DR7 + const DR_X = 1 << 6; + /// GDT/IDT Limit and Base + const DT = 1 << 7; + /// CS/DS/SS/ES Sel/Base/Limit/Attr, CPL + const SEG = 1 << 8; + /// CR2 + const CR2 = 1 << 9; + /// DbgCtlMsr, br_from/to, lastint_from/to + const LBR = 1 << 10; + /// AVIC APIC_BAR; AVIC APIC_BACKING_PAGE, AVIC PHYSICAL_TABLE and AVIC + /// LOGICAL_TABLE Pointers + const AVIC = 1 << 11; + /// S_CET, SSP, ISST_ADDR + const CET = 1 << 12; + /// The hypervisor has not modified the VMCB. + const UNMODIFIED = 0xffff_ffff; + } +} + +bitflags! { + /// EXITINTINFO/EVENTINJ field in the VMCB. + pub struct VmcbIntInfo: u32 { + /// Error Code Valid + const ERROR_CODE = 1 << 11; + /// Valid + const VALID = 1 << 31; + } +} + +#[repr(u32)] +#[derive(Debug)] +pub enum InterruptType { + /// External or virtual interrupt (INTR) + External = 0, + /// Non-maskable interrupt (NMI) + NMI = 2, + /// Exception (fault or trap) + Exception = 3, + /// Software interrupt (caused by INTn instruction) + SoftIntr = 4, +} + +impl VmcbIntInfo { + fn has_error_code(vector: u8) -> bool { + use x86::irq::*; + matches!( + vector, + DOUBLE_FAULT_VECTOR + | INVALID_TSS_VECTOR + | SEGMENT_NOT_PRESENT_VECTOR + | STACK_SEGEMENT_FAULT_VECTOR + | GENERAL_PROTECTION_FAULT_VECTOR + | PAGE_FAULT_VECTOR + | ALIGNMENT_CHECK_VECTOR + ) + } + + pub fn from(int_type: InterruptType, vector: u8) -> Self { + let mut bits = vector as u32; + bits.set_bits(8..11, int_type as u32); + let mut info = Self::from_bits_retain(bits) | Self::VALID; + if Self::has_error_code(vector) { + info |= Self::ERROR_CODE; + } + info + } +} + +#[repr(u8)] +#[derive(Debug)] +pub enum VmcbTlbControl { + /// Do not flush + DoNotFlush = 0, + /// Flush entire TLB (Should be used only on legacy hardware.) + FlushAll = 0x01, + /// Flush this guest's TLB entries + FlushAsid = 0x03, + /// Flush this guest's non-global TLB entries + FlushAsidNonGlobal = 0x07, +} diff --git a/src/svm/frame.rs b/src/svm/frame.rs new file mode 100644 index 0000000..7edeb10 --- /dev/null +++ b/src/svm/frame.rs @@ -0,0 +1,78 @@ +use core::marker::PhantomData; + +use axaddrspace::{AxMmHal, HostPhysAddr}; +use axerrno::{AxResult, ax_err_type}; +use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; + +/// A contiguous block of physical memory frames that will be automatically +/// deallocated when dropped. Used for hardware structures requiring contiguous +/// physical memory (e.g., IOPM, MSRPM). +#[derive(Debug)] +pub struct ContiguousPhysFrames { + start_paddr: Option, + frame_count: usize, + _marker: PhantomData, +} + +impl ContiguousPhysFrames { + pub fn alloc(frame_count: usize) -> AxResult { + let start_paddr = axvisor_api::memory::alloc_contiguous_frames(frame_count, 1) + .ok_or_else(|| ax_err_type!(NoMemory, "allocate contiguous frames failed"))?; + + assert_ne!(start_paddr.as_usize(), 0); + Ok(Self { + start_paddr: Some(start_paddr), + frame_count, + _marker: PhantomData, + }) + } + + pub fn alloc_zero(frame_count: usize) -> AxResult { + let mut frames = Self::alloc(frame_count)?; + frames.fill(0); + Ok(frames) + } + + pub const unsafe fn uninit() -> Self { + Self { + start_paddr: None, + frame_count: 0, + _marker: PhantomData, + } + } + + pub fn start_paddr(&self) -> HostPhysAddr { + self.start_paddr + .expect("uninitialized ContiguousPhysFrames") + } + + pub fn frame_count(&self) -> usize { + self.frame_count + } + + pub fn size(&self) -> usize { + PAGE_SIZE * self.frame_count + } + + pub fn as_mut_ptr(&self) -> *mut u8 { + H::phys_to_virt(self.start_paddr()).as_mut_ptr() + } + + pub fn fill(&mut self, byte: u8) { + unsafe { + core::ptr::write_bytes(self.as_mut_ptr(), byte, self.size()); + } + } +} + +impl Drop for ContiguousPhysFrames { + fn drop(&mut self) { + if let Some(start_paddr) = self.start_paddr { + axvisor_api::memory::dealloc_contiguous_frames(start_paddr, self.frame_count); + debug!( + "[AxVM] deallocated ContiguousPhysFrames({:#x}, {} frames)", + start_paddr, self.frame_count + ); + } + } +} diff --git a/src/svm/instructions.rs b/src/svm/instructions.rs new file mode 100644 index 0000000..aee2daf --- /dev/null +++ b/src/svm/instructions.rs @@ -0,0 +1,71 @@ +//! AMD-SVM 指令封装 +//! +//! 资料来源:AMD64 APM Vol-2 “Secure Virtual Machine” §15 & §16 +//! - VMRUN / VMLOAD / VMSAVE / STGI / CLGI / INVLPGA +//! - 这些指令 **不会像 VMX 指令那样用 RFLAGS.CF / ZF 返回错误**; +//! 如果控制区不合法,会直接 #GP(0) 或 #UD,所以简单包一层即可。 + +use core::arch::asm; + +/// SVM 指令基本都不会返回状态;如果失败直接 #UD/#GP, +/// 我们姑且用最简单的 `Result<(), ()>` 占位,后续如需 +/// 细分错误再扩展枚举即可。 +pub type Result = core::result::Result; + +/// 进入来宾:`vmrun rax` +#[inline(always)] +pub unsafe fn vmrun(vmcb_pa: u64) -> ! { + asm!( + "vmrun {0}", + in(reg) vmcb_pa, + options(noreturn, nostack), + ) +} + +/// 保存 Host-state:`vmsave rax` +#[inline(always)] +pub unsafe fn vmsave(vmcb_pa: u64) -> Result { + asm!( + "vmsave {0}", + in(reg) vmcb_pa, + options(nostack, preserves_flags), + ); + Ok(()) +} + +/// 恢复 Host-state:`vmload rax` +#[inline(always)] +pub unsafe fn vmload(vmcb_pa: u64) -> Result { + asm!( + "vmload {0}", + in(reg) vmcb_pa, + options(nostack, preserves_flags), + ); + Ok(()) +} + +/// 允许全局中断 (`STGI`) +#[inline(always)] +pub unsafe fn stgi() { + asm!("stgi", options(nostack, preserves_flags)); +} + +/// 禁止全局中断 (`CLGI`) +#[inline(always)] +pub unsafe fn clgi() { + asm!("clgi", options(nostack, preserves_flags)); +} + +/// `INVLpga` —— 按 (guest-virt addr, ASID) 刷 TLB +/// +/// * `addr` : Guest 虚拟地址 (通常传 0 代表“全页表”) +/// * `asid` : Address-Space ID,0 触发全局 flush +#[inline(always)] +pub unsafe fn invlpga(addr: u64, asid: u32) { + asm!( + "invlpga {0}, {1:e}", + in(reg) addr, + in(reg) asid, + options(nostack, preserves_flags), + ); +} diff --git a/src/svm/mod.rs b/src/svm/mod.rs new file mode 100644 index 0000000..4e31162 --- /dev/null +++ b/src/svm/mod.rs @@ -0,0 +1,24 @@ +mod definitions; // SvmExitCode / Intercept 位 +mod flags; +mod frame; +mod instructions; // vmrun / vmload / vmsave / stgi / clgi / invlpga +mod percpu; // SvmPerCpuState(EFER.SVME & HSAVE) +mod structs; // IOPm / MSRPm / Vmcb 封装 +mod vcpu; // SvmVcpu(核心逻辑) +mod vmcb; +// VMCB 读写 & VMEXIT 解码 + +pub use self::definitions::{SvmExitCode, SvmIntercept}; +pub use self::percpu::SvmPerCpuState as SvmArchPerCpuState; +pub use self::vcpu::SvmVcpu as SvmArchVCpu; + +pub fn has_hardware_support() -> bool { + if let Some(ext) = raw_cpuid::CpuId::new().get_extended_processor_and_feature_identifiers() { + ext.has_svm() + } else { + false + } +} + +/* 额外:SVM 没有 VMX-instruction-error / VMCS revision id 概念, + * 如需调试可在 vmcb::exit_code() 中直接查看 SvmExitCode。*/ diff --git a/src/svm/percpu.rs b/src/svm/percpu.rs new file mode 100644 index 0000000..8cc4504 --- /dev/null +++ b/src/svm/percpu.rs @@ -0,0 +1,101 @@ +//! AMD-SVM per-CPU enable/disable logic +//! +//! References (AMD APM v2 *System Programming*): +//! https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24593.pdf +//! • § 15.4 Enabling SVM +//! +//! Summary of procedure (compared to Intel VMX): +//! 1. check if the CPU supports SVM +//! 2. Allocate Host-Save Area (HSAVE) and write its physical address to `MSR_VM_HSAVE_PA` +//! 3. Set `EFER.SVME` (bit 12) to enable SVM mode +//! 4. Clearing `EFER.SVME` disables SVM (no need for VMXON/VMXOFF equivalents) + +use axerrno::{AxResult, ax_err, ax_err_type}; +use axvcpu::{AxArchPerCpu, AxVCpuHal}; +use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; +use raw_cpuid::CpuId; +use x86_64::registers::control::{Efer, EferFlags}; + +use crate::msr::Msr; +use crate::svm::has_hardware_support; +use axaddrspace::PhysFrame; + +/// Per-core state for AMD-SVM + +// (AMD64 APM Vol.2, Section 15.30.4) +//The 64-bit read/write VM_HSAVE_PA MSR holds the physical address of a 4KB block of memory where VMRUN saves host state +pub struct SvmPerCpuState { + hsave_page: PhysFrame, +} + +impl AxArchPerCpu for SvmPerCpuState { + fn new(_cpu_id: usize) -> AxResult { + Ok(Self { + hsave_page: unsafe { PhysFrame::uninit() }, + }) + } + + /// Returns true if SVM is enabled on this core (EFER.SVME == 1) + fn is_enabled(&self) -> bool { + let efer = Msr::IA32_EFER.read(); + EferFlags::from_bits_truncate(efer).contains(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE) + } + + fn hardware_enable(&mut self) -> AxResult { + if !has_hardware_support() { + return ax_err!(Unsupported, "CPU does not support AMD-SVM"); + } + if self.is_enabled() { + return ax_err!(ResourceBusy, "SVM already enabled"); + } + + // make sure all perf counters are off + unsafe { + /// Core Performance Event-Select Register (PerfEvtSeln), Counter Enable (bit 22) + const PERF_EVT_SEL_EN: u64 = 1 << 22; + Msr::PERF_EVT_SEL0.write(Msr::PERF_EVT_SEL0.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL1.write(Msr::PERF_EVT_SEL1.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL2.write(Msr::PERF_EVT_SEL2.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL3.write(Msr::PERF_EVT_SEL3.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL4.write(Msr::PERF_EVT_SEL4.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL5.write(Msr::PERF_EVT_SEL5.read() & !PERF_EVT_SEL_EN); + } + + // Enable XSAVE/XRSTOR. + crate::xstate::enable_xsave(); + + // Allocate & register Host-Save Area + self.hsave_page = PhysFrame::alloc_zero()?; + let hsave_pa = self.hsave_page.start_paddr().as_usize() as u64; + unsafe { + Msr::VM_HSAVE_PA.write(hsave_pa); + } + + // Set EFER.SVME to enable SVM + let mut efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); + efer.insert(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE); // bit 12 + unsafe { + Msr::IA32_EFER.write(efer.bits()); + } + + info!("[AxVM] SVM enabled (HSAVE @ {:#x}).", hsave_pa); + Ok(()) + } + + fn hardware_disable(&mut self) -> AxResult { + if !self.is_enabled() { + return ax_err!(BadState, "SVM is not enabled"); + } + unsafe { + // 1) Clear SVME bit + let mut efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); + efer.remove(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE); + Msr::IA32_EFER.write(efer.bits()); + + // 2) Clear HSAVE pointer + Msr::VM_HSAVE_PA.write(0); + } + info!("[AxVM] SVM disabled."); + Ok(()) + } +} diff --git a/src/svm/structs.rs b/src/svm/structs.rs new file mode 100644 index 0000000..d21d497 --- /dev/null +++ b/src/svm/structs.rs @@ -0,0 +1,204 @@ +//! AMD-SVM helper structs +//! https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24593.pdf + +#![allow(dead_code)] + +use crate::svm::vmcb::VmcbStruct; + +use super::frame::ContiguousPhysFrames; +use axaddrspace::HostPhysAddr; +use axaddrspace::PhysFrame; +use axerrno::AxResult; +use axvcpu::AxVCpuHal; +use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; + +/// Virtual-Machine Control Block (VMCB) +/// One 4 KiB page per vCPU: [control-area | save-area]. +#[derive(Debug)] +pub struct VmcbFrame { + page: PhysFrame, +} + +impl VmcbFrame { + pub const unsafe fn uninit() -> Self { + Self { + page: unsafe { PhysFrame::uninit() }, + } + } + + pub fn new() -> AxResult { + Ok(Self { + page: PhysFrame::alloc_zero()?, + }) + } + + pub fn phys_addr(&self) -> HostPhysAddr { + self.page.start_paddr() + } + + pub fn as_mut_ptr(&self) -> *mut u8 { + self.page.as_mut_ptr() + } + + pub fn as_mut_ptr_vmcb(&self) -> *mut VmcbStruct { + self.page.as_mut_ptr() as *mut VmcbStruct + } + + pub fn hex_dump(&self) -> alloc::string::String { + use alloc::string::ToString; + + // dump 16 bytes per line, 256 lines + let mut dump = alloc::string::String::new(); + for i in 0..256 { + let offset = i * 16; + let line = unsafe { + let ptr = self.as_mut_ptr().add(offset); + let bytes = core::slice::from_raw_parts(ptr, 16); + let hex_bytes: alloc::string::String = bytes + .iter() + .map(|b| { + if *b == 0 { + " --".to_string() + } else { + alloc::format!(" {:02x}", b) + } + }) + .collect(); + alloc::format!("{:04x}:{}\n", offset, hex_bytes) + }; + dump.push_str(&line); + } + + dump + } +} + +// (AMD64 APM Vol.2, Section 15.10) +// The I/O Permissions Map (IOPM) occupies 12 Kbytes of contiguous physical memory. +// The map is structured as a linear array of 64K+3 bits (two 4-Kbyte pages, and the first three bits of a third 4-Kbyte page) and must be aligned on a 4-Kbyte boundary; +#[derive(Debug)] +pub struct IOPm { + frames: ContiguousPhysFrames, // 3 contiguous frames (12KB) +} + +impl IOPm { + pub fn passthrough_all() -> AxResult { + let mut frames = ContiguousPhysFrames::::alloc_zero(3)?; + + info!( + "IOPM allocated at phys addr: {:#x}", + frames.start_paddr().as_usize() + ); + + // Set first 3 bits of third frame to intercept (ports > 0xFFFF) + let third_frame_start = frames.as_mut_ptr() as usize + 2 * PAGE_SIZE; + unsafe { + let third_byte = third_frame_start as *mut u8; + *third_byte |= 0x07; // Set bits 0-2 (0b00000111) + } + + Ok(Self { frames }) + } + + #[allow(unused)] + pub fn intercept_all() -> AxResult { + let mut frames = ContiguousPhysFrames::::alloc(3)?; + frames.fill(0xFF); // Set all bits to 1 (intercept) + Ok(Self { frames }) + } + + pub fn phys_addr(&self) -> HostPhysAddr { + self.frames.start_paddr() + } + pub fn as_mut_ptr(&self) -> *mut u8 { + self.frames.as_mut_ptr() + } + + pub fn set_intercept(&mut self, port: u32, intercept: bool) { + let byte_index = port as usize / 8; + let bit_offset = (port % 8) as u8; + let iopm_ptr = self.frames.as_mut_ptr(); + + unsafe { + let byte_ptr = iopm_ptr.add(byte_index); + if intercept { + *byte_ptr |= 1 << bit_offset; + } else { + *byte_ptr &= !(1 << bit_offset); + } + } + } + + pub fn set_intercept_of_range(&mut self, port_base: u32, count: u32, intercept: bool) { + for port in port_base..port_base + count { + self.set_intercept(port, intercept) + } + } +} +// (AMD64 APM Vol.2, Section 15.10) +// The VMM can intercept RDMSR and WRMSR instructions by means of the SVM MSR permissions map (MSRPM) on a per-MSR basis +// The four separate bit vectors must be packed together and located in two contiguous physical pages of memory. +#[derive(Debug)] +pub struct MSRPm { + frames: ContiguousPhysFrames, +} + +impl MSRPm { + pub fn passthrough_all() -> AxResult { + Ok(Self { + frames: ContiguousPhysFrames::alloc_zero(2)?, + }) + } + + #[allow(unused)] + pub fn intercept_all() -> AxResult { + let mut frames = ContiguousPhysFrames::alloc(2)?; + frames.fill(0xFF); + Ok(Self { frames }) + } + + pub fn phys_addr(&self) -> HostPhysAddr { + self.frames.start_paddr() + } + pub fn as_mut_ptr(&self) -> *mut u8 { + self.frames.as_mut_ptr() + } + + pub fn set_intercept(&mut self, msr: u32, is_write: bool, intercept: bool) { + let (segment, msr_low) = if msr <= 0x1fff { + (0u32, msr) + } else if (0xc000_0000..=0xc000_1fff).contains(&msr) { + (1u32, msr & 0x1fff) + } else if (0xc001_0000..=0xc001_1fff).contains(&msr) { + (2u32, msr & 0x1fff) + } else { + unreachable!("MSR {:#x} Not supported by MSRPM", msr); + }; + + let base_offset = (segment * 2048) as usize; + + let byte_in_segment = (msr_low as usize) / 4; + let bit_pair_offset = ((msr_low & 0b11) * 2) as u8; // 0,2,4,6 + let bit_offset = bit_pair_offset + is_write as u8; // +0=读, +1=写 + + unsafe { + let byte_ptr = self.frames.as_mut_ptr().add(base_offset + byte_in_segment); + + let old = core::ptr::read_volatile(byte_ptr); + let new = if intercept { + old | (1u8 << bit_offset) + } else { + old & !(1u8 << bit_offset) + }; + core::ptr::write_volatile(byte_ptr, new); + } + } + + pub fn set_read_intercept(&mut self, msr: u32, intercept: bool) { + self.set_intercept(msr, false, intercept); + } + + pub fn set_write_intercept(&mut self, msr: u32, intercept: bool) { + self.set_intercept(msr, true, intercept); + } +} diff --git a/src/svm/vcpu.rs b/src/svm/vcpu.rs new file mode 100644 index 0000000..25cea23 --- /dev/null +++ b/src/svm/vcpu.rs @@ -0,0 +1,812 @@ +use alloc::collections::VecDeque; +use axvisor_api::vmm::{VCpuId, VMId}; +use bit_field::BitField; +use x86::io::outl; +use core::arch::asm; +use core::fmt::{Debug, Formatter, Result}; +use core::{arch::naked_asm, mem::size_of}; +use x86::controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}; +use x86::dtables::{self, DescriptorTablePointer}; +use x86::msr::IA32_GS_BASE; +use x86::segmentation::SegmentSelector; +use x86_64::registers::control::{Cr0, Cr0Flags, Cr3, Cr4, Cr4Flags, EferFlags}; + +use axaddrspace::device::AccessWidth; +use axaddrspace::{GuestPhysAddr, GuestVirtAddr, HostPhysAddr, NestedPageFaultInfo}; +use axerrno::{AxResult, ax_err, ax_err_type}; +use axvcpu::{AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; +use tock_registers::interfaces::{Debuggable, ReadWriteable, Readable, Writeable}; + +use super::definitions::SvmExitCode; +use super::structs::{IOPm, MSRPm, VmcbFrame}; +use super::vmcb::{NestedCtl, SvmExitInfo, VmcbCleanBits, VmcbTlbControl, set_vmcb_segment}; +use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters, xstate::XState}; + +const QEMU_EXIT_PORT: u16 = 0x604; +const QEMU_EXIT_MAGIC: u64 = 0x2000; + +#[derive(PartialEq, Eq, Debug)] +pub enum VmCpuMode { + Real, + Protected, + Compatibility, // IA-32E mode (CS.L = 0) + Mode64, // IA-32E mode (CS.L = 1) +} + +/// States loaded/stored during VMLOAD/VMSAVE instructions. +/// +/// VMLOAD/VMSAVE only load/store the guest version of these states from/to the +/// VMCB, but not the host version. Therefore, we need to keep track of the host +/// versions separately. +#[derive(Default)] +pub struct VmLoadSaveStates { + /// The base address of the FS segment. + pub fs_base: u64, + /// The base address of the GS segment. + pub gs_base: u64, + /// The value of the KERNEL_GS_BASE MSR. + pub kernel_gs_base: u64, + /// The value of the SYSENTER_CS MSR. + pub sysenter_cs: u64, + /// The value of the SYSENTER_ESP MSR. + pub sysenter_esp: u64, + /// The value of the SYSENTER_EIP MSR. + pub sysenter_eip: u64, + /// The value of the STAR MSR. + pub star: u64, + /// The value of the LSTAR MSR. + pub lstar: u64, + /// The value of the CSTAR MSR. + pub cstar: u64, + /// The value of the SF_MASK MSR. + pub sfmask: u64, + /// The local descriptor table register. + pub ldtr: u16, + /// The task register. + pub tr: u16, +} + +// Some fields are not loaded/saved because they are not used in ArceOS and +// AxVisor, we keep their fields and loader/savers for future use. +#[allow(dead_code)] +impl VmLoadSaveStates { + /// Save the current FS and GS (including KERNEL_GS_BASE) bases. + #[inline(always)] + pub fn save_fs_gs(&mut self) { + self.fs_base = Msr::IA32_FS_BASE.read(); + self.gs_base = Msr::IA32_GS_BASE.read(); + self.kernel_gs_base = Msr::IA32_KERNEL_GSBASE.read(); + } + + /// Save the current SYSENTER MSRs. + #[inline(always)] + pub fn save_sysenter(&mut self) { + self.sysenter_cs = Msr::IA32_SYSENTER_CS.read(); + self.sysenter_esp = Msr::IA32_SYSENTER_ESP.read(); + self.sysenter_eip = Msr::IA32_SYSENTER_EIP.read(); + } + + /// Save the current SYSCALL MSRs. + #[inline(always)] + pub fn save_syscall(&mut self) { + self.star = Msr::IA32_STAR.read(); + self.lstar = Msr::IA32_LSTAR.read(); + self.cstar = Msr::IA32_CSTAR.read(); + self.sfmask = Msr::IA32_FMASK.read(); + } + + /// Save the current LDTR and TR registers. + #[inline(always)] + pub fn save_segs(&mut self) { + let ldtr: u16; + let tr: u16; + + unsafe { + asm!( + "sldt {0:x}", + "str {1:x}", + out(reg) ldtr, + out(reg) tr, + ); + } + + self.ldtr = ldtr; + self.tr = tr; + } + + /// Save all VMLOAD/VMSAVE related states. + pub fn save_all(&mut self) { + self.save_fs_gs(); + self.save_sysenter(); + self.save_syscall(); + self.save_segs(); + } + + /// Load the saved FS and GS (including KERNEL_GS_BASE) bases. + #[inline(always)] + pub fn load_fs_gs(&self) { + unsafe { + Msr::IA32_FS_BASE.write(self.fs_base); + Msr::IA32_GS_BASE.write(self.gs_base); + Msr::IA32_KERNEL_GSBASE.write(self.kernel_gs_base); + } + } + + /// Load the saved SYSENTER MSRs. + #[inline(always)] + pub fn load_sysenter(&self) { + unsafe { + Msr::IA32_SYSENTER_CS.write(self.sysenter_cs); + Msr::IA32_SYSENTER_ESP.write(self.sysenter_esp); + Msr::IA32_SYSENTER_EIP.write(self.sysenter_eip); + } + } + + /// Load the saved SYSCALL MSRs. + #[inline(always)] + pub fn load_syscall(&self) { + unsafe { + Msr::IA32_STAR.write(self.star); + Msr::IA32_LSTAR.write(self.lstar); + Msr::IA32_CSTAR.write(self.cstar); + Msr::IA32_FMASK.write(self.sfmask); + } + } + + /// Load all VMLOAD/VMSAVE related states. + #[inline(always)] + pub fn load_segs(&self) { + let ldtr = self.ldtr; + let tr = self.tr; + + unsafe { + asm!( + "lldt {0:x}", + "ltr {1:x}", + in(reg) ldtr, + in(reg) tr, + ); + } + } + + /// Load all VMLOAD/VMSAVE related states. + pub fn load_all(&self) { + self.load_fs_gs(); + self.load_sysenter(); + self.load_syscall(); + self.load_segs(); + } + + /// Create a new [`VmLoadSaveStates`] instance from current hardware states. + pub fn new_from_hardware() -> Self { + let mut states = Self::default(); + states.save_all(); + states + } +} + +#[repr(C)] +pub struct SvmVcpu { + // DO NOT modify `guest_regs` and `host_stack_top` and their order unless you do know what you are doing! + // DO NOT add anything before or between them unless you do know what you are doing! + guest_regs: GeneralRegisters, + #[allow(dead_code)] // actually used in asm! + host_stack_top: u64, + launched: bool, + vmcb: VmcbFrame, + load_save_states: VmLoadSaveStates, + iopm: IOPm, + msrpm: MSRPm, + pending_events: VecDeque<(u8, Option)>, + xstate: XState, + entry: Option, + npt_root: Option, + // is_host: bool, temporary removed because we don't care about type 1.5 now +} + +impl SvmVcpu { + /// Create a new [`SvmVcpu`]. + pub fn new() -> AxResult { + let vcpu = Self { + guest_regs: GeneralRegisters::default(), + host_stack_top: 0, + launched: false, + vmcb: VmcbFrame::new()?, + load_save_states: VmLoadSaveStates::default(), + iopm: IOPm::passthrough_all()?, + msrpm: MSRPm::passthrough_all()?, + pending_events: VecDeque::with_capacity(8), + xstate: XState::new(), + entry: None, + npt_root: None, + // is_host: false, + }; + info!("[HV] created SvmVcpu(vmcb: {:#x})", vcpu.vmcb.phys_addr()); + Ok(vcpu) + } + + /// Set the new [`SvmVcpu`] context from guest OS. + // pub fn setup(&mut self, npt_root: HostPhysAddr, entry: GuestPhysAddr) -> AxResult { + // self.setup_vmcb(entry, npt_root)?; + // Ok(()) + // } + + /// No operation is needed for SVM binding. + /// + /// Unlike VMX which requires VMCS to be loaded via VMPTRLD, + /// SVM uses the `VMRUN` instruction and takes the VMCB physical address + /// from the `RAX` register at the moment of execution. + /// + /// Since `RAX` is a volatile register and may be clobbered during normal execution, + /// it is unsafe to set `RAX` earlier and rely on it later. + /// Therefore, the correct place to set `RAX` is right before `VMRUN`, + /// inside the actual launch/resume assembly code. + /// + /// This function is kept for interface consistency but performs no action. + pub fn bind_to_current_processor(&self) -> AxResult { + Ok(()) + } + + /// No operation is needed for SVM unbinding. + /// + /// SVM does not maintain a per-CPU binding state like VMX (e.g., via VMPTRLD). + /// Once `VMEXIT` occurs, the VCPU state is saved to the VMCB, and no + /// unbinding step is required. + /// + /// This function is kept for interface compatibility. + pub fn unbind_from_current_processor(&self) -> AxResult { + Ok(()) + } + + pub fn get_cpu_mode(&self) -> VmCpuMode { + let vmcb = &mut unsafe { self.vmcb.as_vmcb() }.state; + + let ia32_efer = vmcb.efer.get(); + let cs_attr = vmcb.cs.attr.get(); + let cr0 = vmcb.cr0.get(); + + if (ia32_efer & (1 << 10)) != 0 { + if (cs_attr & (1 << 13)) != 0 { + // CS.L = 1 + VmCpuMode::Mode64 + } else { + VmCpuMode::Compatibility + } + } else if (cr0 & (1 << 0)) != 0 { + // CR0.PE = 1 + VmCpuMode::Protected + } else { + VmCpuMode::Real + } + } + + pub fn inner_run(&mut self) -> Option { + // Inject pending events + if self.launched { + self.inject_pending_events().unwrap(); + } + + // Run guest + self.load_guest_xstate(); + + unsafe { + self.svm_run(); + } + + self.load_host_xstate(); + + // Handle vm-exits + let exit_info = self.exit_info().unwrap(); + panic!("VM exit: {:#x?}", exit_info); + + match self.builtin_vmexit_handler(&exit_info) { + Some(result) => { + if result.is_err() { + panic!( + "VmxVcpu failed to handle a VM-exit that should be handled by itself: {:?}, error {:?}, vcpu: {:#x?}", + exit_info.exit_info_1, + result.unwrap_err(), + self + ); + } + None + } + None => Some(exit_info), + } + } + + pub fn exit_info(&self) -> AxResult { + unsafe { self.vmcb.as_vmcb().exit_info() } + } + + pub fn raw_interrupt_exit_info(&self) -> AxResult { + todo!() + } + + // pub fn interrupt_exit_info(&self) -> AxResult { + // todo!() + // } + + // pub fn io_exit_info(&self) -> AxResult { + // todo!() + // } + + pub fn nested_page_fault_info(&self) -> AxResult { + todo!() + } + + pub fn regs(&self) -> &GeneralRegisters { + &self.guest_regs + } + + pub fn regs_mut(&mut self) -> &mut GeneralRegisters { + &mut self.guest_regs + } + + pub fn stack_pointer(&self) -> usize { + todo!() + } + + pub fn set_stack_pointer(&mut self, rsp: usize) { + todo!() + } + + pub fn gla2gva(&self, guest_rip: GuestVirtAddr) -> GuestVirtAddr { + let vmcb = unsafe { self.vmcb.as_vmcb() }; + let cpu_mode = self.get_cpu_mode(); + let seg_base = if cpu_mode == VmCpuMode::Mode64 { + 0 + } else { + vmcb.state.cs.base.get() + }; + guest_rip + seg_base as usize + } + + pub fn get_ptw_info(&self) -> GuestPageWalkInfo { + todo!() + } + + pub fn rip(&self) -> usize { + todo!() + } + + pub fn cs(&self) -> u16 { + todo!() + } + + pub fn advance_rip(&mut self, instr_len: u8) -> AxResult { + todo!() + } + + pub fn queue_event(&mut self, vector: u8, err_code: Option) { + todo!() + } + + pub fn set_interrupt_window(&mut self, enable: bool) -> AxResult { + todo!() + } + + pub fn set_io_intercept_of_range(&mut self, port_base: u32, count: u32, intercept: bool) { + todo!() + } + + pub fn set_msr_intercept_of_range(&mut self, msr: u32, intercept: bool) { + todo!() + } +} + +// Implementation of private methods +impl SvmVcpu { + #[allow(dead_code)] + fn setup_io_bitmap(&mut self) -> AxResult { + todo!() + } + + #[allow(dead_code)] + fn setup_msr_bitmap(&mut self) -> AxResult { + todo!() + } + + fn setup_vmcb(&mut self, entry: GuestPhysAddr, npt_root: HostPhysAddr) -> AxResult { + // Commented out because not implemented yet + // self.setup_io_bitmap()?; + // self.setup_msr_bitmap()?; + self.setup_vmcb_guest(entry)?; + self.setup_vmcb_control(npt_root, true) + } + + fn setup_vmcb_guest(&mut self, entry: GuestPhysAddr) -> AxResult { + info!("[AxVM] Setting up VMCB for guest at {:#x}", entry); + let cr0_val: Cr0Flags = + Cr0Flags::NOT_WRITE_THROUGH | Cr0Flags::CACHE_DISABLE | Cr0Flags::EXTENSION_TYPE; + self.set_cr(0, cr0_val.bits())?; + self.set_cr(4, 0)?; + + let st = &mut unsafe { self.vmcb.as_vmcb() }.state; + + macro_rules! seg { + ($seg:ident, $attr:expr) => { + set_vmcb_segment(&mut st.$seg, 0, $attr); + }; + } + + // CS: P S CODE READ (bit 7, 4, 3, 1) = 0x9a + // seg!(cs, 0x9b); + // st.cs.selector.set(0xf000); + // st.cs.base.set(0xffff0000); + // st.cs.limit.set(0xffff); + // st.cs.attr.set(0x9b); + st.cs.selector.set(0); + st.cs.base.set(0); + st.cs.limit.set(0xffff); + st.cs.attr.set(0x9b); + + // DS ~ SS: P S WRITE (bit 7, 4, 1) = 0x92 + seg!(ds, 0x93); + seg!(es, 0x93); + seg!(fs, 0x93); + seg!(gs, 0x93); + seg!(ss, 0x93); + seg!(ldtr, 0x82); + seg!(tr, 0x8b); + + // GDTR / IDTR + st.gdtr.base.set(0); + st.gdtr.limit.set(0xffff); + st.idtr.base.set(0); + st.idtr.limit.set(0xffff); + + // 关键寄存器与指针 + st.cr3.set(0); + st.dr7.set(0x400); + st.rsp.set(0); + st.rip.set(entry.as_usize() as u64); + st.rflags.set(0x2); // bit1 必须为 1 + st.dr6.set(0xffff0ff0); + + // SYSENTER MSRs + // st.sysenter_cs.set(0); + // st.sysenter_esp.set(0); + // st.sysenter_eip.set(0); + + // MSR / PAT / EFER + st.efer + .set(0 | EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE.bits()); // 必须置 SVME 位 + st.g_pat.set(Msr::IA32_PAT.read()); + + // st.cpl.set(0); + // st.star.set(0); + // st.lstar.set(0); + // st.cstar.set(0); + // st.sfmask.set(0); + // st.kernel_gs_base.set(Msr::IA32_KERNEL_GSBASE.read()); + // st.rax.set(0); // hypervisor 返回值 + + Ok(()) + } + + fn setup_vmcb_control(&mut self, npt_root: HostPhysAddr, is_guest: bool) -> AxResult { + let ct = &mut unsafe { self.vmcb.as_vmcb() }.control; // control-area 速记别名 + // ──────────────────────────────────────────────────────── + // 1) 基本运行环境:Nested Paging / ASID / Clean Bits / TLB + // ──────────────────────────────────────────────────────── + + // ① 开启 Nested Paging(AMD 对应 Intel 的 EPT) + // → set bit 0 of NESTED_CTL + ct.nested_ctl.modify(NestedCtl::NP_ENABLE::SET); + + // ② guest ASID:NPT 使用的 TLB 标签 + ct.guest_asid.set(1); + + // ③ 嵌套 CR3(NPT root PA) + ct.nested_cr3.set(npt_root.as_usize() as u64); + + // ④ Clean-Bits:0 = “全部脏” ⇒ 第一次 VMRUN 必定重新加载 save-area + ct.clean_bits.set(0); + + // ⑤ TLB Control:0 = NONE, 1 = FLUSH-ASID, 3 = FLUSH-ALL + ct.tlb_control + .modify(VmcbTlbControl::CONTROL::FlushGuestTlb); + + ct.int_control.set(1 << 24); // V_INTR_MASKING_MASK + + // ──────────────────────────────────────────────────────── + // 2) 选择要拦截的指令 / 事件 + // (相当于 VMX 的 Pin-based / Primary / Secondary CTLS) + // ──────────────────────────────────────────────────────── + + use super::definitions::SvmIntercept; // 你自己定义的枚举 + + for intc in &[ + SvmIntercept::NMI, // 非屏蔽中断 + SvmIntercept::CPUID, // CPUID 指令 + SvmIntercept::SHUTDOWN, // HLT 时 Triple-Fault + SvmIntercept::VMRUN, // 来宾企图再次 VMRUN + SvmIntercept::VMMCALL, // Hypercall + SvmIntercept::VMLOAD, + SvmIntercept::VMSAVE, + SvmIntercept::STGI, // 设置全局中断 + SvmIntercept::CLGI, // 清除全局中断 + SvmIntercept::SKINIT, // 安全启动 + ] { + ct.set_intercept(*intc); + } + + ct.iopm_base_pa.set(self.iopm.phys_addr().as_usize() as u64); + ct.msrpm_base_pa + .set(self.msrpm.phys_addr().as_usize() as u64); + + Ok(()) + } + // 如果你用 bitfield 方式,也可以: + // ct.intercept_vector3.modify(InterceptVec3::NMI::SET + InterceptVec3::VINTR::SET); + + fn get_paging_level(&self) -> usize { + todo!() + } +} +// Implementaton for type1.5 hypervisor +// #[cfg(feature = "type1_5")] +impl SvmVcpu { + pub fn set_cr(&mut self, cr_idx: usize, val: u64) -> AxResult { + let vmcb = unsafe { self.vmcb.as_vmcb() }; + info!("Setting CR{} to {:#x}", cr_idx, val); + + match cr_idx { + 0 => vmcb.state.cr0.set(val), + 3 => vmcb.state.cr3.set(val), + 4 => vmcb.state.cr4.set(val), + _ => return ax_err!(InvalidInput, format_args!("Unsupported CR{}", cr_idx)), + } + + Ok(()) + } + #[allow(dead_code)] + fn cr(&self, cr_idx: usize) -> usize { + let mut vmcb = unsafe { self.vmcb.as_vmcb() }; + (|| -> AxResult { + Ok(match cr_idx { + 0 => vmcb.state.cr0.get() as usize, + 3 => vmcb.state.cr3.get() as usize, + 4 => vmcb.state.cr4.get() as usize, + _ => unreachable!(), + }) + })() + .expect("Failed to read guest control register") + } +} + +impl SvmVcpu { + // unsafe extern "C" fn svm_run(&mut self) -> usize { + // let vmcb_phy = self.vmcb.phys_addr().as_usize() as u64; + // + // unsafe { + // naked_asm!( + // save_regs_to_stack!(), + // // "clgi", // 清除中断,确保 SVM 运行不中断 + // "mov [rdi + {host_stack_size}], rsp", // save current RSP to Vcpu::host_stack_top + // "mov rsp, rdi", // set RSP to guest regs area + // restore_regs_from_stack!(), // restore guest status + // "mov rax,{vmcb}", + // "vmload rax", + // "vmrun rax", + // "jmp {failed}", + // host_stack_size = const size_of::(), + // failed = sym Self::svm_entry_failed, + // vmcb = in(reg) vmcb_phy, // 正确绑定 vmcb 变量 + // // options(noreturn), + // ); + // } + // 0 + // } + + /// Operations immediately before VMRUN instruction. This includes: + /// + /// 1. Disabling interrupts (CLGI) + /// 2. Syncing RAX from guest_regs to VMCB + /// 3. Saving host FS/GS related states + /// 4. `VMLOAD`ing the VMCB + #[inline(always)] + fn before_vmrun(&mut self) { + unsafe { + asm!("clgi"); + } + + unsafe { self.vmcb.as_vmcb().state.rax.set(self.regs().rax) }; + + self.load_save_states.save_fs_gs(); + + unsafe { + asm!( + "vmload rax", + in("rax") self.vmcb.phys_addr().as_usize() as u64, + ); + } + } + + /// Operations immediately after VMRUN instruction. This includes: + /// + /// 1. `VMSAVE`ing the VMCB + /// 2. Restoring host FS/GS related states + /// 3. Syncing RAX from VMCB to guest_regs + /// 4. Enabling interrupts (STGI) + #[inline(always)] + fn after_vmrun(&mut self) { + let vmcb = self.vmcb.phys_addr().as_usize() as u64; + + unsafe { + asm!( + "vmsave rax", + in("rax") vmcb, + ); + } + + self.load_save_states.load_fs_gs(); + + self.regs_mut().rax = unsafe { self.vmcb.as_vmcb().state.rax.get() }; + + unsafe { + asm!("stgi"); + } + } + + pub unsafe fn svm_run(&mut self) { + let self_addr = self as *mut Self as u64; + let vmcb = self.vmcb.phys_addr().as_usize() as u64; + + self.before_vmrun(); + + unsafe { + asm!( + save_regs_to_stack!(norax), // Save host gpr except RAX, which holds vmcb pa + "mov [rdi + {host_stack_top}], rsp", // Save current RSP to Vcpu::host_stack_top + "mov rsp, rdi", // Set RSP to guest_regs area + restore_regs_from_stack!(norax), // Restore guest status except RAX + "vmrun rax", // Let's go! + save_regs_to_stack!(norax), // Save guest gpr except RAX + "mov rdi, rsp", // Regain the pointer to VCpu struct + "mov rsp, [rdi + {host_stack_top}]", // Restore host RSP from Vcpu::host_stack_top + restore_regs_from_stack!(norax), // Restore host gpr except RAX + host_stack_top = const size_of::(), + in("rax") vmcb, + in("rdi") self_addr, + ) + } + + self.after_vmrun(); + } + + fn allow_interrupt(&self) -> bool { + todo!() + } + + fn inject_pending_events(&mut self) -> AxResult { + todo!() + } + + fn builtin_vmexit_handler(&mut self, exit_info: &SvmExitInfo) -> Option { + let exit_code = match exit_info.exit_code { + Ok(code) => code, + Err(code) => { + error!("Unknown #VMEXIT exit code: {:#x}", code); + panic!("wrong code"); + } + }; + + match exit_code { + SvmExitCode::CPUID => Some(self.handle_cpuid()), + _ => None, + } + + // + // let res = match exit_code { + // SvmExitCode::EXCP(vec) => self.handle_exception(vec, &exit_info), + // SvmExitCode::NMI => self.handle_nmi(), + // SvmExitCode::CPUID => self.handle_cpuid(), + // SvmExitCode::VMMCALL => self.handle_hypercall(), + // SvmExitCode::NPF => self.handle_nested_page_fault(&exit_info), + // SvmExitCode::MSR => match exit_info.exit_info_1 { + // 0 => self.handle_msr_read(), + // 1 => self.handle_msr_write(), + // _ => panic!("MSR can't handle"), + // }, + // SvmExitCode::SHUTDOWN => { + // error!("#VMEXIT(SHUTDOWN): {:#x?}", exit_info); + // self.cpu_data.vcpu.inject_fault()?; + // Ok(()) + // } + // _ => panic!("code can't handle"), + // }; + } + + fn handle_svm_preemption_timer(&mut self) -> AxResult { + todo!() + } + + fn handle_cr(&mut self) -> AxResult { + todo!() + } + + fn handle_cpuid(&mut self) -> AxResult { + todo!() + } + + fn handle_xsetbv(&mut self) -> AxResult { + todo!() + } + + fn load_guest_xstate(&mut self) { + self.xstate.switch_to_guest(); + } + + fn load_host_xstate(&mut self) { + self.xstate.switch_to_host(); + } +} + +impl Drop for SvmVcpu { + fn drop(&mut self) { + todo!() + } +} + +impl core::fmt::Debug for SvmVcpu { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + todo!() + } +} + +impl AxArchVCpu for SvmVcpu { + type CreateConfig = (); + type SetupConfig = (); + + fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> AxResult { + Self::new() + } + + fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult { + self.entry = Some(entry); + Ok(()) + } + + fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxResult { + self.npt_root = Some(ept_root); + Ok(()) + } + + fn setup(&mut self, _config: Self::SetupConfig) -> AxResult { + self.setup_vmcb(self.entry.unwrap(), self.npt_root.unwrap()) + } + + fn run(&mut self) -> AxResult { + match self.inner_run() { + Some(exit_info) => { + warn!("VMX unsupported VM-Exit: {:#x?}", exit_info.exit_info_1); + warn!("VCpu {:#x?}", self); + Ok(AxVCpuExitReason::Halt) + } + _ => Ok(AxVCpuExitReason::Halt), + } + } + + fn bind(&mut self) -> AxResult { + self.bind_to_current_processor() + } + + fn unbind(&mut self) -> AxResult { + self.launched = false; + self.unbind_from_current_processor() + } + + fn set_gpr(&mut self, reg: usize, val: usize) { + self.regs_mut().set_reg_of_index(reg as u8, val as u64); + } + + fn inject_interrupt(&mut self, vector: usize) -> AxResult { + todo!() + } + + fn set_return_value(&mut self, val: usize) { + self.regs_mut().rax = val as u64; + } +} diff --git a/src/svm/vmcb.rs b/src/svm/vmcb.rs new file mode 100644 index 0000000..b87126c --- /dev/null +++ b/src/svm/vmcb.rs @@ -0,0 +1,534 @@ +// vmcb.rs — AMD‑SVM Virtual‑Machine Control Block helpers +// +// This is the SVM counterpart of `vmcs.rs`. A VMCB is a single 4‑KiB page +// split into a 1024‑byte Control Area (offset 0x0) and a 3‑KiB +// State‑Save Area (offset 0x400). Unlike Intel VMCS, each field is +// located at a fixed offset inside the page. That means the hypervisor can +// touch the fields with normal memory operations – no special I/O encoding or +// `VMWRITE/VMREAD` instructions are required. +// +// We use tock‑registers to generate strongly‑typed register proxies so you +// get: +// • type‑safe read/write helpers (no accidental mix‑ups) +// • handy bitfield accessors for intercept vectors, event injection, … +// • zero‑cost abstractions that compile to plain loads/stores. +// +// Reference: AMD 64 APM v2,Appendix B VMCB Layout + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] + +use tock_registers::registers::ReadWrite; +use tock_registers::{register_bitfields, register_structs}; + +use axaddrspace::HostPhysAddr; +use axerrno::AxResult; + +use super::definitions::{SvmExitCode, SvmIntercept}; +use super::structs::VmcbFrame; // the user‑supplied wrapper that owns the backing page +use axvcpu::AxVCpuHal; +use memory_addr::MemoryAddr; +use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +// ───────────────────────────────────────────────────────────────────────────── +// Control‑area bitfields +// ───────────────────────────────────────────────────────────────────────────── + +register_bitfields![u32, + // vector 0 + pub InterceptCrRw [ + READ_CR0 0, READ_CR3 3, READ_CR4 4, READ_CR8 8, + WRITE_CR0 16, WRITE_CR3 19, WRITE_CR4 20, WRITE_CR8 24, + ], + + // vector 1 + pub InterceptDrRw [ + READ_DR0 0, READ_DR7 7, + WRITE_DR0 16, WRITE_DR7 23, + ], + + // vector 2 + pub InterceptExceptions [ + DE 0, DB 1, BP 3, OF 4, DF 8, GP 13, PF 14, MC 18, + ], + + /// Vector 3 (offset 0x000C) + pub InterceptVec3 [ + INTR 0, NMI 1, SMI 2, INIT 3, + VINTR 4, CR0_SEL_WRITE 5, IDTR_READ 6, GDTR_READ 7, + LDTR_READ 8, TR_READ 9, IDTR_WRITE 10, GDTR_WRITE 11, + LDTR_WRITE 12, TR_WRITE 13, RDTSC 14, RDPMC 15, + PUSHF 16, POPF 17, CPUID 18, RSM 19, + IRET 20, SWINT 21, INVD 22, PAUSE 23, + HLT 24, INVLPG 25, INVLPGA 26, IOIO_PROT 27, + MSR_PROT 28, TASK_SWITCH 29, FERR_FREEZE 30, SHUTDOWN 31, + ], + + /// Vector 4 (offset 0x0010) + pub InterceptVec4 [ + VMRUN 0, VMMCALL 1, VMLOAD 2, VMSAVE 3, + STGI 4, CLGI 5, SKINIT 6, RDTSCP 7, + ICEBP 8, WBINVD 9, MONITOR 10, MWAIT 11, + MWAIT_CONDITIONAL 12, XSETBV 13, RDPRU 14, EFER_WRITE_TRAP 15, + ], + + /// Vector 5 (offset 0x0014) + pub InterceptVec5 [ + INVLPGB 0, INVLPGB_ILLEGAL 1, INVPCID 2, + MCOMMIT 3, TLBSYNC 4, + ], + // VMCB Clean-Bits 15.15.3 + pub VmcbCleanBits [ + INTERCEPTS 0, + IOPM 1, + ASID 2, + TPR 3, + NP 4, + CRx 5, + DRx 6, + DT 7, + SEG 8, + CR2 9, + LBR 10, + AVIC 11, + CET 12, + ], +]; + +register_bitfields![u64, + pub NestedCtl [ + NP_ENABLE 0, + SEV_ENABLE 1, + SEV_ES_ENABLE 2, + GMET_ENABLE 3, // Guest-Mode-Exec-Trap + SSCheckEn 4, + VTE_ENABLE 5, // Virtual Transparent Encryption + RO_GPT_EN 6, // Read-Only Guest Page Tables + INVLPGB_TLBSYNC 7, + ], +]; + +register_bitfields![u8, + pub VmcbTlbControl [ + CONTROL OFFSET(0) NUMBITS(3) [ + DoNothing = 0, + FlushAllOnVmrun = 1, + FlushGuestTlb = 3, + FlushGuestNonGlobalTlb = 7, + ] + ] +]; + +// register_bitfields![u16, +// pub VmcbSegmentAttr [ +// // ACCESSED OFFSET(0) NUMBITS(1), // not used in VMCB +// READABLE OFFSET(1) NUMBITS(1), + +// /// Code/Data bit, available for User segments (`S` = 1) only +// CODE OFFSET(3) NUMBITS(1), +// /// User/System bit `S` +// USER OFFSET(4) NUMBITS(1), +// /// DPL +// DPL OFFSET(5) NUMBITS(2), +// ] +// ]; + +register_structs![ + pub VmcbControlArea { + (0x0000 => pub intercept_cr: ReadWrite), + (0x0004 => pub intercept_dr: ReadWrite), + + (0x0008 => pub intercept_exceptions: ReadWrite), + (0x000C => pub intercept_vector3: ReadWrite), + (0x0010 => pub intercept_vector4: ReadWrite), + (0x0014 => pub intercept_vector5: ReadWrite), + (0x0018 => _reserved_0018), + (0x003C => pub pause_filter_thresh: ReadWrite), + (0x003E => pub pause_filter_count: ReadWrite), + + (0x0040 => pub iopm_base_pa: ReadWrite), + (0x0048 => pub msrpm_base_pa: ReadWrite), + (0x0050 => pub tsc_offset: ReadWrite), + + (0x0058 => pub guest_asid: ReadWrite), + (0x005C => pub tlb_control: ReadWrite), + (0x005D => _reserved_005D), + + (0x0060 => pub int_control: ReadWrite), + (0x0064 => pub int_vector: ReadWrite), + (0x0068 => pub int_state: ReadWrite), + (0x006C => _reserved_006C), + + // ───── VMEXIT --------------------------------------------------- + (0x0070 => pub exit_code: ReadWrite), + (0x0078 => pub exit_info_1: ReadWrite), + (0x0080 => pub exit_info_2: ReadWrite), + // 15.7.2 + (0x0088 => pub exit_int_info: ReadWrite), + (0x008C => pub exit_int_info_err: ReadWrite), + + // ───── Nested Paging / AVIC ----------------------------------------- + (0x0090 => pub nested_ctl: ReadWrite), + + // 0x0098 — AVIC_VAPIC_BAR(APIC-access BAR,only 40 bit are vaild) + (0x0098 => pub avic_vapic_bar: ReadWrite), + + // 0x00A0 — GHCB guest-physical address + (0x00A0 => pub ghcb_gpa: ReadWrite), + + // ── Event-injection / Nested CR3 / LBR -------------------------------- + (0x00A8 => pub event_inj: ReadWrite), + (0x00AC => pub event_inj_err: ReadWrite), + (0x00B0 => pub nested_cr3: ReadWrite), + (0x00B8 => pub virt_ext: ReadWrite), // LBR-control & V-VMLOAD/VMSAVE + + // ── Clean-bits & Next-RIP -------------------------------------------- + (0x00C0 => pub clean_bits: ReadWrite), + (0x00C4 => pub _rsvd5: ReadWrite), + (0x00C8 => pub next_rip: ReadWrite), + + // ── Decoded-instruction cache ---------------------------------------- + (0x00D0 => pub insn_len: ReadWrite), + (0x00D1 => pub insn_bytes: [ReadWrite; 15]), + + // ── AVIC extra -------------------------------------------------------- + (0x00E0 => pub avic_backing_page: ReadWrite), + (0x00E8 => _reserved_00E8), + + (0x00F0 => pub avic_logical_id: ReadWrite), + (0x00F8 => pub avic_physical_id: ReadWrite), + (0x0100 => _reserved_0100), + + (0x0108 => pub vmsa_pa: ReadWrite), // SEV-ES guest only + (0x0110 => _reserved_0110), + + (0x0120 => pub bus_lock_counter: ReadWrite), + (0x0122 => _reserved_0122), + + (0x0138 => pub allowed_sev_features: ReadWrite), + (0x0140 => pub guest_sev_features: ReadWrite), + (0x0148 => _reserved_0148), + + (0x0400 => @END), + } +]; + +register_structs![ + pub VmcbSegment { + (0x0 => pub selector: ReadWrite), + (0x2 => pub attr: ReadWrite), + (0x4 => pub limit: ReadWrite), + (0x8 => pub base: ReadWrite), + (0x10 => @END), + } +]; + +register_structs![ + pub VmcbStateSaveArea { + (0x0000 => pub es: VmcbSegment), + (0x0010 => pub cs: VmcbSegment), + (0x0020 => pub ss: VmcbSegment), + (0x0030 => pub ds: VmcbSegment), + (0x0040 => pub fs: VmcbSegment), + (0x0050 => pub gs: VmcbSegment), + (0x0060 => pub gdtr: VmcbSegment), + (0x0070 => pub ldtr: VmcbSegment), + (0x0080 => pub idtr: VmcbSegment), + (0x0090 => pub tr: VmcbSegment), + (0x00A0 => _reserved_00A0), + + (0x00CB => pub cpl: ReadWrite), + (0x00CC => _reserved_00CC), + + (0x00D0 => pub efer: ReadWrite), + (0x00D8 => _reserved_00D8), + + (0x0148 => pub cr4: ReadWrite), + (0x0150 => pub cr3: ReadWrite), + (0x0158 => pub cr0: ReadWrite), + (0x0160 => pub dr7: ReadWrite), + (0x0168 => pub dr6: ReadWrite), + (0x0170 => pub rflags:ReadWrite), + (0x0178 => pub rip: ReadWrite), + (0x0180 => _reserved_0180), + + (0x01D8 => pub rsp: ReadWrite), + (0x01E0 => pub s_cet: ReadWrite), + (0x01E8 => pub ssp: ReadWrite), + (0x01F0 => pub isst_addr: ReadWrite), + (0x01F8 => pub rax: ReadWrite), + + (0x0200 => pub star: ReadWrite), + (0x0208 => pub lstar: ReadWrite), + (0x0210 => pub cstar: ReadWrite), + (0x0218 => pub sfmask: ReadWrite), + (0x0220 => pub kernel_gs_base:ReadWrite), + (0x0228 => pub sysenter_cs: ReadWrite), + (0x0230 => pub sysenter_esp: ReadWrite), + (0x0238 => pub sysenter_eip: ReadWrite), + (0x0240 => pub cr2: ReadWrite), + (0x0248 => _reserved_0248), + + (0x0268 => pub g_pat: ReadWrite), + (0x0270 => pub dbgctl: ReadWrite), + (0x0278 => pub br_from: ReadWrite), + (0x0280 => pub br_to: ReadWrite), + (0x0288 => pub last_excp_from:ReadWrite), + (0x0290 => pub last_excp_to: ReadWrite), + (0x0298 => _reserved_0298), + + (0x0C00 => @END), + } +]; + +register_structs![ + pub VmcbStruct { + (0x0000 => pub control: VmcbControlArea), + (0x0400 => pub state: VmcbStateSaveArea), + (0x1000 => @END), + } +]; + +/// Unified façade returning typed accessors to both halves of the VMCB. +pub struct Vmcb<'a> { + pub control: &'a mut VmcbControlArea, + pub state: &'a mut VmcbStateSaveArea, +} + +impl VmcbFrame { + /// # Safety + /// caller must guarantee the page is mapped + pub unsafe fn as_vmcb(&self) -> &mut VmcbStruct { + unsafe { self.as_mut_ptr_vmcb().as_mut().unwrap() } + } +} + +impl VmcbStruct { + /// Zero‑initialise the control area + pub fn clear_control(&mut self) { + unsafe { core::ptr::write_bytes(&mut self.control as *mut _ as *mut u8, 0, 0x400) }; + } + pub fn clean_bits(&mut self) -> &mut ReadWrite { + &mut self.control.clean_bits + } +} + +pub fn set_vmcb_segment(seg: &mut VmcbSegment, selector: u16, attr: u16) { + seg.selector.set(selector); // 一般初始化阶段都传 0 + seg.base.set(0); // 实模式/平坦段:基址 0 + seg.limit.set(0xFFFF); // 64 KiB 段界限 + seg.attr.set(attr); // AR 字节(0x93, 0x9B, 0x8B, 0x82 …) +} + +impl VmcbControlArea { + pub fn set_intercept(&mut self, intc: SvmIntercept) { + use super::definitions::SvmIntercept::*; + match intc { + // ── Vector 3 ─────────────────────────────── + INTR => self.intercept_vector3.modify(InterceptVec3::INTR::SET), + NMI => self.intercept_vector3.modify(InterceptVec3::NMI::SET), + SMI => self.intercept_vector3.modify(InterceptVec3::SMI::SET), + INIT => self.intercept_vector3.modify(InterceptVec3::INIT::SET), + VINTR => self.intercept_vector3.modify(InterceptVec3::VINTR::SET), + CR0_SEL_WRITE => self + .intercept_vector3 + .modify(InterceptVec3::CR0_SEL_WRITE::SET), + IDTR_READ => self.intercept_vector3.modify(InterceptVec3::IDTR_READ::SET), + GDTR_READ => self.intercept_vector3.modify(InterceptVec3::GDTR_READ::SET), + LDTR_READ => self.intercept_vector3.modify(InterceptVec3::LDTR_READ::SET), + TR_READ => self.intercept_vector3.modify(InterceptVec3::TR_READ::SET), + IDTR_WRITE => self + .intercept_vector3 + .modify(InterceptVec3::IDTR_WRITE::SET), + GDTR_WRITE => self + .intercept_vector3 + .modify(InterceptVec3::GDTR_WRITE::SET), + LDTR_WRITE => self + .intercept_vector3 + .modify(InterceptVec3::LDTR_WRITE::SET), + TR_WRITE => self.intercept_vector3.modify(InterceptVec3::TR_WRITE::SET), + RDTSC => self.intercept_vector3.modify(InterceptVec3::RDTSC::SET), + RDPMC => self.intercept_vector3.modify(InterceptVec3::RDPMC::SET), + PUSHF => self.intercept_vector3.modify(InterceptVec3::PUSHF::SET), + POPF => self.intercept_vector3.modify(InterceptVec3::POPF::SET), + CPUID => self.intercept_vector3.modify(InterceptVec3::CPUID::SET), + RSM => self.intercept_vector3.modify(InterceptVec3::RSM::SET), + IRET => self.intercept_vector3.modify(InterceptVec3::IRET::SET), + SWINT => self.intercept_vector3.modify(InterceptVec3::SWINT::SET), + INVD => self.intercept_vector3.modify(InterceptVec3::INVD::SET), + PAUSE => self.intercept_vector3.modify(InterceptVec3::PAUSE::SET), + HLT => self.intercept_vector3.modify(InterceptVec3::HLT::SET), + INVLPG => self.intercept_vector3.modify(InterceptVec3::INVLPG::SET), + INVLPGA => self.intercept_vector3.modify(InterceptVec3::INVLPGA::SET), + IOIO_PROT => self.intercept_vector3.modify(InterceptVec3::IOIO_PROT::SET), + MSR_PROT => self.intercept_vector3.modify(InterceptVec3::MSR_PROT::SET), + TASK_SWITCH => self + .intercept_vector3 + .modify(InterceptVec3::TASK_SWITCH::SET), + FERR_FREEZE => self + .intercept_vector3 + .modify(InterceptVec3::FERR_FREEZE::SET), + SHUTDOWN => self.intercept_vector3.modify(InterceptVec3::SHUTDOWN::SET), + + // ── Vector 4 ─────────────────────────────── + VMRUN => self.intercept_vector4.modify(InterceptVec4::VMRUN::SET), + VMMCALL => self.intercept_vector4.modify(InterceptVec4::VMMCALL::SET), + VMLOAD => self.intercept_vector4.modify(InterceptVec4::VMLOAD::SET), + VMSAVE => self.intercept_vector4.modify(InterceptVec4::VMSAVE::SET), + STGI => self.intercept_vector4.modify(InterceptVec4::STGI::SET), + CLGI => self.intercept_vector4.modify(InterceptVec4::CLGI::SET), + SKINIT => self.intercept_vector4.modify(InterceptVec4::SKINIT::SET), + RDTSCP => self.intercept_vector4.modify(InterceptVec4::RDTSCP::SET), + ICEBP => self.intercept_vector4.modify(InterceptVec4::ICEBP::SET), + WBINVD => self.intercept_vector4.modify(InterceptVec4::WBINVD::SET), + MONITOR => self.intercept_vector4.modify(InterceptVec4::MONITOR::SET), + MWAIT => self.intercept_vector4.modify(InterceptVec4::MWAIT::SET), + MWAIT_CONDITIONAL => self + .intercept_vector4 + .modify(InterceptVec4::MWAIT_CONDITIONAL::SET), + XSETBV => self.intercept_vector4.modify(InterceptVec4::XSETBV::SET), + RDPRU => self.intercept_vector4.modify(InterceptVec4::RDPRU::SET), + EFER_WRITE_TRAP => self + .intercept_vector4 + .modify(InterceptVec4::EFER_WRITE_TRAP::SET), + + // ── Vector 5 ─────────────────────────────── + INVLPGB => self.intercept_vector5.modify(InterceptVec5::INVLPGB::SET), + INVLPGB_ILLEGAL => self + .intercept_vector5 + .modify(InterceptVec5::INVLPGB_ILLEGAL::SET), + INVPCID => self.intercept_vector5.modify(InterceptVec5::INVPCID::SET), + MCOMMIT => self.intercept_vector5.modify(InterceptVec5::MCOMMIT::SET), + TLBSYNC => self.intercept_vector5.modify(InterceptVec5::TLBSYNC::SET), + } + } +} + +#[derive(Debug)] +pub struct SvmExitInfo { + pub exit_code: core::result::Result, + pub exit_info_1: u64, + pub exit_info_2: u64, + pub guest_rip: u64, + pub guest_next_rip: u64, +} + +impl VmcbStruct { + pub fn exit_info(&self) -> AxResult { + Ok(SvmExitInfo { + exit_code: self.control.exit_code.get().try_into(), + exit_info_1: self.control.exit_info_1.get(), + exit_info_2: self.control.exit_info_2.get(), + guest_rip: self.state.rip.get(), + guest_next_rip: self.control.next_rip.get(), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn vmcb_size_check() { + use core::mem::size_of; + + assert_eq!(size_of::(), 0x400); + assert_eq!(size_of::(), 0xC00); + assert_eq!(size_of::(), 0x1000); + } + + #[test] + fn vmcb_offset_check() { + use memoffset::offset_of; + + assert_eq!(offset_of!(VmcbStruct, control), 0x0000); + assert_eq!(offset_of!(VmcbStruct, state), 0x0400); + + macro_rules! assert_vmcb_ctrl_offset { + ($field:ident, $offset:expr) => { + assert_eq!(offset_of!(VmcbControlArea, $field), $offset); + }; + } + + macro_rules! assert_vmcb_save_offset { + ($field:ident, $offset:expr) => { + assert_eq!(offset_of!(VmcbStateSaveArea, $field), $offset); + }; + } + + assert_vmcb_ctrl_offset!(intercept_cr, 0x00); + assert_vmcb_ctrl_offset!(intercept_dr, 0x04); + assert_vmcb_ctrl_offset!(intercept_exceptions, 0x08); + assert_vmcb_ctrl_offset!(intercept_vector3, 0x0C); + assert_vmcb_ctrl_offset!(intercept_vector4, 0x10); + assert_vmcb_ctrl_offset!(intercept_vector5, 0x14); + assert_vmcb_ctrl_offset!(pause_filter_thresh, 0x3C); + assert_vmcb_ctrl_offset!(pause_filter_count, 0x3E); + assert_vmcb_ctrl_offset!(iopm_base_pa, 0x40); + assert_vmcb_ctrl_offset!(msrpm_base_pa, 0x48); + assert_vmcb_ctrl_offset!(tsc_offset, 0x50); + assert_vmcb_ctrl_offset!(guest_asid, 0x58); + assert_vmcb_ctrl_offset!(tlb_control, 0x5C); + assert_vmcb_ctrl_offset!(int_control, 0x60); + assert_vmcb_ctrl_offset!(int_vector, 0x64); + assert_vmcb_ctrl_offset!(int_state, 0x68); + assert_vmcb_ctrl_offset!(exit_code, 0x70); + assert_vmcb_ctrl_offset!(exit_info_1, 0x78); + assert_vmcb_ctrl_offset!(exit_info_2, 0x80); + assert_vmcb_ctrl_offset!(exit_int_info, 0x88); + assert_vmcb_ctrl_offset!(exit_int_info_err, 0x8C); + assert_vmcb_ctrl_offset!(nested_ctl, 0x90); + assert_vmcb_ctrl_offset!(avic_vapic_bar, 0x98); + assert_vmcb_ctrl_offset!(event_inj, 0xA8); + assert_vmcb_ctrl_offset!(event_inj_err, 0xAC); + assert_vmcb_ctrl_offset!(nested_cr3, 0xB0); + assert_vmcb_ctrl_offset!(virt_ext, 0xB8); + assert_vmcb_ctrl_offset!(clean_bits, 0xC0); + assert_vmcb_ctrl_offset!(next_rip, 0xC8); + assert_vmcb_ctrl_offset!(insn_len, 0xD0); + assert_vmcb_ctrl_offset!(insn_bytes, 0xD1); + assert_vmcb_ctrl_offset!(avic_backing_page, 0xE0); + assert_vmcb_ctrl_offset!(avic_logical_id, 0xF0); + assert_vmcb_ctrl_offset!(avic_physical_id, 0xF8); + + assert_vmcb_save_offset!(es, 0x00); + assert_vmcb_save_offset!(cs, 0x10); + assert_vmcb_save_offset!(ss, 0x20); + assert_vmcb_save_offset!(ds, 0x30); + assert_vmcb_save_offset!(fs, 0x40); + assert_vmcb_save_offset!(gs, 0x50); + assert_vmcb_save_offset!(gdtr, 0x60); + assert_vmcb_save_offset!(ldtr, 0x70); + assert_vmcb_save_offset!(idtr, 0x80); + assert_vmcb_save_offset!(tr, 0x90); + assert_vmcb_save_offset!(cpl, 0xCB); + assert_vmcb_save_offset!(efer, 0xD0); + assert_vmcb_save_offset!(cr4, 0x148); + assert_vmcb_save_offset!(cr3, 0x150); + assert_vmcb_save_offset!(cr0, 0x158); + assert_vmcb_save_offset!(dr7, 0x160); + assert_vmcb_save_offset!(dr6, 0x168); + assert_vmcb_save_offset!(rflags, 0x170); + assert_vmcb_save_offset!(rip, 0x178); + assert_vmcb_save_offset!(rsp, 0x1D8); + assert_vmcb_save_offset!(s_cet, 0x1E0); + assert_vmcb_save_offset!(ssp, 0x1E8); + assert_vmcb_save_offset!(isst_addr, 0x1F0); + assert_vmcb_save_offset!(rax, 0x1F8); + assert_vmcb_save_offset!(star, 0x200); + assert_vmcb_save_offset!(lstar, 0x208); + assert_vmcb_save_offset!(cstar, 0x210); + assert_vmcb_save_offset!(sfmask, 0x218); + assert_vmcb_save_offset!(kernel_gs_base, 0x220); + assert_vmcb_save_offset!(sysenter_cs, 0x228); + assert_vmcb_save_offset!(sysenter_esp, 0x230); + assert_vmcb_save_offset!(sysenter_eip, 0x238); + assert_vmcb_save_offset!(cr2, 0x240); + assert_vmcb_save_offset!(g_pat, 0x268); + assert_vmcb_save_offset!(dbgctl, 0x270); + assert_vmcb_save_offset!(br_from, 0x278); + assert_vmcb_save_offset!(br_to, 0x280); + assert_vmcb_save_offset!(last_excp_from, 0x288); + assert_vmcb_save_offset!(last_excp_to, 0x290); + } +} diff --git a/src/vmx/percpu.rs b/src/vmx/percpu.rs index 484a723..c5d979a 100644 --- a/src/vmx/percpu.rs +++ b/src/vmx/percpu.rs @@ -50,7 +50,7 @@ impl AxArchPerCpu for VmxPerCpuState { } // Enable XSAVE/XRSTOR. - super::vcpu::XState::enable_xsave(); + crate::xstate::enable_xsave(); // Enable VMXON, if required. let ctrl = FeatureControl::read(); diff --git a/src/vmx/vcpu.rs b/src/vmx/vcpu.rs index e142fdb..d749cd1 100644 --- a/src/vmx/vcpu.rs +++ b/src/vmx/vcpu.rs @@ -8,7 +8,7 @@ use core::{ use raw_cpuid::CpuId; use x86::{ bits64::vmx, - controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}, + controlregs::Xcr0, dtables::{self, DescriptorTablePointer}, segmentation::SegmentSelector, }; @@ -32,23 +32,13 @@ use super::vmcs::{ self, ApicAccessExitType, VmcsControl32, VmcsControl64, VmcsControlNW, VmcsGuest16, VmcsGuest32, VmcsGuest64, VmcsGuestNW, VmcsHost16, VmcsHost32, VmcsHost64, VmcsHostNW, }; -use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters}; +use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters, xstate::XState}; const VMX_PREEMPTION_TIMER_SET_VALUE: u32 = 1_000_000; const QEMU_EXIT_PORT: u16 = 0x604; const QEMU_EXIT_MAGIC: u64 = 0x2000; -pub struct XState { - host_xcr0: u64, - guest_xcr0: u64, - host_xss: u64, - guest_xss: u64, - - xsave_available: bool, - xsaves_available: bool, -} - #[derive(PartialEq, Eq, Debug)] pub enum VmCpuMode { Real, @@ -57,97 +47,6 @@ pub enum VmCpuMode { Mode64, // IA-32E mode (CS.L = 1) } -impl XState { - /// Create a new [`XState`] instance with current host state - fn new() -> Self { - // Check if XSAVE is available - let xsave_available = Self::xsave_available(); - // Check if XSAVES and XRSTORS (as well as IA32_XSS) are available - let xsaves_available = if xsave_available { - Self::xsaves_available() - } else { - false - }; - - // Read XCR0 iff XSAVE is available - let xcr0 = if xsave_available { - unsafe { xcr0_read().bits() } - } else { - 0 - }; - // Read IA32_XSS iff XSAVES is available - let xss = if xsaves_available { - Msr::IA32_XSS.read() - } else { - 0 - }; - - Self { - host_xcr0: xcr0, - guest_xcr0: xcr0, - host_xss: xss, - guest_xss: xss, - xsave_available, - xsaves_available, - } - } - - /// Enable extended processor state management instructions, including XGETBV and XSAVE. - pub fn enable_xsave() { - if Self::xsave_available() { - unsafe { Cr4::write(Cr4::read() | Cr4Flags::OSXSAVE) }; - } - } - - /// Check if XSAVE is available on the current CPU. - pub fn xsave_available() -> bool { - let cpuid = CpuId::new(); - cpuid - .get_feature_info() - .map(|f| f.has_xsave()) - .unwrap_or(false) - } - - /// Check if XSAVES and XRSTORS (as well as IA32_XSS) are available on the current CPU. - pub fn xsaves_available() -> bool { - let cpuid = CpuId::new(); - cpuid - .get_extended_state_info() - .map(|f| f.has_xsaves_xrstors()) - .unwrap_or(false) - } - - /// Save the current host XCR0 and IA32_XSS values and load the guest values. - pub fn switch_to_guest(&mut self) { - unsafe { - if self.xsave_available { - self.host_xcr0 = xcr0_read().bits(); - xcr0_write(Xcr0::from_bits_unchecked(self.guest_xcr0)); - - if self.xsaves_available { - self.host_xss = Msr::IA32_XSS.read(); - Msr::IA32_XSS.write(self.guest_xss); - } - } - } - } - - /// Save the current guest XCR0 and IA32_XSS values and load the host values. - pub fn switch_to_host(&mut self) { - unsafe { - if self.xsave_available { - self.guest_xcr0 = xcr0_read().bits(); - xcr0_write(Xcr0::from_bits_unchecked(self.host_xcr0)); - - if self.xsaves_available { - self.guest_xss = Msr::IA32_XSS.read(); - Msr::IA32_XSS.write(self.host_xss); - } - } - } - } -} - const MSR_IA32_EFER_LMA_BIT: u64 = 1 << 10; const CR0_PE: usize = 1 << 0; @@ -899,7 +798,7 @@ impl VmxVcpu { /// This function itself never returns, but [`Self::vmx_exit`] will do the return for this. /// /// The return value is a dummy value. - unsafe extern "C" fn vmx_launch(&mut self) -> usize { + unsafe extern fn vmx_launch(&mut self) -> usize { vmx_entry_with!("vmlaunch") } @@ -907,7 +806,7 @@ impl VmxVcpu { /// Enter guest with vmresume. /// /// See [`Self::vmx_launch`] for detail. - unsafe extern "C" fn vmx_resume(&mut self) -> usize { + unsafe extern fn vmx_resume(&mut self) -> usize { vmx_entry_with!("vmresume") } @@ -917,7 +816,7 @@ impl VmxVcpu { /// NEVER call this function directly. /// /// The return value is a dummy value. - unsafe extern "C" fn vmx_exit(&mut self) -> usize { + unsafe extern fn vmx_exit(&mut self) -> usize { // it's not necessary to use another `unsafe` here, as Rust now do not require it in naked functions. naked_asm!( save_regs_to_stack!(), // save guest status, after this, rsp points to the `VmxVcpu` @@ -1235,7 +1134,7 @@ impl VmxVcpu { }) .ok_or(ax_err_type!(InvalidInput)) .and_then(|x| { - self.xstate.guest_xcr0 = x.bits(); + self.xstate.guest.xcr0 = x.bits(); self.advance_rip(VM_EXIT_INSTR_LEN_XSETBV) }) } else { diff --git a/src/xstate.rs b/src/xstate.rs new file mode 100644 index 0000000..be72056 --- /dev/null +++ b/src/xstate.rs @@ -0,0 +1,144 @@ +use raw_cpuid::CpuId; +use x86::controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}; +use x86_64::registers::control::{Cr4, Cr4Flags}; + +use crate::msr::Msr; + +/// Indicates the availability of extended processor state management features. +#[derive(Debug, Clone, Copy)] +pub struct XAvailable { + /// Indicates if XSAVE (as well as xcr0) is available. + pub xsave: bool, + /// Indicates if XSAVES and XRSTORS (as well as IA32_XSS) are available. + pub xsaves: bool, +} + +impl XAvailable { + /// Create a new [`XAvailable`] instance by querying the CPU features. + pub fn new() -> Self { + let xsave_avail = xsave_available(); + let xsaves_avail = xsave_avail && xsaves_available(); + + Self { + xsave: xsave_avail, + xsaves: xsaves_avail, + } + } +} + +/// Control and state registers for extended processor state management. +#[derive(Debug, Clone, Copy)] +pub struct XRegs { + /// The xcr0 extended control register. + pub xcr0: u64, + /// The IA32_XSS model-specific register. + pub xss: u64, +} + +impl XRegs { + /// Create a new [`XRegs`] instance by querying the current CPU state. + pub fn new(avail: XAvailable) -> Self { + let xcr0 = if avail.xsave { + unsafe { xcr0_read().bits() } + } else { + 0 + }; + + let xss = if avail.xsaves { + Msr::IA32_XSS.read() + } else { + 0 + }; + + Self { xcr0, xss } + } + + /// Load the extended processor state registers from this instance. + pub fn load(&self, avail: XAvailable) { + unsafe { + if avail.xsave { + // info!("Loading XCR0: {:#x}", self.xcr0); + xcr0_write(Xcr0::from_bits_unchecked(self.xcr0)); + + if avail.xsaves { + Msr::IA32_XSS.write(self.xss); + } + } + } + } + + /// Save the current extended processor state registers into this instance. + pub fn save(&mut self, avail: XAvailable) { + unsafe { + if avail.xsave { + self.xcr0 = xcr0_read().bits(); + + if avail.xsaves { + self.xss = Msr::IA32_XSS.read(); + } + } + } + } +} + +/// Extended processor state storage for vcpus. +#[derive(Debug)] +pub struct XState { + pub host: XRegs, + pub guest: XRegs, + pub avail: XAvailable, +} + +impl XState { + /// Create a new [`XState`] instance with current host state. + pub fn new() -> Self { + let avail = XAvailable::new(); + let host = XRegs::new(avail); + let guest = host; + + Self { host, guest, avail } + } + + /// Save the current host XCR0 and IA32_XSS values and load the guest values. + pub fn switch_to_guest(&mut self) { + // info!("Switching to guest xstate"); + self.host.save(self.avail); + // info!("Host xstate saved: {:?}", self.host); + // info!("Guest xstate loading: {:?}", self.guest); + self.guest.load(self.avail); + } + + /// Save the current guest XCR0 and IA32_XSS values and load the host values. + pub fn switch_to_host(&mut self) { + // info!("Switching to host xstate"); + self.guest.save(self.avail); + // info!("Guest xstate saved: {:?}", self.guest); + // info!("Host xstate loading: {:?}", self.host); + self.host.load(self.avail); + } +} + +/// Check if XSAVE is available on the current CPU. +pub fn xsave_available() -> bool { + let cpuid = CpuId::new(); + cpuid + .get_feature_info() + .map(|f| f.has_xsave()) + .unwrap_or(false) +} + +/// Check if XSAVES and XRSTORS (as well as IA32_XSS) are available on the current CPU. +pub fn xsaves_available() -> bool { + let cpuid = CpuId::new(); + cpuid + .get_extended_state_info() + .map(|f| f.has_xsaves_xrstors()) + .unwrap_or(false) +} + +/// Enable extended processor state management instructions, including XGETBV and XSAVE. +pub fn enable_xsave() { + if xsave_available() { + unsafe { Cr4::write(Cr4::read() | Cr4Flags::OSXSAVE) }; + } +}