Skip to content

radio_esp32::wifi

esp_wifi_init_internal — handing the Wi-Fi driver its configuration.

Step 5.1. The first call into the Wi-Fi half of the blobs, as [crate::phy] was the first into the RF half.

doc/plan-radio.md warns at step 3.4 that a wrong IDF version shows up “as a magic-number mismatch if you are lucky, and as a working radio that corrupts memory if you are not”. [WifiInitConfig] is where that happens: the blob reads 200 bytes at whatever layout it was compiled against, and nothing checks the middle of it. magic is last precisely so that a struct which is the wrong size fails the magic test — but a struct that is the right size with a field in the wrong place passes.

So the layout is asserted rather than described. The const blocks below pin the size of both structs and the offset of every field whose position is load-bearing, against the numbers in esp_wifi.h and esp_wifi_crypto_types.h at v4.4. A change to either fails the build here instead of on the air.

[WpaCryptoFuncs] is all-null except its self-describing size and version. esp-idf fills it from libwpa_supplicant, which is C source and not one of the blobs, so every entry is something FlintOS would have to provide: AES, SHA-1, SHA-256, HMAC, PBKDF2, RC4, CCMP, GMAC.

That is WPA2’s problem, not init’s and not an open scan’s — which is why step 5.2 (scan) comes before 5.3 (associate) here rather than in issue order. A null table is the honest encoding of “no crypto yet”: the blob calls through it only when it needs to authenticate, and a null pointer there is a fault at a known address rather than a silently wrong key.

  • event — wifi_event_t, from esp_wifi_types.h at v4.4. Only the ones a station
  • mode — wifi_mode_t. The driver refuses most calls until a mode is set.
struct EventBase(*const core::ffi::c_char);

An esp_event_base_t, which is a const char * and therefore not Sync on its own. It is written once, at link time, and never again.

Implements: Sync

struct ScanDone {
pub status: u32,
pub number: u8,
pub scan_id: u8,
}

wifi_event_sta_scan_done_t, the payload of [event::SCAN_DONE].

Implements: Clone, Copy, Debug

struct WifiInitConfig {
pub event_handler: Option<SystemEventHandler>,
pub osi_funcs: *const crate::osi::WifiOsiFuncs,
pub wpa_crypto_funcs: WpaCryptoFuncs,
pub static_rx_buf_num: i32,
pub dynamic_rx_buf_num: i32,
pub tx_buf_type: i32,
pub static_tx_buf_num: i32,
pub dynamic_tx_buf_num: i32,
pub cache_tx_buf_num: i32,
pub csi_enable: i32,
pub ampdu_rx_enable: i32,
pub ampdu_tx_enable: i32,
pub amsdu_tx_enable: i32,
pub nvs_enable: i32,
pub nano_enable: i32,
pub rx_ba_win: i32,
pub wifi_task_core_id: i32,
pub beacon_max_len: i32,
pub mgmt_sbuf_num: i32,
pub feature_caps: u64,
pub sta_disconnected_pm: bool,
pub magic: i32,
}

wifi_init_config_t, field for field and in order.

The order is the header’s and may not be tidied. magic is last because the header says so — “it should be the last field” — and that is the only thing standing between a version mismatch and a radio that runs on misaligned configuration.

Implements: Clone, Copy, Debug

References: SystemEventHandler, WifiOsiFuncs, WpaCryptoFuncs

struct WpaCryptoFuncs {
pub size: u32,
pub version: u32,
pub aes_wrap: Option<fn(..)>,
pub aes_unwrap: Option<fn(..)>,
pub hmac_sha256_vector: Option<fn(..)>,
pub sha256_prf: Option<fn(..)>,
pub hmac_md5: Option<fn(..)>,
pub hmac_md5_vector: Option<fn(..)>,
pub hmac_sha1: Option<fn(..)>,
pub hmac_sha1_vector: Option<fn(..)>,
pub sha1_prf: Option<fn(..)>,
pub sha1_vector: Option<fn(..)>,
pub pbkdf2_sha1: Option<fn(..)>,
pub rc4_skip: Option<fn(..)>,
pub md5_vector: Option<fn(..)>,
pub aes_encrypt: Option<fn(..)>,
pub aes_encrypt_init: Option<fn(..)>,
pub aes_encrypt_deinit: Option<fn(..)>,
pub aes_decrypt: Option<fn(..)>,
pub aes_decrypt_init: Option<fn(..)>,
pub aes_decrypt_deinit: Option<fn(..)>,
pub aes_128_encrypt: Option<fn(..)>,
pub aes_128_decrypt: Option<fn(..)>,
pub omac1_aes_128: Option<fn(..)>,
pub ccmp_decrypt: Option<fn(..)>,
pub ccmp_encrypt: Option<fn(..)>,
pub aes_gmac: Option<fn(..)>,
}

wpa_crypto_funcs_t. Twenty-five function pointers behind two words.

Embedded by value in [WifiInitConfig], not behind a pointer, so its size is part of that struct’s layout and every field after it moves if this one is wrong.

Implements: Clone, Copy, Debug

unsafe fn init() -> i32

Bring the Wi-Fi driver up.

Allocates the driver’s control structures and buffers out of [kernel::heap] through the OSI table, so that must be initialised first — and [crate::nvs] too, since nvs_enable is 1.

Returns the blob’s esp_err_t unchanged. Zero is ESP_OK; everything else is Espressif’s, and is more useful reported than translated.

Calls into the blob, which will call back out through the OSI table on the same thread. Call once, and not from two cores: it writes OSI_TABLE, whose soundness rests on there being exactly one writer before any reader.

unsafe fn set_mode(m: u32) -> i32

esp_wifi_set_mode. One of [mode].

Calls into the blob. [init] must have succeeded.

unsafe fn start() -> i32

esp_wifi_start. Starts the driver in whatever mode was set.

Does not return with the radio ready. The driver posts [event::STA_START] when station mode is actually up, and a scan requested before that returns ESP_ERR_WIFI_NOT_STARTED. In esp-idf the start is synchronous enough in practice that most examples ignore this; it is called out because the failure is intermittent and reads like a scan bug.

Calls into the blob. A mode must have been set.

unsafe fn stop() -> i32

esp_wifi_stop.

Calls into the blob.

const INIT_CONFIG_LEN: usize;

sizeof(wifi_init_config_t).

Two pointers, the crypto table, sixteen ints, then a uint64_t which forces four bytes of padding before it, a bool, three more bytes of padding, and the magic.

const INIT_CONFIG_MAGIC: i32;

WIFI_INIT_CONFIG_MAGIC from esp_wifi.h. Last field of the struct, so a size mismatch lands somewhere else and fails this check.

const WPA_CRYPTO_FUNCS_LEN: usize;

The size esp_wifi_crypto_types.h gives it: two uint32_t and twenty-five pointers.

type SystemEventHandler = fn(..);

system_event_handler_t, v4.4’s, which is not what the name suggests.

The obvious reading of “system_event_handler_t” is a handler taking a system_event_t * — the legacy tagged union — and that reading was written into this file and was wrong. The header settles it:

typedef esp_err_t (*system_event_handler_t)(esp_event_base_t event_base,
int32_t event_id,
void* event_data,
size_t event_data_size,
TickType_t ticks_to_wait);

The same five arguments as _event_post. A one-argument stub sat here and linked, ran, and returned ESP_OK for every event the driver raised — because on the windowed ABI a callee may read fewer arguments than it was passed, so the wrong signature is invisible until you ask what happened to the events.