heap: Add new multi_heap heap implementation to replace FreeRTOS-based tagged heaps

This commit is contained in:
Angus Gratton 2017-04-28 14:08:58 +10:00 committed by Angus Gratton
parent 9b30f66fac
commit 5ee49fd311
21 changed files with 1547 additions and 941 deletions

View file

@ -329,6 +329,12 @@ static void main_task(void* args)
// Now that the application is about to start, disable boot watchdogs
REG_CLR_BIT(TIMG_WDTCONFIG0_REG(0), TIMG_WDT_FLASHBOOT_MOD_EN_S);
REG_CLR_BIT(RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN);
#if !CONFIG_FREERTOS_UNICORE
// Wait for FreeRTOS initialization to finish on APP CPU, before replacing its startup stack
while (port_xSchedulerRunning[1] == 0) {
;
}
#endif
//Enable allocation in region where the startup stacks were located.
heap_alloc_enable_nonos_stack_tag();
app_main();

View file

@ -1,3 +0,0 @@
#pragma once
#warning heap_alloc_caps.h has been renamed to esp_heap_alloc_caps.h. The old header file is deprecated and will be removed in v3.0.
#include "esp_heap_alloc_caps.h"

View file

@ -83,6 +83,7 @@ SECTIONS
_iram_text_start = ABSOLUTE(.);
*(.iram1 .iram1.*)
*libfreertos.a:(.literal .text .literal.* .text.*)
*libheap.a:(.literal .text .literal.* .text.*)
*libesp32.a:panic.o(.literal .text .literal.* .text.*)
*libesp32.a:core_dump.o(.literal .text .literal.* .text.*)
*libesp32.a:heap_alloc_caps.o(.literal .text .literal.* .text.*)

View file

@ -1,591 +0,0 @@
/*
FreeRTOS V8.2.0 - Copyright (C) 2015 Real Time Engineers Ltd.
All rights reserved
VISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION.
This file is part of the FreeRTOS distribution.
FreeRTOS is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License (version 2) as published by the
Free Software Foundation >>!AND MODIFIED BY!<< the FreeRTOS exception.
***************************************************************************
>>! NOTE: The modification to the GPL is included to allow you to !<<
>>! distribute a combined work that includes FreeRTOS without being !<<
>>! obliged to provide the source code for proprietary components !<<
>>! outside of the FreeRTOS kernel. !<<
***************************************************************************
FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. Full license text is available on the following
link: http://www.freertos.org/a00114.html
***************************************************************************
* *
* FreeRTOS provides completely free yet professionally developed, *
* robust, strictly quality controlled, supported, and cross *
* platform software that is more than just the market leader, it *
* is the industry's de facto standard. *
* *
* Help yourself get started quickly while simultaneously helping *
* to support the FreeRTOS project by purchasing a FreeRTOS *
* tutorial book, reference manual, or both: *
* http://www.FreeRTOS.org/Documentation *
* *
***************************************************************************
http://www.FreeRTOS.org/FAQHelp.html - Having a problem? Start by reading
the FAQ page "My application does not run, what could be wrong?". Have you
defined configASSERT()?
http://www.FreeRTOS.org/support - In return for receiving this top quality
embedded software for free we request you assist our global community by
participating in the support forum.
http://www.FreeRTOS.org/training - Investing in training allows your team to
be as productive as possible as early as possible. Now you can receive
FreeRTOS training directly from Richard Barry, CEO of Real Time Engineers
Ltd, and the world's leading authority on the world's leading RTOS.
http://www.FreeRTOS.org/plus - A selection of FreeRTOS ecosystem products,
including FreeRTOS+Trace - an indispensable productivity tool, a DOS
compatible FAT file system, and our tiny thread aware UDP/IP stack.
http://www.FreeRTOS.org/labs - Where new FreeRTOS products go to incubate.
Come and try FreeRTOS+TCP, our new open source TCP/IP stack for FreeRTOS.
http://www.OpenRTOS.com - Real Time Engineers ltd. license FreeRTOS to High
Integrity Systems ltd. to sell under the OpenRTOS brand. Low cost OpenRTOS
licenses offer ticketed support, indemnification and commercial middleware.
http://www.SafeRTOS.com - High Integrity Systems also provide a safety
engineered and independently SIL3 certified version for use in safety and
mission critical applications that require provable dependability.
1 tab == 4 spaces!
*/
/*
* This is a heap allocator that can allocate memory out of several tagged memory regions,
* with the regions having differing capabilities. In the ESP32, this is used to
* allocate memory for the various applications within the space the MMU allows them
* to work with. It can also be used to e.g. allocate memory in DMA-capable regions.
*
* Usage notes:
*
* vPortDefineHeapRegions() ***must*** be called before pvPortMalloc().
* pvPortMalloc() will be called if any task objects (tasks, queues, event
* groups, etc.) are created, therefore vPortDefineHeapRegions() ***must*** be
* called before any other objects are defined.
*
* vPortDefineHeapRegions() takes a single parameter. The parameter is an array
* of HeapRegionTagged_t structures. HeapRegion_t is defined in portable.h as
*
* typedef struct HeapRegion
* {
* uint8_t *pucStartAddress; << Start address of a block of memory that will be part of the heap.
* size_t xSizeInBytes; << Size of the block of memory.
* BaseType_t xTag; << Tag
* } HeapRegionTagged_t;
*
* 'Tag' allows you to allocate memory of a certain type. Tag -1 is special;
* it basically tells the allocator to ignore this region as if it is not
* in the array at all. This facilitates disabling memory regions.
*
* The array is terminated using a NULL zero sized region definition, and the
* memory regions defined in the array ***must*** appear in address order from
* low address to high address. So the following is a valid example of how
* to use the function.
*
* HeapRegionTagged_t xHeapRegions[] =
* {
* { ( uint8_t * ) 0x80000000UL, 0x10000, 1 }, << Defines a block of 0x10000 bytes starting at address 0x80000000, tag 1
* { ( uint8_t * ) 0x90000000UL, 0xa0000, 2 }, << Defines a block of 0xa0000 bytes starting at address of 0x90000000, tag 2
* { NULL, 0, 0 } << Terminates the array.
* };
*
* vPortDefineHeapRegions( xHeapRegions ); << Pass the array into vPortDefineHeapRegions().
*
* Note 0x80000000 is the lower address so appears in the array first.
*
* pvPortMallocTagged can be used to get memory in a tagged region.
*
*/
/*
ToDo:
- This malloc implementation can be somewhat slow, especially when it is called multiple times with multiple tags
when having low memory issues. ToDo: Make it quicker.
-JD
*/
#include <stdlib.h>
/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining
all the API functions to use the MPU wrappers. That should only be done when
task.h is included from an application file. */
#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE
#include "FreeRTOS.h"
#include "task.h"
#include "heap_regions_debug.h"
#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE
#include "heap_regions.h"
#include "rom/ets_sys.h"
/* Block sizes must not get too small. */
#define heapMINIMUM_BLOCK_SIZE ( ( size_t ) ( uxHeapStructSize << 1 ) )
/* Assumes 8bit bytes! */
#define heapBITS_PER_BYTE ( ( size_t ) 8 )
/* Define the linked list structure. This is used to link free blocks in order
of their memory address. This is optimized for size of the linked list struct
and assumes a region is never larger than 16MiB. */
#define HEAPREGIONS_MAX_REGIONSIZE (16*1024*1024)
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK *pxNextFreeBlock; /*<< The next free block in the list. */
int xBlockSize: 24; /*<< The size of the free block. */
int xTag: 7; /*<< Tag of this region */
int xAllocated: 1; /*<< 1 if allocated */
} BlockLink_t;
//Mux to protect the memory status data
static portMUX_TYPE xMallocMutex = portMUX_INITIALIZER_UNLOCKED;
/*-----------------------------------------------------------*/
/*
* Inserts a block of memory that is being freed into the correct position in
* the list of free memory blocks. The block being freed will be merged with
* the block in front it and/or the block behind it if the memory blocks are
* adjacent to each other.
*/
static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert );
/*-----------------------------------------------------------*/
/* The size of the structure placed at the beginning of each allocated memory
block must be correctly byte aligned. */
static const uint32_t uxHeapStructSize = ( ( sizeof ( BlockLink_t ) + BLOCK_HEAD_LEN + BLOCK_TAIL_LEN + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK );
/* Create a couple of list links to mark the start and end of the list. */
static BlockLink_t xStart, *pxEnd = NULL;
/* Keeps track of the number of free bytes remaining, but says nothing about
fragmentation. */
static size_t xFreeBytesRemaining[HEAPREGIONS_MAX_TAGCOUNT] = {0};
static size_t xMinimumEverFreeBytesRemaining[HEAPREGIONS_MAX_TAGCOUNT] = {0};
/*-----------------------------------------------------------*/
void *pvPortMallocTagged( size_t xWantedSize, BaseType_t tag )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
/* The heap must be initialised before the first call to
prvPortMalloc(). */
configASSERT( pxEnd );
taskENTER_CRITICAL(&xMallocMutex);
{
/* The wanted size is increased so it can contain a BlockLink_t
structure in addition to the requested amount of bytes. */
if( xWantedSize > 0 )
{
xWantedSize += uxHeapStructSize;
/* Ensure that blocks are always aligned to the required number
of bytes. */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
/* Byte alignment required. */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining[ tag ] ) )
{
/* Traverse the list from the start (lowest address) block until
one of adequate size is found. */
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while( ( ( pxBlock->xTag != tag ) || ( pxBlock->xBlockSize < xWantedSize ) ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
// ets_printf("Block %x -> %x\n", (uint32_t)pxBlock, (uint32_t)pxBlock->pxNextFreeBlock);
#if (configENABLE_MEMORY_DEBUG == 1)
{
mem_check_block(pxBlock);
}
#endif
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
/* If the end marker was not reached then a block of adequate size
was found. */
if( pxBlock != pxEnd )
{
/* Return the memory space pointed to - jumping over the
BlockLink_t structure at its start. */
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + uxHeapStructSize - BLOCK_TAIL_LEN - BLOCK_HEAD_LEN);
/* This block is being returned for use so must be taken out
of the list of free blocks. */
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
/* If the block is larger than required it can be split into
two. */
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
/* This block is to be split into two. Create a new
block following the number of bytes requested. The void
cast is used to prevent byte alignment warnings from the
compiler. */
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize);
/* Calculate the sizes of two blocks split from the
single block. */
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxNewBlockLink->xTag = tag;
pxBlock->xBlockSize = xWantedSize;
#if (configENABLE_MEMORY_DEBUG == 1)
{
mem_init_dog(pxNewBlockLink);
}
#endif
/* Insert the new block into the list of free blocks. */
prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
xFreeBytesRemaining[ tag ] -= pxBlock->xBlockSize;
if( xFreeBytesRemaining[ tag ] < xMinimumEverFreeBytesRemaining[ tag ] )
{
xMinimumEverFreeBytesRemaining[ tag ] = xFreeBytesRemaining[ tag ];
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* The block is being returned - it is allocated and owned
by the application and has no "next" block. */
pxBlock->xAllocated = 1;
pxBlock->pxNextFreeBlock = NULL;
#if (configENABLE_MEMORY_DEBUG == 1)
{
mem_init_dog(pxBlock);
mem_malloc_block(pxBlock);
}
#endif
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
traceMALLOC( pvReturn, xWantedSize );
}
taskEXIT_CRITICAL(&xMallocMutex);
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
return pvReturn;
}
/*-----------------------------------------------------------*/
void vPortFreeTagged( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
if( pv != NULL )
{
/* The memory being freed will have an BlockLink_t structure immediately
before it. */
puc -= (uxHeapStructSize - BLOCK_TAIL_LEN - BLOCK_HEAD_LEN) ;
/* This casting is to keep the compiler from issuing warnings. */
pxLink = ( void * ) puc;
#if (configENABLE_MEMORY_DEBUG == 1)
{
taskENTER_CRITICAL(&xMallocMutex);
mem_check_block(pxLink);
mem_free_block(pxLink);
taskEXIT_CRITICAL(&xMallocMutex);
}
#endif
/* Check the block is actually allocated. */
configASSERT( ( pxLink->xAllocated ) != 0 );
configASSERT( pxLink->pxNextFreeBlock == NULL );
if( pxLink->xAllocated != 0 )
{
if( pxLink->pxNextFreeBlock == NULL )
{
/* The block is being returned to the heap - it is no longer
allocated. */
pxLink->xAllocated = 0;
taskENTER_CRITICAL(&xMallocMutex);
{
/* Add this block to the list of free blocks. */
xFreeBytesRemaining[ pxLink->xTag ] += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
}
taskEXIT_CRITICAL(&xMallocMutex);
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSizeTagged( BaseType_t tag )
{
return xFreeBytesRemaining[ tag ];
}
/*-----------------------------------------------------------*/
size_t xPortGetMinimumEverFreeHeapSizeTagged( BaseType_t tag )
{
return xMinimumEverFreeBytesRemaining[ tag ];
}
/*-----------------------------------------------------------*/
static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
BlockLink_t *pxIterator;
uint8_t *puc;
/* Iterate through the list until a block is found that has a higher address
than the block being inserted. */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
/* Nothing to do here, just iterate to the right position. */
}
/* Do the block being inserted, and the block it is being inserted after
make a contiguous block of memory, and are the tags the same? */
puc = ( uint8_t * ) pxIterator;
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert && pxBlockToInsert->xTag==pxIterator->xTag)
{
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Do the block being inserted, and the block it is being inserted before
make a contiguous block of memory, and are the tags the same */
puc = ( uint8_t * ) pxBlockToInsert;
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock && pxBlockToInsert->xTag==pxIterator->pxNextFreeBlock->xTag )
{
if( pxIterator->pxNextFreeBlock != pxEnd )
{
/* Form one big block from the two blocks. */
pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
}
else
{
pxBlockToInsert->pxNextFreeBlock = pxEnd;
}
}
else
{
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
}
/* If the block being inserted plugged a gap, so was merged with the block
before and the block after, then it's pxNextFreeBlock pointer will have
already been set, and should not be set here as that would make it point
to itself. */
if( pxIterator != pxBlockToInsert )
{
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/*-----------------------------------------------------------*/
void vPortDefineHeapRegionsTagged( const HeapRegionTagged_t * const pxHeapRegions )
{
BlockLink_t *pxFirstFreeBlockInRegion = NULL, *pxPreviousFreeBlock;
uint8_t *pucAlignedHeap;
size_t xTotalRegionSize, xTotalHeapSize = 0;
BaseType_t xDefinedRegions = 0, xRegIdx = 0;
uint32_t ulAddress;
const HeapRegionTagged_t *pxHeapRegion;
/* Can only call once! */
configASSERT( pxEnd == NULL );
vPortCPUInitializeMutex(&xMallocMutex);
pxHeapRegion = &( pxHeapRegions[ xRegIdx ] );
while( pxHeapRegion->xSizeInBytes > 0 )
{
if ( pxHeapRegion->xTag == -1 ) {
/* Move onto the next HeapRegionTagged_t structure. */
xRegIdx++;
pxHeapRegion = &( pxHeapRegions[ xRegIdx ] );
continue;
}
configASSERT(pxHeapRegion->xTag < HEAPREGIONS_MAX_TAGCOUNT);
configASSERT(pxHeapRegion->xSizeInBytes < HEAPREGIONS_MAX_REGIONSIZE);
xTotalRegionSize = pxHeapRegion->xSizeInBytes;
/* Ensure the heap region starts on a correctly aligned boundary. */
ulAddress = ( uint32_t ) pxHeapRegion->pucStartAddress;
if( ( ulAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
{
ulAddress += ( portBYTE_ALIGNMENT - 1 );
ulAddress &= ~portBYTE_ALIGNMENT_MASK;
/* Adjust the size for the bytes lost to alignment. */
xTotalRegionSize -= ulAddress - ( uint32_t ) pxHeapRegion->pucStartAddress;
}
pucAlignedHeap = ( uint8_t * ) ulAddress;
/* Set xStart if it has not already been set. */
if( xDefinedRegions == 0 )
{
/* xStart is used to hold a pointer to the first item in the list of
free blocks. The void cast is used to prevent compiler warnings. */
xStart.pxNextFreeBlock = ( BlockLink_t * ) (pucAlignedHeap + BLOCK_HEAD_LEN);
xStart.xBlockSize = ( size_t ) 0;
}
else
{
/* Should only get here if one region has already been added to the
heap. */
configASSERT( pxEnd != NULL );
/* Check blocks are passed in with increasing start addresses. */
configASSERT( ulAddress > ( uint32_t ) pxEnd );
}
/* Remember the location of the end marker in the previous region, if
any. */
pxPreviousFreeBlock = pxEnd;
/* pxEnd is used to mark the end of the list of free blocks and is
inserted at the end of the region space. */
ulAddress = ( ( uint32_t ) pucAlignedHeap ) + xTotalRegionSize;
ulAddress -= uxHeapStructSize;
ulAddress &= ~portBYTE_ALIGNMENT_MASK;
pxEnd = ( BlockLink_t * ) (ulAddress + BLOCK_HEAD_LEN);
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
pxEnd->xTag = -1;
/* To start with there is a single free block in this region that is
sized to take up the entire heap region minus the space taken by the
free block structure. */
pxFirstFreeBlockInRegion = ( BlockLink_t * ) (pucAlignedHeap + BLOCK_HEAD_LEN);
pxFirstFreeBlockInRegion->xBlockSize = ulAddress - ( uint32_t ) pxFirstFreeBlockInRegion + BLOCK_HEAD_LEN;
pxFirstFreeBlockInRegion->pxNextFreeBlock = pxEnd;
pxFirstFreeBlockInRegion->xTag=pxHeapRegion->xTag;
/* If this is not the first region that makes up the entire heap space
then link the previous region to this region. */
if( pxPreviousFreeBlock != NULL )
{
pxPreviousFreeBlock->pxNextFreeBlock = pxFirstFreeBlockInRegion;
}
xTotalHeapSize += pxFirstFreeBlockInRegion->xBlockSize;
xMinimumEverFreeBytesRemaining[ pxHeapRegion->xTag ] += pxFirstFreeBlockInRegion->xBlockSize;
xFreeBytesRemaining[ pxHeapRegion->xTag ] += pxFirstFreeBlockInRegion->xBlockSize;
/* Move onto the next HeapRegionTagged_t structure. */
xDefinedRegions++;
xRegIdx++;
pxHeapRegion = &( pxHeapRegions[ xRegIdx ] );
#if (configENABLE_MEMORY_DEBUG == 1)
{
mem_init_dog(pxFirstFreeBlockInRegion);
mem_init_dog(pxEnd);
}
#endif
}
/* Check something was actually defined before it is accessed. */
configASSERT( xTotalHeapSize );
#if (configENABLE_MEMORY_DEBUG == 1)
{
mem_debug_init(uxHeapStructSize, &xStart, pxEnd, &xMallocMutex);
mem_check_all(0);
}
#endif
}

View file

@ -1,191 +0,0 @@
#include "heap_regions_debug.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#if (configENABLE_MEMORY_DEBUG == 1)
static os_block_t g_malloc_list, *g_free_list=NULL, *g_end;
static size_t g_heap_struct_size;
static mem_dbg_ctl_t g_mem_dbg;
char g_mem_print = 0;
static portMUX_TYPE *g_malloc_mutex = NULL;
#define MEM_DEBUG(...)
void mem_debug_init(size_t size, void *start, void *end, portMUX_TYPE *mutex)
{
MEM_DEBUG("size=%d start=%p end=%p mutex=%p%x\n", size, start, end, mutex);
memset(&g_mem_dbg, 0, sizeof(g_mem_dbg));
memset(&g_malloc_list, 0, sizeof(g_malloc_list));
g_malloc_mutex = mutex;
g_heap_struct_size = size;
g_free_list = start;
g_end = end;
}
void mem_debug_push(char type, void *addr)
{
os_block_t *b = (os_block_t*)addr;
debug_block_t *debug_b = DEBUG_BLOCK(b);
MEM_DEBUG("push type=%d addr=%p\n", type, addr);
if (g_mem_print){
if (type == DEBUG_TYPE_MALLOC){
ets_printf("task=%s t=%s s=%u a=%p\n", debug_b->head.task?debug_b->head.task:"", type==DEBUG_TYPE_MALLOC?"m":"f", b->size, addr);
} else {
ets_printf("task=%s t=%s s=%u a=%p\n", debug_b->head.task?debug_b->head.task:"", type==DEBUG_TYPE_MALLOC?"m":"f", b->size, addr);
}
} else {
mem_dbg_info_t *info = &g_mem_dbg.info[g_mem_dbg.cnt%DEBUG_MAX_INFO_NUM];
info->addr = addr;
info->type = type;
info->time = g_mem_dbg.cnt;
g_mem_dbg.cnt++;
}
}
void mem_debug_malloc_show(void)
{
os_block_t *b = g_malloc_list.next;
debug_block_t *d;
taskENTER_CRITICAL(g_malloc_mutex);
while (b){
d = DEBUG_BLOCK(b);
d->head.task[3] = '\0';
ets_printf("t=%s s=%u a=%p\n", d->head.task?d->head.task:"", b->size, b);
b = b->next;
}
taskEXIT_CRITICAL(g_malloc_mutex);
}
void mem_debug_show(void)
{
uint32_t i;
if (!g_mem_print) return;
for (i=0; i<DEBUG_MAX_INFO_NUM; i++){
ets_printf("%u %s %p\n", g_mem_dbg.info[i].time, g_mem_dbg.info[i].type == DEBUG_TYPE_FREE?"f":"m", g_mem_dbg.info[i].addr);
}
}
void mem_check_block(void* data)
{
debug_block_t *b = DEBUG_BLOCK(data);
MEM_DEBUG("check block data=%p\n", data);
if (data && (HEAD_DOG(b) == DEBUG_DOG_VALUE)){
if (TAIL_DOG(b) != DEBUG_DOG_VALUE){
ets_printf("f task=%s a=%p h=%08x t=%08x\n", b->head.task?b->head.task:"", b, HEAD_DOG(b), TAIL_DOG(b));
DOG_ASSERT();
}
} else {
ets_printf("f task=%s a=%p h=%08x\n", b->head.task?b->head.task:"", b, HEAD_DOG(b));\
DOG_ASSERT();
}
}
void mem_init_dog(void *data)
{
debug_block_t *b = DEBUG_BLOCK(data);
xTaskHandle task;
MEM_DEBUG("init dog, data=%p debug_block=%p block_size=%x\n", data, b, b->os_block.size);
if (!data) return;
#if (INCLUDE_pcTaskGetTaskName == 1)
task = xTaskGetCurrentTaskHandle();
if (task){
strncpy(b->head.task, pcTaskGetTaskName(task), 3);
b->head.task[3] = '\0';
}
#else
b->head.task = '\0';
#endif
HEAD_DOG(b) = DEBUG_DOG_VALUE;
TAIL_DOG(b) = DEBUG_DOG_VALUE;
}
void mem_check_all(void* pv)
{
os_block_t *b;
if (pv){
char *puc = (char*)(pv);
os_block_t *b;
puc -= (g_heap_struct_size - BLOCK_TAIL_LEN - BLOCK_HEAD_LEN);
b = (os_block_t*)puc;
mem_check_block(b);
}
taskENTER_CRITICAL(g_malloc_mutex);
b = g_free_list->next;
while(b && b != g_end){
mem_check_block(b);
ets_printf("check b=%p size=%d ok\n", b, b->size);
b = b->next;
}
taskEXIT_CRITICAL(g_malloc_mutex);
}
void mem_malloc_show(void)
{
os_block_t *b = g_malloc_list.next;
debug_block_t *debug_b;
while (b){
debug_b = DEBUG_BLOCK(b);
ets_printf("%s %p %p %u\n", debug_b->head.task, debug_b, b, b->size);
b = b->next;
}
}
void mem_malloc_block(void *data)
{
os_block_t *b = (os_block_t*)data;
MEM_DEBUG("mem malloc block data=%p, size=%u\n", data, b->size);
mem_debug_push(DEBUG_TYPE_MALLOC, data);
if (b){
b->next = g_malloc_list.next;
g_malloc_list.next = b;
}
}
void mem_free_block(void *data)
{
os_block_t *del = (os_block_t*)data;
os_block_t *b = g_malloc_list.next;
os_block_t *pre = &g_malloc_list;
debug_block_t *debug_b;
MEM_DEBUG("mem free block data=%p, size=%d\n", data, del->size);
mem_debug_push(DEBUG_TYPE_FREE, data);
if (!del) {
return;
}
while (b){
if ( (del == b) ){
pre->next = b->next;
b->next = NULL;
return;
}
pre = b;
b = b->next;
}
debug_b = DEBUG_BLOCK(del);
ets_printf("%s %p %p %u already free\n", debug_b->head.task, debug_b, del, del->size);
mem_malloc_show();
abort();
}
#endif

View file

@ -15,9 +15,7 @@
#define _HEAP_REGIONS_H
#include "freertos/FreeRTOS.h"
/* The maximum amount of tags in use */
#define HEAPREGIONS_MAX_TAGCOUNT 16
#include <mumm_malloc.h>
/**
* @brief Structure to define a memory region
@ -28,6 +26,7 @@ typedef struct HeapRegionTagged
size_t xSizeInBytes; ///< Size of the region
BaseType_t xTag; ///< Tag for the region
uint32_t xExecAddr; ///< If non-zero, indicates the region also has an alias in IRAM.
mumm_heap_handle heap;
} HeapRegionTagged_t;
/**
@ -93,4 +92,4 @@ size_t xPortGetMinimumEverFreeHeapSizeTagged( BaseType_t tag );
size_t xPortGetFreeHeapSizeTagged( BaseType_t tag );
#endif
#endif

View file

@ -1,79 +0,0 @@
#ifndef _HEAP_REGION_DEBUG_H
#define _HEAP_REGION_DEBUG_H
#include "FreeRTOS.h"
#if (configENABLE_MEMORY_DEBUG == 1)
#define DEBUG_DOG_VALUE 0x1a2b3c4d
#define DEBUG_MAX_INFO_NUM 20
#define DEBUG_TYPE_MALLOC 1
#define DEBUG_TYPE_FREE 2
typedef struct {
unsigned int dog;
char task[4];
unsigned int pc;
}block_head_t;
typedef struct {
unsigned int dog;
}block_tail_t;
/* Please keep this definition same as BlockLink_t */
typedef struct _os_block_t {
struct _os_block_t *next; /*<< The next free block in the list. */
int size: 24; /*<< The size of the free block. */
int xtag: 7; /*<< Tag of this region */
int xAllocated: 1; /*<< 1 if allocated */
}os_block_t;
typedef struct {
block_head_t head;
os_block_t os_block;
}debug_block_t;
typedef struct _mem_dbg_info{
void *addr;
char *task;
uint32_t pc;
uint32_t time;
uint8_t type;
}mem_dbg_info_t;
typedef struct _mem_dbg_ctl{
mem_dbg_info_t info[DEBUG_MAX_INFO_NUM];
uint32_t cnt;
}mem_dbg_ctl_t;
#define BLOCK_HEAD_LEN sizeof(block_head_t)
#define BLOCK_TAIL_LEN sizeof(block_tail_t)
#define OS_BLOCK(_b) ((os_block_t*)((debug_block_t*)((char*)(_b) + BLOCK_HEAD_LEN)))
#define DEBUG_BLOCK(_b) ((debug_block_t*)((char*)(_b) - BLOCK_HEAD_LEN))
#define HEAD_DOG(_b) ((_b)->head.dog)
#define TAIL_DOG(_b) (*(unsigned int*)((char*)(_b) + (((_b)->os_block.size ) - BLOCK_TAIL_LEN)))
#define DOG_ASSERT()\
{\
mem_debug_show();\
abort();\
}
extern void mem_check_block(void * data);
extern void mem_init_dog(void *data);
extern void mem_debug_init(size_t size, void *start, void *end, portMUX_TYPE *mutex);
extern void mem_malloc_block(void *data);
extern void mem_free_block(void *data);
extern void mem_check_all(void* pv);
#else
#define mem_check_block(...)
#define mem_init_dog(...)
#define BLOCK_HEAD_LEN 0
#define BLOCK_TAIL_LEN 0
#endif
#endif

View file

@ -0,0 +1,3 @@
#
# Component Makefile
#

View file

@ -0,0 +1,16 @@
/* mumm_malloc-compatible DBGLOG API, which maps to esp_log
primitives */
#pragma once
#include "esp_log.h"
static const char *TAG = "mumm_malloc";
#define DBGLOG_TRACE(format, ...) ESP_EARLY_LOGV(TAG, format, ## __VA_ARGS__)
#define DBGLOG_DEBUG(format, ...) ESP_EARLY_LOGD(TAG, format, ## __VA_ARGS__)
#define DBGLOG_CRITICAL(format, ...) ESP_EARLY_LOGW(TAG, format, ## __VA_ARGS__)
#define DBGLOG_ERROR(format, ...) ESP_EARLY_LOGE(TAG, format, ## __VA_ARGS__)
#define DBGLOG_WARNING(format, ...) ESP_EARLY_LOGW(TAG, format, ## __VA_ARGS__)
/* DBGLOG_FORCE in mumm_malloc always prints, but in this case we use ESP_LOGI */
#define DBGLOG_FORCE(force, format, ...) do { if(force) { ESP_LOGI(TAG, format, ## __VA_ARGS__); } } while(0)

View file

@ -11,25 +11,26 @@
// 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 <rom/ets_sys.h>
#include <freertos/heap_regions.h>
#include "esp_heap_alloc_caps.h"
#include "spiram.h"
#include "esp_log.h"
#include <stdbool.h>
#include <assert.h>
#include "esp_heap_alloc_caps.h"
#include "esp_log.h"
#include "multi_heap.h"
#include <freertos/FreeRTOS.h>
#include <freertos/heap_regions.h> /* TODO remove this */
/* The maximum amount of tags in use */
#define HEAPREGIONS_MAX_TAGCOUNT 16
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.
This file, combined with a region allocator that supports multiple heaps, 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.
*/
@ -71,6 +72,16 @@ static const tag_desc_t tag_desc[]={
{ "", { MALLOC_CAP_INVALID, MALLOC_CAP_INVALID, MALLOC_CAP_INVALID }, false} //End
};
typedef struct
{
uint8_t *pucStartAddress; ///< Start address of the region
size_t xSizeInBytes; ///< Size of the region
size_t xTag; ///< Tag for the region
uint32_t xExecAddr; ///< If non-zero, indicates the region also has an alias in IRAM.
multi_heap_handle heap;
portMUX_TYPE heap_mux;
} heap_t;
/*
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
@ -90,63 +101,74 @@ 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, 0x20000, 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
static heap_t regions[]={
{ (uint8_t *)0x3F800000, 0x20000, 15, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //SPI SRAM, if available
{ (uint8_t *)0x3FFAE000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 16 <- used for rom code
{ (uint8_t *)0x3FFB0000, 0x8000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 15 <- if BT is enabled, used as BT HW shared memory
{ (uint8_t *)0x3FFB8000, 0x8000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 14 <- if BT is enabled, used data memory for BT ROM functions.
{ (uint8_t *)0x3FFC0000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 0
{ (uint8_t *)0x3FFC2000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 1
{ (uint8_t *)0x3FFC4000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 2
{ (uint8_t *)0x3FFC6000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 3
{ (uint8_t *)0x3FFC8000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 4
{ (uint8_t *)0x3FFCA000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 5
{ (uint8_t *)0x3FFCC000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 6
{ (uint8_t *)0x3FFCE000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 7
{ (uint8_t *)0x3FFD0000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 8
{ (uint8_t *)0x3FFD2000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 9
{ (uint8_t *)0x3FFD4000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 10
{ (uint8_t *)0x3FFD6000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 11
{ (uint8_t *)0x3FFD8000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 12
{ (uint8_t *)0x3FFDA000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 13
{ (uint8_t *)0x3FFDC000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 14
{ (uint8_t *)0x3FFDE000, 0x2000, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 10-13, mmu page 15
{ (uint8_t *)0x3FFE0000, 0x4000, 1, 0x400BC000, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 9 blk 1
{ (uint8_t *)0x3FFE4000, 0x4000, 1, 0x400B8000, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 9 blk 0
{ (uint8_t *)0x3FFE8000, 0x8000, 1, 0x400B0000, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 8 <- can be remapped to ROM, used for MAC dump
{ (uint8_t *)0x3FFF0000, 0x8000, 1, 0x400A8000, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 7 <- can be used for MAC dump
{ (uint8_t *)0x3FFF8000, 0x4000, 1, 0x400A4000, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 6 blk 1 <- can be used as trace memory
{ (uint8_t *)0x3FFFC000, 0x4000, 1, 0x400A0000, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 6 blk 0 <- can be used as trace memory
{ (uint8_t *)0x40070000, 0x8000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 0
{ (uint8_t *)0x40078000, 0x8000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 1
{ (uint8_t *)0x40080000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 0
{ (uint8_t *)0x40082000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 1
{ (uint8_t *)0x40084000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 2
{ (uint8_t *)0x40086000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 3
{ (uint8_t *)0x40088000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 4
{ (uint8_t *)0x4008A000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 5
{ (uint8_t *)0x4008C000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 6
{ (uint8_t *)0x4008E000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 7
{ (uint8_t *)0x40090000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 8
{ (uint8_t *)0x40092000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 9
{ (uint8_t *)0x40094000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 10
{ (uint8_t *)0x40096000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 11
{ (uint8_t *)0x40098000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 12
{ (uint8_t *)0x4009A000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 13
{ (uint8_t *)0x4009C000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 14
{ (uint8_t *)0x4009E000, 0x2000, 2, 0, 0, portMUX_INITIALIZER_UNLOCKED}, //pool 2-5, mmu page 15
{ NULL, 0, 0, 0, 0, portMUX_INITIALIZER_UNLOCKED} //end
};
/* For the startup code, the stacks live in memory tagged by this tag. Hence, we only enable allocating from this tag
#define NUM_REGIONS (sizeof(regions)/sizeof(heap_t))
/* 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;
static void register_heap_region(heap_t *region)
{
region->heap = multi_heap_register(region->pucStartAddress, region->xSizeInBytes, &(region->heap_mux));
ESP_EARLY_LOGI(TAG, "new heap @ %p", region->heap);
assert(region->heap);
}
void heap_alloc_enable_nonos_stack_tag()
{
nonos_stack_in_use=false;
for (int i = 0; regions[i].xSizeInBytes!=0; i++) {
if (regions[i].xTag == NONOS_STACK_TAG) {
register_heap_region(&regions[i]);
}
}
}
//Modify regions array to disable the given range of memory.
@ -259,13 +281,19 @@ void heap_alloc_caps_init() {
ESP_EARLY_LOGI(TAG, "Initializing. RAM available for dynamic allocation:");
for (i=0; regions[i].xSizeInBytes!=0; i++) {
if (regions[i].xTag != -1) {
ESP_EARLY_LOGI(TAG, "At %08X len %08X (%d KiB): %s",
(int)regions[i].pucStartAddress, regions[i].xSizeInBytes, regions[i].xSizeInBytes/1024, tag_desc[regions[i].xTag].name);
if (regions[i].xTag == -1) {
continue;
}
ESP_EARLY_LOGI(TAG, "At %08X len %08X (%d KiB): %s",
(int)regions[i].pucStartAddress, regions[i].xSizeInBytes, regions[i].xSizeInBytes/1024, tag_desc[regions[i].xTag].name);
if (regions[i].xTag == NONOS_STACK_TAG) {
continue; /* Will be registered when OS scheduler starts */
}
register_heap_region(&regions[i]);
}
//Initialize the malloc implementation.
vPortDefineHeapRegionsTagged( regions );
}
//First and last words of the D/IRAM region, for both the DRAM address as well as the IRAM alias.
@ -306,6 +334,25 @@ void *pvPortMalloc( size_t xWantedSize )
return pvPortMallocCaps( xWantedSize, MALLOC_CAP_8BIT );
}
void vPortFreeTagged( void *pv )
{
intptr_t p = (intptr_t)pv;
if (pv == NULL) {
return;
}
for (size_t i = 0; regions[i].xSizeInBytes!=0; i++) {
if (regions[i].xTag == -1) {
continue;
}
intptr_t start = (intptr_t)regions[i].pucStartAddress;
if(p >= start && p < start + regions[i].xSizeInBytes) {
multi_free(regions[i].heap, pv);
return;
}
}
assert(false && "free() target pointer is outside heap areas");
}
/*
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.
@ -323,6 +370,22 @@ void vPortFree( void *pv )
return vPortFreeTagged(pv);
}
void *pvPortMallocTagged( size_t xWantedSize, BaseType_t tag )
{
if (tag == -1) {
return NULL;
}
for (size_t i = 0; i < NUM_REGIONS; i++) {
if (regions[i].xTag == tag && regions[i].heap != NULL) {
void * r = multi_malloc(regions[i].heap, xWantedSize);
if (r != NULL) {
return r;
}
}
}
return NULL;
}
/*
Routine to allocate a bit of memory with certain capabilities. caps is a bitfield of MALLOC_CAP_* bits.
*/
@ -346,10 +409,6 @@ void *pvPortMallocCaps( size_t xWantedSize, uint32_t caps )
for (prio=0; prio<NO_PRIOS; prio++) {
//Iterate over tag descriptors for this priority
for (tag=0; tag_desc[tag].prio[prio]!=MALLOC_CAP_INVALID; tag++) {
if (nonos_stack_in_use && tag == NONOS_STACK_TAG) {
//Non-os stack lives here and is still in use. Don't alloc here.
continue;
}
if ((tag_desc[tag].prio[prio]&caps)!=0) {
//Tag has at least one of the caps requested. If caps has other bits set that this prio
//doesn't cover, see if they're available in other prios.
@ -381,6 +440,29 @@ void *pvPortMallocCaps( size_t xWantedSize, uint32_t caps )
}
size_t xPortGetFreeHeapSizeTagged(BaseType_t tag)
{
size_t ret = 0;
for (size_t i = 0; regions[i].xSizeInBytes!=0; i++) {
if (regions[i].xTag == tag && regions[i].heap != NULL) {
ret += multi_free_heap_size(regions[i].heap);
}
}
return ret;
}
size_t xPortGetMinimumEverFreeHeapSizeTagged(BaseType_t tag)
{
size_t ret = 0;
for (size_t i = 0; regions[i].xSizeInBytes!=0; i++) {
if (regions[i].xTag == tag && regions[i].heap != NULL) {
ret += multi_minimum_free_heap_size(regions[i].heap);
}
}
return ret;
}
size_t xPortGetFreeHeapSizeCaps( uint32_t caps )
{
int prio;
@ -402,6 +484,7 @@ size_t xPortGetMinimumEverFreeHeapSizeCaps( uint32_t caps )
int prio;
int tag;
size_t ret=0;
for (prio=0; prio<NO_PRIOS; prio++) {
//Iterate over tag descriptors for this priority
for (tag=0; tag_desc[tag].prio[prio]!=MALLOC_CAP_INVALID; tag++) {

View file

@ -18,6 +18,9 @@
extern "C" {
#endif
#include <stdint.h>
#include <stdlib.h>
/**
* @brief Flags to indicate the capabilities of the various memory systems
*/

View file

@ -0,0 +1,168 @@
// 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.
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
/* multi_heap is a heap implementation for handling multiple
heterogenous heaps in a single program.
Any contiguous block of memory can be registered as a heap.
*/
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Opaque handle to a registered heap */
typedef struct multi_heap_info *multi_heap_handle_t;
/** @brief malloc() a buffer in a given heap
*
* Semantics are the same as standard malloc(), only the returned buffer will be allocated in the specified heap.
*
* @param heap Handle to a registered heap.
* @param size Size of desired buffer.
*
* @return Pointer to new memory, or NULL if allocation fails.
*/
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size);
/** @brief free() a buffer in a given heap.
*
* Semantics are the same as standard free(), only the argument 'p' must be NULL or have been allocated in the specified heap.
*
* @param heap Handle to a registered heap.
* @param p NULL, or a pointer previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
*/
void multi_heap_free(multi_heap_handle_t heap, void *p);
/** @brief realloc() a buffer in a given heap.
*
* Semantics are the same as standard realloc(), only the argument 'p' must be NULL or have been allocated in the specified heap.
*
* @param heap Handle to a registered heap.
* @param p NULL, or a pointer previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
* @param size Desired new size for buffer.
*
* @return New buffer of 'size' containing contents of 'p', or NULL if reallocation failed.
*/
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size);
/** @brief Return the size that a particular pointer was allocated with.
*
* @param heap Handle to a registered heap.
* @param p Pointer, must have been previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
*
* @return Size of the memory allocated at this block. May be more than the original size argument, due
* to padding and minimum block sizes.
*/
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p);
/** @brief Register a new heap for use
*
* This function initialises a heap at the specified address, and returns a handle for future heap operations.
*
* There is no equivalent function for deregistering a heap - if all blocks in the heap are free, you can immediately start using the memory for other purposes.
*
* @param start Start address of the memory to use for a new heap.
* @param size Size (in bytes) of the new heap.
*
* @return Handle of a new heap ready for use, or NULL if the heap region was too small to be initialised.
*/
multi_heap_handle_t multi_heap_register(void *start, size_t size);
/** @brief Associate a private lock pointer with a heap
*
* The lock argument is supplied to the MULTI_HEAP_LOCK() and MULTI_HEAP_UNLOCK() macros, defined in multi_heap_platform.h.
*
* When the heap is first registered, the associated lock is NULL.
*
* @param heap Handle to a registered heap.
* @param lock Optional pointer to a locking structure to associate with this heap.
*/
void multi_heap_set_lock(multi_heap_handle_t heap, void* lock);
/** @brief Dump heap information to stdout
*
* For debugging purposes, this function dumps information about every block in the heap to stdout.
*
* @param heap Handle to a registered heap.
*/
void multi_heap_dump(multi_heap_handle_t handle);
/** @brief Check heap integrity
*
* Walks the heap and checks all heap data structures are valid. If any errors are detected, an error-specific message
* can be optionally printed to stderr. Print behaviour can be overriden at compile time by defining
* MULTI_CHECK_FAIL_PRINTF in multi_heap_platform.h.
*
* @param heap Handle to a registered heap.
* @param print_errors If true, errors will be printed to stderr.
* @return true if heap is valid, false otherwise.
*/
bool multi_heap_check(multi_heap_handle_t heap, bool print_errors);
/** @brief Return free heap size
*
* Returns the number of bytes available in the heap.
*
* Equivalent to the total_free_bytes member returned by multi_heap_get_heap_info().
*
* Note that the heap may be fragmented, so the actual maximum size for a single malloc() may be lower. To know this
* size, see the largest_free_block member returned by multi_heap_get_heap_info().
*
* @param heap Handle to a registered heap.
* @return Number of free bytes.
*/
size_t multi_heap_free_size(multi_heap_handle_t heap);
/** @brief Return the lifetime minimum free heap size
*
* Equivalent to the minimum_free_bytes member returned by multi_get_heap_info().
*
* Returns the lifetime "low water mark" of possible values returned from multi_free_heap_size(), for the specified
* heap.
*
* @param heap Handle to a registered heap.
* @return Number of free bytes.
*/
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap);
typedef struct {
size_t total_free_bytes; ///< Total free bytes in the heap. Equivalent to multi_free_heap_size().
size_t total_allocated_bytes; ///< Total bytes allocated to data in the heap.
size_t largest_free_block; ///< Size of largest free block in the heap. This is the largest malloc-able size.
size_t minimum_free_bytes; ///< Lifetime minimum free heap size. Equivalent to multi_minimum_free_heap_size().
size_t allocated_blocks; ///< Number of (variable size) blocks allocated in the heap.
size_t free_blocks; ///< Number of (variable size) free blocks in the heap.
size_t total_blocks; ///< Total number of (variable size) blocks in the heap.
} multi_heap_info_t;
/** @brief Return metadata about a given heap
*
* Fills a multi_heap_info_t structure with information about the specified heap.
*
* @param heap Handle to a registered heap.
* @param info Pointer to a structure to fill with heap metadata.
*/
void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,19 @@
// Copyright 2015-2017 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.
#pragma once
/* Simple wrapper means that mumm_malloc src/ directory doesn't need
to be in the global search path.
*/
#include "../mumm_malloc/src/mumm_malloc.h"

View file

@ -0,0 +1,117 @@
/*
* Configuration for mumm_malloc in IDF
*
* Unlike umm_malloc, this config doesn't include
* much heap configuration - just compiler configuration
*/
#ifndef _MUMM_MALLOC_CFG_H
#define _MUMM_MALLOC_CFG_H
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
/* A couple of macros to make packing structures less compiler dependent */
#define MUMM_H_ATTPACKPRE
#define MUMM_H_ATTPACKSUF __attribute__((__packed__))
#define MUMM_INFO
#ifdef MUMM_INFO
typedef struct MUMM_HEAP_INFO_t {
unsigned short int totalEntries;
unsigned short int usedEntries;
unsigned short int freeEntries;
unsigned short int totalBlocks;
unsigned short int usedBlocks;
unsigned short int freeBlocks;
unsigned short int maxFreeContiguousBlocks;
unsigned short int blockSize;
unsigned short int numBlocks; /* configured, not counted */
}
MUMM_HEAP_INFO;
void *mumm_info( mumm_heap_handle heap, void *ptr, int force, MUMM_HEAP_INFO* info );
size_t mumm_free_heap_size( mumm_heap_handle heap );
#else
#endif
#define MUMM_CRITICAL_ENTRY(PLOCK) taskENTER_CRITICAL((portMUX_TYPE *)(PLOCK))
#define MUMM_CRITICAL_EXIT(PLOCK) taskEXIT_CRITICAL((portMUX_TYPE *)(PLOCK))
/*
* -D MUMM_INTEGRITY_CHECK :
*
* Enables heap integrity check before any heap operation. It affects
* performance, but does NOT consume extra memory.
*
* If integrity violation is detected, the message is printed and user-provided
* callback is called: `UMM_HEAP_CORRUPTION_CB()`
*
* Note that not all buffer overruns are detected: each buffer is aligned by
* 4 bytes, so there might be some trailing "extra" bytes which are not checked
* for corruption.
*/
//#define MUMM_INTEGRITY_CHECK
#ifdef MUMM_INTEGRITY_CHECK
int mumm_integrity_check( mumm_heap_handle heap );
# define INTEGRITY_CHECK(HEAP) mumm_integrity_check(HEAP)
extern void mumm_corruption(void);
# define MUMM_HEAP_CORRUPTION_CB(heap) printf( "Heap Corruption in heap %p!\n", heap )
#else
# define INTEGRITY_CHECK(HEAP) 0
#endif
/*
* -D MUMM_POISON :
*
* Enables heap poisoning: add predefined value (poison) before and after each
* allocation, and check before each heap operation that no poison is
* corrupted.
*
* Other than the poison itself, we need to store exact user-requested length
* for each buffer, so that overrun by just 1 byte will be always noticed.
*
* Customizations:
*
* UMM_POISON_SIZE_BEFORE:
* Number of poison bytes before each block, e.g. 2
* UMM_POISON_SIZE_AFTER:
* Number of poison bytes after each block e.g. 2
* UMM_POISONED_BLOCK_LEN_TYPE
* Type of the exact buffer length, e.g. `short`
*
* NOTE: each allocated buffer is aligned by 4 bytes. But when poisoning is
* enabled, actual pointer returned to user is shifted by
* `(sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE)`.
* It's your responsibility to make resulting pointers aligned appropriately.
*
* If poison corruption is detected, the message is printed and user-provided
* callback is called: `UMM_HEAP_CORRUPTION_CB()`
*/
//#define MUMM_POISON_CHECK
#define UMM_POISON_SIZE_BEFORE 4
#define UMM_POISON_SIZE_AFTER 4
#define UMM_POISONED_BLOCK_LEN_TYPE short
#ifdef MUMM_POISON_CHECK
void *mumm_poison_malloc( mumm_heap_handle heap, size_t size );
void *mumm_poison_calloc( mumm_heap_handle heap, size_t num, size_t size );
void *mumm_poison_realloc( mumm_heap_handle heap, void *ptr, size_t size );
void mumm_poison_free( mumm_heap_handle heap, void *ptr );
int mumm_poison_check( mumm_heap_handle heap );
# define POISON_CHECK(HEAP) mumm_poison_check(HEAP)
#else
# define POISON_CHECK(HEAP) 0
#endif
#endif /* _MUMM_MALLOC_CFG_H */

View file

@ -0,0 +1,594 @@
// 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 <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <multi_heap.h>
/* Note: Keep platform-specific parts in this header, this source
file should depend on libc only */
#include "multi_heap_platform.h"
#define ALIGN(X) ((X) & ~(sizeof(void *)-1))
#define ALIGN_UP(X) ALIGN((X)+sizeof(void *)-1)
struct heap_block;
/* Block in the heap
Heap implementation uses two single linked lists, a block list (all blocks) and a free list (free blocks).
'header' holds a pointer to the next block (used or free) ORed with a free flag (the LSB of the pointer.) is_free() and get_next_block() utility functions allow typed access to these values.
'next_free' is valid if the block is free and is a pointer to the next block in the free list.
*/
typedef struct heap_block {
intptr_t header; /* Encodes next block in heap (used or unused) and also free/used flag */
union {
uint8_t data[1]; /* First byte of data, valid if block is used. Actual size of data is 'block_data_size(block)' */
struct heap_block *next_free; /* Pointer to next free block, valid if block is free */
};
} heap_block_t;
/* These masks apply to the 'header' field of heap_block_t */
#define BLOCK_FREE_FLAG 0x1 /* If set, this block is free & next_free pointer is valid */
#define NEXT_BLOCK_MASK (~3) /* AND header with this mask to get pointer to next block (free or used) */
/* Metadata header for the heap, stored at the beginning of heap space.
'first_block' is a "fake" first block, minimum length, used to provide a pointer to the first used & free block in
the heap. This block is never allocated or merged into an adjacent block.
'last_block' is a pointer to a final free block of length 0, which is added at the end of the heap when it is
registered. This block is also never allocated or merged into an adjacent block.
*/
typedef struct multi_heap_info {
void *lock;
size_t free_bytes;
size_t minimum_free_bytes;
heap_block_t *last_block;
heap_block_t first_block; /* initial 'free block', never allocated */
} heap_t;
/* Given a pointer to the 'data' field of a block (ie the previous malloc/realloc result), return a pointer to the
containing block.
*/
static inline heap_block_t *get_block(const void *data_ptr)
{
return (heap_block_t *)((char *)data_ptr - offsetof(heap_block_t, data));
}
/* Return the next sequential block in the heap.
*/
static inline heap_block_t *get_next_block(const heap_block_t *block)
{
intptr_t next = block->header & NEXT_BLOCK_MASK;
if (next == 0) {
return NULL; /* last_block */
}
assert(next > (intptr_t)block);
return (heap_block_t *)next;
}
/* Return true if this block is free. */
static inline bool is_free(const heap_block_t *block)
{
return block->header & BLOCK_FREE_FLAG;
}
/* Return true if this block is the last_block in the heap
(the only block with no next pointer) */
static inline bool is_last_block(const heap_block_t *block)
{
return (block->header & NEXT_BLOCK_MASK) == 0;
}
/* Data size of the block (excludes this block's header) */
static inline size_t block_data_size(const heap_block_t *block)
{
intptr_t next = (intptr_t)block->header & NEXT_BLOCK_MASK;
intptr_t this = (intptr_t)block;
if (next == 0) {
return 0; /* this is the last block in the heap */
}
return next - this - sizeof(block->header);
}
/* Check a block is valid for this heap. Used to verify parameters. */
static void assert_valid_block(const heap_t *heap, const heap_block_t *block)
{
assert(block >= &heap->first_block && block <= heap->last_block); /* block should be in heap */
if (heap < (const heap_t *)heap->last_block) {
const heap_block_t *next = get_next_block(block);
assert(next >= &heap->first_block && next <= heap->last_block);
if (is_free(block)) {
assert(block->next_free >= &heap->first_block && block->next_free <= heap->last_block);
}
}
}
/* Get the first free block before 'block' in the heap. 'block' can be a free block or in use.
Result is always the closest free block to 'block' in the heap, that is located before 'block'. There may be multiple
allocated blocks between the result and 'block'.
If 'block' is free, the result's 'next_free' pointer will already point to 'block'.
Result will never be NULL, but it may be the header block heap->first_block.
*/
static heap_block_t *get_prev_free_block(heap_t *heap, const heap_block_t *block)
{
assert(block != &heap->first_block); /* can't look for a block before first_block */
for (heap_block_t *b = &heap->first_block; b != NULL && b < block; b = b->next_free) {
assert(is_free(b));
if (b->next_free == NULL || b->next_free >= block) {
if (is_free(block)) {
assert(b->next_free == block); /* if block is on freelist, 'b' should be the item before it. */
}
return b; /* b is the last free block before 'block' */
}
}
abort(); /* There should always be a previous free block, even if it's heap->first_block */
}
/* Merge some block 'a' into the following block 'b'.
If both blocks are free, resulting block is marked free.
If only one block is free, resulting block is marked in use. No data is moved.
This operation may fail if block 'a' is the first block or 'b' is the last block,
the caller should check block_data_size() to know if anything happened here or not.
*/
static heap_block_t *merge_adjacent(heap_t *heap, heap_block_t *a, heap_block_t *b)
{
assert(a < b);
/* Can't merge header blocks, just return the non-header block as-is */
if (is_last_block(b)) {
return a;
}
if (a == &heap->first_block) {
return b;
}
assert(get_next_block(a) == b);
bool free = is_free(a) && is_free(b); /* merging two free blocks creates a free block */
if (!free && (is_free(a) || is_free(b))) {
/* only one of these blocks is free, so resulting block will be a used block.
means we need to take the free block out of the free list
*/
heap_block_t *free_block = is_free(a) ? a : b;
heap_block_t *prev_free = get_prev_free_block(heap, free_block);
assert(free_block->next_free > prev_free);
prev_free->next_free = free_block->next_free;
heap->free_bytes -= block_data_size(free_block);
}
a->header = b->header & NEXT_BLOCK_MASK;
assert(a->header != 0);
if (free) {
a->header |= BLOCK_FREE_FLAG;
assert(b->next_free == NULL || b->next_free > a);
assert(b->next_free == NULL || b->next_free > b);
a->next_free = b->next_free;
/* b's header can be put into the pool of free bytes */
heap->free_bytes += sizeof(a->header);
}
return a;
}
/* Split a block so it can hold at least 'size' bytes of data, making any spare
space into a new free block.
'block' should be marked in-use when this function is called (implementation detail, this function
doesn't set the next_free pointer).
'prev_free_block' is the free block before 'block', if already known. Can be NULL if not yet known.
(This is a performance optimisation to avoid walking the freelist twice when possible.)
*/
static void split_if_necessary(heap_t *heap, heap_block_t *block, size_t size, heap_block_t *prev_free_block)
{
assert(!is_free(block)); /* split_if_necessary doesn't expect a free block */
assert(size <= block_data_size(block)); /* can't grow a block this way! */
size = ALIGN_UP(size);
/* can't split the head or tail block */
assert(block != &heap->first_block);
assert(!is_last_block(block));
if (block_data_size(block) < size + sizeof(heap_block_t)) {
/* Can't split 'block' if we're not going to get a usable free block afterwards */
return;
}
/* Block is larger than it needs to be, insert a new free block after it */
heap_block_t *new_block = (heap_block_t *)(block->data + size);
new_block->header = block->header | BLOCK_FREE_FLAG;
block->header = (intptr_t)new_block;
if (prev_free_block == NULL) {
prev_free_block = get_prev_free_block(heap, block);
}
assert(prev_free_block->next_free > new_block); /* prev_free_block should point to a free block after new_block */
new_block->next_free = prev_free_block->next_free;
prev_free_block->next_free = new_block;
heap->free_bytes += block_data_size(new_block);
}
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
{
heap_block_t *pb = get_block(p);
assert_valid_block(heap, pb);
assert(!is_free(pb));
return block_data_size(pb);
}
multi_heap_handle_t multi_heap_register(void *start, size_t size)
{
heap_t *heap = (heap_t *)ALIGN_UP((intptr_t)start);
uintptr_t end = ALIGN((uintptr_t)start + size);
if (end - (uintptr_t)start < sizeof(heap_t) + 2*sizeof(heap_block_t)) {
return NULL; /* 'size' is too small to fit a heap here */
}
heap->lock = NULL;
heap->last_block = (heap_block_t *)(end - sizeof(heap_block_t));
/* first 'real' (allocatable) free block goes after the heap structure */
heap_block_t *first_free_block = (heap_block_t *)((intptr_t)start + sizeof(heap_t));
first_free_block->header = (intptr_t)heap->last_block | BLOCK_FREE_FLAG;
first_free_block->next_free = heap->last_block;
/* last block is 'free' but has a NULL next pointer */
heap->last_block->header = BLOCK_FREE_FLAG;
heap->last_block->next_free = NULL;
/* first block also 'free' but has legitimate length,
malloc will never allocate into this block. */
heap->first_block.header = (intptr_t)first_free_block | BLOCK_FREE_FLAG;
heap->first_block.next_free = first_free_block;
/* free bytes is:
- total bytes in heap
- minus heap_t header at top (includes heap->first_block)
- minus header of first_free_block
- minus whole block at heap->last_block
*/
heap->free_bytes = ALIGN(size) - sizeof(heap_t) - sizeof(first_free_block->header) - sizeof(heap_block_t);
heap->minimum_free_bytes = heap->free_bytes;
return heap;
}
void multi_heap_set_lock(multi_heap_handle_t heap, void *lock)
{
heap->lock = lock;
}
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
{
heap_block_t *best_block = NULL;
heap_block_t *prev_free = NULL;
heap_block_t *prev = NULL;
size_t best_size = SIZE_MAX;
size = ALIGN_UP(size);
if (size == 0 || heap == NULL || heap->free_bytes < size) {
return NULL;
}
MULTI_HEAP_LOCK(heap->lock);
/* Find best free block to perform the allocation in */
prev = &heap->first_block;
for (heap_block_t *b = heap->first_block.next_free; b != NULL; b = b->next_free) {
assert(is_free(b));
size_t bs = block_data_size(b);
if (bs >= size && bs < best_size) {
best_block = b;
best_size = bs;
prev_free = prev;
if (bs == size) {
break; /* we've found a perfect sized block */
}
}
prev = b;
}
if (best_block == NULL) {
MULTI_HEAP_UNLOCK(heap->lock);
return NULL; /* No room in heap */
}
prev_free->next_free = best_block->next_free;
best_block->header &= ~BLOCK_FREE_FLAG;
heap->free_bytes -= block_data_size(best_block);
split_if_necessary(heap, best_block, size, prev_free);
if (heap->free_bytes < heap->minimum_free_bytes) {
heap->minimum_free_bytes = heap->free_bytes;
}
MULTI_HEAP_UNLOCK(heap->lock);
return best_block->data;
}
void multi_heap_free(multi_heap_handle_t heap, void *p)
{
heap_block_t *pb = get_block(p);
if (heap == NULL || p == NULL) {
return;
}
MULTI_HEAP_LOCK(heap->lock);
assert_valid_block(heap, pb);
assert(!is_free(pb));
assert(!is_last_block(pb));
assert(pb != &heap->first_block);
heap_block_t *next = get_next_block(pb);
/* Update freelist pointers */
heap_block_t *prev_free = get_prev_free_block(heap, pb);
assert(prev_free->next_free == NULL || prev_free->next_free > pb);
pb->next_free = prev_free->next_free;
prev_free->next_free = pb;
/* Mark this block as free */
pb->header |= BLOCK_FREE_FLAG;
heap->free_bytes += block_data_size(pb);
/* Try and merge previous free block into this one */
if (get_next_block(prev_free) == pb) {
pb = merge_adjacent(heap, prev_free, pb);
}
/* If next block is free, try to merge the two */
if (is_free(next)) {
pb = merge_adjacent(heap, pb, next);
}
MULTI_HEAP_UNLOCK(heap->lock);
}
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
{
heap_block_t *pb = get_block(p);
void *result;
size = ALIGN_UP(size);
assert(heap != NULL);
if (p == NULL) {
return multi_heap_malloc(heap, size);
}
assert_valid_block(heap, pb);
assert(!is_free(pb) && "realloc arg should be allocated");
if (size == 0) {
multi_heap_free(heap, p);
return NULL;
}
if (heap == NULL) {
return NULL;
}
MULTI_HEAP_LOCK(heap->lock);
result = NULL;
if (size <= block_data_size(pb)) {
// Shrinking....
split_if_necessary(heap, pb, size, NULL);
result = pb->data;
}
else if (heap->free_bytes < size - block_data_size(pb)) {
// Growing, but there's not enough total free space in the heap
MULTI_HEAP_UNLOCK(heap->lock);
return NULL;
}
// New size is larger than existing block
if (result == NULL) {
// See if we can grow into one or both adjacent blocks
heap_block_t *orig_pb = pb;
size_t orig_size = block_data_size(orig_pb);
heap_block_t *next = get_next_block(pb);
heap_block_t *prev = get_prev_free_block(heap, pb);
// Can only grow into the previous free block if it's adjacent
size_t prev_grow_size = (get_next_block(prev) == pb) ? block_data_size(prev) : 0;
// Can grow into next block? (we may also need to grow into 'prev' to get to our desired size)
if (is_free(next) && (block_data_size(pb) + block_data_size(next) + prev_grow_size >= size)) {
pb = merge_adjacent(heap, pb, next);
}
// Can grow into previous block?
// (do this even if we're already big enough from growing into 'next', as it reduces fragmentation)
if (prev_grow_size > 0 && (block_data_size(pb) + prev_grow_size >= size)) {
pb = merge_adjacent(heap, prev, pb);
assert(block_data_size(pb) >= size);
}
if (block_data_size(pb) >= size) {
memmove(pb->data, orig_pb->data, orig_size);
split_if_necessary(heap, pb, size, NULL);
result = pb->data;
}
}
if (result == NULL) {
// Need to allocate elsewhere and copy data over
result = multi_heap_malloc(heap, size);
if (result != NULL) {
memcpy(result, pb->data, block_data_size(pb));
multi_heap_free(heap, pb->data);
}
}
if (heap->free_bytes < heap->minimum_free_bytes) {
heap->minimum_free_bytes = heap->free_bytes;
}
MULTI_HEAP_UNLOCK(heap->lock);
return result;
}
#define FAIL_PRINT(MSG, ...) do { \
if (print_errors) { \
MULTI_HEAP_STDERR_PRINTF(MSG, __VA_ARGS__); \
} \
valid = false; \
} \
while(0)
bool multi_heap_check(multi_heap_handle_t heap, bool print_errors)
{
bool valid = true;
size_t total_free_bytes = 0;
assert(heap != NULL);
MULTI_HEAP_LOCK(heap->lock);
heap_block_t *prev = NULL;
heap_block_t *prev_free = NULL;
heap_block_t *expected_free = NULL;
/* note: not using get_next_block() in loop, so that assertions aren't checked here */
for(heap_block_t *b = &heap->first_block; b != NULL; b = (heap_block_t *)(b->header & NEXT_BLOCK_MASK)) {
if (b == prev) {
FAIL_PRINT("CORRUPT HEAP: Block %p points to itself\n", b);
goto done;
}
if (b < prev) {
FAIL_PRINT("CORRUPT HEAP: Block %p is before prev block %p\n", b, prev);
goto done;
}
if (b > heap->last_block || b < &heap->first_block) {
FAIL_PRINT("CORRUPT HEAP: Block %p is outside heap (last valid block %p)\n", b, prev);
goto done;
}
prev = b;
if (is_free(b)) {
if (expected_free != NULL && expected_free != b) {
FAIL_PRINT("CORRUPT HEAP: Prev free block %p pointed to next free %p but this free block is %p\n",
prev_free, expected_free, b);
}
prev_free = b;
expected_free = b->next_free;
if (b != &heap->first_block) {
total_free_bytes += block_data_size(b);
}
}
}
if (prev != heap->last_block) {
FAIL_PRINT("CORRUPT HEAP: Ended at %p not %p\n", prev, heap->last_block);
}
if (!is_free(heap->last_block)) {
FAIL_PRINT("CORRUPT HEAP: Expected prev block %p to be free\n", heap->last_block);
}
if (heap->free_bytes != total_free_bytes) {
FAIL_PRINT("CORRUPT HEAP: Expected %u free bytes counted %u\n", (unsigned)heap->free_bytes, (unsigned)total_free_bytes);
}
done:
MULTI_HEAP_UNLOCK(heap->lock);
return valid;
}
void multi_heap_dump(multi_heap_handle_t heap)
{
assert(heap != NULL);
MULTI_HEAP_LOCK(heap->lock);
printf("Heap start %p end %p\nFirst free block %p\n", &heap->first_block, heap->last_block, heap->first_block.next_free);
for(heap_block_t *b = &heap->first_block; b != NULL; b = get_next_block(b)) {
printf("Block %p data size 0x%08zx bytes next block %p", b, block_data_size(b), get_next_block(b));
if (is_free(b)) {
printf(" FREE. Next free %p\n", b->next_free);
} else {
printf("\n");
}
}
MULTI_HEAP_UNLOCK(heap->lock);
}
size_t multi_heap_free_heap_size(multi_heap_handle_t heap)
{
if (heap == NULL) {
return 0;
}
return heap->free_bytes;
}
size_t multi_heap_minimum_free_heap_size(multi_heap_handle_t heap)
{
if (heap == NULL) {
return 0;
}
return heap->minimum_free_bytes;
}
void multi_heap_get_heap_info(multi_heap_handle_t heap, multi_heap_info_t *info)
{
memset(info, 0, sizeof(multi_heap_info_t));
if (heap == NULL) {
return;
}
MULTI_HEAP_LOCK(heap->lock);
for(heap_block_t *b = get_next_block(&heap->first_block); !is_last_block(b); b = get_next_block(b)) {
info->total_blocks++;
if (is_free(b)) {
size_t s = block_data_size(b);
info->total_free_bytes += s;
if (s > info->largest_free_block) {
info->largest_free_block = s;
}
info->free_blocks++;
} else {
info->total_allocated_bytes += block_data_size(b);
info->allocated_blocks++;
}
}
info->minimum_free_bytes = heap->minimum_free_bytes;
assert(info->total_free_bytes == heap->free_bytes);
MULTI_HEAP_UNLOCK(heap->lock);
}

View file

@ -0,0 +1,50 @@
// 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.
#pragma once
#ifdef ESP_PLATFORM
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <rom/ets_sys.h>
/* Because malloc/free can happen inside an ISR context,
we need to use portmux spinlocks here not RTOS mutexes */
#define MULTI_HEAP_LOCK(PLOCK) do { \
if((PLOCK) != NULL) { \
taskENTER_CRITICAL((portMUX_TYPE *)(PLOCK)); \
} \
} while(0)
#define MULTI_HEAP_UNLOCK(PLOCK) do { \
if ((PLOCK) != NULL) { \
taskEXIT_CRITICAL((portMUX_TYPE *)(PLOCK)); \
} \
} while(0)
/* Not safe to use std i/o while in a portmux critical section,
can deadlock, so we use the ROM equivalent functions. */
#define MULTI_HEAP_PRINTF ets_printf
#define MULTI_HEAP_STDERR_PRINTF(MSG, ...) ets_printf(MSG, __VA_ARGS__)
#else
#define MULTI_HEAP_PRINTF printf
#define MULTI_HEAP_STDERR_PRINTF(MSG, ...) fprintf(stderr, MSG, __VA_ARGS__)
#define MULTI_HEAP_LOCK(PLOCK)
#define MULTI_HEAP_UNLOCK(PLOCK)
#endif

View file

@ -0,0 +1,48 @@
TEST_PROGRAM=test_multi_heap
all: $(TEST_PROGRAM)
SOURCE_FILES = $(abspath \
../multi_heap.c \
test_multi_heap.cpp \
main.cpp \
)
INCLUDE_FLAGS = -I../include -I../../../tools/catch
GCOV ?= gcov
CPPFLAGS += $(INCLUDE_FLAGS) -D CONFIG_LOG_DEFAULT_LEVEL -m32
CFLAGS += -fprofile-arcs -ftest-coverage
CXXFLAGS += -std=c++11 -Wall -Werror -fprofile-arcs -ftest-coverage
LDFLAGS += -lstdc++ -fprofile-arcs -ftest-coverage -m32
OBJ_FILES = $(filter %.o, $(SOURCE_FILES:.cpp=.o) $(SOURCE_FILES:.c=.o))
COVERAGE_FILES = $(OBJ_FILES:.o=.gc*)
$(TEST_PROGRAM): $(OBJ_FILES)
g++ $(LDFLAGS) -o $(TEST_PROGRAM) $(OBJ_FILES)
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
test: $(TEST_PROGRAM)
./$(TEST_PROGRAM)
$(COVERAGE_FILES): $(TEST_PROGRAM) test
coverage.info: $(COVERAGE_FILES)
find ../ -name "*.gcno" -exec $(GCOV) -r -pb {} +
lcov --capture --directory $(abspath ../) --no-external --output-file coverage.info --gcov-tool $(GCOV)
coverage_report: coverage.info
genhtml coverage.info --output-directory coverage_report
@echo "Coverage report is in coverage_report/index.html"
clean:
rm -f $(OBJ_FILES) $(TEST_PROGRAM)
rm -f $(COVERAGE_FILES) *.gcov
rm -rf coverage_report/
rm -f coverage.info
.PHONY: clean all test

View file

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

View file

@ -0,0 +1,347 @@
#include "catch.hpp"
#include "multi_heap.h"
#include <string.h>
/* Insurance against accidentally using libc heap functions in tests */
#undef free
#define free #error
#undef malloc
#define malloc #error
#undef calloc
#define calloc #error
#undef realloc
#define realloc #error
TEST_CASE("multi_heap simple allocations", "[multi_heap]")
{
uint8_t small_heap[128];
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
size_t test_alloc_size = (multi_heap_free_size(heap) + 4) / 2;
printf("New heap:\n");
multi_heap_dump(heap);
printf("*********************\n");
void *buf = multi_heap_malloc(heap, test_alloc_size);
printf("First malloc:\n");
multi_heap_dump(heap);
printf("*********************\n");
printf("small_heap %p buf %p\n", small_heap, buf);
REQUIRE( buf != NULL );
REQUIRE((intptr_t)buf >= (intptr_t)small_heap);
REQUIRE( (intptr_t)buf < (intptr_t)(small_heap + sizeof(small_heap)));
REQUIRE( multi_heap_get_allocated_size(heap, buf) >= test_alloc_size );
REQUIRE( multi_heap_get_allocated_size(heap, buf) < test_alloc_size + 16);
memset(buf, 0xEE, test_alloc_size);
REQUIRE( multi_heap_malloc(heap, test_alloc_size) == NULL );
multi_heap_free(heap, buf);
printf("Empty?\n");
multi_heap_dump(heap);
printf("*********************\n");
/* Now there should be space for another allocation */
buf = multi_heap_malloc(heap, test_alloc_size);
REQUIRE( buf != NULL );
multi_heap_free(heap, buf);
REQUIRE( multi_heap_free_size(heap) > multi_heap_minimum_free_size(heap) );
}
TEST_CASE("multi_heap fragmentation", "[multi_heap]")
{
uint8_t small_heap[200];
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
/* allocate enough that we can't fit 6 alloc_size blocks in the heap (due to
per-allocation block overhead. This calculation works for 32-bit pointers,
probably needs tweaking for 64-bit. */
size_t alloc_size = ((multi_heap_free_size(heap)) / 6) & ~(sizeof(void *) - 1);
printf("alloc_size %zu\n", alloc_size);
void *p[4];
for (int i = 0; i < 4; i++) {
multi_heap_dump(heap);
REQUIRE( multi_heap_check(heap, true) );
p[i] = multi_heap_malloc(heap, alloc_size);
printf("%d = %p ****->\n", i, p[i]);
multi_heap_dump(heap);
REQUIRE( p[i] != NULL );
}
printf("allocated %p %p %p %p\n", p[0], p[1], p[2], p[3]);
REQUIRE( multi_heap_malloc(heap, alloc_size * 3) == NULL ); /* no room to allocate 3*alloc_size now */
printf("4 allocations:\n");
multi_heap_dump(heap);
printf("****************\n");
multi_heap_free(heap, p[0]);
multi_heap_free(heap, p[1]);
multi_heap_free(heap, p[3]);
printf("1 allocations:\n");
multi_heap_dump(heap);
printf("****************\n");
void *big = multi_heap_malloc(heap, alloc_size * 3);
REQUIRE( p[3] == big ); /* big should go where p[3] was freed from */
multi_heap_free(heap, big);
multi_heap_free(heap, p[2]);
printf("0 allocations:\n");
multi_heap_dump(heap);
printf("****************\n");
big = multi_heap_malloc(heap, alloc_size * 2);
REQUIRE( p[0] == big ); /* big should now go where p[0] was freed from */
multi_heap_free(heap, big);
}
TEST_CASE("multi_heap many random allocations", "[multi_heap]")
{
uint8_t big_heap[1024];
const int NUM_POINTERS = 64;
void *p[NUM_POINTERS] = { 0 };
size_t s[NUM_POINTERS] = { 0 };
multi_heap_handle_t heap = multi_heap_register(big_heap, sizeof(big_heap));
const size_t initial_free = multi_heap_free_size(heap);
const int ITERATIONS = 100000;
for (int i = 0; i < ITERATIONS; i++) {
/* check all pointers allocated so far are valid inside big_heap */
for (int j = 0; j < NUM_POINTERS; j++) {
if (p[j] != NULL) {
}
}
uint8_t n = rand() % NUM_POINTERS;
if (rand() % 4 == 0) {
/* 1 in 4 iterations, try to realloc the buffer instead
of using malloc/free
*/
size_t new_size = rand() % 1024;
void *new_p = multi_heap_realloc(heap, p[n], new_size);
if (new_size == 0 || new_p != NULL) {
p[n] = new_p;
if (new_size > 0) {
REQUIRE( p[n] >= big_heap );
REQUIRE( p[n] < big_heap + sizeof(big_heap) );
}
s[n] = new_size;
memset(p[n], n, s[n]);
}
REQUIRE( multi_heap_check(heap, true) );
continue;
}
if (p[n] != NULL) {
if (s[n] > 0) {
/* Verify pre-existing contents of p[n] */
uint8_t compare[s[n]];
memset(compare, n, s[n]);
REQUIRE( memcmp(compare, p[n], s[n]) == 0 );
}
//printf("free %zu bytes %p\n", s[n], p[n]);
multi_heap_free(heap, p[n]);
if (!multi_heap_check(heap, true)) {
printf("FAILED iteration %d after freeing %p\n", i, p[n]);
multi_heap_dump(heap);
REQUIRE(0);
}
}
s[n] = rand() % 1024;
p[n] = multi_heap_malloc(heap, s[n]);
if (p[n] != NULL) {
REQUIRE( p[n] >= big_heap );
REQUIRE( p[n] < big_heap + sizeof(big_heap) );
}
if (!multi_heap_check(heap, true)) {
printf("FAILED iteration %d after mallocing %p (%zu bytes)\n", i, p[n], s[n]);
multi_heap_dump(heap);
REQUIRE(0);
}
if (p[n] != NULL) {
memset(p[n], n, s[n]);
}
}
for (int i = 0; i < NUM_POINTERS; i++) {
multi_heap_free(heap, p[i]);
if (!multi_heap_check(heap, true)) {
printf("FAILED during cleanup after freeing %p\n", p[i]);
multi_heap_dump(heap);
REQUIRE(0);
}
}
REQUIRE( initial_free == multi_heap_free_size(heap) );
}
TEST_CASE("multi_heap_get_info() function", "[multi_heap]")
{
uint8_t heapdata[256];
multi_heap_handle_t heap = multi_heap_register(heapdata, sizeof(heapdata));
multi_heap_info_t before, after, freed;
multi_heap_get_info(heap, &before);
printf("before: total_free_bytes %zu\ntotal_allocated_bytes %zu\nlargest_free_block %zu\nminimum_free_bytes %zu\nallocated_blocks %zu\nfree_blocks %zu\ntotal_blocks %zu\n",
before.total_free_bytes,
before.total_allocated_bytes,
before.largest_free_block,
before.minimum_free_bytes,
before.allocated_blocks,
before.free_blocks,
before.total_blocks);
REQUIRE( 0 == before.allocated_blocks );
REQUIRE( 0 == before.total_allocated_bytes );
REQUIRE( before.total_free_bytes == before.minimum_free_bytes );
void *x = multi_heap_malloc(heap, 32);
multi_heap_get_info(heap, &after);
printf("after: total_free_bytes %zu\ntotal_allocated_bytes %zu\nlargest_free_block %zu\nminimum_free_bytes %zu\nallocated_blocks %zu\nfree_blocks %zu\ntotal_blocks %zu\n",
after.total_free_bytes,
after.total_allocated_bytes,
after.largest_free_block,
after.minimum_free_bytes,
after.allocated_blocks,
after.free_blocks,
after.total_blocks);
REQUIRE( 1 == after.allocated_blocks );
REQUIRE( 32 == after.total_allocated_bytes );
REQUIRE( after.minimum_free_bytes < before.minimum_free_bytes);
REQUIRE( after.minimum_free_bytes > 0 );
multi_heap_free(heap, x);
multi_heap_get_info(heap, &freed);
printf("freed: total_free_bytes %zu\ntotal_allocated_bytes %zu\nlargest_free_block %zu\nminimum_free_bytes %zu\nallocated_blocks %zu\nfree_blocks %zu\ntotal_blocks %zu\n",
freed.total_free_bytes,
freed.total_allocated_bytes,
freed.largest_free_block,
freed.minimum_free_bytes,
freed.allocated_blocks,
freed.free_blocks,
freed.total_blocks);
REQUIRE( 0 == freed.allocated_blocks );
REQUIRE( 0 == freed.total_allocated_bytes );
REQUIRE( before.total_free_bytes == freed.total_free_bytes );
REQUIRE( after.minimum_free_bytes == freed.minimum_free_bytes );
}
TEST_CASE("multi_heap minimum-size allocations", "[multi_heap]")
{
uint8_t heapdata[16384];
void *p[sizeof(heapdata) / sizeof(void *)];
const size_t NUM_P = sizeof(p) / sizeof(void *);
multi_heap_handle_t heap = multi_heap_register(heapdata, sizeof(heapdata));
size_t before_free = multi_heap_free_size(heap);
size_t i;
for (i = 0; i < NUM_P; i++) {
p[i] = multi_heap_malloc(heap, 1);
if (p[i] == NULL) {
break;
}
}
REQUIRE( i < NUM_P); // Should have run out of heap before we ran out of pointers
printf("Allocated %zu minimum size chunks\n", i);
REQUIRE( 0 == multi_heap_free_size(heap) );
multi_heap_check(heap, true);
/* Free in random order */
bool has_allocations = true;
while (has_allocations) {
i = rand() % NUM_P;
multi_heap_free(heap, p[i]);
p[i] = NULL;
multi_heap_check(heap, true);
has_allocations = false;
for (i = 0; i < NUM_P && !has_allocations; i++) {
has_allocations = (p[i] != NULL);
}
}
/* all freed! */
REQUIRE( before_free == multi_heap_free_size(heap) );
}
TEST_CASE("multi_heap_realloc()", "[multi_heap]")
{
const uint32_t PATTERN = 0xABABDADA;
uint8_t small_heap[256];
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
uint32_t *a = (uint32_t *)multi_heap_malloc(heap, 64);
uint32_t *b = (uint32_t *)multi_heap_malloc(heap, 32);
REQUIRE( a != NULL );
REQUIRE( b != NULL );
REQUIRE( b > a); /* 'b' takes the block after 'a' */
*a = PATTERN;
uint32_t *c = (uint32_t *)multi_heap_realloc(heap, a, 72);
REQUIRE( multi_heap_check(heap, true));
REQUIRE( c != NULL );
REQUIRE( c > b ); /* 'a' moves, 'c' takes the block after 'b' */
REQUIRE( *c == PATTERN );
uint32_t *d = (uint32_t *)multi_heap_realloc(heap, c, 36);
REQUIRE( multi_heap_check(heap, true) );
REQUIRE( c == d ); /* 'c' block should be shrunk in-place */
REQUIRE( *d == PATTERN);
uint32_t *e = (uint32_t *)multi_heap_malloc(heap, 64);
REQUIRE( multi_heap_check(heap, true));
REQUIRE( a == e ); /* 'e' takes the block formerly occupied by 'a' */
multi_heap_free(heap, d);
uint32_t *f = (uint32_t *)multi_heap_realloc(heap, b, 64);
REQUIRE( multi_heap_check(heap, true) );
REQUIRE( f == b ); /* 'b' should be extended in-place, over space formerly occupied by 'd' */
uint32_t *g = (uint32_t *)multi_heap_realloc(heap, e, 128); /* not enough contiguous space left in the heap */
REQUIRE( g == NULL );
multi_heap_free(heap, f);
/* try again */
g = (uint32_t *)multi_heap_realloc(heap, e, 128);
REQUIRE( multi_heap_check(heap, true) );
REQUIRE( e == g ); /* 'g' extends 'e' in place, into the space formerly held by 'f' */
}
TEST_CASE("corrupt heap block", "[multi_heap]")
{
uint8_t small_heap[256];
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
void *a = multi_heap_malloc(heap, 32);
REQUIRE( multi_heap_check(heap, true) );
memset(a, 0xEE, 64);
REQUIRE( !multi_heap_check(heap, true) );
}

View file

@ -86,12 +86,14 @@ static struct syscall_stub_table s_stub_table = {
#endif
};
void *pvPortMalloc(size_t);
void esp_setup_syscall_table()
{
syscall_table_ptr_pro = &s_stub_table;
syscall_table_ptr_app = &s_stub_table;
_GLOBAL_REENT = &s_reent;
environ = malloc(sizeof(char*));
environ = pvPortMalloc(sizeof(char*));
environ[0] = NULL;
}

View file

@ -269,6 +269,18 @@
#define TICKS_PER_US_ROM 26 // CPU is 80MHz
//}}
/* Overall memory map */
#define SOC_IROM_LOW 0x400D0000
#define SOC_IROM_HIGH 0x40400000
#define SOC_IRAM_LOW 0x40080000
#define SOC_IRAM_HIGH 0x400A0000
#define SOC_DROM_LOW 0x3F400000
#define SOC_DROM_HIGH 0x3F800000
#define SOC_RTC_IRAM_LOW 0x400C0000
#define SOC_RTC_IRAM_HIGH 0x400C2000
#define SOC_RTC_DATA_LOW 0x50000000
#define SOC_RTC_DATA_HIGH 0x50002000
//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*/