// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "esp_heap_alloc_caps.h" #include "esp_psram.h" #include "esp_log.h" #include static const char* TAG = "heap_alloc_caps"; /* This file, combined with a region allocator that supports tags, solves the problem that the ESP32 has RAM that's slightly heterogeneous. Some RAM can be byte-accessed, some allows only 32-bit accesses, some can execute memory, some can be remapped by the MMU to only be accessed by a certain PID etc. In order to allow the most flexible memory allocation possible, this code makes it possible to request memory that has certain capabilities. The code will then use its knowledge of how the memory is configured along with a priority scheme to allocate that memory in the most sane way possible. This should optimize the amount of RAM accessible to the code without hardwiring addresses. */ //Amount of priority slots for the tag descriptors. #define NO_PRIOS 3 typedef struct { const char *name; uint32_t prio[NO_PRIOS]; bool aliasedIram; } tag_desc_t; /* Tag descriptors. These describe the capabilities of a bit of memory that's tagged with the index into this table. Each tag contains NO_PRIOS entries; later entries are only taken if earlier ones can't fulfill the memory request. Make sure there are never more than HEAPREGIONS_MAX_TAGCOUNT (in heap_regions.h) tags (ex the last empty marker) WARNING: The current code assumes the ROM stacks are located in tag 1; no allocation from this tag can be done until the FreeRTOS scheduler has started. */ static const tag_desc_t tag_desc[]={ { "DRAM", { MALLOC_CAP_DMA|MALLOC_CAP_8BIT, MALLOC_CAP_32BIT, MALLOC_CAP_INTERNAL }, false}, //Tag 0: Plain ole D-port RAM { "D/IRAM", { 0, MALLOC_CAP_DMA|MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_EXEC|MALLOC_CAP_INTERNAL }, true}, //Tag 1: Plain ole D-port RAM which has an alias on the I-port { "IRAM", { MALLOC_CAP_EXEC|MALLOC_CAP_32BIT, 0, MALLOC_CAP_INTERNAL }, false}, //Tag 2: IRAM { "PID2IRAM", { MALLOC_CAP_PID2, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, //Tag 3-8: PID 2-7 IRAM { "PID3IRAM", { MALLOC_CAP_PID3, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "PID4IRAM", { MALLOC_CAP_PID4, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "PID5IRAM", { MALLOC_CAP_PID5, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "PID6IRAM", { MALLOC_CAP_PID6, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "PID7IRAM", { MALLOC_CAP_PID7, 0, MALLOC_CAP_EXEC|MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "PID2DRAM", { MALLOC_CAP_PID2, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, //Tag 9-14: PID 2-7 DRAM { "PID3DRAM", { MALLOC_CAP_PID3, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "PID4DRAM", { MALLOC_CAP_PID4, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "PID5DRAM", { MALLOC_CAP_PID5, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "PID6DRAM", { MALLOC_CAP_PID6, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "PID7DRAM", { MALLOC_CAP_PID7, MALLOC_CAP_8BIT, MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL }, false}, // { "SPIRAM", { MALLOC_CAP_SPIRAM, 0, MALLOC_CAP_8BIT|MALLOC_CAP_32BIT}, false}, //Tag 15: SPI SRAM data { "", { MALLOC_CAP_INVALID, MALLOC_CAP_INVALID, MALLOC_CAP_INVALID }, false} //End }; /* Region descriptors. These describe all regions of memory available, and tag them according to the capabilities the hardware has. This array is not marked constant; the initialization code may want to change the tags of some regions because eg BT is detected, applications are loaded etc. The priorities here roughly work like this: - For a normal malloc (MALLOC_CAP_8BIT), 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. - Most other malloc caps only fit in one region anyway. These region descriptors are very ESP32 specific, because they describe the memory pools available there. Because of requirements in the coalescing code as well as the heap allocator itself, this list should always be sorted from low to high start address. This array is *NOT* const because it gets modified depending on what pools are/aren't available. */ static HeapRegionTagged_t regions[]={ { (uint8_t *)0x3F800000, 0x400000, 15, 0}, //SPI SRAM, if available { (uint8_t *)0x3FFAE000, 0x2000, 0, 0}, //pool 16 <- used for rom code { (uint8_t *)0x3FFB0000, 0x8000, 0, 0}, //pool 15 <- if BT is enabled, used as BT HW shared memory { (uint8_t *)0x3FFB8000, 0x8000, 0, 0}, //pool 14 <- if BT is enabled, used data memory for BT ROM functions. { (uint8_t *)0x3FFC0000, 0x2000, 0, 0}, //pool 10-13, mmu page 0 { (uint8_t *)0x3FFC2000, 0x2000, 0, 0}, //pool 10-13, mmu page 1 { (uint8_t *)0x3FFC4000, 0x2000, 0, 0}, //pool 10-13, mmu page 2 { (uint8_t *)0x3FFC6000, 0x2000, 0, 0}, //pool 10-13, mmu page 3 { (uint8_t *)0x3FFC8000, 0x2000, 0, 0}, //pool 10-13, mmu page 4 { (uint8_t *)0x3FFCA000, 0x2000, 0, 0}, //pool 10-13, mmu page 5 { (uint8_t *)0x3FFCC000, 0x2000, 0, 0}, //pool 10-13, mmu page 6 { (uint8_t *)0x3FFCE000, 0x2000, 0, 0}, //pool 10-13, mmu page 7 { (uint8_t *)0x3FFD0000, 0x2000, 0, 0}, //pool 10-13, mmu page 8 { (uint8_t *)0x3FFD2000, 0x2000, 0, 0}, //pool 10-13, mmu page 9 { (uint8_t *)0x3FFD4000, 0x2000, 0, 0}, //pool 10-13, mmu page 10 { (uint8_t *)0x3FFD6000, 0x2000, 0, 0}, //pool 10-13, mmu page 11 { (uint8_t *)0x3FFD8000, 0x2000, 0, 0}, //pool 10-13, mmu page 12 { (uint8_t *)0x3FFDA000, 0x2000, 0, 0}, //pool 10-13, mmu page 13 { (uint8_t *)0x3FFDC000, 0x2000, 0, 0}, //pool 10-13, mmu page 14 { (uint8_t *)0x3FFDE000, 0x2000, 0, 0}, //pool 10-13, mmu page 15 { (uint8_t *)0x3FFE0000, 0x4000, 1, 0x400BC000}, //pool 9 blk 1 { (uint8_t *)0x3FFE4000, 0x4000, 1, 0x400B8000}, //pool 9 blk 0 { (uint8_t *)0x3FFE8000, 0x8000, 1, 0x400B0000}, //pool 8 <- can be remapped to ROM, used for MAC dump { (uint8_t *)0x3FFF0000, 0x8000, 1, 0x400A8000}, //pool 7 <- can be used for MAC dump { (uint8_t *)0x3FFF8000, 0x4000, 1, 0x400A4000}, //pool 6 blk 1 <- can be used as trace memory { (uint8_t *)0x3FFFC000, 0x4000, 1, 0x400A0000}, //pool 6 blk 0 <- can be used as trace memory { (uint8_t *)0x40070000, 0x8000, 2, 0}, //pool 0 { (uint8_t *)0x40078000, 0x8000, 2, 0}, //pool 1 { (uint8_t *)0x40080000, 0x2000, 2, 0}, //pool 2-5, mmu page 0 { (uint8_t *)0x40082000, 0x2000, 2, 0}, //pool 2-5, mmu page 1 { (uint8_t *)0x40084000, 0x2000, 2, 0}, //pool 2-5, mmu page 2 { (uint8_t *)0x40086000, 0x2000, 2, 0}, //pool 2-5, mmu page 3 { (uint8_t *)0x40088000, 0x2000, 2, 0}, //pool 2-5, mmu page 4 { (uint8_t *)0x4008A000, 0x2000, 2, 0}, //pool 2-5, mmu page 5 { (uint8_t *)0x4008C000, 0x2000, 2, 0}, //pool 2-5, mmu page 6 { (uint8_t *)0x4008E000, 0x2000, 2, 0}, //pool 2-5, mmu page 7 { (uint8_t *)0x40090000, 0x2000, 2, 0}, //pool 2-5, mmu page 8 { (uint8_t *)0x40092000, 0x2000, 2, 0}, //pool 2-5, mmu page 9 { (uint8_t *)0x40094000, 0x2000, 2, 0}, //pool 2-5, mmu page 10 { (uint8_t *)0x40096000, 0x2000, 2, 0}, //pool 2-5, mmu page 11 { (uint8_t *)0x40098000, 0x2000, 2, 0}, //pool 2-5, mmu page 12 { (uint8_t *)0x4009A000, 0x2000, 2, 0}, //pool 2-5, mmu page 13 { (uint8_t *)0x4009C000, 0x2000, 2, 0}, //pool 2-5, mmu page 14 { (uint8_t *)0x4009E000, 0x2000, 2, 0}, //pool 2-5, mmu page 15 { NULL, 0, 0, 0} //end }; /* For the startup code, the stacks live in memory tagged by this tag. Hence, we only enable allocating from this tag once FreeRTOS has started up completely. */ #define NONOS_STACK_TAG 1 static bool nonos_stack_in_use=true; void heap_alloc_enable_nonos_stack_tag() { nonos_stack_in_use=false; } bool esp32_ptr_has_memory_caps(void *ptr, int caps) { int tag=-1; //Look up region tag of pointer for (int i=0; regions[i].xSizeInBytes!=0; i++) { if (regions[i].xTag != -1 && (uint8_t*)ptr >= regions[i].pucStartAddress && (uint8_t*)ptr < regions[i].pucStartAddress+regions[i].xSizeInBytes) { tag=regions[i].xTag; break; } } if (tag==-1) return false; //Mask off all the caps the tag does have. What should remain is the caps the tag does not have. for (int i=0; i=from && regEnd<=to) { //Entire region falls in the range. Disable entirely. regions[i].xTag=-1; } else if (regStart>=from && regEnd>to && regStartfrom && regEnd<=to) { //End of the region falls in the range. Modify length. regions[i].xSizeInBytes-=(uint8_t *)regEnd-(uint8_t *)from; } else if (regStartto) { //Range punches a hole in the region! We do not support this. ESP_EARLY_LOGE(TAG, "region %d: hole punching is not supported!", i); regions[i].xTag=-1; //Just disable memory region. That'll teach them! } } } /* Warning: These variables are assumed to have the start and end of the data and iram area used statically by the program, respectively. These variables are defined in the ld file. */ extern int _data_start, _heap_start, _init_start, _iram_text_end; #if CONFIG_MEMMAP_SPIRAM_TEST /* Simple RAM test. Writes a word every 32 bytes. Takes about a second to complete for 4MiB. Returns true when RAM seems OK, false when test fails. */ static bool test_spiram(size_t s) { volatile int *spiram=(volatile int*)0x3F800000; size_t p; int errct=0; int initial_err=-1; for (p=0; p=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 ) { #if CONFIG_MEMMAP_SPIRAM_ENABLE_MALLOC void *ret; if (xWantedSize > CONFIG_MEMMAP_SPIRAM_ALLOC_LIMIT_INTERNAL) { ret = pvPortMallocCaps ( xWantedSize, MALLOC_CAP_8BIT|MALLOC_CAP_SPIRAM ); } else { ret = pvPortMallocCaps ( xWantedSize, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL ); } if (ret == NULL) { //Preferred memory pool has too little space left. Fall back to just grabbing any 8bit-capable memory. ret = pvPortMallocCaps ( xWantedSize, MALLOC_CAP_8BIT ); } return ret; #else //Only have internal memory. Just allocate there. return pvPortMallocCaps( xWantedSize, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL ); #endif } /* 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. */ void *pvPortMallocCaps( size_t xWantedSize, uint32_t caps ) { int prio; 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