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.
Reclaimed, not placed
Section titled “Reclaimed, not placed”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.
All of it is DMA-capable
Section titled “All of it is DMA-capable”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.
What is not here
Section titled “What is not 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
Functions
Section titled “Functions”unsafe fn alloc(size: usize, align: usize) -> *mut u8Allocate from the pool.
Safety
Section titled “Safety”align must be a power of two. The returned pointer must be released with
[free].
alloc_caps
Section titled “alloc_caps”unsafe fn alloc_caps(size: usize, align: usize, caps: Caps) -> *mut u8Allocate for the given capability.
Safety
Section titled “Safety”As [alloc].
References: Caps
alloc_dma
Section titled “alloc_dma”unsafe fn alloc_dma(size: usize, align: usize) -> *mut u8Allocate memory a DMA engine can reach.
Safety
Section titled “Safety”As [alloc].
unsafe fn free(ptr: *mut u8, caps: Caps)Return an allocation to the pool.
Safety
Section titled “Safety”ptr must have come from this module and not been freed already.
References: Caps
free_bytes
Section titled “free_bytes”fn free_bytes(caps: Caps) -> usizeFree 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) -> usizeTake the reclaimable memory and make it allocatable.
Call once, after boot, before the radio starts. Returns the bytes taken.
Safety
Section titled “Safety”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.
is_dma_capable
Section titled “is_dma_capable”fn is_dma_capable(ptr: *const u8) -> boolWhether 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.
largest_free_block
Section titled “largest_free_block”fn largest_free_block(caps: Caps) -> usizeThe largest allocation that can currently succeed, ignoring the header.
References: Caps
realloc
Section titled “realloc”unsafe fn realloc(ptr: *mut u8, size: usize, align: usize) -> *mut u8Grow 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.
Safety
Section titled “Safety”ptr is null or from this module and not yet freed. align must be a
power of two.
total_bytes
Section titled “total_bytes”fn total_bytes(caps: Caps) -> usizeTotal bytes under management.
References: Caps