/// An MMIO register which can only be read from. #[derive(Default)] #[repr(transparent)] pub struct ReadOnly(pub(crate) T); impl ReadOnly { /// Construct a new instance for testing. pub const fn new(value: T) -> Self { Self(value) } } /// An MMIO register which can only be written to. #[derive(Default)] #[repr(transparent)] pub struct WriteOnly(pub(crate) T); /// An MMIO register which may be both read and written. #[derive(Default)] #[repr(transparent)] pub struct Volatile(T); impl Volatile { /// Construct a new instance for testing. pub const fn new(value: T) -> Self { Self(value) } } /// A trait implemented by MMIO registers which may be read from. pub trait VolatileReadable { /// Performs a volatile read from the MMIO register. unsafe fn vread(self) -> T; } #[cfg(not(target_arch = "aarch64"))] impl VolatileReadable for *const ReadOnly { unsafe fn vread(self) -> T { self.read_volatile().0 } } #[cfg(not(target_arch = "aarch64"))] impl VolatileReadable for *const Volatile { unsafe fn vread(self) -> T { self.read_volatile().0 } } /// A trait implemented by MMIO registers which may be written to. pub trait VolatileWritable { /// Performs a volatile write to the MMIO register. unsafe fn vwrite(self, value: T); } #[cfg(not(target_arch = "aarch64"))] impl VolatileWritable for *mut WriteOnly { unsafe fn vwrite(self, value: T) { (self as *mut T).write_volatile(value) } } #[cfg(not(target_arch = "aarch64"))] impl VolatileWritable for *mut Volatile { unsafe fn vwrite(self, value: T) { (self as *mut T).write_volatile(value) } } #[cfg(target_arch = "aarch64")] mod aarch64_mmio { use super::{ReadOnly, Volatile, VolatileReadable, VolatileWritable, WriteOnly}; use crate::{device::net::Status, transport::DeviceStatus}; use core::arch::asm; macro_rules! asm_mmio_write { ($t:ty, $assembly:literal) => { impl VolatileWritable<$t> for *mut WriteOnly<$t> { unsafe fn vwrite(self, value: $t) { asm!( $assembly, value = in(reg) value, ptr = in(reg) (self as *mut $t), ); } } impl VolatileWritable<$t> for *mut Volatile<$t> { unsafe fn vwrite(self, value: $t) { asm!( $assembly, value = in(reg) value, ptr = in(reg) (self as *mut $t), ); } } }; } macro_rules! asm_mmio_read { ($t:ty, $assembly:literal) => { impl VolatileReadable<$t> for *const ReadOnly<$t> { unsafe fn vread(self) -> $t { let value; asm!( $assembly, value = out(reg) value, ptr = in(reg) (self as *const $t), ); value } } impl VolatileReadable<$t> for *const Volatile<$t> { unsafe fn vread(self) -> $t { let value; asm!( $assembly, value = out(reg) value, ptr = in(reg) (self as *const $t), ); value } } }; } asm_mmio_write!(u8, "strb {value:w}, [{ptr}]"); asm_mmio_write!(u16, "strh {value:w}, [{ptr}]"); asm_mmio_write!(u32, "str {value:w}, [{ptr}]"); asm_mmio_write!(u64, "str {value:x}, [{ptr}]"); impl VolatileWritable for *mut WriteOnly { unsafe fn vwrite(self, value: DeviceStatus) { let value: u32 = value.bits(); asm!( "str {value:w}, [{ptr}]", value = in(reg) value, ptr = in(reg) (self as *mut u32), ); } } impl VolatileWritable for *mut Volatile { unsafe fn vwrite(self, value: DeviceStatus) { let value: u32 = value.bits(); asm!( "str {value:w}, [{ptr}]", value = in(reg) value, ptr = in(reg) (self as *mut u32), ); } } asm_mmio_read!(u8, "ldrb {value:w}, [{ptr}]"); asm_mmio_read!(u16, "ldrh {value:w}, [{ptr}]"); asm_mmio_read!(u32, "ldr {value:w}, [{ptr}]"); asm_mmio_read!(u64, "ldr {value:x}, [{ptr}]"); impl VolatileReadable for *const ReadOnly { unsafe fn vread(self) -> DeviceStatus { let value: u32; asm!( "ldr {value:w}, [{ptr}]", value = out(reg) value, ptr = in(reg) (self as *const u32), ); DeviceStatus::from_bits_retain(value) } } impl VolatileReadable for *const Volatile { unsafe fn vread(self) -> DeviceStatus { let value: u32; asm!( "ldr {value:w}, [{ptr}]", value = out(reg) value, ptr = in(reg) (self as *const u32), ); DeviceStatus::from_bits_retain(value) } } impl VolatileReadable for *const ReadOnly { unsafe fn vread(self) -> Status { let value: u16; asm!( "ldrh {value:w}, [{ptr}]", value = out(reg) value, ptr = in(reg) (self as *const u16), ); Status::from_bits_retain(value) } } impl VolatileReadable for *const Volatile { unsafe fn vread(self) -> Status { let value: u16; asm!( "ldrh {value:w}, [{ptr}]", value = out(reg) value, ptr = in(reg) (self as *const u16), ); Status::from_bits_retain(value) } } impl VolatileReadable<[u8; SIZE]> for *const ReadOnly<[u8; SIZE]> { unsafe fn vread(self) -> [u8; SIZE] { let mut value = [0; SIZE]; for i in 0..SIZE { asm!( "ldrb {value:w}, [{ptr}]", value = out(reg) value[i], ptr = in(reg) (self as *const u8).add(i), ); } value } } } /// Performs a volatile read from the given field of pointer to a struct representing an MMIO region. /// /// # Usage /// ```compile_fail /// # use core::ptr::NonNull; /// # use virtio_drivers::volatile::{ReadOnly, volread}; /// struct MmioDevice { /// field: ReadOnly, /// } /// /// let device: NonNull = NonNull::new(0x1234 as *mut MmioDevice).unwrap(); /// let value = unsafe { volread!(device, field) }; /// ``` macro_rules! volread { ($nonnull:expr, $field:ident) => { $crate::volatile::VolatileReadable::vread(core::ptr::addr_of!((*$nonnull.as_ptr()).$field)) }; } /// Performs a volatile write to the given field of pointer to a struct representing an MMIO region. /// /// # Usage /// ```compile_fail /// # use core::ptr::NonNull; /// # use virtio_drivers::volatile::{WriteOnly, volread}; /// struct MmioDevice { /// field: WriteOnly, /// } /// /// let device: NonNull = NonNull::new(0x1234 as *mut MmioDevice).unwrap(); /// unsafe { volwrite!(device, field, 42); } /// ``` macro_rules! volwrite { ($nonnull:expr, $field:ident, $value:expr) => { $crate::volatile::VolatileWritable::vwrite( core::ptr::addr_of_mut!((*$nonnull.as_ptr()).$field), $value, ) }; } pub(crate) use volread; pub(crate) use volwrite;