use core::sync::atomic::{AtomicU8, Ordering};
use raw_cpuid::CpuId;
use crate::{svm::*, vmx::*, *};
const UNSELECTED: u8 = 0;
const VMX: u8 = 1;
const SVM: u8 = 2;
static SELECTED_BACKEND: AtomicU8 = AtomicU8::new(UNSELECTED);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum X86VirtualizationBackend {
Vmx,
Svm,
}
impl X86VirtualizationBackend {
const fn as_raw(self) -> u8 {
match self {
Self::Vmx => VMX,
Self::Svm => SVM,
}
}
const fn from_raw(raw: u8) -> Option<Self> {
match raw {
VMX => Some(Self::Vmx),
SVM => Some(Self::Svm),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct X86VirtualizationCapabilities {
vmx: bool,
svm: bool,
}
pub struct X86PerCpuState<H: X86HostOps> {
inner: X86PerCpuStateInner<H>,
}
enum X86PerCpuStateInner<H: X86HostOps> {
Vmx(VmxPerCpuState<H>),
Svm(SvmPerCpuState<H>),
}
impl<H: X86HostOps> X86PerCpuState<H> {
pub fn new(cpu_id: usize) -> X86VcpuResult<Self> {
match validate_current_cpu_backend()? {
X86VirtualizationBackend::Vmx => VmxPerCpuState::new(cpu_id).map(|inner| Self {
inner: X86PerCpuStateInner::Vmx(inner),
}),
X86VirtualizationBackend::Svm => SvmPerCpuState::new(cpu_id).map(|inner| Self {
inner: X86PerCpuStateInner::Svm(inner),
}),
}
}
pub fn is_enabled(&self) -> bool {
match &self.inner {
X86PerCpuStateInner::Vmx(state) => state.is_enabled(),
X86PerCpuStateInner::Svm(state) => state.is_enabled(),
}
}
pub fn hardware_enable(&mut self) -> X86VcpuResult {
validate_current_cpu_backend()?;
match &mut self.inner {
X86PerCpuStateInner::Vmx(state) => state.hardware_enable(),
X86PerCpuStateInner::Svm(state) => state.hardware_enable(),
}
}
pub fn hardware_disable(&mut self) -> X86VcpuResult {
match &mut self.inner {
X86PerCpuStateInner::Vmx(state) => state.hardware_disable(),
X86PerCpuStateInner::Svm(state) => state.hardware_disable(),
}
}
}
pub struct X86Vcpu<H: X86HostOps> {
inner: X86VcpuInner<H>,
}
enum X86VcpuInner<H: X86HostOps> {
Vmx(VmxVcpu<H>),
Svm(SvmVcpu<H>),
}
macro_rules! dispatch_vcpu {
($self:expr, $method:ident $(, $arg:expr)*) => {
match &mut $self.inner {
X86VcpuInner::Vmx(vcpu) => vcpu.$method($($arg),*),
X86VcpuInner::Svm(vcpu) => vcpu.$method($($arg),*),
}
};
}
impl<H: X86HostOps> X86Vcpu<H> {
pub fn new_with_config(
vm_id: usize,
vcpu_id: usize,
config: X86VcpuCreateConfig,
) -> X86VcpuResult<Self> {
match selected_backend().ok_or(X86VcpuError::BadState)? {
X86VirtualizationBackend::Vmx => {
VmxVcpu::new_with_config(vm_id, vcpu_id, config).map(|inner| Self {
inner: X86VcpuInner::Vmx(inner),
})
}
X86VirtualizationBackend::Svm => {
SvmVcpu::new_with_config(vm_id, vcpu_id, config).map(|inner| Self {
inner: X86VcpuInner::Svm(inner),
})
}
}
}
pub fn set_entry(&mut self, entry: X86GuestPhysAddr) -> X86VcpuResult {
dispatch_vcpu!(self, set_entry, entry)
}
pub fn set_nested_page_table(&mut self, config: X86NestedPagingConfig) -> X86VcpuResult {
dispatch_vcpu!(self, set_nested_page_table, config)
}
pub fn setup(&mut self, config: X86VcpuSetupConfig) -> X86VcpuResult {
dispatch_vcpu!(self, setup, config)
}
pub fn run(&mut self) -> X86VcpuResult<X86VmExit> {
dispatch_vcpu!(self, run)
}
pub fn bind(&mut self) -> X86VcpuResult {
dispatch_vcpu!(self, bind)
}
pub fn unbind(&mut self) -> X86VcpuResult {
dispatch_vcpu!(self, unbind)
}
pub fn set_gpr(&mut self, reg: usize, value: usize) {
dispatch_vcpu!(self, set_gpr, reg, value)
}
pub fn set_gpr_byte(&mut self, reg: X86ByteRegister, value: u8) {
dispatch_vcpu!(self, set_gpr_byte, reg, value)
}
pub fn set_gpr_word(&mut self, reg: usize, value: u16) {
dispatch_vcpu!(self, set_gpr_word, reg, value)
}
pub fn set_gpr_rsp(&mut self, width: X86AccessWidth, value: u64) {
dispatch_vcpu!(self, set_gpr_rsp, width, value)
}
pub fn complete_port_io_string(&mut self, exit: X86PortIoStringExit) -> X86VcpuResult {
dispatch_vcpu!(self, complete_port_io_string, exit)
}
pub fn inject_interrupt(&mut self, vector: usize) -> X86VcpuResult {
dispatch_vcpu!(self, inject_interrupt, vector)
}
pub fn inject_interrupt_with_trigger(
&mut self,
vector: usize,
level_triggered: bool,
) -> X86VcpuResult {
dispatch_vcpu!(self, inject_interrupt_with_trigger, vector, level_triggered)
}
pub fn handle_eoi(&mut self) -> Option<u8> {
dispatch_vcpu!(self, handle_eoi)
}
pub fn set_return_value(&mut self, value: usize) {
dispatch_vcpu!(self, set_return_value, value)
}
}
pub fn has_hardware_support() -> bool {
detect_current_backend().is_ok()
}
pub fn initialize_hardware_support() -> X86VcpuResult {
select_current_backend().map(|_| ())
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum X86NestedPagingFormat {
Ept,
Npt,
}
pub fn selected_nested_paging_format() -> X86VcpuResult<X86NestedPagingFormat> {
match selected_backend().ok_or(X86VcpuError::BadState)? {
X86VirtualizationBackend::Vmx => Ok(X86NestedPagingFormat::Ept),
X86VirtualizationBackend::Svm => Ok(X86NestedPagingFormat::Npt),
}
}
pub fn requires_apic_access_page() -> X86VcpuResult<bool> {
Ok(matches!(
selected_backend().ok_or(X86VcpuError::BadState)?,
X86VirtualizationBackend::Vmx
))
}
pub fn apic_access_page_gpa() -> X86VcpuResult<X86GuestPhysAddr> {
match selected_backend() {
Some(X86VirtualizationBackend::Vmx) => Ok(X86GuestPhysAddr::from(X86_LOCAL_APIC_GPA)),
Some(X86VirtualizationBackend::Svm) => Err(X86VcpuError::Unsupported),
None => Err(X86VcpuError::BadState),
}
}
pub fn apic_access_page_addr<H: X86HostOps>() -> X86VcpuResult<X86HostPhysAddr> {
match selected_backend() {
Some(X86VirtualizationBackend::Vmx) => Ok(crate::vmx::x86_apic_access_page_addr::<H>()),
Some(X86VirtualizationBackend::Svm) => Err(X86VcpuError::Unsupported),
None => Err(X86VcpuError::BadState),
}
}
fn select_backend_from_capabilities(
capabilities: X86VirtualizationCapabilities,
) -> X86VcpuResult<X86VirtualizationBackend> {
match (capabilities.vmx, capabilities.svm) {
(true, false) => Ok(X86VirtualizationBackend::Vmx),
(false, true) => Ok(X86VirtualizationBackend::Svm),
(false, false) => Err(X86VcpuError::Unsupported),
(true, true) => Err(X86VcpuError::InvalidData),
}
}
fn detect_current_backend() -> X86VcpuResult<X86VirtualizationBackend> {
let cpuid = CpuId::new();
select_backend_from_capabilities(X86VirtualizationCapabilities {
vmx: cpuid
.get_feature_info()
.is_some_and(|features| features.has_vmx()),
svm: cpuid
.get_extended_processor_and_feature_identifiers()
.is_some_and(|features| features.has_svm()),
})
}
fn select_current_backend() -> X86VcpuResult<X86VirtualizationBackend> {
let detected = detect_current_backend()?;
match SELECTED_BACKEND.compare_exchange(
UNSELECTED,
detected.as_raw(),
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => Ok(detected),
Err(raw) if X86VirtualizationBackend::from_raw(raw) == Some(detected) => Ok(detected),
Err(_) => Err(X86VcpuError::BadState),
}
}
fn selected_backend() -> Option<X86VirtualizationBackend> {
X86VirtualizationBackend::from_raw(SELECTED_BACKEND.load(Ordering::Acquire))
}
fn validate_current_cpu_backend() -> X86VcpuResult<X86VirtualizationBackend> {
let selected = selected_backend().ok_or(X86VcpuError::BadState)?;
if detect_current_backend()? == selected {
Ok(selected)
} else {
Err(X86VcpuError::BadState)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn selects_vmx_when_only_vmx_is_exposed() {
assert_eq!(
select_backend_from_capabilities(X86VirtualizationCapabilities {
vmx: true,
svm: false,
}),
Ok(X86VirtualizationBackend::Vmx)
);
}
#[test]
fn selects_svm_when_only_svm_is_exposed() {
assert_eq!(
select_backend_from_capabilities(X86VirtualizationCapabilities {
vmx: false,
svm: true,
}),
Ok(X86VirtualizationBackend::Svm)
);
}
#[test]
fn rejects_missing_or_ambiguous_virtualization_extensions() {
assert_eq!(
select_backend_from_capabilities(X86VirtualizationCapabilities::default()),
Err(X86VcpuError::Unsupported)
);
assert_eq!(
select_backend_from_capabilities(X86VirtualizationCapabilities {
vmx: true,
svm: true,
}),
Err(X86VcpuError::InvalidData)
);
}
}