From 740f8a79f0f9e7e5e37427fb39b7f394964fc5c2 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Fri, 22 Sep 2017 16:02:39 +0800 Subject: [PATCH] Add logic to make external RAM usable with malloc() --- components/esp32/Kconfig | 46 +++++++- components/esp32/cpu_start.c | 10 ++ components/esp32/include/esp_spiram.h | 12 ++ components/esp32/intr_alloc.c | 4 +- components/esp32/spiram.c | 13 +++ components/esp32/system_api.c | 4 +- .../freertos/include/freertos/portmacro.h | 15 +++ components/freertos/tasks.c | 14 +-- components/heap/heap_caps.c | 42 ++++++- components/heap/heap_caps_init.c | 8 ++ components/heap/include/esp_heap_caps.h | 16 +++ components/heap/include/esp_heap_caps_init.h | 7 +- components/heap/test/test_malloc.c | 61 +++++++--- components/log/log.c | 6 +- components/soc/esp32/include/soc/soc.h | 7 +- components/soc/esp32/soc_memory_layout.c | 20 ++-- .../soc/include/soc/soc_memory_layout.h | 23 +++- components/spi_flash/flash_mmap.c | 2 +- docs/api-guides/external-ram.rst | 104 ++++++++++++++++++ docs/api-guides/index.rst | 1 + 20 files changed, 364 insertions(+), 51 deletions(-) create mode 100644 docs/api-guides/external-ram.rst diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 65f88dd916..8f0c588bcc 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -47,7 +47,7 @@ config SPIRAM_BOOT_INIT choice SPIRAM_USE prompt "SPI RAM access method" - default SPIRAM_USE_MEMMAP + default SPIRAM_USE_MALLOC help The SPI RAM can be accessed in multiple methods: by just having it available as an unmanaged memory region in the ESP32 memory map, by integrating it in the ESP32s heap as 'special' memory @@ -59,8 +59,7 @@ config SPIRAM_USE_MEMMAP config SPIRAM_USE_CAPS_ALLOC bool "Make RAM allocatable using heap_caps_malloc(..., MALLOC_CAP_SPIRAM)" config SPIRAM_USE_MALLOC - bool "Make RAM allocatable using malloc as well" - depends on TO_BE_DONE + bool "Make RAM allocatable using malloc() as well" endchoice choice SPIRAM_TYPE @@ -118,6 +117,46 @@ config SPIRAM_CACHE_WORKAROUND with the workaround and located in flash instead. +config SPIRAM_MALLOC_ALWAYSINTERNAL + int "Maximum malloc() size, in bytes, to always put in internal memory" + depends on SPIRAM_USE_MALLOC + default 16384 + range 0 131072 + help + If malloc() is capable of also allocating SPI-connected ram, its allocation strategy will prefer to allocate chunks less + than this size in internal memory, while allocations larger than this will be done from external RAM. + If allocation from the preferred region fails, an attempt is made to allocate from the non-preferred + region instead, so malloc() will not suddenly fail when either internal or external memory is full. + +config SPIRAM_MALLOC_RESERVE_INTERNAL + int "Reserve this amount of bytes for data that specifically needs to be in DMA or internal memory" + depends on SPIRAM_USE_MALLOC + default 32768 + range 0 131072 + help + Because the external/internal RAM allocation strategy is not always perfect, it sometimes may happen + that the internal memory is entirely filled up. This causes allocations that are specifically done in + internal memory, for example the stack for new tasks or memory to service DMA or have memory that's + also available when SPI cache is down, to fail. This option reserves a pool specifically for requests + like that; the memory in this pool is not given out when a normal malloc() is called. + + Set this to 0 to disable this feature. + + Note that because FreeRTOS stacks are forced to internal memory, they will also use this memory pool; + be sure to keep this in mind when adjusting this value. + +config SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY + bool "Allow external memory as an argument to xTaskCreateStatic" + default n + depends on SPIRAM_USE_MALLOC + help + Because some bits of the ESP32 code environment cannot be recompiled with the cache workaround, normally + tasks cannot be safely run with their stack residing in external memory; for this reason xTaskCreate and + friends always allocate stack in internal memory and xTaskCreateStatic will check if the memory passed + to it is in internal memory. If you have a task that needs a large amount of stack and does not call on + ROM code in any way (no direct calls, but also no Bluetooth/WiFi), you can try to disable this and use + xTaskCreateStatic to create the tasks stack in external memory. + endmenu config MEMMAP_TRACEMEM @@ -776,6 +815,7 @@ config ESP32_WIFI_STATIC_TX_BUFFER bool "STATIC" config ESP32_WIFI_DYNAMIC_TX_BUFFER bool "DYNAMIC" + depends on !SPIRAM_USE_MALLOC endchoice config ESP32_WIFI_TX_BUFFER_TYPE diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 25e31de45f..f9c921b871 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -262,6 +262,16 @@ void start_cpu0_default(void) ESP_EARLY_LOGE(TAG, "External RAM could not be added to heap!"); abort(); } +#if CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL + r=esp_spiram_reserve_dma_pool(CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL); + if (r != ESP_OK) { + ESP_EARLY_LOGE(TAG, "Could not reserve internal/DMA pool!"); + abort(); + } +#endif +#if CONFIG_SPIRAM_USE_MALLOC + heap_caps_malloc_extmem_enable(CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL); +#endif #endif //Enable trace memory and immediately start trace. diff --git a/components/esp32/include/esp_spiram.h b/components/esp32/include/esp_spiram.h index 9854035295..2dbff1afc2 100644 --- a/components/esp32/include/esp_spiram.h +++ b/components/esp32/include/esp_spiram.h @@ -64,4 +64,16 @@ void esp_spiram_writeback_cache(); +/** + * @brief Reserve a pool of internal memory for specific DMA/internal allocations + * + * @param size Size of reserved pool in bytes + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM when no memory available for pool + */ +esp_err_t esp_spiram_reserve_dma_pool(size_t size); + + #endif \ No newline at end of file diff --git a/components/esp32/intr_alloc.c b/components/esp32/intr_alloc.c index b38fc994ed..c01ecf33c8 100644 --- a/components/esp32/intr_alloc.c +++ b/components/esp32/intr_alloc.c @@ -222,7 +222,7 @@ static vector_desc_t *get_desc_for_int(int intno, int cpu) { vector_desc_t *vd=find_desc_for_int(intno, cpu); if (vd==NULL) { - vector_desc_t *newvd=malloc(sizeof(vector_desc_t)); + vector_desc_t *newvd=heap_caps_malloc(sizeof(vector_desc_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT); if (newvd==NULL) return NULL; memset(newvd, 0, sizeof(vector_desc_t)); newvd->intno=intno; @@ -574,7 +574,7 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre if (source==ETS_INTERNAL_PROFILING_INTR_SOURCE) force=ETS_INTERNAL_PROFILING_INTR_NO; //Allocate a return handle. If we end up not needing it, we'll free it later on. - ret=malloc(sizeof(intr_handle_data_t)); + ret=heap_caps_malloc(sizeof(intr_handle_data_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT); if (ret==NULL) return ESP_ERR_NO_MEM; portENTER_CRITICAL(&spinlock); diff --git a/components/esp32/spiram.c b/components/esp32/spiram.c index aff3b9ce86..6eae6c98e7 100644 --- a/components/esp32/spiram.c +++ b/components/esp32/spiram.c @@ -124,11 +124,24 @@ esp_err_t esp_spiram_init() esp_err_t esp_spiram_add_to_heapalloc() { + ESP_EARLY_LOGI(TAG, "Adding pool of %dK of external SPI memory to heap allocator", CONFIG_SPIRAM_SIZE/1024); //Add entire external RAM region to heap allocator. Heap allocator knows the capabilities of this type of memory, so there's //no need to explicitly specify them. return heap_caps_add_region((intptr_t)SOC_EXTRAM_DATA_LOW, (intptr_t)SOC_EXTRAM_DATA_LOW + CONFIG_SPIRAM_SIZE-1); } + +static uint8_t *dma_heap; + +esp_err_t esp_spiram_reserve_dma_pool(size_t size) { + if (size==0) return ESP_OK; //no-op + ESP_EARLY_LOGI(TAG, "Reserving pool of %dK of internal memory for DMA/internal allocations", size/1024); + dma_heap=heap_caps_malloc(size, MALLOC_CAP_DMA|MALLOC_CAP_INTERNAL); + if (!dma_heap) return ESP_ERR_NO_MEM; + uint32_t caps[]={MALLOC_CAP_DMA|MALLOC_CAP_INTERNAL, 0, MALLOC_CAP_8BIT|MALLOC_CAP_32BIT}; + return heap_caps_add_region_with_caps(caps, dma_heap, dma_heap+size-1); +} + size_t esp_spiram_get_size() { return CONFIG_SPIRAM_SIZE; diff --git a/components/esp32/system_api.c b/components/esp32/system_api.c index 82010595e3..37958db40b 100644 --- a/components/esp32/system_api.c +++ b/components/esp32/system_api.c @@ -360,12 +360,12 @@ void system_restore(void) uint32_t esp_get_free_heap_size( void ) { - return heap_caps_get_free_size( MALLOC_CAP_8BIT ); + return heap_caps_get_free_size( MALLOC_CAP_DEFAULT ); } uint32_t esp_get_minimum_free_heap_size( void ) { - return heap_caps_get_minimum_free_size( MALLOC_CAP_8BIT ); + return heap_caps_get_minimum_free_size( MALLOC_CAP_DEFAULT ); } uint32_t system_get_free_heap_size(void) __attribute__((alias("esp_get_free_heap_size"))); diff --git a/components/freertos/include/freertos/portmacro.h b/components/freertos/include/freertos/portmacro.h index d398ba5da6..fbfe9828f9 100644 --- a/components/freertos/include/freertos/portmacro.h +++ b/components/freertos/include/freertos/portmacro.h @@ -82,6 +82,9 @@ extern "C" { #include "esp_crosscore_int.h" +#include +#include "soc/soc_memory_layout.h" + //#include "xtensa_context.h" /*----------------------------------------------------------- @@ -245,6 +248,18 @@ static inline unsigned portENTER_CRITICAL_NESTED() { unsigned state = XTOS_SET_I #define portSET_INTERRUPT_MASK_FROM_ISR() portENTER_CRITICAL_NESTED() #define portCLEAR_INTERRUPT_MASK_FROM_ISR(state) portEXIT_CRITICAL_NESTED(state) +//Because the ROM routines don't necessarily handle a stack in external RAM correctly, we force +//the stack memory to always be internal. +#define pvPortMallocTcbMem(size) heap_caps_malloc(size, MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT) +#define pvPortMallocStackMem(size) heap_caps_malloc(size, MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT) + +//xTaskCreateStatic uses these functions to check incoming memory. +#define portVALID_TCB_MEM(ptr) (esp_ptr_internal(ptr) && esp_ptr_byte_accessible(ptr)) +#ifndef CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY +#define portVALID_STACK_MEM(ptr) esp_ptr_byte_accessible(ptr) +#else +#define portVALID_STACK_MEM(ptr) (esp_ptr_internal(ptr) && esp_ptr_byte_accessible(ptr)) +#endif /* * Wrapper for the Xtensa compare-and-set instruction. This subroutine will atomically compare diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 9126f862e0..15d2e135d3 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -677,8 +677,8 @@ void taskYIELD_OTHER_CORE( BaseType_t xCoreID, UBaseType_t uxPriority ) TCB_t *pxNewTCB; TaskHandle_t xReturn; - configASSERT( puxStackBuffer != NULL ); - configASSERT( pxTaskBuffer != NULL ); + configASSERT( portVALID_TCB_MEM(pxTaskBuffer) ); + configASSERT( portVALID_STACK_MEM(puxStackBuffer) ); configASSERT( (xCoreID>=0 && xCoreIDpxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ + pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStackMem( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ if( pxNewTCB->pxStack == NULL ) { @@ -799,12 +799,12 @@ void taskYIELD_OTHER_CORE( BaseType_t xCoreID, UBaseType_t uxPriority ) StackType_t *pxStack; /* Allocate space for the stack used by the task being created. */ - pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ + pxStack = ( StackType_t * ) pvPortMallocStackMem( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ if( pxStack != NULL ) { /* Allocate space for the TCB. */ - pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */ + pxNewTCB = ( TCB_t * ) pvPortMallocTcbMem( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */ if( pxNewTCB != NULL ) { diff --git a/components/heap/heap_caps.c b/components/heap/heap_caps.c index 2edf1dbc4a..f8b9ca029e 100644 --- a/components/heap/heap_caps.c +++ b/components/heap/heap_caps.c @@ -133,12 +133,36 @@ IRAM_ATTR void *heap_caps_malloc( size_t size, uint32_t caps ) return NULL; } + +#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. */ IRAM_ATTR void *heap_caps_malloc_default( size_t size ) { - return heap_caps_malloc( size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL ); + if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) { + return heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL); + } else { + void *r; + if (size <= malloc_alwaysinternal_limit) { + r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL ); + } else { + r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM ); + } + if (r==NULL) { + //try again while being less picky + r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT ); + } + return r; + } } /* @@ -147,7 +171,21 @@ IRAM_ATTR void *heap_caps_malloc_default( size_t size ) */ IRAM_ATTR void *heap_caps_realloc_default( void *ptr, size_t size ) { - return heap_caps_realloc( ptr, size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL ); + if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) { + return heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL ); + } else { + void *r; + if (size <= malloc_alwaysinternal_limit) { + r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL ); + } else { + r=heap_caps_realloc( 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( ptr, size, MALLOC_CAP_DEFAULT ); + } + return r; + } } diff --git a/components/heap/heap_caps_init.c b/components/heap/heap_caps_init.c index 0c8405ec1b..c964c655e8 100644 --- a/components/heap/heap_caps_init.c +++ b/components/heap/heap_caps_init.c @@ -230,6 +230,14 @@ esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t 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, ®istered_heaps, next) { + if ( start <= heap->start && heap->start <=end ) return ESP_FAIL; + if ( start <= heap->end && heap->end <=end ) return ESP_FAIL; + } + heap_t *p_new = malloc(sizeof(heap_t)); if (p_new == NULL) { err = ESP_ERR_NO_MEM; diff --git a/components/heap/include/esp_heap_caps.h b/components/heap/include/esp_heap_caps.h index 022ae669a4..6dd1b9b5a6 100644 --- a/components/heap/include/esp_heap_caps.h +++ b/components/heap/include/esp_heap_caps.h @@ -32,6 +32,7 @@ #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_INVALID (1<<31) ///< Memory can't be used / list end marker /** @@ -172,3 +173,18 @@ void heap_caps_print_heap_info( uint32_t caps ); * @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 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); diff --git a/components/heap/include/esp_heap_caps_init.h b/components/heap/include/esp_heap_caps_init.h index b29e785262..5cfb8d82b5 100644 --- a/components/heap/include/esp_heap_caps_init.h +++ b/components/heap/include/esp_heap_caps_init.h @@ -73,8 +73,11 @@ esp_err_t heap_caps_add_region(intptr_t start, intptr_t end); * @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. + * @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_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); diff --git a/components/heap/test/test_malloc.c b/components/heap/test/test_malloc.c index 2ef74965a1..23eef9d8b7 100644 --- a/components/heap/test/test_malloc.c +++ b/components/heap/test/test_malloc.c @@ -15,32 +15,42 @@ #include "soc/uart_reg.h" #include "soc/dport_reg.h" #include "soc/io_mux_reg.h" +#include "esp_heap_caps.h" #include "esp_panic.h" +#include "sdkconfig.h" + + +static int **allocatedMem; +static int noAllocated; + static int tryAllocMem() { - int **mem; - int i, noAllocated, j; + int i, j; + const int allocateMaxK=1024*5; //try to allocate a max of 5MiB - mem=malloc(sizeof(int *)*1024); - if (!mem) return 0; + allocatedMem=malloc(sizeof(int *)*allocateMaxK); + if (!allocatedMem) return 0; - for (i=0; i<1024; i++) { - mem[i]=malloc(1024); - if (mem[i]==NULL) break; - for (j=0; j<1024/4; j++) mem[i][j]=(0xdeadbeef); + for (i=0; i 1024) +TEST_CASE("Check if reserved DMA pool still can allocate even when malloc()'ed memory is exhausted", "[heap]") +{ + char** dmaMem=malloc(sizeof(char*)*512); + assert(dmaMem); + int m=tryAllocMem(); + int i=0; + for (i=0; i<512; i++) { + dmaMem[i]=heap_caps_malloc(1024, MALLOC_CAP_DMA); + if (dmaMem[i]==NULL) break; + } + for (int j=0; j #include "soc/soc.h" +#include "sdkconfig.h" +#include "esp_attr.h" #define SOC_MEMORY_TYPE_NO_PRIOS 3 @@ -58,12 +60,12 @@ typedef struct extern const soc_reserved_region_t soc_reserved_regions[]; extern const size_t soc_reserved_region_count; -inline static bool esp_ptr_dma_capable(const void *p) +inline static bool IRAM_ATTR esp_ptr_dma_capable(const void *p) { return (intptr_t)p >= SOC_DMA_LOW && (intptr_t)p < SOC_DMA_HIGH; } -inline static bool esp_ptr_executable(const void *p) +inline static bool IRAM_ATTR esp_ptr_executable(const void *p) { intptr_t ip = (intptr_t) p; return (ip >= SOC_IROM_LOW && ip < SOC_IROM_HIGH) @@ -71,8 +73,19 @@ inline static bool esp_ptr_executable(const void *p) || (ip >= SOC_RTC_IRAM_LOW && ip < SOC_RTC_IRAM_HIGH); } -inline bool esp_ptr_byte_accesible(const void *p) +inline static bool IRAM_ATTR esp_ptr_byte_accessible(const void *p) { - //currently only support DRAM, add PSRAM region in the future - return (intptr_t)p >= SOC_BYTE_ACCESSIBLE_LOW && (intptr_t)p < SOC_BYTE_ACCESSIBLE_HIGH; + bool r; + r = ((intptr_t)p >= SOC_BYTE_ACCESSIBLE_LOW && (intptr_t)p < SOC_BYTE_ACCESSIBLE_HIGH); +#if CONFIG_SPIRAM_SUPPORT + r |= ((intptr_t)p >= SOC_EXTRAM_DATA_LOW && (intptr_t)p < SOC_EXTRAM_DATA_HIGH); +#endif + return r; +} + +inline static bool IRAM_ATTR esp_ptr_internal(const void *p) { + bool r; + r = ((intptr_t)p >= SOC_MEM_INTERNAL_LOW && (intptr_t)p < SOC_MEM_INTERNAL_HIGH); + r |= ((intptr_t)p >= SOC_RTC_DATA_LOW && (intptr_t)p < SOC_RTC_DATA_HIGH); + return r; } diff --git a/components/spi_flash/flash_mmap.c b/components/spi_flash/flash_mmap.c index 8b98e2cb10..52e64c11a5 100644 --- a/components/spi_flash/flash_mmap.c +++ b/components/spi_flash/flash_mmap.c @@ -135,7 +135,7 @@ esp_err_t IRAM_ATTR spi_flash_mmap_pages(int *pages, size_t page_count, spi_flas return ESP_ERR_INVALID_ARG; } } - mmap_entry_t* new_entry = (mmap_entry_t*) malloc(sizeof(mmap_entry_t)); + mmap_entry_t* new_entry = (mmap_entry_t*) heap_caps_malloc(sizeof(mmap_entry_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT); if (new_entry == 0) { return ESP_ERR_NO_MEM; } diff --git a/docs/api-guides/external-ram.rst b/docs/api-guides/external-ram.rst new file mode 100644 index 0000000000..52704365db --- /dev/null +++ b/docs/api-guides/external-ram.rst @@ -0,0 +1,104 @@ +Support for external RAM +************************ + +.. toctree:: + :maxdepth: 1 + +Introduction +============ + +The ESP32 has a few hundred KiB of internal RAM, residing on the same die as the rest of the ESP32. For some purposes, this is insufficient, +and therefore the ESP32 incorporates the ability to also use up to 4MiB of external SPI RAM memory as memory. The external memory is incorporated +in the memory map and is, within certain restrictions, usable in the same way internal data RAM is. + +Hardware +======== + +The ESP32 supports SPI (P)SRAM connected in parallel with the SPI flash chip. While the ESP32 is capable of supporting several types +of RAM chips, the ESP32 SDK at the moment only supports the ESP-PSRAM32 chip. + +The ESP-PSRAM32 chip is an 1.8V device, and can only be used in parallel with an 1.8V flash part. Make sure to either set the MTDI +pin to a high signal level on bootup, or program the fuses in the ESP32 to always use a VDD_SIO level of 1.8V. Not doing this risks +damaging the PSRAM and/or flash chip. + +To connect the ESP-PSRAM chip to the ESP32D0W*, connect the following signals: + * PSRAM /CE (pin 1) - ESP32 GPIO 16 + * PSRAM SO (pin 2) - flash DO + * PSRAM SIO[2] (pin 3) - flash WP + * PSRAM SI (pin 5) - flash DI + * PSRAM SCLK (pin 6) - ESP32 GPIO 17 + * PSRAM SIO[3] (pin 7) - flash HOLD + * PSRAM Vcc (pin 8) - ESP32 VCC_SDIO + +Connections for the ESP32D2W* chips are TBD. + +.. NOTE:: + Espressif sells an ESP-WROVER module which contains an ESP32, 1.8V flash and the ESP-PSRAM32 integrated in a module, ready for inclusion + on an end product PCB. + +Software +======== + +ESP-IDF fully supports integrating external memory use into your applications. ESP-IDF can be configured to handle external RAM in several ways: + * Only initialize RAM. This allows the application to manually place data here by dereferencing pointers pointed at the external RAM memory + region (0x3F800000 and up). + * Initialize RAM and add it to the capability allocator. This allows a program to specifically allocate a chunk of external RAM using + ``heap_caps_malloc(size, MALLOC_CAP_SPIRAM)``. This memory can be used and subsequently freed using a normal ``free()`` call. + * Initialize RAM, add it to the capability allocator and add memory to the pool of RAM that can be returned by ``malloc()``. This allows + any application to use the external RAM without having to rewrite the code to use ``heap_caps_malloc``. + +All these options can be selected from the menuconfig menu. + +Restrictions +------------ + +The use of external RAM has a few restrictions: + * When disabling flash cache (for example, because the flash is being written to), the external RAM also becomes inaccessible; any reads from or + writes to it will lead to an illegal cache access exception. This is also the reason that ESP-IDF will never allocate a tasks stack in external + RAM. + * External RAM cannot be used as a place to store DMA transaction descriptors or as a buffer for a DMA transfer to read from or write into. Any + buffers that will be used in combination with DMA must be allocated using ``heap_caps_malloc(size, MALLOC_CAP_DMA)`` (and can be freed using a + standard ``free()`` call.) + * External RAM uses the same cache region as the external flash. This means that often accessed variables in external RAM can be read and + modified almost as quickly as in internal ram. However, when accessing large chunks of data (>32K), the cache can be insufficient and speeds + will fall back to the access speed of the external RAM. Moreover, accessing large chunks of data can 'push out' cached flash, possibly making + execution of code afterwards slower. + * External RAM cannot be used as task stack memory; because of this, xTaskCreate and similar functions will always allocate internal memory + for stack and task TCBs and xTaskCreateStatic-type functions will check if the buffers passed are internal. However, for tasks not calling + on code in ROM in any way, directly or indirectly, the menuconfig option :ref:`CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY` will eliminate + the check in xTaskCreateStatic, allowing task stack in external RAM. Using this is not advised, however. + + +Because there are a fair few situations that have a specific need for internal memory, but it is also possible to use malloc() to exhaust +internal memory, there is a pool reserved specifically for requests that cannot be resolved from external memory; allocating task +stack, DMA buffers and memory that stays accessible when cache is disabled is drawn from this pool. The size of this pool is configurable +in menuconfig. + + +Chip revisions +============== + +There are some issues with certain revisions of the ESP32 that have repercussions for use with external RAM. These are documented in the ESP32 +ECO_ document. In particular, ESP-IDF handles the bugs mentioned in the following ways: + +ESP32 rev v0 +------------ +ESP-IDF has no workaround for the bugs in this revision of silicon, and it cannot be used to map external PSRAM into the ESP32s main memory map. + +ESP32 rev v1 +------------ +The bugs in this silicon revision introduce a hazard when certain sequences of machine instructions operate on external memory locations (ESP32 ECO 3.2). +To work around this, the gcc compiler to compile ESP-IDF has been expanded with a flag: ``-mfix-esp32-psram-cache-issue``. With this flag passed to gcc +on the command line, the compiler works around these sequences and only outputs code that can safely be executed. + +In ESP-IDF, this flag is enabled when you select :ref:`CONFIG_SPIRAM_CACHE_WORKAROUND`. ESP-IDF also takes other measures to make +sure no combination of PSRAM access plus the offending instruction sets are used: it links to a version of Newlib recompiled with the gcc flag, doesn't use +some ROM functions and allocates static memory for the WiFi stack. + +.. _ECO: https://www.espressif.com/sites/default/files/documentation/eco_and_workarounds_for_bugs_in_esp32_en.pdf + + + + + + diff --git a/docs/api-guides/index.rst b/docs/api-guides/index.rst index 72bd9f04b1..662ab0e791 100644 --- a/docs/api-guides/index.rst +++ b/docs/api-guides/index.rst @@ -20,3 +20,4 @@ API Guides Console Component ROM debug console WiFi Driver + External SPI-connected RAM -- 2.40.0