use super::{VmcsControl32, VmcsField, VmcsReadWrite, VmxControls};
use crate::{
registers::Msr,
virtualization::{Backend, ControlMemory, VirtualizationError},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VmxControl {
PinBased,
Primary,
Secondary,
Entry,
Exit,
}
#[derive(Clone, Copy, Debug)]
pub struct VmxControlCapabilities {
mandatory: u32,
permitted: u32,
legacy_default: u32,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum VmxControlError {
#[error("VMX control masks overlap")]
ConflictingMasks,
#[error("unsupported VMX control set mask {0:#x}")]
UnsupportedSet(u32),
#[error("unsupported VMX control clear mask {0:#x}")]
UnsupportedClear(u32),
#[error(transparent)]
Cpu(#[from] VirtualizationError),
}
impl VmxControlCapabilities {
pub const fn allows(self, bits: u32) -> bool {
self.permitted & bits == bits
}
fn adjust(self, previous: u32, set: u32, clear: u32) -> Result<u32, VmxControlError> {
if set & clear != 0 {
return Err(VmxControlError::ConflictingMasks);
}
if set & !self.permitted != 0 {
return Err(VmxControlError::UnsupportedSet(set & !self.permitted));
}
if clear & self.mandatory != 0 {
return Err(VmxControlError::UnsupportedClear(clear & self.mandatory));
}
Ok(self.mandatory | (previous & self.permitted & !(set | clear)) | set)
}
}
impl VmxControl {
fn field(self) -> VmcsField<u32, VmcsReadWrite> {
match self {
Self::PinBased => VmcsControl32::PINBASED_EXEC_CONTROLS,
Self::Primary => VmcsControl32::PRIMARY_PROCBASED_EXEC_CONTROLS,
Self::Secondary => VmcsControl32::SECONDARY_PROCBASED_EXEC_CONTROLS,
Self::Entry => VmcsControl32::VMENTRY_CONTROLS,
Self::Exit => VmcsControl32::VMEXIT_CONTROLS,
}
}
pub unsafe fn capabilities(self) -> Result<VmxControlCapabilities, VirtualizationError> {
if Backend::detect() != Some(Backend::Vmx) {
return Err(VirtualizationError::Unavailable);
}
let (legacy, true_control) = match self {
Self::PinBased => (Msr::Ia32VmxPinbasedCtls, Msr::Ia32VmxTruePinbasedCtls),
Self::Primary => (Msr::Ia32VmxProcbasedCtls, Msr::Ia32VmxTrueProcbasedCtls),
Self::Secondary => (Msr::Ia32VmxProcbasedCtls2, Msr::Ia32VmxProcbasedCtls2),
Self::Entry => (Msr::Ia32VmxEntryCtls, Msr::Ia32VmxTrueEntryCtls),
Self::Exit => (Msr::Ia32VmxExitCtls, Msr::Ia32VmxTrueExitCtls),
};
let (legacy_value, active) = unsafe {
let value = legacy.read();
let active = if legacy != true_control && Msr::Ia32VmxBasic.read() & (1 << 55) != 0 {
true_control.read()
} else {
value
};
(value, active)
};
Ok(VmxControlCapabilities {
mandatory: active as u32,
permitted: (active >> 32) as u32,
legacy_default: legacy_value as u32,
})
}
}
impl<M: ControlMemory> VmxControls<M> {
fn apply_control(
&mut self,
control: VmxControl,
previous: Option<u32>,
set: u32,
clear: u32,
) -> Result<(), VmxControlError> {
if !self.vmcs().is_bound() {
return Err(VirtualizationError::NotEnabled.into());
}
let capability = unsafe { control.capabilities()? };
let value = capability.adjust(previous.unwrap_or(capability.legacy_default), set, clear)?;
self.write(control.field(), value)?;
Ok(())
}
pub fn initialize_control(
&mut self,
control: VmxControl,
set: u32,
clear: u32,
) -> Result<(), VmxControlError> {
self.apply_control(control, None, set, clear)
}
pub fn update_control(
&mut self,
control: VmxControl,
set: u32,
clear: u32,
) -> Result<(), VmxControlError> {
let previous = self.read(control.field())?;
self.apply_control(control, Some(previous), set, clear)
}
}
impl<M: ControlMemory> VmxControls<M> {
pub fn synchronize_long_mode(&mut self, guest_cr0: u64) -> Result<(), VmxControlError> {
use super::VmcsGuest64;
const LME: u64 = 1 << 8;
const LMA: u64 = 1 << 10;
const PG: u64 = 1 << 31;
const IA32E: u32 = 1 << 9;
let previous = self.read(VmcsGuest64::IA32_EFER)?;
let previous_entry = self.read(VmcsControl32::VMENTRY_CONTROLS)?;
let active = previous & LME != 0 && guest_cr0 & PG != 0;
self.update_control(
VmxControl::Entry,
if active { IA32E } else { 0 },
if active { 0 } else { IA32E },
)?;
let next = (previous & !LMA) | if active { LMA } else { 0 };
if let Err(error) = self.write(VmcsGuest64::IA32_EFER, next) {
self.write(VmcsControl32::VMENTRY_CONTROLS, previous_entry)?;
return Err(error.into());
}
Ok(())
}
}