Skip to main content

regio/
mmio.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::marker::PhantomData;
6
7use super::{
8    AccessRestrictsTo, Accessible, IoHandle, LayoutOver, NoWrite, ReadHandle, Readable, Register,
9    Writable, WriteHandle,
10};
11
12/// Represents an in-memory register with a given layout and access permissions
13/// located at an offset from a base address.
14#[derive(Debug)]
15pub struct Offset<Layout, Access> {
16    /// The offset in bytes.
17    pub value: usize,
18    _marker: PhantomData<(Layout, Access)>,
19}
20
21impl<Layout, Access> Offset<Layout, Access> {
22    pub const fn new(value: usize) -> Self {
23        Self { value, _marker: PhantomData }
24    }
25}
26
27//
28// We manually implement Clone and Copy, without having to make further
29// assumptions about `Access`.
30//
31
32impl<Layout, Access> Clone for Offset<Layout, Access> {
33    fn clone(&self) -> Self {
34        Self { value: self.value, _marker: PhantomData }
35    }
36}
37
38impl<Layout, Access> Copy for Offset<Layout, Access> {}
39
40/// A specialization of [`Register`] representing an MMIO address.
41pub type Mmio<Layout, Base, Access> = Register<Layout, Access, MmioPtr<Base, Access>>;
42
43impl<Layout, Base, Access> Mmio<Layout, Base, Access>
44where
45    Layout: LayoutOver<Base>,
46    Base: Copy,
47    Access: Accessible,
48{
49    /// Constructs a new memory-backed register from a pointer wrapper.
50    pub const fn new<InputAccess>(ptr: MmioPtr<Base, InputAccess>) -> Self
51    where
52        InputAccess: AccessRestrictsTo<Access>,
53    {
54        // Safety: MmioPtr's construction attested to both the handle-specific
55        // access preconditions and that the access permissions are correctly
56        // modeled; restriction only narrows the latter claim.
57        unsafe { Register::from_io(ptr.restrict()) }
58    }
59}
60
61/// Represents a pointer to an MMIO address - with specific access
62/// permissions - at which a register might be located.
63#[derive(Debug)]
64pub struct MmioPtr<T, Access: Accessible>(*const T, PhantomData<Access>);
65
66//
67// Read-only pointers will deal in *const T, while writable ones will deal in
68// *mut T.
69//
70
71impl<T, R> MmioPtr<T, (R, NoWrite)>
72where
73    (R, NoWrite): Accessible,
74{
75    /// Constructs a new, read-only MMIO pointer wrapper.
76    ///
77    /// # Safety
78    ///
79    /// The caller must guarantee the safety preconditions of
80    /// [`core::ptr::read_volatile()`] for the lifetime of [`MmioPtr`]. In
81    /// particular...
82    ///
83    /// * that `ptr` is aligned and points to a mapped MMIO address;
84    ///
85    /// * the mapping remains valid for the lifetime of the returned `MmioPtr`
86    ///   (and any copies of it);
87    ///
88    /// * accesses must never trap.
89    ///
90    /// Note that it is not assumed that the backing memory is immutable.
91    pub const unsafe fn new(ptr: *const T) -> Self {
92        Self(ptr, PhantomData)
93    }
94
95    /// Returns the underlying pointer.
96    pub const fn as_ptr(self) -> *const T {
97        self.0
98    }
99}
100
101impl<T, Access: Writable> MmioPtr<T, Access> {
102    /// Constructs a new, writable MMIO pointer wrapper.
103    ///
104    /// # Safety
105    ///
106    /// The caller must guarantee the safety preconditions of
107    /// [`core::ptr::read_volatile()`] (if readable) and
108    /// [`core::ptr::write_volatile()`] for the lifetime of [`MmioPtr`]. In
109    /// particular...
110    ///
111    /// * that `ptr` is aligned and points to a mapped MMIO address;
112    ///
113    /// * the mapping remains valid for the lifetime of the returned `MmioPtr`
114    ///   (and any copies of it);
115    ///
116    /// * accesses must never trap.
117    ///
118    /// Also,
119    ///
120    /// * If `Access` expresses safe-writability (see
121    /// [`SafeWrite`](crate::SafeWrite)), that writing any value whatsoever to
122    /// this address cannot result in undefined behaviour.
123    ///
124    /// Note that it is not assumed that the backing memory is immutable, or
125    /// that a writable [`MmioPtr`] has exclusive access to it.
126    pub const unsafe fn new(ptr: *mut T) -> Self {
127        Self(ptr.cast(), PhantomData)
128    }
129
130    /// Returns the underlying pointer.
131    pub const fn as_ptr(self) -> *mut T {
132        // `MmioPtr`s with writable access were necessarily constructed with a
133        // mutable pointer (see above).
134        self.0.cast_mut()
135    }
136}
137
138impl<T, Access: Accessible> MmioPtr<T, Access> {
139    /// Restricts the access permissions of this pointer.
140    pub const fn restrict<NewAccess>(self) -> MmioPtr<T, NewAccess>
141    where
142        NewAccess: Accessible,
143        Access: AccessRestrictsTo<NewAccess>,
144    {
145        MmioPtr(self.0, PhantomData)
146    }
147}
148
149//
150// We manually implement Clone and Copy (as expected of a pointer), without
151// having to make further assumptions about `Access`.
152//
153
154impl<T, Access: Accessible> Clone for MmioPtr<T, Access> {
155    fn clone(&self) -> Self {
156        Self(self.0.clone(), PhantomData)
157    }
158}
159
160impl<T, Access: Accessible> Copy for MmioPtr<T, Access> {}
161
162impl<T, Access> IoHandle for MmioPtr<T, Access>
163where
164    T: Copy,
165    Access: Accessible,
166{
167    type Base = T;
168}
169
170macro_rules! impl_mmio_ptr_handle {
171    ($ty:ty, $read_fn:ident, $write_fn:ident) => {
172        impl<Access> ReadHandle for MmioPtr<$ty, Access>
173        where
174            Access: Readable,
175        {
176            #[inline(always)]
177            unsafe fn read_raw(&self) -> $ty {
178                unsafe { mmio_ptr::$read_fn(self.0) }
179            }
180        }
181
182        impl<Access> WriteHandle for MmioPtr<$ty, Access>
183        where
184            Access: Writable,
185        {
186            #[inline(always)]
187            unsafe fn write_raw(&self, value: $ty) {
188                // Regarding the `cast_mut()`, `MmioPtr`s with writable access were
189                // necessarily constructed with a mutable pointer (see above).
190                unsafe { mmio_ptr::$write_fn(value, self.0.cast_mut()) }
191            }
192        }
193    };
194}
195
196impl_mmio_ptr_handle!(u8, read8, write8);
197impl_mmio_ptr_handle!(u16, read16, write16);
198impl_mmio_ptr_handle!(u32, read32, write32);
199
200#[cfg(target_pointer_width = "64")]
201impl_mmio_ptr_handle!(u64, read64, write64);
202
203// Safety: An MMIO address has no thread affinity.
204unsafe impl<T, Access: Accessible> Send for MmioPtr<T, Access> {}
205
206// Safety: All accesses through `MmioPtr` are volatile operations.
207unsafe impl<T, Access: Accessible> Sync for MmioPtr<T, Access> {}
208
209/// Represents a contiguous region of memory containing registers. Contained
210/// registers are accessed via [`MmioBank::at`] and must feature access
211/// permissions narrower than `MaxAccess`.
212pub struct MmioBank<Base, MaxAccess: Accessible> {
213    base: MmioPtr<Base, MaxAccess>,
214    size_bytes: usize,
215}
216
217impl<Base, MaxAccess> MmioBank<Base, MaxAccess>
218where
219    Base: Copy,
220    MaxAccess: Accessible,
221{
222    /// Constructs a new bank from its base pointer and length.
223    pub const fn new(base: MmioPtr<Base, MaxAccess>, size_bytes: usize) -> Self {
224        Self { base, size_bytes }
225    }
226
227    /// Returns the memory-backed register at the specified offset into the
228    /// bank.
229    ///
230    /// # Panics
231    ///
232    /// Panics if the offset is misaligned for `Base` or exceeds the bounds of
233    /// the bank.
234    ///
235    /// # Safety
236    ///
237    /// Same as [`MmioPtr::new`] for underlying offset pointer.
238    pub const unsafe fn at<Layout, Access>(
239        &self,
240        offset: Offset<Layout, Access>,
241    ) -> Mmio<Layout, Base, Access>
242    where
243        Layout: LayoutOver<Base>,
244        Access: Accessible,
245        MaxAccess: AccessRestrictsTo<Access>,
246    {
247        assert!(offset.value.is_multiple_of(align_of::<Base>()));
248        assert!(size_of::<Base>() <= self.size_bytes);
249        assert!(offset.value <= self.size_bytes - size_of::<Base>());
250
251        // Safety: The offset pointer inherits the attested access
252        // preconditions of `self.base` (including having been constructed from
253        // a mutable pointer, if writable), with the asserts above ensuring
254        // that it remains within the bank's bounds.
255        let ptr = unsafe {
256            let offset_addr = self.base.0.byte_add(offset.value);
257            MmioPtr::<Base, MaxAccess>(offset_addr, PhantomData)
258        };
259        Mmio::new(ptr)
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266    use crate::{Ro, RwSafe, RwUnsafe, WoSafe, WoUnsafe};
267
268    #[test]
269    fn mmio_ptr_ro() {
270        let data = [10u32, 20u32];
271
272        // Safety: obviously a valid pointer.
273        let ptr = unsafe { MmioPtr::<u32, Ro>::new(data.as_ptr()) };
274
275        // Compile-time test of the raw pointer constructor.
276        //
277        // Safety: obviously points to valid memory.
278        let bank = MmioBank::new(ptr, size_of_val(&data));
279
280        const OFFSET0: Offset<u32, Ro> = Offset::new(0);
281        let reg0 = unsafe { bank.at(OFFSET0) }; // Safety: within bounds
282        assert_eq!(reg0.read(), 10);
283
284        const OFFSET4: Offset<u32, Ro> = Offset::new(4);
285        let reg4 = unsafe { bank.at(OFFSET4) }; // Safety: within bounds
286        assert_eq!(reg4.read(), 20);
287    }
288
289    #[test]
290    fn mmio_ptr_wo_safe() {
291        let mut data = [10u32, 20u32];
292
293        // Safety: obviously a valid pointer.
294        let ptr = unsafe { MmioPtr::<u32, WoSafe>::new(data.as_mut_ptr()) };
295        let bank = MmioBank::new(ptr, size_of_val(&data));
296
297        const OFFSET0: Offset<u32, WoSafe> = Offset::new(0);
298        let reg0 = unsafe { bank.at(OFFSET0) }; // Safety: within bounds
299        assert_eq!(data[0], 10);
300        reg0.write(100u32);
301        assert_eq!(data[0], 100);
302
303        const OFFSET4: Offset<u32, WoSafe> = Offset::new(4);
304        let reg4 = unsafe { bank.at(OFFSET4) }; // Safety: within bounds
305        assert_eq!(data[1], 20);
306        reg4.write(200u32);
307        assert_eq!(data[1], 200);
308    }
309
310    #[test]
311    fn mmio_ptr_wo_unsafe() {
312        let mut data = [10u32, 20u32];
313
314        // Safety: obviously a valid pointer.
315        let ptr = unsafe { MmioPtr::<u32, WoUnsafe>::new(data.as_mut_ptr()) };
316        let bank = MmioBank::new(ptr, size_of_val(&data));
317
318        const OFFSET0: Offset<u32, WoUnsafe> = Offset::new(0);
319        let reg0 = unsafe { bank.at(OFFSET0) }; // Safety: within bounds
320        assert_eq!(data[0], 10);
321        unsafe { reg0.write(100u32) }; // Unsafe required: WAI!
322        assert_eq!(data[0], 100);
323
324        const OFFSET4: Offset<u32, WoUnsafe> = Offset::new(4);
325        let reg4 = unsafe { bank.at(OFFSET4) }; // Safety: within bounds
326        assert_eq!(data[1], 20);
327        unsafe { reg4.write(200u32) }; // Unsafe required: WAI!
328        assert_eq!(data[1], 200);
329    }
330
331    #[test]
332    fn mmio_ptr_rw_safe() {
333        let mut data = [10u32, 20u32];
334
335        // Safety: obviously a valid pointer.
336        let ptr = unsafe { MmioPtr::<u32, RwSafe>::new(data.as_mut_ptr()) };
337        let bank = MmioBank::new(ptr, size_of_val(&data));
338
339        const OFFSET0: Offset<u32, RwSafe> = Offset::new(0);
340        let reg0 = unsafe { bank.at(OFFSET0) }; // Within bounds
341        assert_eq!(reg0.read(), 10);
342        reg0.write(100u32);
343        assert_eq!(reg0.read(), 100);
344        assert_eq!(data[0], 100);
345
346        const OFFSET4: Offset<u32, RwSafe> = Offset::new(4);
347        let reg4 = unsafe { bank.at(OFFSET4) }; // Within bounds
348        assert_eq!(reg4.read(), 20);
349        reg4.write(200u32);
350        assert_eq!(reg4.read(), 200);
351        assert_eq!(data[1], 200);
352
353        reg4.modify(|val| {
354            assert_eq!(*val, 200);
355            *val = 300;
356        });
357        assert_eq!(reg4.read(), 300);
358        assert_eq!(data[1], 300);
359    }
360
361    #[test]
362    fn mmio_ptr_rw_unsafe() {
363        let mut data = [10u32, 20u32];
364
365        // Safety: obviously a valid pointer.
366        let ptr = unsafe { MmioPtr::<u32, RwUnsafe>::new(data.as_mut_ptr()) };
367        let bank = MmioBank::new(ptr, size_of_val(&data));
368
369        const OFFSET0: Offset<u32, RwUnsafe> = Offset::new(0);
370        let reg0 = unsafe { bank.at(OFFSET0) }; // Within bounds
371        assert_eq!(reg0.read(), 10);
372        unsafe { reg0.write(100u32) }; // Unsafe required: WAI!
373        assert_eq!(reg0.read(), 100);
374        assert_eq!(data[0], 100);
375
376        const OFFSET4: Offset<u32, RwUnsafe> = Offset::new(4);
377        let reg4 = unsafe { bank.at(OFFSET4) }; // Within bounds
378        assert_eq!(reg4.read(), 20);
379        unsafe { reg4.write(200u32) }; // Unsafe required: WAI!
380        assert_eq!(reg4.read(), 200);
381        assert_eq!(data[1], 200);
382
383        // Unsafe required: WAI!
384        unsafe {
385            reg4.modify(|val| {
386                assert_eq!(*val, 200);
387                *val = 300;
388            });
389        }
390        assert_eq!(reg4.read(), 300);
391        assert_eq!(data[1], 300);
392    }
393
394    #[test]
395    fn mmio_bank_at_access_restrictions() {
396        let mut data = [0u32; 1];
397        let raw = data.as_mut_ptr();
398        let size = size_of_val(&data);
399
400        // Ro allows Ro
401        {
402            let base = unsafe { MmioPtr::<u32, Ro>::new(raw) };
403            let bank = MmioBank::new(base, size);
404            let _ = unsafe { bank.at(Offset::<u32, Ro>::new(0)) };
405        }
406
407        // WoSafe allows WoSafe, WoUnsafe
408        {
409            let base = unsafe { MmioPtr::<u32, WoSafe>::new(raw) };
410            let bank = MmioBank::new(base, size);
411            let _ = unsafe { bank.at(Offset::<u32, WoSafe>::new(0)) };
412            let _ = unsafe { bank.at(Offset::<u32, WoUnsafe>::new(0)) };
413        }
414
415        // WoUnsafe allows WoUnsafe
416        {
417            let base = unsafe { MmioPtr::<u32, WoUnsafe>::new(raw) };
418            let bank = MmioBank::new(base, size);
419            let _ = unsafe { bank.at(Offset::<u32, WoUnsafe>::new(0)) };
420        }
421
422        // RwSafe allows RwSafe, RwUnsafe, WoSafe, WoUnsafe, Ro
423        {
424            let base = unsafe { MmioPtr::<u32, RwSafe>::new(raw) };
425            let bank = MmioBank::new(base, size);
426            let _ = unsafe { bank.at(Offset::<u32, RwSafe>::new(0)) };
427            let _ = unsafe { bank.at(Offset::<u32, RwUnsafe>::new(0)) };
428            let _ = unsafe { bank.at(Offset::<u32, WoSafe>::new(0)) };
429            let _ = unsafe { bank.at(Offset::<u32, WoUnsafe>::new(0)) };
430            let _ = unsafe { bank.at(Offset::<u32, Ro>::new(0)) };
431        }
432
433        // RwUnsafe allows RwUnsafe, WoUnsafe, Ro
434        {
435            let base = unsafe { MmioPtr::<u32, RwUnsafe>::new(raw) };
436            let bank = MmioBank::new(base, size);
437            let _ = unsafe { bank.at(Offset::<u32, RwUnsafe>::new(0)) };
438            let _ = unsafe { bank.at(Offset::<u32, WoUnsafe>::new(0)) };
439            let _ = unsafe { bank.at(Offset::<u32, Ro>::new(0)) };
440        }
441    }
442}