#![no_std]
#![doc = include_str!("../README.md")]
#[cfg(any(feature = "vmx", feature = "svm"))]
#[macro_use]
extern crate log;
#[cfg(not(any(feature = "vmx", feature = "svm")))]
extern crate log;
extern crate alloc;
#[cfg(test)]
extern crate std;
#[cfg(all(feature = "vmx", feature = "svm"))]
compile_error!("features `vmx` and `svm` are mutually exclusive");
#[cfg(test)]
mod test_utils;
mod types;
pub use types::{
X86AccessFlags, X86AccessWidth, X86GuestPhysAddr, X86GuestVirtAddr, X86HostPhysAddr,
X86HostVirtAddr, X86MsrAddr, X86NestedPageFaultInfo, X86NestedPagingConfig, X86Port,
X86VcpuError, X86VcpuResult, X86VmExit,
};
macro_rules! x86_err {
($kind:ident) => {
Err($crate::X86VcpuError::$kind)
};
($kind:ident, $msg:expr) => {{
let _ = &$msg;
Err($crate::X86VcpuError::$kind)
}};
}
#[cfg(any(feature = "vmx", feature = "svm"))]
macro_rules! x86_err_type {
($kind:ident) => {
$crate::X86VcpuError::$kind
};
($kind:ident, $msg:expr) => {{
let _ = &$msg;
$crate::X86VcpuError::$kind
}};
}
pub const X86_MAX_PASSTHROUGH_PORT_RANGES: usize = 16;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct X86VCpuCreateConfig;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct X86PassthroughPortRange {
pub base: u16,
pub length: u16,
}
#[derive(Clone, Copy, Debug)]
pub struct X86VCpuSetupConfig {
pub emulate_com1: bool,
pub passthrough_ports: [Option<X86PassthroughPortRange>; X86_MAX_PASSTHROUGH_PORT_RANGES],
}
impl Default for X86VCpuSetupConfig {
fn default() -> Self {
Self {
emulate_com1: false,
passthrough_ports: [None; X86_MAX_PASSTHROUGH_PORT_RANGES],
}
}
}
impl X86VCpuSetupConfig {
pub fn add_passthrough_port_range(&mut self, base: u16, length: u16) -> X86VcpuResult {
if length == 0 {
return Err(X86VcpuError::InvalidInput);
}
if base.checked_add(length - 1).is_none() {
return Err(X86VcpuError::InvalidInput);
}
let range = X86PassthroughPortRange { base, length };
if self.passthrough_ports.contains(&Some(range)) {
return Ok(());
}
if let Some(slot) = self
.passthrough_ports
.iter_mut()
.find(|slot| slot.is_none())
{
*slot = Some(range);
return Ok(());
}
Err(X86VcpuError::NoMemory)
}
pub fn passthrough_port_ranges(&self) -> impl Iterator<Item = X86PassthroughPortRange> + '_ {
self.passthrough_ports.iter().filter_map(|range| *range)
}
}
pub mod host;
pub use host::X86HostOps;
pub(crate) mod msr;
#[cfg(feature = "vmx")]
#[macro_use]
pub(crate) mod regs;
mod ept;
#[cfg(not(feature = "vmx"))]
pub(crate) mod regs;
#[cfg(any(feature = "vmx", feature = "svm"))]
pub(crate) mod xstate;
#[cfg(any(feature = "vmx", feature = "svm", test))]
const X86_RESET_VECTOR_GPA: usize = 0xffff_fff0;
#[cfg(any(feature = "vmx", feature = "svm", test))]
const X86_RESET_CS_SELECTOR: u16 = 0xf000;
#[cfg(any(feature = "vmx", feature = "svm", test))]
const X86_RESET_CS_BASE: usize = 0xffff_0000;
#[cfg(any(feature = "vmx", feature = "svm", test))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct X86RealModeEntryState {
pub(crate) cs_selector: u16,
pub(crate) cs_base: usize,
pub(crate) rip: usize,
}
#[cfg(any(feature = "vmx", feature = "svm", test))]
pub(crate) fn x86_real_mode_entry_state(entry: X86GuestPhysAddr) -> X86RealModeEntryState {
if entry.as_usize() == X86_RESET_VECTOR_GPA {
return X86RealModeEntryState {
cs_selector: X86_RESET_CS_SELECTOR,
cs_base: X86_RESET_CS_BASE,
rip: X86_RESET_VECTOR_GPA - X86_RESET_CS_BASE,
};
}
X86RealModeEntryState {
cs_selector: 0,
cs_base: 0,
rip: entry.as_usize(),
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "vmx")] {
mod vmx;
use vmx as vendor;
pub use vmx::{VmxExitInfo, VmxExitReason, VmxInterruptInfo, VmxIoExitInfo};
pub use vendor::{
VmxArchPerCpuState, VmxArchPerCpuState as X86ArchPerCpuState, VmxArchVCpu,
VmxArchVCpu as X86ArchVCpu, X86_APIC_ACCESS_GPA, x86_apic_access_page_addr,
};
} else if #[cfg(feature = "svm")] {
mod svm;
use svm as vendor;
pub use svm::{SvmExitCode, SvmExitInfo, SvmIntercept};
pub use vendor::{
SvmArchPerCpuState, SvmArchPerCpuState as X86ArchPerCpuState, SvmArchVCpu,
SvmArchVCpu as X86ArchVCpu,
};
} else {
mod no_backend;
pub use no_backend::{X86ArchPerCpuState, X86ArchVCpu};
}
}
pub use ept::GuestPageWalkInfo;
pub use regs::GeneralRegisters;
#[cfg(any(feature = "vmx", feature = "svm"))]
pub use vendor::has_hardware_support;
#[cfg(not(any(feature = "vmx", feature = "svm")))]
pub fn has_hardware_support() -> bool {
false
}
#[cfg(any(feature = "vmx", feature = "svm"))]
pub(crate) fn restore_host_interrupt_flag(host_rflags: u64) {
if host_rflags & x86_64::registers::rflags::RFlags::INTERRUPT_FLAG.bits() != 0 {
x86_64::instructions::interrupts::enable();
} else {
x86_64::instructions::interrupts::disable();
}
}
#[cfg(any(feature = "vmx", feature = "svm"))]
pub(crate) fn host_tsc_frequency_mhz<H: X86HostOps>() -> Option<u32> {
u32::try_from(host::nanos_to_ticks::<H>(1_000))
.ok()
.filter(|&freq| freq > 0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn real_mode_entry_keeps_normal_entry_flat() {
assert_eq!(
x86_real_mode_entry_state(X86GuestPhysAddr::from(0x8000)),
X86RealModeEntryState {
cs_selector: 0,
cs_base: 0,
rip: 0x8000,
}
);
}
#[test]
fn real_mode_entry_maps_reset_vector_to_reset_cs_state() {
assert_eq!(
x86_real_mode_entry_state(X86GuestPhysAddr::from(0xffff_fff0)),
X86RealModeEntryState {
cs_selector: 0xf000,
cs_base: 0xffff_0000,
rip: 0xfff0,
}
);
}
#[test]
fn setup_config_records_passthrough_port_ranges() {
let mut config = X86VCpuSetupConfig::default();
config.add_passthrough_port_range(0x6000, 0x80).unwrap();
config.add_passthrough_port_range(0x6000, 0x80).unwrap();
let ranges = config
.passthrough_port_ranges()
.collect::<std::vec::Vec<_>>();
assert_eq!(
ranges,
std::vec![X86PassthroughPortRange {
base: 0x6000,
length: 0x80
}]
);
}
#[test]
fn setup_config_rejects_invalid_or_excess_passthrough_port_ranges() {
let mut config = X86VCpuSetupConfig::default();
assert!(config.add_passthrough_port_range(0x6000, 0).is_err());
assert!(config.add_passthrough_port_range(0xfff0, 0x20).is_err());
for index in 0..X86_MAX_PASSTHROUGH_PORT_RANGES {
config
.add_passthrough_port_range((0x1000 + index * 0x10) as u16, 1)
.unwrap();
}
assert!(config.add_passthrough_port_range(0x3000, 1).is_err());
}
}