1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
//! A test that verifies the correctness of the [`TimerQueue`].
//!
//! To run this test, you need to activate the `critical-section/std` feature.
use std::{fmt::Debug, time::Duration};
use parking_lot::Mutex;
use rtic_time::{Monotonic, TimerQueue};
use tokio::sync::watch;
static START: Mutex<Option<std::time::Instant>> = Mutex::new(None);
pub struct StdTokioMono;
// An instant that "starts" at Duration::ZERO, so we can
// have a zero value.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
pub struct Instant(std::time::Duration);
impl Instant {
pub fn init() {
assert!(START.lock().is_none());
let _ = START.lock().insert(std::time::Instant::now());
}
pub fn now() -> Self {
let start = channel_read("Instant start not initialized", &START);
Self(start.elapsed())
}
}
impl core::ops::Add<Duration> for Instant {
type Output = Instant;
fn add(self, rhs: Duration) -> Self::Output {
Self(self.0 + rhs)
}
}
impl core::ops::Sub<Duration> for Instant {
type Output = Instant;
fn sub(self, rhs: Duration) -> Self::Output {
Self(self.0 - rhs)
}
}
impl core::ops::Sub<Instant> for Instant {
type Output = Duration;
fn sub(self, rhs: Instant) -> Self::Output {
self.0 - rhs.0
}
}
fn channel_read<T: Clone>(msg: &str, channel: &Mutex<Option<T>>) -> T {
channel.lock().as_ref().expect(msg).clone()
}
fn event_write<T: Debug>(msg: &str, channel: &Mutex<Option<watch::Sender<T>>>, value: T) {
channel.lock().as_ref().expect(msg).send(value).unwrap()
}
static COMPARE_RX: Mutex<Option<watch::Receiver<Instant>>> = Mutex::new(None);
static COMPARE_TX: Mutex<Option<watch::Sender<Instant>>> = Mutex::new(None);
static INTERRUPT_RX: Mutex<Option<watch::Receiver<()>>> = Mutex::new(None);
static INTERRUPT_TX: Mutex<Option<watch::Sender<()>>> = Mutex::new(None);
impl StdTokioMono {
/// Initialize the monotonic.
///
/// Returns a [`watch::Sender`] that will cause the interrupt
/// & compare-change tasks to exit if a value is sent to it or it
/// is dropped.
#[must_use = "Dropping the returned Sender stops interrupts & compare-change events"]
pub fn init() -> watch::Sender<()> {
Instant::init();
let (compare_tx, compare_rx) = watch::channel(Instant(Duration::ZERO));
let (irq_tx, irq_rx) = watch::channel(());
assert!(COMPARE_RX.lock().is_none());
assert!(COMPARE_TX.lock().is_none());
let _ = COMPARE_RX.lock().insert(compare_rx);
let _ = COMPARE_TX.lock().insert(compare_tx);
assert!(INTERRUPT_RX.lock().is_none());
assert!(INTERRUPT_TX.lock().is_none());
let _ = INTERRUPT_RX.lock().insert(irq_rx);
let _ = INTERRUPT_TX.lock().insert(irq_tx);
Self::queue().initialize(Self);
let (killer_tx, mut killer_rx) = watch::channel(());
let mut killer_clone = killer_rx.clone();
// Set up a task that watches for changes to the COMPARE value,
// and re-starts a timeout based on that change
tokio::spawn(async move {
let mut compare_rx = channel_read("Compare RX not initialized", &COMPARE_RX);
loop {
let compare = compare_rx.borrow().clone();
let end = channel_read("Start not initialized", &START) + compare.0;
tokio::select! {
_ = killer_clone.changed() => break,
_ = compare_rx.changed() => {},
_ = tokio::time::sleep_until(end.into()) => {
event_write("Interrupt TX not initialized", &INTERRUPT_TX, ());
// Sleep for a bit to avoid re-firing the interrupt a bunch of
// times.
tokio::time::sleep(Duration::from_millis(1)).await;
},
}
}
});
// Set up a task that emulates an interrupt handler, calling `on_monotonic_interrupt`
// whenever an "interrupt" is generated.
tokio::spawn(async move {
let mut interrupt_rx = channel_read("Interrupt RX not initialized.", &INTERRUPT_RX);
loop {
tokio::select! {
_ = killer_rx.changed() => break,
_ = interrupt_rx.changed() => {
// TODO: verify that we get interrupts triggered by an
// explicit pend or due to COMPARE at the correct time.
}
}
unsafe {
StdTokioMono::queue().on_monotonic_interrupt();
}
}
});
killer_tx
}
/// Used to access the underlying timer queue
pub fn queue() -> &'static TimerQueue<StdTokioMono> {
&TIMER_QUEUE
}
}
impl Monotonic for StdTokioMono {
const ZERO: Self::Instant = Instant(Duration::ZERO);
type Instant = Instant;
type Duration = Duration;
fn now() -> Self::Instant {
Instant::now()
}
fn set_compare(instant: Self::Instant) {
// TODO: verify that we receive the correct amount & values
// for `set_compare`.
log::info!("Setting compare to {} ms", instant.0.as_millis());
event_write("Compare TX not initialized", &COMPARE_TX, instant);
}
fn clear_compare_flag() {}
fn pend_interrupt() {
event_write("Interrupt TX not initialized", &INTERRUPT_TX, ());
}
}
static TIMER_QUEUE: TimerQueue<StdTokioMono> = TimerQueue::new();
#[tokio::test]
async fn main() {
pretty_env_logger::init();
let _interrupt_killer = StdTokioMono::init();
let start = std::time::Instant::now();
let build_delay_test = |threshold: u128, pre_delay: Option<u64>, delay: u64| {
let delay = Duration::from_millis(delay);
let pre_delay = pre_delay.map(Duration::from_millis);
let total = if let Some(pre_delay) = pre_delay {
pre_delay + delay
} else {
delay
};
let total_millis = total.as_millis();
async move {
if let Some(pre_delay) = pre_delay {
tokio::time::sleep_until((start + pre_delay).into()).await;
}
StdTokioMono::queue().delay(delay).await;
let elapsed = start.elapsed().as_millis();
log::info!("{total_millis} ms delay reached (after {elapsed} ms)");
if elapsed > total_millis.saturating_add(threshold)
|| elapsed < total_millis.saturating_sub(threshold)
{
panic!("{total_millis} ms delay was not on time ({elapsed} ms passed instead)");
}
}
};
// TODO: depending on the precision of the delays that can be used, this threshold
// may have to be altered a bit.
const TIME_THRESHOLD_MS: u128 = 5;
let sec1 = build_delay_test(TIME_THRESHOLD_MS, Some(100), 100);
let sec2 = build_delay_test(TIME_THRESHOLD_MS, None, 300);
let sec3 = build_delay_test(TIME_THRESHOLD_MS, None, 400);
tokio::join!(sec2, sec1, sec3);
}
|