Skip to main content

mmio/
mmio.rs

1// Copyright 2025 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.
4use crate::register::{
5    IndexedRegister, IndexedRegisterProxy, IndexedRegisterProxyMut, ReadableIndexedRegister,
6    ReadableRegister, Register, RegisterProxy, RegisterProxyMut, WritableIndexedRegister,
7    WritableRegister,
8};
9use core::fmt::{Debug, Display};
10use core::ops::{BitAnd, BitOr, Not};
11
12/// An error which may be encountered when performing operations on an MMIO region.
13#[derive(Copy, Clone, PartialEq, Eq, Debug, thiserror::Error)]
14pub enum MmioError {
15    /// An operation was attempted outside the bounds of an MMIO region.
16    #[error("An MMIO operation was out of range")]
17    OutOfRange,
18    /// An operation would be unaligned for the operand type at the given offset into the MMIO
19    /// region.
20    #[error("An MMIO operation was unaligned")]
21    Unaligned,
22}
23
24/// An `Mmio` implementation provides access to an MMIO region.
25///
26/// # Device memory
27/// For implementations that provide access to device memory, all memory must be accessed with
28/// volatile semantics. I/O instructions should not be coalesced or cached and should occur in
29/// program order.
30///
31/// Implementations are not required to provide any ordering guarantees between device memory
32/// access and other instructions. Furthermore there is no guarantee that any side-effects are
33/// visible to subsequent operations.
34///
35/// Callers who need guarantees around instruction ordering and side-effect visibility should
36/// insert the appropriate compiler fences and/or memory barriers.
37///
38/// # Safety
39/// Implementations of `Mmio` are required to be safe in a Rust sense, however loads may have
40/// side-effects and device memory may change outside of the control of the host.
41///
42/// # Offsets, Alignment and Capacity
43/// Mmio implementations provide access to device memory via offsets, not addresses. There are no
44/// requirements on the alignment of offset `0`.
45///
46/// When using the load and store operations, callers are required to provide offsets representing
47/// locations that are suitably aligned and fully within bounds of the location. Failure to do so
48/// does not cause any safety issues, but may result in errors for the checked loads and stores, or
49/// panics for the non-checked variants.
50///
51/// If these guarantees can't be provided statically for a given use case, the following functions
52/// can be used to ensure these requirements:
53///
54/// - [MmioExt::check_aligned_for<T>]: returns an [MmioError::Unaligned] error if the given offset is
55///   not suitably aligned.
56/// - [MmioExt::check_capacity_for<T>]: returns an [MmioError::OutOfRange] error if there is not
57///   sufficient capacity at the given offset.
58/// - [MmioExt::check_suitable_for<T>]: returns an [MmioError] if there is not sufficient capacity at
59///   the given offset or it is not suitably aligned.
60///
61/// # Dyn Compatibility
62/// This trait is dyn compatible. See the [MmioExt] trait for useful utilities that extend this
63/// trait.
64pub trait Mmio {
65    /// Returns the size in bytes of this MMIO region.
66    fn len(&self) -> usize;
67
68    /// Returns true if the MMIO region has a length of 0.
69    fn is_empty(&self) -> bool {
70        self.len() == 0
71    }
72
73    /// Computes the first offset within this region that is aligned to `align`.
74    ///
75    /// See the trait-level documentation for more information on offsets and alignment.
76    ///
77    /// # Panics
78    /// If `align` is not a power of 2.
79    fn align_offset(&self, align: usize) -> usize;
80
81    /// Loads one byte from this MMIO region at the given offset.
82    ///
83    /// See the trait-level documentation for information about offset requirements.
84    ///
85    /// # Errors
86    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
87    fn try_load8(&self, offset: usize) -> Result<u8, MmioError>;
88
89    /// Loads one byte from this MMIO region at the given offset.
90    ///
91    /// See the trait-level documentation for information about offset requirements.
92    ///
93    /// # Panics
94    /// - If the load would exceed the bounds of this MMIO region.
95    ///
96    /// See [Mmio::try_load8] for a non-panicking version.
97    fn load8(&self, offset: usize) -> u8 {
98        self.try_load8(offset).unwrap()
99    }
100
101    /// Loads two bytes from this MMIO region at the given offset.
102    ///
103    /// See the trait-level documentation for information about offset requirements.
104    ///
105    /// # Errors
106    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
107    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
108    fn try_load16(&self, offset: usize) -> Result<u16, MmioError>;
109
110    /// Loads two bytes from this MMIO region at the given offset.
111    ///
112    /// See the trait-level documentation for information about offset requirements.
113    ///
114    /// # Panics
115    /// - If the load would exceed the bounds of this MMIO region.
116    /// - If the offset is not suitably aligned.
117    ///
118    /// See [Mmio::try_load16] for a non-panicking version.
119    fn load16(&self, offset: usize) -> u16 {
120        self.try_load16(offset).unwrap()
121    }
122
123    /// Loads four bytes from this MMIO region at the given offset.
124    ///
125    /// See the trait-level documentation for information about offset requirements.
126    ///
127    /// # Errors
128    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
129    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
130    fn try_load32(&self, offset: usize) -> Result<u32, MmioError>;
131
132    /// Loads four bytes from this MMIO region at the given offset.
133    ///
134    /// See the trait-level documentation for information about offset requirements.
135    ///
136    /// # Panics
137    /// - If the load would exceed the bounds of this MMIO region.
138    /// - If the offset is not suitably aligned.
139    ///
140    /// See [Mmio::try_load32] for a non-panicking version.
141    fn load32(&self, offset: usize) -> u32 {
142        self.try_load32(offset).unwrap()
143    }
144
145    /// Loads eight bytes from this MMIO region at the given offset.
146    ///
147    /// See the trait-level documentation for information about offset requirements.
148    ///
149    /// # Errors
150    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
151    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
152    fn try_load64(&self, offset: usize) -> Result<u64, MmioError>;
153
154    /// Loads eight bytes from this MMIO region at the given offset.
155    ///
156    /// See the trait-level documentation for information about offset requirements.
157    ///
158    /// # Panics
159    /// - If the load would exceed the bounds of this MMIO region.
160    /// - If the offset is not suitably aligned.
161    ///
162    /// See [Mmio::try_load64] for a non-panicking version.
163    fn load64(&self, offset: usize) -> u64 {
164        self.try_load64(offset).unwrap()
165    }
166
167    /// Stores one byte to this MMIO region at the given offset.
168    ///
169    /// See the trait-level documentation for information about offset requirements.
170    ///
171    /// # Errors
172    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
173    fn try_store8(&mut self, offset: usize, value: u8) -> Result<(), MmioError>;
174
175    /// Stores one byte to this MMIO region at the given offset.
176    ///
177    /// See the trait-level documentation for information about offset requirements.
178    ///
179    /// # Panics
180    /// - If the store would exceed the bounds of this MMIO region.
181    ///
182    /// See [Mmio::try_store8] for a non-panicking version.
183    fn store8(&mut self, offset: usize, value: u8) {
184        self.try_store8(offset, value).unwrap();
185    }
186
187    /// Stores two bytes to this MMIO region at the given offset.
188    ///
189    /// See the trait-level documentation for information about offset requirements.
190    ///
191    /// # Errors
192    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
193    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
194    fn try_store16(&mut self, offset: usize, value: u16) -> Result<(), MmioError>;
195
196    /// Stores two bytes to this MMIO region at the given offset.
197    ///
198    /// See the trait-level documentation for information about offset requirements.
199    ///
200    /// # Panics
201    /// - If the store would exceed the bounds of this MMIO region.
202    /// - If the offset is not suitably aligned.
203    ///
204    /// See [Mmio::try_store16] for a non-panicking version.
205    fn store16(&mut self, offset: usize, value: u16) {
206        self.try_store16(offset, value).unwrap();
207    }
208
209    /// Stores four bytes to this MMIO region at the given offset.
210    ///
211    /// See the trait-level documentation for information about offset requirements.
212    ///
213    /// # Errors
214    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
215    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
216    fn try_store32(&mut self, offset: usize, value: u32) -> Result<(), MmioError>;
217
218    /// Stores four bytes to this MMIO region at the given offset.
219    ///
220    /// See the trait-level documentation for information about offset requirements.
221    ///
222    /// # Panics
223    /// - If the store would exceed the bounds of this MMIO region.
224    /// - If the offset is not suitably aligned.
225    ///
226    /// See [Mmio::try_store32] for a non-panicking version.
227    fn store32(&mut self, offset: usize, value: u32) {
228        self.try_store32(offset, value).unwrap();
229    }
230
231    /// Stores eight bytes to this MMIO region at the given offset.
232    ///
233    /// See the trait-level documentation for information about offset requirements.
234    ///
235    /// # Errors
236    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
237    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
238    fn try_store64(&mut self, offset: usize, value: u64) -> Result<(), MmioError>;
239
240    /// Stores eight bytes to this MMIO region at the given offset.
241    ///
242    /// See the trait-level documentation for information about offset requirements.
243    ///
244    /// # Panics
245    /// - If the store would exceed the bounds of this MMIO region.
246    /// - If the offset is not suitably aligned.
247    ///
248    /// See [Mmio::try_store64] for a non-panicking version.
249    fn store64(&mut self, offset: usize, value: u64) {
250        self.try_store64(offset, value).unwrap();
251    }
252
253    /// Issues a memory write barrier.  It is guaranteed that all stores preceding this barrier will
254    /// appear to have happened before all stores following this barrier.
255    fn write_barrier(&self);
256}
257
258/// An MMIO region from which ownership of disjoint sub-regions can be split off.
259pub trait MmioSplit: Mmio + Sized {
260    /// Splits this Mmio region into two at the given mid-point and returns the left-hand-side.
261    /// This MMIO region's bounds are updated to contain only the right-hand-side.
262    ///
263    /// # Errors
264    /// - [MmioError::OutOfRange]: if `mid > self.len()`.
265    fn try_split_off(&mut self, mid: usize) -> Result<Self, MmioError>;
266
267    /// Splits this MMIO region into two at the given mid-point and returns the left-hand-side.
268    /// This MMIO region's bounds are updated to contain only the right-hand-side.
269    ///
270    /// # Panics
271    /// - If `mid > self.len()`.
272    fn split_off(&mut self, mid: usize) -> Self {
273        self.try_split_off(mid).unwrap()
274    }
275}
276
277/// Implemented for the fundamental types supported by all [Mmio] implementations.
278///
279/// This is a sealed trait, implemented for the types: u8, u16, u32, u64.
280pub trait MmioOperand:
281    BitAnd<Output = Self>
282    + BitOr<Output = Self>
283    + Not<Output = Self>
284    + sealed::MmioOperand
285    + PartialEq
286    + Copy
287    + Debug
288    + Display
289    + Sized
290{
291    /// Loads a value of the this type from the MMIO region at the given offset.
292    ///
293    /// # Errors
294    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
295    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
296    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError>;
297
298    /// Stores a value of this type to the MMIO region at the given offset.
299    ///
300    /// # Errors
301    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
302    /// - [MmioError::Unaligned: if the offset is not suitably aligned.
303    fn try_store<M: Mmio + ?Sized>(
304        mmio: &mut M,
305        offset: usize,
306        value: Self,
307    ) -> Result<(), MmioError>;
308}
309
310mod sealed {
311    /// Internal sealed MmioOperand trait as the types implementing this must match the types in
312    /// the loadn/storen trait methods.
313    pub trait MmioOperand {}
314
315    impl MmioOperand for u8 {}
316    impl MmioOperand for u16 {}
317    impl MmioOperand for u32 {}
318    impl MmioOperand for u64 {}
319}
320
321impl MmioOperand for u8 {
322    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
323        mmio.try_load8(offset)
324    }
325
326    fn try_store<M: Mmio + ?Sized>(
327        mmio: &mut M,
328        offset: usize,
329        value: Self,
330    ) -> Result<(), MmioError> {
331        mmio.try_store8(offset, value)
332    }
333}
334
335impl MmioOperand for u16 {
336    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
337        mmio.try_load16(offset)
338    }
339
340    fn try_store<M: Mmio + ?Sized>(
341        mmio: &mut M,
342        offset: usize,
343        value: Self,
344    ) -> Result<(), MmioError> {
345        mmio.try_store16(offset, value)
346    }
347}
348
349impl MmioOperand for u32 {
350    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
351        mmio.try_load32(offset)
352    }
353
354    fn try_store<M: Mmio + ?Sized>(
355        mmio: &mut M,
356        offset: usize,
357        value: Self,
358    ) -> Result<(), MmioError> {
359        mmio.try_store32(offset, value)
360    }
361}
362
363impl MmioOperand for u64 {
364    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
365        mmio.try_load64(offset)
366    }
367
368    fn try_store<M: Mmio + ?Sized>(
369        mmio: &mut M,
370        offset: usize,
371        value: Self,
372    ) -> Result<(), MmioError> {
373        mmio.try_store64(offset, value)
374    }
375}
376
377/// This trait extends [Mmio] with some useful utilities. There is a blanket implementation for all
378/// types implementing [Mmio].
379///
380/// Functions may go into this trait instead of [Mmio] if any of the following is true:
381/// - their behavior shouldn't differ across Mmio implementations
382/// - they would make Mmio not dyn compatible
383/// - they would introduce an unnecessary burden on [Mmio] implementers
384pub trait MmioExt: Mmio {
385    /// Check that the given offset into this MMIO region would be suitable aligned for type `T`.
386    /// There is no guarantee that this offset is within the bounds of the MMIO region or that
387    /// there would be sufficient capacity to hold a `T` at that offset. See
388    /// [MmioExt::check_suitable_for].
389    ///
390    /// Returns [MmioError::Unaligned] if the offset os not suitably aligned.
391    fn check_aligned_for<T>(&self, offset: usize) -> Result<(), MmioError> {
392        let align = align_of::<T>();
393        let align_offset = self.align_offset(align);
394        // An offset is aligned if (offset = align_offset + i * align) for some i.
395        if offset.wrapping_sub(align_offset).is_multiple_of(align) {
396            Ok(())
397        } else {
398            Err(MmioError::Unaligned)
399        }
400    }
401
402    /// Checks that the given offset into this MMIO region has sufficient capacity to hold a value
403    /// of type `T`. There is no guarantee that the offset is suitably aligned. See
404    /// [MmioExt::check_capacity_for].
405    fn check_capacity_for<T>(&self, offset: usize) -> Result<(), MmioError> {
406        let capacity_at_offset = self.len().checked_sub(offset).ok_or(MmioError::OutOfRange)?;
407        if capacity_at_offset >= size_of::<T>() { Ok(()) } else { Err(MmioError::OutOfRange) }
408    }
409
410    /// Checks that the given offset into this MMIO rgion is suitably aligned and has sufficient
411    /// capacity for a value of type `T`.
412    fn check_suitable_for<T>(&self, offset: usize) -> Result<(), MmioError> {
413        self.check_aligned_for::<T>(offset)?;
414        self.check_capacity_for::<T>(offset)?;
415        Ok(())
416    }
417
418    /// Loads an [MmioOperand] from the MMIO region at the given offset.
419    ///
420    /// # Errors
421    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
422    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
423    fn try_load<T: MmioOperand>(&self, offset: usize) -> Result<T, MmioError> {
424        T::try_load(self, offset)
425    }
426
427    /// Loads an [MmioOperand] from the MMIO region at the given offset.
428    ///
429    /// # Panics
430    /// If `self.check_suitable_for::<T>(offset)` would fail.
431    ///
432    /// See [MmioExt::try_load] for a non-panicking version.
433    fn load<T: MmioOperand>(&self, offset: usize) -> T {
434        self.try_load(offset).unwrap()
435    }
436
437    /// Stores an [MmioOperand] to the MMIO region at the given offset.
438    ///
439    /// # Errors
440    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
441    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
442    fn try_store<T: MmioOperand>(&mut self, offset: usize, value: T) -> Result<(), MmioError> {
443        T::try_store(self, offset, value)
444    }
445
446    /// Stores an [MmioOperand] to the MMIO region at the given offset.
447    ///
448    /// # Panics
449    /// If `self.check_suitable_for::<T>(offset)` would fail.
450    ///
451    /// See [MmioExt::try_store] for a non-panicking version.
452    fn store<T: MmioOperand>(&mut self, offset: usize, value: T) {
453        self.try_store(offset, value).unwrap()
454    }
455
456    /// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
457    /// set in the given mask.
458    ///
459    /// # Errors
460    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
461    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
462    fn try_masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> Result<T, MmioError> {
463        self.try_load::<T>(offset).map(|v| v & mask)
464    }
465
466    /// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
467    /// set in the given mask.
468    ///
469    /// # Panics
470    /// If `self.check_suitable_for::<T>(offset)` would fail.
471    ///
472    /// See [MmioExt::try_masked_load] for a non-panicking version.
473    fn masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> T {
474        self.try_masked_load::<T>(offset, mask).unwrap()
475    }
476
477    /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
478    /// the given mask.
479    ///
480    /// This operation performs a read-modify-write in order to only modify the bits matching the
481    /// mask. As this is performed as a load from device memory followed by a store to device
482    /// memory, the device may change state in between these operations.
483    ///
484    /// Callers must ensure that the this sequence of operations is valid for the device they're
485    /// accessing and their use case.
486    ///
487    /// # Errors
488    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
489    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
490    fn try_masked_modify<T: MmioOperand>(
491        &mut self,
492        offset: usize,
493        mask: T,
494        value: T,
495    ) -> Result<(), MmioError> {
496        let current = self.try_load::<T>(offset)?;
497        let unchanged_bits = current & !mask;
498        let changed_bits = value & mask;
499        self.try_store(offset, unchanged_bits | changed_bits)
500    }
501
502    /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
503    /// the given mask.
504    ///
505    /// This operation performs a read-modify-write in order to only modify the bits matching the
506    /// mask. As this is performed as a load from device memory followed by a store to device
507    /// memory, the device may change state in between these operations.
508    ///
509    /// Callers must ensure that the this sequence of operations is valid for the device they're
510    /// accessing and their use case.
511    ///
512    /// # Panics
513    ///
514    /// If `self.check_suitable_for::<T>(offset)` would fail.
515    ///
516    /// See [MmioExt::try_masked_modify] for a non-panicking version.
517    fn masked_modify<T: MmioOperand>(&mut self, offset: usize, mask: T, value: T) {
518        self.try_masked_modify(offset, mask, value).unwrap()
519    }
520
521    /// Reads a [`ReadableRegister`] from the MMIO region.
522    fn read_reg<R: ReadableRegister>(&self) -> R {
523        R::read(self)
524    }
525
526    /// Writes a [`WritableRegister`] to the MMIO region.
527    fn write_reg<R: WritableRegister>(&mut self, reg: R) {
528        reg.write(self)
529    }
530
531    /// Reads a [`Register`] from the MMIO region, updates it with the given function, and writes it
532    /// back.
533    fn update_reg<R: ReadableRegister + WritableRegister, F>(&mut self, f: F)
534    where
535        F: FnOnce(&mut R),
536    {
537        let mut reg = self.read_reg::<R>();
538        f(&mut reg);
539        self.write_reg(reg);
540    }
541
542    /// Reads a [`ReadableIndexedRegister`] from the MMIO region at the given index.
543    fn read_index_reg<R: ReadableIndexedRegister>(&self, index: usize) -> R {
544        R::read_index(self, index)
545    }
546
547    /// Writes a [`WritableIndexedRegister`] to the MMIO region at the given index.
548    fn write_index_reg<R: WritableIndexedRegister>(&mut self, index: usize, reg: R) {
549        reg.write_index(self, index)
550    }
551
552    /// Reads an [`IndexedRegister`] from the MMIO region at the given index, updates it with the
553    /// given function, and writes it back.
554    fn update_index_reg<R: ReadableIndexedRegister + WritableIndexedRegister, F>(
555        &mut self,
556        index: usize,
557        f: F,
558    ) where
559        F: FnOnce(&mut R),
560    {
561        let mut reg = self.read_index_reg::<R>(index);
562        f(&mut reg);
563        self.write_index_reg(index, reg);
564    }
565
566    /// Returns a [`RegisterProxy`] to ergonomically read a single `Register`.
567    fn reg<R: Register>(&self) -> RegisterProxy<'_, Self, R> {
568        RegisterProxy::new(self)
569    }
570
571    /// Returns a [`RegisterProxyMut`] to ergonomically mutate a single `Register`.
572    fn reg_mut<R: Register>(&mut self) -> RegisterProxyMut<'_, Self, R> {
573        RegisterProxyMut::new(self)
574    }
575
576    /// Returns an [`IndexedRegisterProxy`] to ergonomically read a single `IndexedRegister`.
577    fn indexed_reg<R: IndexedRegister>(&self) -> IndexedRegisterProxy<'_, Self, R> {
578        IndexedRegisterProxy::new(self)
579    }
580
581    /// Returns an [`IndexedRegisterProxyMut`] to ergonomically mutate a single `IndexedRegister`.
582    fn indexed_reg_mut<R: IndexedRegister>(&mut self) -> IndexedRegisterProxyMut<'_, Self, R> {
583        IndexedRegisterProxyMut::new(self)
584    }
585}
586
587impl<M: Mmio + ?Sized> MmioExt for M {}