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    /// Computes the first offset within this region that is aligned to `align`.
64    ///
65    /// See the trait-level documentation for more information on offsets and alignment.
66    ///
67    /// # Panics
68    /// If `align` is not a power of 2.
69    fn align_offset(&self, align: usize) -> usize;
70
71    /// Loads one byte from this MMIO region at the given offset.
72    ///
73    /// See the trait-level documentation for information about offset requirements.
74    ///
75    /// # Errors
76    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
77    fn try_load8(&self, offset: usize) -> Result<u8, MmioError>;
78
79    /// Loads one byte from this MMIO region at the given offset.
80    ///
81    /// See the trait-level documentation for information about offset requirements.
82    ///
83    /// # Panics
84    /// - If the load would exceed the bounds of this MMIO region.
85    ///
86    /// See [Mmio::try_load8] for a non-panicking version.
87    fn load8(&self, offset: usize) -> u8 {
88        self.try_load8(offset).unwrap()
89    }
90
91    /// Loads two bytes from this MMIO region at the given offset.
92    ///
93    /// See the trait-level documentation for information about offset requirements.
94    ///
95    /// # Errors
96    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
97    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
98    fn try_load16(&self, offset: usize) -> Result<u16, MmioError>;
99
100    /// Loads two bytes from this MMIO region at the given offset.
101    ///
102    /// See the trait-level documentation for information about offset requirements.
103    ///
104    /// # Panics
105    /// - If the load would exceed the bounds of this MMIO region.
106    /// - If the offset is not suitably aligned.
107    ///
108    /// See [Mmio::try_load16] for a non-panicking version.
109    fn load16(&self, offset: usize) -> u16 {
110        self.try_load16(offset).unwrap()
111    }
112
113    /// Loads four bytes from this MMIO region at the given offset.
114    ///
115    /// See the trait-level documentation for information about offset requirements.
116    ///
117    /// # Errors
118    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
119    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
120    fn try_load32(&self, offset: usize) -> Result<u32, MmioError>;
121
122    /// Loads four bytes from this MMIO region at the given offset.
123    ///
124    /// See the trait-level documentation for information about offset requirements.
125    ///
126    /// # Panics
127    /// - If the load would exceed the bounds of this MMIO region.
128    /// - If the offset is not suitably aligned.
129    ///
130    /// See [Mmio::try_load32] for a non-panicking version.
131    fn load32(&self, offset: usize) -> u32 {
132        self.try_load32(offset).unwrap()
133    }
134
135    /// Loads eight bytes from this MMIO region at the given offset.
136    ///
137    /// See the trait-level documentation for information about offset requirements.
138    ///
139    /// # Errors
140    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
141    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
142    fn try_load64(&self, offset: usize) -> Result<u64, MmioError>;
143
144    /// Loads eight bytes from this MMIO region at the given offset.
145    ///
146    /// See the trait-level documentation for information about offset requirements.
147    ///
148    /// # Panics
149    /// - If the load would exceed the bounds of this MMIO region.
150    /// - If the offset is not suitably aligned.
151    ///
152    /// See [Mmio::try_load64] for a non-panicking version.
153    fn load64(&self, offset: usize) -> u64 {
154        self.try_load64(offset).unwrap()
155    }
156
157    /// Stores one byte to this MMIO region at the given offset.
158    ///
159    /// See the trait-level documentation for information about offset requirements.
160    ///
161    /// # Errors
162    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
163    fn try_store8(&mut self, offset: usize, value: u8) -> Result<(), MmioError>;
164
165    /// Stores one byte to this MMIO region at the given offset.
166    ///
167    /// See the trait-level documentation for information about offset requirements.
168    ///
169    /// # Panics
170    /// - If the store would exceed the bounds of this MMIO region.
171    ///
172    /// See [Mmio::try_store8] for a non-panicking version.
173    fn store8(&mut self, offset: usize, value: u8) {
174        self.try_store8(offset, value).unwrap();
175    }
176
177    /// Stores two bytes to this MMIO region at the given offset.
178    ///
179    /// See the trait-level documentation for information about offset requirements.
180    ///
181    /// # Errors
182    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
183    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
184    fn try_store16(&mut self, offset: usize, value: u16) -> Result<(), MmioError>;
185
186    /// Stores two bytes to this MMIO region at the given offset.
187    ///
188    /// See the trait-level documentation for information about offset requirements.
189    ///
190    /// # Panics
191    /// - If the store would exceed the bounds of this MMIO region.
192    /// - If the offset is not suitably aligned.
193    ///
194    /// See [Mmio::try_store16] for a non-panicking version.
195    fn store16(&mut self, offset: usize, value: u16) {
196        self.try_store16(offset, value).unwrap();
197    }
198
199    /// Stores four bytes to this MMIO region at the given offset.
200    ///
201    /// See the trait-level documentation for information about offset requirements.
202    ///
203    /// # Errors
204    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
205    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
206    fn try_store32(&mut self, offset: usize, value: u32) -> Result<(), MmioError>;
207
208    /// Stores four bytes to this MMIO region at the given offset.
209    ///
210    /// See the trait-level documentation for information about offset requirements.
211    ///
212    /// # Panics
213    /// - If the store would exceed the bounds of this MMIO region.
214    /// - If the offset is not suitably aligned.
215    ///
216    /// See [Mmio::try_store32] for a non-panicking version.
217    fn store32(&mut self, offset: usize, value: u32) {
218        self.try_store32(offset, value).unwrap();
219    }
220
221    /// Stores eight bytes to this MMIO region at the given offset.
222    ///
223    /// See the trait-level documentation for information about offset requirements.
224    ///
225    /// # Errors
226    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
227    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
228    fn try_store64(&mut self, offset: usize, value: u64) -> Result<(), MmioError>;
229
230    /// Stores eight bytes to this MMIO region at the given offset.
231    ///
232    /// See the trait-level documentation for information about offset requirements.
233    ///
234    /// # Panics
235    /// - If the store would exceed the bounds of this MMIO region.
236    /// - If the offset is not suitably aligned.
237    ///
238    /// See [Mmio::try_store64] for a non-panicking version.
239    fn store64(&mut self, offset: usize, value: u64) {
240        self.try_store64(offset, value).unwrap();
241    }
242}
243
244/// An MMIO region from which ownership of disjoint sub-regions can be split off.
245pub trait MmioSplit: Mmio + Sized {
246    /// Splits this Mmio region into two at the given mid-point and returns the left-hand-side.
247    /// This MMIO region's bounds are updated to contain only the right-hand-side.
248    ///
249    /// # Errors
250    /// - [MmioError::OutOfRange]: if `mid > self.len()`.
251    fn try_split_off(&mut self, mid: usize) -> Result<Self, MmioError>;
252
253    /// Splits this MMIO region into two at the given mid-point and returns the left-hand-side.
254    /// This MMIO region's bounds are updated to contain only the right-hand-side.
255    ///
256    /// # Panics
257    /// - If `mid > self.len()`.
258    fn split_off(&mut self, mid: usize) -> Self {
259        self.try_split_off(mid).unwrap()
260    }
261}
262
263/// Implemented for the fundamental types supported by all [Mmio] implementations.
264///
265/// This is a sealed trait, implemented for the types: u8, u16, u32, u64.
266pub trait MmioOperand:
267    BitAnd<Output = Self>
268    + BitOr<Output = Self>
269    + Not<Output = Self>
270    + sealed::MmioOperand
271    + PartialEq
272    + Copy
273    + Debug
274    + Display
275    + Sized
276{
277    /// Loads a value of the this type from the MMIO region at the given offset.
278    ///
279    /// # Errors
280    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
281    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
282    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError>;
283
284    /// Stores a value of this type to the MMIO region at the given offset.
285    ///
286    /// # Errors
287    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
288    /// - [MmioError::Unaligned: if the offset is not suitably aligned.
289    fn try_store<M: Mmio + ?Sized>(
290        mmio: &mut M,
291        offset: usize,
292        value: Self,
293    ) -> Result<(), MmioError>;
294}
295
296mod sealed {
297    /// Internal sealed MmioOperand trait as the types implementing this must match the types in
298    /// the loadn/storen trait methods.
299    pub trait MmioOperand {}
300
301    impl MmioOperand for u8 {}
302    impl MmioOperand for u16 {}
303    impl MmioOperand for u32 {}
304    impl MmioOperand for u64 {}
305}
306
307impl MmioOperand for u8 {
308    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
309        mmio.try_load8(offset)
310    }
311
312    fn try_store<M: Mmio + ?Sized>(
313        mmio: &mut M,
314        offset: usize,
315        value: Self,
316    ) -> Result<(), MmioError> {
317        mmio.try_store8(offset, value)
318    }
319}
320
321impl MmioOperand for u16 {
322    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
323        mmio.try_load16(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_store16(offset, value)
332    }
333}
334
335impl MmioOperand for u32 {
336    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
337        mmio.try_load32(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_store32(offset, value)
346    }
347}
348
349impl MmioOperand for u64 {
350    fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
351        mmio.try_load64(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_store64(offset, value)
360    }
361}
362
363/// This trait extends [Mmio] with some useful utilities. There is a blanket implementation for all
364/// types implementing [Mmio].
365///
366/// Functions may go into this trait instead of [Mmio] if any of the following is true:
367/// - their behavior shouldn't differ across Mmio implementations
368/// - they would make Mmio not dyn compatible
369/// - they would introduce an unnecessary burden on [Mmio] implementers
370pub trait MmioExt: Mmio {
371    /// Check that the given offset into this MMIO region would be suitable aligned for type `T`.
372    /// There is no guarantee that this offset is within the bounds of the MMIO region or that
373    /// there would be sufficient capacity to hold a `T` at that offset. See
374    /// [MmioExt::check_suitable_for].
375    ///
376    /// Returns [MmioError::Unaligned] if the offset os not suitably aligned.
377    fn check_aligned_for<T>(&self, offset: usize) -> Result<(), MmioError> {
378        let align = align_of::<T>();
379        let align_offset = self.align_offset(align);
380        // An offset is aligned if (offset = align_offset + i * align) for some i.
381        if offset.wrapping_sub(align_offset) % align == 0 {
382            Ok(())
383        } else {
384            Err(MmioError::Unaligned)
385        }
386    }
387
388    /// Checks that the given offset into this MMIO region has sufficient capacity to hold a value
389    /// of type `T`. There is no guarantee that the offset is suitably aligned. See
390    /// [MmioExt::check_capacity_for].
391    fn check_capacity_for<T>(&self, offset: usize) -> Result<(), MmioError> {
392        let capacity_at_offset = self.len().checked_sub(offset).ok_or(MmioError::OutOfRange)?;
393        if capacity_at_offset >= size_of::<T>() {
394            Ok(())
395        } else {
396            Err(MmioError::OutOfRange)
397        }
398    }
399
400    /// Checks that the given offset into this MMIO rgion is suitably aligned and has sufficient
401    /// capacity for a value of type `T`.
402    fn check_suitable_for<T>(&self, offset: usize) -> Result<(), MmioError> {
403        self.check_aligned_for::<T>(offset)?;
404        self.check_capacity_for::<T>(offset)?;
405        Ok(())
406    }
407
408    /// Loads an [MmioOperand] from the MMIO region at the given offset.
409    ///
410    /// # Errors
411    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
412    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
413    fn try_load<T: MmioOperand>(&self, offset: usize) -> Result<T, MmioError> {
414        T::try_load(self, offset)
415    }
416
417    /// Loads an [MmioOperand] from the MMIO region at the given offset.
418    ///
419    /// # Panics
420    /// If `self.check_suitable_for::<T>(offset)` would fail.
421    ///
422    /// See [MmioExt::try_load] for a non-panicking version.
423    fn load<T: MmioOperand>(&self, offset: usize) -> T {
424        self.try_load(offset).unwrap()
425    }
426
427    /// Stores an [MmioOperand] to the MMIO region at the given offset.
428    ///
429    /// # Errors
430    /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
431    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
432    fn try_store<T: MmioOperand>(&mut self, offset: usize, value: T) -> Result<(), MmioError> {
433        T::try_store(self, offset, value)
434    }
435
436    /// Stores an [MmioOperand] to the MMIO region at the given offset.
437    ///
438    /// # Panics
439    /// If `self.check_suitable_for::<T>(offset)` would fail.
440    ///
441    /// See [MmioExt::try_store] for a non-panicking version.
442    fn store<T: MmioOperand>(&mut self, offset: usize, value: T) {
443        self.try_store(offset, value).unwrap()
444    }
445
446    /// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
447    /// set in the given mask.
448    ///
449    /// # Errors
450    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
451    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
452    fn try_masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> Result<T, MmioError> {
453        self.try_load::<T>(offset).map(|v| v & mask)
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    /// # Panics
460    /// If `self.check_suitable_for::<T>(offset)` would fail.
461    ///
462    /// See [MmioExt::try_masked_load] for a non-panicking version.
463    fn masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> T {
464        self.try_masked_load::<T>(offset, mask).unwrap()
465    }
466
467    /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
468    /// the given mask.
469    ///
470    /// This operation performs a read-modify-write in order to only modify the bits matching the
471    /// mask. As this is performed as a load from device memory followed by a store to device
472    /// memory, the device may change state in between these operations.
473    ///
474    /// Callers must ensure that the this sequence of operations is valid for the device they're
475    /// accessing and their use case.
476    ///
477    /// # Errors
478    /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
479    /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
480    fn try_masked_modify<T: MmioOperand>(
481        &mut self,
482        offset: usize,
483        mask: T,
484        value: T,
485    ) -> Result<(), MmioError> {
486        let current = self.try_load::<T>(offset)?;
487        let unchanged_bits = current & !mask;
488        let changed_bits = value & mask;
489        self.try_store(offset, unchanged_bits | changed_bits)
490    }
491
492    /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
493    /// the given mask.
494    ///
495    /// This operation performs a read-modify-write in order to only modify the bits matching the
496    /// mask. As this is performed as a load from device memory followed by a store to device
497    /// memory, the device may change state in between these operations.
498    ///
499    /// Callers must ensure that the this sequence of operations is valid for the device they're
500    /// accessing and their use case.
501    ///
502    /// # Panics
503    ///
504    /// If `self.check_suitable_for::<T>(offset)` would fail.
505    ///
506    /// See [MmioExt::try_masked_modify] for a non-panicking version.
507    fn masked_modify<T: MmioOperand>(&mut self, offset: usize, mask: T, value: T) {
508        self.try_masked_modify(offset, mask, value).unwrap()
509    }
510}
511
512impl<M: Mmio> MmioExt for M {}