Skip to main content

mock_mmio/
lib.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 mocking MMIO regions.
6//!
7//! This crate provides the type [MockMemoryOps] allowing expectations to be set on accesses to a
8//! block of memory. This type is generated by the [mockall] crate - see its documentation for
9//! setting expectations.
10//!
11//! # Usage Example
12//! ```
13//! use mmio::Mmio;
14//! use mockall::predicate::eq;
15//! let mut ops = MockMemoryOps::new();
16//! // Expect a single load32 call at offset 64, returning the value 0xabcd.
17//! ops.expect_load32()
18//!     .times(1)
19//!     .with(eq(64))
20//!     .return_const(0xabcd_u32);
21//!
22//! let len = 1024;
23//! // Create an MMIO region using the given mock ops and of the given size.
24//! // The returned region may be split and sent to other threads.
25//! let mmio = new_mock_mmio(&ops, len);
26//! assert_eq!(mmio.load32(64), 0xabcd_u32);
27//! ```
28use mmio::MmioSplit;
29use mmio::region::{MmioRegion, UnsafeMmio};
30use mockall::mock;
31use std::ops::Deref;
32
33mock! {
34    pub MemoryOps {
35        pub fn load8(&self, addr: usize) -> u8;
36        pub fn load16(&self, addr: usize) -> u16;
37        pub fn load32(&self, addr: usize) -> u32;
38        pub fn load64(&self, addr: usize) -> u64;
39
40        pub fn store8(&self, addr: usize, value: u8);
41        pub fn store16(&self, addr: usize, value: u16);
42        pub fn store32(&self, addr: usize, value: u32);
43        pub fn store64(&self, addr: usize, value: u64);
44
45        pub fn write_barrier(&self);
46        pub fn read_barrier(&self);
47    }
48}
49
50/// Create a mock MMIO operation of the given `len` that uses the given mock `ops`.
51///
52/// The returned MMIO region is splittable and sendable.
53pub fn new_mock_mmio<O: Deref<Target = MockMemoryOps> + Clone + Send + Sync>(
54    ops: O,
55    len: usize,
56) -> impl MmioSplit + Send + Sync + use<O> {
57    MmioRegion::new(MockMmio { ops, len })
58}
59
60#[derive(Clone)]
61struct MockMmio<O> {
62    ops: O,
63    len: usize,
64}
65
66impl<O: Deref<Target = MockMemoryOps>> UnsafeMmio for MockMmio<O> {
67    fn len(&self) -> usize {
68        self.len
69    }
70
71    fn align_offset(&self, _align: usize) -> usize {
72        0
73    }
74
75    unsafe fn load8_unchecked(&self, offset: usize) -> u8 {
76        self.ops.load8(offset)
77    }
78
79    unsafe fn load16_unchecked(&self, offset: usize) -> u16 {
80        self.ops.load16(offset)
81    }
82
83    unsafe fn load32_unchecked(&self, offset: usize) -> u32 {
84        self.ops.load32(offset)
85    }
86
87    unsafe fn load64_unchecked(&self, offset: usize) -> u64 {
88        self.ops.load64(offset)
89    }
90
91    unsafe fn store8_unchecked(&self, offset: usize, value: u8) {
92        self.ops.store8(offset, value)
93    }
94
95    unsafe fn store16_unchecked(&self, offset: usize, value: u16) {
96        self.ops.store16(offset, value)
97    }
98
99    unsafe fn store32_unchecked(&self, offset: usize, value: u32) {
100        self.ops.store32(offset, value)
101    }
102
103    unsafe fn store64_unchecked(&self, offset: usize, value: u64) {
104        self.ops.store64(offset, value)
105    }
106
107    fn write_barrier(&self) {
108        self.ops.write_barrier();
109    }
110
111    fn read_barrier(&self) {
112        self.ops.read_barrier();
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use mmio::Mmio;
120    use mockall::Sequence;
121    use mockall::predicate::eq;
122
123    #[test]
124    fn test_mock_ops() {
125        let mut ops = MockMemoryOps::new();
126        ops.expect_load8().with(eq(0)).return_const(17);
127
128        let mmio = new_mock_mmio(&ops, 1024);
129        assert_eq!(mmio.load8(0), 17);
130    }
131
132    #[test]
133    fn test_sequence_interleaving() {
134        // This test defines two concurrent sequences each with 2 operations:
135        // - [a1, a2]
136        // - [b1, b2]
137        //
138        // The operations in each sequence must be performed in order, but the sequences may be
139        // interleaved. There are 6 possible interleavings:
140        //
141        // - [a1, a2, b1, b2]
142        // - [a1, b1, a2, b2]
143        // - [a1, b1, b2, a2]
144        // - [b1, a1, a2, b2]
145        // - [b1, a1, b2, a2]
146        // - [b1, b2, a1, a2]
147        enum Op {
148            A1,
149            A2,
150            B1,
151            B2,
152        }
153        for interleaving in [
154            [Op::A1, Op::A2, Op::B1, Op::B2],
155            [Op::A1, Op::B1, Op::A2, Op::B2],
156            [Op::A1, Op::B1, Op::B2, Op::A2],
157            [Op::B1, Op::A1, Op::A2, Op::B2],
158            [Op::B1, Op::A1, Op::B2, Op::A2],
159            [Op::B1, Op::B2, Op::A1, Op::A2],
160            // An invalid interleaving. Uncommenting the following line should cause a failure.
161            //[Op::A1, Op::B2, Op::A2, Op::B1],
162        ] {
163            let mut ops = MockMemoryOps::new();
164
165            let mut seq1 = Sequence::new();
166            let mut seq2 = Sequence::new();
167
168            ops.expect_load8()
169                .times(1)
170                .in_sequence(&mut seq1)
171                .with(eq(0_usize))
172                .return_const(17_u8);
173            ops.expect_load8()
174                .times(1)
175                .in_sequence(&mut seq1)
176                .with(eq(1_usize))
177                .return_const(36_u8);
178
179            ops.expect_store16()
180                .times(1)
181                .in_sequence(&mut seq2)
182                .with(eq(2_usize), eq(1023_u16))
183                .return_const(());
184            ops.expect_load16()
185                .times(1)
186                .in_sequence(&mut seq2)
187                .with(eq(2_usize))
188                .return_const(1023_u16);
189
190            let mut mmio = new_mock_mmio(&ops, 1024);
191            let r1 = mmio.split_off(2);
192            let mut r2 = mmio.split_off(2);
193
194            for op in interleaving {
195                match op {
196                    Op::A1 => assert_eq!(r1.load8(0), 17),
197                    Op::A2 => assert_eq!(r1.load8(1), 36),
198                    Op::B1 => r2.store16(0, 1023),
199                    Op::B2 => assert_eq!(r2.load16(0), 1023),
200                }
201            }
202        }
203    }
204}