Skip to content

radio_esp32::events

The driver’s events: a bounded queue and a task that drains it.

Why a queue, when a direct call is shorter

Section titled “Why a queue, when a direct call is shorter”

_event_post is called from inside the blob, on wifiT, part-way through whatever the driver was doing. The first implementation called the application’s handler right there. That is one function call instead of a copy, an allocation-free ring push and a context switch — and it is wrong, for a reason both references state rather than imply.

Zephyr’s event thread carries the comment:

Dispatch library-posted events on this dedicated task so a handler runs on its own stack and cannot stall or overrun the Wi-Fi library task. A single queue drained by one task preserves event order.

esp-idf does the same through esp_event’s loop task. Both then do the thing a synchronous handler cannot: they call back into esp_wifi_* from the handler. Zephyr drains esp_wifi_scan_get_ap_record inside its WIFI_EVENT_SCAN_DONE case. A handler invoked on wifiT cannot do that — it would re-enter the driver from the driver’s own task, behind the API lock that task already holds.

That is the whole reason this file exists, and it was found by comparing against the references rather than by debugging: esp_wifi_scan_get_ap_num did not return, and the shape of the call was a pattern neither reference uses.

event_data points at the driver’s own storage and is valid only for the duration of the call. A queue that stored the pointer would hand the handler freed memory some microseconds later — which is exactly the class of bug the OSI table pointer already produced once (see [crate::wifi::init]). So the payload is copied into the entry.

event_base is not copied: it is a const char * to a string literal in the blob’s .rodata, or to [crate::wifi::WIFI_EVENT], both of which outlive everything here. It is compared by pointer, which is what makes it an identity rather than a string.

fn dropped() -> u32

How many events have been refused because the queue was full.

Reported rather than silent: a dropped event is a scan result nobody collects or a disconnect nobody notices, and the only symptom is absence.

unsafe fn post(base: *const core::ffi::c_char, id: i32, data: *mut core::ffi::c_void, len: usize, _ticks: u32) -> i32

_event_post(base, id, data, len, ticks), and wifi_init_config_t’s event_handler.

Both routes land here. esp-idf’s esp_event_send_internal — the one the config field points at — calls esp_event_post, the OSI entry, and then forwards to the legacy loop; with no legacy loop to forward to, the two converge on one function.

Always returns ESP_OK, including when the queue is full. The driver treats a failed post as a fault worth retrying, and a full queue is a consumer that is behind, not an error the driver can do anything about. The drop is counted instead — see [dropped].

ticks is ignored. It is how long esp-idf would block waiting for room on the event queue, and blocking here would be blocking wifiT, which is the thing this file exists to stop.

base is a C string the blob owns. data is readable for len bytes. Called by the blob.

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

Install what the dispatch task calls. Returns the previous handler.

The handler runs on the dispatch task, on its own stack, with nothing of the driver’s held. It may call back into esp_wifi_*, which is the point — see the module docs.

References: EventHandler

fn start()

Start the dispatch task. Idempotent.

Called from [crate::wifi::init], for the reason ets_timer::start is: a service the adapter depends on should not be each application’s job to remember, and init is the only place that can guarantee “before the blob posts anything”.

const MAX_PAYLOAD: usize;

The largest event payload kept.

The biggest a station build posts is wifi_event_sta_disconnected_t: a 32-byte SSID, its length, a 6-byte BSSID, a reason and an RSSI — 45 bytes. 64 leaves room for the ones that grow without making the queue expensive: this is [QUEUE_LEN] times this many bytes of .bss, and nothing else.

A payload larger than this is truncated rather than dropped, and says so. The alternative — dropping — loses the event and the fact that it happened, and every event carries its id in the entry regardless.

const QUEUE_LEN: usize;

How many events may be waiting.

Eight. The driver posts one per state change, and the dispatch task runs above the application, so the queue is normally empty or holds one. Depth is here for the burst when a scan finishes and something else changes at the same time.

type EventHandler = fn(..);

What a handler is given.

base is an esp_event_base_t — compare it against [crate::wifi::WIFI_EVENT] by pointer. data points into the dispatch task’s own copy and is valid for the duration of the call only.