Skip to content

Commit

Permalink
change(system): heap_caps_alloc returns aligned memory if caps indica…
Browse files Browse the repository at this point in the history
…te 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.
  • Loading branch information
Spritetm committed May 27, 2024
1 parent b1a5d80 commit a1ba660
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 53 deletions.
2 changes: 2 additions & 0 deletions components/esp_mm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
17 changes: 13 additions & 4 deletions components/esp_mm/esp_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
Expand Down
100 changes: 100 additions & 0 deletions components/esp_mm/heap_align_hw.c
Original file line number Diff line number Diff line change
@@ -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;
}
16 changes: 16 additions & 0 deletions components/esp_mm/include/esp_private/esp_cache_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/**
Expand All @@ -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, ...);

/**
Expand All @@ -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);

/**
Expand All @@ -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, ...);

/**
Expand Down
39 changes: 39 additions & 0 deletions components/esp_mm/include/esp_private/heap_align_hw.h
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions components/esp_mm/test_apps/mm/main/test_cache_msync_malloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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);

Expand All @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions components/esp_rom/include/esp32c61/rom/cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion components/hal/esp32c61/include/hal/cache_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading

0 comments on commit a1ba660

Please sign in to comment.