Expand description
Waiting for hardware, bounded by the CLOCK.
§Why this exists
Every driver waits for a register: a reset bit to clear, a ready bit to set, a busy line to drop. Before this module each driver wrote that loop itself, and the copies disagreed:
- Some bounded the wait by an iteration count (
while ... { t += 1; if t > 1_000_000 ...). A count is not a duration (CLAUDE.md 26.6): a million register reads is a fraction of a second on one machine and several seconds on a board reaching its device across a bus. - The time-bounded ones each re-derived the conversion from
tsc_ticks_per_10ms, and disagreed about the one case that matters most: a machine whose clock the kernel could not calibrate (the rate reads 0). Some fell back to a poll count; others turned the zero into a one-tick budget and gave up after a single look.
This is the one loop, written once.
§The shapes
use godspeed::driver::wait::{self, Budget};
// A single condition: true when the wait is over.
if wait::until(ctx, Budget::ms(100), || m.read32(CTRL) & RESET == 0).is_err() {
ctx.log("mydev: the controller never left reset"); // the CALLER says which wait, in its words
}
// A loop with more than one way out (done, an error bit, or out of time):
let mut d = wait::Deadline::start(ctx, Budget::ms(500));
loop {
let i = m.read32(INT);
if i & DONE != 0 { break; }
if i & ERR != 0 { return Err("the device reported an error"); }
if d.expired() { return Err("the device never reported done"); }
}
// A wait measured in seconds, which should not hammer the device: the same, PACED.
let mut d = wait::Deadline::paced(ctx, Budget::ms(3_000), Budget::ms(1));
loop {
if ready() { break; }
if d.expired() { return Err("the function never reported ready"); }
d.pause();
}§What it will not do for you
It does not log. Expiry is returned, never swallowed (TimedOut is #[must_use] through
Result), and the caller reports it - because “the controller never left reset” and “the clock
never stabilised” are different faults, and only the driver knows which wait this was (26.7).
It sleeps only when asked to. until and Deadline::start poll: a wait measured in
microseconds cannot afford a scheduler quantum. A wait measured in seconds should not spend them
hammering the device, and until_paced and Deadline::paced sleep a PACE between looks.
The pace is the wait’s, not the caller’s, and that is the point of having it here: a caller sleeping
inside its own Deadline loop (as xhci and sdk/wifi did) leaves the uncalibrated count below
measuring looks it does not know are seconds apart.
On an uncalibrated machine the bound is a count. There is no other bound to have. A polling wait
gets UNCALIBRATED_POLLS looks, the figure the network drivers had already settled on. A paced
wait gets as many looks as its pace fits into its budget, because 200,000 looks with a sleep between
each is not a bound anyone meant; and since the kernel’s sleep is itself only approximate on such a
machine (sleep_ms floors to one scheduler quantum), that is a count of pauses, not a duration.
Either way the wait still ends, it just does not end on time. calibrated says which case this
machine is in, for a caller that wants to report it.
Structs§
- Budget
- How long a wait may take. Microsecond resolution, because some hardware gaps are that short.
- Deadline
- A running bound. Make it with
Deadline::start(polling) orDeadline::paced(sleeping), askDeadline::expiredonce per look, and callDeadline::pausebetween looks. - Since
- A moment to measure from, KEPT - what a
Deadlinecannot be, because it borrows the context it reads the clock through and so cannot outlive the call that made it. - Timed
Out - The wait ran out of budget before its condition held.
Constants§
- UNCALIBRATED_
POLLS - How many looks a wait gets when the kernel has no calibrated clock to measure it by.
Functions§
- calibrated
- Does this machine have a calibrated clock, so that a
Budgetis a real duration? - counter_
ms - The machine’s counter in milliseconds: a reading every task shares, that only goes forward, so two taken by two instances of one service - the second after the first died - are in order. For telling one instance’s events from the next one’s, not for a date, and not time since boot on every board (the counter’s origin is the board’s). 0 when the counter is uncalibrated, where it cannot be known.
- ticks
- The counter itself, in its own units.
- ticks_
per_ 10ms - How many
ticksmake 10 ms on this machine, or 0 if the kernel could not calibrate the counter. - until
- Wait until
condreturns true orbudgetruns out.Okcarries the microseconds it took (0 if the machine is uncalibrated or the condition already held). - until_
paced until, sleepingpacebetween looks: for a wait measured in seconds, where a look every moment would only hammer the device. SeeDeadline::paced.