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