diff --git a/components/esp32/heap_alloc_caps.c b/components/esp32/heap_alloc_caps.c index 511903c438..a77d372b4f 100644 --- a/components/esp32/heap_alloc_caps.c +++ b/components/esp32/heap_alloc_caps.c @@ -18,6 +18,7 @@ #include "esp_heap_alloc_caps.h" #include "spiram.h" #include "esp_log.h" +#include static const char* TAG = "heap_alloc_caps"; @@ -38,6 +39,7 @@ hardwiring addresses. typedef struct { const char *name; uint32_t prio[NO_PRIOS]; + bool aliasedIram; } tag_desc_t; /* @@ -46,23 +48,23 @@ Each tag contains NO_PRIOS entries; later entries are only taken if earlier ones Make sure there are never more than HEAPREGIONS_MAX_TAGCOUNT (in heap_regions.h) tags (ex the last empty marker) */ static const tag_desc_t tag_desc[]={ - { "DRAM", { MALLOC_CAP_DMA|MALLOC_CAP_8BIT, MALLOC_CAP_32BIT, 0 }}, //Tag 0: Plain ole D-port RAM - { "D/IRAM", { 0, MALLOC_CAP_DMA|MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_EXEC }}, //Tag 1: Plain ole D-port RAM which has an alias on the I-port - { "IRAM", { MALLOC_CAP_EXEC|MALLOC_CAP_32BIT, 0, 0 }}, //Tag 2: IRAM - { "PID2IRAM", { MALLOC_CAP_PID2, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }}, //Tag 3-8: PID 2-7 IRAM - { "PID3IRAM", { MALLOC_CAP_PID3, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }}, // - { "PID4IRAM", { MALLOC_CAP_PID4, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }}, // - { "PID5IRAM", { MALLOC_CAP_PID5, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }}, // - { "PID6IRAM", { MALLOC_CAP_PID6, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }}, // - { "PID7IRAM", { MALLOC_CAP_PID7, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }}, // - { "PID2DRAM", { MALLOC_CAP_PID2, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }}, //Tag 9-14: PID 2-7 DRAM - { "PID3DRAM", { MALLOC_CAP_PID3, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }}, // - { "PID4DRAM", { MALLOC_CAP_PID4, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }}, // - { "PID5DRAM", { MALLOC_CAP_PID5, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }}, // - { "PID6DRAM", { MALLOC_CAP_PID6, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }}, // - { "PID7DRAM", { MALLOC_CAP_PID7, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }}, // - { "SPISRAM", { MALLOC_CAP_SPISRAM, 0, MALLOC_CAP_DMA|MALLOC_CAP_8BIT|MALLOC_CAP_32BIT}}, //Tag 15: SPI SRAM data - { "", { MALLOC_CAP_INVALID, MALLOC_CAP_INVALID, MALLOC_CAP_INVALID }} //End + { "DRAM", { MALLOC_CAP_DMA|MALLOC_CAP_8BIT, MALLOC_CAP_32BIT, 0 }, false}, //Tag 0: Plain ole D-port RAM + { "D/IRAM", { 0, MALLOC_CAP_DMA|MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_EXEC }, true}, //Tag 1: Plain ole D-port RAM which has an alias on the I-port + { "IRAM", { MALLOC_CAP_EXEC|MALLOC_CAP_32BIT, 0, 0 }, false}, //Tag 2: IRAM + { "PID2IRAM", { MALLOC_CAP_PID2, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }, false}, //Tag 3-8: PID 2-7 IRAM + { "PID3IRAM", { MALLOC_CAP_PID3, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }, false}, // + { "PID4IRAM", { MALLOC_CAP_PID4, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }, false}, // + { "PID5IRAM", { MALLOC_CAP_PID5, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }, false}, // + { "PID6IRAM", { MALLOC_CAP_PID6, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }, false}, // + { "PID7IRAM", { MALLOC_CAP_PID7, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }, false}, // + { "PID2DRAM", { MALLOC_CAP_PID2, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false}, //Tag 9-14: PID 2-7 DRAM + { "PID3DRAM", { MALLOC_CAP_PID3, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false}, // + { "PID4DRAM", { MALLOC_CAP_PID4, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false}, // + { "PID5DRAM", { MALLOC_CAP_PID5, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false}, // + { "PID6DRAM", { MALLOC_CAP_PID6, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false}, // + { "PID7DRAM", { MALLOC_CAP_PID7, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false}, // + { "SPISRAM", { MALLOC_CAP_SPISRAM, 0, MALLOC_CAP_DMA|MALLOC_CAP_8BIT|MALLOC_CAP_32BIT}, false}, //Tag 15: SPI SRAM data + { "", { MALLOC_CAP_INVALID, MALLOC_CAP_INVALID, MALLOC_CAP_INVALID }, false} //End }; /* @@ -231,14 +233,61 @@ void heap_alloc_caps_init() { vPortDefineHeapRegionsTagged( regions ); } +//First and last words of the D/IRAM region, for both the DRAM address as well as the IRAM alias. +#define DIRAM_IRAM_START 0x400A0000 +#define DIRAM_IRAM_END 0x400BFFFC +#define DIRAM_DRAM_START 0x3FFE0000 +#define DIRAM_DRAM_END 0x3FFFFFFC + /* -Standard malloc() implementation. Will return ho-hum byte-accessible data memory. + 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 wel 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. + + In theory, we can also make this work by prepending a struct that looks similar to the block link struct used by the + heap allocator itself, which will allow inspection tools relying on any block returned from any sort of malloc to + have such a block in front of it, work. We may do this later, if/when there is demand for it. For now, a simple + pointer is used. +*/ +void *dram_alloc_to_iram_addr(void *addr, size_t len) +{ + uint32_t dstart=(int)addr; //First word + uint32_t dend=((int)addr)+len-4; //Last word + configASSERT(dstart>=DIRAM_DRAM_START); + configASSERT(dend<=DIRAM_DRAM_END); + configASSERT((dstart&3)==0); + configASSERT((dend&3)==0); + uint32_t istart=DIRAM_IRAM_START+(DIRAM_DRAM_END-dend); + uint32_t *iptr=(uint32_t*)istart; + *iptr=dstart; + return (void*)(iptr+1); +} + +/* +Standard malloc() implementation. Will return standard no-frills byte-accessible data memory. */ void *pvPortMalloc( size_t xWantedSize ) { return pvPortMallocCaps( xWantedSize, MALLOC_CAP_8BIT ); } +/* + Standard free() implementation. Will pass memory on to the allocator unless it's an IRAM address where the + actual meory is allocated in DRAM, it will convert to the DRAM address then. + */ +void vPortFree( void *pv ) +{ + if (((int)pv>=DIRAM_IRAM_START) && ((int)pv<=DIRAM_IRAM_END)) { + //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*)pv; + return vPortFreeTagged((void*)dramAddrPtr[-1]); + } + + return vPortFreeTagged(pv); +} + /* Routine to allocate a bit of memory with certain capabilities. caps is a bitfield of MALLOC_CAP_* bits. */ @@ -248,6 +297,17 @@ void *pvPortMallocCaps( size_t xWantedSize, uint32_t caps ) int tag, j; void *ret=NULL; uint32_t remCaps; + 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 tag_desc + //table would indicate there is a tag for this. + if ((caps & MALLOC_CAP_8BIT) || (caps & MALLOC_CAP_DMA)) { + return NULL; + } + //If any, EXEC memory should be 32-bit aligned, so round up to the next multiple of 4. + xWantedSize=(xWantedSize+3)&(~3); + } for (prio=0; prio +#include +#include "unity.h" +#include "rom/ets_sys.h" +#include "esp_heap_alloc_caps.h" +#include + + +TEST_CASE("Capabilities allocator test", "[esp32]") +{ + char *m1, *m2[10]; + int x; + size_t free8start, free32start, free8, free32; + free8start=xPortGetFreeHeapSizeCaps(MALLOC_CAP_8BIT); + free32start=xPortGetFreeHeapSizeCaps(MALLOC_CAP_32BIT); + printf("Free 8bit-capable memory: %dK, 32-bit capable memory %dK\n", free8start, free32start); + TEST_ASSERT(free32start>free8start); + printf("Allocating 10K of 8-bit capable RAM\n"); + m1=pvPortMallocCaps(10*1024, MALLOC_CAP_8BIT); + printf("--> %p\n", m1); + free8=xPortGetFreeHeapSizeCaps(MALLOC_CAP_8BIT); + free32=xPortGetFreeHeapSizeCaps(MALLOC_CAP_32BIT); + printf("Free 8bit-capable memory: %dK, 32-bit capable memory %dK\n", free8, free32); + //Both should have gone down by 10K; 8bit capable ram is also 32-bit capable + TEST_ASSERT(free8<(free8start-10*1024)); + TEST_ASSERT(free32<(free32start-10*1024)); + //Assume we got DRAM back + TEST_ASSERT((((int)m1)&0xFF000000)==0x3F000000); + free(m1); + printf("Freeing; allocating 10K of 32K-capable RAM\n"); + m1=pvPortMallocCaps(10*1024, MALLOC_CAP_32BIT); + printf("--> %p\n", m1); + free8=xPortGetFreeHeapSizeCaps(MALLOC_CAP_8BIT); + free32=xPortGetFreeHeapSizeCaps(MALLOC_CAP_32BIT); + printf("Free 8bit-capable memory: %dK, 32-bit capable memory %dK\n", free8, free32); + //Only 32-bit should have gone down by 10K: 32-bit isn't necessarily 8bit capable + TEST_ASSERT(free32<(free32start-10*1024)); + TEST_ASSERT(free8==free8start); + //Assume we got IRAM back + TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000); + free(m1); + printf("Allocating impossible caps\n"); + m1=pvPortMallocCaps(10*1024, MALLOC_CAP_8BIT|MALLOC_CAP_EXEC); + printf("--> %p\n", m1); + TEST_ASSERT(m1==NULL); + printf("Testing changeover iram -> dram"); + for (x=0; x<10; x++) { + m2[x]=pvPortMallocCaps(10*1024, MALLOC_CAP_32BIT); + printf("--> %p\n", m2[x]); + } + TEST_ASSERT((((int)m2[0])&0xFF000000)==0x40000000); + TEST_ASSERT((((int)m2[9])&0xFF000000)==0x3F000000); + printf("Test if allocating executable code still gives IRAM, even with dedicated IRAM region depleted\n"); + m1=pvPortMallocCaps(10*1024, MALLOC_CAP_EXEC); + printf("--> %p\n", m1); + TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000); + free(m1); + for (x=0; x<10; x++) free(m2[x]); + printf("Done.\n"); +} diff --git a/components/freertos/heap_regions.c b/components/freertos/heap_regions.c index 5ff1f2817e..a7c9606036 100644 --- a/components/freertos/heap_regions.c +++ b/components/freertos/heap_regions.c @@ -341,7 +341,7 @@ void *pvReturn = NULL; } /*-----------------------------------------------------------*/ -void vPortFree( void *pv ) +void vPortFreeTagged( void *pv ) { uint8_t *puc = ( uint8_t * ) pv; BlockLink_t *pxLink; diff --git a/components/freertos/include/freertos/heap_regions.h b/components/freertos/include/freertos/heap_regions.h index 30d0dcb39c..090c5b9b36 100644 --- a/components/freertos/include/freertos/heap_regions.h +++ b/components/freertos/include/freertos/heap_regions.h @@ -58,6 +58,15 @@ void vPortDefineHeapRegionsTagged( const HeapRegionTagged_t * const pxHeapRegion */ void *pvPortMallocTagged( size_t xWantedSize, BaseType_t tag ); +/** + * @brief Free memory allocated with pvPortMallocTagged + * + * This is basically an implementation of free(). + * + * @param pv Pointer to region allocated by pvPortMallocTagged + */ +void vPortFreeTagged( void *pv ); + /** * @brief Get the lowest amount of memory free for a certain tag * diff --git a/docs/api/mem_alloc.rst b/docs/api/mem_alloc.rst index d2af7f2875..cebea5b8a5 100644 --- a/docs/api/mem_alloc.rst +++ b/docs/api/mem_alloc.rst @@ -14,6 +14,9 @@ can create an OR-mask of the required capabilities and pass that to pvPortMalloc code internally allocates memory with ```pvPortMallocCaps(size, MALLOC_CAP_8BIT)``` in order to get data memory that is byte-addressable. +Because malloc uses this allocation system as well, memory allocated using pvPortMallocCaps can be freed by calling +the standard ```free()``` function. + Internally, this allocator is split in two pieces. The allocator in the FreeRTOS directory can allocate memory from tagged regions: a tag is an integer value and every region of free memory has one of these tags. The esp32-specific code initializes these regions with specific tags, and contains the logic to select applicable tags from the @@ -59,11 +62,6 @@ Type Definitions .. doxygentypedef:: HeapRegionTagged_t -Enumerations -^^^^^^^^^^^^ - -Structures -^^^^^^^^^^ Functions ^^^^^^^^^ @@ -74,5 +72,6 @@ Functions .. doxygenfunction:: xPortGetMinimumEverFreeHeapSizeCaps .. doxygenfunction:: vPortDefineHeapRegionsTagged .. doxygenfunction:: pvPortMallocTagged +.. doxygenfunction:: vPortFreeTagged .. doxygenfunction:: xPortGetMinimumEverFreeHeapSizeTagged .. doxygenfunction:: xPortGetFreeHeapSizeTagged diff --git a/tools/unit-test-app/sdkconfig b/tools/unit-test-app/sdkconfig index 1b9db99518..14b31e0d18 100644 --- a/tools/unit-test-app/sdkconfig +++ b/tools/unit-test-app/sdkconfig @@ -93,6 +93,7 @@ CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2048 CONFIG_MAIN_TASK_STACK_SIZE=4096 CONFIG_NEWLIB_STDOUT_ADDCR=y +# CONFIG_NEWLIB_NANO_FORMAT is not set CONFIG_CONSOLE_UART_DEFAULT=y # CONFIG_CONSOLE_UART_CUSTOM is not set # CONFIG_CONSOLE_UART_NONE is not set @@ -171,6 +172,8 @@ CONFIG_MBEDTLS_HARDWARE_AES=y CONFIG_MBEDTLS_HARDWARE_MPI=y CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y CONFIG_MBEDTLS_HARDWARE_SHA=y +CONFIG_MBEDTLS_HAVE_TIME=y +# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set # # SPI Flash driver