]> granicus.if.org Git - esp-idf/commitdiff
Add logic to make external RAM usable with malloc()
authorJeroen Domburg <jeroen@espressif.com>
Fri, 22 Sep 2017 08:02:39 +0000 (16:02 +0800)
committerJeroen Domburg <jeroen@espressif.com>
Thu, 28 Sep 2017 09:17:50 +0000 (17:17 +0800)
20 files changed:
components/esp32/Kconfig
components/esp32/cpu_start.c
components/esp32/include/esp_spiram.h
components/esp32/intr_alloc.c
components/esp32/spiram.c
components/esp32/system_api.c
components/freertos/include/freertos/portmacro.h
components/freertos/tasks.c
components/heap/heap_caps.c
components/heap/heap_caps_init.c
components/heap/include/esp_heap_caps.h
components/heap/include/esp_heap_caps_init.h
components/heap/test/test_malloc.c
components/log/log.c
components/soc/esp32/include/soc/soc.h
components/soc/esp32/soc_memory_layout.c
components/soc/include/soc/soc_memory_layout.h
components/spi_flash/flash_mmap.c
docs/api-guides/external-ram.rst [new file with mode: 0644]
docs/api-guides/index.rst

index 65f88dd916e6cb7b2b564144903622314c9e5c3d..8f0c588bcc32acc80fc48ed416f2b9a04bf7022b 100644 (file)
@@ -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
index 25e31de45f6b81e869884c8e3ce21b0890f5e2f1..f9c921b871b5eb7d9acc8e392dac58c2085548bc 100644 (file)
@@ -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.
index 9854035295cd9069c7c971025c73527e00a7addf..2dbff1afc2cca85276d445b7c06047d11d5e3485 100644 (file)
@@ -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
index b38fc994eda37fcc7faf962d171b55b2f63157bf..c01ecf33c811d20e4e1ea6ac35ccd2354a71399c 100644 (file)
@@ -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);
index aff3b9ce86c0a9b0dc52ced3ad46de6dd314f60f..6eae6c98e743dc89d41382e57b199c570ccce916 100644 (file)
@@ -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;
index 82010595e31e67af8da54d97c55ffd200c276630..37958db40bd85f235b5dd234574afca5ca73f1ae 100644 (file)
@@ -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")));
index d398ba5da68ef1cb20befdefa95964d370f4727f..fbfe9828f9ef432889bc041bea35861937315376 100644 (file)
@@ -82,6 +82,9 @@ extern "C" {
 #include "esp_crosscore_int.h"
 
 
+#include <esp_heap_caps.h>
+#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
index 9126f862e0e2ba42091812a829f9029a1586f899..15d2e135d3eed0b4ced0e1f4a7ded3f3e6446fdf 100644 (file)
@@ -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 && xCoreID<portNUM_PROCESSORS) || (xCoreID==tskNO_AFFINITY) );
 
                if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
@@ -724,7 +724,7 @@ void taskYIELD_OTHER_CORE( BaseType_t xCoreID, UBaseType_t uxPriority )
                        /* Allocate space for the TCB.  Where the memory comes from depends
                        on the implementation of the port malloc function and whether or
                        not static allocation is being used. */
-                       pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
+                       pxNewTCB = ( TCB_t * ) pvPortMallocTcbMem( sizeof( TCB_t ) );
 
                        if( pxNewTCB != NULL )
                        {
@@ -777,14 +777,14 @@ void taskYIELD_OTHER_CORE( BaseType_t xCoreID, UBaseType_t uxPriority )
                        /* Allocate space for the TCB.  Where the memory comes from depends on
                        the implementation of the port malloc function and whether or not static
                        allocation is being used. */
-                       pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
+                       pxNewTCB = ( TCB_t * ) pvPortMallocTcbMem( sizeof( TCB_t ) );
 
                        if( pxNewTCB != NULL )
                        {
                                /* Allocate space for the stack used by the task being created.
                                The base of the stack memory stored in the TCB so the task can
                                be deleted later if required. */
-                               pxNewTCB->pxStack = ( 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 )
                                {
index 2edf1dbc4ad06d5b00e8760d5755b8a5af05f89c..f8b9ca029e795e60a7b758d5d8f7ab3fec47b064 100644 (file)
@@ -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;
+    }
 }
 
 
index 0c8405ec1bbc60a59e772a9097c79d9f10122152..c964c655e8086b5f696d580be63aab3e2069ccc9 100644 (file)
@@ -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, &registered_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;
index 022ae669a49c5431b04b3708703a1f7721040ac1..6dd1b9b5a6b9a67734ab589ecbdc6eb71809c82f 100644 (file)
@@ -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);
index b29e785262002e1e06589e5d69356090cd447162..5cfb8d82b51a65b2a1ec9c0f6470efca307577f9 100644 (file)
@@ -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);
 
index 2ef74965a18fece8db6a9b712409a83416e2413c..23eef9d8b7403b14e4184644203a4e205a38e0e9 100644 (file)
 #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<allocateMaxK; i++) {
+        allocatedMem[i]=malloc(1024);
+        if (allocatedMem[i]==NULL) break;
+        for (j=0; j<1024/4; j++) allocatedMem[i][j]=(0xdeadbeef);
     }
-
     noAllocated=i;
+    return i;
+}
+
 
+static void tryAllocMemFree() {
+    int i, j;
     for (i=0; i<noAllocated; i++) {
         for (j=0; j<1024/4; j++) {
-            TEST_ASSERT(mem[i][j]==(0xdeadbeef));
+            TEST_ASSERT(allocatedMem[i][j]==(0xdeadbeef));
         }
-        free(mem[i]);
+        free(allocatedMem[i]);
     }
-    free(mem);
-    return noAllocated;
+    free(allocatedMem);
 }
 
 
@@ -48,8 +58,33 @@ TEST_CASE("Malloc/overwrite, then free all available DRAM", "[heap]")
 {
     int m1=0, m2=0;
     m1=tryAllocMem();
+    tryAllocMemFree();
     m2=tryAllocMem();
+    tryAllocMemFree();
     printf("Could allocate %dK on first try, %dK on 2nd try.\n", m1, m2);
     TEST_ASSERT(m1==m2);
 }
 
+
+#if CONFIG_SPIRAM_USE_MALLOC
+
+#if (CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL > 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<i; j++) free(dmaMem[j]);
+    free(dmaMem);
+    tryAllocMemFree();
+    printf("Could allocate %dK of DMA memory after allocating all of %dK of normal memory.\n", i, m);
+    TEST_ASSERT(i);
+}
+#endif
+
+#endif
\ No newline at end of file
index ccd61eac2a7eb15f5d2cc69722274b92dfcbaf5e..038e8e16357b2d89c0e1eb0ea0c30e474de8d2af 100644 (file)
@@ -354,7 +354,7 @@ void esp_log_buffer_hex_internal(const char *tag, const void *buffer, uint16_t b
         } else {
             bytes_cur_line = buff_len;
         }
-        if ( !esp_ptr_byte_accesible(buffer) ) {
+        if ( !esp_ptr_byte_accessible(buffer) ) {
             //use memcpy to get around alignment issue
             memcpy( temp_buffer, buffer, (bytes_cur_line+3)/4*4 );
             ptr_line = temp_buffer;
@@ -386,7 +386,7 @@ void esp_log_buffer_char_internal(const char *tag, const void *buffer, uint16_t
         } else {
             bytes_cur_line = buff_len;
         }
-        if ( !esp_ptr_byte_accesible(buffer) ) {
+        if ( !esp_ptr_byte_accessible(buffer) ) {
             //use memcpy to get around alignment issue
             memcpy( temp_buffer, buffer, (bytes_cur_line+3)/4*4 );
             ptr_line = temp_buffer;
@@ -421,7 +421,7 @@ void esp_log_buffer_hexdump_internal( const char *tag, const void *buffer, uint1
         } else {
             bytes_cur_line = buff_len;
         }
-        if ( !esp_ptr_byte_accesible(buffer) ) {
+        if ( !esp_ptr_byte_accessible(buffer) ) {
             //use memcpy to get around alignment issue
             memcpy( temp_buffer, buffer, (bytes_cur_line+3)/4*4 );
             ptr_line = temp_buffer;
index b889f5fa5607ec46d750cc61e0392dad06c826e2..0e633821fdb6aa36cca330344d0bed38eb9d6208 100644 (file)
 #define SOC_DMA_LOW  0x3FFAE000
 #define SOC_DMA_HIGH 0x40000000
 
-// Region of memory that is byte-accessible. See esp_ptr_byte_accesible().
+// Region of memory that is byte-accessible. See esp_ptr_byte_accessible().
 #define SOC_BYTE_ACCESSIBLE_LOW     0x3FFAE000
 #define SOC_BYTE_ACCESSIBLE_HIGH    0x40000000
 
+//Region of memory that is internal, as in on the same silicon die as the ESP32 CPUs (excluding RTC data region, that's checked separately.) See esp_ptr_internal().
+#define SOC_MEM_INTERNAL_LOW        0x3F400000
+#define SOC_MEM_INTERNAL_HIGH       0x400C2000
+
+
 //Interrupt hardware source table
 //This table is decided by hardware, don't touch this.
 #define ETS_WIFI_MAC_INTR_SOURCE                0/**< interrupt of WiFi MAC, level*/
index 02bced2630ed904f07af67dcfbea96a26ae7d853..737216c84f06f185d7bfb115724b2c952f4043e0 100644 (file)
@@ -31,7 +31,7 @@ Each type contains an array of prioritised capabilities; types with later entrie
 ones can't fulfill the memory request.
 
 The prioritised capabilities work roughly like this:
-- For a normal malloc (MALLOC_CAP_8BIT), give away the DRAM-only memory first, then pass off any dual-use IRAM regions,
+- For a normal malloc (MALLOC_CAP_DEFAULT), give away the DRAM-only memory first, then pass off any dual-use IRAM regions,
   finally eat into the application memory.
 - For a malloc where 32-bit-aligned-only access is okay, first allocate IRAM, then DRAM, finally application IRAM.
 - Application mallocs (PIDx) will allocate IRAM first, if possible, then DRAM.
@@ -40,10 +40,10 @@ The prioritised capabilities work roughly like this:
 */
 const soc_memory_type_desc_t soc_memory_types[] = {
     //Type 0: Plain ole D-port RAM
-    { "DRAM", { MALLOC_CAP_DMA|MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL, MALLOC_CAP_32BIT, 0 }, false, false},
+    { "DRAM", { MALLOC_CAP_8BIT|MALLOC_CAP_DEFAULT, MALLOC_CAP_INTERNAL|MALLOC_CAP_DMA|MALLOC_CAP_32BIT, 0 }, false, false},
     //Type 1: Plain ole D-port RAM which has an alias on the I-port
     //(This DRAM is also the region used by ROM during startup)
-    { "D/IRAM", { 0, MALLOC_CAP_DMA|MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL, MALLOC_CAP_32BIT|MALLOC_CAP_EXEC }, true, true},
+    { "D/IRAM", { 0, MALLOC_CAP_DMA|MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL|MALLOC_CAP_DEFAULT, MALLOC_CAP_32BIT|MALLOC_CAP_EXEC }, true, true},
     //Type 2: IRAM
     { "IRAM", { MALLOC_CAP_EXEC|MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL, 0, 0 }, false, false},
     //Type 3-8: PID 2-7 IRAM
@@ -54,14 +54,14 @@ const soc_memory_type_desc_t soc_memory_types[] = {
     { "PID6IRAM", { MALLOC_CAP_PID6|MALLOC_CAP_INTERNAL, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }, false, false},
     { "PID7IRAM", { MALLOC_CAP_PID7|MALLOC_CAP_INTERNAL, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT }, false, false},
     //Type 9-14: PID 2-7 DRAM
-    { "PID2DRAM", { MALLOC_CAP_PID2|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false, false},
-    { "PID3DRAM", { MALLOC_CAP_PID3|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false, false},
-    { "PID4DRAM", { MALLOC_CAP_PID4|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false, false},
-    { "PID5DRAM", { MALLOC_CAP_PID5|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false, false},
-    { "PID6DRAM", { MALLOC_CAP_PID6|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false, false},
-    { "PID7DRAM", { MALLOC_CAP_PID7|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT }, false, false},
+    { "PID2DRAM", { MALLOC_CAP_PID2|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_DEFAULT }, false, false},
+    { "PID3DRAM", { MALLOC_CAP_PID3|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_DEFAULT }, false, false},
+    { "PID4DRAM", { MALLOC_CAP_PID4|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_DEFAULT }, false, false},
+    { "PID5DRAM", { MALLOC_CAP_PID5|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_DEFAULT }, false, false},
+    { "PID6DRAM", { MALLOC_CAP_PID6|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_DEFAULT }, false, false},
+    { "PID7DRAM", { MALLOC_CAP_PID7|MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_DEFAULT }, false, false},
     //Type 15: SPI SRAM data
-    { "SPIRAM", { MALLOC_CAP_SPIRAM, 0, MALLOC_CAP_DMA|MALLOC_CAP_8BIT|MALLOC_CAP_32BIT}, false, false},
+    { "SPIRAM", { MALLOC_CAP_SPIRAM|MALLOC_CAP_DEFAULT, 0, MALLOC_CAP_8BIT|MALLOC_CAP_32BIT}, false, false},
 };
 
 const size_t soc_memory_type_count = sizeof(soc_memory_types)/sizeof(soc_memory_type_desc_t);
index 0a282062e5fca19cc15f0b854a8d234bfc48d666..1c1415e3dc31b5253ee62b4667deb1e7d19b9954 100644 (file)
@@ -17,6 +17,8 @@
 #include <stdbool.h>
 
 #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;
 }
index 8b98e2cb108f6463f18461167b0d433466eabf80..52e64c11a5c61fc1087e9f843a036fc187813545 100644 (file)
@@ -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 (file)
index 0000000..5270436
--- /dev/null
@@ -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
+
+
+
+
+
+
index 72bd9f04b1b26d576ea35c18d6f9b56c7efd5847..662ab0e79156c8e7fdbd33de30fa6653a0704fc1 100644 (file)
@@ -20,3 +20,4 @@ API Guides
    Console Component <console>
    ROM debug console <romconsole>
    WiFi Driver <wifi>
+   External SPI-connected RAM <external-ram>