Skip to main content

mmio_api/
lib.rs

1#![cfg_attr(target_os = "none", no_std)]
2
3use core::{fmt::Display, ops::Deref, ptr::NonNull, sync::atomic::Ordering};
4
5#[cfg(test)]
6extern crate alloc;
7
8#[derive(thiserror::Error, Debug)]
9pub enum MapError {
10    #[error("Invalid MMIO address or size")]
11    Invalid,
12    #[error("Failed to allocate memory for MMIO mapping")]
13    NoMemory,
14    #[error("MMIO address is already in use")]
15    Busy,
16}
17
18pub trait MmioOp: Sync + Send + 'static {
19    fn ioremap(&self, addr: MmioAddr, size: usize) -> Result<MmioRaw, MapError>;
20    fn iounmap(&self, mmio: &MmioRaw);
21}
22
23static mut MMIO_OP: Option<&'static dyn MmioOp> = None;
24static INIT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
25
26pub fn init(mmio_op: &'static dyn MmioOp) {
27    if INIT
28        .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
29        .is_err()
30    {
31        return;
32    }
33
34    unsafe {
35        MMIO_OP = Some(mmio_op);
36    }
37}
38
39/// # Safety
40///
41/// Caller should manually unmap the returned `Mmio` by calling `iounmap` when it is no longer needed.
42pub unsafe fn ioremap_raw(addr: MmioAddr, size: usize) -> Result<MmioRaw, MapError> {
43    let mmio_op = unsafe { MMIO_OP.expect("MmioOp is not initialized") };
44    mmio_op.ioremap(addr, size)
45}
46
47/// # Safety
48///
49/// Caller must ensure that `mmio` was previously mapped by `ioremap`.
50pub unsafe fn iounmap(mmio: &MmioRaw) {
51    let mmio_op = unsafe { MMIO_OP.expect("MmioOp is not initialized") };
52    mmio_op.iounmap(mmio);
53}
54
55pub fn ioremap(addr: MmioAddr, size: usize) -> Result<Mmio, MapError> {
56    let mmio = unsafe { ioremap_raw(addr, size)? };
57    Ok(Mmio(mmio))
58}
59
60/// Physical MMIO Address
61#[derive(
62    Default,
63    derive_more::From,
64    derive_more::Into,
65    Clone,
66    Copy,
67    derive_more::Debug,
68    derive_more::Display,
69    PartialEq,
70    Eq,
71    PartialOrd,
72    Ord,
73    Hash,
74)]
75#[repr(transparent)]
76#[debug("PhysAddr({_0:#x})")]
77#[display("{_0:#x}")]
78pub struct MmioAddr(usize);
79
80impl MmioAddr {
81    pub fn as_usize(&self) -> usize {
82        self.0
83    }
84}
85
86impl From<u64> for MmioAddr {
87    fn from(value: u64) -> Self {
88        MmioAddr(value as usize)
89    }
90}
91
92#[derive(Debug, Clone)]
93pub struct MmioRaw {
94    phys: MmioAddr,
95    virt: NonNull<u8>,
96    size: usize,
97}
98
99impl MmioRaw {
100    /// # Safety
101    ///
102    /// Caller must ensure that `virt` is a valid mapping for the given `phys` and `size`.
103    pub unsafe fn new(phys: MmioAddr, virt: NonNull<u8>, size: usize) -> Self {
104        MmioRaw { phys, virt, size }
105    }
106
107    pub fn phys_addr(&self) -> MmioAddr {
108        self.phys
109    }
110
111    pub fn as_slice(&self) -> &[u8] {
112        unsafe { core::slice::from_raw_parts(self.virt.as_ptr(), self.size) }
113    }
114
115    pub fn as_ptr(&self) -> *mut u8 {
116        self.virt.as_ptr()
117    }
118
119    pub fn as_nonnull_ptr(&self) -> NonNull<u8> {
120        self.virt
121    }
122
123    pub fn size(&self) -> usize {
124        self.size
125    }
126
127    pub fn read<T>(&self, offset: usize) -> T {
128        assert!(offset < self.size);
129        unsafe { self.virt.add(offset).cast::<T>().read_volatile() }
130    }
131
132    pub fn write<T>(&self, offset: usize, value: T) {
133        assert!(offset < self.size);
134        unsafe { self.virt.add(offset).cast::<T>().write_volatile(value) }
135    }
136}
137
138pub struct Mmio(MmioRaw);
139
140impl Deref for Mmio {
141    type Target = MmioRaw;
142
143    fn deref(&self) -> &Self::Target {
144        &self.0
145    }
146}
147
148impl Drop for Mmio {
149    fn drop(&mut self) {
150        let mmio_op = unsafe { MMIO_OP.expect("MmioOp is not initialized") };
151        mmio_op.iounmap(self);
152    }
153}
154
155unsafe impl Send for MmioRaw {}
156unsafe impl Sync for MmioRaw {}
157
158impl Display for MmioRaw {
159    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
160        write!(
161            f,
162            "Mmio [{}, {:#x}) -> virt: {:#p}",
163            self.phys,
164            self.phys.0 + self.size,
165            self.virt
166        )
167    }
168}
169
170#[cfg(all(test, not(target_os = "none")))]
171mod tests {
172    use super::MmioRaw;
173
174    struct DummyMmioOp;
175    impl super::MmioOp for DummyMmioOp {
176        fn ioremap(&self, addr: super::MmioAddr, size: usize) -> Result<MmioRaw, super::MapError> {
177            Ok(MmioRaw {
178                phys: addr,
179                virt: core::ptr::NonNull::dangling(),
180                size,
181            })
182        }
183
184        fn iounmap(&self, _mmio: &MmioRaw) {}
185    }
186
187    #[test]
188    fn test_mmio_new() {
189        super::init(&DummyMmioOp);
190
191        let addr = MmioRaw {
192            phys: super::MmioAddr(0x1000),
193            virt: core::ptr::NonNull::dangling(),
194            size: 0x100,
195        };
196        println!("Mmio address: {:?}", addr);
197        println!("Mmio address display: {}", addr);
198    }
199}