Skip to main content

strat9_bus_drivers/
mmio.rs

1use core::sync::atomic::Ordering;
2
3pub struct MmioRegion {
4    base: usize,
5    size: usize,
6}
7
8impl MmioRegion {
9    /// Creates a new instance.
10    pub const fn new() -> Self {
11        Self { base: 0, size: 0 }
12    }
13
14    /// Performs the init operation.
15    pub fn init(&mut self, base: usize, size: usize) {
16        self.base = base;
17        self.size = size;
18    }
19
20    /// Performs the base operation.
21    pub fn base(&self) -> usize {
22        self.base
23    }
24
25    /// Returns whether valid.
26    pub fn is_valid(&self) -> bool {
27        self.base != 0
28    }
29
30    /// Bounds + alignment check for a would-be access of `width` bytes at
31    /// `offset`, without performing it.
32    ///
33    /// Drivers exposing user-controllable offsets (e.g. `/bus/<drv>/reg/<hex>`)
34    /// must call this before touching MMIO so an out-of-range request fails
35    /// with a clean error instead of tripping the panic backstop in
36    /// [`Self::checked_addr`] and taking the kernel down.
37    pub fn contains(&self, offset: usize, width: usize) -> bool {
38        if width == 0 {
39            return false;
40        }
41        match offset.checked_add(width) {
42            Some(end) => self.base != 0 && end <= self.size && offset % width == 0,
43            None => false,
44        }
45    }
46    /// Validate a caller-supplied 32-bit register offset (bounds + natural
47    /// alignment), mapping failures to [`BusError::InvalidAddress`] instead
48    /// of tripping the panic backstop inside the accessors.
49    pub fn check_user_offset(&self, offset: usize) -> Result<(), crate::BusError> {
50        if self.contains(offset, core::mem::size_of::<u32>()) {
51            Ok(())
52        } else {
53            Err(crate::BusError::InvalidAddress)
54        }
55    }
56
57    /// Performs the checked addr operation.
58    ///
59    /// Enforces region bounds AND natural alignment for the access width:
60    /// `read_volatile`/`write_volatile` on a misaligned pointer is UB on
61    /// several supported architectures (strict-alignment ARM/MIPS), not
62    /// just a slow access.
63    fn checked_addr(&self, offset: usize, width: usize) -> usize {
64        let base = self.base();
65        assert!(base != 0, "mmio access on uninitialized MmioRegion");
66        let end = offset.checked_add(width).expect("mmio offset overflow");
67        assert!(
68            end <= self.size,
69            "mmio access out of bounds (offset={:#x}, width={}, size={:#x})",
70            offset,
71            width,
72            self.size
73        );
74        assert!(
75            offset % width == 0,
76            "misaligned mmio access (offset={:#x}, width={})",
77            offset,
78            width
79        );
80        base.checked_add(offset).expect("mmio address overflow")
81    }
82
83    /// Performs the read8 operation.
84    pub fn read8(&self, offset: usize) -> u8 {
85        let addr = self.checked_addr(offset, core::mem::size_of::<u8>());
86        // SAFETY: caller guarantees this address is a valid MMIO region
87        unsafe { core::ptr::read_volatile(addr as *const u8) }
88    }
89
90    /// Performs the read16 operation.
91    pub fn read16(&self, offset: usize) -> u16 {
92        let addr = self.checked_addr(offset, core::mem::size_of::<u16>());
93        // SAFETY: caller guarantees this address is a valid MMIO region
94        unsafe { core::ptr::read_volatile(addr as *const u16) }
95    }
96
97    /// Performs the read32 operation.
98    pub fn read32(&self, offset: usize) -> u32 {
99        let addr = self.checked_addr(offset, core::mem::size_of::<u32>());
100        // SAFETY: caller guarantees this address is a valid MMIO region
101        unsafe { core::ptr::read_volatile(addr as *const u32) }
102    }
103
104    /// Performs the read64 operation.
105    pub fn read64(&self, offset: usize) -> u64 {
106        let addr = self.checked_addr(offset, core::mem::size_of::<u64>());
107        // SAFETY: caller guarantees this address is a valid MMIO region
108        unsafe { core::ptr::read_volatile(addr as *const u64) }
109    }
110
111    /// Performs the write8 operation.
112    pub fn write8(&self, offset: usize, val: u8) {
113        let addr = self.checked_addr(offset, core::mem::size_of::<u8>());
114        // SAFETY: caller guarantees this address is a valid MMIO region
115        unsafe { core::ptr::write_volatile(addr as *mut u8, val) }
116    }
117
118    /// Performs the write16 operation.
119    pub fn write16(&self, offset: usize, val: u16) {
120        let addr = self.checked_addr(offset, core::mem::size_of::<u16>());
121        // SAFETY: caller guarantees this address is a valid MMIO region
122        unsafe { core::ptr::write_volatile(addr as *mut u16, val) }
123    }
124
125    /// Performs the write32 operation.
126    pub fn write32(&self, offset: usize, val: u32) {
127        let addr = self.checked_addr(offset, core::mem::size_of::<u32>());
128        // SAFETY: caller guarantees this address is a valid MMIO region
129        unsafe { core::ptr::write_volatile(addr as *mut u32, val) }
130    }
131
132    /// Performs the write64 operation.
133    pub fn write64(&self, offset: usize, val: u64) {
134        let addr = self.checked_addr(offset, core::mem::size_of::<u64>());
135        // SAFETY: caller guarantees this address is a valid MMIO region
136        unsafe { core::ptr::write_volatile(addr as *mut u64, val) }
137    }
138
139    /// Sets bits32.
140    pub fn set_bits32(&self, offset: usize, bits: u32) {
141        let val = self.read32(offset);
142        self.write32(offset, val | bits);
143    }
144
145    /// Performs the clear bits32 operation.
146    pub fn clear_bits32(&self, offset: usize, bits: u32) {
147        let val = self.read32(offset);
148        self.write32(offset, val & !bits);
149    }
150
151    /// Performs the modify32 operation.
152    pub fn modify32(&self, offset: usize, clear: u32, set: u32) {
153        let val = self.read32(offset);
154        self.write32(offset, (val & !clear) | set);
155    }
156
157    /// Reads field32.
158    pub fn read_field32(&self, offset: usize, mask: u32, shift: u32) -> u32 {
159        (self.read32(offset) & mask) >> shift
160    }
161
162    /// Writes field32.
163    ///
164    /// `value` is masked to the field width *before* shifting so that a
165    /// caller passing an unshifted field value can never trigger a shift
166    /// overflow (`value` may be up to u32::MAX when `shift > 0`). Bits of
167    /// `value` outside the field width are ignored by contract.
168    pub fn write_field32(&self, offset: usize, mask: u32, shift: u32, value: u32) {
169        let width_mask = mask >> shift;
170        let field = ((value & width_mask) << shift) & mask;
171        self.modify32(offset, mask, field);
172    }
173}
174
175// SAFETY: MmioRegion contains only an atomic base address and a size.
176// Access to the MMIO region itself requires the caller to ensure
177// the mapping is valid and not concurrently mutated.
178unsafe impl Send for MmioRegion {}
179unsafe impl Sync for MmioRegion {}
180
181/// Performs the memory barrier operation.
182pub fn memory_barrier() {
183    core::sync::atomic::fence(Ordering::SeqCst);
184}