Skip to main content

globally_ordered_mock_mmio/
lib.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//! Globally-ordered mock for MMIO driver testing in Rust.
6//!
7//! Built on top of the MMIO region abstractions in
8//! `//sdk/lib/driver/mmio/rust`, this crate provides a high-density, readable,
9//! trace-like API for testing drivers that interact with memory-mapped I/O
10//! (MMIO).
11//!
12//! # Usage guide
13//!
14//! ## Initialize the test double
15//!
16//! Create a [`MockMmioRegionBuilder`] instance covering the expected MMIO
17//! region size in bytes.
18//!
19//! ```
20//! use globally_ordered_mock_mmio::MockMmioRegionBuilder;
21//!
22//! let mock_builder = MockMmioRegionBuilder::new(0x1000);
23//! ```
24//!
25//! ## Declare expectations
26//!
27//! Record expected MMIO operations using the [`ExpectedTraceBuilder`] API.
28//!
29//! ### Typed register operations
30//!
31//! Use [`ExpectedTraceBuilder::read`] and [`ExpectedTraceBuilder::write`] for
32//! expectations on register types defined with [`mmio::register!`]. The methods
33//! deduce byte offsets and access widths.
34//!
35//! ```
36//! # mod registers {
37//! #     use mmio::register;
38//! #     register! {
39//! #         #[register(offset = 0x00, mode = RO)]
40//! #         pub struct Status(u32);
41//! #         #[register(offset = 0x04, mode = WO)]
42//! #         pub struct Command(u32);
43//! #     }
44//! # }
45//! use globally_ordered_mock_mmio::MockMmioRegionBuilder;
46//! use mmio::{ReadableRegister, WritableRegister};
47//!
48//! let mock_builder = MockMmioRegionBuilder::new(0x1000);
49//! mock_builder.expect(|t| {
50//!     // Expect a read from Status returning 0x0000_0001
51//!     t.read::<registers::Status>(0x0000_0001);
52//!
53//!     // Expect a write to Command with value 0x0000_0002
54//!     t.write::<registers::Command>(0x0000_0002);
55//! });
56//!
57//! // Performed by the code under test.
58//! let mut mmio = mock_builder.build();
59//! assert_eq!(registers::Status::read(&mmio).0, 0x0000_0001);
60//! registers::Command(0x0000_0002).write(&mut mmio);
61//! ```
62//!
63//! ### Indexed register operations
64//!
65//! Use [`ExpectedTraceBuilder::read_indexed`] and
66//! [`ExpectedTraceBuilder::write_indexed`] for expectations on indexed register
67//! types defined with `#[indexed_register(...)]`.
68//!
69//! ```
70//! # mod registers {
71//! #     use mmio::register;
72//! #     register! {
73//! #         #[indexed_register(offset = 0x100, stride = 4, count = 4, mode = RW)]
74//! #         pub struct IndexedData(u32);
75//! #     }
76//! # }
77//! use globally_ordered_mock_mmio::MockMmioRegionBuilder;
78//! use mmio::{ReadableIndexedRegister, WritableIndexedRegister};
79//!
80//! let mock_builder = MockMmioRegionBuilder::new(0x1000);
81//! mock_builder.expect(|t| {
82//!     // Expect a read from IndexedData at index 2 returning 0x30
83//!     t.read_indexed::<registers::IndexedData>(2, 0x30);
84//!
85//!     // Expect a write to IndexedData at index 3 with value 0x40
86//!     t.write_indexed::<registers::IndexedData>(3, 0x40);
87//! });
88//!
89//! // Performed by the code under test.
90//! let mut mmio = mock_builder.build();
91//! assert_eq!(registers::IndexedData::read_index(&mmio, 2).0, 0x30);
92//! registers::IndexedData(0x40).write_index(&mut mmio, 3);
93//! ```
94//!
95//! ### Successful polling
96//!
97//! Use [`ExpectedTraceBuilder::poll`] to model a status register that changes
98//! value across successive reads.
99//!
100//! ```
101//! # mod registers {
102//! #     use mmio::register;
103//! #     register! {
104//! #         #[register(offset = 0x00, mode = RO)]
105//! #         pub struct Status(u32);
106//! #     }
107//! # }
108//! use globally_ordered_mock_mmio::MockMmioRegionBuilder;
109//! use mmio::ReadableRegister;
110//!
111//! let mock_builder = MockMmioRegionBuilder::new(0x1000);
112//! mock_builder.expect(|t| {
113//!     // First two reads return 0x01 (busy); subsequent reads return 0x02 (ready)
114//!     t.poll::<registers::Status>([0x0000_0001, 0x0000_0001, 0x0000_0002]);
115//! });
116//!
117//! // Performed by the code under test.
118//! let mmio = mock_builder.build();
119//! while registers::Status::read(&mmio).0 != 0x0000_0002 {}
120//! ```
121//!
122//! ### Timed out polling
123//!
124//! Use [`ExpectedTraceBuilder::poll_indefinitely`] to model hardware remaining
125//! busy until the driver times out and executes recovery.
126//!
127//! ```
128//! # mod registers {
129//! #     use mmio::register;
130//! #     register! {
131//! #         #[register(offset = 0x00, mode = RO)]
132//! #         pub struct Status(u32);
133//! #         #[register(offset = 0x04, mode = WO)]
134//! #         pub struct Command(u32);
135//! #     }
136//! # }
137//! use globally_ordered_mock_mmio::MockMmioRegionBuilder;
138//! use mmio::{ReadableRegister, WritableRegister};
139//!
140//! let mock_builder = MockMmioRegionBuilder::new(0x1000);
141//! mock_builder.expect(|t| {
142//!     // Returns 0x01 indefinitely until the driver executes a non-matching access
143//!     t.poll_indefinitely::<registers::Status>(0x0000_0001);
144//!     t.write::<registers::Command>(0x0000_0001); // Reset command after timeout
145//! });
146//!
147//! // Performed by the code under test.
148//! let mut mmio = mock_builder.build();
149//! for _ in 0..10 {
150//!     assert_eq!(registers::Status::read(&mmio).0, 0x0000_0001);
151//! }
152//! registers::Command(0x0000_0001).write(&mut mmio);
153//! ```
154//!
155//! ### Raw numeric offsets
156//!
157//! When the reference test data is a trace containing raw MMIO offsets and
158//! values, pass the raw offset to [`ExpectedTraceBuilder::at`] and express the
159//! operation and value using the [`RawOffsetTraceBuilder`] methods, such as
160//! [`RawOffsetTraceBuilder::read32`] and [`RawOffsetTraceBuilder::write32`].
161//!
162//! ```
163//! use globally_ordered_mock_mmio::MockMmioRegionBuilder;
164//! use mmio::Mmio;
165//!
166//! let mock_builder = MockMmioRegionBuilder::new(0x1000);
167//! mock_builder.expect(|t| {
168//!     t.at(0x0).read8(0x12);
169//!     t.at(0x1).write8(0x34);
170//!     t.at(0x8).read32(0x1234_5678);
171//!     t.at(0xc).write32(0x9abc_def0);
172//! });
173//!
174//! // Performed by the code under test.
175//! let mut mmio = mock_builder.build();
176//! assert_eq!(mmio.load8(0x0), 0x12);
177//! mmio.store8(0x1, 0x34);
178//! assert_eq!(mmio.load32(0x8), 0x1234_5678);
179//! mmio.store32(0xc, 0x9abc_def0);
180//! ```
181//!
182//! ### Memory barriers
183//!
184//! Use [`ExpectedTraceBuilder::write_barrier`] to expect a write barrier
185//! between two MMIO accesses.
186//!
187//! ```
188//! use globally_ordered_mock_mmio::MockMmioRegionBuilder;
189//! use mmio::Mmio;
190//!
191//! let mock_builder = MockMmioRegionBuilder::new(0x1000);
192//! mock_builder.expect(|t| {
193//!     t.at(0x0).write32(0x1234_5678);
194//!     t.write_barrier();
195//!     t.at(0x4).write32(0x9abc_def0);
196//! });
197//!
198//! // Performed by the code under test.
199//! let mut mmio = mock_builder.build();
200//! mmio.store32(0x0, 0x1234_5678);
201//! mmio.write_barrier();
202//! mmio.store32(0x4, 0x9abc_def0);
203//! ```
204//!
205//! ## Obtain and use the MMIO region
206//!
207//! Call [`MockMmioRegionBuilder::build`] to obtain a [`MockMmioRegion`], which
208//! is an [`mmio::region::MmioRegion`] implementation.
209//!
210//! The returned region implements [`mmio::Mmio`] and [`mmio::MmioSplit`],
211//! allowing driver code to split sub-regions across multiple threads. Accesses
212//! from all threads are checked against a single global expectation list, in
213//! the order in which the accesses reach the mock.
214//!
215//! The mock does not impose any ordering on accesses issued by different
216//! threads. Tests that exercise concurrent driver code must establish their own
217//! ordering, for example by having the threads synchronize on a channel.
218//!
219//! ## Verification and teardown
220//!
221//! Expectations are verified on [`Drop`], after the [`MockMmioRegionBuilder`]
222//! and all the [`MockMmioRegion`]s it produced are dropped. Any unretired
223//! expectation results in a test failure. No explicit verification call is
224//! needed.
225//!
226//! Verification is skipped while the thread is already panicking, so that the
227//! failure that caused the panic is not masked.
228
229// The module structure is private to the crate.
230
231mod data_access;
232mod expectation;
233mod formatting;
234mod history_entry;
235mod mmio_operand_value;
236mod mock_builder;
237mod operation;
238mod scoreboard;
239mod source_info;
240mod trace_builder;
241
242// The public API is exposed below. Keep to a minimum.
243
244pub use mmio_operand_value::mmio_operand_to_u64;
245pub use mock_builder::{MockMmioRegion, MockMmioRegionBuilder};
246pub use trace_builder::{ExpectedTraceBuilder, RawOffsetTraceBuilder};
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use mmio::{
252        Mmio, MmioSplit, ReadableIndexedRegister, ReadableRegister, WritableIndexedRegister,
253        WritableRegister,
254    };
255
256    mod registers {
257        use mmio::register;
258
259        register! {
260            #[register(offset = 0x10, mode = RW)]
261            pub struct Test(u32);
262
263            #[indexed_register(offset = 0x100, stride = 4, count = 4, mode = RW)]
264            pub struct TestIndexed(u32);
265        }
266    }
267
268    #[fuchsia::test]
269    fn test_public_api_raw_accesses() {
270        let mock_builder = MockMmioRegionBuilder::new(0x100);
271        mock_builder.expect(|t| {
272            t.at(0x0).write8(0x42);
273            t.at(0x1).read8(0x5a);
274        });
275        let mut mmio = mock_builder.build();
276        mmio.store8(0x0, 0x42);
277        assert_eq!(mmio.load8(0x1), 0x5a);
278    }
279
280    #[fuchsia::test]
281    fn test_public_api_typed_registers() {
282        let mock_builder = MockMmioRegionBuilder::new(0x1000);
283        mock_builder.expect(|t| {
284            t.read::<registers::Test>(0x1234);
285            t.write::<registers::Test>(0x5678);
286        });
287        let mut mmio = mock_builder.build();
288        assert_eq!(registers::Test::read(&mmio).value(), 0x1234);
289        registers::Test(0x5678).write(&mut mmio);
290    }
291
292    #[fuchsia::test]
293    fn test_public_api_indexed_registers() {
294        let mock_builder = MockMmioRegionBuilder::new(0x1000);
295        mock_builder.expect(|t| {
296            t.read_indexed::<registers::TestIndexed>(2, 0x30);
297            t.write_indexed::<registers::TestIndexed>(3, 0x40);
298        });
299        let mut mmio = mock_builder.build();
300        assert_eq!(registers::TestIndexed::read_index(&mmio, 2).value(), 0x30);
301        registers::TestIndexed(0x40).write_index(&mut mmio, 3);
302    }
303
304    #[fuchsia::test]
305    fn test_public_api_polling_sequences() {
306        let mock_builder = MockMmioRegionBuilder::new(0x1000);
307        mock_builder.expect(|t| {
308            t.poll::<registers::Test>([0x1, 0x1, 0x2]);
309        });
310        let mmio = mock_builder.build();
311        assert_eq!(registers::Test::read(&mmio).value(), 0x1);
312        assert_eq!(registers::Test::read(&mmio).value(), 0x1);
313        assert_eq!(registers::Test::read(&mmio).value(), 0x2);
314    }
315
316    #[fuchsia::test]
317    fn test_public_api_indefinite_poll_and_recovery() {
318        let mock_builder = MockMmioRegionBuilder::new(0x1000);
319        mock_builder.expect(|t| {
320            t.poll_indefinitely::<registers::Test>(0x1);
321            t.write::<registers::Test>(0x2);
322        });
323        let mut mmio = mock_builder.build();
324        for _ in 0..5 {
325            assert_eq!(registers::Test::read(&mmio).value(), 0x1);
326        }
327        registers::Test(0x2).write(&mut mmio);
328    }
329
330    #[fuchsia::test]
331    fn test_public_api_mmio_operand_to_u64() {
332        assert_eq!(mmio_operand_to_u64(0x12u8), 0x12);
333        assert_eq!(mmio_operand_to_u64(0x1234u16), 0x1234);
334        assert_eq!(mmio_operand_to_u64(0x1234_5678u32), 0x1234_5678);
335        assert_eq!(mmio_operand_to_u64(0x1234_5678_9abc_def0u64), 0x1234_5678_9abc_def0);
336    }
337
338    #[fuchsia::test]
339    fn test_public_api_write_barrier_and_split() {
340        let mock_builder = MockMmioRegionBuilder::new(0x1000);
341        mock_builder.expect(|t| {
342            t.write_barrier();
343            t.at(0x0).write8(0x11);
344            t.at(0x20).write8(0x22);
345        });
346        let mut mmio = mock_builder.build();
347        mmio.write_barrier();
348        let mut r1 = mmio.split_off(0x20);
349        let mut r2 = mmio.split_off(0x20);
350        r1.store8(0x0, 0x11);
351        r2.store8(0x0, 0x22);
352    }
353}