change(system): heap_caps_alloc returns aligned memory if caps indicate a need for it

The implicit promise of heap_alloc_caps() and friends is that the memory it
returns is fit for the purpose as requested in the caps field. Before
this commit, that did not happen; e.g. DMA-capable memory wass returned
from a correct region, but not aligned/sized to something the DMA subsystem
can handle.

This commit adds an API to the esp_mm component that is then used by the
heap component to adjust allocation alignment, caps and size dependent on
the hardware requirement of the requested allocation caps.
This commit is contained in:
Jeroen Domburg 2024-04-23 12:59:39 +08:00
parent b1a5d80bcc
commit a1ba660b4a
15 changed files with 323 additions and 53 deletions

View File

@ -21,6 +21,8 @@ if(NOT CONFIG_APP_BUILD_TYPE_PURE_RAM_APP)
endif()
endif()
list(APPEND srcs "heap_align_hw.c")
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${includes}
PRIV_REQUIRES ${priv_requires}

View File

@ -93,7 +93,10 @@ esp_err_t esp_cache_msync(void *addr, size_t size, int flags)
return ESP_OK;
}
esp_err_t esp_cache_aligned_malloc(size_t size, uint32_t heap_caps, void **out_ptr, size_t *actual_size)
//The esp_cache_aligned_malloc function is marked deprecated but also called by other
//(also deprecated) functions in this file. In order to work around that generating warnings, it's
//split into a non-deprecated internal function and the stubbed external deprecated function.
static esp_err_t esp_cache_aligned_malloc_internal(size_t size, uint32_t heap_caps, void **out_ptr, size_t *actual_size)
{
ESP_RETURN_ON_FALSE_ISR(out_ptr, ESP_ERR_INVALID_ARG, TAG, "null pointer");
uint32_t valid_caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
@ -127,6 +130,12 @@ esp_err_t esp_cache_aligned_malloc(size_t size, uint32_t heap_caps, void **out_p
return ESP_OK;
}
//this is the deprecated stub for the above function
esp_err_t esp_cache_aligned_malloc(size_t size, uint32_t heap_caps, void **out_ptr, size_t *actual_size)
{
return esp_cache_aligned_malloc_internal(size, heap_caps, out_ptr, actual_size);
}
esp_err_t esp_cache_aligned_malloc_prefer(size_t size, void **out_ptr, size_t *actual_size, size_t flag_nums, ...)
{
ESP_RETURN_ON_FALSE_ISR(out_ptr, ESP_ERR_INVALID_ARG, TAG, "null pointer");
@ -139,7 +148,7 @@ esp_err_t esp_cache_aligned_malloc_prefer(size_t size, void **out_ptr, size_t *a
while (flag_nums--) {
flags = va_arg(argp, uint32_t);
ret = esp_cache_aligned_malloc(size, flags, out_ptr, actual_size);
ret = esp_cache_aligned_malloc_internal(size, flags, out_ptr, actual_size);
if (ret == ESP_OK) {
break;
}
@ -161,7 +170,7 @@ esp_err_t esp_cache_aligned_calloc(size_t n, size_t size, uint32_t heap_caps, vo
ESP_RETURN_ON_FALSE_ISR(!ovf, ESP_ERR_INVALID_ARG, TAG, "wrong size, total size overflow");
void *ptr = NULL;
ret = esp_cache_aligned_malloc(size_bytes, heap_caps, &ptr, actual_size);
ret = esp_cache_aligned_malloc_internal(size_bytes, heap_caps, &ptr, actual_size);
if (ret == ESP_OK) {
memset(ptr, 0, size_bytes);
*out_ptr = ptr;
@ -188,7 +197,7 @@ esp_err_t esp_cache_aligned_calloc_prefer(size_t n, size_t size, void **out_ptr,
int arg;
for (int i = 0; i < flag_nums; i++) {
arg = va_arg(argp, int);
ret = esp_cache_aligned_malloc_prefer(size_bytes, &ptr, actual_size, flag_nums, arg);
ret = esp_cache_aligned_malloc_internal(size_bytes, arg, &ptr, actual_size);
if (ret == ESP_OK) {
memset(ptr, 0, size_bytes);
*out_ptr = ptr;

View File

@ -0,0 +1,100 @@
/*
* SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdarg.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_heap_caps.h"
#include "esp_private/esp_cache_private.h"
#include "soc/soc_caps.h"
#if SOC_GDMA_SUPPORTED
#include "hal/gdma_ll.h"
#endif
#if CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH
#define HEAP_IRAM_ATTR
#else
#define HEAP_IRAM_ATTR IRAM_ATTR
#endif
#define CAPS_NEEDING_ALIGNMENT (MALLOC_CAP_DMA|MALLOC_CAP_DMA_DESC_AHB|MALLOC_CAP_DMA_DESC_AXI|MALLOC_CAP_CACHE_ALIGNED)
HEAP_IRAM_ATTR void esp_heap_adjust_alignment_to_hw(size_t *p_alignment, size_t *p_size, uint32_t *p_caps)
{
size_t size = *p_size;
size_t alignment = *p_alignment;
uint32_t caps = *p_caps;
//Bail out early if we don't need alignment
if (!(caps & CAPS_NEEDING_ALIGNMENT)) {
return;
}
#if CONFIG_APP_BUILD_TYPE_PURE_RAM_APP
//Cache functions aren't linked in so we cannot allocate anything needing alignment.
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
//Cannot do this for either internal or external memory.
*p_caps |= MALLOC_CAP_INVALID;
#else
//Internal memory DMA alloc is allowed.
if (caps & MALLOC_CAP_SPIRAM) {
*p_caps |= MALLOC_CAP_INVALID;
}
#endif
return;
#endif
//Ask cache driver what alignment is applicable here.
size_t cache_alignment_bytes = 0;
esp_err_t ret = esp_cache_get_alignment(caps, &cache_alignment_bytes);
if (ret != ESP_OK) {
//This is not supposed to happen.
*p_caps |= MALLOC_CAP_INVALID;
return;
}
#if SOC_GDMA_SUPPORTED && SOC_AXI_GDMA_SUPPORTED
//Special case: AXI DMA descriptors need to be aligned to 8-byte boundaries.
if ((caps & MALLOC_CAP_DMA_DESC_AXI) && (cache_alignment_bytes < GDMA_LL_AXI_DESC_ALIGNMENT)) {
cache_alignment_bytes = GDMA_LL_AXI_DESC_ALIGNMENT;
}
#endif
// We assume both DMA alignment and requested alignment are powers of two. We can safely
// do this because:
// - DMA alignment in current chips always is a power of two, and is unlikely to ever
// be something else,
// - Requested alignment is checked by heap_caps_aligned_check_args to be a power
// of two.
if (cache_alignment_bytes > alignment) {
alignment = cache_alignment_bytes;
}
// Align up `size` to resulting alignment as well.
size = (size + alignment - 1) & (~(alignment - 1));
// For the heap allocator itself, there's no difference between data and descriptor DMA; the regions
// are only marked as DMA-capable.
if (caps & (MALLOC_CAP_DMA_DESC_AHB | MALLOC_CAP_DMA_DESC_AXI)) {
caps &= ~(MALLOC_CAP_DMA_DESC_AHB | MALLOC_CAP_DMA_DESC_AXI);
caps |= MALLOC_CAP_DMA;
}
// Workaround: the heap allocator doesn't have regions marked `MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM`
// so we need to request those without the DMA flag.
if (caps & MALLOC_CAP_SPIRAM) {
caps &= ~MALLOC_CAP_DMA;
//Note: we do not erase any DMA descriptor caps. DMA descriptors cannot be in external
//memory, so the MALLOC_CAP_SPIRAM|MALLOC_CAP_DMA_DESC_* simply will not return any
//usable memory.
}
// MALLOC_CAP_CACHE_ALIGNED is not a real flag the heap_base component will understand; it
// only sets alignment (which we handled here)
caps &= ~ MALLOC_CAP_CACHE_ALIGNED;
*p_size = size;
*p_alignment = alignment;
*p_caps = caps;
}

View File

@ -25,11 +25,15 @@ extern "C" {
* @param[out] out_ptr A pointer to the memory allocated successfully
* @param[out] actual_size Actual size for allocation in bytes, when the size you specified doesn't meet the cache alignment requirements, this value might be bigger than the size you specified. Set null if you don't care this value.
*
* @deprecated This function is deprecated and will be removed in the future.
* Use 'heap_caps_malloc' with MALLOC_CAP_CACHE_ALIGNED caps instead
*
* @return
* - ESP_OK:
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NO_MEM: No enough memory for allocation
*/
__attribute__((deprecated("Use 'heap_caps_malloc' with MALLOC_CAP_CACHE_ALIGNED caps instead")))
esp_err_t esp_cache_aligned_malloc(size_t size, uint32_t heap_caps, void **out_ptr, size_t *actual_size);
/**
@ -45,11 +49,15 @@ esp_err_t esp_cache_aligned_malloc(size_t size, uint32_t heap_caps, void **out_p
* 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.
*
* @deprecated This function is deprecated and will be removed in the future.
* Use 'heap_caps_malloc_prefer' with MALLOC_CAP_CACHE_ALIGNED caps instead
*
* @return
* - ESP_OK:
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NO_MEM: No enough memory for allocation
*/
__attribute__((deprecated("Use 'heap_caps_malloc_prefer' with MALLOC_CAP_CACHE_ALIGNED caps instead")))
esp_err_t esp_cache_aligned_malloc_prefer(size_t size, void **out_ptr, size_t *actual_size, size_t flag_nums, ...);
/**
@ -63,11 +71,15 @@ esp_err_t esp_cache_aligned_malloc_prefer(size_t size, void **out_ptr, size_t *a
* @param[out] out_ptr A pointer to the memory allocated successfully
* @param[out] actual_size Actual size for allocation in bytes, when the size you specified doesn't meet the cache alignment requirements, this value might be bigger than the size you specified. Set null if you don't care this value.
*
* @deprecated This function is deprecated and will be removed in the future.
* Use 'heap_caps_calloc' with MALLOC_CAP_CACHE_ALIGNED caps instead
*
* @return
* - ESP_OK:
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NO_MEM: No enough memory for allocation
*/
__attribute__((deprecated("Use 'heap_caps_calloc' with MALLOC_CAP_CACHE_ALIGNED caps instead")))
esp_err_t esp_cache_aligned_calloc(size_t n, size_t size, uint32_t heap_caps, void **out_ptr, size_t *actual_size);
/**
@ -84,11 +96,15 @@ esp_err_t esp_cache_aligned_calloc(size_t n, size_t size, uint32_t heap_caps, vo
* 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.
*
* @deprecated This function is deprecated and will be removed in the future.
* Use 'heap_caps_calloc_prefer' with MALLOC_CAP_CACHE_ALIGNED caps instead
*
* @return
* - ESP_OK:
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NO_MEM: No enough memory for allocation
*/
__attribute__((deprecated("Use 'heap_caps_calloc' with MALLOC_CAP_CACHE_ALIGNED caps instead")))
esp_err_t esp_cache_aligned_calloc_prefer(size_t n, size_t size, void **out_ptr, size_t *actual_size, size_t flag_nums, ...);
/**

View File

@ -0,0 +1,39 @@
/*
* 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 "sdkconfig.h"
#include "esp_err.h"
#include "esp_attr.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Adjust size, alignment and caps of a memory allocation request to the specific
* hardware requirements, dependent on where the memory gets allocated.
*
* @note Note that heap_caps_base.c has its own definition for this function in order not to depend
* on this component.
*
* @param[in,out] p_alignment Pointer to alignment requirements. This may be modified upwards if the
* hardware has stricter alignment requirements.
* @param[in,out] p_size Pointer to size of memory to be allocated. This may be modified upwards
* if e.g. the memory needs to be aligned to a cache line.
* @param[in,out] p_caps Pointer to memory requirements. This may be adjusted if the memory
* requirements need modification for the heap caps allocator to work
* properly.
*/
void esp_heap_adjust_alignment_to_hw(size_t *p_alignment, size_t *p_size, uint32_t *p_caps);
#ifdef __cplusplus
}
#endif

View File

@ -11,7 +11,9 @@
#include "esp_log.h"
#include "esp_attr.h"
#include "unity.h"
#include "esp_private/esp_cache_private.h"
#include "esp_memory_utils.h"
const static char *TAG = "CACHE_MALLOC_TEST";
@ -20,7 +22,10 @@ TEST_CASE("test esp_cache_aligned_malloc_prefer", "[cache]")
{
void *ptr = NULL;
size_t actual_size = 0;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
TEST_ESP_OK(esp_cache_aligned_malloc_prefer(40, &ptr, &actual_size, 1, MALLOC_CAP_DMA, 0));
#pragma GCC diagnostic pop
TEST_ASSERT(esp_ptr_dma_capable(ptr));
ESP_LOGI(TAG, "actual size: 0x%x", actual_size);
@ -31,7 +36,10 @@ TEST_CASE("test esp_cache_aligned_calloc_prefer", "[cache]")
{
void *ptr = NULL;
size_t actual_size = 0;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
TEST_ESP_OK(esp_cache_aligned_calloc_prefer(1, 40, &ptr, &actual_size, 1, MALLOC_CAP_DMA, 0));
#pragma GCC diagnostic pop
TEST_ASSERT(esp_ptr_dma_capable(ptr));
ESP_LOGI(TAG, "actual size: 0d%d", actual_size);

View File

@ -530,7 +530,7 @@ void Cache_Resume_Cache(uint32_t autoload);
*
* @return uint32_t: 16, 32, 64 Byte
*/
uint32_t Cache_Get_Cache_Line_Size(void);
uint32_t Cache_Get_Line_Size(void);
/**
* @brief Enable freeze for ICache.
@ -574,7 +574,7 @@ void Cache_Travel_Tag_Memory(struct cache_mode * mode, uint32_t filter_addr, voi
*
* @param struct cache_mode * mode : the cache to calculate the virtual address and the cache mode.
*
* @param uint32_t tag : the tag part fo a tag item, 12-14 bits.
* @param uint32_t tag : the tag part of a tag item, 12-14 bits.
*
* @param uint32_t addr_offset : the virtual address offset of the cache ways.
*

View File

@ -168,7 +168,7 @@ __attribute__((always_inline))
static inline uint32_t cache_ll_get_line_size(uint32_t cache_level, cache_type_t type, uint32_t cache_id)
{
uint32_t size = 0;
size = Cache_Get_Cache_Line_Size();
size = Cache_Get_Line_Size();
return size;
}

View File

@ -23,6 +23,12 @@
#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);
//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.
@ -66,14 +72,26 @@ HEAP_IRAM_ATTR void heap_caps_free( void *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()
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_malloc_base( size_t size, uint32_t caps)
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) ) {
@ -118,7 +136,8 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_malloc_base( size_t size, uint32_t
//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 = multi_heap_malloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size) + 4); // int overflow checked above
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);
@ -128,7 +147,8 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_malloc_base( size_t size, uint32_t
}
} else {
//Just try to alloc, nothing special.
ret = multi_heap_malloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size));
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);
@ -145,6 +165,11 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_malloc_base( size_t size, uint32_t
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()
@ -155,8 +180,12 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_realloc_base( void *ptr, size_t siz
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_malloc_base(size, caps);
return heap_caps_aligned_alloc_base(alignment, size, caps);
}
if (size == 0) {
@ -200,7 +229,9 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_realloc_base( void *ptr, size_t siz
// requested ones?
bool compatible_caps = (caps & get_all_caps(heap)) == caps;
if (compatible_caps && !ptr_in_diram_case) {
//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));
@ -214,7 +245,7 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_realloc_base( void *ptr, size_t siz
// 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_malloc_base(size, caps);
void *new_p = heap_caps_aligned_alloc_base(alignment, size, caps);
if (new_p != NULL) {
size_t old_size = 0;
@ -256,34 +287,3 @@ HEAP_IRAM_ATTR void *heap_caps_calloc_base( size_t n, size_t size, uint32_t caps
}
return result;
}
HEAP_IRAM_ATTR void *heap_caps_aligned_alloc_base(size_t alignment, size_t size, uint32_t caps)
{
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) {
// Just try to alloc, nothing special. Provide the size of the block owner
// as an offset to prevent a miscalculation of the alignment.
void *ret = multi_heap_aligned_alloc_offs(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;
}

View File

@ -43,6 +43,9 @@ extern "C" {
#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
@ -385,7 +388,7 @@ void *heap_caps_malloc_prefer( size_t size, size_t num, ... );
*
* @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 paramters
* @param num Number of variable parameters
*
* @return Pointer to a new buffer of size 'size', or NULL if allocation failed.
*/
@ -396,7 +399,7 @@ void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ... );
*
* @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 paramters
* @param num Number of variable parameters
*
* @return A pointer to the memory allocated on success, NULL on failure
*/

View File

@ -1,5 +1,6 @@
set(src_test "test_heap_main.c"
"test_aligned_alloc_caps.c"
"test_heap_align_hw.c"
"test_allocator_timings.c"
"test_corruption_check.c"
"test_diram.c"
@ -13,5 +14,5 @@ set(src_test "test_heap_main.c"
idf_component_register(SRCS ${src_test}
INCLUDE_DIRS "."
REQUIRES unity esp_psram spi_flash
REQUIRES unity esp_psram spi_flash esp_mm
WHOLE_ARCHIVE)

View File

@ -0,0 +1,87 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include <sys/param.h>
#include <string.h>
#include "inttypes.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "unity.h"
#include "esp_private/esp_cache_private.h"
#include "esp_memory_utils.h"
#include "hal/cache_ll.h"
#include "hal/cache_hal.h"
TEST_CASE("test heap_caps_malloc_prefer for dma memory", "[hw-align]")
{
void *ptr = NULL;
ptr = heap_caps_malloc_prefer(40, 1, MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(ptr);
TEST_ASSERT(esp_ptr_dma_capable(ptr));
free(ptr);
}
TEST_CASE("test heap_caps_calloc_prefer for dma memory", "[hw-align]")
{
void *ptr = NULL;
ptr = heap_caps_calloc_prefer(40, 1, 1, MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(ptr);
TEST_ASSERT(esp_ptr_dma_capable(ptr));
free(ptr);
}
#include "esp_private/heap_align_hw.h"
#define TEST_ALLOC_COUNT 100
static bool test_alignment(uint32_t caps, int expected_alignment) {
bool ret=true;
void *mem[TEST_ALLOC_COUNT];
size_t size[TEST_ALLOC_COUNT];
//First, check if we can allocate memory with these caps anyway.
void *tst=heap_caps_malloc(1, caps);
if (!tst) return true;
free(tst);
//Step 1: generate sizes and allocate memory.
for (int i=0; i<TEST_ALLOC_COUNT; i++) {
size[i]=rand()&128;
mem[i]=heap_caps_malloc(size[i], caps);
}
// Step 2: check alignment and fill up memory up to the aligned size
// (which should succeed as we expect to get an integer amount of cache lines)
for (int i=0; i<TEST_ALLOC_COUNT; i++) {
intptr_t off=(intptr_t)mem[i];
if (off&(expected_alignment-1)) ret=false;
size_t size_aligned_up = (size[i] + expected_alignment - 1) & (~(expected_alignment - 1));
memset(mem[i], 0xA5, size_aligned_up);
}
//Step 3: Free the memory again.
//This should not lead to heap corruption (from the memset) being detected.
for (int i=0; i<TEST_ALLOC_COUNT; i++) {
free(mem[i]);
}
return ret;
}
TEST_CASE("test alignment for dma", "[hw-align]")
{
int int_cache_size=cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
int ext_cache_size=cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
test_alignment(MALLOC_CAP_DMA, int_cache_size);
test_alignment(MALLOC_CAP_DMA_DESC_AHB, int_cache_size);
test_alignment(MALLOC_CAP_DMA_DESC_AXI, int_cache_size>8?int_cache_size:8);
test_alignment(MALLOC_CAP_DMA|MALLOC_CAP_SPIRAM, ext_cache_size);
}

View File

@ -107,7 +107,8 @@ Use the ``MALLOC_CAP_DMA`` flag to allocate memory which is suitable for use wit
.. only SOC_SPIRAM_SUPPORTED and not esp32::
The EDMA hardware feature allows DMA buffers to be placed in external PSRAM, but there may be additional alignment constraints. Consult the {IDF_TARGET_NAME} Technical Reference Manual for details. To allocate a DMA-capable external memory buffer, use the ``MALLOC_CAP_SPIRAM`` capabilities flag together with :cpp:func:`heap_caps_aligned_alloc` with the necessary alignment specified.
The EDMA hardware feature allows DMA buffers to be placed in external PSRAM, but there may be additional alignment constraints. Consult the {IDF_TARGET_NAME} Technical Reference Manual for details. To allocate a DMA-capable external memory buffer, use the ``MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA`` capabilities flags; the heap allocator will take care of alignment requirements imposed by the cache and DMA subsystems. If a peripheral has additional alignment requirements, you can use :cpp:func:`heap_caps_aligned_alloc` with the necessary alignment specified.
.. _32-bit accessible memory:

View File

@ -50,7 +50,7 @@ ESP-IDF 应用程序使用常见的计算机架构模式:由程序控制流动
DRAM
^^^^
启动时DRAM 堆包含应用程序未静态分配的所有数据内存,减少静态分配的缓冲区将增加可用的空闲堆空间。
启动时DRAM 堆包含应用程序未静态分配的所有数据内存,减少静态分配的 buffer 将增加可用的空闲堆空间。
调用命令 :ref:`idf.py size <idf.py-size>` 可查找静态分配内存大小。
@ -107,7 +107,8 @@ DMA 存储器
.. only SOC_SPIRAM_SUPPORTED and not esp32::
EDMA 硬件功能允许将 DMA 缓冲区放置在外部 PSRAM但可能存在其他对齐限制详情请参阅 {IDF_TARGET_NAME} 技术参考手册。要分配一个可用 DMA 的外部内存缓冲区,请使用 ``MALLOC_CAP_SPIRAM`` 属性标志 和 :cpp:func:`heap_caps_aligned_alloc`,并指定必要的对齐方式。
EDMA 硬件功能可以将 DMA buffer 放置在外部 PSRAM但可能存在一定的对齐限制详情请参阅 {IDF_TARGET_NAME} 技术参考手册。若要分配一个可用 DMA 的外部 buffer请使用 ``MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA`` 属性标志,堆分配器将处理 cache 及 DMA 子系统的对齐要求。如果某个外设有额外的对齐要求,可以调用 :cpp:func:heap_caps_aligned_alloc 并指定必要的对齐方式。
.. _32-bit accessible memory:
@ -138,7 +139,7 @@ DMA 存储器
堆函数是线程安全的,因此可不受限制,在不同任务中同时调用多个堆函数。
从中断处理程序 (ISR) 上下文中调用 ``malloc````free`` 和相关函数虽然在技术层面可行(请参阅 :ref:`calling-heap-related-functions-from-isr`),但不建议使用此种方法,因为调用堆函数可能会延迟其他中断。建议重构应用程序,将 ISR 使用的任何缓冲区预先分配到 ISR 之外。之后可能会删除从 ISR 调用堆函数的功能。
从中断处理程序 (ISR) 上下文中调用 ``malloc````free`` 和相关函数虽然在技术层面可行(请参阅 :ref:`calling-heap-related-functions-from-isr`),但不建议使用此种方法,因为调用堆函数可能会延迟其他中断。建议重构应用程序,将 ISR 使用的任何 buffer 预先分配到 ISR 之外。之后可能会删除从 ISR 调用堆函数的功能。
.. _calling-heap-related-functions-from-isr:

View File

@ -4,3 +4,6 @@ CONFIG_APP_BUILD_TYPE_RAM=y
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
CONFIG_APP_BUILD_TYPE_PURE_RAM_APP=n
CONFIG_COMPILER_OPTIMIZATION_DEBUG=n
CONFIG_COMPILER_OPTIMIZATION_SIZE=y