chore: migrate project into clean repository

This commit is contained in:
yuuux
2026-08-13 16:50:52 +08:00
commit d1d25a09e7
27405 changed files with 9422808 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
if(CONFIG_MODULE_HEAP)
set(TOP_PATH ${CMAKE_HOME_DIRECTORY})
set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR})
listenai_library_named(heap)
set(LIB_SRCS "heap_caps_base.c"
"heap_caps.c"
"heap_caps_init.c"
"multi_heap.c"
"tlsf/tlsf.c"
"port/arcs.c"
)
if(CONFIG_HEAP_TASK_TRACKING)
listenai_library_sources("heap_task_info.c")
endif()
if(NOT CONFIG_HEAP_POISONING_DISABLED)
listenai_library_sources("multi_heap_poisoning.c")
endif()
if(CONFIG_HEAP_TRACING_STANDALONE)
listenai_library_sources("heap_trace_standalone.c")
set_source_files_properties(heap_trace_standalone.c
PROPERTIES COMPILE_FLAGS -Wno-frame-address)
endif()
listenai_library_sources(${LIB_SRCS})
listenai_include_directories(
${LIB_PATH}/include
${LIB_PATH}/include/idf
${LIB_PATH}/include/soc
${LIB_PATH}
${LIB_PATH}/tlsf
${LIB_PATH}/tlsf/include
${TOP_PATH}/include
${TOP_PATH}/shell
${TOP_PATH}/rtos/include
${TOP_PATH}/modules/arcs-hal/chip/arcs/include
${TOP_PATH}/modules/arcs-hal/chip/arcs/include/register
# ${TOP_PATH}/modules/arcs-hal/chip/arcs/bsp
# ${TOP_PATH}/modules/arcs-hal/include/bsp
${TOP_PATH}/modules/arcs-hal/include/NMSIS/Core/Include
)
endif()

View File

@@ -0,0 +1,112 @@
menuconfig MODULE_HEAP
bool "Heap Manager Module"
default y
if MODULE_HEAP
config HEAP_NAME
string "Module Name"
default "heap"
choice HEAP_CORRUPTION_DETECTION
prompt "Heap corruption detection"
default HEAP_POISONING_DISABLED
help
Enable heap poisoning features to detect heap corruption caused by out-of-bounds access to heap memory.
See the "Heap Memory Debugging" page of the IDF documentation
for a description of each level of heap corruption detection.
config HEAP_POISONING_DISABLED
bool "Basic (no poisoning)"
config HEAP_POISONING_LIGHT
bool "Light impact"
config HEAP_POISONING_COMPREHENSIVE
bool "Comprehensive"
endchoice
choice HEAP_TRACING_DEST
bool "Heap tracing"
default HEAP_TRACING_OFF
help
Enables the heap tracing API defined in esp_heap_trace.h.
This function causes a moderate increase in IRAM code side and a minor increase in heap function
(malloc/free/realloc) CPU overhead, even when the tracing feature is not used.
So it's best to keep it disabled unless tracing is being used.
config HEAP_TRACING_OFF
bool "Disabled"
config HEAP_TRACING_STANDALONE
bool "Standalone"
select HEAP_TRACING
config HEAP_TRACING_TOHOST
bool "Host-based"
select HEAP_TRACING
endchoice
config HEAP_TRACING
bool
default n
help
Enables/disables heap tracing API.
config HEAP_TRACE_HASH_MAP
bool "Use hash map mechanism to access heap trace records"
depends on HEAP_TRACING_STANDALONE
default n
help
Enable this flag to use a hash map to increase performance in handling
heap trace records.
Heap trace standalone supports storing records as a list, or a list + hash map.
Using only a list takes less memory, but calls to 'free' will get slower as the
list grows. This is particularly affected when using HEAP_TRACE_ALL mode.
By using a list + hash map, calls to 'free' remain fast, at the cost of
additional memory to store the hash map.
config HEAP_TRACE_HASH_MAP_IN_EXT_RAM
bool "Place hash map in external RAM"
depends on HEAP_TRACE_HASH_MAP
default n
help
When enabled this configuration forces the hash map to be placed in external RAM.
config HEAP_TRACE_HASH_MAP_SIZE
int "The number of entries in the hash map"
depends on HEAP_TRACE_HASH_MAP
default 512
help
Defines the number of entries in the heap trace hashmap. Each entry takes 8 bytes.
The bigger this number is, the better the performance. Recommended range: 200 - 2000.
config HEAP_USE_HOOKS
bool "Use allocation and free hooks"
help
Enable the user to implement function hooks triggered for each successful allocation and free.
config HEAP_TASK_TRACKING
bool "Enable heap task tracking"
help
Enables tracking the task responsible for each heap allocation.
This function depends on heap poisoning being enabled and adds four more bytes of overhead for each block
allocated.
config HEAP_ABORT_WHEN_ALLOCATION_FAILS
bool "Abort if memory allocation fails"
default n
help
When enabled, if a memory allocation operation fails it will cause a system abort.
config HEAP_PLACE_FUNCTION_INTO_FLASH
bool "Force the entire heap component to be placed in flash memory"
default n
help
Enable this flag to save up RAM space by placing the heap component in the flash memory
Note that it is only safe to enable this configuration if no functions from esp_heap_caps.h
or esp_heap_trace.h are called from ISR.
endif

View File

@@ -0,0 +1,618 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdarg.h>
#include <sys/param.h>
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "multi_heap.h"
#include "esp_log.h"
#include "heap_private.h"
/*
This file, combined with a region allocator that supports multiple heaps, solves the problem that the ESP32 has RAM
that's slightly heterogeneous. Some RAM can be byte-accessed, some allows only 32-bit accesses, some can execute memory,
some can be remapped by the MMU to only be accessed by a certain PID etc. In order to allow the most flexible memory
allocation possible, this code makes it possible to request memory that has certain capabilities. The code will then use
its knowledge of how the memory is configured along with a priority scheme to allocate that memory in the most sane way
possible. This should optimize the amount of RAM accessible to the code without hardwiring addresses.
*/
static esp_alloc_failed_hook_t alloc_failed_callback;
#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
HEAP_IRAM_ATTR static void hex_to_str(char buf[8], uint32_t n)
{
for (int i = 0; i < 8; i++) {
uint8_t b4 = (n >> (28 - i * 4)) & 0b1111;
buf[i] = b4 <= 9 ? '0' + b4 : 'a' + b4 - 10;
}
}
HEAP_IRAM_ATTR static void fmt_abort_str(char dest[48], size_t size, uint32_t caps)
{
char sSize[8];
char sCaps[8];
hex_to_str(sSize, size);
hex_to_str(sCaps, caps);
memcpy(dest, "Mem alloc fail. size 0x00000000 caps 0x00000000", 48);
memcpy(dest + 23, sSize, 8);
memcpy(dest + 39, sCaps, 8);
}
#endif
HEAP_IRAM_ATTR NOINLINE_ATTR static void heap_caps_alloc_failed(size_t requested_size, uint32_t caps, const char *function_name)
{
if (alloc_failed_callback) {
alloc_failed_callback(requested_size, caps, function_name);
}
#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
char buf[48];
fmt_abort_str(buf, requested_size, caps);
esp_system_abort(buf);
#endif
}
esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback)
{
if (callback == NULL) {
return ESP_ERR_INVALID_ARG;
}
alloc_failed_callback = callback;
return ESP_OK;
}
bool heap_caps_match(const heap_t *heap, uint32_t caps)
{
return heap->heap != NULL && ((get_all_caps(heap) & caps) == caps);
}
/*
Routine to allocate a bit of memory with certain capabilities. caps is a bitfield of MALLOC_CAP_* bits.
*/
HEAP_IRAM_ATTR void *heap_caps_malloc( size_t size, uint32_t caps)
{
void* ptr = heap_caps_malloc_base(size, caps);
if (!ptr && size > 0){
heap_caps_alloc_failed(size, caps, __func__);
}
return ptr;
}
#define MALLOC_DISABLE_EXTERNAL_ALLOCS -1
//Dual-use: -1 (=MALLOC_DISABLE_EXTERNAL_ALLOCS) disables allocations in external memory, >=0 sets the limit for allocations preferring internal memory.
static int malloc_alwaysinternal_limit=MALLOC_DISABLE_EXTERNAL_ALLOCS;
void heap_caps_malloc_extmem_enable(size_t limit)
{
malloc_alwaysinternal_limit=limit;
}
/*
Default memory allocation implementation. Should return standard 8-bit memory. malloc() essentially resolves to this function.
*/
HEAP_IRAM_ATTR void *heap_caps_malloc_default( size_t size )
{
if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) {
return heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
} else {
// use heap_caps_malloc_base() since we'll
// check for allocation failure ourselves
void *r;
if (size <= (size_t)malloc_alwaysinternal_limit) {
r=heap_caps_malloc_base( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL );
} else {
r=heap_caps_malloc_base( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM );
}
if (r==NULL && size > 0) {
//try again while being less picky
r=heap_caps_malloc_base( size, MALLOC_CAP_DEFAULT );
}
// allocation failure?
if (r==NULL && size > 0){
heap_caps_alloc_failed(size, MALLOC_CAP_DEFAULT, __func__);
}
return r;
}
}
/*
Same for realloc()
Note: keep the logic in here the same as in heap_caps_malloc_default (or merge the two as soon as this gets more complex...)
*/
HEAP_IRAM_ATTR void *heap_caps_realloc_default( void *ptr, size_t size )
{
if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) {
return heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL );
} else {
// We use heap_caps_realloc_base() since we'll
// handle allocation failure ourselves
void *r;
if (size <= (size_t)malloc_alwaysinternal_limit) {
r=heap_caps_realloc_base( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
} else {
r=heap_caps_realloc_base( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM);
}
if (r==NULL && size>0) {
//We needed to allocate memory, but we didn't. Try again while being less picky.
r=heap_caps_realloc_base( ptr, size, MALLOC_CAP_DEFAULT);
}
// allocation failure?
if (r==NULL && size>0){
heap_caps_alloc_failed(size, MALLOC_CAP_DEFAULT, __func__);
}
return r;
}
}
/*
Memory allocation as preference in decreasing order.
*/
HEAP_IRAM_ATTR void *heap_caps_malloc_prefer( size_t size, size_t num, ... )
{
va_list argp;
va_start( argp, num );
void *r = NULL;
uint32_t caps = MALLOC_CAP_DEFAULT;
while (num--) {
caps = va_arg( argp, uint32_t );
r = heap_caps_malloc_base( size, caps );
if (r != NULL || size == 0) {
break;
}
}
if (r == NULL && size > 0){
heap_caps_alloc_failed(size, caps, __func__);
}
va_end( argp );
return r;
}
/*
Memory reallocation as preference in decreasing order.
*/
HEAP_IRAM_ATTR void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ... )
{
va_list argp;
va_start( argp, num );
void *r = NULL;
uint32_t caps = MALLOC_CAP_DEFAULT;
while (num--) {
caps = va_arg( argp, uint32_t );
r = heap_caps_realloc_base( ptr, size, caps );
if (r != NULL || size == 0) {
break;
}
}
if (r == NULL && size > 0){
heap_caps_alloc_failed(size, caps, __func__);
}
va_end( argp );
return r;
}
/*
Memory callocation as preference in decreasing order.
*/
HEAP_IRAM_ATTR void *heap_caps_calloc_prefer( size_t n, size_t size, size_t num, ... )
{
va_list argp;
va_start( argp, num );
void *r = NULL;
uint32_t caps = MALLOC_CAP_DEFAULT;
while (num--) {
caps = va_arg( argp, uint32_t );
r = heap_caps_calloc_base( n, size, caps );
if (r != NULL || size == 0){
break;
}
}
if (r == NULL && size > 0){
heap_caps_alloc_failed(size, caps, __func__);
}
va_end( argp );
return r;
}
HEAP_IRAM_ATTR void *heap_caps_realloc( void *ptr, size_t size, uint32_t caps)
{
ptr = heap_caps_realloc_base(ptr, size, caps);
if (ptr == NULL && size > 0){
heap_caps_alloc_failed(size, caps, __func__);
}
return ptr;
}
HEAP_IRAM_ATTR void *heap_caps_calloc( size_t n, size_t size, uint32_t caps)
{
void* ptr = heap_caps_calloc_base(n, size, caps);
if (!ptr && size > 0){
heap_caps_alloc_failed(n * size, caps, __func__);
}
return ptr;
}
size_t heap_caps_get_total_size(uint32_t caps)
{
size_t total_size = 0;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
total_size += (heap->end - heap->start);
}
}
return total_size;
}
size_t heap_caps_get_free_size( uint32_t caps )
{
size_t ret = 0;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
ret += multi_heap_free_size(heap->heap);
}
}
return ret;
}
size_t heap_caps_get_minimum_free_size( uint32_t caps )
{
size_t ret = 0;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
ret += multi_heap_minimum_free_size(heap->heap);
}
}
return ret;
}
size_t heap_caps_get_largest_free_block( uint32_t caps )
{
multi_heap_info_t info;
heap_caps_get_info(&info, caps);
return info.largest_free_block;
}
static struct {
size_t *values; // Array of minimum_free_bytes used to keep the different values when starting monitoring
size_t counter; // Keep count of registered heap when monitoring to prevent any added heap to create an out of bound access on values
multi_heap_lock_t mux; // protect access to min_free_bytes_monitoring fields in start/stop monitoring functions
} min_free_bytes_monitoring = {NULL, 0, MULTI_HEAP_LOCK_STATIC_INITIALIZER};
esp_err_t heap_caps_monitor_local_minimum_free_size_start(void)
{
// update minimum_free_bytes on all affected heap, and store the "old value"
// as a snapshot of the heaps minimum_free_bytes state.
heap_t *heap = NULL;
MULTI_HEAP_LOCK(&min_free_bytes_monitoring.mux);
if (min_free_bytes_monitoring.values == NULL) {
SLIST_FOREACH(heap, &registered_heaps, next) {
min_free_bytes_monitoring.counter++;
}
min_free_bytes_monitoring.values = heap_caps_malloc(sizeof(size_t) * min_free_bytes_monitoring.counter, MALLOC_CAP_DEFAULT);
assert(min_free_bytes_monitoring.values != NULL && "not enough memory to store min_free_bytes value");
memset(min_free_bytes_monitoring.values, 0xFF, sizeof(size_t) * min_free_bytes_monitoring.counter);
}
heap = SLIST_FIRST(&registered_heaps);
for (size_t counter = 0; counter < min_free_bytes_monitoring.counter; counter++) {
size_t old_minimum = multi_heap_reset_minimum_free_bytes(heap->heap);
if (min_free_bytes_monitoring.values[counter] > old_minimum) {
min_free_bytes_monitoring.values[counter] = old_minimum;
}
heap = SLIST_NEXT(heap, next);
}
MULTI_HEAP_UNLOCK(&min_free_bytes_monitoring.mux);
return ESP_OK;
}
esp_err_t heap_caps_monitor_local_minimum_free_size_stop(void)
{
if (min_free_bytes_monitoring.values == NULL) {
return ESP_FAIL;
}
MULTI_HEAP_LOCK(&min_free_bytes_monitoring.mux);
heap_t *heap = SLIST_FIRST(&registered_heaps);
for (size_t counter = 0; counter < min_free_bytes_monitoring.counter; counter++) {
multi_heap_restore_minimum_free_bytes(heap->heap, min_free_bytes_monitoring.values[counter]);
heap = SLIST_NEXT(heap, next);
}
heap_caps_free(min_free_bytes_monitoring.values);
min_free_bytes_monitoring.values = NULL;
min_free_bytes_monitoring.counter = 0;
MULTI_HEAP_UNLOCK(&min_free_bytes_monitoring.mux);
return ESP_OK;
}
void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps )
{
memset(info, 0, sizeof(multi_heap_info_t));
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
multi_heap_info_t hinfo;
multi_heap_get_info(heap->heap, &hinfo);
info->total_free_bytes += hinfo.total_free_bytes - MULTI_HEAP_BLOCK_OWNER_SIZE();
info->total_allocated_bytes += (hinfo.total_allocated_bytes -
hinfo.allocated_blocks * MULTI_HEAP_BLOCK_OWNER_SIZE());
info->largest_free_block = MAX(info->largest_free_block,
hinfo.largest_free_block);
info->largest_free_block -= info->largest_free_block ? MULTI_HEAP_BLOCK_OWNER_SIZE() : 0;
info->minimum_free_bytes += hinfo.minimum_free_bytes - MULTI_HEAP_BLOCK_OWNER_SIZE();
info->allocated_blocks += hinfo.allocated_blocks;
info->free_blocks += hinfo.free_blocks;
info->total_blocks += hinfo.total_blocks;
}
}
}
void heap_caps_travel( void (*callback)(void *start, void *end, multi_heap_info_t *info) )
{
multi_heap_info_t info;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
multi_heap_get_info(heap->heap, &info);
if (callback)
callback((void *)heap->start, (void *)heap->end, &info);
}
}
void heap_caps_print_heap_info( uint32_t caps )
{
multi_heap_info_t info;
printf("Heap summary for capabilities 0x%08"PRIX32":\n", caps);
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
multi_heap_get_info(heap->heap, &info);
printf(" At 0x%08x len %d free %d allocated %d min_free %d\n",
heap->start, heap->end - heap->start, info.total_free_bytes, info.total_allocated_bytes, info.minimum_free_bytes);
printf(" largest_free_block %d alloc_blocks %d free_blocks %d total_blocks %d\n",
info.largest_free_block, info.allocated_blocks,
info.free_blocks, info.total_blocks);
}
}
printf(" Totals:\n");
heap_caps_get_info(&info, caps);
printf(" free %d allocated %d min_free %d largest_free_block %d\n", info.total_free_bytes, info.total_allocated_bytes, info.minimum_free_bytes, info.largest_free_block);
}
bool heap_caps_check_integrity(uint32_t caps, bool print_errors)
{
bool all_heaps = caps & MALLOC_CAP_INVALID;
bool valid = true;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap != NULL
&& (all_heaps || (get_all_caps(heap) & caps) == caps)) {
valid = multi_heap_check(heap->heap, print_errors) && valid;
}
}
return valid;
}
bool heap_caps_check_integrity_all(bool print_errors)
{
return heap_caps_check_integrity(MALLOC_CAP_INVALID, print_errors);
}
bool heap_caps_check_integrity_addr(intptr_t addr, bool print_errors)
{
heap_t *heap = find_containing_heap((void *)addr);
if (heap == NULL) {
return false;
}
return multi_heap_check(heap->heap, print_errors);
}
void heap_caps_dump(uint32_t caps)
{
bool all_heaps = caps & MALLOC_CAP_INVALID;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap != NULL
&& (all_heaps || (get_all_caps(heap) & caps) == caps)) {
multi_heap_dump(heap->heap);
}
}
}
void heap_caps_dump_all(void)
{
heap_caps_dump(MALLOC_CAP_INVALID);
}
size_t heap_caps_get_allocated_size( void *ptr )
{
// add the block owner bytes back to ptr before handing over
// to multi heap layer.
ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(ptr);
heap_t *heap = find_containing_heap(ptr);
assert(heap);
size_t size = multi_heap_get_allocated_size(heap->heap, ptr);
return MULTI_HEAP_REMOVE_BLOCK_OWNER_SIZE(size);
}
static HEAP_IRAM_ATTR esp_err_t heap_caps_aligned_check_args(size_t alignment, size_t size, uint32_t caps, const char *funcname)
{
if (!alignment) {
return ESP_FAIL;
}
// Alignment must be a power of two:
if ((alignment & (alignment - 1)) != 0) {
return ESP_FAIL;
}
if (size == 0) {
return ESP_FAIL;
}
if (MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size) > HEAP_SIZE_MAX) {
// Avoids int overflow when adding small numbers to size, or
// calculating 'end' from start+size, by limiting 'size' to the possible range
heap_caps_alloc_failed(size, caps, funcname);
return ESP_FAIL;
}
return ESP_OK;
}
HEAP_IRAM_ATTR void *heap_caps_aligned_alloc_default(size_t alignment, size_t size)
{
void *ret = NULL;
if (malloc_alwaysinternal_limit == MALLOC_DISABLE_EXTERNAL_ALLOCS) {
return heap_caps_aligned_alloc(alignment, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
}
if (heap_caps_aligned_check_args(alignment, size, MALLOC_CAP_DEFAULT, __func__) != ESP_OK) {
return NULL;
}
if (size <= (size_t)malloc_alwaysinternal_limit) {
ret = heap_caps_aligned_alloc_base(alignment, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
} else {
ret = heap_caps_aligned_alloc_base(alignment, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM);
}
if (ret != NULL) {
return ret;
}
ret = heap_caps_aligned_alloc_base(alignment, size, MALLOC_CAP_DEFAULT);
if (ret == NULL) {
heap_caps_alloc_failed(size, MALLOC_CAP_DEFAULT, __func__);
}
return ret;
}
HEAP_IRAM_ATTR void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps)
{
void *ret = NULL;
if (heap_caps_aligned_check_args(alignment, size, caps, __func__) != ESP_OK) {
return NULL;
}
ret = heap_caps_aligned_alloc_base(alignment, size, caps);
if (ret == NULL) {
heap_caps_alloc_failed(size, caps, __func__);
}
return ret;
}
HEAP_IRAM_ATTR void heap_caps_aligned_free(void *ptr)
{
heap_caps_free(ptr);
}
void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps)
{
size_t size_bytes;
if (__builtin_mul_overflow(n, size, &size_bytes)) {
return NULL;
}
void *ptr = heap_caps_aligned_alloc(alignment,size_bytes, caps);
if(ptr != NULL) {
memset(ptr, 0, size_bytes);
}
return ptr;
}
typedef struct walker_data {
void *opaque_ptr;
heap_caps_walker_cb_t cb_func;
heap_t *heap;
} walker_data_t;
__attribute__((noinline)) static bool heap_caps_walker(void* block_ptr, size_t block_size, int block_used, void *user_data)
{
walker_data_t *walker_data = (walker_data_t*)user_data;
walker_heap_into_t heap_info = {
(intptr_t)walker_data->heap->start,
(intptr_t)walker_data->heap->end
};
walker_block_info_t block_info = {
block_ptr,
block_size,
(bool)block_used
};
return walker_data->cb_func(heap_info, block_info, walker_data->opaque_ptr);
}
void heap_caps_walk(uint32_t caps, heap_caps_walker_cb_t walker_func, void *user_data)
{
assert(walker_func != NULL);
bool all_heaps = caps & MALLOC_CAP_INVALID;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap != NULL
&& (all_heaps || (get_all_caps(heap) & caps) == caps)) {
walker_data_t walker_data = {user_data, walker_func, heap};
multi_heap_walk(heap->heap, heap_caps_walker, &walker_data);
}
}
}
void heap_caps_walk_all(heap_caps_walker_cb_t walker_func, void *user_data)
{
heap_caps_walk(MALLOC_CAP_INVALID, walker_func, user_data);
}

View File

@@ -0,0 +1,295 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <sys/param.h>
#include "esp_attr.h"
#include "multi_heap.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "heap_private.h"
#ifdef CONFIG_HEAP_USE_HOOKS
#define CALL_HOOK(hook, ...) { \
if (hook != NULL) { \
hook(__VA_ARGS__); \
} \
}
#else
#define CALL_HOOK(hook, ...) {}
#endif
//This is normally provided by the heap-memalign-hw component.
extern void esp_heap_adjust_alignment_to_hw(size_t *p_alignment, size_t *p_size, uint32_t *p_caps);
extern bool esp_ptr_in_diram_iram(const void *p);
extern bool esp_ptr_in_diram_dram(const void *p);
extern void *esp_ptr_diram_dram_to_iram(const void *p);
extern bool esp_dram_match_iram(void);
//Default alignment the multiheap allocator / tlsf will align 'unaligned' memory to, in bytes
#define UNALIGNED_MEM_ALIGNMENT_BYTES 4
/*
This takes a memory chunk in a region that can be addressed as both DRAM as well as IRAM. It will convert it to
IRAM in such a way that it can be later freed. It assumes both the address as well as the length to be word-aligned.
It returns a region that's 1 word smaller than the region given because it stores the original Dram address there.
*/
HEAP_IRAM_ATTR static void *dram_alloc_to_iram_addr(void *addr, size_t len)
{
uintptr_t dstart = (uintptr_t)addr; //First word
uintptr_t dend __attribute__((unused)) = dstart + len - 4; //Last word
assert(esp_ptr_in_diram_dram((void *)dstart));
assert(esp_ptr_in_diram_dram((void *)dend));
assert((dstart & 3) == 0);
assert((dend & 3) == 0);
#if SOC_DIRAM_INVERTED // We want the word before the result to hold the DRAM address
uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dend);
#else
uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dstart);
#endif
*iptr = dstart;
return iptr + 1;
}
HEAP_IRAM_ATTR void heap_caps_free( void *ptr)
{
if (ptr == NULL) {
return;
}
if (esp_ptr_in_diram_iram(ptr)) {
//Memory allocated here is actually allocated in the DRAM alias region and
//cannot be de-allocated as usual. dram_alloc_to_iram_addr stores a pointer to
//the equivalent DRAM address, though; free that.
uint32_t *dramAddrPtr = (uint32_t *)ptr;
ptr = (void *)dramAddrPtr[-1];
}
void *block_owner_ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(ptr);
heap_t *heap = find_containing_heap(block_owner_ptr);
assert(heap != NULL && "free() target pointer is outside heap areas");
multi_heap_free(heap->heap, block_owner_ptr);
CALL_HOOK(esp_heap_trace_free_hook, ptr);
}
HEAP_IRAM_ATTR static inline void *aligned_or_unaligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset) {
if (alignment<=UNALIGNED_MEM_ALIGNMENT_BYTES) { //alloc and friends align to 32-bit by default
return multi_heap_malloc(heap, size);
} else {
return multi_heap_aligned_alloc_offs(heap, size, alignment, offset);
}
}
/*
This function should not be called directly as it does not check for failure / call heap_caps_alloc_failed()
Note that this function does 'unaligned' alloc calls if alignment <= UNALIGNED_MEM_ALIGNMENT_BYTES (=4) as the
allocator will align to that value by default.
*/
HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_aligned_alloc_base(size_t alignment, size_t size, uint32_t caps)
{
void *ret = NULL;
// Alignment, size and caps may need to be modified because of hardware requirements.
esp_heap_adjust_alignment_to_hw(&alignment, &size, &caps);
// remove block owner size to HEAP_SIZE_MAX rather than adding the block owner size
// to size to prevent overflows.
if (size == 0 || size > MULTI_HEAP_REMOVE_BLOCK_OWNER_SIZE(HEAP_SIZE_MAX) ) {
// Avoids int overflow when adding small numbers to size, or
// calculating 'end' from start+size, by limiting 'size' to the possible range
return NULL;
}
if (caps & MALLOC_CAP_EXEC) {
//MALLOC_CAP_EXEC forces an alloc from IRAM. There is a region which has both this as well as the following
//caps, but the following caps are not possible for IRAM. Thus, the combination is impossible and we return
//NULL directly, even although our heap capabilities (based on soc_memory_tags & soc_memory_regions) would
//indicate there is a tag for this.
if ((caps & MALLOC_CAP_8BIT) || (caps & MALLOC_CAP_DMA)) {
return NULL;
}
caps |= MALLOC_CAP_32BIT; // IRAM is 32-bit accessible RAM
}
if (caps & MALLOC_CAP_32BIT) {
/* 32-bit accessible RAM should allocated in 4 byte aligned sizes
* (Future versions of ESP-IDF should possibly fail if an invalid size is requested)
*/
size = (size + 3) & (~3); // int overflow checked above
}
for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
//Iterate over heaps and check capabilities at this priority
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap == NULL) {
continue;
}
if ((heap->caps[prio] & caps) != 0) {
//Heap has at least one of the caps requested. If caps has other bits set that this prio
//doesn't cover, see if they're available in other prios.
if ((get_all_caps(heap) & caps) == caps) {
//This heap can satisfy all the requested capabilities. See if we can grab some memory using it.
// If MALLOC_CAP_EXEC is requested but the DRAM and IRAM are on the same addresses (like on esp32c6)
// proceed as for a default allocation.
if (((caps & MALLOC_CAP_EXEC) && !esp_dram_match_iram()) && esp_ptr_in_diram_dram((void *)heap->start)) {
//This is special, insofar that what we're going to get back is a DRAM address. If so,
//we need to 'invert' it (lowest address in DRAM == highest address in IRAM and vice-versa) and
//add a pointer to the DRAM equivalent before the address we're going to return.
ret = aligned_or_unaligned_alloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size) + 4,
alignment, MULTI_HEAP_BLOCK_OWNER_SIZE()); // int overflow checked above
if (ret != NULL) {
MULTI_HEAP_SET_BLOCK_OWNER(ret);
ret = MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ret);
uint32_t *iptr = dram_alloc_to_iram_addr(ret, size + 4); // int overflow checked above
CALL_HOOK(esp_heap_trace_alloc_hook, iptr, size, caps);
return iptr;
}
} else {
//Just try to alloc, nothing special.
ret = aligned_or_unaligned_alloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size),
alignment, MULTI_HEAP_BLOCK_OWNER_SIZE());
if (ret != NULL) {
MULTI_HEAP_SET_BLOCK_OWNER(ret);
ret = MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ret);
CALL_HOOK(esp_heap_trace_alloc_hook, ret, size, caps);
return ret;
}
}
}
}
}
}
//Nothing usable found.
return NULL;
}
//Wrapper for heap_caps_aligned_alloc_base as that can also do unaligned allocs.
HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_malloc_base( size_t size, uint32_t caps) {
return heap_caps_aligned_alloc_base(UNALIGNED_MEM_ALIGNMENT_BYTES, size, caps);
}
/*
This function should not be called directly as it does not
check for failure / call heap_caps_alloc_failed()
*/
HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_realloc_base( void *ptr, size_t size, uint32_t caps)
{
bool ptr_in_diram_case = false;
heap_t *heap = NULL;
void *dram_ptr = NULL;
//See if memory needs alignment because of hardware reasons.
size_t alignment = UNALIGNED_MEM_ALIGNMENT_BYTES;
esp_heap_adjust_alignment_to_hw(&alignment, &size, &caps);
if (ptr == NULL) {
return heap_caps_aligned_alloc_base(alignment, size, caps);
}
if (size == 0) {
heap_caps_free(ptr);
return NULL;
}
// remove block owner size to HEAP_SIZE_MAX rather than adding the block owner size
// to size to prevent overflows.
if (size > MULTI_HEAP_REMOVE_BLOCK_OWNER_SIZE(HEAP_SIZE_MAX)) {
return NULL;
}
//The pointer to memory may be aliased, we need to
//recover the corresponding address before to manage a new allocation:
if(esp_ptr_in_diram_iram((void *)ptr)) {
uint32_t *dram_addr = (uint32_t *)ptr;
dram_ptr = (void *)dram_addr[-1];
dram_ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(dram_ptr);
heap = find_containing_heap(dram_ptr);
assert(heap != NULL && "realloc() pointer is outside heap areas");
//with pointers that reside on diram space, we avoid using
//the realloc implementation due to address translation issues,
//instead force a malloc/copy/free
ptr_in_diram_case = true;
} else {
heap = find_containing_heap(ptr);
assert(heap != NULL && "realloc() pointer is outside heap areas");
}
// shift ptr by block owner offset. Since the ptr returned to the user
// does not include the block owner bytes (that are located at the
// beginning of the allocated memory) we have to add them back before
// processing the realloc.
ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(ptr);
// are the existing heap's capabilities compatible with the
// requested ones?
bool compatible_caps = (caps & get_all_caps(heap)) == caps;
//Note we don't try realloc() on memory that needs to be aligned, that is handled
//by the fallthrough code.
if (compatible_caps && !ptr_in_diram_case && alignment<=UNALIGNED_MEM_ALIGNMENT_BYTES) {
// try to reallocate this memory within the same heap
// (which will resize the block if it can)
void *r = multi_heap_realloc(heap->heap, ptr, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size));
if (r != NULL) {
MULTI_HEAP_SET_BLOCK_OWNER(r);
r = MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(r);
CALL_HOOK(esp_heap_trace_alloc_hook, r, size, caps);
return r;
}
}
// if we couldn't do that, try to see if we can reallocate
// in a different heap with requested capabilities.
void *new_p = heap_caps_aligned_alloc_base(alignment, size, caps);
if (new_p != NULL) {
size_t old_size = 0;
//If we're dealing with aliased ptr, information regarding its containing
//heap can only be obtained with translated address.
if(ptr_in_diram_case) {
old_size = multi_heap_get_allocated_size(heap->heap, dram_ptr);
} else {
old_size = multi_heap_get_allocated_size(heap->heap, ptr);
}
assert(old_size > 0);
// do not copy the block owner bytes
memcpy(new_p, MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ptr), MIN(size, old_size));
// add the block owner bytes to ptr since they are removed in heap_caps_free
heap_caps_free(MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ptr));
return new_p;
}
return NULL;
}
/*
This function should not be called directly as it does not
check for failure / call heap_caps_alloc_failed()
*/
HEAP_IRAM_ATTR void *heap_caps_calloc_base( size_t n, size_t size, uint32_t caps)
{
void *result;
size_t size_bytes;
if (__builtin_mul_overflow(n, size, &size_bytes)) {
return NULL;
}
result = heap_caps_malloc_base(size_bytes, caps);
if (result != NULL) {
memset(result, 0, size_bytes);
}
return result;
}

View File

@@ -0,0 +1,303 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "heap_private.h"
#include <assert.h>
#include <string.h>
#include <sys/lock.h>
#include "esp_log.h"
#include "multi_heap.h"
#include "multi_heap_platform.h"
#include "esp_heap_caps_init.h"
#include "heap_memory_layout.h"
// static const char *TAG = "heap_init";
/* Linked-list of registered heaps */
struct registered_heap_ll registered_heaps;
/**
* @brief This helper function adds a new heap to list of registered
* heaps making sure to keep the heaps sorted by ascending size.
*
* @param new_heap heap to be inserted in the list of registered
* heaps
*/
static void sorted_add_to_registered_heaps(heap_t *new_heap)
{
// if list empty, insert head and return
if (SLIST_EMPTY(&registered_heaps)) {
SLIST_INSERT_HEAD(&registered_heaps, new_heap, next);
return;
}
// else, go through the registered heaps and add the new one
// so the registered heaps are sorted by increasing heap size.
heap_t *cur_heap = NULL;
heap_t *prev_heap = NULL;
const size_t new_heap_size = new_heap->end - new_heap->start;
SLIST_FOREACH(cur_heap, &registered_heaps, next) {
const size_t cur_heap_size = cur_heap->end - cur_heap->start;
if (cur_heap_size >= new_heap_size) {
if (prev_heap != NULL) {
SLIST_INSERT_AFTER(prev_heap, new_heap, next);
} else {
SLIST_INSERT_HEAD(&registered_heaps, new_heap, next);
}
return;
}
prev_heap = cur_heap;
}
// new heap size if the biggest so far, insert it at the end
SLIST_INSERT_AFTER(prev_heap, new_heap, next);
}
static void register_heap(heap_t *region)
{
size_t heap_size = region->end - region->start;
// assert(heap_size <= HEAP_SIZE_MAX);
if (heap_size > HEAP_SIZE_MAX)
ESP_EARLY_LOGI(TAG, "Heap Size(%d) > %d", heap_size, HEAP_SIZE_MAX);
region->heap = multi_heap_register((void *)region->start, heap_size);
// if (region->heap != NULL) {
// ESP_EARLY_LOGD(TAG, "New heap initialised at %p", region->heap);
// }
}
void heap_caps_enable_nonos_stack_heaps(void)
{
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
// Assume any not-yet-registered heap is
// a nonos-stack heap
if (heap->heap == NULL) {
register_heap(heap);
if (heap->heap != NULL) {
multi_heap_set_lock(heap->heap, &heap->heap_mux);
}
}
}
}
/* Initialize the heap allocator to use all of the memory not
used by static data or reserved for other purposes
*/
void heap_caps_init(void)
{
#ifdef CONFIG_HEAP_TLSF_USE_ROM_IMPL
extern void multi_heap_in_rom_init(void);
multi_heap_in_rom_init();
#endif
/* Get the array of regions that we can use for heaps
(with reserved memory removed already.)
*/
size_t num_regions = soc_get_available_memory_region_max_count();
soc_memory_region_t regions[num_regions];
num_regions = soc_get_available_memory_regions(regions);
// the following for loop will calculate the number of possible heaps
// based on how many regions were coalesced.
size_t num_heaps = num_regions;
//The heap allocator will treat every region given to it as separate. In order to get bigger ranges of contiguous memory,
//it's useful to coalesce adjacent regions that have the same type.
for (size_t i = 1; i < num_regions; i++) {
soc_memory_region_t *a = &regions[i - 1];
soc_memory_region_t *b = &regions[i];
if (b->start == (intptr_t)(a->start + a->size) && b->type == a->type && b->startup_stack == a->startup_stack ) {
a->type = -1;
b->start = a->start;
b->size += a->size;
// remove one heap from the number of heaps as
// 2 regions just got coalesced.
num_heaps--;
}
}
/* Start by allocating the registered heap data on the stack.
Once we have a heap to copy it to, we will copy it to a heap buffer.
*/
heap_t temp_heaps[num_heaps];
size_t heap_idx = 0;
// ESP_EARLY_LOGI(TAG, "Initializing. RAM available for dynamic allocation:");
for (size_t i = 0; i < num_regions; i++) {
soc_memory_region_t *region = &regions[i];
const soc_memory_type_desc_t *type = &soc_memory_types[region->type];
heap_t *heap = &temp_heaps[heap_idx];
if (region->type == -1) {
memset(heap, 0, sizeof(*heap));
continue;
}
heap_idx++;
assert(heap_idx <= num_heaps);
memcpy(heap->caps, type->caps, sizeof(heap->caps));
heap->start = region->start;
heap->end = region->start + region->size;
MULTI_HEAP_LOCK_INIT(&heap->heap_mux);
if (region->startup_stack) {
/* Will be registered when OS scheduler starts */
heap->heap = NULL;
} else {
register_heap(heap);
}
SLIST_NEXT(heap, next) = NULL;
// ESP_EARLY_LOGI(TAG, "At %08X len %08X (%d KiB): %s",
// region->start, region->size, region->size / 1024, type->name);
}
assert(heap_idx == num_heaps);
/* Allocate the permanent heap data that we'll use as a linked list at runtime.
Allocate this part of data contiguously, even though it's a linked list... */
assert(SLIST_EMPTY(&registered_heaps));
heap_t *heaps_array = NULL;
for (size_t i = 0; i < num_heaps; i++) {
if (heap_caps_match(&temp_heaps[i], MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL)) {
/* use the first DRAM heap which can fit the data.
* the allocated block won't include the block owner bytes since this operation
* is done by the top level API heap_caps_malloc(). So we need to add it manually
* after successful allocation. Allocate extra 4 bytes for that purpose. */
heaps_array = multi_heap_malloc(temp_heaps[i].heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(sizeof(heap_t) * num_heaps));
if (heaps_array != NULL) {
break;
}
}
}
assert(heaps_array != NULL); /* if NULL, there's not enough free startup heap space */
MULTI_HEAP_SET_BLOCK_OWNER(heaps_array);
heaps_array = (heap_t *)MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(heaps_array);
memcpy(heaps_array, temp_heaps, sizeof(heap_t)*num_heaps);
/* Iterate the heaps and set their locks, also add them to the linked list. */
for (size_t i = 0; i < num_heaps; i++) {
if (heaps_array[i].heap != NULL) {
multi_heap_set_lock(heaps_array[i].heap, &heaps_array[i].heap_mux);
}
/* Since the registered heaps list is always traversed from head
* to tail when looking for a suitable heap when allocating memory, it is
* best to place smaller heap first. In that way, if several heaps share
* the same set of capabilities, the smallest heaps will be used first when
* processing small allocation requests, leaving the bigger heaps untouched
* until the smaller heaps are full. */
sorted_add_to_registered_heaps(&heaps_array[i]);
}
}
esp_err_t heap_caps_add_region(intptr_t start, intptr_t end)
{
if (start == 0) {
return ESP_ERR_INVALID_ARG;
}
for (size_t i = 0; i < soc_memory_region_count; i++) {
const soc_memory_region_t *region = &soc_memory_regions[i];
// Test requested start only as 'end' may be in a different region entry, assume 'end' has same caps
if (region->start <= start && (intptr_t)(region->start + region->size) > start) {
const uint32_t *caps = soc_memory_types[region->type].caps;
return heap_caps_add_region_with_caps(caps, start, end);
}
}
return ESP_ERR_NOT_FOUND;
}
/* This API is used for internal test purpose and hence its not marked as static */
bool heap_caps_check_add_region_allowed(intptr_t heap_start, intptr_t heap_end, intptr_t start, intptr_t end)
{
/*
* We assume that in any region, the "start" must be strictly less than the end.
* Specially, the 3rd scenario can be allowed. For example, allocate memory from heap,
* then change the capability and call this function to create a new region for special
* application.
* This 'start = start' and 'end = end' scenario is incorrect because the same region
* cannot be added twice. In fact, registering the same memory region as a heap twice
* would cause a corruption and then an exception at runtime.
*
* the existing heap region start end
* |----------------------|
*
* 1.add region (e1<s) |-----| correct: bool condition_1 = end < heap_start;
*
* 2.add region (s2<s && e2>s) |-----------------| wrong: bool condition_2 = start < heap_start && end > heap_start;
* |---------------------------------| wrong
*
* 3.add region (s3>=s && e3<e) |---------------| correct: bool condition_3 = start >= heap_start && end < heap_end;
* |--------------| correct
*
* 4.add region (s4<e && e4>e) |------------------------| wrong: bool condition_4 = start < heap_end && end > heap_end;
* |---------------------| wrong
*
* 5.add region (s5>=e) |----| correct: bool condition_5 = start >= heap_end;
*
* 6.add region (s6==s && e6==e) |----------------------| wrong: bool condition_6 = start == heap_start && end == heap_end;
*/
bool condition_2 = start < heap_start && end > heap_start; // if true then region not allowed
bool condition_4 = start < heap_end && end > heap_end; // if true then region not allowed
bool condition_6 = start == heap_start && end == heap_end; // if true then region not allowed
return !(condition_2 || condition_4 || condition_6);
}
esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t start, intptr_t end)
{
esp_err_t err = ESP_FAIL;
if (caps == NULL || start == 0 || end == 0 || end <= start) {
return ESP_ERR_INVALID_ARG;
}
//Check if region overlaps the start and/or end of an existing region. If so, the
//region is invalid (or maybe added twice)
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (!heap_caps_check_add_region_allowed(heap->start, heap->end, start, end)) {
ESP_EARLY_LOGD(TAG, "invalid overlap detected with existing heap region");
return ESP_FAIL;
}
}
heap_t *p_new = heap_caps_malloc(sizeof(heap_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT);
if (p_new == NULL) {
err = ESP_ERR_NO_MEM;
goto done;
}
memcpy(p_new->caps, caps, sizeof(p_new->caps));
p_new->start = start;
p_new->end = end;
MULTI_HEAP_LOCK_INIT(&p_new->heap_mux);
p_new->heap = multi_heap_register((void *)start, end - start);
SLIST_NEXT(p_new, next) = NULL;
if (p_new->heap == NULL) {
err = ESP_ERR_INVALID_SIZE;
goto done;
}
multi_heap_set_lock(p_new->heap, &p_new->heap_mux);
/* (This insertion is atomic to registered_heaps, so
we don't need to worry about thread safety for readers,
only for writers. */
// static multi_heap_lock_t registered_heaps_write_lock = MULTI_HEAP_LOCK_STATIC_INITIALIZER;
MULTI_HEAP_LOCK(&registered_heaps_write_lock);
SLIST_INSERT_HEAD(&registered_heaps, p_new, next);
MULTI_HEAP_UNLOCK(&registered_heaps_write_lock);
err = ESP_OK;
done:
if (err != ESP_OK) {
free(p_new);
}
return err;
}

View File

@@ -0,0 +1,93 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <heap_memory_layout.h>
#include "multi_heap.h"
#include "multi_heap_platform.h"
#include "sys/queue.h"
#include "esp_attr.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Some common heap registration data structures used
for heap_caps_init.c to share heap information with heap_caps.c
*/
// #define HEAP_SIZE_MAX (SOC_MAX_CONTIGUOUS_RAM_SIZE)
#define HEAP_SIZE_MAX ( 16 << 20 )
/* Type for describing each registered heap */
typedef struct heap_t_ {
uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS]; ///< Capabilities for the type of memory in this heap (as a prioritised set). Copied from soc_memory_types so it's in RAM not flash.
intptr_t start;
intptr_t end;
multi_heap_lock_t heap_mux;
multi_heap_handle_t heap;
SLIST_ENTRY(heap_t_) next;
} heap_t;
/* All registered heaps.
Forms a single linked list, even though most entries are contiguous.
This means at the expense of 4 bytes per heap, new heaps can be
added at runtime in a fast & thread-safe way.
*/
extern SLIST_HEAD(registered_heap_ll, heap_t_) registered_heaps;
bool heap_caps_match(const heap_t *heap, uint32_t caps);
/* return all possible capabilities (across all priorities) for a given heap */
FORCE_INLINE_ATTR uint32_t get_all_caps(const heap_t *heap)
{
if (heap->heap == NULL) {
return 0;
}
uint32_t all_caps = 0;
for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
all_caps |= heap->caps[prio];
}
return all_caps;
}
/* Find the heap which belongs to ptr, or return NULL if it's
not in any heap.
(This confirms if ptr is inside the heap's region, doesn't confirm if 'ptr'
is an allocated block or is some other random address inside the heap.)
*/
FORCE_INLINE_ATTR heap_t *find_containing_heap(void *ptr )
{
intptr_t p = (intptr_t)ptr;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap != NULL && p >= heap->start && p < heap->end) {
return heap;
}
}
return NULL;
}
/*
Because we don't want to add _another_ known allocation method to the stack of functions to trace wrt memory tracing,
these are declared private. The newlib malloc()/realloc() implementation also calls these, so they are declared
separately in newlib/syscalls.c.
*/
void *heap_caps_realloc_default(void *p, size_t size);
void *heap_caps_malloc_default(size_t size);
void *heap_caps_aligned_alloc_default(size_t alignment, size_t size);
void *heap_caps_realloc_base(void *ptr, size_t size, uint32_t caps);
void *heap_caps_calloc_base(size_t n, size_t size, uint32_t caps);
void *heap_caps_malloc_base(size_t size, uint32_t caps);
void *heap_caps_aligned_alloc_base(size_t alignment, size_t size, uint32_t caps);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <multi_heap.h>
#include "multi_heap_internal.h"
#include "heap_private.h"
#include "esp_heap_task_info.h"
#ifdef CONFIG_HEAP_TASK_TRACKING
/*
* Return per-task heap allocation totals and lists of blocks.
*
* For each task that has allocated memory from the heap, return totals for
* allocations within regions matching one or more sets of capabilities.
*
* Optionally also return an array of structs providing details about each
* block allocated by one or more requested tasks, or by all tasks.
*
* Returns the number of block detail structs returned.
*/
size_t heap_caps_get_per_task_info(heap_task_info_params_t *params)
{
heap_t *reg;
heap_task_block_t *blocks = params->blocks;
size_t count = *params->num_totals;
size_t remaining = params->max_blocks;
// Clear out totals for any prepopulated tasks.
if (params->totals) {
for (size_t i = 0; i < count; ++i) {
for (size_t type = 0; type < NUM_HEAP_TASK_CAPS; ++type) {
params->totals[i].size[type] = 0;
params->totals[i].count[type] = 0;
}
}
}
SLIST_FOREACH(reg, &registered_heaps, next) {
multi_heap_handle_t heap = reg->heap;
if (heap == NULL) {
continue;
}
// Find if the capabilities of this heap region match on of the desired
// sets of capabilities.
uint32_t caps = get_all_caps(reg);
uint32_t type;
for (type = 0; type < NUM_HEAP_TASK_CAPS; ++type) {
if ((caps & params->mask[type]) == params->caps[type]) {
break;
}
}
if (type == NUM_HEAP_TASK_CAPS) {
continue;
}
multi_heap_block_handle_t b = multi_heap_get_first_block(heap);
multi_heap_internal_lock(heap);
for ( ; b ; b = multi_heap_get_next_block(heap, b)) {
if (multi_heap_is_free(b)) {
continue;
}
void *p = multi_heap_get_block_address(b); // Safe, only arithmetic
size_t bsize = multi_heap_get_allocated_size(heap, p); // Validates
TaskHandle_t btask = MULTI_HEAP_GET_BLOCK_OWNER(p);
// Accumulate per-task allocation totals.
if (params->totals) {
size_t i;
for (i = 0; i < count; ++i) {
if (params->totals[i].task == btask) {
break;
}
}
if (i < count) {
params->totals[i].size[type] += bsize;
params->totals[i].count[type] += 1;
}
else {
if (count < params->max_totals) {
params->totals[count].task = btask;
params->totals[count].size[type] = bsize;
params->totals[i].count[type] = 1;
++count;
}
}
}
// Return details about allocated blocks for selected tasks.
if (blocks && remaining > 0) {
if (params->tasks) {
size_t i;
for (i = 0; i < params->num_tasks; ++i) {
if (btask == params->tasks[i]) {
break;
}
}
if (i == params->num_tasks) {
continue;
}
}
blocks->task = btask;
blocks->address = p;
blocks->size = bsize;
++blocks;
--remaining;
}
}
multi_heap_internal_unlock(heap);
}
*params->num_totals = count;
return params->max_blocks - remaining;
}
#endif // CONFIG_HEAP_TASK_TRACKING

View File

@@ -0,0 +1,502 @@
/*
* SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include "multi_heap.h"
#include <autoconf.h>
#include "esp_err.h"
#include "esp_attr.h"
#ifdef __cplusplus
extern "C" {
#endif
#if CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH
#define HEAP_IRAM_ATTR
#else
#define HEAP_IRAM_ATTR IRAM_ATTR
#endif
/**
* @brief Flags to indicate the capabilities of the various memory systems
*/
#define MALLOC_CAP_EXEC (1<<0) ///< Memory must be able to run executable code
#define MALLOC_CAP_32BIT (1<<1) ///< Memory must allow for aligned 32-bit data accesses
#define MALLOC_CAP_8BIT (1<<2) ///< Memory must allow for 8/16/...-bit data accesses
#define MALLOC_CAP_DMA (1<<3) ///< Memory must be able to accessed by DMA
#define MALLOC_CAP_PID2 (1<<4) ///< Memory must be mapped to PID2 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID3 (1<<5) ///< Memory must be mapped to PID3 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID4 (1<<6) ///< Memory must be mapped to PID4 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID5 (1<<7) ///< Memory must be mapped to PID5 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID6 (1<<8) ///< Memory must be mapped to PID6 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID7 (1<<9) ///< Memory must be mapped to PID7 memory space (PIDs are not currently used)
#define MALLOC_CAP_SPIRAM (1<<10) ///< Memory must be in SPI RAM
#define MALLOC_CAP_INTERNAL (1<<11) ///< Memory must be internal; specifically it should not disappear when flash/spiram cache is switched off
#define MALLOC_CAP_DEFAULT (1<<12) ///< Memory can be returned in a non-capability-specific memory allocation (e.g. malloc(), calloc()) call
#define MALLOC_CAP_IRAM_8BIT (1<<13) ///< Memory must be in IRAM and allow unaligned access
#define MALLOC_CAP_RETENTION (1<<14) ///< Memory must be able to accessed by retention DMA
#define MALLOC_CAP_RTCRAM (1<<15) ///< Memory must be in RTC fast memory
#define MALLOC_CAP_TCM (1<<16) ///< Memory must be in TCM memory
#define MALLOC_CAP_DMA_DESC_AHB (1<<17) ///< Memory must be capable of containing AHB DMA descriptors
#define MALLOC_CAP_DMA_DESC_AXI (1<<18) ///< Memory must be capable of containing AXI DMA descriptors
#define MALLOC_CAP_CACHE_ALIGNED (1<<19) ///< Memory must be aligned to the cache line size of any intermediate caches
#define MALLOC_CAP_INVALID (1<<31) ///< Memory can't be used / list end marker
/**
* @brief callback called when an allocation operation fails, if registered
* @param size in bytes of failed allocation
* @param caps capabilities requested of failed allocation
* @param function_name function which generated the failure
*/
typedef void (*esp_alloc_failed_hook_t) (size_t size, uint32_t caps, const char * function_name);
/**
* @brief registers a callback function to be invoked if a memory allocation operation fails
* @param callback caller defined callback to be invoked
* @return ESP_OK if callback was registered.
*/
esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback);
#ifdef CONFIG_HEAP_USE_HOOKS
/**
* @brief callback called after every allocation
* @param ptr the allocated memory
* @param size in bytes of the allocation
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type of memory allocated.
* @note this hook is called on the same thread as the allocation, which may be within a low level operation.
* You should refrain from doing heavy work, logging, flash writes, or any locking.
*/
__attribute__((weak)) HEAP_IRAM_ATTR void esp_heap_trace_alloc_hook(void* ptr, size_t size, uint32_t caps);
/**
* @brief callback called after every free
* @param ptr the memory that was freed
* @note this hook is called on the same thread as the allocation, which may be within a low level operation.
* You should refrain from doing heavy work, logging, flash writes, or any locking.
*/
__attribute__((weak)) HEAP_IRAM_ATTR void esp_heap_trace_free_hook(void* ptr);
#endif
/**
* @brief Allocate a chunk of memory which has the given capabilities
*
* Equivalent semantics to libc malloc(), for capability-aware memory.
*
* @param size Size, in bytes, of the amount of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory to be returned
*
* @return A pointer to the memory allocated on success, NULL on failure
*/
void *heap_caps_malloc(size_t size, uint32_t caps);
/**
* @brief Free memory previously allocated via heap_caps_malloc() or heap_caps_realloc().
*
* Equivalent semantics to libc free(), for capability-aware memory.
*
* In IDF, ``free(p)`` is equivalent to ``heap_caps_free(p)``.
*
* @param ptr Pointer to memory previously returned from heap_caps_malloc() or heap_caps_realloc(). Can be NULL.
*/
void heap_caps_free( void *ptr);
/**
* @brief Reallocate memory previously allocated via heap_caps_malloc() or heap_caps_realloc().
*
* Equivalent semantics to libc realloc(), for capability-aware memory.
*
* In IDF, ``realloc(p, s)`` is equivalent to ``heap_caps_realloc(p, s, MALLOC_CAP_8BIT)``.
*
* 'caps' parameter can be different to the capabilities that any original 'ptr' was allocated with. In this way,
* realloc can be used to "move" a buffer if necessary to ensure it meets a new set of capabilities.
*
* @param ptr Pointer to previously allocated memory, or NULL for a new allocation.
* @param size Size of the new buffer requested, or 0 to free the buffer.
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory desired for the new allocation.
*
* @return Pointer to a new buffer of size 'size' with capabilities 'caps', or NULL if allocation failed.
*/
void *heap_caps_realloc( void *ptr, size_t size, uint32_t caps);
/**
* @brief Allocate an aligned chunk of memory which has the given capabilities
*
* Equivalent semantics to libc aligned_alloc(), for capability-aware memory.
* @param alignment How the pointer received needs to be aligned
* must be a power of two
* @param size Size, in bytes, of the amount of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory to be returned
*
* @return A pointer to the memory allocated on success, NULL on failure
*
*
*/
void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps);
/**
* @brief Used to deallocate memory previously allocated with heap_caps_aligned_alloc
*
* @param ptr Pointer to the memory allocated
* @note This function is deprecated, please consider using heap_caps_free() instead
*/
void __attribute__((deprecated)) heap_caps_aligned_free(void *ptr);
/**
* @brief Allocate an aligned chunk of memory which has the given capabilities. The initialized value in the memory is set to zero.
*
* @param alignment How the pointer received needs to be aligned
* must be a power of two
* @param n Number of continuing chunks of memory to allocate
* @param size Size, in bytes, of a chunk of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory to be returned
*
* @return A pointer to the memory allocated on success, NULL on failure
*
*/
void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps);
/**
* @brief Allocate a chunk of memory which has the given capabilities. The initialized value in the memory is set to zero.
*
* Equivalent semantics to libc calloc(), for capability-aware memory.
*
* In IDF, ``calloc(p)`` is equivalent to ``heap_caps_calloc(p, MALLOC_CAP_8BIT)``.
*
* @param n Number of continuing chunks of memory to allocate
* @param size Size, in bytes, of a chunk of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory to be returned
*
* @return A pointer to the memory allocated on success, NULL on failure
*/
void *heap_caps_calloc(size_t n, size_t size, uint32_t caps);
/**
* @brief Get the total size of all the regions that have the given capabilities
*
* This function takes all regions capable of having the given capabilities allocated in them
* and adds up the total space they have.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
* @return total size in bytes
*/
size_t heap_caps_get_total_size(uint32_t caps);
/**
* @brief Get the total free size of all the regions that have the given capabilities
*
* This function takes all regions capable of having the given capabilities allocated in them
* and adds up the free space they have.
*
* @note Note that because of heap fragmentation it is probably not possible to allocate a single block of memory
* of this size. Use heap_caps_get_largest_free_block() for this purpose.
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
* @return Amount of free bytes in the regions
*/
size_t heap_caps_get_free_size( uint32_t caps );
/**
* @brief Get the total minimum free memory of all regions with the given capabilities
*
* This adds all the low watermarks of the regions capable of delivering the memory
* with the given capabilities.
*
* @note Note the result may be less than the global all-time minimum available heap of this kind, as "low watermarks" are
* tracked per-region. Individual regions' heaps may have reached their "low watermarks" at different points in time. However,
* this result still gives a "worst case" indication for all-time minimum free heap.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
* @return Amount of free bytes in the regions
*/
size_t heap_caps_get_minimum_free_size( uint32_t caps );
/**
* @brief Get the largest free block of memory able to be allocated with the given capabilities.
*
* Returns the largest value of ``s`` for which ``heap_caps_malloc(s, caps)`` will succeed.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
* @return Size of the largest free block in bytes.
*/
size_t heap_caps_get_largest_free_block( uint32_t caps );
/**
* @brief Start monitoring the value of minimum_free_bytes from the moment this
* function is called instead of from startup.
*
* @note This allows to detect local lows of the minimum_free_bytes value
* that wouldn't be detected otherwise.
*
* @return esp_err_t ESP_OK if the function executed properly
* ESP_FAIL if called when monitoring already active
*/
esp_err_t heap_caps_monitor_local_minimum_free_size_start(void);
/**
* @brief Stop monitoring the value of minimum_free_bytes. After this call
* the minimum_free_bytes value calculated from startup will be returned in
* heap_caps_get_info and heap_caps_get_minimum_free_size.
*
* @return esp_err_t ESP_OK if the function executed properly
* ESP_FAIL if called when monitoring not active
*/
esp_err_t heap_caps_monitor_local_minimum_free_size_stop(void);
/**
* @brief Get heap info for all regions with the given capabilities.
*
* Calls multi_heap_info() on all heaps which share the given capabilities. The information returned is an aggregate
* across all matching heaps. The meanings of fields are the same as defined for multi_heap_info_t, except that
* ``minimum_free_bytes`` has the same caveats described in heap_caps_get_minimum_free_size().
*
* @param info Pointer to a structure which will be filled with relevant
* heap metadata.
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
*/
void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps );
/**
* @brief Print a summary of all memory with the given capabilities.
*
* Calls multi_heap_info on all heaps which share the given capabilities, and
* prints a two-line summary for each, then a total summary.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
*/
void heap_caps_print_heap_info( uint32_t caps );
/**
* @brief Check integrity of all heap memory in the system.
*
* Calls multi_heap_check on all heaps. Optionally print errors if heaps are corrupt.
*
* Calling this function is equivalent to calling heap_caps_check_integrity
* with the caps argument set to MALLOC_CAP_INVALID.
*
* @param print_errors Print specific errors if heap corruption is found.
*
* @note Please increase the value of `CONFIG_ESP_INT_WDT_TIMEOUT_MS` when using this API
* with PSRAM enabled.
*
* @return True if all heaps are valid, False if at least one heap is corrupt.
*/
bool heap_caps_check_integrity_all(bool print_errors);
/**
* @brief Check integrity of all heaps with the given capabilities.
*
* Calls multi_heap_check on all heaps which share the given capabilities. Optionally
* print errors if the heaps are corrupt.
*
* See also heap_caps_check_integrity_all to check all heap memory
* in the system and heap_caps_check_integrity_addr to check memory
* around a single address.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
* @param print_errors Print specific errors if heap corruption is found.
*
* @note Please increase the value of `CONFIG_ESP_INT_WDT_TIMEOUT_MS` when using this API
* with PSRAM capability flag.
*
* @return True if all heaps are valid, False if at least one heap is corrupt.
*/
bool heap_caps_check_integrity(uint32_t caps, bool print_errors);
/**
* @brief Check integrity of heap memory around a given address.
*
* This function can be used to check the integrity of a single region of heap memory,
* which contains the given address.
*
* This can be useful if debugging heap integrity for corruption at a known address,
* as it has a lower overhead than checking all heap regions. Note that if the corrupt
* address moves around between runs (due to timing or other factors) then this approach
* won't work, and you should call heap_caps_check_integrity or
* heap_caps_check_integrity_all instead.
*
* @note The entire heap region around the address is checked, not only the adjacent
* heap blocks.
*
* @param addr Address in memory. Check for corruption in region containing this address.
* @param print_errors Print specific errors if heap corruption is found.
*
* @return True if the heap containing the specified address is valid,
* False if at least one heap is corrupt or the address doesn't belong to a heap region.
*/
bool heap_caps_check_integrity_addr(intptr_t addr, bool print_errors);
/**
* @brief Enable malloc() in external memory and set limit below which
* malloc() attempts are placed in internal memory.
*
* When external memory is in use, the allocation strategy is to initially try to
* satisfy smaller allocation requests with internal memory and larger requests
* with external memory. This sets the limit between the two, as well as generally
* enabling allocation in external memory.
*
* @param limit Limit, in bytes.
*/
void heap_caps_malloc_extmem_enable(size_t limit);
/**
* @brief Allocate a chunk of memory as preference in decreasing order.
*
* @attention The variable parameters are bitwise OR of MALLOC_CAP_* flags indicating the type of memory.
* This API prefers to allocate memory with the first parameter. If failed, allocate memory with
* the next parameter. It will try in this order until allocating a chunk of memory successfully
* or fail to allocate memories with any of the parameters.
*
* @param size Size, in bytes, of the amount of memory to allocate
* @param num Number of variable parameters
*
* @return A pointer to the memory allocated on success, NULL on failure
*/
void *heap_caps_malloc_prefer( size_t size, size_t num, ... );
/**
* @brief Reallocate a chunk of memory as preference in decreasing order.
*
* @param ptr Pointer to previously allocated memory, or NULL for a new allocation.
* @param size Size of the new buffer requested, or 0 to free the buffer.
* @param num Number of variable parameters
*
* @return Pointer to a new buffer of size 'size', or NULL if allocation failed.
*/
void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ... );
/**
* @brief Allocate a chunk of memory as preference in decreasing order.
*
* @param n Number of continuing chunks of memory to allocate
* @param size Size, in bytes, of a chunk of memory to allocate
* @param num Number of variable parameters
*
* @return A pointer to the memory allocated on success, NULL on failure
*/
void *heap_caps_calloc_prefer( size_t n, size_t size, size_t num, ... );
/**
* @brief Dump the full structure of all heaps with matching capabilities.
*
* Prints a large amount of output to serial (because of locking limitations,
* the output bypasses stdout/stderr). For each (variable sized) block
* in each matching heap, the following output is printed on a single line:
*
* - Block address (the data buffer returned by malloc is 4 bytes after this
* if heap debugging is set to Basic, or 8 bytes otherwise).
* - Data size (the data size may be larger than the size requested by malloc,
* either due to heap fragmentation or because of heap debugging level).
* - Address of next block in the heap.
* - If the block is free, the address of the next free block is also printed.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*/
void heap_caps_dump(uint32_t caps);
/**
* @brief Dump the full structure of all heaps.
*
* Covers all registered heaps. Prints a large amount of output to serial.
*
* Output is the same as for heap_caps_dump.
*
*/
void heap_caps_dump_all(void);
/**
* @brief Return the size that a particular pointer was allocated with.
*
* @param ptr Pointer to currently allocated heap memory. Must be a pointer value previously
* returned by heap_caps_malloc, malloc, calloc, etc. and not yet freed.
*
* @note The app will crash with an assertion failure if the pointer is not valid.
*
* @return Size of the memory allocated at this block.
*
*/
size_t heap_caps_get_allocated_size( void *ptr );
/**
* @brief Structure used to store heap related data passed to
* the walker callback function
*/
typedef struct walker_heap_info {
intptr_t start; ///< Start address of the heap in which the block is located
intptr_t end; ///< End address of the heap in which the block is located
} walker_heap_into_t;
/**
* @brief Structure used to store block related data passed to
* the walker callback function
*/
typedef struct walker_block_info {
void *ptr; ///< Pointer to the block data
size_t size; ///< The size of the block
bool used; ///< Block status. True: used, False: free
} walker_block_info_t;
/**
* @brief Function callback used to get information of memory block
* during calls to heap_caps_walk or heap_caps_walk_all
*
* @param heap_info See walker_heap_into_t
* @param block_info See walker_block_info_t
* @param user_data Opaque pointer to user defined data
*
* @return True to proceed with the heap traversal
* False to stop the traversal of the current heap and continue
* with the traversal of the next heap (if any)
*/
typedef bool (*heap_caps_walker_cb_t)(walker_heap_into_t heap_info, walker_block_info_t block_info, void *user_data);
/**
* @brief Function called to walk through the heaps with the given set of capabilities
*
* @param caps The set of capabilities assigned to the heaps to walk through
* @param walker_func Callback called for each block of the heaps being traversed
* @param user_data Opaque pointer to user defined data
*/
void heap_caps_walk(uint32_t caps, heap_caps_walker_cb_t walker_func, void *user_data);
/**
* @brief Function called to walk through all heaps defined by the heap component
*
* @param walker_func Callback called for each block of the heaps being traversed
* @param user_data Opaque pointer to user defined data
*/
void heap_caps_walk_all(heap_caps_walker_cb_t walker_func, void *user_data);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,111 @@
/*
* SPDX-FileCopyrightText: 2017-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "esp_heap_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initialize the capability-aware heap allocator.
*
* This is called once in the IDF startup code. Do not call it
* at other times.
*/
void heap_caps_init(void);
/**
* @brief Enable heap(s) in memory regions where the startup stacks are located.
*
* On startup, the pro/app CPUs have a certain memory region they use as stack, so we
* cannot do allocations in the regions these stack frames are. When FreeRTOS is
* completely started, they do not use that memory anymore and heap(s) there can
* be enabled.
*/
void heap_caps_enable_nonos_stack_heaps(void);
/**
* @brief Add a region of memory to the collection of heaps at runtime.
*
* Most memory regions are defined in soc_memory_layout.c for the SoC,
* and are registered via heap_caps_init(). Some regions can't be used
* immediately and are later enabled via heap_caps_enable_nonos_stack_heaps().
*
* Call this function to add a region of memory to the heap at some later time.
*
* This function does not consider any of the "reserved" regions or other data in soc_memory_layout, caller needs to
* consider this themselves.
*
* All memory within the region specified by start & end parameters must be otherwise unused.
*
* The capabilities of the newly registered memory will be determined by the start address, as looked up in the regions
* specified in soc_memory_layout.c.
*
* Use heap_caps_add_region_with_caps() to register a region with custom capabilities.
*
* @note Please refer to following example for memory regions allowed for addition to heap based on an existing region
* (address range for demonstration purpose only):
@verbatim
Existing region: 0x1000 <-> 0x3000
New region: 0x1000 <-> 0x3000 (Allowed)
New region: 0x1000 <-> 0x2000 (Allowed)
New region: 0x0000 <-> 0x1000 (Allowed)
New region: 0x3000 <-> 0x4000 (Allowed)
New region: 0x0000 <-> 0x2000 (NOT Allowed)
New region: 0x0000 <-> 0x4000 (NOT Allowed)
New region: 0x1000 <-> 0x4000 (NOT Allowed)
New region: 0x2000 <-> 0x4000 (NOT Allowed)
@endverbatim
*
* @param start Start address of new region.
* @param end End address of new region.
*
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if a parameter is invalid, ESP_ERR_NOT_FOUND if the
* specified start address doesn't reside in a known region, or any error returned by heap_caps_add_region_with_caps().
*/
esp_err_t heap_caps_add_region(intptr_t start, intptr_t end);
/**
* @brief Add a region of memory to the collection of heaps at runtime, with custom capabilities.
*
* Similar to heap_caps_add_region(), only custom memory capabilities are specified by the caller.
*
* @note Please refer to following example for memory regions allowed for addition to heap based on an existing region
* (address range for demonstration purpose only):
@verbatim
Existing region: 0x1000 <-> 0x3000
New region: 0x1000 <-> 0x3000 (Allowed)
New region: 0x1000 <-> 0x2000 (Allowed)
New region: 0x0000 <-> 0x1000 (Allowed)
New region: 0x3000 <-> 0x4000 (Allowed)
New region: 0x0000 <-> 0x2000 (NOT Allowed)
New region: 0x0000 <-> 0x4000 (NOT Allowed)
New region: 0x1000 <-> 0x4000 (NOT Allowed)
New region: 0x2000 <-> 0x4000 (NOT Allowed)
@endverbatim
*
* @param caps Ordered array of capability masks for the new region, in order of priority. Must have length
* SOC_MEMORY_TYPE_NO_PRIOS. Does not need to remain valid after the call returns.
* @param start Start address of new region.
* @param end End address of new region.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if a parameter is invalid
* - ESP_ERR_NO_MEM if no memory to register new heap.
* - ESP_ERR_INVALID_SIZE if the memory region is too small to fit a heap
* - ESP_FAIL if region overlaps the start and/or end of an existing region
*/
esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t start, intptr_t end);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,96 @@
/*
* SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#ifdef CONFIG_HEAP_TASK_TRACKING
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifdef __cplusplus
extern "C" {
#endif
// This macro controls how much space is provided for partitioning the per-task
// heap allocation info according to one or more sets of heap capabilities.
#define NUM_HEAP_TASK_CAPS 4
/** @brief Structure to collect per-task heap allocation totals partitioned by selected caps */
typedef struct {
TaskHandle_t task; ///< Task to which these totals belong
size_t size[NUM_HEAP_TASK_CAPS]; ///< Total allocations partitioned by selected caps
size_t count[NUM_HEAP_TASK_CAPS]; ///< Number of blocks partitioned by selected caps
} heap_task_totals_t;
/** @brief Structure providing details about a block allocated by a task */
typedef struct {
TaskHandle_t task; ///< Task that allocated the block
void *address; ///< User address of allocated block
uint32_t size; ///< Size of the allocated block
} heap_task_block_t;
/** @brief Structure to provide parameters to heap_caps_get_per_task_info
*
* The 'caps' and 'mask' arrays allow partitioning the per-task heap allocation
* totals by selected sets of heap region capabilities so that totals for
* multiple regions can be accumulated in one scan. The capabilities flags for
* each region ANDed with mask[i] are compared to caps[i] in order; the
* allocations in that region are added to totals->size[i] and totals->count[i]
* for the first i that matches. To collect the totals without any
* partitioning, set mask[0] and caps[0] both to zero. The allocation totals
* are returned in the 'totals' array of heap_task_totals_t structs. To allow
* easily comparing the totals array between consecutive calls, that array can
* be left populated from one call to the next so the order of tasks is the
* same even if some tasks have freed their blocks or have been deleted. The
* number of blocks prepopulated is given by num_totals, which is updated upon
* return. If there are more tasks with allocations than the capacity of the
* totals array (given by max_totals), information for the excess tasks will be
* not be collected. The totals array pointer can be NULL if the totals are
* not desired.
*
* The 'tasks' array holds a list of handles for tasks whose block details are
* to be returned in the 'blocks' array of heap_task_block_t structs. If the
* tasks array pointer is NULL, block details for all tasks will be returned up
* to the capacity of the buffer array, given by max_blocks. The function
* return value tells the number of blocks filled into the array. The blocks
* array pointer can be NULL if block details are not desired, or max_blocks
* can be set to zero.
*/
typedef struct {
int32_t caps[NUM_HEAP_TASK_CAPS]; ///< Array of caps for partitioning task totals
int32_t mask[NUM_HEAP_TASK_CAPS]; ///< Array of masks under which caps must match
TaskHandle_t *tasks; ///< Array of tasks whose block info is returned
size_t num_tasks; ///< Length of tasks array
heap_task_totals_t *totals; ///< Array of structs to collect task totals
size_t *num_totals; ///< Number of task structs currently in array
size_t max_totals; ///< Capacity of array of task totals structs
heap_task_block_t *blocks; ///< Array of task block details structs
size_t max_blocks; ///< Capacity of array of task block info structs
} heap_task_info_params_t;
/**
* @brief Return per-task heap allocation totals and lists of blocks.
*
* For each task that has allocated memory from the heap, return totals for
* allocations within regions matching one or more sets of capabilities.
*
* Optionally also return an array of structs providing details about each
* block allocated by one or more requested tasks, or by all tasks.
*
* @param params Structure to hold all the parameters for the function
* (@see heap_task_info_params_t).
* @return Number of block detail structs returned (@see heap_task_block_t).
*/
extern size_t heap_caps_get_per_task_info(heap_task_info_params_t *params);
#ifdef __cplusplus
}
#endif
#endif // CONFIG_HEAP_TASK_TRACKING

View File

@@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#include "sys/queue.h"
#include <stdbool.h>
#include <stdint.h>
#include <esp_err.h>
#ifdef __cplusplus
extern "C" {
#endif
#if !defined(CONFIG_HEAP_TRACING) && !defined(HEAP_TRACE_SRCFILE)
#warning "esp_heap_trace.h is included but heap tracing is disabled in menuconfig, functions are no-ops"
#endif
#ifndef CONFIG_HEAP_TRACING_STACK_DEPTH
#define CONFIG_HEAP_TRACING_STACK_DEPTH 0
#endif
typedef enum {
HEAP_TRACE_ALL,
HEAP_TRACE_LEAKS,
} heap_trace_mode_t;
/**
* @brief Trace record data type. Stores information about an allocated region of memory.
*/
typedef struct heap_trace_record_t {
uint32_t ccount; ///< CCOUNT of the CPU when the allocation was made. LSB (bit value 1) is the CPU number (0 or 1).
void *address; ///< Address which was allocated. If NULL, then this record is empty.
size_t size; ///< Size of the allocation
bool freed; ///< State of the allocation (false if not freed, true if freed)
void *alloced_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which allocated the memory.
void *freed_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which freed the memory (all zero if not freed.)
#if CONFIG_HEAP_TRACING_STANDALONE
TAILQ_ENTRY(heap_trace_record_t) tailq_list; ///< Linked list: prev & next records
#if CONFIG_HEAP_TRACE_HASH_MAP
SLIST_ENTRY(heap_trace_record_t) slist_hashmap; ///< Linked list: next in hashmap entry list
#endif // CONFIG_HEAP_TRACE_HASH_MAP
#endif // CONFIG_HEAP_TRACING_STANDALONE
} heap_trace_record_t;
/**
* @brief Stores information about the result of a heap trace.
*/
typedef struct {
heap_trace_mode_t mode; ///< The heap trace mode we just completed / are running
size_t total_allocations; ///< The total number of allocations made during tracing
size_t total_frees; ///< The total number of frees made during tracing
size_t count; ///< The number of records in the internal buffer
size_t capacity; ///< The capacity of the internal buffer
size_t high_water_mark; ///< The maximum value that 'count' got to
size_t has_overflowed; ///< True if the internal buffer overflowed at some point
#if CONFIG_HEAP_TRACE_HASH_MAP
size_t total_hashmap_hits; ///< If hashmap is used, the total number of hits
size_t total_hashmap_miss; ///< If hashmap is used, the total number of misses (possibly due to overflow)
#endif
} heap_trace_summary_t;
/**
* @brief Initialise heap tracing in standalone mode.
*
* This function must be called before any other heap tracing functions.
*
* To disable heap tracing and allow the buffer to be freed, stop tracing and then call heap_trace_init_standalone(NULL, 0);
*
* @param record_buffer Provide a buffer to use for heap trace data.
* Note: External RAM is allowed, but it prevents recording allocations made from ISR's.
* @param num_records Size of the heap trace buffer, as number of record structures.
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing is currently in progress.
* - ESP_OK Heap tracing initialised successfully.
*/
esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records);
/**
* @brief Initialise heap tracing in host-based mode.
*
* This function must be called before any other heap tracing functions.
*
* @return
* - ESP_ERR_INVALID_STATE Heap tracing is currently in progress.
* - ESP_OK Heap tracing initialised successfully.
*/
esp_err_t heap_trace_init_tohost(void);
/**
* @brief Start heap tracing. All heap allocations & frees will be traced, until heap_trace_stop() is called.
*
* @note heap_trace_init_standalone() must be called to provide a valid buffer, before this function is called.
*
* @note Calling this function while heap tracing is running will reset the heap trace state and continue tracing.
*
* @param mode Mode for tracing.
* - HEAP_TRACE_ALL means all heap allocations and frees are traced.
* - HEAP_TRACE_LEAKS means only suspected memory leaks are traced. (When memory is freed, the record is removed from the trace buffer.)
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE A non-zero-length buffer has not been set via heap_trace_init_standalone().
* - ESP_OK Tracing is started.
*/
esp_err_t heap_trace_start(heap_trace_mode_t mode);
/**
* @brief Stop heap tracing.
*
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was not in progress.
* - ESP_OK Heap tracing stopped.
*/
esp_err_t heap_trace_stop(void);
/**
* @brief Pause heap tracing of allocations.
*
* @note This function puts the heap tracing in the state where the new allocations
* will no longer be traced but the free will still be. This can be used to e.g.,
* strategically monitor a set of allocations to make sure each of them will get freed
* without polluting the list of records with unwanted allocations.
*
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was not in progress.
* - ESP_OK Heap tracing paused.
*/
esp_err_t heap_trace_alloc_pause(void);
/**
* @brief Resume heap tracing which was previously stopped.
*
* Unlike heap_trace_start(), this function does not clear the
* buffer of any pre-existing trace records.
*
* The heap trace mode is the same as when heap_trace_start() was
* last called (or HEAP_TRACE_ALL if heap_trace_start() was never called).
*
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was already started.
* - ESP_OK Heap tracing resumed.
*/
esp_err_t heap_trace_resume(void);
/**
* @brief Return number of records in the heap trace buffer
*
* It is safe to call this function while heap tracing is running.
*/
size_t heap_trace_get_count(void);
/**
* @brief Return a raw record from the heap trace buffer
*
* @note It is safe to call this function while heap tracing is
* running, however in HEAP_TRACE_LEAK mode record indexing may
* skip entries unless heap tracing is stopped first.
*
* @param index Index (zero-based) of the record to return.
* @param[out] record Record where the heap trace record will be copied.
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was not initialised.
* - ESP_ERR_INVALID_ARG Index is out of bounds for current heap trace record count.
* - ESP_OK Record returned successfully.
*/
esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record);
/**
* @brief Dump heap trace record data to stdout
*
* @note It is safe to call this function while heap tracing is
* running, however in HEAP_TRACE_LEAK mode the dump may skip
* entries unless heap tracing is stopped first.
*/
void heap_trace_dump(void);
/**
* @brief Dump heap trace from the memory of the capabilities passed as parameter.
*
* @param caps Capability(ies) of the memory from which to dump the trace.
* Set MALLOC_CAP_INTERNAL to dump heap trace data from internal memory.
* Set MALLOC_CAP_SPIRAM to dump heap trace data from PSRAM.
* Set both to dump both heap trace data.
*/
void heap_trace_dump_caps(const uint32_t caps);
/**
* @brief Get summary information about the result of a heap trace
*
* @note It is safe to call this function while heap tracing is running.
*/
esp_err_t heap_trace_summary(heap_trace_summary_t *summary);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2010-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "autoconf.h"
#define SOC_MEMORY_TYPE_NO_PRIOS 3
#ifdef __cplusplus
extern "C" {
#endif
/* Type descriptor holds a description for a particular type of memory on a particular SoC.
*/
typedef struct {
const char *name; ///< Name of this memory type
uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS]; ///< Capabilities for this memory type (as a prioritised set)
} soc_memory_type_desc_t;
/* Constant table of tag descriptors for all this SoC's tags */
extern const soc_memory_type_desc_t soc_memory_types[];
extern const size_t soc_memory_type_count;
/* Region descriptor holds a description for a particular region of memory on a particular SoC.
*/
typedef struct {
intptr_t start; ///< Start address of the region
size_t size; ///< Size of the region in bytes
size_t type; ///< Type of the region (index into soc_memory_types array)
intptr_t iram_address; ///< If non-zero, is equivalent address in IRAM
bool startup_stack; ///< If true, memory of this type is used for ROM stack during startup
} soc_memory_region_t;
extern const soc_memory_region_t soc_memory_regions[];
extern const size_t soc_memory_region_count;
/* Region descriptor holds a description for a particular region of
memory reserved on this SoC for a particular use (ie not available
for stack/heap usage.) */
typedef struct {
intptr_t start;
intptr_t end;
} soc_reserved_region_t;
/* Use this macro to reserved a fixed region of RAM (hardcoded addresses)
* for a particular purpose.
*
* Usually used to mark out memory addresses needed for hardware or ROM code
* purposes.
*
* Don't call this macro from user code which can use normal C static allocation
* instead.
*
* @param START Start address to be reserved.
* @param END One after the address of the last byte to be reserved. (ie length of
* the reserved region is (END - START) in bytes.
* @param NAME Name for the reserved region. Must be a valid variable name,
* unique to this source file.
*/
#define SOC_RESERVE_MEMORY_REGION(START, END, NAME) \
__attribute__((section(".reserved_memory_address"))) __attribute__((used)) \
static soc_reserved_region_t reserved_region_##NAME = { START, END };
/* Return available memory regions for this SoC. Each available memory
* region is a contiguous piece of memory which is not being used by
* static data, used by ROM code, or reserved by a component using
* the SOC_RESERVE_MEMORY_REGION() macro.
*
* This result is soc_memory_regions[] minus all regions reserved
* via the SOC_RESERVE_MEMORY_REGION() macro (which may also split
* some regions up.)
*
* At startup, all available memory returned by this function is
* registered as heap space.
*
* @note OS-level startup function only, not recommended to call from
* app code.
*
* @param regions Pointer to an array for reading available regions into.
* Size of the array should be at least the result of
* soc_get_available_memory_region_max_count(). Entries in the array
* will be ordered by memory address.
*
* @return Number of entries copied to 'regions'. Will be no greater than
* the result of soc_get_available_memory_region_max_count().
*/
size_t soc_get_available_memory_regions(soc_memory_region_t *regions);
/* Return the maximum number of available memory regions which could be
* returned by soc_get_available_memory_regions(). Used to size the
* array passed to that function.
*/
size_t soc_get_available_memory_region_max_count(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,14 @@
#ifndef __SOC_ATTR_H__
#define __SOC_ATTR_H__
// Forces code into IRAM instead of flash
#define IRAM_ATTR //__attribute__((section(".heap.code")))
// Forces a function to be inlined
#define FORCE_INLINE_ATTR static inline __attribute__((always_inline))
// Forces to not inline function
#define NOINLINE_ATTR __attribute__((noinline))
#endif /* __SOC_ATTR_H__ */

View File

@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef int esp_err_t;
/* Definitions for error constants. */
#define ESP_OK 0 /*!< esp_err_t value indicating success (no error) */
#define ESP_FAIL -1 /*!< Generic esp_err_t code indicating failure */
#define ESP_ERR_NO_MEM 0x101 /*!< Out of memory */
#define ESP_ERR_INVALID_ARG 0x102 /*!< Invalid argument */
#define ESP_ERR_INVALID_STATE 0x103 /*!< Invalid state */
#define ESP_ERR_INVALID_SIZE 0x104 /*!< Invalid size */
#define ESP_ERR_NOT_FOUND 0x105 /*!< Requested resource not found */
#define ESP_ERR_NOT_SUPPORTED 0x106 /*!< Operation or feature not supported */
#define ESP_ERR_TIMEOUT 0x107 /*!< Operation timed out */
#define ESP_ERR_INVALID_RESPONSE 0x108 /*!< Received response was invalid */
#define ESP_ERR_INVALID_CRC 0x109 /*!< CRC or checksum was invalid */
#define ESP_ERR_INVALID_VERSION 0x10A /*!< Version was invalid */
#define ESP_ERR_INVALID_MAC 0x10B /*!< MAC address was invalid */
#define ESP_ERR_NOT_FINISHED 0x10C /*!< Operation has not fully completed */
#define ESP_ERR_NOT_ALLOWED 0x10D /*!< Operation is not allowed */
#define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */
#define ESP_ERR_MESH_BASE 0x4000 /*!< Starting number of MESH error codes */
#define ESP_ERR_FLASH_BASE 0x6000 /*!< Starting number of flash error codes */
#define ESP_ERR_HW_CRYPTO_BASE 0xc000 /*!< Starting number of HW cryptography module error codes */
#define ESP_ERR_MEMPROT_BASE 0xd000 /*!< Starting number of Memory Protection API error codes */
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,11 @@
#include <stdarg.h>
#if CONFIG_MODULE_SHELL
#include "shell.h"
#endif
#ifndef CRLF
#define CRLF "\r\n"
#endif
#define ESP_EARLY_LOGI(TAG, FMT, ...) printk("[heap] " FMT CRLF, ##__VA_ARGS__)
#define ESP_EARLY_LOGD(TAG, FMT, ...) printk("[heap] " FMT CRLF, ##__VA_ARGS__)

View File

@@ -0,0 +1,235 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
/* multi_heap is a heap implementation for handling multiple
heterogenous heaps in a single program.
Any contiguous block of memory can be registered as a heap.
*/
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Opaque handle to a registered heap */
typedef struct multi_heap_info *multi_heap_handle_t;
/**
* @brief allocate a chunk of memory with specific alignment
*
* @param heap Handle to a registered heap.
* @param size size in bytes of memory chunk
* @param alignment how the memory must be aligned
*
* @return pointer to the memory allocated, NULL on failure
*/
void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment);
/** @brief malloc() a buffer in a given heap
*
* Semantics are the same as standard malloc(), only the returned buffer will be allocated in the specified heap.
*
* @param heap Handle to a registered heap.
* @param size Size of desired buffer.
*
* @return Pointer to new memory, or NULL if allocation fails.
*/
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size);
/** @brief free() a buffer aligned in a given heap.
*
* @param heap Handle to a registered heap.
* @param p NULL, or a pointer previously returned from multi_heap_aligned_alloc() for the same heap.
* @note This function is deprecated, consider using multi_heap_free() instead
*/
void __attribute__((deprecated)) multi_heap_aligned_free(multi_heap_handle_t heap, void *p);
/** @brief free() a buffer in a given heap.
*
* Semantics are the same as standard free(), only the argument 'p' must be NULL or have been allocated in the specified heap.
*
* @param heap Handle to a registered heap.
* @param p NULL, or a pointer previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
*/
void multi_heap_free(multi_heap_handle_t heap, void *p);
/** @brief realloc() a buffer in a given heap.
*
* Semantics are the same as standard realloc(), only the argument 'p' must be NULL or have been allocated in the specified heap.
*
* @param heap Handle to a registered heap.
* @param p NULL, or a pointer previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
* @param size Desired new size for buffer.
*
* @return New buffer of 'size' containing contents of 'p', or NULL if reallocation failed.
*/
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size);
/** @brief Return the size that a particular pointer was allocated with.
*
* @param heap Handle to a registered heap.
* @param p Pointer, must have been previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
*
* @return Size of the memory allocated at this block. May be more than the original size argument, due
* to padding and minimum block sizes.
*/
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p);
/** @brief Register a new heap for use
*
* This function initialises a heap at the specified address, and returns a handle for future heap operations.
*
* There is no equivalent function for deregistering a heap - if all blocks in the heap are free, you can immediately start using the memory for other purposes.
*
* @param start Start address of the memory to use for a new heap.
* @param size Size (in bytes) of the new heap.
*
* @return Handle of a new heap ready for use, or NULL if the heap region was too small to be initialised.
*/
multi_heap_handle_t multi_heap_register(void *start, size_t size);
/** @brief Associate a private lock pointer with a heap
*
* The lock argument is supplied to the MULTI_HEAP_LOCK() and MULTI_HEAP_UNLOCK() macros, defined in multi_heap_platform.h.
*
* The lock in question must be recursive.
*
* When the heap is first registered, the associated lock is NULL.
*
* @param heap Handle to a registered heap.
* @param lock Optional pointer to a locking structure to associate with this heap.
*/
void multi_heap_set_lock(multi_heap_handle_t heap, void* lock);
/** @brief Dump heap information to stdout
*
* For debugging purposes, this function dumps information about every block in the heap to stdout.
*
* @param heap Handle to a registered heap.
*/
void multi_heap_dump(multi_heap_handle_t heap);
/** @brief Check heap integrity
*
* Walks the heap and checks all heap data structures are valid. If any errors are detected, an error-specific message
* can be optionally printed to stderr. Print behaviour can be overridden at compile time by defining
* MULTI_CHECK_FAIL_PRINTF in multi_heap_platform.h.
*
* @note This function is not thread-safe as it sets a global variable with the value of print_errors.
*
* @param heap Handle to a registered heap.
* @param print_errors If true, errors will be printed to stderr.
* @return true if heap is valid, false otherwise.
*/
bool multi_heap_check(multi_heap_handle_t heap, bool print_errors);
/** @brief Return free heap size
*
* Returns the number of bytes available in the heap.
*
* Equivalent to the total_free_bytes member returned by multi_heap_get_heap_info().
*
* Note that the heap may be fragmented, so the actual maximum size for a single malloc() may be lower. To know this
* size, see the largest_free_block member returned by multi_heap_get_heap_info().
*
* @param heap Handle to a registered heap.
* @return Number of free bytes.
*/
size_t multi_heap_free_size(multi_heap_handle_t heap);
/** @brief Return the lifetime minimum free heap size
*
* Equivalent to the minimum_free_bytes member returned by multi_heap_get_info().
*
* Returns the lifetime "low watermark" of possible values returned from multi_free_heap_size(), for the specified
* heap.
*
* @param heap Handle to a registered heap.
* @return Number of free bytes.
*/
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap);
/** @brief Structure to access heap metadata via multi_heap_get_info */
typedef struct {
size_t total_free_bytes; ///< Total free bytes in the heap. Equivalent to multi_free_heap_size().
size_t total_allocated_bytes; ///< Total bytes allocated to data in the heap.
size_t largest_free_block; ///< Size of the largest free block in the heap. This is the largest malloc-able size.
size_t minimum_free_bytes; ///< Lifetime minimum free heap size. Equivalent to multi_minimum_free_heap_size().
size_t allocated_blocks; ///< Number of (variable size) blocks allocated in the heap.
size_t free_blocks; ///< Number of (variable size) free blocks in the heap.
size_t total_blocks; ///< Total number of (variable size) blocks in the heap.
} multi_heap_info_t;
/** @brief Return metadata about a given heap
*
* Fills a multi_heap_info_t structure with information about the specified heap.
*
* @param heap Handle to a registered heap.
* @param info Pointer to a structure to fill with heap metadata.
*/
void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info);
/**
* @brief Perform an aligned allocation from the provided offset
*
* @param heap The heap in which to perform the allocation
* @param size The size of the allocation
* @param alignment How the memory must be aligned
* @param offset The offset at which the alignment should start
* @return void* The ptr to the allocated memory
*/
void *multi_heap_aligned_alloc_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset);
/**
* @brief Reset the minimum_free_bytes value (setting it to free_bytes) and return the former value
*
* @param heap The heap in which the reset is taking place
* @return size_t the value of minimum_free_bytes before it is reset
*/
size_t multi_heap_reset_minimum_free_bytes(multi_heap_handle_t heap);
/**
* @brief Set the value of minimum_free_bytes to new_minimum_free_bytes_value or keep
* the current value of minimum_free_bytes if it is smaller than new_minimum_free_bytes_value
*
* @param heap The heap in which the restore is taking place
* @param new_minimum_free_bytes_value The value to restore the minimum_free_bytes to
*/
void multi_heap_restore_minimum_free_bytes(multi_heap_handle_t heap, const size_t new_minimum_free_bytes_value);
/**
* @brief Callback called when walking the given heap blocks of memory
*
* @param block_ptr Pointer to the block data
* @param block_size The size of the block
* @param block_used Block status. 0: free, 1: allocated
* @param user_data Opaque pointer to user defined data
*
* @return True if the walker is expected to continue the heap traversal
* False if the walker is expected to stop the traversal of the heap
*/
typedef bool (*multi_heap_walker_cb_t)(void *block_ptr, size_t block_size, int block_used, void *user_data);
/**
* @brief Call the tlsf_walk_pool function of the heap given as parameter with
* the walker function passed as parameter
*
* @param heap The heap to traverse
* @param walker_func The walker to trigger on each block of the heap
* @param user_data Opaque pointer to user defined data
*/
void multi_heap_walk(multi_heap_handle_t heap, multi_heap_walker_cb_t walker_func, void *user_data);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,9 @@
# Function placement in IRAM section
The heap component is compiled and linked in a way that minimizes the utilization of the IRAM section of memory without impacting the performance of its core functionalities. For this reason, the heap component API provided through [esp_heap_caps.h](./include/esp_heap_caps.h) and [esp_heap_caps_init.h](./include/esp_heap_caps_init.h) can be sorted into two sets of functions.
1. The performance related functions placed into the IRAM by using the `IRAM_ATTR` defined in [esp_attr.h](./../../components/esp_common/include/esp_attr.h) (e.g., `heap_caps_malloc`, `heap_caps_free`, `heap_caps_realloc`, etc.)
2. The functions that does not require the best of performance placed in the flash (e.g., `heap_caps_print_heap_info`, `heap_caps_dump`, `heap_caps_dump_all`, etc.)
With that in mind, all the functions defined in [multi_heap.c](./multi_heap.c), [multi_heap_poisoning.c](./multi_heap_poisoning.c) and [tlsf.c](./tlsf/tlsf.c) that are directly or indirectly called from one of the heap component API functions placed in IRAM have to also be placed in IRAM. Symmetrically, the functions directly or indirectly called from one of the heap component API functions placed in flash will also be placed in flash.

View File

@@ -0,0 +1,54 @@
[mapping:heap]
archive: libheap.a
entries:
if HEAP_PLACE_FUNCTION_INTO_FLASH = n:
if HEAP_TLSF_USE_ROM_IMPL = n:
tlsf:tlsf_block_size (noflash)
tlsf:tlsf_size (noflash)
tlsf:tlsf_alloc_overhead (noflash)
tlsf:tlsf_get_pool (noflash)
tlsf:tlsf_malloc (noflash)
tlsf:tlsf_memalign_offs (noflash)
tlsf:tlsf_memalign (noflash)
tlsf:tlsf_free (noflash)
tlsf:tlsf_realloc (noflash)
multi_heap:multi_heap_get_block_address_impl (noflash)
multi_heap:multi_heap_get_allocated_size_impl (noflash)
multi_heap:multi_heap_set_lock (noflash)
multi_heap:multi_heap_get_first_block (noflash)
multi_heap:multi_heap_get_next_block (noflash)
multi_heap:multi_heap_is_free (noflash)
multi_heap:multi_heap_malloc_impl (noflash)
multi_heap:multi_heap_free_impl (noflash)
multi_heap:multi_heap_realloc_impl (noflash)
multi_heap:multi_heap_aligned_alloc_impl_offs (noflash)
multi_heap:multi_heap_aligned_alloc_impl (noflash)
multi_heap:multi_heap_internal_lock (noflash)
multi_heap:multi_heap_internal_unlock (noflash)
multi_heap:assert_valid_block (noflash)
if HEAP_TLSF_USE_ROM_IMPL = y:
multi_heap:_multi_heap_lock (noflash)
multi_heap:_multi_heap_unlock (noflash)
multi_heap:multi_heap_in_rom_init (noflash)
if HEAP_POISONING_DISABLED = n:
multi_heap_poisoning:poison_allocated_region (noflash)
multi_heap_poisoning:verify_allocated_region (noflash)
multi_heap_poisoning:multi_heap_aligned_alloc (noflash)
multi_heap_poisoning:multi_heap_malloc (noflash)
multi_heap_poisoning:multi_heap_free (noflash)
multi_heap_poisoning:multi_heap_aligned_free (noflash)
multi_heap_poisoning:multi_heap_realloc (noflash)
multi_heap_poisoning:multi_heap_get_block_address (noflash)
multi_heap_poisoning:multi_heap_get_allocated_size (noflash)
multi_heap_poisoning:multi_heap_internal_check_block_poisoning (noflash)
multi_heap_poisoning:multi_heap_internal_poison_fill_region (noflash)
multi_heap_poisoning:multi_heap_aligned_alloc_offs (noflash)
else:
multi_heap:multi_heap_aligned_alloc_offs (noflash)
if HEAP_POISONING_COMPREHENSIVE = y:
multi_heap_poisoning:verify_fill_pattern (noflash)
multi_heap_poisoning:block_absorb_post_hook (noflash)

View File

@@ -0,0 +1,457 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/cdefs.h>
#include <sys/param.h>
#include "multi_heap.h"
#include "multi_heap_internal.h"
#include "tlsf.h"
#include "tlsf_block_functions.h"
/* Note: Keep platform-specific parts in this header, this source
file should depend on libc only */
#include "multi_heap_platform.h"
/* Defines compile-time configuration macros */
#include "multi_heap_config.h"
#if (!defined MULTI_HEAP_POISONING)
void *multi_heap_aligned_alloc_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset)
{
return multi_heap_aligned_alloc_impl_offs(heap, size, alignment, offset);
}
#if (!defined CONFIG_HEAP_TLSF_USE_ROM_IMPL)
/* if no heap poisoning, public API aliases directly to these implementations */
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
__attribute__((alias("multi_heap_malloc_impl")));
void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment)
__attribute__((alias("multi_heap_aligned_alloc_impl")));
void multi_heap_aligned_free(multi_heap_handle_t heap, void *p)
__attribute__((alias("multi_heap_free_impl")));
void multi_heap_free(multi_heap_handle_t heap, void *p)
__attribute__((alias("multi_heap_free_impl")));
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
__attribute__((alias("multi_heap_realloc_impl")));
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
__attribute__((alias("multi_heap_get_allocated_size_impl")));
multi_heap_handle_t multi_heap_register(void *start, size_t size)
__attribute__((alias("multi_heap_register_impl")));
void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info)
__attribute__((alias("multi_heap_get_info_impl")));
size_t multi_heap_free_size(multi_heap_handle_t heap)
__attribute__((alias("multi_heap_free_size_impl")));
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
__attribute__((alias("multi_heap_minimum_free_size_impl")));
void *multi_heap_get_block_address(multi_heap_block_handle_t block)
__attribute__((alias("multi_heap_get_block_address_impl")));
#endif // !CONFIG_HEAP_TLSF_USE_ROM_IMPL
#endif // !MULTI_HEAP_POISONING
#define ALIGN(X) ((X) & ~(sizeof(void *)-1))
#define ALIGN_UP(X) ALIGN((X)+sizeof(void *)-1)
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
typedef struct multi_heap_info {
void *lock;
size_t free_bytes;
size_t minimum_free_bytes;
size_t pool_size;
void* heap_data;
} heap_t;
#if CONFIG_HEAP_TLSF_USE_ROM_IMPL
void _multi_heap_lock(void *lock)
{
MULTI_HEAP_LOCK(lock);
}
void _multi_heap_unlock(void *lock)
{
MULTI_HEAP_UNLOCK(lock);
}
multi_heap_os_funcs_t multi_heap_os_funcs = {
.lock = _multi_heap_lock,
.unlock = _multi_heap_unlock,
};
void multi_heap_in_rom_init(void)
{
multi_heap_os_funcs_init(&multi_heap_os_funcs);
}
#else // CONFIG_HEAP_TLSF_USE_ROM_IMPL
/* Check a block is valid for this heap. Used to verify parameters. */
__attribute__((noinline)) NOCLONE_ATTR static void assert_valid_block(const heap_t *heap, const multi_heap_block_handle_t block)
{
pool_t pool = tlsf_get_pool(heap->heap_data);
void *ptr = block_to_ptr(block);
MULTI_HEAP_ASSERT((ptr >= pool) &&
(ptr < pool + heap->pool_size),
(uintptr_t)ptr);
}
void *multi_heap_get_block_address_impl(multi_heap_block_handle_t block)
{
return block_to_ptr(block);
}
size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p)
{
return tlsf_block_size(p);
}
multi_heap_handle_t multi_heap_register_impl(void *start_ptr, size_t size)
{
assert(start_ptr);
if(size < (sizeof(heap_t))) {
//Region too small to be a heap.
return NULL;
}
heap_t *result = (heap_t *)start_ptr;
size -= sizeof(heap_t);
/* Do not specify any maximum size for the allocations so that the default configuration is used */
const size_t max_bytes = 0;
result->heap_data = tlsf_create_with_pool(start_ptr + sizeof(heap_t), size, max_bytes);
if(!result->heap_data) {
return NULL;
}
result->lock = NULL;
result->free_bytes = size - tlsf_size(result->heap_data);
result->pool_size = size;
result->minimum_free_bytes = result->free_bytes;
return result;
}
void multi_heap_set_lock(multi_heap_handle_t heap, void *lock)
{
heap->lock = lock;
}
void multi_heap_internal_lock(multi_heap_handle_t heap)
{
MULTI_HEAP_LOCK(heap->lock);
}
void multi_heap_internal_unlock(multi_heap_handle_t heap)
{
MULTI_HEAP_UNLOCK(heap->lock);
}
multi_heap_block_handle_t multi_heap_get_first_block(multi_heap_handle_t heap)
{
assert(heap != NULL);
pool_t pool = tlsf_get_pool(heap->heap_data);
multi_heap_block_handle_t block = offset_to_block(pool, -(int)block_header_overhead);
return block;
}
multi_heap_block_handle_t multi_heap_get_next_block(multi_heap_handle_t heap, multi_heap_block_handle_t block)
{
assert(heap != NULL);
assert_valid_block(heap, block);
multi_heap_block_handle_t next = block_next(block);
if(block_size(next) == 0) {
//Last block:
return NULL;
} else {
return next;
}
}
bool multi_heap_is_free(multi_heap_block_handle_t block)
{
return block_is_free(block);
}
void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size)
{
if (size == 0 || heap == NULL) {
return NULL;
}
multi_heap_internal_lock(heap);
void *result = tlsf_malloc(heap->heap_data, size);
if(result) {
heap->free_bytes -= tlsf_block_size(result);
heap->free_bytes -= tlsf_alloc_overhead();
if (heap->free_bytes < heap->minimum_free_bytes) {
heap->minimum_free_bytes = heap->free_bytes;
}
}
multi_heap_internal_unlock(heap);
return result;
}
void multi_heap_free_impl(multi_heap_handle_t heap, void *p)
{
if (heap == NULL || p == NULL) {
return;
}
assert_valid_block(heap, block_from_ptr(p));
multi_heap_internal_lock(heap);
heap->free_bytes += tlsf_block_size(p);
heap->free_bytes += tlsf_alloc_overhead();
tlsf_free(heap->heap_data, p);
multi_heap_internal_unlock(heap);
}
void *multi_heap_realloc_impl(multi_heap_handle_t heap, void *p, size_t size)
{
assert(heap != NULL);
if (p == NULL) {
return multi_heap_malloc_impl(heap, size);
}
assert_valid_block(heap, block_from_ptr(p));
if (heap == NULL) {
return NULL;
}
multi_heap_internal_lock(heap);
size_t previous_block_size = tlsf_block_size(p);
void *result = tlsf_realloc(heap->heap_data, p, size);
if(result) {
/* No need to subtract the tlsf_alloc_overhead() as it has already
* been subtracted when allocating the block at first with malloc */
heap->free_bytes += previous_block_size;
heap->free_bytes -= tlsf_block_size(result);
if (heap->free_bytes < heap->minimum_free_bytes) {
heap->minimum_free_bytes = heap->free_bytes;
}
}
multi_heap_internal_unlock(heap);
return result;
}
void *multi_heap_aligned_alloc_impl_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset)
{
if(heap == NULL) {
return NULL;
}
if(!size) {
return NULL;
}
//Alignment must be a power of two:
if(((alignment & (alignment - 1)) != 0) ||(!alignment)) {
return NULL;
}
multi_heap_internal_lock(heap);
void *result = tlsf_memalign_offs(heap->heap_data, alignment, size, offset);
if(result) {
heap->free_bytes -= tlsf_block_size(result);
heap->free_bytes -= tlsf_alloc_overhead();
if(heap->free_bytes < heap->minimum_free_bytes) {
heap->minimum_free_bytes = heap->free_bytes;
}
}
multi_heap_internal_unlock(heap);
return result;
}
void *multi_heap_aligned_alloc_impl(multi_heap_handle_t heap, size_t size, size_t alignment)
{
return multi_heap_aligned_alloc_impl_offs(heap, size, alignment, 0);
}
#ifdef MULTI_HEAP_POISONING
/*!
* @brief Global definition of print_errors set in multi_heap_check() when
* MULTI_HEAP_POISONING is active. Allows the transfer of the value to
* multi_heap_poisoning.c without having to propagate it to the tlsf submodule
* and back.
*/
static bool g_print_errors = false;
/*!
* @brief Definition of the weak function declared in TLSF repository.
* The call of this function execute a check for block poisoning on the memory
* chunk passed as parameter.
*
* @param start: pointer to the start of the memory region to check for corruption
* @param size: size of the memory region to check for corruption
* @param is_free: indicate if the pattern to use the fill the region should be
* an after free or after allocation pattern.
*
* @return bool: true if the the memory is not corrupted, false if the memory if corrupted.
*/
bool tlsf_check_hook(void *start, size_t size, bool is_free)
{
return multi_heap_internal_check_block_poisoning(start, size, is_free, g_print_errors);
}
#endif // MULTI_HEAP_POISONING
bool multi_heap_check(multi_heap_handle_t heap, bool print_errors)
{
bool valid = true;
assert(heap != NULL);
multi_heap_internal_lock(heap);
#ifdef MULTI_HEAP_POISONING
g_print_errors = print_errors;
#else
(void) print_errors;
#endif
if(tlsf_check(heap->heap_data)) {
valid = false;
}
if(tlsf_check_pool(tlsf_get_pool(heap->heap_data))) {
valid = false;
}
multi_heap_internal_unlock(heap);
return valid;
}
__attribute__((noinline)) static bool multi_heap_dump_tlsf(void *ptr, size_t size, int used, void *user)
{
(void)user;
MULTI_HEAP_STDERR_PRINTF("Block %p data, size: %d bytes, Free: %s \n", (void *)ptr, size, used ? "No" : "Yes");
return true;
}
void multi_heap_dump(multi_heap_handle_t heap)
{
assert(heap != NULL);
multi_heap_internal_lock(heap);
MULTI_HEAP_STDERR_PRINTF("Showing data for heap: %p \n", (void *)heap);
tlsf_walk_pool(tlsf_get_pool(heap->heap_data), multi_heap_dump_tlsf, NULL);
multi_heap_internal_unlock(heap);
}
size_t multi_heap_free_size_impl(multi_heap_handle_t heap)
{
if (heap == NULL) {
return 0;
}
return heap->free_bytes;
}
size_t multi_heap_minimum_free_size_impl(multi_heap_handle_t heap)
{
if (heap == NULL) {
return 0;
}
return heap->minimum_free_bytes;
}
__attribute__((noinline)) static bool multi_heap_get_info_tlsf(void* ptr, size_t size, int used, void* user)
{
multi_heap_info_t *info = user;
if(used) {
info->allocated_blocks++;
} else {
info->free_blocks++;
if(size > info->largest_free_block ) {
info->largest_free_block = size;
}
}
info->total_blocks++;
return true;
}
void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info)
{
uint32_t overhead;
memset(info, 0, sizeof(multi_heap_info_t));
if (heap == NULL) {
return;
}
multi_heap_internal_lock(heap);
tlsf_walk_pool(tlsf_get_pool(heap->heap_data), multi_heap_get_info_tlsf, info);
/* TLSF has an overhead per block. Calculate the total amount of overhead, it shall not be
* part of the allocated bytes */
overhead = info->allocated_blocks * tlsf_alloc_overhead();
info->total_allocated_bytes = (heap->pool_size - tlsf_size(heap->heap_data)) - heap->free_bytes - overhead;
info->minimum_free_bytes = heap->minimum_free_bytes;
info->total_free_bytes = heap->free_bytes;
info->largest_free_block = tlsf_fit_size(heap->heap_data, info->largest_free_block);
multi_heap_internal_unlock(heap);
}
void multi_heap_walk(multi_heap_handle_t heap, multi_heap_walker_cb_t walker_func, void *user_data)
{
assert(heap != NULL);
multi_heap_internal_lock(heap);
tlsf_walk_pool(tlsf_get_pool(heap->heap_data), walker_func, user_data);
multi_heap_internal_unlock(heap);
}
#endif // CONFIG_HEAP_TLSF_USE_ROM_IMPL
size_t multi_heap_reset_minimum_free_bytes(multi_heap_handle_t heap)
{
multi_heap_internal_lock(heap);
const size_t old_minimum = heap->minimum_free_bytes;
heap->minimum_free_bytes = heap->free_bytes;
multi_heap_internal_unlock(heap);
return old_minimum;
}
void multi_heap_restore_minimum_free_bytes(multi_heap_handle_t heap, const size_t new_minimum_free_bytes_value)
{
multi_heap_internal_lock(heap);
// keep the value of minimum_free_bytes if it is lower than the value passed as parameter
heap->minimum_free_bytes = MIN(heap->minimum_free_bytes, new_minimum_free_bytes_value);
multi_heap_internal_unlock(heap);
}

View File

@@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef ESP_PLATFORM
#include "sdkconfig.h"
#include "soc/soc.h"
#include "soc/soc_caps.h"
#endif
/* Configuration macros for multi-heap */
#ifdef CONFIG_HEAP_POISONING_LIGHT
#define MULTI_HEAP_POISONING
#endif
#ifdef CONFIG_HEAP_POISONING_COMPREHENSIVE
#define MULTI_HEAP_POISONING
#define MULTI_HEAP_POISONING_SLOW
#endif

View File

@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/* Define a noclone attribute when compiled with GCC as certain functions
* in the heap component should not be cloned by the compiler */
#if defined __has_attribute && __has_attribute(noclone)
#define NOCLONE_ATTR __attribute((noclone))
#else
#define NOCLONE_ATTR
#endif
/* Define a structure that contains some function pointers that point to OS-related functions.
An instance of this structure will be provided to the heap in ROM for use if needed.
*/
typedef struct {
void (*lock)(void *lock);
void (*unlock)(void *lock);
} multi_heap_os_funcs_t;
/** @brief Initialize structure pointer that points a structure that contains OS-related functions pointers.
*
* @param heap_os_funcs Points to a structure that contains some OS-related function pointers.
* @return None.
*
*/
void multi_heap_os_funcs_init(multi_heap_os_funcs_t *heap_os_funcs);
/* Opaque handle to a heap block */
typedef const struct block_header_t *multi_heap_block_handle_t;
/* Internal definitions for the "implementation" of the multi_heap API,
as defined in multi_heap.c.
If heap poisioning is disabled, these are aliased directly to the public API.
If heap poisoning is enabled, wrapper functions call each of these.
*/
void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size);
/* Allocate a memory region of minimum `size` bytes, aligned on `alignment`. */
void *multi_heap_aligned_alloc_impl(multi_heap_handle_t heap, size_t size, size_t alignment);
/* Allocate a memory region of minimum `size` bytes, where memory's `offset` is aligned on `alignment`. */
void *multi_heap_aligned_alloc_impl_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset);
void multi_heap_free_impl(multi_heap_handle_t heap, void *p);
void *multi_heap_realloc_impl(multi_heap_handle_t heap, void *p, size_t size);
multi_heap_handle_t multi_heap_register_impl(void *start, size_t size);
void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info);
size_t multi_heap_free_size_impl(multi_heap_handle_t heap);
size_t multi_heap_minimum_free_size_impl(multi_heap_handle_t heap);
size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p);
void *multi_heap_get_block_address_impl(multi_heap_block_handle_t block);
/* Some internal functions for heap poisoning use */
/* Check an allocated block's poison bytes are correct. Called by multi_heap_check(). */
bool multi_heap_internal_check_block_poisoning(void *start, size_t size, bool is_free, bool print_errors);
/* Fill a region of memory with the free or malloced pattern.
Called when merging blocks, to overwrite the old block header.
*/
void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free);
/* Allow heap poisoning to lock/unlock the heap to avoid race conditions
if multi_heap_check() is running concurrently.
*/
void multi_heap_internal_lock(multi_heap_handle_t heap);
void multi_heap_internal_unlock(multi_heap_handle_t heap);
/* Some internal functions for heap debugging code to use */
/* Get the handle to the first (fixed free) block in a heap */
multi_heap_block_handle_t multi_heap_get_first_block(multi_heap_handle_t heap);
/* Get the handle to the next block in a heap, with validation */
multi_heap_block_handle_t multi_heap_get_next_block(multi_heap_handle_t heap, multi_heap_block_handle_t block);
/* Test if a heap block is free */
bool multi_heap_is_free(const multi_heap_block_handle_t block);
/* Get the data address of a heap block */
void *multi_heap_get_block_address(multi_heap_block_handle_t block);

View File

@@ -0,0 +1,93 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define MULTI_HEAP_FREERTOS 1// by danny
#ifdef MULTI_HEAP_FREERTOS
#include "FreeRTOS.h"
#include "autoconf.h"
// #include "esp_rom_sys.h"
#include <assert.h>
// typedef portMUX_TYPE multi_heap_lock_t;
typedef uint32_t multi_heap_lock_t;
/* Because malloc/free can happen inside an ISR context,
we need to use portmux spinlocks here not RTOS mutexes */
#define MULTI_HEAP_LOCK(PLOCK) portENTER_CRITICAL()
#define MULTI_HEAP_UNLOCK(PLOCK) portEXIT_CRITICAL()
#define MULTI_HEAP_LOCK_INIT(PLOCK) {}
#define MULTI_HEAP_LOCK_STATIC_INITIALIZER 0
/* Not safe to use std i/o while in a portmux critical section,
can deadlock, so we use the ROM equivalent functions. */
extern int printk(const char *__restrict fmt, ...);
#define MULTI_HEAP_PRINTF printk
#define MULTI_HEAP_STDERR_PRINTF(MSG, ...) printk(MSG, __VA_ARGS__)
inline static void multi_heap_assert(bool condition, const char *format, int line, intptr_t address)
{
/* Can't use libc assert() here as it calls printf() which can cause another malloc() for a newlib lock.
Also, it's useful to be able to print the memory address where corruption was detected.
*/
#ifndef NDEBUG
if(!condition) {
#ifndef CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT
printk(format, line, address);
#endif // CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT
abort();
}
#else // NDEBUG
(void) condition;
#endif // NDEBUG
}
#define MULTI_HEAP_ASSERT(CONDITION, ADDRESS) \
multi_heap_assert((CONDITION), "CORRUPT HEAP: multi_heap.c:%d detected at 0x%08x\n", \
__LINE__, (intptr_t)(ADDRESS))
#ifdef CONFIG_HEAP_TASK_TRACKING
#include <freertos/task.h>
#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD) *((TaskHandle_t*)HEAD) = xTaskGetCurrentTaskHandle()
#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) *((TaskHandle_t*)HEAD)
#define MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(HEAD) ((TaskHandle_t*)(HEAD) + 1)
#define MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(HEAD) ((TaskHandle_t*)(HEAD) - 1)
#define MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(SIZE) ((SIZE) + sizeof(TaskHandle_t))
#define MULTI_HEAP_REMOVE_BLOCK_OWNER_SIZE(SIZE) ((SIZE) - sizeof(TaskHandle_t))
#define MULTI_HEAP_BLOCK_OWNER_SIZE() sizeof(TaskHandle_t)
#else
#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD)
#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) (NULL)
#define MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(HEAD) (HEAD)
#define MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(HEAD) (HEAD)
#define MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(SIZE) (SIZE)
#define MULTI_HEAP_REMOVE_BLOCK_OWNER_SIZE(SIZE) (SIZE)
#define MULTI_HEAP_BLOCK_OWNER_SIZE() 0
#endif // CONFIG_HEAP_TASK_TRACKING
#else // MULTI_HEAP_FREERTOS
#include <assert.h>
#define MULTI_HEAP_PRINTF printf
#define MULTI_HEAP_STDERR_PRINTF(MSG, ...) fprintf(stderr, MSG, __VA_ARGS__)
#define MULTI_HEAP_LOCK(PLOCK) (void) (PLOCK)
#define MULTI_HEAP_UNLOCK(PLOCK) (void) (PLOCK)
#define MULTI_HEAP_LOCK_INIT(PLOCK) (void) (PLOCK)
#define MULTI_HEAP_LOCK_STATIC_INITIALIZER 0
#define MULTI_HEAP_ASSERT(CONDITION, ADDRESS) assert((CONDITION) && "Heap corrupt")
#define MULTI_HEAP_BLOCK_OWNER
#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD)
#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) (NULL)
#endif // MULTI_HEAP_FREERTOS

View File

@@ -0,0 +1,452 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/param.h>
#include <multi_heap.h>
#include "multi_heap_internal.h"
/* Note: Keep platform-specific parts in this header, this source
file should depend on libc only */
#include "multi_heap_platform.h"
/* Defines compile-time configuration macros */
#include "multi_heap_config.h"
#if CONFIG_HEAP_TLSF_USE_ROM_IMPL
/* Header containing the declaration of tlsf_poison_fill_pfunc_set()
* and tlsf_poison_check_pfunc_set() used to register callbacks to
* fill and check memory region with given patterns in the heap
* components.
*/
#include "esp_rom_tlsf.h"
#endif
#ifdef MULTI_HEAP_POISONING
/* Alias MULTI_HEAP_POISONING_SLOW to SLOW for better readabilty */
#ifdef SLOW
#error "external header has defined SLOW"
#endif
#ifdef MULTI_HEAP_POISONING_SLOW
#define SLOW 1
#endif
#define MALLOC_FILL_PATTERN 0xce
#define FREE_FILL_PATTERN 0xfe
#define HEAD_CANARY_PATTERN 0xABBA1234
#define TAIL_CANARY_PATTERN 0xBAAD5678
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
typedef struct {
uint32_t head_canary;
size_t alloc_size;
} poison_head_t;
typedef struct {
uint32_t tail_canary;
} poison_tail_t;
#define POISON_OVERHEAD (sizeof(poison_head_t) + sizeof(poison_tail_t))
/* Given a "poisoned" region with pre-data header 'head', and actual data size 'alloc_size', fill in the head and tail
region checks.
Returns the pointer to the actual usable data buffer (ie after 'head')
*/
__attribute__((noinline)) static uint8_t *poison_allocated_region(poison_head_t *head, size_t alloc_size)
{
uint8_t *data = (uint8_t *)(&head[1]); /* start of data ie 'real' allocated buffer */
poison_tail_t *tail = (poison_tail_t *)(data + alloc_size);
head->alloc_size = alloc_size;
head->head_canary = HEAD_CANARY_PATTERN;
uint32_t tail_canary = TAIL_CANARY_PATTERN;
if ((intptr_t)tail % sizeof(void *) == 0) {
tail->tail_canary = tail_canary;
} else {
/* unaligned tail_canary */
memcpy(&tail->tail_canary, &tail_canary, sizeof(uint32_t));
}
return data;
}
/* Given a pointer to some allocated data, check the head & tail poison structures (before & after it) that were
previously injected by poison_allocated_region().
Returns a pointer to the poison header structure, or NULL if the poison structures are corrupt.
*/
__attribute__((noinline)) static poison_head_t *verify_allocated_region(void *data, bool print_errors)
{
poison_head_t *head = (poison_head_t *)((intptr_t)data - sizeof(poison_head_t));
poison_tail_t *tail = (poison_tail_t *)((intptr_t)data + head->alloc_size);
/* check if the beginning of the data was overwritten */
if (head->head_canary != HEAD_CANARY_PATTERN) {
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Bad head at %p. Expected 0x%08x got 0x%08x\n", &head->head_canary,
HEAD_CANARY_PATTERN, head->head_canary);
}
return NULL;
}
/* check if the end of the data was overrun */
uint32_t canary;
if ((intptr_t)tail % sizeof(void *) == 0) {
canary = tail->tail_canary;
} else {
/* tail is unaligned */
memcpy(&canary, &tail->tail_canary, sizeof(canary));
}
if (canary != TAIL_CANARY_PATTERN) {
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Bad tail at %p. Expected 0x%08x got 0x%08x\n", &tail->tail_canary,
TAIL_CANARY_PATTERN, canary);
}
return NULL;
}
return head;
}
#ifdef SLOW
/* Go through a region that should have the specified fill byte 'pattern',
verify it.
if expect_free is true, expect FREE_FILL_PATTERN otherwise MALLOC_FILL_PATTERN.
if swap_pattern is true, swap patterns in the buffer (ie replace MALLOC_FILL_PATTERN with FREE_FILL_PATTERN, and vice versa.)
Returns true if verification checks out.
This function has the attribute noclone to prevent the compiler to create a clone on flash where expect_free is removed (as this
function is called only with expect_free == true throughout the component).
*/
__attribute__((noinline)) NOCLONE_ATTR
static bool verify_fill_pattern(void *data, size_t size, const bool print_errors, const bool expect_free, bool swap_pattern)
{
const uint32_t FREE_FILL_WORD = (FREE_FILL_PATTERN << 24) | (FREE_FILL_PATTERN << 16) | (FREE_FILL_PATTERN << 8) | FREE_FILL_PATTERN;
const uint32_t MALLOC_FILL_WORD = (MALLOC_FILL_PATTERN << 24) | (MALLOC_FILL_PATTERN << 16) | (MALLOC_FILL_PATTERN << 8) | MALLOC_FILL_PATTERN;
const uint32_t EXPECT_WORD = expect_free ? FREE_FILL_WORD : MALLOC_FILL_WORD;
const uint32_t REPLACE_WORD = expect_free ? MALLOC_FILL_WORD : FREE_FILL_WORD;
bool valid = true;
/* Use 4-byte operations as much as possible */
if ((intptr_t)data % 4 == 0) {
uint32_t *p = data;
while (size >= 4) {
if (*p != EXPECT_WORD) {
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Invalid data at %p. Expected 0x%08x got 0x%08x\n", p, EXPECT_WORD, *p);
}
valid = false;
#ifndef NDEBUG
/* If an assertion is going to fail as soon as we're done verifying the pattern, leave the rest of the
buffer contents as-is for better post-mortem analysis
*/
swap_pattern = false;
#endif
}
if (swap_pattern) {
*p = REPLACE_WORD;
}
p++;
size -= 4;
}
data = p;
}
uint8_t *p = data;
for (size_t i = 0; i < size; i++) {
if (p[i] != (uint8_t)EXPECT_WORD) {
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Invalid data at %p. Expected 0x%02x got 0x%02x\n", p, (uint8_t)EXPECT_WORD, *p);
}
valid = false;
#ifndef NDEBUG
swap_pattern = false; // same as above
#endif
}
if (swap_pattern) {
p[i] = (uint8_t)REPLACE_WORD;
}
}
return valid;
}
/*!
* @brief Definition of the weak function declared in TLSF repository.
* The call of this function assures that the header of an absorbed
* block is filled with the correct pattern in case of comprehensive
* heap poisoning.
*
* @param start: pointer to the start of the memory region to fill
* @param size: size of the memory region to fill
* @param is_free: Indicate if the pattern to use the fill the region should be
* an after free or after allocation pattern.
*/
void block_absorb_post_hook(void *start, size_t size, bool is_free)
{
multi_heap_internal_poison_fill_region(start, size, is_free);
}
#endif
void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment)
{
return multi_heap_aligned_alloc_offs(heap, size, alignment, 0);
}
void *multi_heap_aligned_alloc_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset)
{
if (!size) {
return NULL;
}
if (size > SIZE_MAX - POISON_OVERHEAD) {
return NULL;
}
multi_heap_internal_lock(heap);
poison_head_t *head = multi_heap_aligned_alloc_impl_offs(heap, size + POISON_OVERHEAD,
alignment, offset + sizeof(poison_head_t));
uint8_t *data = NULL;
if (head != NULL) {
data = poison_allocated_region(head, size);
#ifdef SLOW
/* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */
bool ret = verify_fill_pattern(data, size, true, true, true);
assert( ret );
#endif
} else {
multi_heap_internal_unlock(heap);
return NULL;
}
multi_heap_internal_unlock(heap);
return data;
}
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
{
if (!size) {
return NULL;
}
if(size > SIZE_MAX - POISON_OVERHEAD) {
return NULL;
}
multi_heap_internal_lock(heap);
poison_head_t *head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD);
uint8_t *data = NULL;
if (head != NULL) {
data = poison_allocated_region(head, size);
#ifdef SLOW
/* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */
bool ret = verify_fill_pattern(data, size, true, true, true);
assert( ret );
#endif
}
multi_heap_internal_unlock(heap);
return data;
}
/* This function has the noclone attribute to prevent the compiler to optimize out the
* check for p == NULL and create a clone function placed in flash. */
NOCLONE_ATTR void multi_heap_free(multi_heap_handle_t heap, void *p)
{
if (p == NULL) {
return;
}
multi_heap_internal_lock(heap);
poison_head_t *head = verify_allocated_region(p, true);
assert(head != NULL);
#ifdef SLOW
/* replace everything with FREE_FILL_PATTERN, including the poison head/tail */
memset(head, FREE_FILL_PATTERN,
head->alloc_size + POISON_OVERHEAD);
#endif
multi_heap_free_impl(heap, head);
multi_heap_internal_unlock(heap);
}
void multi_heap_aligned_free(multi_heap_handle_t heap, void *p)
{
multi_heap_free(heap, p);
}
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
{
poison_head_t *head = NULL;
poison_head_t *new_head;
void *result = NULL;
if(size > SIZE_MAX - POISON_OVERHEAD) {
return NULL;
}
if (p == NULL) {
return multi_heap_malloc(heap, size);
}
if (size == 0) {
multi_heap_free(heap, p);
return NULL;
}
/* p != NULL, size != 0 */
head = verify_allocated_region(p, true);
assert(head != NULL);
multi_heap_internal_lock(heap);
#ifndef SLOW
new_head = multi_heap_realloc_impl(heap, head, size + POISON_OVERHEAD);
if (new_head != NULL) {
/* For "fast" poisoning, we only overwrite the head/tail of the new block so it's safe
to poison, so no problem doing this even if realloc resized in place.
*/
result = poison_allocated_region(new_head, size);
}
#else // SLOW
/* When slow poisoning is enabled, it becomes very fiddly to try and correctly fill memory when resizing in place
(where the buffer may be moved (including to an overlapping address with the old buffer), grown, or shrunk in
place.)
For now we just malloc a new buffer, copy, and free. :|
Note: If this ever changes, multi_heap defrag realloc test should be enabled.
*/
size_t orig_alloc_size = head->alloc_size;
new_head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD);
if (new_head != NULL) {
result = poison_allocated_region(new_head, size);
memcpy(result, p, MIN(size, orig_alloc_size));
multi_heap_free(heap, p);
}
#endif
multi_heap_internal_unlock(heap);
return result;
}
void *multi_heap_get_block_address(multi_heap_block_handle_t block)
{
char *head = multi_heap_get_block_address_impl(block);
return head + sizeof(poison_head_t);
}
multi_heap_handle_t multi_heap_register(void *start, size_t size)
{
#ifdef SLOW
if (start != NULL) {
memset(start, FREE_FILL_PATTERN, size);
}
#endif
#if CONFIG_HEAP_TLSF_USE_ROM_IMPL
tlsf_poison_fill_pfunc_set(multi_heap_internal_poison_fill_region);
tlsf_poison_check_pfunc_set(multi_heap_internal_check_block_poisoning);
#endif // CONFIG_HEAP_TLSF_USE_ROM_IMPL
return multi_heap_register_impl(start, size);
}
static inline __attribute__((always_inline)) void subtract_poison_overhead(size_t *arg) {
if (*arg > POISON_OVERHEAD) {
*arg -= POISON_OVERHEAD;
} else {
*arg = 0;
}
}
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
{
poison_head_t *head = verify_allocated_region(p, true);
assert(head != NULL);
size_t result = multi_heap_get_allocated_size_impl(heap, head);
subtract_poison_overhead(&result);
return result;
}
void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info)
{
multi_heap_get_info_impl(heap, info);
/* don't count the heap poison head & tail overhead in the allocated bytes size */
info->total_allocated_bytes -= info->allocated_blocks * POISON_OVERHEAD;
/* trim largest_free_block to account for poison overhead */
subtract_poison_overhead(&info->largest_free_block);
/* similarly, trim total_free_bytes so there's no suggestion that
a block this big may be available. */
subtract_poison_overhead(&info->total_free_bytes);
subtract_poison_overhead(&info->minimum_free_bytes);
}
size_t multi_heap_free_size(multi_heap_handle_t heap)
{
size_t r = multi_heap_free_size_impl(heap);
subtract_poison_overhead(&r);
return r;
}
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
{
size_t r = multi_heap_minimum_free_size_impl(heap);
subtract_poison_overhead(&r);
return r;
}
/* Internal hooks used by multi_heap to manage poisoning, while keeping some modularity */
bool multi_heap_internal_check_block_poisoning(void *start, size_t size, bool is_free, bool print_errors)
{
if (is_free) {
#ifdef SLOW
return verify_fill_pattern(start, size, print_errors, true, false);
#else
return true; /* can only verify empty blocks in SLOW mode */
#endif
} else {
void *data = (void *)((intptr_t)start + sizeof(poison_head_t));
poison_head_t *head = verify_allocated_region(data, print_errors);
if (head != NULL && head->alloc_size > size - POISON_OVERHEAD) {
/* block can be bigger than alloc_size, for reasons of alignment & fragmentation,
but block can never be smaller than head->alloc_size... */
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Size at %p expected <=0x%08x got 0x%08x\n", &head->alloc_size,
size - POISON_OVERHEAD, head->alloc_size);
}
return false;
}
return head != NULL;
}
}
void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free)
{
memset(start, is_free ? FREE_FILL_PATTERN : MALLOC_FILL_PATTERN, size);
}
#else // !MULTI_HEAP_POISONING
#ifdef MULTI_HEAP_POISONING_SLOW
#error "MULTI_HEAP_POISONING_SLOW requires MULTI_HEAP_POISONING"
#endif
#endif // MULTI_HEAP_POISONING

View File

@@ -0,0 +1,32 @@
#include "esp_heap_caps.h"
HEAP_IRAM_ATTR void esp_heap_adjust_alignment_to_hw(size_t *p_align, size_t *p_size, uint32_t *p_caps)
{
if (*p_caps & (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED)) {
// MALLOC_CAP_CACHE_ALIGNED is not a real flag the heap_base component will
// understand; it only sets alignment (which we handled here)
*p_caps &= ~MALLOC_CAP_CACHE_ALIGNED;
*p_align = 32;
*p_size = (*p_size + 31) / 32 * 32;
}
}
HEAP_IRAM_ATTR bool esp_ptr_in_diram_iram(const void *p)
{
return false;
}
HEAP_IRAM_ATTR bool esp_ptr_in_diram_dram(const void *p)
{
return false;
}
HEAP_IRAM_ATTR void *esp_ptr_diram_dram_to_iram(const void *p)
{
return (void *)p;
}
HEAP_IRAM_ATTR bool esp_dram_match_iram(void)
{
return true;
}

View File

@@ -0,0 +1,92 @@
# tlsf
Two-Level Segregated Fit memory allocator implementation.
Written by Matthew Conte (matt@baisoku.org).
Released under the BSD license.
Features
--------
* O(1) cost for malloc, free, realloc, memalign
* Extremely low overhead per allocation (4 bytes)
* Low overhead per TLSF management of pools (~3kB)
* Low fragmentation
* Compiles to only a few kB of code and data
* Support for adding and removing memory pool regions on the fly
Caveats
-------
* Currently, assumes architecture can make 4-byte aligned accesses
* Not designed to be thread safe; the user must provide this
Notes
-----
This code was based on the TLSF 1.4 spec and documentation found at:
http://www.gii.upv.es/tlsf/main/docs
It also leverages the TLSF 2.0 improvement to shrink the per-block overhead from 8 to 4 bytes.
History
-------
2016/04/10 - v3.1
* Code moved to github
* tlsfbits.h rolled into tlsf.c
* License changed to BSD
2014/02/08 - v3.0
* This version is based on improvements from 3DInteractive GmbH
* Interface changed to allow more than one memory pool
* Separated pool handling from control structure (adding, removing, debugging)
* Control structure and pools can still be constructed in the same memory block
* Memory blocks for control structure and pools are checked for alignment
* Added functions to retrieve control structure size, alignment size, min and max block size, overhead of pool structure, and overhead of a single allocation
* Minimal Pool size is tlsf_block_size_min() + tlsf_pool_overhead()
* Pool must be empty when it is removed, in order to allow O(1) removal
2011/10/20 - v2.0
* 64-bit support
* More compiler intrinsics for ffs/fls
* ffs/fls verification during TLSF creation in debug builds
2008/04/04 - v1.9
* Add tlsf_heap_check, a heap integrity check
* Support a predefined tlsf_assert macro
* Fix realloc case where block should shrink; if adjacent block is in use, execution would go down the slow path
2007/02/08 - v1.8
* Fix for unnecessary reallocation in tlsf_realloc
2007/02/03 - v1.7
* tlsf_heap_walk takes a callback
* tlsf_realloc now returns NULL on failure
* tlsf_memalign optimization for 4-byte alignment
* Usage of size_t where appropriate
2006/11/21 - v1.6
* ffs/fls broken out into tlsfbits.h
* tlsf_overhead queries per-pool overhead
2006/11/07 - v1.5
* Smart realloc implementation
* Smart memalign implementation
2006/10/11 - v1.4
* Add some ffs/fls implementations
* Minor code footprint reduction
2006/09/14 - v1.3
* Profiling indicates heavy use of blocks of size 1-128, so implement small block handling
* Reduce pool overhead by about 1kb
* Reduce minimum block size from 32 to 12 bytes
* Realloc bug fix
2006/09/09 - v1.2
* Add tlsf_block_size
* Static assertion mechanism for invariants
* Minor bugfixes
2006/09/01 - v1.1
* Add tlsf_realloc
* Add tlsf_walk_heap
2006/08/25 - v1.0
* First release

View File

@@ -0,0 +1,83 @@
/*
* SPDX-FileCopyrightText: 2006-2016 Matthew Conte
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef INCLUDED_tlsf
#define INCLUDED_tlsf
#include <assert.h>
#include <stddef.h>
#include <stdbool.h>
#if defined(__cplusplus)
extern "C" {
#endif
/* tlsf_t: a TLSF structure. Can contain 1 to N pools. */
/* pool_t: a block of memory that TLSF can manage. */
typedef void* tlsf_t;
typedef void* pool_t;
/* Create/destroy a memory pool. */
tlsf_t tlsf_create(void* mem, size_t max_bytes);
tlsf_t tlsf_create_with_pool(void* mem, size_t pool_bytes, size_t max_bytes);
void tlsf_destroy(tlsf_t tlsf);
pool_t tlsf_get_pool(tlsf_t tlsf);
/* Add/remove memory pools. */
pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes);
void tlsf_remove_pool(tlsf_t tlsf, pool_t pool);
/* malloc/memalign/realloc/free replacements. */
void* tlsf_malloc(tlsf_t tlsf, size_t size);
void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size);
void* tlsf_memalign_offs(tlsf_t tlsf, size_t align, size_t size, size_t offset);
void* tlsf_malloc_addr(tlsf_t tlsf, size_t size, void *address);
void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size);
void tlsf_free(tlsf_t tlsf, void* ptr);
/* Returns internal block size, not original request size */
size_t tlsf_block_size(void* ptr);
/* Overheads/limits of internal structures. */
size_t tlsf_size(tlsf_t tlsf);
size_t tlsf_pool_overhead(void);
size_t tlsf_alloc_overhead(void);
/**
* @brief Return the allocable size based on the size passed
* as parameter
*
* @param tlsf Pointer to the tlsf structure
* @param size The allocation size
* @return size_t The updated allocation size
*/
size_t tlsf_fit_size(tlsf_t tlsf, size_t size);
/* Debugging. */
typedef bool (*tlsf_walker)(void* ptr, size_t size, int used, void* user);
void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user);
/* Returns nonzero if any internal consistency check fails. */
int tlsf_check(tlsf_t tlsf);
int tlsf_check_pool(pool_t pool);
/**
* @brief Weak function called on every free block of memory allowing the user to implement
* application specific checks on the memory.
*
* @param start The start pointer to the memory of a block
* @param size The size of the memory in the block
* @param is_free Set to true when the memory belongs to a free block.
* False if it belongs to an allocated block.
* @return true The checks found no inconsistency in the memory
* @return false The checks in the function highlighted an inconsistency in the memory
*/
__attribute__((weak)) bool tlsf_check_hook(void *start, size_t size, bool is_free);
#if defined(__cplusplus)
};
#endif
#endif

View File

@@ -0,0 +1,712 @@
/*
* SPDX-FileCopyrightText: 2006-2016 Matthew Conte
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include "tlsf.h"
#include "tlsf_block_functions.h"
#include "tlsf_control_functions.h"
/*
** Static assertion mechanism.
*/
#define _tlsf_glue2(x, y) x ## y
#define _tlsf_glue(x, y) _tlsf_glue2(x, y)
#define tlsf_static_assert(exp) \
typedef char _tlsf_glue(static_assert, __LINE__) [(exp) ? 1 : -1]
/* This code has been tested on 32- and 64-bit (LP/LLP) architectures. */
tlsf_static_assert(sizeof(int) * CHAR_BIT == 32);
tlsf_static_assert(sizeof(size_t) * CHAR_BIT >= 32);
tlsf_static_assert(sizeof(size_t) * CHAR_BIT <= 64);
/* Clear structure and point all empty lists at the null block. */
static control_t* control_construct(control_t* control, size_t bytes)
{
// check that the requested size can at least hold the control_t. This will allow us
// to fill in the field of control_t necessary to determine the final size of
// the metadata overhead and check that the requested size can hold
// this data and at least a block of minimum size
if (bytes < sizeof(control_t))
{
return NULL;
}
/* Find the closest power of two for first layer */
control->fl_index_max = 32 - __builtin_clz(bytes);
/* Adapt second layer to the pool */
if (bytes <= 16 * 1024) control->sl_index_count_log2 = 3;
else if (bytes <= 256 * 1024) control->sl_index_count_log2 = 4;
else control->sl_index_count_log2 = 5;
control->fl_index_shift = (control->sl_index_count_log2 + ALIGN_SIZE_LOG2);
control->sl_index_count = 1 << control->sl_index_count_log2;
control->fl_index_count = control->fl_index_max - control->fl_index_shift + 1;
control->small_block_size = 1 << control->fl_index_shift;
// the total size fo the metadata overhead is the size of the control_t
// added to the size of the sl_bitmaps and the size of blocks
control->size = sizeof(control_t) + (sizeof(*control->sl_bitmap) * control->fl_index_count) +
(sizeof(*control->blocks) * (control->fl_index_count * control->sl_index_count));
// check that the requested size can hold the whole control structure and
// a small block at least
if (bytes < control->size + block_size_min)
{
return NULL;
}
control->block_null.next_free = &control->block_null;
control->block_null.prev_free = &control->block_null;
control->fl_bitmap = 0;
control->sl_bitmap = align_ptr(control + 1, sizeof(*control->sl_bitmap));
control->blocks = align_ptr(control->sl_bitmap + control->fl_index_count, sizeof(*control->blocks));
/* SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type. */
tlsf_assert(sizeof(unsigned int) * CHAR_BIT >= control->sl_index_count
&& "CHAR_BIT less than sl_index_count");
/* Ensure we've properly tuned our sizes. */
tlsf_assert(ALIGN_SIZE == control->small_block_size / control->sl_index_count); //ALIGN_SIZE does not match");
for (int i = 0; i < control->fl_index_count; ++i)
{
control->sl_bitmap[i] = 0;
for (int j = 0; j < control->sl_index_count; ++j)
{
control->blocks[i * control->sl_index_count + j] = &control->block_null;
}
}
return control;
}
/*
** Debugging utilities.
*/
typedef struct integrity_t
{
int prev_status;
int status;
} integrity_t;
#define tlsf_insist(x) { if (!(x)) { status--; } }
static bool integrity_walker(void* ptr, size_t size, int used, void* user)
{
block_header_t* block = block_from_ptr(ptr);
integrity_t* integ = tlsf_cast(integrity_t*, user);
const int this_prev_status = block_is_prev_free(block) ? 1 : 0;
const int this_status = block_is_free(block) ? 1 : 0;
const size_t this_block_size = block_size(block);
int status = 0;
tlsf_insist(integ->prev_status == this_prev_status && "prev status incorrect");
tlsf_insist(size == this_block_size && "block size incorrect");
if (tlsf_check_hook != NULL)
{
/* block_size(block) returns the size of the usable memory when the block is allocated.
* As the block under test is free, we need to subtract to the block size the next_free
* and prev_free fields of the block header as they are not a part of the usable memory
* when the block is free. In addition, we also need to subtract the size of prev_phys_block
* as this field is in fact part of the current free block and not part of the next (allocated)
* block. Check the comments in block_split function for more details.
*/
const size_t actual_free_block_size = used ? this_block_size :
this_block_size - offsetof(block_header_t, next_free)- block_header_overhead;
void* ptr_block = used ? (void*)block + block_start_offset :
(void*)block + sizeof(block_header_t);
tlsf_insist(tlsf_check_hook(ptr_block, actual_free_block_size, !used));
}
integ->prev_status = this_status;
integ->status += status;
return true;
}
int tlsf_check(tlsf_t tlsf)
{
int i, j;
control_t* control = tlsf_cast(control_t*, tlsf);
int status = 0;
/* Check that the free lists and bitmaps are accurate. */
for (i = 0; i < control->fl_index_count; ++i)
{
for (j = 0; j < control->sl_index_count; ++j)
{
const int fl_map = control->fl_bitmap & (1U << i);
const int sl_list = control->sl_bitmap[i];
const int sl_map = sl_list & (1U << j);
const block_header_t* block = control->blocks[i * control->sl_index_count + j];
/* Check that first- and second-level lists agree. */
if (!fl_map)
{
tlsf_insist(!sl_map && "second-level map must be null");
}
if (!sl_map)
{
tlsf_insist(block == &control->block_null && "block list must be null");
continue;
}
/* Check that there is at least one free block. */
tlsf_insist(sl_list && "no free blocks in second-level map");
tlsf_insist(block != &control->block_null && "block should not be null");
while (block != &control->block_null)
{
int fli, sli;
const bool is_block_free = block_is_free(block);
tlsf_insist(is_block_free && "block should be free");
tlsf_insist(!block_is_prev_free(block) && "blocks should have coalesced");
tlsf_insist(!block_is_free(block_next(block)) && "blocks should have coalesced");
tlsf_insist(block_is_prev_free(block_next(block)) && "block should be free");
tlsf_insist(block_size(block) >= block_size_min && "block not minimum size");
mapping_insert(control, block_size(block), &fli, &sli);
tlsf_insist(fli == i && sli == j && "block size indexed in wrong list");
block = block->next_free;
}
}
}
return status;
}
#undef tlsf_insist
static bool default_walker(void* ptr, size_t size, int used, void* user)
{
(void)user;
printf("\t%p %s size: %x (%p)\n", ptr, used ? "used" : "free", (unsigned int)size, block_from_ptr(ptr));
return true;
}
void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user)
{
tlsf_walker pool_walker = walker ? walker : default_walker;
block_header_t* block =
offset_to_block(pool, -(int)block_header_overhead);
bool ret_val = true;
while (block && !block_is_last(block) && ret_val == true)
{
ret_val = pool_walker(
block_to_ptr(block),
block_size(block),
!block_is_free(block),
user);
if (ret_val == true) {
block = block_next(block);
}
}
}
size_t tlsf_block_size(void* ptr)
{
size_t size = 0;
if (ptr)
{
const block_header_t* block = block_from_ptr(ptr);
size = block_size(block);
}
return size;
}
int tlsf_check_pool(pool_t pool)
{
/* Check that the blocks are physically correct. */
integrity_t integ = { 0, 0 };
tlsf_walk_pool(pool, integrity_walker, &integ);
return integ.status;
}
size_t tlsf_fit_size(tlsf_t tlsf, size_t size)
{
if (size == 0 || tlsf == NULL) {
return 0;
}
control_t* control = tlsf_cast(control_t*, tlsf);
if (size < control->small_block_size) {
return adjust_request_size(tlsf, size, ALIGN_SIZE);
}
/* because it's GoodFit, allocable size is one range lower */
size_t sl_interval;
sl_interval = (1 << (32 - __builtin_clz(size) - 1)) / control->sl_index_count;
return size & ~(sl_interval - 1);
}
/*
** Size of the TLSF structures in a given memory block passed to
** tlsf_create, equal to the size of a control_t
*/
size_t tlsf_size(tlsf_t tlsf)
{
if (tlsf == NULL)
{
return 0;
}
control_t* control = tlsf_cast(control_t*, tlsf);
return control->size;
}
/*
** Overhead of the TLSF structures in a given memory block passed to
** tlsf_add_pool, equal to the overhead of a free block and the
** sentinel block.
*/
size_t tlsf_pool_overhead(void)
{
return 2 * block_header_overhead;
}
size_t tlsf_alloc_overhead(void)
{
return block_header_overhead;
}
pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes)
{
block_header_t* block;
block_header_t* next;
const size_t pool_overhead = tlsf_pool_overhead();
const size_t pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE);
if (((ptrdiff_t)mem % ALIGN_SIZE) != 0)
{
printf("tlsf_add_pool: Memory must be aligned by %u bytes.\n",
(unsigned int)ALIGN_SIZE);
return 0;
}
if (pool_bytes < block_size_min || pool_bytes > tlsf_block_size_max(tlsf))
{
#if defined (TLSF_64BIT)
printf("tlsf_add_pool: Memory size must be between 0x%x and 0x%x00 bytes.\n",
(unsigned int)(pool_overhead + block_size_min),
(unsigned int)((pool_overhead + tlsf_block_size_max(tlsf)) / 256));
#else
printf("tlsf_add_pool: Memory size must be between %u and %u bytes.\n",
(unsigned int)(pool_overhead + block_size_min),
(unsigned int)(pool_overhead + tlsf_block_size_max(tlsf)));
#endif
return 0;
}
/*
** Create the main free block. Offset the start of the block slightly
** so that the prev_phys_block field falls outside of the pool -
** it will never be used.
*/
block = offset_to_block(mem, -(tlsfptr_t)block_header_overhead);
block_set_size(block, pool_bytes);
block_set_free(block);
block_set_prev_used(block);
block_insert(tlsf_cast(control_t*, tlsf), block);
/* Split the block to create a zero-size sentinel block. */
next = block_link_next(block);
block_set_size(next, 0);
block_set_used(next);
block_set_prev_free(next);
return mem;
}
void tlsf_remove_pool(tlsf_t tlsf, pool_t pool)
{
control_t* control = tlsf_cast(control_t*, tlsf);
block_header_t* block = offset_to_block(pool, -(int)block_header_overhead);
int fl = 0, sl = 0;
tlsf_assert(block_is_free(block) && "block should be free");
tlsf_assert(!block_is_free(block_next(block)) && "next block should not be free");
tlsf_assert(block_size(block_next(block)) == 0 && "next block size should be zero");
mapping_insert(control, block_size(block), &fl, &sl);
remove_free_block(control, block, fl, sl);
}
/*
** TLSF main interface.
*/
#if _DEBUG
int test_ffs_fls()
{
/* Verify ffs/fls work properly. */
int rv = 0;
rv += (tlsf_ffs(0) == -1) ? 0 : 0x1;
rv += (tlsf_fls(0) == -1) ? 0 : 0x2;
rv += (tlsf_ffs(1) == 0) ? 0 : 0x4;
rv += (tlsf_fls(1) == 0) ? 0 : 0x8;
rv += (tlsf_ffs(0x80000000) == 31) ? 0 : 0x10;
rv += (tlsf_ffs(0x80008000) == 15) ? 0 : 0x20;
rv += (tlsf_fls(0x80000008) == 31) ? 0 : 0x40;
rv += (tlsf_fls(0x7FFFFFFF) == 30) ? 0 : 0x80;
#if defined (TLSF_64BIT)
rv += (tlsf_fls_sizet(0x80000000) == 31) ? 0 : 0x100;
rv += (tlsf_fls_sizet(0x100000000) == 32) ? 0 : 0x200;
rv += (tlsf_fls_sizet(0xffffffffffffffff) == 63) ? 0 : 0x400;
#endif
if (rv)
{
printf("test_ffs_fls: %x ffs/fls tests failed.\n", rv);
}
return rv;
}
#endif
tlsf_t tlsf_create(void* mem, size_t max_bytes)
{
#if _DEBUG
if (test_ffs_fls())
{
return NULL;
}
#endif
if (mem == NULL)
{
return NULL;
}
if (((tlsfptr_t)mem % ALIGN_SIZE) != 0)
{
printf("tlsf_create: Memory must be aligned to %u bytes.\n",
(unsigned int)ALIGN_SIZE);
return NULL;
}
control_t* control_ptr = control_construct(tlsf_cast(control_t*, mem), max_bytes);
return tlsf_cast(tlsf_t, control_ptr);
}
tlsf_t tlsf_create_with_pool(void* mem, size_t pool_bytes, size_t max_bytes)
{
tlsf_t tlsf = tlsf_create(mem, max_bytes ? max_bytes : pool_bytes);
if (tlsf != NULL)
{
tlsf_add_pool(tlsf, (char*)mem + tlsf_size(tlsf), pool_bytes - tlsf_size(tlsf));
}
return tlsf;
}
void tlsf_destroy(tlsf_t tlsf)
{
/* Nothing to do. */
(void)tlsf;
}
pool_t tlsf_get_pool(tlsf_t tlsf)
{
return tlsf_cast(pool_t, (char*)tlsf + tlsf_size(tlsf));
}
void* tlsf_malloc(tlsf_t tlsf, size_t size)
{
control_t* control = tlsf_cast(control_t*, tlsf);
size_t adjust = adjust_request_size(tlsf, size, ALIGN_SIZE);
// Returned size is 0 when the requested size is larger than the max block
// size.
if (adjust == 0) {
return NULL;
}
// block_locate_free() may adjust our allocated size further.
block_header_t* block = block_locate_free(control, &adjust);
return block_prepare_used(control, block, adjust);
}
/**
* @brief Allocate memory of at least `size` bytes at a given address in the pool.
*
* @param tlsf TLSF structure to allocate memory from.
* @param size Minimum size, in bytes, of the memory to allocate
* @param address address at which the allocation must be done
*
* @return pointer to free memory or NULL in case of incapacity to perform the malloc
*/
void* tlsf_malloc_addr(tlsf_t tlsf, size_t size, void *address)
{
control_t* control = tlsf_cast(control_t*, tlsf);
/* adjust the address to be ALIGN_SIZE bytes aligned. */
const unsigned int addr_adjusted = align_down(tlsf_cast(unsigned int, address), ALIGN_SIZE);
/* adjust the size to be ALIGN_SIZE bytes aligned. Add to the size the difference
* between the requested address and the address_adjusted. */
size_t size_adjusted = align_up(size + (tlsf_cast(unsigned int, address) - addr_adjusted), ALIGN_SIZE);
/* find the free block that starts before the address in the pool and is big enough
* to support the size of allocation at the given address */
block_header_t* block = offset_to_block(tlsf_get_pool(tlsf), -(int)block_header_overhead);
const char *alloc_start = tlsf_cast(char*, addr_adjusted);
const char *alloc_end = alloc_start + size_adjusted;
bool block_found = false;
do {
const char *block_start = tlsf_cast(char*, block_to_ptr(block));
const char *block_end = tlsf_cast(char*, block_to_ptr(block)) + block_size(block);
if (block_start <= alloc_start && block_end > alloc_start) {
/* A: block_end >= alloc_end. B: block is free */
if (block_end < alloc_end || !block_is_free(block)) {
/* not(A) || not(B)
* We won't find another suitable block from this point on
* so we can break and return NULL */
break;
}
/* A && B
* The block can fit the alloc and is located at a position allowing for the alloc
* to be placed at the given address. We can return from the while */
block_found = true;
} else if (!block_is_last(block)) {
/* the block doesn't match the expected criteria, continue with the next block */
block = block_next(block);
}
} while (!block_is_last(block) && block_found == false);
if (!block_found) {
return NULL;
}
/* remove block from the free list since a part of it will be used */
block_remove(control, block);
/* trim any leading space or add the leading space to the overall requested size
* if the leading space is not big enough to store a block of minimum size */
const size_t space_before_addr_adjusted = addr_adjusted - tlsf_cast(unsigned int, block_to_ptr(block));
block_header_t *return_block = block;
if (space_before_addr_adjusted >= block_size_min) {
return_block = block_trim_free_leading(control, block, space_before_addr_adjusted);
}
else {
size_adjusted += space_before_addr_adjusted;
}
/* trim trailing space if any and return a pointer to the first usable byte allocated */
return block_prepare_used(control, return_block, size_adjusted);
}
/**
* @brief Allocate memory of at least `size` bytes where byte at `data_offset` will be aligned to `alignment`.
*
* This function will allocate memory pointed by `ptr`. However, the byte at `data_offset` of
* this piece of memory (i.e., byte at `ptr` + `data_offset`) will be aligned to `alignment`.
* This function is useful for allocating memory that will internally have a header, and the
* usable memory following the header (i.e. `ptr` + `data_offset`) must be aligned.
*
* For example, a call to `multi_heap_aligned_alloc_impl_offs(heap, 64, 256, 20)` will return a
* pointer `ptr` to free memory of minimum 64 bytes, where `ptr + 20` is aligned on `256`.
* So `(ptr + 20) % 256` equals 0.
*
* @param tlsf TLSF structure to allocate memory from.
* @param align Alignment for the returned pointer's offset.
* @param size Minimum size, in bytes, of the memory to allocate INCLUDING
* `data_offset` bytes.
* @param data_offset Offset to be aligned on `alignment`. This can be 0, in
* this case, the returned pointer will be aligned on
* `alignment`. If it is not a multiple of CPU word size,
* it will be aligned up to the closest multiple of it.
*
* @return pointer to free memory.
*/
void* tlsf_memalign_offs(tlsf_t tlsf, size_t align, size_t size, size_t data_offset)
{
control_t* control = tlsf_cast(control_t*, tlsf);
const size_t adjust = adjust_request_size(tlsf, size, ALIGN_SIZE);
const size_t off_adjust = align_up(data_offset, ALIGN_SIZE);
/*
** We must allocate an additional minimum block size bytes so that if
** our free block will leave an alignment gap which is smaller, we can
** trim a leading free block and release it back to the pool. We must
** do this because the previous physical block is in use, therefore
** the prev_phys_block field is not valid, and we can't simply adjust
** the size of that block.
*/
const size_t gap_minimum = sizeof(block_header_t) + off_adjust;
/* The offset is included in both `adjust` and `gap_minimum`, so we
** need to subtract it once.
*/
const size_t size_with_gap = adjust_request_size(tlsf, adjust + align + gap_minimum - off_adjust, align);
/*
** If alignment is less than or equal to base alignment, we're done, because
** we are guaranteed that the size is at least sizeof(block_header_t), enough
** to store next blocks' metadata. Plus, all pointers allocated will all be
** aligned on a 4-byte bound, so ptr + data_offset will also have this
** alignment constraint. Thus, the gap is not required.
** If we requested 0 bytes, return null, as tlsf_malloc(0) does.
*/
size_t aligned_size = (adjust && align > ALIGN_SIZE) ? size_with_gap : adjust;
block_header_t* block = block_locate_free(control, &aligned_size);
/* This can't be a static assert. */
tlsf_assert(sizeof(block_header_t) == block_size_min + block_header_overhead);
if (block)
{
void* ptr = block_to_ptr(block);
void* aligned = align_ptr(ptr, align);
size_t gap = tlsf_cast(size_t,
tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr));
/*
** If gap size is too small or if there is no gap but we need one,
** offset to next aligned boundary.
** NOTE: No need for a gap if the alignment required is less than or is
** equal to ALIGN_SIZE.
*/
if ((gap && gap < gap_minimum) || (!gap && off_adjust && align > ALIGN_SIZE))
{
const size_t gap_remain = gap_minimum - gap;
const size_t offset = tlsf_max(gap_remain, align);
const void* next_aligned = tlsf_cast(void*,
tlsf_cast(tlsfptr_t, aligned) + offset);
aligned = align_ptr(next_aligned, align);
gap = tlsf_cast(size_t,
tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr));
}
if (gap)
{
tlsf_assert(gap >= gap_minimum && "gap size too small");
block = block_trim_free_leading(control, block, gap - off_adjust);
}
}
/* Preparing the block will also the trailing free memory. */
return block_prepare_used(control, block, adjust);
}
/**
* @brief Same as `tlsf_memalign_offs` function but with a 0 offset.
* The pointer returned is aligned on `align`.
*/
void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size)
{
return tlsf_memalign_offs(tlsf, align, size, 0);
}
void tlsf_free(tlsf_t tlsf, void* ptr)
{
/* Don't attempt to free a NULL pointer. */
if (ptr)
{
control_t* control = tlsf_cast(control_t*, tlsf);
block_header_t* block = block_from_ptr(ptr);
tlsf_assert(!block_is_free(block) && "block already marked as free");
block_mark_as_free(block);
block = block_merge_prev(control, block);
block = block_merge_next(control, block);
block_insert(control, block);
}
}
/*
** The TLSF block information provides us with enough information to
** provide a reasonably intelligent implementation of realloc, growing or
** shrinking the currently allocated block as required.
**
** This routine handles the somewhat esoteric edge cases of realloc:
** - a non-zero size with a null pointer will behave like malloc
** - a zero size with a non-null pointer will behave like free
** - a request that cannot be satisfied will leave the original buffer
** untouched
** - an extended buffer size will leave the newly-allocated area with
** contents undefined
*/
void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size)
{
control_t* control = tlsf_cast(control_t*, tlsf);
void* p = 0;
/* Zero-size requests are treated as free. */
if (ptr && size == 0)
{
tlsf_free(tlsf, ptr);
}
/* Requests with NULL pointers are treated as malloc. */
else if (!ptr)
{
p = tlsf_malloc(tlsf, size);
}
else
{
block_header_t* block = block_from_ptr(ptr);
block_header_t* next = block_next(block);
const size_t cursize = block_size(block);
const size_t combined = cursize + block_size(next) + block_header_overhead;
const size_t adjust = adjust_request_size(tlsf, size, ALIGN_SIZE);
// if adjust if equal to 0, the size is too big
if (adjust == 0)
{
return p;
}
tlsf_assert(!block_is_free(block) && "block already marked as free");
/*
** If the next block is used, or when combined with the current
** block, does not offer enough space, we must reallocate and copy.
*/
if (adjust > cursize && (!block_is_free(next) || adjust > combined))
{
p = tlsf_malloc(tlsf, size);
if (p)
{
const size_t minsize = tlsf_min(cursize, size);
memcpy(p, ptr, minsize);
tlsf_free(tlsf, ptr);
}
}
else
{
/* Do we need to expand to the next block? */
if (adjust > cursize)
{
block_merge_next(control, block);
block_mark_as_used(block);
}
/* Trim the resulting block and return the original pointer. */
block_trim_used(control, block, adjust);
p = ptr;
}
}
return p;
}

View File

@@ -0,0 +1,190 @@
/*
* SPDX-FileCopyrightText: 2006-2016 Matthew Conte
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#if defined(__cplusplus)
extern "C" {
#endif
/*
** Constants definition for poisoning.
** These defines are used as 3rd argument of tlsf_poison_fill_region() for readability purposes.
*/
#define POISONING_AFTER_FREE true
#define POISONING_AFTER_MALLOC !POISONING_AFTER_FREE
/* A type used for casting when doing pointer arithmetic. */
typedef ptrdiff_t tlsfptr_t;
/*
** Cast and min/max macros.
*/
#if !defined (tlsf_cast)
#define tlsf_cast(t, exp) ((t) (exp))
#endif
#if !defined (tlsf_min)
#define tlsf_min(a, b) ((a) < (b) ? (a) : (b))
#endif
#if !defined (tlsf_max)
#define tlsf_max(a, b) ((a) > (b) ? (a) : (b))
#endif
/*
** Set assert macro, if it has not been provided by the user.
*/
#if !defined (tlsf_assert)
#define tlsf_assert assert
#endif
typedef struct block_header_t
{
/* Points to the previous physical block. */
struct block_header_t* prev_phys_block;
/* The size of this block, excluding the block header. */
size_t size;
/* Next and previous free blocks. */
struct block_header_t* next_free;
struct block_header_t* prev_free;
} block_header_t;
/* User data starts directly after the size field in a used block. */
#define block_start_offset (offsetof(block_header_t, size) + sizeof(size_t))
/*
** A free block must be large enough to store its header minus the size of
** the prev_phys_block field, and no larger than the number of addressable
** bits for FL_INDEX.
*/
#define block_size_min (sizeof(block_header_t) - sizeof(block_header_t*))
/*
** Since block sizes are always at least a multiple of 4, the two least
** significant bits of the size field are used to store the block status:
** - bit 0: whether block is busy or free
** - bit 1: whether previous block is busy or free
*/
#define block_header_free_bit (1UL << 0)
#define block_header_prev_free_bit (1UL << 1)
/*
** The size of the block header exposed to used blocks is the size field.
** The prev_phys_block field is stored *inside* the previous free block.
*/
#define block_header_overhead (sizeof(size_t))
/*
** block_header_t member functions.
*/
static inline __attribute__((always_inline)) size_t block_size(const block_header_t* block)
{
return block->size & ~(block_header_free_bit | block_header_prev_free_bit);
}
static inline __attribute__((always_inline)) void block_set_size(block_header_t* block, size_t size)
{
const size_t oldsize = block->size;
block->size = size | (oldsize & (block_header_free_bit | block_header_prev_free_bit));
}
static inline __attribute__((always_inline)) int block_is_last(const block_header_t* block)
{
return block_size(block) == 0;
}
static inline __attribute__((always_inline)) int block_is_free(const block_header_t* block)
{
return tlsf_cast(int, block->size & block_header_free_bit);
}
static inline __attribute__((always_inline)) void block_set_free(block_header_t* block)
{
block->size |= block_header_free_bit;
}
static inline __attribute__((always_inline)) void block_set_used(block_header_t* block)
{
block->size &= ~block_header_free_bit;
}
static inline __attribute__((always_inline)) int block_is_prev_free(const block_header_t* block)
{
return tlsf_cast(int, block->size & block_header_prev_free_bit);
}
static inline __attribute__((always_inline)) void block_set_prev_free(block_header_t* block)
{
block->size |= block_header_prev_free_bit;
}
static inline __attribute__((always_inline)) void block_set_prev_used(block_header_t* block)
{
block->size &= ~block_header_prev_free_bit;
}
static inline __attribute__((always_inline)) block_header_t* block_from_ptr(const void* ptr)
{
return tlsf_cast(block_header_t*,
tlsf_cast(unsigned char*, ptr) - block_start_offset);
}
static inline __attribute__((always_inline)) void* block_to_ptr(const block_header_t* block)
{
return tlsf_cast(void*,
tlsf_cast(unsigned char*, block) + block_start_offset);
}
/* Return location of next block after block of given size. */
static inline __attribute__((always_inline)) block_header_t* offset_to_block(const void* ptr, size_t size)
{
return tlsf_cast(block_header_t*, tlsf_cast(tlsfptr_t, ptr) + size);
}
/* Return location of previous block. */
static inline __attribute__((always_inline)) block_header_t* block_prev(const block_header_t* block)
{
tlsf_assert(block_is_prev_free(block) && "previous block must be free");
return block->prev_phys_block;
}
/* Return location of next existing block. */
static inline __attribute__((always_inline)) block_header_t* block_next(const block_header_t* block)
{
block_header_t* next = offset_to_block(block_to_ptr(block),
block_size(block) - block_header_overhead);
tlsf_assert(!block_is_last(block));
return next;
}
/* Link a new block with its physical neighbor, return the neighbor. */
static inline __attribute__((always_inline)) block_header_t* block_link_next(block_header_t* block)
{
block_header_t* next = block_next(block);
next->prev_phys_block = block;
return next;
}
static inline __attribute__((always_inline)) void block_mark_as_free(block_header_t* block)
{
/* Link the block to the next block, first. */
block_header_t* next = block_link_next(block);
block_set_prev_free(next);
block_set_free(block);
}
static inline __attribute__((always_inline)) void block_mark_as_used(block_header_t* block)
{
block_header_t* next = block_next(block);
block_set_prev_used(next);
block_set_used(block);
}
#if defined(__cplusplus)
};
#endif

View File

@@ -0,0 +1,642 @@
/*
* SPDX-FileCopyrightText: 2024 Matthew Conte
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "tlsf_block_functions.h"
#if defined(__cplusplus)
extern "C" {
#define tlsf_decl static inline
#else
#define tlsf_decl static inline __attribute__((always_inline))
#endif
enum tlsf_config
{
/* All allocation sizes and addresses are aligned to 4 bytes. */
ALIGN_SIZE_LOG2 = 2,
ALIGN_SIZE = (1 << ALIGN_SIZE_LOG2),
};
/* The TLSF control structure. */
typedef struct control_t
{
/* Empty lists point at this block to indicate they are free. */
block_header_t block_null;
/* Local parameter for the pool. Given the maximum
* value of each field, all the following parameters
* can fit on 4 bytes when using bitfields
*/
unsigned int fl_index_count : 5; // 5 cumulated bits
unsigned int fl_index_shift : 3; // 8 cumulated bits
unsigned int fl_index_max : 6; // 14 cumulated bits
unsigned int sl_index_count : 6; // 20 cumulated bits
/* log2 of number of linear subdivisions of block sizes. Larger
** values require more memory in the control structure. Values of
** 4 or 5 are typical.
*/
unsigned int sl_index_count_log2 : 3; // 23 cumulated bits
unsigned int small_block_size : 8; // 31 cumulated bits
/* size of the metadata ( size of control block,
* sl_bitmap and blocks )
*/
size_t size;
/* Bitmaps for free lists. */
unsigned int fl_bitmap;
unsigned int *sl_bitmap;
/* Head of free lists. */
block_header_t** blocks;
} control_t;
/*
** Architecture-specific bit manipulation routines.
**
** TLSF achieves O(1) cost for malloc and free operations by limiting
** the search for a free block to a free list of guaranteed size
** adequate to fulfill the request, combined with efficient free list
** queries using bitmasks and architecture-specific bit-manipulation
** routines.
**
** Most modern processors provide instructions to count leading zeroes
** in a word, find the lowest and highest set bit, etc. These
** specific implementations will be used when available, falling back
** to a reasonably efficient generic implementation.
**
** NOTE: TLSF spec relies on ffs/fls returning value 0..31.
** ffs/fls return 1-32 by default, returning 0 for error.
*/
/*
** Detect whether or not we are building for a 32- or 64-bit (LP/LLP)
** architecture. There is no reliable portable method at compile-time.
*/
#if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) \
|| defined (_WIN64) || defined (__LP64__) || defined (__LLP64__)
#define TLSF_64BIT
#endif
/*
** gcc 3.4 and above have builtin support, specialized for architecture.
** Some compilers masquerade as gcc; patchlevel test filters them out.
*/
#if defined (__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) \
&& defined (__GNUC_PATCHLEVEL__)
#if defined (__SNC__)
/* SNC for Playstation 3. */
tlsf_decl int tlsf_ffs(unsigned int word)
{
const unsigned int reverse = word & (~word + 1);
const int bit = 32 - __builtin_clz(reverse);
return bit - 1;
}
#else
tlsf_decl int tlsf_ffs(unsigned int word)
{
return __builtin_ffs(word) - 1;
}
#endif
tlsf_decl int tlsf_fls(unsigned int word)
{
const int bit = word ? 32 - __builtin_clz(word) : 0;
return bit - 1;
}
#elif defined (_MSC_VER) && (_MSC_VER >= 1400) && (defined (_M_IX86) || defined (_M_X64))
/* Microsoft Visual C++ support on x86/X64 architectures. */
#include <intrin.h>
#pragma intrinsic(_BitScanReverse)
#pragma intrinsic(_BitScanForward)
tlsf_decl int tlsf_fls(unsigned int word)
{
unsigned long index;
return _BitScanReverse(&index, word) ? index : -1;
}
tlsf_decl int tlsf_ffs(unsigned int word)
{
unsigned long index;
return _BitScanForward(&index, word) ? index : -1;
}
#elif defined (_MSC_VER) && defined (_M_PPC)
/* Microsoft Visual C++ support on PowerPC architectures. */
#include <ppcintrinsics.h>
tlsf_decl int tlsf_fls(unsigned int word)
{
const int bit = 32 - _CountLeadingZeros(word);
return bit - 1;
}
tlsf_decl int tlsf_ffs(unsigned int word)
{
const unsigned int reverse = word & (~word + 1);
const int bit = 32 - _CountLeadingZeros(reverse);
return bit - 1;
}
#elif defined (__ARMCC_VERSION)
/* RealView Compilation Tools for ARM */
tlsf_decl int tlsf_ffs(unsigned int word)
{
const unsigned int reverse = word & (~word + 1);
const int bit = 32 - __clz(reverse);
return bit - 1;
}
tlsf_decl int tlsf_fls(unsigned int word)
{
const int bit = word ? 32 - __clz(word) : 0;
return bit - 1;
}
#elif defined (__ghs__)
/* Green Hills support for PowerPC */
#include <ppc_ghs.h>
tlsf_decl int tlsf_ffs(unsigned int word)
{
const unsigned int reverse = word & (~word + 1);
const int bit = 32 - __CLZ32(reverse);
return bit - 1;
}
tlsf_decl int tlsf_fls(unsigned int word)
{
const int bit = word ? 32 - __CLZ32(word) : 0;
return bit - 1;
}
#else
/* Fall back to generic implementation. */
tlsf_decl int tlsf_fls_generic(unsigned int word)
{
int bit = 32;
if (!word) bit -= 1;
if (!(word & 0xffff0000)) { word <<= 16; bit -= 16; }
if (!(word & 0xff000000)) { word <<= 8; bit -= 8; }
if (!(word & 0xf0000000)) { word <<= 4; bit -= 4; }
if (!(word & 0xc0000000)) { word <<= 2; bit -= 2; }
if (!(word & 0x80000000)) { word <<= 1; bit -= 1; }
return bit;
}
/* Implement ffs in terms of fls. */
tlsf_decl int tlsf_ffs(unsigned int word)
{
return tlsf_fls_generic(word & (~word + 1)) - 1;
}
tlsf_decl int tlsf_fls(unsigned int word)
{
return tlsf_fls_generic(word) - 1;
}
#endif
/* Possibly 64-bit version of tlsf_fls. */
#if defined (TLSF_64BIT)
tlsf_decl int tlsf_fls_sizet(size_t size)
{
int high = (int)(size >> 32);
int bits = 0;
if (high)
{
bits = 32 + tlsf_fls(high);
}
else
{
bits = tlsf_fls((int)size & 0xffffffff);
}
return bits;
}
#else
#define tlsf_fls_sizet tlsf_fls
#endif
tlsf_decl size_t align_up(size_t x, size_t align)
{
tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two");
return (x + (align - 1)) & ~(align - 1);
}
tlsf_decl size_t align_down(size_t x, size_t align)
{
tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two");
return x - (x & (align - 1));
}
tlsf_decl void* align_ptr(const void* ptr, size_t align)
{
const tlsfptr_t aligned =
(tlsf_cast(tlsfptr_t, ptr) + (align - 1)) & ~(align - 1);
tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two");
return tlsf_cast(void*, aligned);
}
tlsf_decl size_t tlsf_align_size(void)
{
return ALIGN_SIZE;
}
tlsf_decl size_t tlsf_block_size_min(void)
{
return block_size_min;
}
tlsf_decl size_t tlsf_block_size_max(control_t *control)
{
if (control == NULL)
{
return 0;
}
return tlsf_cast(size_t, 1) << control->fl_index_max;
}
/*
** Adjust an allocation size to be aligned to word size, and no smaller
** than internal minimum.
*/
tlsf_decl size_t adjust_request_size(control_t *control, size_t size, size_t align)
{
size_t adjust = 0;
if (size)
{
const size_t aligned = align_up(size, align);
/* aligned sized must not exceed block_size_max or we'll go out of bounds on sl_bitmap */
if (aligned < tlsf_block_size_max(control))
{
adjust = tlsf_max(aligned, block_size_min);
}
}
return adjust;
}
/*
** TLSF utility functions. In most cases, these are direct translations of
** the documentation found in the white paper.
*/
tlsf_decl void mapping_insert(control_t* control, size_t size, int* fli, int* sli)
{
int fl, sl;
if (size < control->small_block_size)
{
/* Store small blocks in first list. */
fl = 0;
sl = tlsf_cast(int, size) / (control->small_block_size / control->sl_index_count);
}
else
{
fl = tlsf_fls_sizet(size);
sl = tlsf_cast(int, size >> (fl - control->sl_index_count_log2)) ^ (1 << control->sl_index_count_log2);
fl -= (control->fl_index_shift - 1);
}
*fli = fl;
*sli = sl;
}
/* This version rounds up to the next block size (for allocations) */
tlsf_decl void mapping_search(control_t* control, size_t* size, int* fli, int* sli)
{
if (*size >= control->small_block_size)
{
const size_t round = (1 << (tlsf_fls_sizet(*size) - control->sl_index_count_log2));
*size = align_up(*size, round);
}
mapping_insert(control, *size, fli, sli);
}
tlsf_decl block_header_t* search_suitable_block(control_t* control, int* fli, int* sli)
{
int fl = *fli;
int sl = *sli;
/*
** First, search for a block in the list associated with the given
** fl/sl index.
*/
unsigned int sl_map = control->sl_bitmap[fl] & (~0U << sl);
if (!sl_map)
{
/* No block exists. Search in the next largest first-level list. */
const unsigned int fl_map = control->fl_bitmap & (~0U << (fl + 1));
if (!fl_map)
{
/* No free blocks available, memory has been exhausted. */
return 0;
}
fl = tlsf_ffs(fl_map);
*fli = fl;
sl_map = control->sl_bitmap[fl];
}
tlsf_assert(sl_map && "internal error - second level bitmap is null");
sl = tlsf_ffs(sl_map);
*sli = sl;
/* Return the first block in the free list. */
return control->blocks[fl * control->sl_index_count + sl];
}
/* Remove a free block from the free list.*/
tlsf_decl void remove_free_block(control_t* control, block_header_t* block, int fl, int sl)
{
block_header_t* prev = block->prev_free;
block_header_t* next = block->next_free;
tlsf_assert(prev && "prev_free field can not be null");
tlsf_assert(next && "next_free field can not be null");
next->prev_free = prev;
prev->next_free = next;
/* If this block is the head of the free list, set new head. */
if (control->blocks[fl * control->sl_index_count + sl] == block)
{
control->blocks[fl * control->sl_index_count + sl] = next;
/* If the new head is null, clear the bitmap. */
if (next == &control->block_null)
{
control->sl_bitmap[fl] &= ~(1U << sl);
/* If the second bitmap is now empty, clear the fl bitmap. */
if (!control->sl_bitmap[fl])
{
control->fl_bitmap &= ~(1U << fl);
}
}
}
}
/* Insert a free block into the free block list. */
tlsf_decl void insert_free_block(control_t* control, block_header_t* block, int fl, int sl)
{
block_header_t* current = control->blocks[fl * control->sl_index_count + sl];
tlsf_assert(current && "free list cannot have a null entry");
tlsf_assert(block && "cannot insert a null entry into the free list");
block->next_free = current;
block->prev_free = &control->block_null;
current->prev_free = block;
tlsf_assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE)
&& "block not aligned properly");
/*
** Insert the new block at the head of the list, and mark the first-
** and second-level bitmaps appropriately.
*/
control->blocks[fl * control->sl_index_count + sl] = block;
control->fl_bitmap |= (1U << fl);
control->sl_bitmap[fl] |= (1U << sl);
}
/* Remove a given block from the free list. */
tlsf_decl void block_remove(control_t* control, block_header_t* block)
{
int fl, sl;
mapping_insert(control, block_size(block), &fl, &sl);
remove_free_block(control, block, fl, sl);
}
/* Insert a given block into the free list. */
tlsf_decl void block_insert(control_t* control, block_header_t* block)
{
int fl, sl;
mapping_insert(control, block_size(block), &fl, &sl);
insert_free_block(control, block, fl, sl);
}
tlsf_decl int block_can_split(block_header_t* block, size_t size)
{
return block_size(block) >= sizeof(block_header_t) + size;
}
/* Split a block into two, the second of which is free. */
tlsf_decl block_header_t* block_split(block_header_t* block, size_t size)
{
/* Calculate the amount of space left in the remaining block.
* REMINDER: remaining pointer's first field is `prev_phys_block` but this field is part of the
* previous physical block. */
block_header_t* remaining =
offset_to_block(block_to_ptr(block), size - block_header_overhead);
/* `size` passed as an argument is the first block's new size, thus, the remaining block's size
* is `block_size(block) - size`. However, the block's data must be precedeed by the data size.
* This field is NOT part of the size, so it has to be substracted from the calculation. */
const size_t remain_size = block_size(block) - (size + block_header_overhead);
tlsf_assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE)
&& "remaining block not aligned properly");
tlsf_assert(block_size(block) == remain_size + size + block_header_overhead);
block_set_size(remaining, remain_size);
tlsf_assert(block_size(remaining) >= block_size_min && "block split with invalid size");
block_set_size(block, size);
block_mark_as_free(remaining);
/**
* Here is the final outcome of this function:
*
* block remaining (block_ptr + size - BHO)
* + +
* | |
* v v
* +----------------------------------------------------------------------+
* |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################|
* |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################|
* |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################|
* |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################|
* +----------------------------------------------------------------------+
* | | | |
* + +<------------------------->+ +<------------------------->
* BHO `size` (argument) bytes BHO `remain_size` bytes
*
* Where BHO = block_header_overhead,
* 0: part of the memory owned by a `block`'s previous neighbour,
* x: part of the memory owned by `block`.
* #: part of the memory owned by `remaining`.
*/
return remaining;
}
/*!
* @brief Weak function filling the given memory with a given fill pattern.
*
* @param start: pointer to the start of the memory region to fill
* @param size: size of the memory region to fill
* @param is_free: Indicate if the pattern to use the fill the region should be
* an after free or after allocation pattern.
*/
__attribute__((weak)) void block_absorb_post_hook(void *start, size_t size, bool is_free);
/* Absorb a free block's storage into an adjacent previous free block. */
tlsf_decl block_header_t* block_absorb(block_header_t* prev, block_header_t* block)
{
tlsf_assert(!block_is_last(prev) && "previous block can't be last");
/* Note: Leaves flags untouched. */
prev->size += block_size(block) + block_header_overhead;
block_link_next(prev);
if (block_absorb_post_hook != NULL)
{
block_absorb_post_hook(block, sizeof(block_header_t), POISONING_AFTER_FREE);
}
return prev;
}
/* Merge a just-freed block with an adjacent previous free block. */
tlsf_decl block_header_t* block_merge_prev(control_t* control, block_header_t* block)
{
if (block_is_prev_free(block))
{
block_header_t* prev = block_prev(block);
tlsf_assert(prev && "prev physical block can't be null");
tlsf_assert(block_is_free(prev) && "prev block is not free though marked as such");
block_remove(control, prev);
block = block_absorb(prev, block);
}
return block;
}
/* Merge a just-freed block with an adjacent free block. */
tlsf_decl block_header_t* block_merge_next(control_t* control, block_header_t* block)
{
block_header_t* next = block_next(block);
tlsf_assert(next && "next physical block can't be null");
if (block_is_free(next))
{
tlsf_assert(!block_is_last(block) && "previous block can't be last");
block_remove(control, next);
block = block_absorb(block, next);
}
return block;
}
/* Trim any trailing block space off the end of a block, return to pool. */
tlsf_decl void block_trim_free(control_t* control, block_header_t* block, size_t size)
{
tlsf_assert(block_is_free(block) && "block must be free");
if (block_can_split(block, size))
{
block_header_t* remaining_block = block_split(block, size);
block_link_next(block);
block_set_prev_free(remaining_block);
block_insert(control, remaining_block);
}
}
/* Trim any trailing block space off the end of a used block, return to pool. */
tlsf_decl void block_trim_used(control_t* control, block_header_t* block, size_t size)
{
tlsf_assert(!block_is_free(block) && "block must be used");
if (block_can_split(block, size))
{
/* If the next block is free, we must coalesce. */
block_header_t* remaining_block = block_split(block, size);
block_set_prev_used(remaining_block);
remaining_block = block_merge_next(control, remaining_block);
block_insert(control, remaining_block);
}
}
tlsf_decl block_header_t* block_trim_free_leading(control_t* control, block_header_t* block, size_t size)
{
block_header_t* remaining_block = block;
if (block_can_split(block, size))
{
/* We want to split `block` in two: the first block will be freed and the
* second block will be returned. */
remaining_block = block_split(block, size - block_header_overhead);
/* `remaining_block` is the second block, mark its predecessor (first
* block) as free. */
block_set_prev_free(remaining_block);
block_link_next(block);
/* Put back the first block into the free memory list. */
block_insert(control, block);
}
return remaining_block;
}
tlsf_decl block_header_t* block_locate_free(control_t* control, size_t* size)
{
int fl = 0, sl = 0;
block_header_t* block = 0;
if (*size)
{
mapping_search(control, size, &fl, &sl);
/*
** mapping_search can futz with the size, so for excessively large sizes it can sometimes wind up
** with indices that are off the end of the block array.
** So, we protect against that here, since this is the only callsite of mapping_search.
** Note that we don't need to check sl, since it comes from a modulo operation that guarantees it's always in range.
*/
if (fl < control->fl_index_count)
{
block = search_suitable_block(control, &fl, &sl);
}
}
if (block)
{
tlsf_assert(block_size(block) >= *size);
remove_free_block(control, block, fl, sl);
}
return block;
}
tlsf_decl void* block_prepare_used(control_t* control, block_header_t* block, size_t size)
{
void* p = 0;
if (block)
{
tlsf_assert(size && "size must be non-zero");
block_trim_free(control, block, size);
block_mark_as_used(block);
p = block_to_ptr(block);
}
return p;
}
#undef tlsf_decl
#if defined(__cplusplus)
};
#endif