Skip to content

kernel::alarm

A one-shot alarm at an absolute microsecond deadline.

[crate::clock] answers “what time is it”. This answers “wake me at”. They are different questions and want different hardware: a free-running counter for the first, a programmable compare and an interrupt for the second.

The radio blobs. Espressif’s Wi-Fi driver arms ETSTimers in microseconds — channel dwell, frame timing, retry windows — and gets them back through five entries in wifi_osi_funcs_t. The first implementation of those served them from a task that woke every scheduler tick and polled, which made a 100 µs timer fire up to ten times late. All three references use an alarm instead: esp-idf’s esp_timer programs the LAC timer’s compare register and dispatches from its interrupt, NuttX reaches the same code, and Arduino ships esp-idf unchanged.

TG0’s LAC counter. It is a fifth counter inside timer group 0, separate from the four general-purpose timers — which matters, because all four are spoken for: TIMG1/T1 is [crate::clock], and TIMG0/T0, TIMG0/T1 and TIMG1/T0 drive on-target self-tests. esp-idf picked the same counter for the same reason.

It lives here rather than in the crate that wanted it because of the layer rules, exactly as [crate::clock] does: radio/* may name kernel, hal, soc/* and lib/* but not drivers/physical/*, so the radio adapter cannot reach TIMG at all.

[set_handler] takes a fn() and calls it from the interrupt. It must not block and must not take a lock a task could be holding — the usual top-half contract. The radio’s use of it is to signal a service task, which is what esp-idf’s timer_alarm_isr does too: it clears the interrupt and notifies.

The handler is registered as IRAM-safe, so it keeps running while a flash operation has the cache off. That is a promise about the handler, not something this module can check, and it is why the handler is a fn() rather than a closure: whatever is behind it has to be in IRAM.

fn cancel()

Cancel a pending alarm. The counter keeps running.

fn is_available() -> bool

Whether init succeeded.

fn set_after_us(after_us: u64)

Fire the alarm after_us microseconds from now.

Replaces any alarm already set — there is one compare register, so this is a single deadline rather than a queue. The caller keeps the queue and arms the earliest, which is what radio_esp32::ets_timer does.

A delay of zero fires as soon as the hardware can, not never.

fn set_handler(f: Option<fn(..)>) -> Option<fn(..)>

Install what the alarm calls. Returns the previous handler.

The handler runs in trap context. See the module docs.

fn wait(timeout_ms: u32) -> bool

Block until the alarm fires or timeout_ms passes. true if woken.

The timeout is a backstop, not the mechanism: an alarm that is missed makes the caller late rather than never, and on a host — where there is no LAC timer — it is the only thing that returns at all.

One waiter. A second task calling this would be woken by the same alarm and there is nothing here to say which; the intended caller is a single service task.