Skip to main content

Module wait

Module wait 

Source
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) or Deadline::paced (sleeping), ask Deadline::expired once per look, and call Deadline::pause between looks.
Since
A moment to measure from, KEPT - what a Deadline cannot be, because it borrows the context it reads the clock through and so cannot outlive the call that made it.
TimedOut
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 Budget is 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 ticks make 10 ms on this machine, or 0 if the kernel could not calibrate the counter.
until
Wait until cond returns true or budget runs out. Ok carries the microseconds it took (0 if the machine is uncalibrated or the condition already held).
until_paced
until, sleeping pace between looks: for a wait measured in seconds, where a look every moment would only hammer the device. See Deadline::paced.