Skip to content

radio_esp32::interrupts

_set_intr, _clear_intr and _set_isr — the blob’s route to an interrupt.

Three entries in wifi_osi_funcs_t, and between them the reason the radio cannot deliver a packet without this file. esp-idf implements them as intr_matrix_set and xt_set_interrupt_handler; FlintOS already has both halves — soc_esp32::intr_map for the crossbar and kernel::interrupt for the CPU-side mask and dispatch — so what is left is the shape mismatch between them.

The blob hands over void (*f)(void *arg) and expects its arg back. kernel::interrupt::register takes a plain fn(), which carries no data, and that is deliberate: a top-half runs in trap context where it cannot take a lock, so the kernel gives it nothing that would need one.

A closure cannot bridge that — fn() has no environment. What can is a distinct function per CPU interrupt, each knowing its own number at compile time. trampoline::<N> is a separate function item for every N, so TRAMPOLINES is 32 different fn() values, each of which looks up slot N and calls what the blob installed there.

That costs one static array and no allocation, and it keeps the kernel’s contract intact rather than widening register to take a *mut c_void that only this crate would ever use.

struct Route {
pub source: u8,
pub num: u8,
pub core: u8,
pub connected: bool,
}

A route the blob asked for, kept so it can be reported later.

Implements: Clone, Copy, Eq, PartialEq, StructuralPartialEq

unsafe fn clear_intr(_source: u32, _num: u32)

_clear_intr(source, num).

esp-idf’s own table leaves this pointing at a function that does nothing on the ESP32 — the crossbar entry is overwritten by the next intr_matrix_set rather than torn down, and the Wi-Fi driver never unroutes. Implemented as the same no-op, and named as one, because a version that did unroute would be a behaviour esp-idf does not have and the blob has never been tested against.

Does nothing. Called by the blob.

fn fires(n: usize) -> u32

How many times CPU interrupt n’s trampoline has run.

fn for_each_route<impl FnMut(Route)>(f: impl FnMut)

Call f with every interrupt route the blob has asked for.

The answer to “did the radio’s interrupt get connected, and to what?”, which is otherwise invisible — a scan that times out looks identical whether the MAC interrupt is misrouted, refused, or never requested. On a v4.4 station bring-up the expected answer is exactly one route: source 0 to a level-1 CPU input on the core the Wi-Fi task is pinned to.

fn installed_handler(n: usize) -> usize

_set_isr(n, f, arg) — install f as the handler for CPU interrupt n.

The handler the driver installed for CPU interrupt n, as an address.

Zero if none. The point of comparison: the driver’s own MAC handler is wDev_ProcessFiq, which links at a known address, and it is the only function in the blobs that reads or clears the receiver’s event word. If what we recorded is not that function, we wired up something else and every observation downstream of the interrupt is explained.

unsafe fn set_intr(cpu: i32, source: u32, num: u32, _prio: i32)

_set_intr(cpu, source, num, prio) — route peripheral source to CPU interrupt num and unmask it.

esp-idf’s intr_matrix_set. prio is accepted and ignored: on this chip the priority is a property of the CPU interrupt number, not something set alongside it, so the blob’s choice of num has already decided it. Saying so here beats silently taking an argument that cannot be honoured.

cpu is checked rather than ignored. The blob asks for the core it is running on, and routing a radio interrupt to the other core would deliver it somewhere the blob does not expect.

Writes the interrupt crossbar and unmasks a CPU input. Called by the blob.

unsafe fn set_isr(n: i32, f: *mut core::ffi::c_void, arg: *mut core::ffi::c_void)

esp-idf’s xt_set_interrupt_handler. Installing is separate from routing: the blob calls this first and _set_intr after, or the other way round, and neither order may lose the other’s work. So this only records the handler, and [set_intr] does the routing and unmasking.

f is called in trap context with arg. Called by the blob.