soc_esp32::poll
Busy-wait on a hardware condition, with a bound, spelled once.
A dozen drivers wrote the same loop:
let mut spins = 0;while reg::read(r) & DONE == 0 { spins += 1; if spins > SPINS { return Err(..Timeout); } core::hint::spin_loop();}Individually fine; together, twelve places to get the polarity or the bound
wrong. [until] is that loop, once, returning [Timeout] the driver maps
into its own error.
Prefer the microsecond bound
Section titled “Prefer the microsecond bound”[until_us] bounds the wait by wall-clock time and is what a driver should
reach for. [until]’s max_spins is a raw loop count, so its real duration
depends on the CPU clock — the same number means one thing at 80 MHz and a
third of it at 240 MHz, and nothing at all on another core. It remains for
the loops that run before the clock is installed, or with the instruction
cache off (a flash erase), where the microsecond clock cannot be read.
The clock the microsecond bound reads is installed by the kernel
at boot: its timer is a Layer-1 driver a Layer-0 SoC crate may not depend on,
so the kernel hands the reader down rather than this crate reaching up for
it — the same shape as esp32_flash::set_interrupt_hooks.
Structs
Section titled “Structs”Deadline
Section titled “Deadline”struct Deadline { /* private fields */ }A microsecond deadline for a hand-rolled poll loop whose several exit
branches [until_us]’s single condition cannot express — an I2C wait that
must tell a NAK from an arbitration loss from a timeout, say.
Construct it once before the loop, then call expired
once per iteration where a spin counter used to be checked. Before the clock
is installed it falls back to the same [DEFAULT_SPINS] budget [until]
uses, so a loop that races boot still terminates.
Timeout
Section titled “Timeout”struct Timeout;A poll gave up before its condition held.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
Functions
Section titled “Functions”set_clock
Section titled “set_clock”fn set_clock(now_us: fn(..))Install the monotonic microsecond clock the [until_us] bound reads.
Called once from boot, before any driver polls. Passing a null-equivalent is
impossible: a fn pointer is never zero, so the “not installed” sentinel can
never collide with a real reader.
fn until<impl FnMut() -> bool>(ready: impl FnMut, max_spins: u32) -> Result<(), Timeout>Spin until ready() returns true, or max_spins iterations pass.
Returns [Timeout] on expiry. ready is polled at least once. Prefer
[until_us] unless the wait runs before the clock is installed or with the
cache off.
References: Timeout
until_us
Section titled “until_us”fn until_us<impl FnMut() -> bool>(timeout_us: u64, ready: impl FnMut) -> Result<(), Timeout>Spin until ready() returns true, or timeout_us microseconds pass — a
bound that means the same wall-clock time at any CPU frequency, unlike
[until].
ready is polled at least once. Before the kernel installs a clock (early
boot) it falls back to [until] with [DEFAULT_SPINS], so a poll that races
boot still terminates.
References: Timeout
Constants
Section titled “Constants”DEFAULT_SPINS
Section titled “DEFAULT_SPINS”const DEFAULT_SPINS: u32;A generous default iteration bound for [until]. A 12-bit ADC conversion or
a short frame is microseconds; this absorbs interrupts and still fails dead
hardware.
DEFAULT_TIMEOUT_US
Section titled “DEFAULT_TIMEOUT_US”const DEFAULT_TIMEOUT_US: u64;A generous default timeout for [until_us], in microseconds. Absorbs an
interrupt storm and still fails dead hardware quickly, at any CPU frequency.