Skip to main content

mmio/
region.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.
4
5//! Support for implementing splittable MMIO regions.
6//!
7//! This module defines the [MmioRegion] type which provides safe [Mmio] and [MmioSplit]
8//! implementations on top of the more relaxed [UnsafeMmio] trait.
9//!
10//! The [UnsafeMmio] trait allows mutations through a shared reference, provided the caller
11//! ensures that store operations are not performed concurrently with any other operation that may
12//! overlap it.
13//!
14//! Implementing [UnsafeMmio] correctly is likely to be simpler than implementing [Mmio] and
15//! [MmioSplit] for many use cases.
16
17use crate::{Mmio, MmioError, MmioExt, MmioSplit};
18use core::borrow::Borrow;
19use core::marker::PhantomData;
20use core::ops::Range;
21use std::rc::Rc;
22use std::sync::Arc;
23
24/// An MMIO region that can be stored to through a shared reference.
25///
26/// This trait requires the caller to uphold some safety constraints, but enables a generic
27/// implementation of [MmioSplit]. See the [MmioRegion] which provides a safe wrapper on top of
28/// this trait.
29///
30/// This is primarily intended to simplify implementing the [MmioSplit] trait, not for users of the
31/// library. However, it is possible to use [UnsafeMmio] directly, provided the safety requirements
32/// are met.
33///
34/// # Safety
35/// - Callers must ensure that stores are never performed concurrently with any other operation on
36///   an overlapping range.
37/// - Concurrent loads are allowed on overlapping ranges.
38/// - Callers must ensure that offsets are suitably aligned for the type being loaded or stored.
39pub trait UnsafeMmio {
40    /// Returns the size, in bytes, of the underlying MMIO region that can be accessed through this
41    /// object.
42    fn len(&self) -> usize;
43
44    /// Returns true if the MMIO region has a length of 0.
45    fn is_empty(&self) -> bool {
46        self.len() == 0
47    }
48
49    /// Returns the first offset into this MMIO region that is suitably aligned for`align`.
50    ///
51    /// An offset is suitably aligned if `offset = align_offset(align) + i * align` for some `i`.
52    fn align_offset(&self, align: usize) -> usize;
53
54    /// Loads a u8 from this MMIO region at the given offset.
55    ///
56    /// # Safety
57    /// See the trait-level documentation.
58    unsafe fn load8_unchecked(&self, offset: usize) -> u8;
59
60    /// Loads a u16 from this MMIO region at the given offset.
61    ///
62    /// # Safety
63    /// See the trait-level documentation.
64    unsafe fn load16_unchecked(&self, offset: usize) -> u16;
65
66    /// Loads a u32 from this MMIO region at the given offset.
67    ///
68    /// # Safety
69    /// See the trait-level documentation.
70    unsafe fn load32_unchecked(&self, offset: usize) -> u32;
71
72    /// Loads a u64 from this MMIO region at the given offset.
73    ///
74    /// # Safety
75    /// See the trait-level documentation.
76    unsafe fn load64_unchecked(&self, offset: usize) -> u64;
77
78    /// Stores a u8 to this MMIO region at the given offset.
79    ///
80    /// # Safety
81    /// See the trait-level documentation.
82    unsafe fn store8_unchecked(&self, offset: usize, v: u8);
83
84    /// Stores a u16 to this MMIO region at the given offset.
85    ///
86    /// # Safety
87    /// See the trait-level documentation.
88    unsafe fn store16_unchecked(&self, offset: usize, v: u16);
89
90    /// Stores a u32 to this MMIO region at the given offset.
91    ///
92    /// # Safety
93    /// See the trait-level documentation.
94    unsafe fn store32_unchecked(&self, offset: usize, v: u32);
95
96    /// Stores a u64 to this MMIO region at the given offset.
97    ///
98    /// # Safety
99    /// See the trait-level documentation.
100    unsafe fn store64_unchecked(&self, offset: usize, v: u64);
101
102    /// Issues a memory write barrier.  It is guaranteed that all stores preceding this barrier will
103    /// appear to have happened before all stores following this barrier.
104    fn write_barrier(&self);
105
106    /// Issues a memory read barrier.  It is guaranteed that all loads preceding this barrier will
107    /// appear to have happened before all loads following this barrier.
108    ///
109    /// The default implementation issues the architecture's read barrier.
110    fn read_barrier(&self) {
111        crate::arch::read_barrier();
112    }
113}
114
115/// An `MmioRegion` provides a safe implementation of [Mmio] and [MmioSplit] on top of an
116/// [UnsafeMmio] implementation.
117///
118/// The safety constraints of [UnsafeMmio] require callers to ensure that stores are not performed
119/// concurrently with loads for any overlapping range.
120///
121/// This type meets these requirements while supporting being split into independently owned due to
122/// the following:
123///
124/// 1. An MmioRegion has exclusive ownership of a sub-region from the wrapped [UnsafeMmio]
125///    implementation (required by [MmioRegion::new]).
126/// 2. An MmioRegion only performs operations that are fully contained within the region it owns.
127/// 3. All stores are performed through a mutable reference (ensuring stores are exclusive with all
128///    other operations to the owned region).
129/// 4. When splitting off `MmioRegions`, the split_off region owns a range that was owned by region
130///    it was split off from prior to the split, and that it has exclusive ownership of after the
131///    split.
132///
133/// # Type Parameters
134/// An MmioRegion is parameterized by two types:
135/// - `Impl`: the [UnsafeMmio] implementation wrapped by this region.
136/// - `Owner`: an object with shared ownership of the `Impl` instance.
137///
138/// An MmioRegion is splittable if `Owner` can be cloned.
139pub struct MmioRegion<Impl, Owner = Impl> {
140    owner: Owner,
141    bounds: Range<usize>,
142    phantom: PhantomData<Impl>,
143}
144
145impl<U: UnsafeMmio> MmioRegion<U> {
146    /// Create a new `MmioRegion` that has exclusive ownership of the entire range.
147    ///
148    /// The returned object is guaranteed to be the only one capable of referencing any value in
149    /// the range. It can be converted into one that can be split
150    pub fn new(inner: U) -> Self {
151        let bounds = 0..inner.len();
152        let owner = inner;
153        Self { owner, bounds, phantom: PhantomData }
154    }
155
156    /// Converts this region into one which can be split.
157    pub fn into_split(self) -> MmioRegion<U, Rc<U>> {
158        let owner = Rc::new(self.owner);
159        let bounds = self.bounds;
160        // Safety:
161        // - this region exclusively owns its bounds.
162        // - ownership of the UnsafeMmio is transferred into the Rc.
163        // - the returned region has the same bounds as self did at the start of the call.
164        unsafe { MmioRegion::<U, _>::new_unchecked(owner, bounds) }
165    }
166
167    /// Maps the inner type to a new type with `f`.
168    pub fn map<O: UnsafeMmio, F: FnOnce(U) -> O>(self, f: F) -> MmioRegion<O> {
169        MmioRegion::new(f(self.owner))
170    }
171}
172
173impl<U: UnsafeMmio + Send + Sync> MmioRegion<U> {
174    /// Converts this region into one which can be split and sent.
175    pub fn into_split_send(self) -> MmioRegion<U, Arc<U>> {
176        let owner = Arc::new(self.owner);
177        let bounds = self.bounds;
178        // Safety:
179        // - this region exclusively owns its bounds.
180        // - ownership of the UnsafeMmio is transferred into the Arc.
181        // - the returned region has the same bounds as self did at the start of the call.
182        unsafe { MmioRegion::<U, _>::new_unchecked(owner, bounds) }
183    }
184}
185
186impl<Impl: UnsafeMmio, Owner: Borrow<Impl>> MmioRegion<Impl, Owner> {
187    /// Create an MmioRegion that constrains all operations to the underlying wrapped UnsafeMmio
188    /// to be within the given bounds.
189    ///
190    /// # Safety
191    /// - For the lifetime of this MmioRegion or any split off from it the given range must only be
192    ///   accessed through this MmioRegion or a region split off from it.
193    pub unsafe fn new_unchecked(owner: Owner, bounds: Range<usize>) -> Self {
194        Self { owner, bounds, phantom: PhantomData }
195    }
196
197    /// Resolves the offset relative to the start of this MmioRegion's bounds, provided that offset
198    /// is suitably aligned for type T and there is sufficient capacity within this MmioRegion's
199    /// bounds at the given offset.
200    pub fn resolve_offset<T>(&self, offset: usize) -> Result<usize, MmioError> {
201        self.check_suitable_for::<T>(offset)?;
202        Ok(self.bounds.start + offset)
203    }
204}
205
206impl<Impl: UnsafeMmio, Owner: Borrow<Impl>> MmioRegion<Impl, Owner> {
207    /// Provides access to the wrapped [`UnsafeMmio`] instance.
208    pub fn unsafe_mmio(&self) -> &Impl {
209        self.owner.borrow()
210    }
211}
212
213impl<Impl: UnsafeMmio, Owner: Borrow<Impl>> Mmio for MmioRegion<Impl, Owner> {
214    fn len(&self) -> usize {
215        self.bounds.len()
216    }
217
218    fn align_offset(&self, align: usize) -> usize {
219        // Determine the first offset into the wrapped region that is correctly aligned.
220        let first_aligned_offset = self.owner.borrow().align_offset(align);
221
222        // An aligned offset is any where offset = first_aligned_offset + i * align.
223        // Or where (offset - first_aligned_offset) % align = 0.
224        //
225        // For offsets relative to the start of this region, they are aligned if:
226        // (rel_offset + region_start - first_aligned_offset) % align = 0.
227        // or rel_offset % align = (first_aligned_offset - region_start) % align
228        //
229        // Therefore, the first aligned offset, relative to the start of this region, is:
230        // (first_aligned_offset - region_start) % align.
231        first_aligned_offset.wrapping_sub(self.bounds.start) % align
232    }
233
234    fn try_load8(&self, offset: usize) -> Result<u8, MmioError> {
235        let offset = self.resolve_offset::<u8>(offset)?;
236        // Safety:
237        // - this region exclusively owns its covered range (required by safety constraints)
238        // - the immutable receiver excludes stores for this entire range
239        Ok(unsafe { self.owner.borrow().load8_unchecked(offset) })
240    }
241
242    fn try_load16(&self, offset: usize) -> Result<u16, MmioError> {
243        let offset = self.resolve_offset::<u16>(offset)?;
244        // Safety:
245        // - this region exclusively owns its covered range (required by safety constraints)
246        // - the immutable receiver excludes stores for this entire range
247        Ok(unsafe { self.owner.borrow().load16_unchecked(offset) })
248    }
249
250    fn try_load32(&self, offset: usize) -> Result<u32, MmioError> {
251        let offset = self.resolve_offset::<u32>(offset)?;
252        // Safety:
253        // - this region exclusively owns its covered range (required by safety constraints)
254        // - the immutable receiver excludes stores for this entire range
255        Ok(unsafe { self.owner.borrow().load32_unchecked(offset) })
256    }
257
258    fn try_load64(&self, offset: usize) -> Result<u64, MmioError> {
259        let offset = self.resolve_offset::<u64>(offset)?;
260        // Safety:
261        // - this region exclusively owns its covered range (required by safety constraints)
262        // - the immutable receiver excludes stores for this entire range
263        Ok(unsafe { self.owner.borrow().load64_unchecked(offset) })
264    }
265
266    fn try_store8(&mut self, offset: usize, v: u8) -> Result<(), MmioError> {
267        let offset = self.resolve_offset::<u8>(offset)?;
268        // Safety:
269        // - this region exclusively owns its covered range (required by safety constraints)
270        // - the mutable receiver excludes all other operations for this entire range
271        unsafe {
272            self.owner.borrow().store8_unchecked(offset, v);
273        }
274        Ok(())
275    }
276
277    fn try_store16(&mut self, offset: usize, v: u16) -> Result<(), MmioError> {
278        let offset = self.resolve_offset::<u16>(offset)?;
279        // Safety:
280        // - this region exclusively owns its covered range (required by safety constraints)
281        // - the mutable receiver excludes all other operations for this entire range
282        unsafe {
283            self.owner.borrow().store16_unchecked(offset, v);
284        }
285        Ok(())
286    }
287
288    fn try_store32(&mut self, offset: usize, v: u32) -> Result<(), MmioError> {
289        let offset = self.resolve_offset::<u32>(offset)?;
290        // Safety:
291        // - this region exclusively owns its covered range (required by safety constraints)
292        // - the mutable receiver excludes all other operations for this entire range
293        unsafe {
294            self.owner.borrow().store32_unchecked(offset, v);
295        }
296        Ok(())
297    }
298
299    fn try_store64(&mut self, offset: usize, v: u64) -> Result<(), MmioError> {
300        let offset = self.resolve_offset::<u64>(offset)?;
301        // Safety:
302        // - this region exclusively owns its covered range (required by safety constraints)
303        // - the mutable receiver excludes all other operations for this entire range
304        unsafe {
305            self.owner.borrow().store64_unchecked(offset, v);
306        }
307        Ok(())
308    }
309
310    fn write_barrier(&self) {
311        self.owner.borrow().write_barrier();
312    }
313
314    fn read_barrier(&self) {
315        self.owner.borrow().read_barrier();
316    }
317}
318
319impl<Impl: UnsafeMmio, Owner: Borrow<Impl> + Clone> MmioSplit for MmioRegion<Impl, Owner> {
320    fn try_split_off(&mut self, mid: usize) -> Result<Self, MmioError> {
321        if mid > self.len() {
322            return Err(MmioError::OutOfRange);
323        }
324
325        // Resolve the midpoint to an absolute offset.
326        let mid = self.bounds.start + mid;
327
328        // Split the bounds into two disjoint ranges.
329        let lhs = self.bounds.start..mid;
330        let rhs = mid..self.bounds.end;
331
332        // Relinquish ownership of the lhs.
333        self.bounds = rhs;
334
335        // Safety:
336        // - this region exclusively owns its covered range (required by safety constraints)
337        // - the mutable receiver excludes all other operations for this entire range
338        // - this mmio region splits off a portion of its owned range and relinquishes ownership of
339        // it before returning
340        // - the returned MmioRegion owns a range that was owned by this MmioRegion at the start of
341        // this call and no longer is
342        Ok(unsafe { Self::new_unchecked(self.owner.clone(), lhs) })
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::MmioOperand;
350    use fuchsia_sync::RwLock;
351    use rand::RngExt as _;
352    use std::sync::Barrier;
353    use std::sync::atomic::{AtomicUsize, Ordering};
354    use std::thread::sleep;
355    use std::time::Duration;
356
357    /// An UnsafeMmio implementation that dynamically checks violations of the safety requirements:
358    /// - a store concurrent with another operation on a memory range
359    /// - an unaligned access
360    ///
361    /// This implementation will panic if an unaligned operation is issued.
362    ///
363    /// This implementation *might* panic on unsafe concurrent usage. If it does panic in this case
364    /// there was unsafe concurrent usage, however the lack of a panic doesn't guarantee all usage
365    /// was safe. The mean_op_duration parameter to new controls how long the average borrow will
366    /// last - increasing this can make it more likely that unsafe usage will be detected.
367    struct CheckedRegisters {
368        cells: Vec<RwLock<u8>>,
369        mean_op_duration: f32,
370        write_barriers: AtomicUsize,
371        read_barriers: AtomicUsize,
372    }
373
374    impl CheckedRegisters {
375        fn new(len: usize, mean_op_duration: Duration) -> Self {
376            let mut cells = Vec::new();
377            cells.resize_with(len, || RwLock::new(0));
378
379            let mean_op_duration = mean_op_duration.as_secs_f32();
380
381            Self {
382                cells,
383                mean_op_duration,
384                write_barriers: AtomicUsize::new(0),
385                read_barriers: AtomicUsize::new(0),
386            }
387        }
388
389        fn sleep(&self) {
390            // model op duration as a poisson process to get some jitter.
391            let uniform_sample: f32 = rand::random::<f32>().max(0.000001);
392            let duration_secs = -self.mean_op_duration * uniform_sample.ln();
393            sleep(Duration::from_secs_f32(duration_secs));
394        }
395
396        fn load<const N: usize>(&self, start: usize) -> [u8; N] {
397            let borrows: [_; N] = core::array::from_fn(|i| {
398                self.cells[start + i]
399                    .try_read()
400                    .expect("attempt to load from an address that is being stored to")
401            });
402
403            // Sleep while borrowing these cells to increase the chance that unsafe usage will be
404            // detected.
405            self.sleep();
406
407            borrows.map(|r| *r)
408        }
409
410        fn store<const N: usize>(&self, start: usize, bytes: [u8; N]) {
411            let borrows: [_; N] = core::array::from_fn(|i| {
412                self.cells[start + i]
413                    .try_write()
414                    .expect("attempt to store to an address concurrently with another operation")
415            });
416
417            // Sleep while borrowing these cells to increase the chance that unsafe usage will be
418            // detected.
419            self.sleep();
420
421            borrows.into_iter().zip(bytes).for_each(|(mut r, b)| *r = b);
422        }
423    }
424
425    impl UnsafeMmio for CheckedRegisters {
426        fn len(&self) -> usize {
427            self.cells.len()
428        }
429
430        fn align_offset(&self, _align: usize) -> usize {
431            0
432        }
433
434        unsafe fn load8_unchecked(&self, offset: usize) -> u8 {
435            self.load::<1>(offset)[0]
436        }
437
438        unsafe fn load16_unchecked(&self, offset: usize) -> u16 {
439            assert_eq!(offset % 2, 0);
440            u16::from_le_bytes(self.load::<2>(offset))
441        }
442
443        unsafe fn load32_unchecked(&self, offset: usize) -> u32 {
444            assert_eq!(offset % 4, 0);
445            u32::from_le_bytes(self.load::<4>(offset))
446        }
447
448        unsafe fn load64_unchecked(&self, offset: usize) -> u64 {
449            assert_eq!(offset % 8, 0);
450            u64::from_le_bytes(self.load::<8>(offset))
451        }
452
453        unsafe fn store8_unchecked(&self, offset: usize, v: u8) {
454            self.store::<1>(offset, [v]);
455        }
456
457        unsafe fn store16_unchecked(&self, offset: usize, v: u16) {
458            assert_eq!(offset % 2, 0);
459            self.store::<2>(offset, v.to_le_bytes())
460        }
461
462        unsafe fn store32_unchecked(&self, offset: usize, v: u32) {
463            assert_eq!(offset % 4, 0);
464            self.store::<4>(offset, v.to_le_bytes())
465        }
466
467        unsafe fn store64_unchecked(&self, offset: usize, v: u64) {
468            assert_eq!(offset % 8, 0);
469            self.store::<8>(offset, v.to_le_bytes())
470        }
471
472        fn write_barrier(&self) {
473            // Only counted: ordinary memory needs no barrier.
474            self.write_barriers.fetch_add(1, Ordering::Relaxed);
475        }
476
477        fn read_barrier(&self) {
478            // Only counted: ordinary memory needs no barrier.
479            self.read_barriers.fetch_add(1, Ordering::Relaxed);
480        }
481    }
482
483    #[test]
484    fn test_region_forwards_barriers() {
485        let mut region = MmioRegion::new(CheckedRegisters::new(16, Duration::ZERO)).into_split();
486        let split = region.split_off(8);
487
488        region.write_barrier();
489        split.read_barrier();
490        split.read_barrier();
491
492        let registers = region.unsafe_mmio();
493        assert_eq!(registers.write_barriers.load(Ordering::Relaxed), 1);
494        assert_eq!(registers.read_barriers.load(Ordering::Relaxed), 2);
495    }
496
497    #[test]
498    fn test_memory_region_thread_safety() {
499        // The number of concurrent threads.
500        const CONCURRENCY: usize = 64;
501
502        // The number of bytes each thread owns. Must be a non-zero multiple of 8.
503        const BYTES_PER_THREAD: usize = 8;
504
505        // The average time for an operation to hold a borrow.
506        const MEAN_OP_TIME: Duration = Duration::from_micros(100);
507
508        // The number of ops to perform per thread. At 100us per op the minimum sleep time per
509        // thread should be around 0.5s.
510        const THREAD_OP_COUNT: usize = 5000;
511
512        // The total size of the Mmio region.
513        const LEN: usize = CONCURRENCY * BYTES_PER_THREAD;
514
515        // These are required for test correctness.
516        assert_ne!(BYTES_PER_THREAD, 0);
517        assert_eq!(BYTES_PER_THREAD % 8, 0);
518
519        let registers = CheckedRegisters::new(LEN, MEAN_OP_TIME);
520        // Safety:
521        // - CheckedRegisters only references memory it owns
522        // - MmioRegion takes ownership of the CheckedRegisters object
523        let mut region = MmioRegion::new(registers).into_split_send();
524
525        let barrier = Barrier::new(CONCURRENCY);
526
527        std::thread::scope(|s| {
528            let barrier = &barrier;
529            for _ in 0..CONCURRENCY {
530                let mut split = region.split_off(BYTES_PER_THREAD);
531                s.spawn(move || {
532                    let mut rng = rand::rng();
533
534                    // Wait until threads are ready to start to increase the chance of a race.
535                    barrier.wait();
536
537                    for _i in 0..THREAD_OP_COUNT {
538                        let offset = rng.random_range(0..BYTES_PER_THREAD);
539                        let op = rng.random_range(0usize..8);
540
541                        let size = 1 << (op % 4);
542                        // Choose a random offset from 0 to 2x the size of this region. MmioRegion
543                        // should prevent reading out of bounds.
544                        let offset = offset.next_multiple_of(size) % (BYTES_PER_THREAD * 2);
545
546                        // We don't care whether these operations fail.
547                        let _ = match op {
548                            0 => split.try_load8(offset).err(),
549                            1 => split.try_load16(offset).err(),
550                            2 => split.try_load32(offset).err(),
551                            3 => split.try_load64(offset).err(),
552                            4 => split.try_store8(offset, rng.random()).err(),
553                            5 => split.try_store16(offset, rng.random()).err(),
554                            6 => split.try_store32(offset, rng.random()).err(),
555                            7 => split.try_store64(offset, rng.random()).err(),
556                            _ => unreachable!(),
557                        };
558                    }
559                });
560            }
561        });
562    }
563
564    #[test]
565    fn test_alignment() {
566        const LEN: usize = 64;
567        let registers = CheckedRegisters::new(LEN, Duration::ZERO);
568        let mut region = MmioRegion::new(registers).into_split();
569        let mut rng = rand::rng();
570
571        fn assert_alignment<M: Mmio, T: MmioOperand>(
572            mmio: &mut M,
573            offset: usize,
574            region_offset: usize,
575            v: T,
576        ) {
577            let absolute_offset = offset + region_offset;
578            let is_aligned = absolute_offset.is_multiple_of(align_of::<T>());
579            let expected_res = if is_aligned { Ok(()) } else { Err(MmioError::Unaligned) };
580            assert_eq!(mmio.check_suitable_for::<T>(offset), expected_res);
581            assert_eq!(mmio.try_store(offset, v), expected_res);
582            assert_eq!(mmio.try_load(offset), expected_res.map(|_| v));
583        }
584
585        for region_offset in 0..8 {
586            // Do at least two cycles of the largest operand alignment to test modular arithmetic.
587            for relative_offset in 0..16 {
588                let v: u64 = rng.random();
589                assert_alignment(&mut region, relative_offset, region_offset, v as u8);
590                assert_alignment(&mut region, relative_offset, region_offset, v as u16);
591                assert_alignment(&mut region, relative_offset, region_offset, v as u32);
592                assert_alignment(&mut region, relative_offset, region_offset, v);
593            }
594            // Throw away the first byte to advance the region's bounds.
595            let _ = region.split_off(1);
596        }
597    }
598
599    #[test]
600    fn test_wrapped_alignment() {
601        // Test all combinations for how the wrapped MMIO regions alignment, region start and region
602        // relative offsets may stack with respect to alignment.
603        struct OffsetMmio(usize);
604        impl UnsafeMmio for OffsetMmio {
605            fn len(&self) -> usize {
606                isize::MAX as usize
607            }
608
609            fn align_offset(&self, align: usize) -> usize {
610                align.wrapping_sub(self.0) % align
611            }
612
613            unsafe fn load8_unchecked(&self, _offset: usize) -> u8 {
614                unreachable!()
615            }
616
617            unsafe fn load16_unchecked(&self, _offset: usize) -> u16 {
618                unreachable!()
619            }
620
621            unsafe fn load32_unchecked(&self, _offset: usize) -> u32 {
622                unreachable!()
623            }
624
625            unsafe fn load64_unchecked(&self, _offset: usize) -> u64 {
626                unreachable!()
627            }
628
629            unsafe fn store8_unchecked(&self, _offset: usize, _value: u8) {
630                unreachable!()
631            }
632
633            unsafe fn store16_unchecked(&self, _offset: usize, _value: u16) {
634                unreachable!()
635            }
636
637            unsafe fn store32_unchecked(&self, _offset: usize, _value: u32) {
638                unreachable!()
639            }
640
641            unsafe fn store64_unchecked(&self, _offset: usize, _value: u64) {
642                unreachable!()
643            }
644
645            fn write_barrier(&self) {
646                unreachable!()
647            }
648        }
649
650        // Loop through all combinations of the wrapped offset, region_start and relative_offset
651        // for 2x the size of the largest operand in order to test all combinations of these in the
652        // face of the modular arithmetic.
653        for wrapped_offset in 0..16 {
654            let offset_mmio = OffsetMmio(wrapped_offset);
655            let mut region = MmioRegion::new(offset_mmio).into_split();
656
657            for region_start in 0..16 {
658                let absolute_region_start = wrapped_offset + region_start;
659                assert_eq!(region.align_offset(1), 0);
660                assert_eq!(region.align_offset(2), 2_usize.wrapping_sub(absolute_region_start) % 2);
661                assert_eq!(region.align_offset(4), 4_usize.wrapping_sub(absolute_region_start) % 4);
662                assert_eq!(region.align_offset(8), 8_usize.wrapping_sub(absolute_region_start) % 8);
663
664                for relative_offset in 0..16 {
665                    let absolute_offset = wrapped_offset + region_start + relative_offset;
666
667                    // Every offset is suitably aligned for u8.
668                    assert_eq!(region.check_aligned_for::<u8>(relative_offset), Ok(()));
669                    assert_eq!(
670                        region.check_aligned_for::<u16>(relative_offset),
671                        if absolute_offset % 2 == 0 { Ok(()) } else { Err(MmioError::Unaligned) }
672                    );
673                    assert_eq!(
674                        region.check_aligned_for::<u32>(relative_offset),
675                        if absolute_offset % 4 == 0 { Ok(()) } else { Err(MmioError::Unaligned) }
676                    );
677                    assert_eq!(
678                        region.check_aligned_for::<u64>(relative_offset),
679                        if absolute_offset % 8 == 0 { Ok(()) } else { Err(MmioError::Unaligned) }
680                    );
681                }
682                // Drop the first byte to advance the region's offset.
683                let _ = region.split_off(1);
684            }
685        }
686    }
687}