Skip to content

radio_esp32::adapter

Filling in [crate::osi::WifiOsiFuncs].

Each function here is a translation between FreeRTOS’s conventions and FlintOS’s, and the translations are where the bugs are. The three that matter most, written down once rather than at every call site:

Return codes are inverted. FreeRTOS returns pdTRUE (1) for success and pdFALSE (0) for failure. FlintOS returns bool. Getting this backwards makes every blocking call appear to time out, which looks like a dead radio rather than a wrong constant.

Priorities are inverted. FreeRTOS counts up to more urgent; FlintOS counts down. A blob task created at the number it asks for would run at the opposite end of the system. See [priority_from_freertos].

Timeouts are in ticks, not milliseconds. The blob speaks ticks and dynobj speaks milliseconds. OSI_FUNCS_TIME_BLOCKING maps to dynobj::FOREVER; anything else is converted.

The table is what Espressif made replaceable. Further down, under by_name, are the symbols the archives call directly instead — malloc, the logging hooks, the mesh stubs. That distinction is Espressif’s rather than a real one, and keeping the two in separate files is what let malloc end up implemented twice.

The blob holds void* handles it never dereferences. Each object is allocated on the radio heap and the pointer handed back, so the handle is the object. That keeps _delete honest — it frees what it was given — and means a stale handle is a use-after-free rather than an index into a table that has been reused, which is the easier of the two to catch under a debugger.

  • calls — How often the blob has used the entries its start path leans on.
struct Alloc {
pub kind: u8,
pub phase: u8,
pub size: u32,
pub ptr: usize,
pub freed: bool,
}

One allocation, as the driver asked for it and as it was answered.

Counts alone hide what matters: they are cumulative, so they cannot say what is still held, and they merge entries that the driver chose between deliberately. _malloc_internal and _wifi_malloc mean different things to the caller even where they mean the same thing to us, and which one was asked for is evidence about what the memory was for.

Bounded, and the overflow is counted rather than silently dropped: a trace that quietly stops is worse than one that says where it stopped.

Implements: Clone, Copy

struct ObjectId {
pub kind: &'static str,
pub nth: u32,
}

Implements: Clone, Copy, Eq, PartialEq, StructuralPartialEq

fn alloc_totals() -> (u32, u32, u32, u32, u32)

Recorded, still held, bytes still held, records lost to the table filling, and frees that matched nothing.

fn describe(addr: usize) -> Option<ObjectId>

What was created at addr, if the adapter made it.

References: ObjectId

fn for_each_live_alloc<impl FnMut(&Alloc)>(f: impl FnMut)

Every allocation still outstanding, oldest first.

fn forget_thread_semaphore(task: u32)

Release the per-task semaphore belonging to task, if it has one.

Called from [crate::tasks] when a blob task is deleted. A task FlintOS did not create for the blob never reaches here, which matches esp-idf: its destructor is a pthread key’s, and only pthreads have one.

fn ms_from_ticks(ticks: u32) -> u32

Blob ticks to FlintOS milliseconds.

The blob’s tick is esp-idf’s portTICK_PERIOD_MS, which is 1 ms in every configuration Espressif ships for the ESP32 — CONFIG_FREERTOS_HZ is 100 by default but the Wi-Fi driver requires 1000. Treating a tick as a millisecond is therefore exact rather than approximate, and this function exists so that assumption has one home if it ever stops being true.

fn mutex_unlock_failures() -> u32

How many unlocks have been refused. See MUTEX_UNLOCK_FAILED.

const fn priority_from_freertos(prio: u32) -> hal::types::Priority

FreeRTOS priority to FlintOS priority.

FreeRTOS counts up: 0 is idle and configMAX_PRIORITIES - 1 is the most urgent. FlintOS counts down: 0 is the most urgent. So the two are mirror images and a value passed straight through lands at the opposite end of the system — a Wi-Fi task that must pre-empt everything would instead run below the idle-adjacent background work, and the symptom is dropped frames under load rather than anything that points here.

Espressif creates its tasks between 18 and 23 out of 25. Those map to the urgent end here, which is what they are asking for.

References: Priority

fn report_unimplemented(t: &crate::osi::WifiOsiFuncs) -> usize

Log every entry the table still leaves null, by name.

The list this replaced was prose and went a year out of date. This is the same question asked of the table itself, and it is what turned the gap from something discovered one epc=0x00000000 at a time into a list printed before the blob is ever handed the table.

Returns the count, so a caller can decide whether to go on.

References: WifiOsiFuncs

fn set_alloc_phase(phase: u8)

Mark which stage of bring-up the records that follow belong to.

fn table() -> crate::osi::WifiOsiFuncs

Build the table the blob is given.

On the target this fills all 115. On a host it leaves the entries that need silicon null — see NEEDS_HARDWARE in the tests, which is the list that has to stay true.

References: WifiOsiFuncs

const ALLOC_NAMES: [&str; 7];
const A_CALLOC_INTERNAL: u8;
const A_MALLOC: u8;
const A_MALLOC_INTERNAL: u8;
const A_WIFI_CALLOC: u8;
const A_WIFI_MALLOC: u8;
const A_WIFI_ZALLOC: u8;
const A_ZALLOC_INTERNAL: u8;
const PD_FALSE: i32;

FreeRTOS pdFALSE.

const PD_TRUE: i32;

FreeRTOS pdTRUE.