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