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