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