Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

NewMalloc/NewMalloc.cpp

Committer:
arnoz
Date:
2021-10-01
Revision:
116:7a67265d7c19
Parent:
79:682ae3171a08

File content as of revision 116:7a67265d7c19:

#include "mbed.h"
#include "NewMalloc.h"

extern void diagLED(int, int, int);

// Custom memory allocator.  We use our own version of malloc() for more
// efficient memory usage, and to provide diagnostics if we run out of heap.
//
// We can implement a more efficient malloc than the library can because we
// can make an assumption that the library can't: allocations are permanent.
// The normal malloc has to assume that allocations can be freed, so it has
// to track blocks individually.  For the purposes of this program, though,
// we don't have to do this because virtually all of our allocations are 
// de facto permanent.  We only allocate dyanmic memory during setup, and 
// once we set things up, we never delete anything.  This means that we can 
// allocate memory in bare blocks without any bookkeeping overhead.
//
// In addition, we can make a larger overall pool of memory available in
// a custom allocator.  The RTL malloc() seems to have a pool of about 3K 
// to work with, even though there really seems to be at least 8K left after 
// reserving a reasonable amount of space for the stack.

// halt with a diagnostic display if we run out of memory
void HaltOutOfMem()
{
    printf("\r\nOut Of Memory\r\n");
    // halt with the diagnostic display (by looping forever)
    for (;;)
    {
        diagLED(1, 0, 0);
        wait_us(200000);
        diagLED(1, 0, 1);
        wait_us(200000);
    }
}

// For our custom malloc, we take advantage of the known layout of the
// mbed library memory management.  The mbed library puts all of the
// static read/write data at the low end of RAM; this includes the
// initialized statics and the "ZI" (zero-initialized) statics.  The
// malloc heap starts just after the last static, growing upwards as
// memory is allocated.  The stack starts at the top of RAM and grows
// downwards.  
//
// To figure out where the free memory starts, we simply call the system
// malloc() to make a dummy allocation the first time we're called, and 
// use the address it returns as the start of our free memory pool.  The
// first malloc() call presumably returns the lowest byte of the pool in
// the compiler RTL's way of thinking, and from what we know about the
// mbed heap layout, we know everything above this point should be free,
// at least until we reach the lowest address used by the stack.
//
// The ultimate size of the stack is of course dynamic and unpredictable.
// In testing, it appears that we currently need a little over 1K.  To be
// conservative, we'll reserve 2K for the stack, by taking it out of the
// space at top of memory we consider fair game for malloc.
//
// Note that we could do this a little more low-level-ly if we wanted.
// The ARM linker provides a pre-defined extern char[] variable named 
// Image$$RW_IRAM1$$ZI$$Limit, which is always placed just after the
// last static data variable.  In principle, this tells us the start
// of the available malloc pool.  However, in testing, it doesn't seem
// safe to use this as the start of our malloc pool.  I'm not sure why,
// but probably something in the startup code (either in the C RTL or 
// the mbed library) is allocating from the pool before we get control. 
// So we won't use that approach.  Besides, that would tie us even more
// closely to the ARM compiler.  With our malloc() probe approach, we're
// at least portable to any compiler that uses the same basic memory
// layout, with the heap above the statics and the stack at top of 
// memory; this isn't universal, but it's very typical.

extern "C" {
    void *$Sub$$malloc(size_t);
    void *$Super$$malloc(size_t);
    void $Sub$$free(void *);
};

// override the system malloc
void *$Sub$$malloc(size_t siz)
{
    return xmalloc(siz);
}

// custom allocator pool
static char *xmalloc_nxt = 0;
size_t xmalloc_rem = 0;

// custom allocator
void *xmalloc(size_t siz)
{
    // initialize the pool if we haven't already
    if (xmalloc_nxt == 0)
    {
        // do a dummy allocation with the system malloc() to find where
        // the free pool starts
        xmalloc_nxt = (char *)$Super$$malloc(4);
        
        // figure the amount of space we can use - we have from the base
        // of the pool to the top of RAM, minus an allowance for the stack
        const uint32_t TopOfRAM = 0x20003000UL;
        const uint32_t StackSize = 2*1024;
        xmalloc_rem = TopOfRAM - StackSize - uint32_t(xmalloc_nxt);
    }
    
    // align to a dword boundary
    siz = (siz + 3) & ~3;
    
    // make sure we have enough space left for this chunk
    if (siz > xmalloc_rem)
        HaltOutOfMem();
        
    // carve the chunk out of the remaining free pool
    char *ret = xmalloc_nxt;
    xmalloc_nxt += siz;
    xmalloc_rem -= siz;
    
    // return the allocated space
    return ret;
}

// Remaining free memory
size_t mallocBytesFree() 
{
    return xmalloc_rem;
}