Skip to main content

fake_mmio/inject/
offset.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
5use std::ops::Range;
6
7use crate::inject::{
8    GenericVmoMemoryHandler, MaybeVmoMemoryHandler, VmoMemoryHandler, VmoOpHelper,
9};
10use crate::operand::MmioOperand;
11
12/// An MMIO handler that applies an offset to operations before dispatching the
13/// call to the next handler.
14///
15/// Use this combinator so that the inner handlers can operate in relative
16/// address spaces. For example, if a register bank is defined in an offset in
17/// conjunction with [`mmio::MmioSplit`], it is desirable to reuse the same
18/// [`mmio::Register`] implementations in the fakes.
19pub struct Offset<H> {
20    inner: H,
21    offset: usize,
22}
23
24impl<H> Offset<H> {
25    /// Returns a new [`Offset`] that dispatches calls into `inner` after
26    /// applying `offset` to [`VmoOpHelper`] in both loads and stores.
27    pub fn new(inner: H, offset: usize) -> Self {
28        Self { inner, offset }
29    }
30}
31
32impl<H: VmoMemoryHandler> GenericVmoMemoryHandler for Offset<H> {
33    fn load<T: MmioOperand>(&self, op: VmoOpHelper<'_, T>) -> T {
34        T::load(&self.inner, op.apply_offset(self.offset))
35    }
36
37    fn store<T: MmioOperand>(&self, op: VmoOpHelper<'_, T>, value: T) {
38        T::store(&self.inner, op.apply_offset(self.offset), value)
39    }
40}
41
42impl<H: MaybeVmoMemoryHandler> MaybeVmoMemoryHandler for Offset<H> {
43    fn get_handler(&self, range: Range<usize>) -> Option<&(impl VmoMemoryHandler + ?Sized)> {
44        let Some(start) = range.start.checked_sub(self.offset) else {
45            panic!("{range:?} before offset {}", self.offset);
46        };
47        self.inner.get_handler(start..start + range.len())
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use crate::CachedVmoMemory;
55    use crate::inject::dispatch::DispatchBuilder;
56    use crate::inject::passthrough::Passthrough;
57    use crate::inject::{MockMaybeVmoMemoryHandler, MockVmoMemoryHandler, VmoMemory};
58    use crate::operand::tests::OperandCallable;
59    use mmio::MmioExt as _;
60    use mmio::region::MmioRegion;
61
62    #[test]
63    fn test_offset_passthrough() {
64        const BASE_OFFSET: usize = 0x100;
65
66        struct TestRunner<'a>(&'a mut MmioRegion<VmoMemory<Offset<Passthrough>>>);
67
68        impl OperandCallable for TestRunner<'_> {
69            fn call<T: MmioOperand>(&mut self) {
70                let val = T::try_from(0x42).unwrap();
71                self.0.store::<T>(BASE_OFFSET, val);
72                assert_eq!(self.0.load::<T>(BASE_OFFSET), val);
73            }
74        }
75
76        let handler = Offset::new(Passthrough, BASE_OFFSET);
77        let mut mmio = CachedVmoMemory::new_mapped(4096)
78            .expect("Failed to map VMO.")
79            .map(|vmo| VmoMemory::new(handler, vmo));
80
81        TestRunner(&mut mmio).call_all();
82    }
83
84    #[test]
85    fn test_offset_op_helper_attributes() {
86        const BASE_OFFSET: usize = 0x200;
87        let expected_val = 0x1234_5678_u32;
88
89        let mut mock = MockVmoMemoryHandler::new();
90        let _ = mock
91            .expect_load32()
92            .once()
93            .withf(|op| {
94                op.offset() == 0
95                    && op.range() == (0..4)
96                    && op.vmo_offset() == BASE_OFFSET
97                    && op.vmo_range() == (BASE_OFFSET..BASE_OFFSET + 4)
98            })
99            .return_const(expected_val);
100
101        let val_to_store = 0x8765_4321_u32;
102        let _ = mock
103            .expect_store32()
104            .once()
105            .withf(move |op, val| {
106                op.offset() == 4
107                    && op.range() == (4..8)
108                    && op.vmo_offset() == BASE_OFFSET + 4
109                    && op.vmo_range() == (BASE_OFFSET + 4..BASE_OFFSET + 8)
110                    && *val == val_to_store
111            })
112            .return_const(());
113
114        let handler = Offset::new(mock, BASE_OFFSET);
115        let mut mmio = CachedVmoMemory::new_mapped(4096)
116            .expect("Failed to map VMO.")
117            .map(|vmo| VmoMemory::new(handler, vmo));
118
119        assert_eq!(mmio.load::<u32>(BASE_OFFSET), expected_val);
120        mmio.store::<u32>(BASE_OFFSET + 4, val_to_store);
121    }
122
123    #[test]
124    fn test_offset_with_dispatch() {
125        const BASE_OFFSET: usize = 0x100;
126        let dispatch = DispatchBuilder::new().insert_node(0..4, Passthrough).build();
127        let handler = Offset::new(dispatch, BASE_OFFSET);
128
129        let mut mmio = CachedVmoMemory::new_mapped(4096)
130            .expect("Failed to map VMO.")
131            .map(|vmo| VmoMemory::new(handler, vmo));
132
133        let val = 0xab_cd_ef_01_u32;
134        mmio.store::<u32>(BASE_OFFSET, val);
135        assert_eq!(mmio.load::<u32>(BASE_OFFSET), val);
136    }
137
138    #[test]
139    fn test_maybe_handler() {
140        const BASE_OFFSET: usize = 0x100;
141        let mut mock = MockMaybeVmoMemoryHandler::<Passthrough>::new();
142        let _ = mock
143            .expect_get_handler()
144            .once()
145            .withf(|range| range == &(0usize..4))
146            .return_const(None);
147
148        let handler = Offset::new(mock, BASE_OFFSET);
149        assert!(handler.get_handler(BASE_OFFSET..BASE_OFFSET + 4).is_none());
150    }
151
152    #[fuchsia::test(logging = false)]
153    #[should_panic(expected = "too big for current offset")]
154    fn test_offset_underflow_panics() {
155        const BASE_OFFSET: usize = 0x100;
156        let handler = Offset::new(Passthrough, BASE_OFFSET);
157
158        let mmio = CachedVmoMemory::new_mapped(4096)
159            .expect("Failed to map VMO.")
160            .map(|vmo| VmoMemory::new(handler, vmo));
161
162        // Offset 0x50 is smaller than BASE_OFFSET 0x100, which causes underflow.
163        let _ = mmio.load::<u32>(0x50);
164    }
165}