#![no_std]
extern crate alloc;
use alloc::{string::String, vec::Vec};
use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex};
use ax_memory_addr::{AddrRange, PhysAddr, VirtAddr, def_usize_addr, def_usize_addr_formatter};
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>;
pub type AxVmResult<T = ()> = ax_errno::AxResult<T>;
pub type AxVmError = ax_errno::AxError;
#[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) -> AxVmResult<Self>;
fn set_entry(&mut self, entry: GuestPhysAddr) -> AxVmResult;
fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> AxVmResult;
fn setup(&mut self, config: Self::SetupConfig) -> AxVmResult;
fn run(&mut self) -> AxVmResult<Self::Exit>;
fn bind(&mut self) -> AxVmResult;
fn unbind(&mut self) -> AxVmResult;
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) -> AxVmResult;
fn inject_interrupt_with_trigger(
&mut self,
vector: usize,
trigger: InterruptTriggerMode,
) -> AxVmResult {
debug_assert!(
trigger == InterruptTriggerMode::EdgeTriggered,
"level-triggered interrupt injection requires an architecture-specific implementation"
);
self.inject_interrupt(vector)
}
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) -> AxVmResult<Self>;
fn is_enabled(&self) -> bool;
fn hardware_enable(&mut self) -> AxVmResult;
fn hardware_disable(&mut self) -> AxVmResult;
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,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VmVcpuState {
Invalid = 0,
Created = 1,
Free = 2,
Ready = 3,
Running = 4,
Blocked = 5,
}
#[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)]
pub struct EmulatedDeviceConfig {
pub name: String,
pub base_gpa: usize,
pub length: usize,
pub irq_id: usize,
pub emu_type: EmulatedDeviceType,
pub cfg_list: Vec<usize>,
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct PassThroughDeviceConfig {
pub name: String,
pub base_gpa: usize,
pub base_hpa: usize,
pub length: usize,
pub irq_id: usize,
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct PassThroughAddressConfig {
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 PassThroughPortConfig {
pub base: u16,
pub length: u16,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum VMBootProtocol {
#[default]
Direct,
Multiboot,
Uefi,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum VMInterruptMode {
#[default]
NoIrq,
Emulated,
Passthrough,
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum EmulatedDeviceType {
#[default]
Dummy = 0x0,
InterruptController = 0x1,
Console = 0x2,
FwCfg = 0x3,
IVCChannel = 0xA,
GPPTRedistributor = 0x20,
GPPTDistributor = 0x21,
GPPTITS = 0x22,
X86IoApic = 0x23,
X86Pit = 0x24,
LoongArchPchPic = 0x25,
PPPTGlobal = 0x30,
VirtioBlk = 0xE1,
VirtioNet = 0xE2,
VirtioConsole = 0xE3,
}
#[cfg(test)]
mod tests {
use super::*;
struct MockPerCpu {
enabled: bool,
}
impl VmArchPerCpuOps for MockPerCpu {
fn new(_cpu_id: usize) -> AxVmResult<Self> {
Ok(Self { enabled: false })
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn hardware_enable(&mut self) -> AxVmResult {
self.enabled = true;
Ok(())
}
fn hardware_disable(&mut self) -> AxVmResult {
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) -> AxVmResult<Self> {
Ok(Self)
}
fn set_entry(&mut self, _entry: GuestPhysAddr) -> AxVmResult {
Ok(())
}
fn set_nested_page_table(&mut self, _config: NestedPagingConfig) -> AxVmResult {
Ok(())
}
fn setup(&mut self, _config: Self::SetupConfig) -> AxVmResult {
Ok(())
}
fn run(&mut self) -> AxVmResult<Self::Exit> {
Ok(MockExit::SysRegRead { reg: 2 })
}
fn bind(&mut self) -> AxVmResult {
Ok(())
}
fn unbind(&mut self) -> AxVmResult {
Ok(())
}
fn set_gpr(&mut self, _reg: usize, _val: usize) {}
fn inject_interrupt(&mut self, _vector: usize) -> AxVmResult {
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,
..
}
));
}
}
impl Display for EmulatedDeviceType {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
EmulatedDeviceType::Console => write!(f, "console"),
EmulatedDeviceType::FwCfg => write!(f, "fw_cfg"),
EmulatedDeviceType::InterruptController => write!(f, "interrupt controller"),
EmulatedDeviceType::GPPTRedistributor => {
write!(f, "gic partial passthrough redistributor")
}
EmulatedDeviceType::GPPTDistributor => write!(f, "gic partial passthrough distributor"),
EmulatedDeviceType::GPPTITS => write!(f, "gic partial passthrough its"),
EmulatedDeviceType::X86IoApic => write!(f, "x86 io apic"),
EmulatedDeviceType::X86Pit => write!(f, "x86 pit"),
EmulatedDeviceType::LoongArchPchPic => write!(f, "loongarch pch pic"),
EmulatedDeviceType::PPPTGlobal => write!(f, "plic partial passthrough global"),
EmulatedDeviceType::IVCChannel => write!(f, "ivc channel"),
EmulatedDeviceType::Dummy => write!(f, "meta device"),
EmulatedDeviceType::VirtioBlk => write!(f, "virtio block"),
EmulatedDeviceType::VirtioNet => write!(f, "virtio net"),
EmulatedDeviceType::VirtioConsole => write!(f, "virtio console"),
}
}
}
impl EmulatedDeviceType {
pub const ALL: [Self; 15] = [
EmulatedDeviceType::Dummy,
EmulatedDeviceType::InterruptController,
EmulatedDeviceType::Console,
EmulatedDeviceType::FwCfg,
EmulatedDeviceType::IVCChannel,
EmulatedDeviceType::GPPTRedistributor,
EmulatedDeviceType::GPPTDistributor,
EmulatedDeviceType::GPPTITS,
EmulatedDeviceType::X86IoApic,
EmulatedDeviceType::X86Pit,
EmulatedDeviceType::LoongArchPchPic,
EmulatedDeviceType::PPPTGlobal,
EmulatedDeviceType::VirtioBlk,
EmulatedDeviceType::VirtioNet,
EmulatedDeviceType::VirtioConsole,
];
pub const fn all() -> &'static [Self] {
&Self::ALL
}
pub fn removable(&self) -> bool {
matches!(
*self,
EmulatedDeviceType::InterruptController
| EmulatedDeviceType::GPPTRedistributor
| EmulatedDeviceType::X86IoApic
| EmulatedDeviceType::X86Pit
| EmulatedDeviceType::VirtioBlk
| EmulatedDeviceType::VirtioNet
| EmulatedDeviceType::VirtioConsole
)
}
pub const fn from_usize(value: usize) -> Option<Self> {
match value {
0x0 => Some(EmulatedDeviceType::Dummy),
0x1 => Some(EmulatedDeviceType::InterruptController),
0x2 => Some(EmulatedDeviceType::Console),
0x3 => Some(EmulatedDeviceType::FwCfg),
0xA => Some(EmulatedDeviceType::IVCChannel),
0x20 => Some(EmulatedDeviceType::GPPTRedistributor),
0x21 => Some(EmulatedDeviceType::GPPTDistributor),
0x22 => Some(EmulatedDeviceType::GPPTITS),
0x23 => Some(EmulatedDeviceType::X86IoApic),
0x24 => Some(EmulatedDeviceType::X86Pit),
0x25 => Some(EmulatedDeviceType::LoongArchPchPic),
0x30 => Some(EmulatedDeviceType::PPPTGlobal),
0xE1 => Some(EmulatedDeviceType::VirtioBlk),
0xE2 => Some(EmulatedDeviceType::VirtioNet),
0xE3 => Some(EmulatedDeviceType::VirtioConsole),
_ => None,
}
}
}