Skip to main content

godspeed/driver/
wait.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Waiting for hardware, bounded by the CLOCK.
3//!
4//! # Why this exists
5//!
6//! Every driver waits for a register: a reset bit to clear, a ready bit to set, a busy line to drop.
7//! Before this module each driver wrote that loop itself, and the copies disagreed:
8//!
9//! - **Some bounded the wait by an iteration count** (`while ... { t += 1; if t > 1_000_000 ...`). A count
10//!   is not a duration (CLAUDE.md 26.6): a million register reads is a fraction of a second on one
11//!   machine and several seconds on a board reaching its device across a bus.
12//! - **The time-bounded ones each re-derived the conversion** from `tsc_ticks_per_10ms`, and disagreed
13//!   about the one case that matters most: a machine whose clock the kernel could not calibrate (the
14//!   rate reads 0). Some fell back to a poll count; others turned the zero into a one-tick budget and
15//!   gave up after a single look.
16//!
17//! This is the one loop, written once.
18//!
19//! # The shapes
20//!
21//! ```ignore
22//! use godspeed::driver::wait::{self, Budget};
23//!
24//! // A single condition: true when the wait is over.
25//! if wait::until(ctx, Budget::ms(100), || m.read32(CTRL) & RESET == 0).is_err() {
26//!     ctx.log("mydev: the controller never left reset");   // the CALLER says which wait, in its words
27//! }
28//!
29//! // A loop with more than one way out (done, an error bit, or out of time):
30//! let mut d = wait::Deadline::start(ctx, Budget::ms(500));
31//! loop {
32//!     let i = m.read32(INT);
33//!     if i & DONE != 0 { break; }
34//!     if i & ERR != 0 { return Err("the device reported an error"); }
35//!     if d.expired() { return Err("the device never reported done"); }
36//! }
37//!
38//! // A wait measured in seconds, which should not hammer the device: the same, PACED.
39//! let mut d = wait::Deadline::paced(ctx, Budget::ms(3_000), Budget::ms(1));
40//! loop {
41//!     if ready() { break; }
42//!     if d.expired() { return Err("the function never reported ready"); }
43//!     d.pause();
44//! }
45//! ```
46//!
47//! # What it will not do for you
48//!
49//! **It does not log.** Expiry is returned, never swallowed (`TimedOut` is `#[must_use]` through
50//! `Result`), and the caller reports it - because "the controller never left reset" and "the clock
51//! never stabilised" are different faults, and only the driver knows which wait this was (26.7).
52//!
53//! **It sleeps only when asked to.** [`until`] and [`Deadline::start`] poll: a wait measured in
54//! microseconds cannot afford a scheduler quantum. A wait measured in seconds should not spend them
55//! hammering the device, and [`until_paced`] and [`Deadline::paced`] sleep a PACE between looks.
56//!
57//! The pace is the wait's, not the caller's, and that is the point of having it here: a caller sleeping
58//! inside its own `Deadline` loop (as `xhci` and `sdk/wifi` did) leaves the uncalibrated count below
59//! measuring looks it does not know are seconds apart.
60//!
61//! **On an uncalibrated machine the bound is a count.** There is no other bound to have. A polling wait
62//! gets [`UNCALIBRATED_POLLS`] looks, the figure the network drivers had already settled on. A paced
63//! wait gets as many looks as its pace fits into its budget, because 200,000 looks with a sleep between
64//! each is not a bound anyone meant; and since the kernel's sleep is itself only approximate on such a
65//! machine (`sleep_ms` floors to one scheduler quantum), that is a count of pauses, not a duration.
66//! Either way the wait still ends, it just does not end on time. [`calibrated`] says which case this
67//! machine is in, for a caller that wants to report it.
68
69#[cfg(not(test))]
70use godspeed_sdk::service_context::ServiceContext;
71
72/// How many looks a wait gets when the kernel has no calibrated clock to measure it by.
73pub const UNCALIBRATED_POLLS: u32 = 200_000;
74
75/// How long a wait may take. Microsecond resolution, because some hardware gaps are that short.
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub struct Budget {
78    us: u64,
79}
80
81impl Budget {
82    pub const fn us(us: u64) -> Self {
83        Budget { us }
84    }
85    pub const fn ms(ms: u64) -> Self {
86        Budget { us: ms.saturating_mul(1000) }
87    }
88    pub const fn as_us(self) -> u64 {
89        self.us
90    }
91}
92
93/// The wait ran out of budget before its condition held.
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub struct TimedOut;
96
97/// Counter ticks in `us` microseconds at `per_10ms` ticks per 10 ms, never zero: a zero budget would
98/// expire before the condition was looked at even once.
99pub(crate) fn ticks_for(per_10ms: u64, us: u64) -> u64 {
100    (per_10ms.saturating_mul(us) / 10_000).max(1)
101}
102
103/// Looks an uncalibrated paced wait gets: as many paces as fit in the budget, rounded up, never zero.
104pub(crate) fn paced_looks(budget_us: u64, pace_us: u64) -> u32 {
105    let pace = pace_us.max(1);
106    let looks = budget_us / pace + (budget_us % pace != 0) as u64;
107    looks.clamp(1, u32::MAX as u64) as u32
108}
109
110/// Microseconds in `ticks` at `per_10ms` ticks per 10 ms. 0 when uncalibrated, which is "unknown".
111///
112/// Divided before it is multiplied, so it is exact for any `ticks`: multiplying first saturated at about
113/// 1.8e15 ticks, about 10 days on a 2 GHz TSC, and `counter_ms` then stopped advancing (`backlog/84` C1).
114/// The remainder's product cannot overflow, because the remainder is below `per_10ms` and no counter
115/// runs at 1.8e17 ticks per second.
116pub(crate) fn us_for(per_10ms: u64, ticks: u64) -> u64 {
117    if per_10ms == 0 {
118        return 0;
119    }
120    let whole = ticks / per_10ms;
121    let part = ticks % per_10ms;
122    whole.saturating_mul(10_000).saturating_add(part.saturating_mul(10_000) / per_10ms)
123}
124
125/// Does this machine have a calibrated clock, so that a [`Budget`] is a real duration?
126#[cfg(not(test))]
127pub fn calibrated(ctx: &ServiceContext) -> bool {
128    ctx.tsc_ticks_per_10ms() != 0
129}
130
131/// The counter itself, in its own units.
132///
133/// For code that MEASURES in counter ticks rather than waits on them: a benchmark that reports cycles,
134/// or a domain library that takes a timestamp (the SDK's key repeat, `godspeed_sdk::hid::KeyRepeat`).
135/// **A wait is never written on this.** A loop that compares two of these against a hand-converted
136/// budget is the copy [`Deadline`] exists to replace, and it disagrees with it on exactly the machine
137/// that matters - one whose clock is uncalibrated, where this still counts but [`ticks_per_10ms`] is 0.
138#[cfg(not(test))]
139pub fn ticks(ctx: &ServiceContext) -> u64 {
140    ctx.read_tsc()
141}
142
143/// The machine's counter in milliseconds: a reading every task shares, that only goes forward, so two taken
144/// by two instances of one service - the second after the first died - are in order. For telling one
145/// instance's events from the next one's, not for a date, and not time since boot on every board (the
146/// counter's origin is the board's). 0 when the counter is uncalibrated, where it cannot be known.
147#[cfg(not(test))]
148pub fn counter_ms(ctx: &ServiceContext) -> u64 {
149    us_for(ctx.tsc_ticks_per_10ms(), ctx.read_tsc()) / 1000
150}
151
152/// How many [`ticks`] make 10 ms on this machine, or 0 if the kernel could not calibrate the counter.
153///
154/// For converting a measurement in ticks into time for a report, and for configuring a domain library
155/// that counts in ticks. Zero is a real answer, not an error: say so rather than divide by it.
156#[cfg(not(test))]
157pub fn ticks_per_10ms(ctx: &ServiceContext) -> u64 {
158    ctx.tsc_ticks_per_10ms()
159}
160
161/// A running bound. Make it with [`Deadline::start`] (polling) or [`Deadline::paced`] (sleeping), ask
162/// [`Deadline::expired`] once per look, and call [`Deadline::pause`] between looks.
163#[cfg(not(test))]
164pub struct Deadline<'a> {
165    ctx: &'a ServiceContext,
166    start: u64,
167    /// The budget in counter ticks. Unused when uncalibrated.
168    ticks: u64,
169    /// Ticks per 10 ms, 0 when the kernel could not calibrate.
170    per_10ms: u64,
171    /// Looks remaining, counted only when uncalibrated.
172    polls_left: u32,
173    /// Milliseconds [`Deadline::pause`] sleeps; 0 for a polling deadline, whose pause is a spin hint.
174    pace_ms: u64,
175}
176
177#[cfg(not(test))]
178impl<'a> Deadline<'a> {
179    /// A polling deadline: [`Deadline::pause`] is only a spin hint. On an uncalibrated machine the
180    /// budget becomes [`UNCALIBRATED_POLLS`] looks.
181    pub fn start(ctx: &'a ServiceContext, budget: Budget) -> Self {
182        let per_10ms = ctx.tsc_ticks_per_10ms();
183        Deadline {
184            ctx,
185            start: ctx.read_tsc(),
186            ticks: ticks_for(per_10ms, budget.as_us()),
187            per_10ms,
188            polls_left: UNCALIBRATED_POLLS,
189            pace_ms: 0,
190        }
191    }
192
193    /// A deadline whose looks are `pace` apart: call [`Deadline::pause`] between them. The pace is in
194    /// whole milliseconds, the unit `sleep_ms` takes, and at least one. Only ARMv7 (the Pi 2) has a
195    /// sub-tick timer for a sleep; every other port ends one on its scheduler tick, so there a pace
196    /// shorter than a quantum (nominally 10 ms) lasts about one. On an
197    /// uncalibrated machine the budget becomes as many looks as the pace fits into it.
198    pub fn paced(ctx: &'a ServiceContext, budget: Budget, pace: Budget) -> Self {
199        let pace_ms = (pace.as_us() / 1000).max(1);
200        let mut d = Deadline::start(ctx, budget);
201        d.pace_ms = pace_ms;
202        d.polls_left = paced_looks(budget.as_us(), pace_ms.saturating_mul(1000));
203        d
204    }
205
206    /// Wait out one pace between looks: a sleep for a paced deadline, a spin hint for a polling one.
207    pub fn pause(&self) {
208        if self.pace_ms == 0 {
209            core::hint::spin_loop();
210        } else {
211            self.ctx.sleep_ms(self.pace_ms);
212        }
213    }
214
215    /// Has the budget run out? Call it once per look, AFTER checking the condition, so a condition
216    /// already true costs no clock read at all.
217    pub fn expired(&mut self) -> bool {
218        if self.per_10ms == 0 {
219            if self.polls_left == 0 {
220                return true;
221            }
222            self.polls_left -= 1;
223            return false;
224        }
225        // `wrapping_sub` so a counter that wraps mid-wait still measures the interval correctly.
226        self.ctx.read_tsc().wrapping_sub(self.start) >= self.ticks
227    }
228
229    /// Microseconds since the start, for a caller that reports how long a wait took. 0 when
230    /// uncalibrated, where it cannot be known.
231    pub fn elapsed_us(&self) -> u64 {
232        us_for(self.per_10ms, self.ctx.read_tsc().wrapping_sub(self.start))
233    }
234}
235
236/// A moment to measure from, KEPT - what a [`Deadline`] cannot be, because it borrows the context it reads
237/// the clock through and so cannot outlive the call that made it.
238///
239/// For a wait that is not a loop: something started now and asked about later, from a different call. A
240/// radio's sweep is the first - each channel is listened to for a dwell, and the serve loop asks the radio
241/// once per turn whether it is time to tune the next; and the same loop times how long it has gone
242/// without reading a joined radio, and how long a request took to serve.
243///
244/// **On an uncalibrated machine every budget has passed** and nothing has elapsed. There is no duration
245/// to compare with, and the alternative - nothing ever passes - turns a dwell into a hang; this way a
246/// sweep still runs, only too fast to hear everything. [`calibrated`] says which case this is.
247#[derive(Clone, Copy)]
248pub struct Since {
249    start: u64,
250    per_10ms: u64,
251}
252
253#[cfg(not(test))]
254impl Since {
255    /// Now.
256    pub fn now(ctx: &ServiceContext) -> Self {
257        Since { start: ctx.read_tsc(), per_10ms: ctx.tsc_ticks_per_10ms() }
258    }
259
260    /// Has `budget` passed since this moment?
261    pub fn passed(&self, ctx: &ServiceContext, budget: Budget) -> bool {
262        if self.per_10ms == 0 {
263            return true;
264        }
265        ctx.read_tsc().wrapping_sub(self.start) >= ticks_for(self.per_10ms, budget.as_us())
266    }
267
268    /// Microseconds since this moment; 0 when uncalibrated, where it cannot be known.
269    pub fn elapsed_us(&self, ctx: &ServiceContext) -> u64 {
270        us_for(self.per_10ms, ctx.read_tsc().wrapping_sub(self.start))
271    }
272
273    /// Milliseconds since this moment; 0 when uncalibrated, where it cannot be known.
274    pub fn elapsed_ms(&self, ctx: &ServiceContext) -> u64 {
275        self.elapsed_us(ctx) / 1000
276    }
277
278    /// Counter ticks since this moment - the unit a benchmark reports in. Counts even when the machine
279    /// is uncalibrated, because the counter runs whether or not anyone knows its rate.
280    pub fn elapsed_ticks(&self, ctx: &ServiceContext) -> u64 {
281        ctx.read_tsc().wrapping_sub(self.start)
282    }
283}
284
285/// Wait until `cond` returns true or `budget` runs out. `Ok` carries the microseconds it took (0 if
286/// the machine is uncalibrated or the condition already held).
287#[cfg(not(test))]
288pub fn until(ctx: &ServiceContext, budget: Budget, mut cond: impl FnMut() -> bool) -> Result<u64, TimedOut> {
289    if cond() {
290        return Ok(0);
291    }
292    let mut d = Deadline::start(ctx, budget);
293    loop {
294        if cond() {
295            return Ok(d.elapsed_us());
296        }
297        if d.expired() {
298            return Err(TimedOut);
299        }
300        core::hint::spin_loop();
301    }
302}
303
304/// [`until`], sleeping `pace` between looks: for a wait measured in seconds, where a look every
305/// moment would only hammer the device. See [`Deadline::paced`].
306#[cfg(not(test))]
307pub fn until_paced(
308    ctx: &ServiceContext,
309    budget: Budget,
310    pace: Budget,
311    mut cond: impl FnMut() -> bool,
312) -> Result<u64, TimedOut> {
313    if cond() {
314        return Ok(0);
315    }
316    let mut d = Deadline::paced(ctx, budget, pace);
317    loop {
318        d.pause();
319        if cond() {
320            return Ok(d.elapsed_us());
321        }
322        if d.expired() {
323            return Err(TimedOut);
324        }
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    #[test]
333    fn budget_units() {
334        assert_eq!(Budget::ms(3).as_us(), 3_000);
335        assert_eq!(Budget::us(7).as_us(), 7);
336        assert_eq!(Budget::ms(u64::MAX).as_us(), u64::MAX);
337    }
338
339    #[test]
340    fn ticks_are_never_zero() {
341        assert_eq!(ticks_for(0, 1_000), 1);
342        assert_eq!(ticks_for(540_000, 0), 1); // 54 MHz, no budget
343        assert_eq!(ticks_for(5_000, 1), 1); // 0.5 MHz: one microsecond is under a tick, and rounds up to one
344    }
345
346    #[test]
347    fn ticks_follow_the_calibration() {
348        // The Pi 4's generic timer: 54 MHz, 540_000 ticks per 10 ms.
349        assert_eq!(ticks_for(540_000, 10_000), 540_000);
350        assert_eq!(ticks_for(540_000, 500_000), 27_000_000);
351        // A ~2 GHz x86 TSC.
352        assert_eq!(ticks_for(20_000_000, 1_000), 2_000_000);
353        assert_eq!(ticks_for(u64::MAX, u64::MAX), u64::MAX / 10_000);
354    }
355
356    #[test]
357    fn paced_looks_fit_the_budget() {
358        assert_eq!(paced_looks(3_000_000, 1_000), 3_000); // 3 s at 1 ms
359        assert_eq!(paced_looks(1_000_000, 10_000), 100); // 1 s at 10 ms - the mmc core's CMD5 retry
360        assert_eq!(paced_looks(1_500, 1_000), 2); // a partial pace still gets its look
361        assert_eq!(paced_looks(0, 1_000), 1); // never zero: the condition is always looked at
362        assert_eq!(paced_looks(5, 0), 5); // a zero pace is read as one microsecond, not a division by zero
363        assert_eq!(paced_looks(u64::MAX, 1), u32::MAX);
364    }
365
366    #[test]
367    fn elapsed_round_trips() {
368        assert_eq!(us_for(540_000, ticks_for(540_000, 123_000)), 123_000);
369        assert_eq!(us_for(0, 999), 0);
370    }
371
372    #[test]
373    fn elapsed_does_not_stop_after_ten_days() {
374        // backlog/84 C1: a 2 GHz TSC at 2e15 ticks (about 11.6 days) is 1e12 us. Multiplying first
375        // saturated at about 1.8e15 ticks and froze `counter_ms` from then on.
376        assert_eq!(us_for(20_000_000, 2_000_000_000_000_000), 1_000_000_000_000);
377        assert!(us_for(20_000_000, 2_000_000_000_000_001) >= us_for(20_000_000, 2_000_000_000_000_000));
378        assert!(us_for(20_000_000, 4_000_000_000_000_000) > us_for(20_000_000, 2_000_000_000_000_000));
379        // The whole range: u64::MAX ticks is 9.2e15 us, not a saturated constant.
380        assert_eq!(us_for(20_000_000, u64::MAX), u64::MAX / 20_000_000 * 10_000 + (u64::MAX % 20_000_000) * 10_000 / 20_000_000);
381        // Exact below a whole 10 ms too.
382        assert_eq!(us_for(20_000_000, 19_999_999), 9_999);
383    }
384}