#![no_std]
#![allow(stable_features)]
#![feature(trait_upcasting)]
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]
#![warn(missing_docs)]
extern crate alloc;
mod device;
use alloc::{string::String, sync::Arc};
pub use axvm_types::{GuestPhysAddr, GuestPhysAddrRange, InterruptTriggerMode, IrqLineId};
pub use crate::device::{
AccessWidth, BusKind, DeviceAccess, DeviceAddr, DeviceAddrRange, DeviceError, DeviceResult,
Port, PortRange, SysRegAddr, SysRegAddrRange,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(u32);
impl DeviceId {
pub const fn new(id: u32) -> Self {
Self(id)
}
pub const fn as_u32(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceVcpuId(usize);
impl DeviceVcpuId {
pub const fn new(id: usize) -> Self {
Self(id)
}
pub const fn as_usize(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arch {
AArch64,
Riscv64,
X86_64,
LoongArch64,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Resource {
MmioRange {
base: u64,
size: u64,
},
PortRange {
base: u16,
size: u16,
},
SysReg {
addr: u32,
count: u32,
},
IrqLine {
line: u32,
trigger: InterruptTriggerMode,
},
}
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
pub enum InvalidResourceReason {
#[error("resource size or count is zero")]
ZeroSized,
#[error("resource end address overflows")]
AddressOverflow,
#[error("resource extends beyond the bus address range")]
OutOfBusRange,
#[error("resource bus is unsupported on this architecture")]
UnsupportedOnArchitecture,
#[error("device resources overlap")]
OverlappingResources,
#[error("duplicate IRQ line {line}")]
DuplicateIrqLine {
line: u32,
},
}
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
pub enum RegistryError {
#[error("invalid device resource {resource:?}: {reason}")]
InvalidResource {
resource: Resource,
reason: InvalidResourceReason,
},
#[error(
"device resource {resource:?} conflicts with {existing:?} owned by device \
{existing_device:?}"
)]
AddressConflict {
resource: Resource,
existing: Resource,
existing_device: DeviceId,
},
#[error("device bus {kind:?} is unsupported on {arch:?}")]
BusKindNotSupported {
kind: BusKind,
arch: Arch,
},
#[error(
"device {device_name} requires {required_arch:?}, but the current architecture is \
{current_arch:?}"
)]
ArchNotSupported {
device_name: String,
required_arch: Arch,
current_arch: Arch,
},
#[error("IRQ line {line} conflicts with device {existing_device:?}")]
IrqLineConflict {
line: u32,
existing_device: DeviceId,
},
#[error("invalid device registry state for {operation}: {detail}")]
InvalidState {
operation: &'static str,
detail: String,
},
}
pub trait Device: Send + Sync {
fn name(&self) -> &str;
fn resources(&self) -> &[Resource];
fn read(&self, access: &DeviceAccess, context: &mut dyn DeviceContext) -> DeviceResult<u64>;
fn write(
&self,
access: &DeviceAccess,
value: u64,
context: &mut dyn DeviceContext,
) -> DeviceResult;
}
macro_rules! define_grant {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Clone)]
pub struct $name {
token: Arc<()>,
}
impl $name {
pub fn new() -> Self {
Self { token: Arc::new(()) }
}
pub fn same_token(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.token, &other.token)
}
}
impl Default for $name {
fn default() -> Self {
Self::new()
}
}
};
}
define_grant!(
DmaGrant
);
define_grant!(
TimerGrant
);
define_grant!(
WakeGrant
);
define_grant!(
StopGrant
);
pub trait GuestMemoryAccess {
fn read(&mut self, addr: GuestPhysAddr, data: &mut [u8]) -> DeviceResult;
fn write(&mut self, addr: GuestPhysAddr, data: &[u8]) -> DeviceResult;
}
pub trait DeviceContext {
fn device_id(&self) -> DeviceId;
fn read_guest_memory(
&mut self,
_grant: &DmaGrant,
_addr: GuestPhysAddr,
_data: &mut [u8],
) -> DeviceResult {
Err(DeviceError::Unsupported {
operation: "read guest memory from device access",
detail: "this bus access has no DMA memory grant".into(),
})
}
fn write_guest_memory(
&mut self,
_grant: &DmaGrant,
_addr: GuestPhysAddr,
_data: &[u8],
) -> DeviceResult {
Err(DeviceError::Unsupported {
operation: "write guest memory from device access",
detail: "this bus access has no DMA memory grant".into(),
})
}
fn schedule_timer(&mut self, _grant: &TimerGrant, _deadline_ns: u64) -> DeviceResult {
Err(DeviceError::Unsupported {
operation: "schedule timer from device access",
detail: "this bus access has no timer grant".into(),
})
}
fn wake_vcpu(&mut self, _grant: &WakeGrant, _vcpu_id: usize) -> DeviceResult {
Err(DeviceError::Unsupported {
operation: "wake vCPU from device access",
detail: "this bus access has no wake grant".into(),
})
}
fn request_vm_stop(&mut self, _grant: &StopGrant, _reason: &str) -> DeviceResult {
Err(DeviceError::Unsupported {
operation: "request VM stop from device access",
detail: "this bus access has no stop grant".into(),
})
}
}
pub struct NoopDeviceContext {
device_id: DeviceId,
}
impl NoopDeviceContext {
pub const fn new(device_id: DeviceId) -> Self {
Self { device_id }
}
}
impl DeviceContext for NoopDeviceContext {
fn device_id(&self) -> DeviceId {
self.device_id
}
}
pub trait DeviceRegistry {
fn register(&mut self, device: Arc<dyn Device>) -> Result<DeviceId, RegistryError>;
}
mod interrupt;
pub use interrupt::*;