Skip to main content

lab_proxy_core/
hardware_backend.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//! Hardware access abstraction for the executor.
6//!
7//! The real driver implements [`MmioBackend`] over a locally mapped MMIO
8//! region; tests and driver realm fakes use [`FakeMmio`]. Keeping the trait
9//! in the core crate lets the entire read path run on the build host.
10
11use std::collections::{BTreeMap, BTreeSet};
12
13/// Errors surfaced by a hardware backend.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum BackendError {
16    /// The backend could not complete the access; the proxy treats this as
17    /// fatal backend state for the current operation.
18    Fault,
19}
20
21/// A source of target monotonic time, injected so core logic is
22/// deterministic under test. The driver supplies zx monotonic time.
23pub trait Clock {
24    /// Current timestamp in nanoseconds.
25    fn now_ns(&mut self) -> i64;
26}
27
28/// An asynchronous timer for delays and polling.
29pub trait Timer: Clock + Send {
30    /// Asynchronously sleeps for `duration_ns` nanoseconds.
31    fn sleep(&mut self, duration_ns: i64) -> impl std::future::Future<Output = ()> + Send;
32}
33
34/// Deterministic test clock advancing by a fixed step per reading.
35#[derive(Clone, Debug, Default)]
36pub struct FakeClock {
37    /// Timestamp returned by the next call.
38    pub now: i64,
39    /// Amount added after each call.
40    pub step: i64,
41    /// Record of sleeps requested.
42    pub sleeps: Vec<i64>,
43}
44
45impl FakeClock {
46    /// Creates a fake clock.
47    pub fn new(now: i64, step: i64) -> Self {
48        Self { now, step, sleeps: Vec::new() }
49    }
50}
51
52impl Clock for FakeClock {
53    fn now_ns(&mut self) -> i64 {
54        let now = self.now;
55        self.now += self.step;
56        now
57    }
58}
59
60impl Timer for FakeClock {
61    async fn sleep(&mut self, duration_ns: i64) {
62        if duration_ns > 0 {
63            self.now += duration_ns;
64            self.sleeps.push(duration_ns);
65        }
66    }
67}
68
69/// Volatile 32-bit MMIO access to one resource.
70pub trait MmioBackend {
71    /// Performs one volatile 32-bit read at `offset` bytes from the start
72    /// of the logical resource.
73    fn read32(&mut self, offset: u64) -> Result<u32, BackendError>;
74
75    /// Performs one volatile 32-bit write at `offset` bytes from the start
76    /// of the logical resource.
77    fn write32(&mut self, offset: u64, value: u32) -> Result<(), BackendError>;
78
79    /// Applies a platform MMIO memory barrier.
80    fn barrier(&mut self);
81}
82
83/// In-memory fake MMIO region that records every access, for unit tests
84/// and driver realm fakes. Reads of unset offsets return zero.
85#[derive(Clone, Debug, Default)]
86pub struct FakeMmio {
87    values: BTreeMap<u64, u32>,
88    faults: BTreeSet<u64>,
89    /// Offsets read, in order.
90    pub accesses: Vec<u64>,
91    /// Writes performed, in order: (offset, value).
92    pub write_accesses: Vec<(u64, u32)>,
93    /// Number of memory barriers performed.
94    pub barriers: usize,
95}
96
97impl FakeMmio {
98    /// Creates an empty fake region.
99    pub fn new() -> Self {
100        Self::default()
101    }
102
103    /// Sets the value returned for reads at `offset`.
104    pub fn set(&mut self, offset: u64, value: u32) {
105        self.values.insert(offset, value);
106    }
107
108    /// Makes reads or writes at `offset` fail with [`BackendError::Fault`].
109    pub fn fail_at(&mut self, offset: u64) {
110        self.faults.insert(offset);
111    }
112}
113
114impl MmioBackend for FakeMmio {
115    fn read32(&mut self, offset: u64) -> Result<u32, BackendError> {
116        self.accesses.push(offset);
117        if self.faults.contains(&offset) {
118            return Err(BackendError::Fault);
119        }
120        Ok(self.values.get(&offset).copied().unwrap_or(0))
121    }
122
123    fn write32(&mut self, offset: u64, value: u32) -> Result<(), BackendError> {
124        self.write_accesses.push((offset, value));
125        if self.faults.contains(&offset) {
126            return Err(BackendError::Fault);
127        }
128        self.values.insert(offset, value);
129        Ok(())
130    }
131
132    fn barrier(&mut self) {
133        self.barriers += 1;
134    }
135}