401 lines
12 KiB
C++
401 lines
12 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 };




// Scans the status of the heap definition table, and returns the number of INITIALISED heaps.


int microbit_active_heaps()


{


int heapCount = 0;




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


{


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


heapCount++;


}




return heapCount;


}




#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(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;


}




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);




// Disable IRQ temporarily to ensure no race conditions!


__disable_irq();




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;


}




// Enable Interrupts


__enable_irq();




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 CONFIG_ENABLED(MICROBIT_HEAP_REUSE_SD)




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


#if CONFIG_ENABLED(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 MICROBIT_OK;


#else


return MICROBIT_NOT_SUPPORTED;


#endif


}




int


microbit_create_nested_heap(HeapDefinition &heap)


{


uint32_t mb_heap_max;


void *p;




// Ensure we're configured to use this heap at all. If not, we can safely return.


if (MICROBIT_HEAP_SIZE <= 0)


return MICROBIT_INVALID_PARAMETER;




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


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 MICROBIT_NO_RESOURCES;


}


}




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


microbit_initialise_heap(heap);




return MICROBIT_OK;


}




/**


* 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 standard mbed heap.


*/


int


microbit_heap_init()


{


int result;




// Disable IRQ temporarily to ensure no race conditions!


__disable_irq();




result = microbit_create_nested_heap(heap[0]);


if (result != MICROBIT_OK)


{


__enable_irq();


return MICROBIT_NO_RESOURCES;


}




result = microbit_create_sd_heap(heap[1]);


if (result != MICROBIT_OK)


{


__enable_irq();


return MICROBIT_NO_RESOURCES;


}




// Enable Interrupts


__enable_irq();




#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)


microbit_heap_print();


#endif




return MICROBIT_OK;


}




/**


* 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)


{


#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(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)


{


#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)


// Keep everything trasparent if we've not been initialised yet


if (microbit_active_heaps())


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


#endif


return p;


}




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


#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)


// Keep everything trasparent if we've not been initialised yet


if (microbit_active_heaps())


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


#endif




#if CONFIG_ENABLED(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;




#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)


if (microbit_active_heaps())


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);


}


