use core::marker::PhantomData;
use x86_64::registers::control::{Cr0, Cr4};
use super::memory::ControlRegion;
use crate::virtualization::{ControlMemory, VirtualizationError};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Backend {
Vmx,
Svm,
}
impl Backend {
pub fn detect() -> Option<Self> {
use core::arch::x86_64::__cpuid;
if __cpuid(0).eax >= 1 && __cpuid(1).ecx & (1 << 5) != 0 {
Some(Self::Vmx)
} else if __cpuid(0x8000_0000).eax >= 0x8000_0001
&& __cpuid(0x8000_0001).ecx & (1 << 2) != 0
{
Some(Self::Svm)
} else {
None
}
}
}
enum HostState {
Vmx { cr0: u64, cr4: u64 },
Svm { efer: u64, hsave: u64 },
}
pub struct PerCpu<M: ControlMemory> {
region: Option<ControlRegion<M>>,
backend: Backend,
host: Option<HostState>,
local: PhantomData<*mut ()>,
}
impl<M: ControlMemory> PerCpu<M> {
pub fn new(memory: M) -> Result<Self, VirtualizationError> {
let backend = Backend::detect().ok_or(VirtualizationError::Unavailable)?;
Ok(Self {
region: Some(ControlRegion::new(memory, 4096, 4096)?),
backend,
host: None,
local: PhantomData,
})
}
pub const fn backend(&self) -> Backend {
self.backend
}
pub fn is_enabled(&self) -> bool {
self.host.is_some()
}
pub unsafe fn enable(&mut self) -> Result<(), VirtualizationError> {
if self.host.is_some() {
return Err(VirtualizationError::AlreadyEnabled);
}
if Backend::detect() != Some(self.backend) {
return Err(VirtualizationError::Unavailable);
}
let region = self
.region
.as_mut()
.expect("inactive owner retains its control lease");
let state = unsafe {
match self.backend {
Backend::Vmx => enable_vmx(region)?,
Backend::Svm => enable_svm(region)?,
}
};
self.host = Some(state);
Ok(())
}
pub unsafe fn disable(&mut self) -> Result<(), VirtualizationError> {
let host = self.host.as_ref().ok_or(VirtualizationError::NotEnabled)?;
unsafe {
match *host {
HostState::Vmx { cr0, cr4 } => {
x86::bits64::vmx::vmxoff()
.map_err(|_| VirtualizationError::InstructionFailed)?;
Cr4::write_raw(cr4);
Cr0::write_raw(cr0);
}
HostState::Svm { efer, hsave } => {
x86::msr::wrmsr(0xc000_0080, efer);
x86::msr::wrmsr(0xc001_0117, hsave);
}
}
}
self.host = None;
Ok(())
}
pub fn into_memory(mut self) -> Result<M, Self> {
if self.host.is_some() {
return Err(self);
}
Ok(self
.region
.take()
.expect("owner retains its control lease")
.into_memory())
}
}
impl<M: ControlMemory> Drop for PerCpu<M> {
fn drop(&mut self) {
if self.host.is_some()
&& let Some(region) = self.region.as_mut()
{
region.retain_on_failed_retirement();
}
}
}
pub(super) fn address_fits<M: ControlMemory>(region: &ControlRegion<M>, bits: u32) -> bool {
(12..64).contains(&bits) && region.physical_address().as_usize() as u64 <= (1u64 << bits) - 4096
}
pub(super) fn physical_address_bits() -> u32 {
use core::arch::x86_64::__cpuid;
if __cpuid(0x8000_0000).eax >= 0x8000_0008 {
__cpuid(0x8000_0008).eax & 0xff
} else {
32
}
}
unsafe fn enable_vmx<M: ControlMemory>(
region: &mut ControlRegion<M>,
) -> Result<HostState, VirtualizationError> {
unsafe {
let cr0 = Cr0::read_raw();
let cr4 = Cr4::read_raw();
if cr4 & (1 << 13) != 0 {
return Err(VirtualizationError::AlreadyEnabled);
}
let feature_control = x86::msr::rdmsr(0x3a);
if feature_control & 5 != 5 {
return Err(VirtualizationError::FeatureControlUnavailable);
}
let cr0_fixed0 = x86::msr::rdmsr(0x486);
let cr0_fixed1 = x86::msr::rdmsr(0x487);
let cr4_fixed0 = x86::msr::rdmsr(0x488);
let cr4_fixed1 = x86::msr::rdmsr(0x489);
let vmx_cr0 = (cr0 | cr0_fixed0) & cr0_fixed1;
let vmx_cr4 = ((cr4 | cr4_fixed0) & cr4_fixed1) | (1 << 13);
if vmx_cr0 & cr0_fixed0 != cr0_fixed0
|| vmx_cr4 & cr4_fixed0 != cr4_fixed0
|| vmx_cr4 & !cr4_fixed1 != 0
{
return Err(VirtualizationError::Unavailable);
}
initialize_vmx_region(region)?;
Cr0::write_raw(vmx_cr0);
Cr4::write_raw(vmx_cr4);
if x86::bits64::vmx::vmxon(region.physical_address().as_usize() as u64).is_err() {
Cr4::write_raw(cr4);
Cr0::write_raw(cr0);
return Err(VirtualizationError::InstructionFailed);
}
Ok(HostState::Vmx { cr0, cr4 })
}
}
unsafe fn enable_svm<M: ControlMemory>(
region: &mut ControlRegion<M>,
) -> Result<HostState, VirtualizationError> {
unsafe {
if x86::msr::rdmsr(0xc001_0114) & (1 << 4) != 0 {
return Err(VirtualizationError::Unavailable);
}
let efer = x86::msr::rdmsr(0xc000_0080);
if efer & (1 << 12) != 0 {
return Err(VirtualizationError::AlreadyEnabled);
}
if !address_fits(region, physical_address_bits()) {
return Err(VirtualizationError::InvalidControlMemory);
}
let hsave = x86::msr::rdmsr(0xc001_0117);
region.clear();
x86::msr::wrmsr(0xc001_0117, region.physical_address().as_usize() as u64);
x86::msr::wrmsr(0xc000_0080, efer | (1 << 12));
Ok(HostState::Svm { efer, hsave })
}
}
pub unsafe fn authorize_vmx() -> Result<(), VirtualizationError> {
if Backend::detect() != Some(Backend::Vmx) {
return Err(VirtualizationError::Unavailable);
}
unsafe {
let value = x86::msr::rdmsr(0x3a);
if value & 1 != 0 {
return if value & 4 != 0 {
Ok(())
} else {
Err(VirtualizationError::FeatureControlUnavailable)
};
}
x86::msr::wrmsr(0x3a, value | 5);
}
Ok(())
}
pub(super) unsafe fn initialize_vmx_region<M: ControlMemory>(
region: &mut ControlRegion<M>,
) -> Result<(), VirtualizationError> {
unsafe {
let basic = x86::msr::rdmsr(0x480);
let size = (basic >> 32) & 0x1fff;
let address_bits = if basic & (1 << 48) != 0 {
32
} else {
physical_address_bits()
};
if size == 0
|| size > 4096
|| (basic >> 50) & 0xf != 6
|| !address_fits(region, address_bits)
{
return Err(VirtualizationError::InvalidControlMemory);
}
region.clear();
region.write_revision((basic & 0x7fff_ffff) as u32);
}
Ok(())
}