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    }
47}
48
49/// Create a mock MMIO operation of the given `len` that uses the given mock `ops`.
50///
51/// The returned MMIO region is splittable and sendable.
52pub fn new_mock_mmio<O: Deref<Target = MockMemoryOps> + Clone + Send + Sync>(
53    ops: O,
54    len: usize,
55) -> impl MmioSplit + Send + Sync + use<O> {
56    MmioRegion::new(MockMmio { ops, len })
57}
58
59#[derive(Clone)]
60struct MockMmio<O> {
61    ops: O,
62    len: usize,
63}
64
65impl<O: Deref<Target = MockMemoryOps>> UnsafeMmio for MockMmio<O> {
66    fn len(&self) -> usize {
67        self.len
68    }
69
70    fn align_offset(&self, _align: usize) -> usize {
71        0
72    }
73
74    unsafe fn load8_unchecked(&self, offset: usize) -> u8 {
75        self.ops.load8(offset)
76    }
77
78    unsafe fn load16_unchecked(&self, offset: usize) -> u16 {
79        self.ops.load16(offset)
80    }
81
82    unsafe fn load32_unchecked(&self, offset: usize) -> u32 {
83        self.ops.load32(offset)
84    }
85
86    unsafe fn load64_unchecked(&self, offset: usize) -> u64 {
87        self.ops.load64(offset)
88    }
89
90    unsafe fn store8_unchecked(&self, offset: usize, value: u8) {
91        self.ops.store8(offset, value)
92    }
93
94    unsafe fn store16_unchecked(&self, offset: usize, value: u16) {
95        self.ops.store16(offset, value)
96    }
97
98    unsafe fn store32_unchecked(&self, offset: usize, value: u32) {
99        self.ops.store32(offset, value)
100    }
101
102    unsafe fn store64_unchecked(&self, offset: usize, value: u64) {
103        self.ops.store64(offset, value)
104    }
105
106    fn write_barrier(&self) {
107        self.ops.write_barrier();
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use mmio::Mmio;
115    use mockall::Sequence;
116    use mockall::predicate::eq;
117
118    #[test]
119    fn test_mock_ops() {
120        let mut ops = MockMemoryOps::new();
121        ops.expect_load8().with(eq(0)).return_const(17);
122
123        let mmio = new_mock_mmio(&ops, 1024);
124        assert_eq!(mmio.load8(0), 17);
125    }
126
127    #[test]
128    fn test_sequence_interleaving() {
129        // This test defines two concurrent sequences each with 2 operations:
130        // - [a1, a2]
131        // - [b1, b2]
132        //
133        // The operations in each sequence must be performed in order, but the sequences may be
134        // interleaved. There are 6 possible interleavings:
135        //
136        // - [a1, a2, b1, b2]
137        // - [a1, b1, a2, b2]
138        // - [a1, b1, b2, a2]
139        // - [b1, a1, a2, b2]
140        // - [b1, a1, b2, a2]
141        // - [b1, b2, a1, a2]
142        enum Op {
143            A1,
144            A2,
145            B1,
146            B2,
147        }
148        for interleaving in [
149            [Op::A1, Op::A2, Op::B1, Op::B2],
150            [Op::A1, Op::B1, Op::A2, Op::B2],
151            [Op::A1, Op::B1, Op::B2, Op::A2],
152            [Op::B1, Op::A1, Op::A2, Op::B2],
153            [Op::B1, Op::A1, Op::B2, Op::A2],
154            [Op::B1, Op::B2, Op::A1, Op::A2],
155            // An invalid interleaving. Uncommenting the following line should cause a failure.
156            //[Op::A1, Op::B2, Op::A2, Op::B1],
157        ] {
158            let mut ops = MockMemoryOps::new();
159
160            let mut seq1 = Sequence::new();
161            let mut seq2 = Sequence::new();
162
163            ops.expect_load8()
164                .times(1)
165                .in_sequence(&mut seq1)
166                .with(eq(0_usize))
167                .return_const(17_u8);
168            ops.expect_load8()
169                .times(1)
170                .in_sequence(&mut seq1)
171                .with(eq(1_usize))
172                .return_const(36_u8);
173
174            ops.expect_store16()
175                .times(1)
176                .in_sequence(&mut seq2)
177                .with(eq(2_usize), eq(1023_u16))
178                .return_const(());
179            ops.expect_load16()
180                .times(1)
181                .in_sequence(&mut seq2)
182                .with(eq(2_usize))
183                .return_const(1023_u16);
184
185            let mut mmio = new_mock_mmio(&ops, 1024);
186            let r1 = mmio.split_off(2);
187            let mut r2 = mmio.split_off(2);
188
189            for op in interleaving {
190                match op {
191                    Op::A1 => assert_eq!(r1.load8(0), 17),
192                    Op::A2 => assert_eq!(r1.load8(1), 36),
193                    Op::B1 => r2.store16(0, 1023),
194                    Op::B2 => assert_eq!(r2.load16(0), 1023),
195                }
196            }
197        }
198    }
199}