use alloc::{boxed::Box, string::String, sync::Arc};
use core::any::Any;
use crate::{
BaseDeviceOps, Device, EmuDeviceType, GuestPhysAddr, Resource,
device::{BusAccess, BusResponse, DeviceError, Port, PortRange, SysRegAddr, SysRegAddrRange},
};
fn type_name(emu_type: EmuDeviceType) -> String {
alloc::format!("{:?}-adapter", emu_type)
}
fn mmio_resources(range: &crate::GuestPhysAddrRange) -> Box<[Resource]> {
let base = range.start.as_usize() as u64;
let size = if range.end.as_usize() >= range.start.as_usize() {
(range.end.as_usize() - range.start.as_usize()) as u64
} else {
0
};
alloc::vec![Resource::MmioRange { base, size }].into_boxed_slice()
}
fn sysreg_resources(range: &SysRegAddrRange) -> Box<[Resource]> {
let addr = range.start.0 as u32;
let count = if range.end.0 >= range.start.0 {
(range.end.0 - range.start.0) as u32 + 1
} else {
0
};
alloc::vec![Resource::SysReg { addr, count }].into_boxed_slice()
}
fn port_resources(range: &PortRange) -> Box<[Resource]> {
let base = range.start.0;
let size = if range.end.0 >= range.start.0 {
(range.end.0 - range.start.0).wrapping_add(1)
} else {
0
};
alloc::vec![Resource::PortRange { base, size }].into_boxed_slice()
}
pub struct MmioDeviceAdapter<T> {
inner: Arc<T>,
name: String,
resources: Box<[Resource]>,
}
impl<T: Send> MmioDeviceAdapter<T>
where
T: BaseDeviceOps<crate::GuestPhysAddrRange>,
{
pub fn new(device: T) -> Self {
let resources = mmio_resources(&device.address_range());
Self {
name: type_name(device.emu_type()),
inner: Arc::new(device),
resources,
}
}
pub fn from_arc(device: Arc<T>) -> Arc<dyn Device>
where
T: Send + Sync + 'static,
T: BaseDeviceOps<crate::GuestPhysAddrRange>,
{
let resources = mmio_resources(&device.address_range());
Arc::new(Self {
name: type_name(device.emu_type()),
inner: device,
resources,
})
}
pub fn inner(&self) -> &T {
&self.inner
}
}
impl<T: Send + Sync + 'static> Device for MmioDeviceAdapter<T>
where
T: BaseDeviceOps<crate::GuestPhysAddrRange>,
{
fn name(&self) -> &str {
&self.name
}
fn resources(&self) -> &[Resource] {
&self.resources
}
fn handle(&self, access: &BusAccess) -> Result<BusResponse, DeviceError> {
let addr = GuestPhysAddr::from(access.addr as usize);
if access.is_read {
self.inner
.handle_read(addr, access.width)
.map(|v| BusResponse::Read { value: v as u64 })
.map_err(|_| DeviceError::Internal)
} else {
self.inner
.handle_write(addr, access.width, access.data as usize)
.map(|_| BusResponse::Write)
.map_err(|_| DeviceError::Internal)
}
}
fn as_any(&self) -> &dyn Any {
&*self.inner
}
}
pub struct SysRegDeviceAdapter<T> {
inner: Arc<T>,
name: String,
resources: Box<[Resource]>,
}
impl<T: Send> SysRegDeviceAdapter<T>
where
T: BaseDeviceOps<SysRegAddrRange>,
{
pub fn new(device: T) -> Self {
let resources = sysreg_resources(&device.address_range());
Self {
name: type_name(device.emu_type()),
inner: Arc::new(device),
resources,
}
}
pub fn from_arc(device: Arc<T>) -> Arc<dyn Device>
where
T: Send + Sync + 'static,
T: BaseDeviceOps<SysRegAddrRange>,
{
let resources = sysreg_resources(&device.address_range());
Arc::new(Self {
name: type_name(device.emu_type()),
inner: device,
resources,
})
}
pub fn inner(&self) -> &T {
&self.inner
}
}
impl<T: Send + Sync + 'static> Device for SysRegDeviceAdapter<T>
where
T: BaseDeviceOps<SysRegAddrRange>,
{
fn name(&self) -> &str {
&self.name
}
fn resources(&self) -> &[Resource] {
&self.resources
}
fn handle(&self, access: &BusAccess) -> Result<BusResponse, DeviceError> {
let addr = SysRegAddr::new(access.addr as usize);
if access.is_read {
self.inner
.handle_read(addr, access.width)
.map(|v| BusResponse::Read { value: v as u64 })
.map_err(|_| DeviceError::Internal)
} else {
self.inner
.handle_write(addr, access.width, access.data as usize)
.map(|_| BusResponse::Write)
.map_err(|_| DeviceError::Internal)
}
}
fn as_any(&self) -> &dyn Any {
&*self.inner
}
}
pub struct PortDeviceAdapter<T> {
inner: Arc<T>,
name: String,
resources: Box<[Resource]>,
}
impl<T: Send> PortDeviceAdapter<T>
where
T: BaseDeviceOps<PortRange>,
{
pub fn new(device: T) -> Self {
let resources = port_resources(&device.address_range());
Self {
name: type_name(device.emu_type()),
inner: Arc::new(device),
resources,
}
}
pub fn from_arc(device: Arc<T>) -> Arc<dyn Device>
where
T: Send + Sync + 'static,
T: BaseDeviceOps<PortRange>,
{
let resources = port_resources(&device.address_range());
Arc::new(Self {
name: type_name(device.emu_type()),
inner: device,
resources,
})
}
pub fn inner(&self) -> &T {
&self.inner
}
}
impl<T: Send + Sync + 'static> Device for PortDeviceAdapter<T>
where
T: BaseDeviceOps<PortRange>,
{
fn name(&self) -> &str {
&self.name
}
fn resources(&self) -> &[Resource] {
&self.resources
}
fn handle(&self, access: &BusAccess) -> Result<BusResponse, DeviceError> {
let port = Port::new(access.addr as u16);
if access.is_read {
self.inner
.handle_read(port, access.width)
.map(|v| BusResponse::Read { value: v as u64 })
.map_err(|_| DeviceError::Internal)
} else {
self.inner
.handle_write(port, access.width, access.data as usize)
.map(|_| BusResponse::Write)
.map_err(|_| DeviceError::Internal)
}
}
fn as_any(&self) -> &dyn Any {
&*self.inner
}
}
#[cfg(test)]
mod tests {
use ax_errno::AxResult;
use axvm_types::GuestPhysAddr;
use super::MmioDeviceAdapter;
use crate::{
BaseDeviceOps, Device, EmuDeviceType, GuestPhysAddrRange, Resource,
device::{AccessWidth, BusAccess, BusKind, BusResponse},
};
struct MockMmioDevice {
addr: GuestPhysAddr,
size: usize,
read_val: usize,
}
impl BaseDeviceOps<GuestPhysAddrRange> for MockMmioDevice {
fn emu_type(&self) -> EmuDeviceType {
EmuDeviceType::Dummy
}
fn address_range(&self) -> GuestPhysAddrRange {
(self.addr..GuestPhysAddr::from(self.addr.as_usize() + self.size))
.try_into()
.unwrap()
}
fn handle_read(&self, _addr: GuestPhysAddr, _width: AccessWidth) -> AxResult<usize> {
Ok(self.read_val)
}
fn handle_write(&self, _addr: GuestPhysAddr, _width: AccessWidth, _val: usize) -> AxResult {
Ok(())
}
}
#[test]
fn test_mmio_adapter() {
let dev = MockMmioDevice {
addr: GuestPhysAddr::from(0x1000),
size: 0x100,
read_val: 42,
};
let adapter = MmioDeviceAdapter::new(dev);
let r = adapter.resources();
assert_eq!(r.len(), 1);
match r[0] {
Resource::MmioRange { base, size } => {
assert_eq!(base, 0x1000);
assert_eq!(size, 0x100);
}
_ => panic!(),
}
let resp = adapter
.handle(&BusAccess {
kind: BusKind::Mmio,
is_read: true,
addr: 0x1000,
width: AccessWidth::Dword,
data: 0,
})
.unwrap();
match resp {
BusResponse::Read { value } => assert_eq!(value, 42),
_ => panic!(),
}
assert!(adapter.as_any().downcast_ref::<MockMmioDevice>().is_some());
}
}