regmock_rs/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{
4    sync::{Arc, Mutex, OnceLock},
5    time::Duration,
6};
7
8pub mod matchers;
9pub mod utils;
10use crate::utils::Regmock;
11
12thread_local! {
13    #[allow(clippy::missing_const_for_thread_local)]
14    /// Global Regmock object used by `read_fn`,`write_fn` and `ldmst_fn`
15    /// to mock registers and chip behavior.
16    pub(crate) static  MOCK: ThreadLocalRegmock = const {OnceLock::new()};
17}
18
19type ThreadLocalRegmock = OnceLock<Arc<Mutex<Regmock>>>;
20
21/// Errors generated when handling the `thread_local` locked [`Regmock`] object.
22#[derive(Debug, Clone)]
23pub enum MockError {
24    /// [`init_regmock`] was not called
25    MockNotInitialized,
26    /// could not acquire lock to [`Regmock`] object.
27    LockError,
28}
29
30impl From<MockError> for String {
31    fn from(value: MockError) -> Self {
32        format!("failed due to: {:?}", value)
33    }
34}
35
36/// Execute function against `thread_local` [`Regmock`] object.
37pub fn with_mock<F, R>(f: F) -> Result<R, MockError>
38where
39    F: FnOnce(&mut Regmock) -> R,
40{
41    MOCK.with(|mock| -> Result<R, MockError> {
42        let mut mock = mock
43            .get()
44            .ok_or(MockError::MockNotInitialized)?
45            .lock()
46            .map_err(|_| MockError::LockError)?;
47        Ok((f)(&mut mock))
48    })
49}
50
51/// Initialize the thread_local regmock object.
52///
53/// # Panics
54///
55/// This function will `panic!()` when not being able to initialize the `thread_local`
56/// [`Regmock`] object.
57pub fn init_regmock(mock: Arc<Mutex<Regmock>>) {
58    MOCK.with(|m| match m.set(mock) {
59        Ok(_) => {}
60        Err(e) => panic!("Failed to initialize thread_local Regmock with: {:?}", e),
61    })
62}
63
64/// Disable logging and execution of callbacks during the closure `f`.
65///
66/// # Panics
67///
68/// Will panic if the thread-local [`Regmock`] object can't be accessed.
69///
70/// # Examples
71///
72/// To perform a write to a register without logging, pass the write as closure to
73/// this function.
74///
75/// ```rust,ignore
76/// regmock_rs::silent(|| {
77///     let _ = pac::REGISTER.bitfield().read();
78/// });
79/// ```
80///
81/// This function can be called recursively and will restore state.
82/// ```rust,ignore
83/// regmock_rs::silent(|| unsafe {
84///     let val = pac::REGISTER.bitfield().read(); // no log, no callback
85///     regmock_rs::logging(true);
86///     let val2 = pac::REGISTER.bitfield().read(); // logged, no callback
87///     regmock_rs::silent(||{
88///         pac::REGISTER.bitfield().write(val); // no log, no callback
89///     });
90///     pac::REGISTER.bitfield().write(val2); // logged, no callback
91/// });
92/// ```
93pub fn silent<T>(f: impl FnOnce() -> T) -> T {
94    let prev_state = with_mock(|regmock| {
95        let state = (regmock.log_enabled, regmock.callback_enabled);
96        regmock.log_enabled = false;
97        regmock.callback_enabled = false;
98        state
99    })
100    .expect("Could not access regmock thread-local for silent access. Most likely you forgot to initialize regmock.");
101
102    let ret = f();
103
104    with_mock(|regmock| {
105        (regmock.log_enabled, regmock.callback_enabled) = prev_state;
106    })
107    .expect("Could not access regmock thread-local for silent access. Most likely your forgot to initialize regmock.");
108
109    ret
110}
111
112/// Enable/disable logging of register accesses in the `thread_local` MOCK object.
113///
114/// # Panics
115///
116/// Will panic if the thread-local [`Regmock`] object can't be accessed.
117pub fn logging(state: bool) {
118    with_mock(|mock| {
119        mock.log_enabled = state;
120    })
121    .expect("Could not access regmock thread-local for setting logging state. Most likely your forgot to initialize regmock.")
122}
123
124/// Block until specific register is being polled or timeout occurs.
125///
126/// `count` specifies the number of consecutive reads to a register that should
127/// be considered polling.
128/// If no timeout is given, a default of 5 seconds is used.
129///
130/// # Panics
131///
132/// Will panic if the thread-local [`Regmock`] object can't be accessed.
133pub fn wait_until_polled(
134    addr: usize,
135    count: usize,
136    timeout: Option<std::time::Duration>,
137) -> Result<(), String> {
138    let timeout = timeout.unwrap_or(Duration::from_secs(5));
139    let start = std::time::Instant::now();
140    loop {
141        match with_mock(|mock| mock.log.is_being_polled(addr, count)).expect("Could not access regmock thread-local for setting logging state. Most likely your forgot to initialize regmock.") {
142            true => return Ok(()),
143            false if start.elapsed() > timeout => return Err(format!(
144                        "Timed out waiting for 0x{:08X} to be polled. Last access: {:?}",
145                        addr,
146                        with_mock(|mock| mock.log.log.last().cloned())
147                    )),
148            _ => std::thread::yield_now(),
149        };
150    }
151}
152
153/// Enable/disable the execution of callbacks in the `thread_local` MOCK object.
154///
155/// # Panics
156///
157/// Will panic if the thead-local, [`Regmock`] object can't be accessed.
158pub fn callbacks(state: bool) {
159    with_mock(|mock| {
160        mock.callback_enabled = state;
161    })
162    .expect("Couldn't get regmock thread-local for setting callback state. Most likely your forgot to initialize regmock.")
163}
164
165/// Get the [`utils::RegmockLog`] form the `thread_local` MOCK object.
166///
167/// # Panics
168///
169/// Will panic of the thread-local [`Regmock`] object can't be accessed.
170pub fn logs() -> utils::RegmockLog {
171    with_mock(|mock| mock.get_logs()).expect("Coudn't get regmock thead-local for getting logs. Most likely your forgot to initialize regmock.")
172}
173
174/// Perform a read from the mocked registers.
175/// Register this function as the `READ_FN` in the `pacgen` PAC.
176///
177/// # Panics
178///
179/// Will panic if the thead-local, [`Regmock`] object can't be accessed.
180pub fn read_fn(reg: usize, len: usize) -> u64 {
181    with_mock(|mock| mock.read_volatile(reg, len)).unwrap_or_else(|e| {
182        panic!(
183            "Cound not `read_volatile(0x{:08X}, {:?})` due to: {:?}",
184            reg, len, e
185        )
186    })
187}
188
189/// Perform a write from the mocked registers.
190/// Register this function as the `WRITE_FN` in the `pacgen` PAC.
191///
192/// # Panics
193///
194/// This function calls `panic!()` if the `thead_local`, [`Regmock`] object
195/// cannot be accessed.
196pub fn write_fn(reg: usize, len: usize, value: u64) {
197    with_mock(|mock| mock.write_volatile(reg, len, value)).unwrap_or_else(|e| {
198        panic!(
199            "Cound not `write_volatile(reg: 0x{:08X}, len: {:?}, value: 0x{:08X})` due to: {:?}",
200            reg, len, value, e
201        )
202    })
203}
204
205/// Perform a write from the mocked registers.
206/// Register this function as the `WRITE_FN` in the `pacgen` PAC.
207///
208/// # Panics
209///
210/// This function calls `panic!()` if the `thead_local`, [`Regmock`] object
211/// cannot be accessed.
212#[cfg(feature = "aurix")]
213pub fn ldmst_fn(reg: usize, len: usize, value: u64) {
214    with_mock(|mock| mock.load_modify_store(reg, len, value)).unwrap_or_else(|e| {
215        panic!(
216            "Cound not `load_modify_store(reg: 0x{:08X}, value: 0x{:08X})` due to: {:?}",
217            reg, len, e
218        )
219    })
220}