Skip to main content

fake_mmio/
inject.rs

1// Copyright 2026 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//! MMIO operation injection support for intercepting MMIO access.
6//!
7//! This module provides a framework for injecting side-effects on VMO access.
8//!
9//! # Usage
10//!
11//! [`VmoMemory`] is the main entry type for injection in regular usages of the
12//! [`mmio`] crate. It replaces [`mmio::vmo::VmoMemory`] as the main implementer
13//! of [`UnsafeMmio`] backing the operations for an [`MmioRegion`].
14//!
15//! See the documentation of [`VmoMemory`] for more details.
16//!
17//! ## Conditional Compilation
18//!
19//! For conditional compilation, the pattern to use is:
20//!
21//! ```
22//! #[cfg(test)]
23//! mod memory {
24//!     // Wrapper is a type implementing `VmoMemoryWrapper`.
25//!     pub type VmoMemory = fake_mmio::inject::VmoMemory<Wrapper>;
26//! }
27//!
28//! #[cfg(not(test))]
29//! mod memory {
30//!     pub type VmoMemory = mmio::vmo::VmoMemory;
31//! }
32//! ```
33//!
34//! ## Type parameters
35//!
36//! [`VmoMemory`] implements [`VmoMapper`] like [`mmio::vmo::VmoMemory`] so it
37//! can also be used in type parameters instead.
38//!
39//! ```
40//! pub fn create_driver<M: mmio::vmo::VmoMapper>() -> MyDriver<M> { ... };
41//!
42//! #[cfg(test)]
43//! fn test_fn() {
44//!     let driver = create_driver::<fake_mmio::injec::VmoMemory<Wrapper>>();
45//! }
46//!
47//! fn load_driver() {
48//!     let driver = create_driver::<mmio::vmo::VmoMemory>();
49//! }
50//! ```
51//!
52//!
53
54use std::marker::PhantomData;
55use std::ops::Range;
56
57use mmio::MmioExt;
58use mmio::region::{MmioRegion, UnsafeMmio};
59use mmio::vmo::VmoMapper;
60
61use crate::atomic::{AtomicMmioPtr, AtomicOperand};
62use crate::cached_vmo::CachedVmoMemory;
63use crate::operand::MmioOperand;
64
65mod after_store;
66mod dispatch;
67mod offset;
68mod passthrough;
69mod range_override;
70mod registry;
71mod w1c;
72
73pub use after_store::AfterStore;
74pub use dispatch::{
75    DispatchBuilder, DispatchHandler, DispatchNode, DispatchNodeHandler, OpDispatcher,
76    RegisterRange,
77};
78pub use offset::Offset;
79pub use passthrough::Passthrough;
80pub use range_override::{MaybeVmoMemoryHandler, RangeOverride};
81pub use registry::{BaseRegistry, Registry, RegistryHandler, ScopedRegistry, StrictRegistry};
82pub use w1c::WriteOneToClear;
83
84#[cfg(test)]
85use range_override::MockMaybeVmoMemoryHandler;
86
87/// A factory trait for constructing a handler associated with a given VMO.
88pub trait VmoMemoryWrapper: VmoMemoryHandler {
89    /// Creates a new handler instance for `vmo`.
90    fn new(vmo: &zx::Vmo) -> Self;
91}
92
93/// Handles MMIO read and write operations on a VMO.
94///
95/// Implementors define custom behavior when MMIO loads and stores of various
96/// sizes occur.
97///
98/// The [`crate::inject`] module contain a number of implementations of
99/// `VmoMemoryHandler` that can be used to alter the behavior of loads and
100/// stores for rich MMIO fakes.
101pub trait VmoMemoryHandler {
102    /// Handles an 8-bit MMIO load operation.
103    fn load8(&self, op: VmoOpHelper<'_, u8>) -> u8;
104
105    /// Handles a 16-bit MMIO load operation.
106    fn load16(&self, op: VmoOpHelper<'_, u16>) -> u16;
107
108    /// Handles a 32-bit MMIO load operation.
109    fn load32(&self, op: VmoOpHelper<'_, u32>) -> u32;
110
111    /// Handles a 64-bit MMIO load operation.
112    fn load64(&self, op: VmoOpHelper<'_, u64>) -> u64;
113
114    /// Handles an 8-bit MMIO store operation.
115    fn store8(&self, op: VmoOpHelper<'_, u8>, value: u8);
116
117    /// Handles a 16-bit MMIO store operation.
118    fn store16(&self, op: VmoOpHelper<'_, u16>, value: u16);
119
120    /// Handles a 32-bit MMIO store operation.
121    fn store32(&self, op: VmoOpHelper<'_, u32>, value: u32);
122
123    /// Handles a 64-bit MMIO store operation.
124    fn store64(&self, op: VmoOpHelper<'_, u64>, value: u64);
125}
126
127/// A generic handler for MMIO operations parameterized by operand type.
128///
129/// Implementing this trait automatically provides an implementation of
130/// [`VmoMemoryHandler`].
131pub trait GenericVmoMemoryHandler {
132    /// Handles an MMIO load operation for type `T`.
133    fn load<T: MmioOperand>(&self, op: VmoOpHelper<'_, T>) -> T;
134
135    /// Handles an MMIO store operation for type `T`.
136    fn store<T: MmioOperand>(&self, op: VmoOpHelper<'_, T>, value: T);
137}
138
139impl<O> VmoMemoryHandler for O
140where
141    O: GenericVmoMemoryHandler,
142{
143    fn load8(&self, op: VmoOpHelper<'_, u8>) -> u8 {
144        self.load(op)
145    }
146
147    fn load16(&self, op: VmoOpHelper<'_, u16>) -> u16 {
148        self.load(op)
149    }
150
151    fn load32(&self, op: VmoOpHelper<'_, u32>) -> u32 {
152        self.load(op)
153    }
154
155    fn load64(&self, op: VmoOpHelper<'_, u64>) -> u64 {
156        self.load(op)
157    }
158
159    fn store8(&self, op: VmoOpHelper<'_, u8>, value: u8) {
160        self.store(op, value)
161    }
162
163    fn store16(&self, op: VmoOpHelper<'_, u16>, value: u16) {
164        self.store(op, value)
165    }
166
167    fn store32(&self, op: VmoOpHelper<'_, u32>, value: u32) {
168        self.store(op, value)
169    }
170
171    fn store64(&self, op: VmoOpHelper<'_, u64>, value: u64) {
172        self.store(op, value)
173    }
174}
175
176/// A wrapper around an [`UnsafeMmio`] underlying storage that permits injecting
177/// behavior on reads and writes to the underlying VMO.
178///
179/// The `H` handler type is an implementation of [`VmoMemoryHandler`], which is
180/// tailored specifically for intersecting loads and stores to the underlying
181/// VMO.
182///
183/// Note that [`VmoMemory`] _always_ operates on [`CachedVmoMemory`], since the
184/// fakes are running in the same process as the test, and there's no
185/// device/peripheral memory in play.
186pub struct VmoMemory<H> {
187    handler: H,
188    vmo: CachedVmoMemory,
189}
190
191impl<H: VmoMemoryWrapper> VmoMemory<H> {
192    /// Maps a `zx::Vmo` into memory and wraps the resulting `VmoMemory`
193    /// inside `VmoMemory`.
194    pub fn map(offset: usize, size: usize, vmo: zx::Vmo) -> Result<MmioRegion<Self>, zx::Status> {
195        let handler = H::new(&vmo);
196        let mmio = CachedVmoMemory::map(offset, size, vmo)?;
197
198        Ok(mmio.map(move |vmo| Self { handler, vmo }))
199    }
200}
201
202impl<H> VmoMemory<H> {
203    /// Creates a new `VmoMemory` with the provided `handler` and backing `vmo`.
204    pub fn new(handler: H, vmo: CachedVmoMemory) -> Self {
205        Self { handler, vmo }
206    }
207
208    fn op_helper<T>(&self, offset: usize) -> VmoOpHelper<'_, T> {
209        VmoOpHelper {
210            vmo_offset: offset,
211            relative_offset: offset,
212            inner: &self.vmo,
213            _marker: PhantomData,
214        }
215    }
216}
217
218impl<H: VmoMemoryHandler> UnsafeMmio for VmoMemory<H> {
219    #[inline]
220    fn len(&self) -> usize {
221        self.vmo.len()
222    }
223
224    #[inline]
225    fn align_offset(&self, align: usize) -> usize {
226        self.vmo.align_offset(align)
227    }
228
229    #[inline]
230    unsafe fn load8_unchecked(&self, offset: usize) -> u8 {
231        self.handler.load8(self.op_helper(offset))
232    }
233
234    #[inline]
235    unsafe fn load16_unchecked(&self, offset: usize) -> u16 {
236        self.handler.load16(self.op_helper(offset))
237    }
238
239    #[inline]
240    unsafe fn load32_unchecked(&self, offset: usize) -> u32 {
241        self.handler.load32(self.op_helper(offset))
242    }
243
244    #[inline]
245    unsafe fn load64_unchecked(&self, offset: usize) -> u64 {
246        self.handler.load64(self.op_helper(offset))
247    }
248
249    #[inline]
250    unsafe fn store8_unchecked(&self, offset: usize, v: u8) {
251        self.handler.store8(self.op_helper(offset), v)
252    }
253
254    #[inline]
255    unsafe fn store16_unchecked(&self, offset: usize, v: u16) {
256        self.handler.store16(self.op_helper(offset), v)
257    }
258
259    #[inline]
260    unsafe fn store32_unchecked(&self, offset: usize, v: u32) {
261        self.handler.store32(self.op_helper(offset), v)
262    }
263
264    #[inline]
265    unsafe fn store64_unchecked(&self, offset: usize, v: u64) {
266        self.handler.store64(self.op_helper(offset), v)
267    }
268
269    #[inline]
270    fn write_barrier(&self) {
271        self.vmo.write_barrier();
272    }
273
274    #[inline]
275    fn read_barrier(&self) {
276        self.vmo.read_barrier();
277    }
278}
279
280impl<H> AtomicMmioPtr for VmoMemory<H> {
281    fn ptr<T: AtomicOperand>(&self, offset: usize) -> std::ptr::NonNull<T> {
282        self.vmo.ptr(offset)
283    }
284}
285
286impl<H: VmoMemoryWrapper> VmoMapper for VmoMemory<H> {
287    fn map(offset: usize, size: usize, vmo: zx::Vmo) -> Result<MmioRegion<Self>, zx::Status> {
288        Self::map(offset, size, vmo)
289    }
290}
291
292/// Helper providing contextual information and operations for an MMIO access.
293pub struct VmoOpHelper<'a, T> {
294    vmo_offset: usize,
295    relative_offset: usize,
296    inner: &'a CachedVmoMemory,
297    _marker: PhantomData<T>,
298}
299
300impl<'a, T> VmoOpHelper<'a, T> {
301    /// Returns the _relative offset_ for this vmo operation.
302    ///
303    /// NOTE: A `VmoOpHelper` can be decorated by shifting an internally-kept
304    /// relative offset to help build fakes with relative register bank
305    /// addresses. See [`VmoOpHelper::apply_offset`].
306    pub fn offset(&self) -> usize {
307        self.relative_offset
308    }
309
310    /// Returns the _relative memory range_ for this vmo operation.
311    ///
312    /// See [`VmoOpHelper::offset`].
313    pub fn range(&self) -> Range<usize> {
314        self.relative_offset..(self.relative_offset + std::mem::size_of::<T>())
315    }
316
317    /// Returns the offset of this operation relative to the start of the
318    /// wrapped VMO.
319    ///
320    /// Use [`VmoOpHelper::vmo_offset`] when using [`VmoOpHelper::unsafe_mmio`].
321    pub fn vmo_offset(&self) -> usize {
322        self.vmo_offset
323    }
324
325    /// Returns the memory for this vmo operation relative to the start of the
326    /// wrapped VMO.
327    ///
328    /// See [`VmoOpHelper::vmo_offset`].
329    pub fn vmo_range(&self) -> Range<usize> {
330        self.vmo_offset..(self.vmo_offset + std::mem::size_of::<T>())
331    }
332
333    /// Provides access to the underlying unsafe mmio implementation.
334    pub fn unsafe_mmio(&self) -> &'a impl UnsafeMmio {
335        self.inner
336    }
337
338    /// Applies `offset` to this `VmoOpHelper`.
339    ///
340    /// This function is used so that any further receivers of this
341    /// `VmoOpHelper` can work in an offset address space. For example, when
342    /// register banks are defined with offsets or when reusing register
343    /// definitions post an [`mmio::MmioSplit`] region.
344    ///
345    /// # Panics
346    ///
347    /// Panics if `offset` is larger than the current relative offset.
348    pub fn apply_offset(self, offset: usize) -> Self {
349        let Some(relative_offset) = self.relative_offset.checked_sub(offset) else {
350            panic!("offset {offset} too big for current offset {}", self.relative_offset);
351        };
352        Self { relative_offset, ..self }
353    }
354
355    fn borrow_region(&self) -> MmioRegion<CachedVmoMemory, &'a CachedVmoMemory> {
356        // SAFETY: We're piggybacking on the handy implementation of
357        // `MmioRegion` here to be able to implement `unsafe` operations in the
358        // context of a `VmoOpHelper`.
359        //
360        // We're guaranteeing this is ok by creation of a `VmoOpHelper`
361        // exclusively in the context of `UnsafeMmio` calls.
362        unsafe { MmioRegion::new_unchecked(self.inner, self.vmo_range()) }
363    }
364}
365
366impl<'a, T: MmioOperand> VmoOpHelper<'a, T> {
367    /// Performs the load from the underlying VMO memory.
368    pub fn load(&self) -> T {
369        self.borrow_region().load(0)
370    }
371
372    /// Performs the store to the underlying VMO memory.
373    pub fn store(&self, value: T) {
374        self.borrow_region().store(0, value)
375    }
376}
377
378/// Provides a mock for `VmoMemoryHandler` via `mockall`.
379///
380/// NOTE: We can't use `automock` here because `mockall` gets confused with the
381/// lifetimes.
382#[cfg(test)]
383mod mock {
384    #![allow(clippy::extra_unused_lifetimes)]
385
386    use super::*;
387    mockall::mock! {
388        pub VmoMemoryHandler {}
389
390        impl VmoMemoryHandler for VmoMemoryHandler {
391            fn load8<'a>(&self, op: VmoOpHelper<'a, u8>) -> u8;
392            fn load16<'a>(&self, op: VmoOpHelper<'a, u16>) -> u16;
393            fn load32<'a>(&self, op: VmoOpHelper<'a, u32>) -> u32;
394            fn load64<'a>(&self, op: VmoOpHelper<'a, u64>) -> u64;
395            fn store8<'a>(&self, op: VmoOpHelper<'a, u8>, value: u8);
396            fn store16<'a>(&self, op: VmoOpHelper<'a, u16>, value: u16);
397            fn store32<'a>(&self, op: VmoOpHelper<'a, u32>, value: u32);
398            fn store64<'a>(&self, op: VmoOpHelper<'a, u64>, value: u64);
399        }
400    }
401}
402#[cfg(test)]
403pub use mock::*;
404
405#[cfg(test)]
406mod tests {
407    use super::*;
408    use mmio::Mmio as _;
409
410    const VMO_SIZE: usize = 4096;
411
412    #[test]
413    fn test_load8() {
414        let mut mock = MockVmoMemoryHandler::new();
415        let expected_val = 0xab_u8;
416        let _ = mock
417            .expect_load8()
418            .once()
419            .withf(|op| op.offset() == 0 && op.range() == (0..1))
420            .return_const(expected_val);
421
422        let mmio = CachedVmoMemory::new_mapped(VMO_SIZE)
423            .expect("Failed to map VMO.")
424            .map(|vmo| VmoMemory::new(mock, vmo));
425
426        assert_eq!(mmio.load::<u8>(0), expected_val);
427    }
428
429    #[test]
430    fn test_load16() {
431        let mut mock = MockVmoMemoryHandler::new();
432        let expected_val = 0x1234_u16;
433        let _ = mock
434            .expect_load16()
435            .once()
436            .withf(|op| op.offset() == 2 && op.range() == (2..4))
437            .return_const(expected_val);
438
439        let mmio = CachedVmoMemory::new_mapped(VMO_SIZE)
440            .expect("Failed to map VMO.")
441            .map(|vmo| VmoMemory::new(mock, vmo));
442
443        assert_eq!(mmio.load::<u16>(2), expected_val);
444    }
445
446    #[test]
447    fn test_load32() {
448        let mut mock = MockVmoMemoryHandler::new();
449        let expected_val = 0x5678_9abc_u32;
450        let _ = mock
451            .expect_load32()
452            .once()
453            .withf(|op| op.offset() == 4 && op.range() == (4..8))
454            .return_const(expected_val);
455
456        let mmio = CachedVmoMemory::new_mapped(VMO_SIZE)
457            .expect("Failed to map VMO.")
458            .map(|vmo| VmoMemory::new(mock, vmo));
459
460        assert_eq!(mmio.load::<u32>(4), expected_val);
461    }
462
463    #[test]
464    fn test_load64() {
465        let mut mock = MockVmoMemoryHandler::new();
466        let expected_val = 0xdef0_1234_5678_9abc_u64;
467        let _ = mock
468            .expect_load64()
469            .once()
470            .withf(|op| op.offset() == 8 && op.range() == (8..16))
471            .return_const(expected_val);
472
473        let mmio = CachedVmoMemory::new_mapped(VMO_SIZE)
474            .expect("Failed to map VMO.")
475            .map(|vmo| VmoMemory::new(mock, vmo));
476
477        assert_eq!(mmio.load::<u64>(8), expected_val);
478    }
479
480    #[test]
481    fn test_store8() {
482        let mut mock = MockVmoMemoryHandler::new();
483        let val_to_store = 0xab_u8;
484        let _ = mock
485            .expect_store8()
486            .once()
487            .withf(move |op, val| op.offset() == 0 && op.range() == (0..1) && *val == val_to_store)
488            .return_const(());
489
490        let mut mmio = CachedVmoMemory::new_mapped(VMO_SIZE)
491            .expect("Failed to map VMO.")
492            .map(|vmo| VmoMemory::new(mock, vmo));
493
494        mmio.store::<u8>(0, val_to_store);
495    }
496
497    #[test]
498    fn test_store16() {
499        let mut mock = MockVmoMemoryHandler::new();
500        let val_to_store = 0x1234_u16;
501        let _ = mock
502            .expect_store16()
503            .once()
504            .withf(move |op, val| op.offset() == 2 && op.range() == (2..4) && *val == val_to_store)
505            .return_const(());
506
507        let mut mmio = CachedVmoMemory::new_mapped(VMO_SIZE)
508            .expect("Failed to map VMO.")
509            .map(|vmo| VmoMemory::new(mock, vmo));
510
511        mmio.store::<u16>(2, val_to_store);
512    }
513
514    #[test]
515    fn test_store32() {
516        let mut mock = MockVmoMemoryHandler::new();
517        let val_to_store = 0x5678_9abc_u32;
518        let _ = mock
519            .expect_store32()
520            .once()
521            .withf(move |op, val| op.offset() == 4 && op.range() == (4..8) && *val == val_to_store)
522            .return_const(());
523
524        let mut mmio = CachedVmoMemory::new_mapped(VMO_SIZE)
525            .expect("Failed to map VMO.")
526            .map(|vmo| VmoMemory::new(mock, vmo));
527
528        mmio.store::<u32>(4, val_to_store);
529    }
530
531    #[test]
532    fn test_store64() {
533        let mut mock = MockVmoMemoryHandler::new();
534        let val_to_store = 0xdef0_1234_5678_9abc_u64;
535        let _ = mock
536            .expect_store64()
537            .once()
538            .withf(move |op, val| op.offset() == 8 && op.range() == (8..16) && *val == val_to_store)
539            .return_const(());
540
541        let mut mmio = CachedVmoMemory::new_mapped(VMO_SIZE)
542            .expect("Failed to map VMO.")
543            .map(|vmo| VmoMemory::new(mock, vmo));
544
545        mmio.store::<u64>(8, val_to_store);
546    }
547
548    #[test]
549    fn test_vmo_memory_helpers() {
550        let mock = MockVmoMemoryHandler::new();
551        let mmio = CachedVmoMemory::new_mapped(VMO_SIZE)
552            .expect("Failed to map VMO.")
553            .map(|vmo| VmoMemory::new(mock, vmo));
554
555        assert_eq!(mmio.len(), VMO_SIZE);
556        assert_eq!(mmio.align_offset(8), 0);
557        mmio.write_barrier();
558        mmio.read_barrier();
559    }
560}