#![no_std]
extern crate alloc;
mod error;
use alloc::string::String;
use core::fmt::{Debug, Formatter, LowerHex, UpperHex};
use ax_memory_addr::{AddrRange, PhysAddr, VirtAddr, def_usize_addr, def_usize_addr_formatter};
pub use error::{VmBackendError, VmBackendResult};
bitflags::bitflags! {
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct MappingFlags: usize {
const READ = 1 << 0;
const WRITE = 1 << 1;
const EXECUTE = 1 << 2;
const USER = 1 << 3;
const DEVICE = 1 << 4;
const UNCACHED = 1 << 5;
}
}
impl Debug for MappingFlags {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
Debug::fmt(&self.0, f)
}
}
pub type VMId = usize;
pub type VCpuId = usize;
pub type InterruptVector = u8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterruptTriggerMode {
EdgeTriggered,
LevelTriggered,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct IrqLineId(pub usize);
pub const MAX_VCPU_NUM: usize = 64;
pub type VCpuSet = ax_cpumask::CpuMask<MAX_VCPU_NUM>;
pub type HostVirtAddr = VirtAddr;
pub type HostPhysAddr = PhysAddr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NestedPagingConfig {
pub root_paddr: HostPhysAddr,
pub levels: usize,
pub gpa_bits: usize,
pub mode: usize,
}
impl NestedPagingConfig {
pub const fn new(
root_paddr: HostPhysAddr,
levels: usize,
gpa_bits: usize,
mode: usize,
) -> Self {
Self {
root_paddr,
levels,
gpa_bits,
mode,
}
}
}
def_usize_addr! {
pub type GuestVirtAddr;
pub type GuestPhysAddr;
}
def_usize_addr_formatter! {
GuestVirtAddr = "GVA:{}";
GuestPhysAddr = "GPA:{}";
}
pub type GuestVirtAddrRange = AddrRange<GuestVirtAddr>;
pub type GuestPhysAddrRange = AddrRange<GuestPhysAddr>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AccessWidth {
Byte,
Word,
Dword,
Qword,
}
impl TryFrom<usize> for AccessWidth {
type Error = ();
fn try_from(value: usize) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::Byte),
2 => Ok(Self::Word),
4 => Ok(Self::Dword),
8 => Ok(Self::Qword),
_ => Err(()),
}
}
}
impl From<AccessWidth> for usize {
fn from(width: AccessWidth) -> usize {
match width {
AccessWidth::Byte => 1,
AccessWidth::Word => 2,
AccessWidth::Dword => 4,
AccessWidth::Qword => 8,
}
}
}
impl AccessWidth {
pub fn size(&self) -> usize {
(*self).into()
}
pub fn bits_range(&self) -> core::ops::Range<usize> {
match self {
AccessWidth::Byte => 0..8,
AccessWidth::Word => 0..16,
AccessWidth::Dword => 0..32,
AccessWidth::Qword => 0..64,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Port(pub u16);
impl Port {
pub const fn new(port: u16) -> Self {
Self(port)
}
pub const fn number(&self) -> u16 {
self.0
}
}
impl LowerHex for Port {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "Port({:#x})", self.0)
}
}
impl UpperHex for Port {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "Port({:#X})", self.0)
}
}
impl Debug for Port {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "Port({})", self.0)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct SysRegAddr(pub usize);
impl SysRegAddr {
pub const fn new(addr: usize) -> Self {
Self(addr)
}
pub const fn addr(&self) -> usize {
self.0
}
}
impl From<usize> for SysRegAddr {
fn from(value: usize) -> Self {
Self(value)
}
}
impl LowerHex for SysRegAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "SysRegAddr({:#x})", self.0)
}
}
impl UpperHex for SysRegAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "SysRegAddr({:#X})", self.0)
}
}
impl Debug for SysRegAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "SysRegAddr({})", self.0)
}
}
#[derive(Debug)]
pub struct NestedPageFaultInfo {
pub access_flags: MappingFlags,
pub fault_guest_paddr: GuestPhysAddr,
}
#[non_exhaustive]
#[derive(Debug)]
pub enum VmExit {
Hypercall {
nr: u64,
args: [u64; 6],
},
MmioRead {
addr: GuestPhysAddr,
width: AccessWidth,
reg: usize,
reg_width: AccessWidth,
signed_ext: bool,
},
MmioWrite {
addr: GuestPhysAddr,
width: AccessWidth,
data: u64,
},
SysRegRead {
addr: SysRegAddr,
reg: usize,
},
SysRegWrite {
addr: SysRegAddr,
value: u64,
},
IoRead {
port: Port,
width: AccessWidth,
},
IoWrite {
port: Port,
width: AccessWidth,
data: u64,
},
ExternalInterrupt {
vector: u64,
},
NestedPageFault {
addr: GuestPhysAddr,
access_flags: MappingFlags,
},
Halt,
Idle,
CpuUp {
target_cpu: u64,
entry_point: GuestPhysAddr,
arg: u64,
},
CpuDown {
_state: u64,
},
SystemDown,
Nothing,
PreemptionTimer,
InterruptEnd {
vector: Option<u8>,
},
FailEntry {
hardware_entry_failure_reason: u64,
},
SendIPI {
target_cpu: u64,
target_cpu_aux: u64,
send_to_all: bool,
send_to_self: bool,
vector: u64,
},
}
pub trait VmArchVcpuOps: Sized {
type CreateConfig;
type SetupConfig;
type Exit: Debug;
fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> VmBackendResult<Self>;
fn guest_mpidr_from_create_config(_config: &Self::CreateConfig) -> Option<u64> {
None
}
fn set_entry(&mut self, entry: GuestPhysAddr) -> VmBackendResult;
fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> VmBackendResult;
fn setup(&mut self, config: Self::SetupConfig) -> VmBackendResult;
fn run(&mut self) -> VmBackendResult<Self::Exit>;
fn bind(&mut self) -> VmBackendResult;
fn unbind(&mut self) -> VmBackendResult;
fn set_gpr(&mut self, reg: usize, val: usize);
fn decode_mmio_fault(
&mut self,
_fault_addr: GuestPhysAddr,
_access_flags: MappingFlags,
) -> Option<VmExit> {
None
}
fn inject_interrupt(&mut self, vector: usize) -> VmBackendResult;
fn inject_interrupt_with_trigger(
&mut self,
vector: usize,
trigger: InterruptTriggerMode,
) -> VmBackendResult {
match trigger {
InterruptTriggerMode::EdgeTriggered => self.inject_interrupt(vector),
InterruptTriggerMode::LevelTriggered => Err(VmBackendError::Unsupported),
}
}
fn handle_eoi(&mut self) -> Option<u8> {
None
}
fn set_return_value(&mut self, val: usize);
}
pub trait VmArchPerCpuOps: Sized {
fn new(cpu_id: usize) -> VmBackendResult<Self>;
fn is_enabled(&self) -> bool;
fn hardware_enable(&mut self) -> VmBackendResult;
fn hardware_disable(&mut self) -> VmBackendResult;
fn max_guest_page_table_levels(&self) -> usize {
4
}
fn guest_phys_addr_bits(&self) -> usize {
match self.max_guest_page_table_levels() {
0..=3 => 39,
_ => 48,
}
}
fn timer_frequency_hz(&self) -> Option<u64> {
None
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VmVcpuState {
Invalid = 0,
Created = 1,
Free = 2,
Ready = 3,
Running = 4,
Blocked = 5,
Starting = 6,
}
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub enum VMType {
VMTHostVM = 0,
#[default]
VMTRTOS = 1,
VMTLinux = 2,
}
impl From<usize> for VMType {
fn from(value: usize) -> Self {
match value {
0 => Self::VMTHostVM,
1 => Self::VMTRTOS,
2 => Self::VMTLinux,
_ => Self::default(),
}
}
}
impl From<VMType> for usize {
fn from(value: VMType) -> Self {
value as usize
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum AddressSpacePolicy {
#[default]
Virtualized,
Passthrough,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum VmMemMappingType {
#[default]
MapAlloc = 0,
MapIdentical = 1,
MapReserved = 2,
}
#[derive(Debug, Default, Clone)]
pub struct VmMemConfig {
pub gpa: usize,
pub size: usize,
pub flags: usize,
pub map_type: VmMemMappingType,
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct HostDeviceAssignment {
pub name: String,
pub base_gpa: usize,
pub base_hpa: usize,
pub length: usize,
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct HostAddressAssignment {
pub base_gpa: usize,
pub length: usize,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct ReservedAddressConfig {
pub base_gpa: usize,
pub length: usize,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct HostPortAssignment {
pub base: u16,
pub length: u16,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum VMBootProtocol {
#[default]
Direct,
Multiboot,
Uefi,
}
#[cfg(test)]
mod tests {
use super::*;
struct MockPerCpu {
enabled: bool,
}
impl VmArchPerCpuOps for MockPerCpu {
fn new(_cpu_id: usize) -> VmBackendResult<Self> {
Ok(Self { enabled: false })
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn hardware_enable(&mut self) -> VmBackendResult {
self.enabled = true;
Ok(())
}
fn hardware_disable(&mut self) -> VmBackendResult {
self.enabled = false;
Ok(())
}
}
#[derive(Debug, PartialEq, Eq)]
enum MockExit {
SysRegRead { reg: usize },
}
struct MockVcpu;
impl VmArchVcpuOps for MockVcpu {
type CreateConfig = ();
type SetupConfig = ();
type Exit = MockExit;
fn new(
_vm_id: VMId,
_vcpu_id: VCpuId,
_config: Self::CreateConfig,
) -> VmBackendResult<Self> {
Ok(Self)
}
fn set_entry(&mut self, _entry: GuestPhysAddr) -> VmBackendResult {
Ok(())
}
fn set_nested_page_table(&mut self, _config: NestedPagingConfig) -> VmBackendResult {
Ok(())
}
fn setup(&mut self, _config: Self::SetupConfig) -> VmBackendResult {
Ok(())
}
fn run(&mut self) -> VmBackendResult<Self::Exit> {
Ok(MockExit::SysRegRead { reg: 2 })
}
fn bind(&mut self) -> VmBackendResult {
Ok(())
}
fn unbind(&mut self) -> VmBackendResult {
Ok(())
}
fn set_gpr(&mut self, _reg: usize, _val: usize) {}
fn inject_interrupt(&mut self, _vector: usize) -> VmBackendResult {
Ok(())
}
fn inject_interrupt_with_trigger(
&mut self,
_vector: usize,
_trigger: InterruptTriggerMode,
) -> VmBackendResult {
Ok(())
}
fn set_return_value(&mut self, _val: usize) {}
}
#[test]
fn vcpu_protocol_lives_in_axvm_types() {
let mut percpu = MockPerCpu::new(0).unwrap();
assert!(!percpu.is_enabled());
percpu.hardware_enable().unwrap();
assert!(percpu.is_enabled());
let mut vcpu = MockVcpu::new(1, 0, ()).unwrap();
vcpu.set_entry(GuestPhysAddr::from(0x8020_0000)).unwrap();
vcpu.set_nested_page_table(NestedPagingConfig::new(
HostPhysAddr::from(0x1000),
4,
48,
0,
))
.unwrap();
vcpu.setup(()).unwrap();
assert!(matches!(
vcpu.run().unwrap(),
MockExit::SysRegRead { reg: 2 }
));
}
#[test]
fn vm_exit_keeps_access_width_and_state_types() {
let state = VmVcpuState::Created;
assert_eq!(state as u8, 1);
let exit = VmExit::MmioRead {
addr: GuestPhysAddr::from(0x1000),
width: AccessWidth::Dword,
reg: 3,
reg_width: AccessWidth::Qword,
signed_ext: true,
};
assert!(matches!(
exit,
VmExit::MmioRead {
width: AccessWidth::Dword,
reg: 3,
..
}
));
}
}