Expand description
Waiting for a device’s INTERRUPT - and for requests, on the same endpoint, without losing one.
§Why this exists
A driver that takes interrupts idles in one place: a timed receive on its endpoint. The kernel delivers the device’s interrupt there as a message (CLAUDE.md 12.2), and the driver’s clients send their requests there too, because a task owns exactly one endpoint. So every wake is one of three things - the interrupt, a request, or the deadline - and the driver must tell them apart.
xhci, ehci and dwc2 each wrote that out by hand, and the copies record what it cost:
- A request taken and dropped. The receive CONSUMES; it does not peek.
dwc2took a message, found no disk, and let it go - andblock-driver, blocked in request/reply, hung before printing a line, withfsbehind it. The samelet _ =once cost the keyboard its interrupts. - Any message counted as an interrupt.
xhciset its “waking on interrupts” flag on every wake, and with a disk attached disk requests set it, so a log line that read as proof of MSI proved only that something had arrived. - A zero deadline.
recv_timeout(0)blocks FOREVER, so a computed budget that rounds to zero turns a watchdog into a hang.
This is the one wait, written once, from those three - and since 2026-10-22 it is the wait of all three
of them, and of both audio drivers: every driver in services/ that takes an interrupt waits for it
here. A driver that drains its endpoint before it blocks takes what is queued with Irq::take, the
same sorting without the wait.
§The shape
use godspeed::driver::irq::{Irq, Woke};
use godspeed::driver::wait::Budget;
let irq = Irq::granted(ctx); // None-vector is honest: no interrupt was routed
loop {
match irq.wait(ctx, Budget::ms(150)) {
Woke::Interrupt => { /* read and clear the device's status; then */ irq.rearm(ctx); }
Woke::Request(msg) => serve(ctx, &msg), // SERVED, never dropped
Woke::Timeout => { /* the watchdog: look anyway, an interrupt can be lost */ }
}
do_the_work();
}With no interrupt the same loop is a paced poll that still serves requests, so a driver keeps
one loop rather than an interrupt loop and a polling loop that drift apart. Irq::routed says
which case it is in, for a driver that should say so (invariant 12: a driver that wanted an
interrupt and polls must not do it silently).
§What it will not do for you
It does not read the device. What raised the interrupt, and how to clear it, are the device’s;
a driver that does not clear its source before Irq::rearm gets the interrupt again at once.
It does not log, for the reason wait does not: only the driver knows what a timeout means.
It cannot tell a one-byte request equal to the vector from an interrupt. The kernel’s notification is a one-byte payload naming the vector, and that is the only mark it carries - the convention all three drivers already relied on. A protocol whose requests are longer than one byte, as every protocol in the tree is, never meets it.
Structs§
- Irq
- The interrupt this driver was granted, if any, and how many it has seen.
Enums§
- Woke
- What ended a wait.