Skip to content

kernel::heap

The radio heap: which memory it gets, and which flavour it hands back.

[heap] is the allocator; this is the part that knows the ESP32’s map. It exists for the radio blobs and nothing else — see the ground rule in doc/plan-radio.md. The kernel remains statically allocated, so bounded latency stays a property of the kernel rather than a hope about the blob.

Wi-Fi wants roughly 50 KB and nothing that size fits below the 0x3FFDC200 bound — the static map leaves a few kilobytes spare. The way out is the memory the linker script already describes as reclaimable: the ROM keeps its data and stack above that bound during boot and has no claim on it afterwards.

So the regions below are added at runtime, once, after boot. Nothing is placed there at link time and the static map does not move.

An earlier version of this module split the memory in two, on the belief that the DMA engines could not reach SRAM1. They can, and esp-idf says so plainly:

#define SOC_DMA_LOW 0x3FFAE000
#define SOC_DMA_HIGH 0x40000000
inline static bool IRAM_ATTR esp_ptr_dma_capable(const void *p)
{
return (intptr_t)p >= SOC_DMA_LOW && (intptr_t)p < SOC_DMA_HIGH;
}

Its heap agrees — the SRAM1 regions are type 1, whose capability list is MALLOC_CAP_DMA|MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL|MALLOC_CAP_DEFAULT — and NuttX puts ordinary heap regions at 0x3ffe0450 onward. The belief came from a comment in this project’s own linker script and was simply wrong. The corrected window is supplied by the selected SoC.

So there is one pool and every byte of it can back a DMA descriptor. [Caps] survives because the blob’s allocator API asks in those terms and the adapter has to answer, not because the two mean different memory here.

No #[global_allocator]. Registering one would put alloc:: in reach of every crate in the tree and quietly end the no-allocation property. Callers name this module.

enum Caps {
Internal,
Dma,
}

What a caller needs from the memory, beyond its size.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq

unsafe fn alloc(size: usize, align: usize) -> *mut u8

Allocate from the pool.

align must be a power of two. The returned pointer must be released with [free].

unsafe fn alloc_caps(size: usize, align: usize, caps: Caps) -> *mut u8

Allocate for the given capability.

As [alloc].

References: Caps

unsafe fn alloc_dma(size: usize, align: usize) -> *mut u8

Allocate memory a DMA engine can reach.

As [alloc].

unsafe fn free(ptr: *mut u8, caps: Caps)

Return an allocation to the pool.

ptr must have come from this module and not been freed already.

References: Caps

fn free_bytes(caps: Caps) -> usize

Free bytes in the pool.

The sum of the free blocks, which is what get_free_heap_size means. It is not a promise that a request of this size will succeed — see [largest_free_block].

References: Caps

unsafe fn init(free_from: u32) -> usize

Take the reclaimable memory and make it allocatable.

Call once, after boot, before the radio starts. Returns the bytes taken.

The ROM must be finished with its boot-time data — that is, this must not run during early startup — and nothing else may be using these regions. free_from is the first free address above the static map, which the linker script provides as _dma_pool_end.

fn is_dma_capable(ptr: *const u8) -> bool

Whether an address is one a DMA engine can reach.

Delegates to the SoC crate, so there is one definition of the window rather than a copy here that can drift away from it. This used to say exactly that while the host arm of a cfg held a second copy of the range – the drift the comment warned about, already present. soc-esp32 is an unconditional dependency now, so both builds ask the same function.

fn largest_free_block(caps: Caps) -> usize

The largest allocation that can currently succeed, ignoring the header.

References: Caps

unsafe fn realloc(ptr: *mut u8, size: usize, align: usize) -> *mut u8

Grow or shrink an allocation, preserving its contents.

The radio blobs’ _realloc_internal and _wifi_realloc. See [heap::Heap::realloc] for the two C edges it honours — a failed call leaves the original valid, and a size of zero frees.

ptr is null or from this module and not yet freed. align must be a power of two.

fn total_bytes(caps: Caps) -> usize

Total bytes under management.

References: Caps