361 lines
10 KiB
C++
361 lines
10 KiB
C++

#include "MicroBit.h"






/**



* A simple 32 bit block based memory allocator. This allows one or more memory segments to



* be designated as heap storage, and is designed to run in a static memory area or inside the standard C



* heap for use by the micro:bit runtime. This is required for several reasons:



*



* 1) It reduces memory fragmentation due to the high churn sometime placed on the heap



* by ManagedTypes, fibers and user code. Underlying heap implentations are often have very simplistic



* allocation pilicies and suffer from fragmentation in prolonged use  which can cause programs to



* stop working after a period of time. The algorithm implemented here is simple, but highly tolerant to



* large amounts of churn.



*



* 2) It allows us to reuse the 8K of SRAM set aside for SoftDevice as additional heap storage



* when BLE is not in use.



*



* 3) It gives a simple example of how memory allocation works! :)



*



* N.B. The need for this should be reviewed in the future, should a different memory allocator be



* made availiable in the mbed platform.



*



* P.S. This is a very simple allocator, therefore not without its weaknesses. Why don't you consider



* what these are, and consider the tradeoffs against simplicity...



*



* TODO: Consider caching recently freed blocks to improve allocation time.



*/



struct HeapDefinition



{



uint32_t *heap_start; // Physical address of the start of this heap.



uint32_t *heap_end; // Physical address of the end of this heap.



};






// Create the necessary heap definitions.



// We use two heaps by default: one for SoftDevice reuse, and one to run inside the mbed heap.



HeapDefinition heap[MICROBIT_HEAP_COUNT] = {NULL};









#if defined(MICROBIT_DBG) && defined(MICROBIT_HEAP_DBG)



// Internal diagnostics function.



// Diplays a usage summary about a given heap...



void microbit_heap_print(HeapDefinition &heap)



{



uint32_t blockSize;



uint32_t *block;



int totalFreeBlock = 0;



int totalUsedBlock = 0;



int cols = 0;






if (heap.heap_start == NULL)



{



pc.printf(" HEAP NOT INITIALISED \n");



return;



}






// Disable IRQ temporarily to ensure no race conditions!



__disable_irq();






pc.printf("heap_start : %p\n", heap.heap_start);



pc.printf("heap_end : %p\n", heap.heap_end);



pc.printf("heap_size : %d\n", (int)heap.heap_end  (int)heap.heap_start);






block = heap.heap_start;



while (block < heap.heap_end)



{



blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE;



pc.printf("[%C:%d] ", *block & MICROBIT_HEAP_BLOCK_FREE ? 'F' : 'U', blockSize*4);



if (cols++ == 20)



{



pc.printf("\n");



cols = 0;



}






if (*block & MICROBIT_HEAP_BLOCK_FREE)



totalFreeBlock += blockSize;



else



totalUsedBlock += blockSize;






block += blockSize;



}



pc.printf("\n");






pc.printf("mb_total_free : %d\n", totalFreeBlock*4);



pc.printf("mb_total_used : %d\n", totalUsedBlock*4);



}









// Internal diagnostics function.



// Diplays a usage summary about all known heaps...



void microbit_heap_print()



{



for (int i=0; i < MICROBIT_HEAP_COUNT; i++)



{



pc.printf("\nHEAP %d: \n", i);



microbit_heap_print(heap[i]);



}



}






#endif






void microbit_initialise_heap(HeapDefinition &heap)



{



// Simply mark the entire heap as free.



*heap.heap_start = ((uint32_t) heap.heap_end  (uint32_t) heap.heap_start) / MICROBIT_HEAP_BLOCK_SIZE;



*heap.heap_start = MICROBIT_HEAP_BLOCK_FREE;



}






int



microbit_create_sd_heap(HeapDefinition &heap)



{



#if !defined(MICROBIT_HEAP_REUSE_SD)



// We're not configure to use memory of this sort.



return 0;



#endif






// OK, see how much of the RAM assigned to Soft Device we can reclaim.



#ifdef MICROBIT_BLE_ENABLED



heap.heap_start = (uint32_t *)MICROBIT_HEAP_BASE_BLE_ENABLED;



heap.heap_end = (uint32_t *)MICROBIT_HEAP_SD_LIMIT;



#else



heap.heap_start = (uint32_t *)MICROBIT_HEAP_BASE_BLE_DISABLED;



heap.heap_end = (uint32_t *)MICROBIT_HEAP_SD_LIMIT;



#endif






microbit_initialise_heap(heap);



return 1;



}






int



microbit_create_nested_heap(HeapDefinition &heap)



{



uint32_t mb_heap_max;






// Snapshot something at the top of the main heap.



void *p = native_malloc(sizeof(uint32_t));






// Compute the size left in our heap, taking care to ensure it lands on a word boundary.



mb_heap_max = (uint32_t) (((float)(MICROBIT_HEAP_END  (uint32_t)p)) * MICROBIT_HEAP_SIZE);



mb_heap_max &= 0xFFFFFFFC;






// Release our reference pointer.



native_free(p);






// Allocate memory for our heap.



// We do this iteratively, as some build configurations seem to have static limits



// on heap size... This allows us to be keep going anyway!



while (heap.heap_start == NULL)



{



heap.heap_start = (uint32_t *)native_malloc(mb_heap_max);



if (heap.heap_start == NULL)



{



mb_heap_max = 32;



if (mb_heap_max <= 0)



return 0;



}



}






heap.heap_end = heap.heap_start + mb_heap_max / MICROBIT_HEAP_BLOCK_SIZE;



microbit_initialise_heap(heap);






return 1;



}






/**



* Initialise the microbit heap according to the parameters defined in MicroBitConfig.h



* After this is called, any future calls to malloc, new, free or delete will use the new heap.



* n.b. only code that #includes MicroBitHeapAllocator.h will use this heap. This includes all micro:bit runtime



* code, and user code targetting the runtime. External code can choose to include this file, or



* simply use the strandard mbed heap.



*/



int



microbit_heap_init()



{



int r = 0;






// Disable IRQ temporarily to ensure no race conditions!



__disable_irq();






r += microbit_create_nested_heap(heap[0]);



r += microbit_create_sd_heap(heap[1]);






// Enable Interrupts



__enable_irq();






#if defined(MICROBIT_DBG) && defined(MICROBIT_HEAP_DBG)



microbit_heap_print();



#endif



return r;



}






/**



* Attempt to allocate a given amount of memory from the given heap.



* @param size The amount of memory, in bytes, to allocate.



* @param heap The heap the memory is to be allocated from.



* @return A pointer to the allocated memory, or NULL if insufficient memory is available.



*/



void *microbit_malloc(size_t size, HeapDefinition &heap)



{



uint32_t blockSize = 0;



uint32_t blocksNeeded = size % MICROBIT_HEAP_BLOCK_SIZE == 0 ? size / MICROBIT_HEAP_BLOCK_SIZE : size / MICROBIT_HEAP_BLOCK_SIZE + 1;



uint32_t *block;



uint32_t *next;






if (size <= 0)



return NULL;






// Account for the index block;



blocksNeeded++;






// Disable IRQ temporarily to ensure no race conditions!



__disable_irq();






// We implement a first fit algorithm with cache to handle rapid churn...



// We also defragment free blocks as we search, to optimise this and future searches.



block = heap.heap_start;



while (block < heap.heap_end)



{



// If the block is used, then keep looking.



if(!(*block & MICROBIT_HEAP_BLOCK_FREE))



{



block += *block;



continue;



}






blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE;






// We have a free block. Let's see if the subsequent ones are too. If so, we can merge...



next = block + blockSize;






while (*next & MICROBIT_HEAP_BLOCK_FREE)



{



if (next >= heap.heap_end)



break;






// We can merge!



blockSize += (*next & ~MICROBIT_HEAP_BLOCK_FREE);



*block = blockSize  MICROBIT_HEAP_BLOCK_FREE;






next = block + blockSize;



}






// We have a free block. Let's see if it's big enough.



// If so, we have a winner.



if (blockSize >= blocksNeeded)



break;






// Otherwise, keep looking...



block += blockSize;



}






// We're full!



if (block >= heap.heap_end)



{



__enable_irq();



return NULL;



}






// If we're at the end of memory or have very near match then mark the whole segment as in use.



if (blockSize <= blocksNeeded+1  block+blocksNeeded+1 >= heap.heap_end)



{



// Just mark the whole block as used.



*block &= ~MICROBIT_HEAP_BLOCK_FREE;



}



else



{



// We need to split the block.



uint32_t *splitBlock = block + blocksNeeded;



*splitBlock = blockSize  blocksNeeded;



*splitBlock = MICROBIT_HEAP_BLOCK_FREE;






*block = blocksNeeded;



}






// Enable Interrupts



__enable_irq();






return block+1;



}






/**



* Attempt to allocate a given amount of memory from any of our configured heap areas.



* @param size The amount of memory, in bytes, to allocate.



* @return A pointer to the allocated memory, or NULL if insufficient memory is available.



*/



void *microbit_malloc(size_t size)



{



void *p;






// Assign the memory from the first heap created that has space.



for (int i=0; i < MICROBIT_HEAP_COUNT; i++)



{



if(heap[i].heap_start != NULL)



{



p = microbit_malloc(size, heap[i]);



if (p != NULL)



{



#ifdef MICROBIT_HEAP_DBG



pc.printf("microbit_malloc: ALLOCATED: %d [%p]\n", size, p);



#endif



return p;



}



}



}






// If we reach here, then either we have no memory available, or our heap spaces



// haven't been initialised. Either way, we try the native allocator.






p = native_malloc(size);



if (p!= NULL)



{



#ifdef MICROBIT_HEAP_DBG



pc.printf("microbit_malloc: NATIVE ALLOCATED: %d [%p]\n", size, p);



#endif



return p;



}






// We're totally out of options (and memory!).



#ifdef MICROBIT_HEAP_DBG



pc.printf("microbit_malloc: OUT OF MEMORY\n");



#endif






#ifdef MICROBIT_PANIC_HEAP_FULL



panic(MICROBIT_OOM);



#endif






return NULL;



}






/**



* Release a given area of memory from the heap.



* @param mem The memory area to release.



*/



void microbit_free(void *mem)



{



uint32_t *memory = (uint32_t *)mem;



uint32_t *cb = memory1;






#ifdef MICROBIT_HEAP_DBG



pc.printf("microbit_free: %p\n", mem);



#endif



// Sanity check.



if (memory == NULL)



return;






// If this memory was created from a heap registered with us, free it.



for (int i=0; i < MICROBIT_HEAP_COUNT; i++)



{



if(memory > heap[i].heap_start && memory < heap[i].heap_end)



{



// The memory block given is part of this heap, so we can simply



// flag that this memory area is now free, and we're done.



*cb = MICROBIT_HEAP_BLOCK_FREE;



return;



}



}






// If we reach here, then the memory is not part of any registered heap.



// Forward it to the native heap allocator, and let nature take its course...



native_free(mem);



}



