use ax_errno::{AxResult, ax_err, ax_err_type};
use ax_memory_addr::PAGE_SIZE_4K as PAGE_SIZE;
use axvcpu::AxArchPerCpu;
use x86::bits64::vmx;
use x86_64::registers::control::{Cr0, Cr4, Cr4Flags};
use crate::{
msr::Msr,
vmx::{
has_hardware_support,
structs::{FeatureControl, FeatureControlFlags, VmxBasic, VmxRegion},
},
xstate::enable_xsave,
};
#[derive(Debug)]
pub struct VmxPerCpuState {
pub(crate) vmcs_revision_id: u32,
vmx_region: VmxRegion,
}
impl AxArchPerCpu for VmxPerCpuState {
fn new(_cpu_id: usize) -> AxResult<Self> {
Ok(Self {
vmcs_revision_id: 0,
vmx_region: unsafe { VmxRegion::uninit() },
})
}
fn is_enabled(&self) -> bool {
Cr4::read().contains(Cr4Flags::VIRTUAL_MACHINE_EXTENSIONS)
}
fn hardware_enable(&mut self) -> AxResult {
if !has_hardware_support() {
return ax_err!(Unsupported, "CPU does not support feature VMX");
}
if self.is_enabled() {
return ax_err!(ResourceBusy, "VMX is already turned on");
}
enable_xsave();
let ctrl = FeatureControl::read();
let locked = ctrl.contains(FeatureControlFlags::LOCKED);
let vmxon_outside = ctrl.contains(FeatureControlFlags::VMXON_ENABLED_OUTSIDE_SMX);
if !locked {
FeatureControl::write(
ctrl | FeatureControlFlags::LOCKED | FeatureControlFlags::VMXON_ENABLED_OUTSIDE_SMX,
)
} else if !vmxon_outside {
return ax_err!(Unsupported, "VMX disabled by BIOS");
}
macro_rules! cr_is_valid {
($value:expr, $crx:ident) => {{
use Msr::*;
let value = $value;
paste::paste! {
let fixed0 = [<IA32_VMX_ $crx _FIXED0>].read();
let fixed1 = [<IA32_VMX_ $crx _FIXED1>].read();
}
(!fixed0 | value != 0) && (fixed1 | !value != 0)
}};
}
if !cr_is_valid!(Cr0::read().bits(), CR0) {
return ax_err!(BadState, "host CR0 is not valid in VMX operation");
}
if !cr_is_valid!(Cr4::read().bits(), CR4) {
return ax_err!(BadState, "host CR4 is not valid in VMX operation");
}
let vmx_basic = VmxBasic::read();
if vmx_basic.region_size as usize != PAGE_SIZE {
return ax_err!(Unsupported);
}
if vmx_basic.mem_type != VmxBasic::VMX_MEMORY_TYPE_WRITE_BACK {
return ax_err!(Unsupported);
}
if vmx_basic.is_32bit_address {
return ax_err!(Unsupported);
}
if !vmx_basic.io_exit_info {
return ax_err!(Unsupported);
}
if !vmx_basic.vmx_flex_controls {
return ax_err!(Unsupported);
}
self.vmcs_revision_id = vmx_basic.revision_id;
self.vmx_region = VmxRegion::new(self.vmcs_revision_id, false)?;
unsafe {
Cr4::write(Cr4::read() | Cr4Flags::VIRTUAL_MACHINE_EXTENSIONS);
vmx::vmxon(self.vmx_region.phys_addr().as_usize() as _).map_err(|err| {
ax_err_type!(
BadState,
format_args!("VMX instruction vmxon failed: {:?}", err)
)
})?;
}
info!("[AxVM] succeeded to turn on VMX.");
Ok(())
}
fn hardware_disable(&mut self) -> AxResult {
if !self.is_enabled() {
return ax_err!(BadState, "VMX is not enabled");
}
unsafe {
vmx::vmxoff().map_err(|err| {
ax_err_type!(
BadState,
format_args!("VMX instruction vmxoff failed: {:?}", err)
)
})?;
Cr4::update(|cr4| cr4.remove(Cr4Flags::VIRTUAL_MACHINE_EXTENSIONS));
};
info!("[AxVM] succeeded to turn off VMX.");
self.vmx_region = unsafe { VmxRegion::uninit() };
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloc::{format, vec::Vec};
use super::*;
use crate::test_utils::mock::MockMmHal;
#[test]
fn test_vmx_per_cpu_state_new() {
MockMmHal::reset(); let result = VmxPerCpuState::new(0);
assert!(result.is_ok());
let state = result.unwrap();
assert_eq!(state.vmcs_revision_id, 0);
}
#[test]
fn test_vmx_per_cpu_state_default_values() {
MockMmHal::reset(); let state = VmxPerCpuState::new(0).unwrap();
assert_eq!(state.vmcs_revision_id, 0);
}
#[test]
fn test_multiple_cpu_states_independence() {
MockMmHal::reset(); let mut states = Vec::new();
for cpu_id in 0..4 {
let state = VmxPerCpuState::new(cpu_id).unwrap();
states.push(state);
}
states[0].vmcs_revision_id = 0x12345678;
states[1].vmcs_revision_id = 0x87654321;
assert_eq!(states[0].vmcs_revision_id, 0x12345678);
assert_eq!(states[1].vmcs_revision_id, 0x87654321);
assert_eq!(states[2].vmcs_revision_id, 0);
assert_eq!(states[3].vmcs_revision_id, 0);
}
#[test]
fn test_vmx_per_cpu_state_debug() {
MockMmHal::reset(); let state = VmxPerCpuState::new(0).unwrap();
let debug_str = format!("{:?}", state);
assert!(!debug_str.is_empty());
}
#[test]
fn test_vmx_per_cpu_state_size() {
use core::mem;
let size = mem::size_of::<VmxPerCpuState>();
assert!(size > 4);
assert!(size < 1024);
}
}