https://bitbucket.org/daniel_fort/magic-lantern
Raw File
Tip revision: 359195a96dace42d263686ee248c04e77f7dcbc1 authored by daniel_fort on 02 December 2016, 16:09:37 UTC
closed unified branch head 2b90b7a
Tip revision: 359195a
mem.c
/**
 * Memory management routines that can wrap all malloc-like memory backends in a transparent way.
 * 
 * Instead of a few small allocation functions, from user code you will only see a huge memory pool, ready to use.
 * 
 * Temporary hack: in order to work with existing code, 
 * it intercepts calls to malloc/free, AllocateMemory, alloc_dma_memory, shoot_malloc and SmallAlloc.
 * 
 * Implementation based on memcheck by g3gg0.
 */
#define NO_MALLOC_REDIRECT

#undef MEM_DEBUG        /* define this one to print a log about who is allocating what */

#include "compiler.h"
#include "tasks.h"
#include "limits.h"
#include "bmp.h"
#include "beep.h"
#include "menu.h"
#include "edmac.h"
#include "util.h"

#ifdef MEM_DEBUG
#define dbg_printf(fmt,...) { printf(fmt, ## __VA_ARGS__); }
#else
#define dbg_printf(fmt,...) {}
#endif

#define MEM_SEC_ZONE 32
#define MEMCHECK_ENTRIES 256
#define HISTORY_ENTRIES 256
#define TASK_NAME_SIZE 12

#define JUST_FREED 0xF12EEEED   /* FREEED */
#define UNTRACKED 0xFFFFFFFF

/* used for faking the cacheable flag (internally we must use the same flag as returned by allocator) */
#define UNCACHEABLE_FLAG 0x8000

typedef void* (*mem_init_func)();
typedef void* (*mem_alloc_func)(size_t size);
typedef void (*mem_free_func)(void* ptr);
typedef int (*mem_get_free_space_func)();
typedef int (*mem_get_max_region_func)();

/* use underscore for allocator functions to prevent other code from calling them directly */
extern void* _malloc(size_t size);
extern void  _free(void* ptr);
extern void* _AllocateMemory(size_t size);
extern void  _FreeMemory(void* ptr);
extern void* _alloc_dma_memory(size_t size);
extern void  _free_dma_memory(void* ptr);
extern int   _shoot_get_free_space();
extern struct memSuite *_shoot_malloc_suite(size_t size);
extern void  _shoot_free_suite(struct memSuite * hSuite);
extern struct memSuite * _shoot_malloc_suite_contig(size_t size);
extern void* _shoot_malloc( size_t len );
extern void  _shoot_free( void * buf );

/* wrappers for the selftest module, not to be used in other code */
#ifndef CONFIG_INSTALLER
void* __priv_malloc(size_t size)           { return _malloc(size);           }
void  __priv_free(void* ptr)               { _free(ptr);                     }
void* __priv_AllocateMemory(size_t size)   { return _AllocateMemory(size);   }
void  __priv_FreeMemory(void* ptr)         { _FreeMemory(ptr);               }
void* __priv_alloc_dma_memory(size_t size) { return _alloc_dma_memory(size); }
void  __priv_free_dma_memory(void* ptr)    { _free_dma_memory(ptr);          }
void* __priv_shoot_malloc(size_t size)     { return _shoot_malloc(size);     }
void  __priv_shoot_free(void* ptr)         { _shoot_free(ptr);               }
#endif

static struct semaphore * mem_sem = 0;

struct mem_allocator
{
    char name[16];                          /* malloc, AllocateMemory, shoot_malloc, task_mem... */
    mem_init_func init;                     /* can be null; called at startup, before all other INIT_FUNCs */
    mem_alloc_func malloc;
    mem_free_func free;
    mem_alloc_func malloc_dma;              /* optional, if you can allocate uncacheable (DMA) memory from here */
    mem_free_func free_dma;
    mem_get_free_space_func get_free_space; /* can be null; will never try to malloc more than MAX(returned value / 2, get_max_region()) */
    mem_get_max_region_func get_max_region; /* can be null; will never try to malloc more than returned value */
    
    int is_preferred_for_temporary_space;   /* prefer using this for memory that will be freed shortly after used */
    
    int preferred_min_alloc_size;           /* if size is outside this range, it will try from other allocators */
    int preferred_max_alloc_size;           /* (but if it can't find any, it may still use this buffer) */
    int preferred_free_space;               /* if free space would drop under this, will try from other allocators first */
    int minimum_free_space;                 /* will never allocate if free space would drop under this */
    int minimum_alloc_size;                 /* will never allocate a buffer smaller than this */
    int maximum_blocks;                     /* will never allocate more than N buffers */
    
    /* private stuff */
    int mem_used;
    int num_blocks;
};

/* Canon stubs */
extern int GetMemoryInformation(int* total, int* free);
extern int GetSizeOfMaxRegion(int* max_region);

int GetFreeMemForAllocateMemory()
{
    int a,b;
    GetMemoryInformation(&a,&b);
    return b;
}

static int GetMaxRegionForAllocateMemory()
{
    int a;
    int err = GetSizeOfMaxRegion(&a);
    if (err) return 0;
    return a;
}

int GetFreeMemForMalloc()
{
    return MALLOC_FREE_MEMORY;
}

static struct mem_allocator allocators[] = {
    {
        .name = "malloc",
        .malloc = _malloc,
        .free = _free,
        .get_free_space = GetFreeMemForMalloc,
        .preferred_min_alloc_size = 0,
        .preferred_max_alloc_size = 512 * 1024,
        .preferred_free_space = 128 * 1024,
        .minimum_free_space = 64 * 1024,
    },

    {
        .name = "AllocateMemory",
        .malloc = _AllocateMemory,
        .free = _FreeMemory,
        .malloc_dma = _alloc_dma_memory,
        .free_dma = _free_dma_memory,
        .get_free_space = GetFreeMemForAllocateMemory,
        .get_max_region = GetMaxRegionForAllocateMemory,
        .preferred_min_alloc_size = 0,
        .preferred_max_alloc_size = 512 * 1024,
        .preferred_free_space = 1024 * 1024 * 3/2,  /* at 1MB free, "dispcheck" may stop working */
        #ifdef CONFIG_1100D
        .minimum_free_space = 384 * 1024,
        #else
        .minimum_free_space = 512 * 1024,           /* Canon code also allocates from here, so keep it free */
        #endif
    },
#ifndef CONFIG_INSTALLER    /* installer only needs the basic allocators */


#if 0 /* not implemented yet */
    {
        .name = "RscMgr",
        .malloc = _rscmgr_malloc,
        .free = _rscmgr_free,
        .get_free_space = rscmgr_get_free_space,
        .preferred_min_alloc_size = 16 * 1024,
        .preferred_max_alloc_size = 2 * 1024 * 1024,
    },
#endif

#if 0 /* not implemented yet */
    {
        .name = "task_mem",
        .malloc = _task_malloc,
        .free = _task_free,
        .get_free_space = task_get_free_space,
        .preferred_min_alloc_size = 64 * 1024,
        .preferred_max_alloc_size = 512 * 1024,
    },
#endif

#if !defined(CONFIG_QEMU)
    /* must be completely free when navigating Canon menus, so only use it as a last resort */
    {
        .name = "shoot_malloc",
        .malloc = _shoot_malloc,
        .free = _shoot_free,
        .malloc_dma = _shoot_malloc,       /* can be used for both cacheable and uncacheable memory */
        .free_dma = _shoot_free,
        .get_free_space = _shoot_get_free_space,
        .get_max_region = _shoot_get_free_space,    /* we usually have a bunch of large contiguous chunks */
        
        .is_preferred_for_temporary_space = 1,  /* if we know we'll free this memory quickly, prefer this one */

        /* 5D3 crashes at around 1300 calls to AllocateContinuousMemoryResource, no idea about others */
        .maximum_blocks = 1000,
        
        /* no free space check yet; just assume it's BIG */
        .preferred_min_alloc_size = 512 * 1024,
        .preferred_max_alloc_size = 20 * 1024 * 1024,
        .minimum_alloc_size = 5 * 1024,
    },
#endif

#if 1
    /* large buffers (30-40 MB), but you can't even take a picture with one of those allocated */
    {
        .name = "srm_malloc",
        .malloc = _srm_malloc,
        .free = _srm_free,
        .malloc_dma = _srm_malloc,       /* can be used for both cacheable and uncacheable memory */
        .free_dma = _srm_free,
        .get_free_space = _srm_get_free_space,
        .get_max_region = _srm_get_max_region,

        .is_preferred_for_temporary_space = 2,  /* prefer not to use it, use shoot_malloc if you can */

        /* only use it for huge buffers */
        .minimum_alloc_size = 20 * 1024 * 1024,
    },
#endif
#endif  /* CONFIG_INSTALLER */
};

/* total memory allocated (for printing it) */
static volatile int alloc_total = 0;

/* total memory allocated, including memcheck overhead */
static volatile int alloc_total_with_memcheck = 0;

/* peak memory allocated (max value since power on), including memcheck overhead */
static volatile int alloc_total_peak_with_memcheck = 0;

/* to show a graph with memory usage */
struct mem_history_node
{
    int timestamp;
    int alloc_total;
};

static struct mem_history_node history[HISTORY_ENTRIES];
static int history_index = 0;

struct memcheck_hdr
{
    unsigned int id;
    unsigned int length;
    uint16_t allocator;
    uint16_t flags;
};

struct memcheck_entry
{
    unsigned int ptr;
    char * file;
    uint16_t failed;
    uint16_t line;
    char task_name[TASK_NAME_SIZE];
};

static struct memcheck_entry memcheck_mallocbuf[MEMCHECK_ENTRIES];
static unsigned int memcheck_bufpos = 0;

static volatile int last_error = 0;
static char last_error_msg_short[20] = "";
static char last_error_msg[100] = "";

static char* file_name_without_path(const char* file)
{
    /* only show the file name, not full path */
    char* fn = (char*)file + strlen(file) - 1;
    while (fn > file && *(fn-1) != '/') fn--;
    return fn;
}

/* warning: can't call this twice in the same printf */
const char * format_memory_size(uint64_t size)
{
    static char str[16];
    
    const uint32_t kB = 1024;
    const uint32_t MB = 1024*1024;
    const uint64_t GB = 1024*1024*1024;
    
    if (size >= 10*GB)
    {
        int size_gb = (size + GB/2) / GB;
        snprintf( str, sizeof(str), "%dGB", size_gb);
    }
    else if ( size >= GB)
    {
        int size_gb10 = (size * 10 + GB/2) / GB;
        snprintf( str, sizeof(str), "%d.%dGB", size_gb10/10, size_gb10%10);
    }
    else if ( size >= 10*MB )
    {
        int size_mb = ((int) size + MB/2) / MB;
        snprintf( str, sizeof(str), "%dMB", size_mb);
    }
    else if ( size >= MB )
    {
        int size_mb10 = ((int) size * 10 + MB/2) / MB;
        snprintf( str, sizeof(str), "%d.%dMB", size_mb10/10, size_mb10%10);
    }
    else if ( size >= 10*kB )
    {
        int size_kb = ((int) size + kB/2) / kB;
        snprintf( str, sizeof(str), "%dkB", size_kb);
    }
    else if ( size >= kB )
    {
        int size_kb10 = ((int) size * 10 + kB/2) / kB;
        snprintf( str, sizeof(str), "%d.%dkB", size_kb10/10, size_kb10%10);
    }
    else if (size > 0)
    {
        snprintf( str, sizeof(str), "%d B", (int) size);
    }
    else
    {
        snprintf( str, sizeof(str), "0");
    }

    return str;
}

static const char * format_memory_size_and_flags( unsigned size, unsigned flags)
{
    static char str[32];
    snprintf(str, sizeof(str), format_memory_size(size));
    if (flags & MEM_TEMPORARY) STR_APPEND(str, "|TMP");
    if (flags & MEM_DMA) STR_APPEND(str, "|DMA");
    return str;
}

/* second arg is optional, -1 if not available */
static unsigned int memcheck_check(unsigned int ptr, unsigned int entry)
{
    unsigned int failed = 0;
    unsigned int failed_pos = 0;
    
    for(int pos = sizeof(struct memcheck_hdr); pos < MEM_SEC_ZONE; pos++)
    {
        unsigned char value = ((unsigned char *)ptr)[pos];
        // dbg_printf("free check %d %x\n ", pos, value);
        if (value != 0xA5)
        {
            failed |= 2;
            failed_pos = pos;
        }
    }
    for(int pos = 0; pos < MEM_SEC_ZONE; pos++)
    {
        int pos2 = MEM_SEC_ZONE + ((struct memcheck_hdr *)ptr)->length + pos;
        unsigned char value = ((unsigned char *)ptr)[pos2];
        // dbg_printf("free check %d %x\n ", pos2, value);
        if (value != 0xA5)
        {
            failed |= 4;
            failed_pos = pos2;
        }
    }

    unsigned int id = ((struct memcheck_hdr *)ptr)->id;
    int id_ok = 0;

    if (id == UNTRACKED)
    {
        /* we no longer keep track of this block => nothing to check */
    }
    else if (id == JUST_FREED) /* already freed? */
    {
        failed |= 16;
    }
    else if (id != entry && entry != 0xFFFFFFFF) /* wrong ID? */
    {
        failed |= 8;
    }
    else if (id >= MEMCHECK_ENTRIES) /* out of range? */
    {
        failed |= 8;
    }
    else /* ID looks alright */
    {
        id_ok = 1;
    }

    if (failed && !last_error)
    {
        last_error = failed;

        int flags = ((struct memcheck_hdr *)ptr)->flags;
        int size = ((struct memcheck_hdr *)ptr)->length;
        int allocator = ((struct memcheck_hdr *)ptr)->allocator;

        char* file = "unk";
        int line = 0;
        char* task_name = "unk";
        char* allocator_name = "unk";
        if (id_ok)
        {
            file = (char*) memcheck_mallocbuf[id].file;
            line = memcheck_mallocbuf[id].line;
            task_name = memcheck_mallocbuf[id].task_name;
        }
        else
        {
            task_name = get_task_name_from_id((int)get_current_task());
        }
        
        if (allocator >= 0 && allocator < COUNT(allocators))
        {
            allocator_name = allocators[allocator].name;
        }

        char err_flags[20] = "";
        if (failed & 2) STR_APPEND(err_flags, "underflow,");
        if (failed & 4) STR_APPEND(err_flags, "overflow,");
        if (failed & 8) STR_APPEND(err_flags, "ID error,");
        if (failed & 16) STR_APPEND(err_flags, "double free,");
        if (failed & ~(2|4|8|16)) STR_APPEND(err_flags, "unknown error,");
        err_flags[strlen(err_flags)-1] = 0;
        int index_err = (failed & 6);
        snprintf(last_error_msg_short, sizeof(last_error_msg_short), err_flags);
        snprintf(last_error_msg, sizeof(last_error_msg),
            "%s[%d/%d] %s(%s) at %s:%d, task %s.",
            err_flags, index_err ? failed_pos - MEM_SEC_ZONE : 0, index_err ? size : 0,
            allocator_name, format_memory_size_and_flags(size, flags),
            file, line, task_name
        );
    }
    
    if (entry < COUNT(memcheck_mallocbuf))
    {
        memcheck_mallocbuf[entry].failed |= failed;
    }
    
    return failed;
}

static unsigned int memcheck_get_failed()
{
    unsigned int buf_pos = 0;
    
    for(buf_pos = 0; buf_pos < MEMCHECK_ENTRIES; buf_pos++)
    {
        if(memcheck_mallocbuf[buf_pos].ptr)
        {
            memcheck_check(memcheck_mallocbuf[buf_pos].ptr, buf_pos);
            
            /* marked as failed? */
            if(memcheck_mallocbuf[buf_pos].failed)
            {
                return memcheck_mallocbuf[buf_pos].failed;
            }
        }
    }
    return 0;
}

static void memcheck_add(unsigned int ptr, const char *file, unsigned int line)
{
    int tries = MEMCHECK_ENTRIES;
    
    unsigned int state = cli();
    while(memcheck_mallocbuf[memcheck_bufpos].ptr != 0)
    {
        memcheck_bufpos++;
        memcheck_bufpos %= MEMCHECK_ENTRIES;
        
        if(--tries <= 0)
        {
            ((struct memcheck_hdr *)ptr)->id = UNTRACKED;
            sei(state);
            return;
        }
    }

    memcheck_mallocbuf[memcheck_bufpos].ptr = ptr;
    memcheck_mallocbuf[memcheck_bufpos].failed = 0;
    memcheck_mallocbuf[memcheck_bufpos].file = file_name_without_path(file);
    memcheck_mallocbuf[memcheck_bufpos].line = line;
    snprintf((char*)memcheck_mallocbuf[memcheck_bufpos].task_name, TASK_NAME_SIZE, "%s", get_task_name_from_id((int)get_current_task()));
    
    ((struct memcheck_hdr *)ptr)->id = memcheck_bufpos;
    
    sei(state);
}

static void memcheck_remove(unsigned int ptr, unsigned int failed)
{
    unsigned int buf_pos = ((struct memcheck_hdr *)ptr)->id;
    ((struct memcheck_hdr *)ptr)->id = JUST_FREED;
    
    if(buf_pos != UNTRACKED && (failed || memcheck_mallocbuf[buf_pos].ptr != ptr))
    {
        for(buf_pos = 0;buf_pos < MEMCHECK_ENTRIES;buf_pos++)
        {
            if(memcheck_mallocbuf[buf_pos].ptr == ptr)
            {
                memcheck_mallocbuf[buf_pos].ptr = (intptr_t) PTR_INVALID;
                memcheck_mallocbuf[buf_pos].failed |= (0x00000001 | failed);
            }            
        }
    }
    else
    {
        memcheck_mallocbuf[buf_pos].failed = 0;
        memcheck_mallocbuf[buf_pos].file = 0;
        
        unsigned int state = cli();
        memcheck_mallocbuf[buf_pos].ptr = 0;
        memcheck_bufpos = buf_pos;
        sei(state);
    }
}

static void *memcheck_malloc( unsigned int len, const char *file, unsigned int line, int allocator_index, unsigned int flags)
{
    unsigned int ptr;
    
    //~ dbg_printf("alloc %d %s:%d\n ", len, file, line);
    //~ int t0 = get_ms_clock_value();

    int requires_dma = flags & MEM_DMA;
    if (requires_dma)
    {
        ptr = (unsigned int) allocators[allocator_index].malloc_dma(len + 2 * MEM_SEC_ZONE);
    }
    else
    {
        ptr = (unsigned int) allocators[allocator_index].malloc(len + 2 * MEM_SEC_ZONE);
    }

    //~ int t1 = get_ms_clock_value();
    //~ dbg_printf("alloc returned %x, took %s%d.%03d s\n", ptr, FMT_FIXEDPOINT3(t1-t0));
    
    /* some allocators may return invalid ptr; discard it and return 0, as C malloc does */
    if ((intptr_t)ptr & 1) return 0;
    if (!ptr) return 0;
    
    /* fill MEM_SEC_ZONE with 0xA5 */
    for(unsigned pos = 0; pos < MEM_SEC_ZONE; pos++)
    {
        ((unsigned char *)ptr)[pos] = 0xA5;
    }

    for(unsigned pos = len + MEM_SEC_ZONE; pos < len + 2 * MEM_SEC_ZONE; pos++)
    {
        ((unsigned char *)ptr)[pos] = 0xA5;
    }
    
    /* did our allocator return a cacheable or uncacheable pointer? */
    unsigned int uncacheable_flag = (ptr == (unsigned int) UNCACHEABLE(ptr)) ? UNCACHEABLE_FLAG : 0;
    
    ((struct memcheck_hdr *)ptr)->length = len;
    ((struct memcheck_hdr *)ptr)->allocator = allocator_index;
    ((struct memcheck_hdr *)ptr)->flags = flags | uncacheable_flag;

    memcheck_add(ptr, file, line);
    
    /* keep track of allocated memory and update history */
    allocators[allocator_index].num_blocks++;
    allocators[allocator_index].mem_used += len + 2 * MEM_SEC_ZONE;
    alloc_total += len;
    alloc_total_with_memcheck += len + 2 * MEM_SEC_ZONE;
    alloc_total_peak_with_memcheck = MAX(alloc_total_peak_with_memcheck, alloc_total_with_memcheck);
    history[history_index].timestamp = get_ms_clock_value();
    history[history_index].alloc_total = alloc_total_with_memcheck;
    history_index = MOD(history_index + 1, HISTORY_ENTRIES);
    
    return (void*)(ptr + MEM_SEC_ZONE);
}

static void memcheck_free( void * buf, int allocator_index, unsigned int flags)
{
    unsigned int ptr = ((unsigned int)buf - MEM_SEC_ZONE);

    int failed = memcheck_check(ptr, 0xFFFFFFFF);
    
    memcheck_remove(ptr, failed);
    
    /* if there are errors, do not free this block */
    if (failed)
    {
        return;
    }

    /* keep track of allocated memory and update history */
    int len = ((struct memcheck_hdr *)ptr)->length;
    allocators[allocator_index].num_blocks--;
    allocators[allocator_index].mem_used -= (len + 2 * MEM_SEC_ZONE);
    alloc_total -= len;
    alloc_total_with_memcheck -= (len + 2 * MEM_SEC_ZONE);
    history[history_index].timestamp = get_ms_clock_value();
    history[history_index].alloc_total = alloc_total_with_memcheck;
    history_index = MOD(history_index + 1, HISTORY_ENTRIES);

    /* tell the backend to free this block */
    int requires_dma = flags & MEM_DMA;
    if (requires_dma)
    {
        allocators[allocator_index].free_dma((void*)ptr);
    }
    else
    {
        allocators[allocator_index].free((void*)ptr);
    }
}

static int search_for_allocator(int size, int require_preferred_size, int require_preferred_free_space, int require_tmp, int require_dma)
{
    dbg_printf("search_for_allocator(%s, prefer %s%s%s%s)\n",
        format_memory_size(size),
        require_preferred_size ? "size " : "",
        require_preferred_free_space ? "space " : "",
        require_tmp == 1 ? "tmp1 " : require_tmp == 2 ? "tmp2 " : require_tmp == -1 ? "tmp_no" : require_tmp ? "err" : "",
        require_dma ? "dma " : ""
    );
    
    for (int a = 0; a < COUNT(allocators); a++)
    {
        int has_non_dma = allocators[a].malloc ? 1 : 0;
        int has_dma = allocators[a].malloc_dma ? 1 : 0;
        int preferred_for_tmp = allocators[a].is_preferred_for_temporary_space;
        if (!preferred_for_tmp) preferred_for_tmp = -1;

        /* do we need DMA? */
        if (!(
                (require_dma && has_dma) ||
                (!require_dma && has_non_dma)
           ))
        {
            dbg_printf("%s: dma mismatch (%d,%d,%d)\n", allocators[a].name, require_dma, has_dma, has_non_dma);
            continue;
        }

        /* is this pool preferred for temporary allocations? */
        if (!(
                !require_tmp ||
                (require_tmp == preferred_for_tmp)
           ))
        {
            dbg_printf("%s: tmp mismatch (%d,%d)\n", allocators[a].name, require_tmp, preferred_for_tmp);
            continue;
        }
        
        /* matches preferred size criteria? */
        int preferred_min = allocators[a].preferred_min_alloc_size;
        int preferred_max = allocators[a].preferred_max_alloc_size ? allocators[a].preferred_max_alloc_size : INT_MAX;
        if 
            (!(
                (
                    !require_preferred_size ||
                    (size >= preferred_min && size <= preferred_max)
                )
                && 
                (
                    /* minimum_alloc_size is mandatory (e.g. don't allocate 5-byte blocks from shoot_malloc) */
                    size >= allocators[a].minimum_alloc_size
                )
           ))
        {
            dbg_printf("%s: pref size mismatch (req=%d, pref=%d..%d, min=%d)\n", allocators[a].name, size, preferred_min, preferred_max, allocators[a].minimum_alloc_size);
            continue;
        }
        
        /* do we have enough free space without exceeding the preferred limit? */
        int free_space = allocators[a].get_free_space ? allocators[a].get_free_space() : 30*1024*1024;
        //~ dbg_printf("%s: free space %s\n", allocators[a].name, format_memory_size(free_space));
        if (!(
                (
                    /* preferred free space is... well... optional */
                    !require_preferred_free_space ||
                    (free_space - size - 1024 > allocators[a].preferred_free_space)
                )
                &&
                (
                    /* minimum_free_space is mandatory */
                    free_space - size - 1024 > allocators[a].minimum_free_space
                )
           ))
        {
            dbg_printf("%s: free space mismatch (req=%d,free=%d,pref=%d,min=%d)\n", allocators[a].name, size, free_space, allocators[a].preferred_free_space, allocators[a].minimum_free_space);
            continue;
        }
        
        /* do we have a large enough contiguous chunk? */
        /* use a heuristic if we don't know, use a safety margin even if we know */
        int max_region = allocators[a].get_max_region ? allocators[a].get_max_region() - 4096 : free_space / 4;
        if (size > max_region)
        {
            dbg_printf("%s: max region mismatch %s\n", allocators[a].name, format_memory_size(max_region));
            continue;
        }
        
        /* do we have enough free blocks? */
        int max_blocks = allocators[a].maximum_blocks ? allocators[a].maximum_blocks : INT_MAX;
        if (allocators[a].num_blocks >= max_blocks)
        {
            dbg_printf("%s: not enough free blocks (%d,%d)\n", allocators[a].num_blocks, max_blocks);
            continue;
        }
        
        /* yes, we do! */
        return a;
    }
    return -1;
}

static int choose_allocator(int size, unsigned int flags)
{
    /* note: free space routines may be queried more than once (this can be optimized) */
    
    int needs_dma = (flags & MEM_DMA) ? 1 : 0;
    int prefers_tmp = (flags & MEM_TEMPORARY) ? 1 : (flags & MEM_SRM) ? 2 : -1;
    
    int a;
    
    /* first try to find an allocator that meets all the conditions (preferred size, free space, temporary preference and DMA); */
    a = search_for_allocator(size, 1, 1, prefers_tmp, needs_dma);
    if (a >= 0) return a;

    /* next, try something that doesn't meet the preferred buffer size */
    a = search_for_allocator(size, 0, 1, prefers_tmp, needs_dma);
    if (a >= 0) return a;

    /* next, try something that doesn't meet the preferred free space */
    a = search_for_allocator(size, 0, 0, prefers_tmp, needs_dma);
    if (a >= 0) return a;

    /* next, try something that doesn't meet the temporary preference */
    if (prefers_tmp)
    {
        /* try again preferred size and free space */
        a = search_for_allocator(size, 1, 1, 0, needs_dma);
        if (a >= 0) return a;

        /* relax preferred buffer size */
        a = search_for_allocator(size, 0, 1, 0, needs_dma);
        if (a >= 0) return a;

        /* relax preferred free space as well */
        a = search_for_allocator(size, 0, 0, 0, needs_dma);
        if (a >= 0) return a;
    }
    
    /* DMA is mandatory, don't relax it */
    
    /* if we arrive here, you should probably solder some memory chips on the mainboard */
    return -1;
}

/* these two will replace all malloc calls */

/* returns 0 if it couldn't allocate */
void* __mem_malloc(size_t size, unsigned int flags, const char* file, unsigned int line)
{
    take_semaphore(mem_sem, 0);

    dbg_printf("alloc(%s) from %s:%d task %s\n", format_memory_size_and_flags(size, flags), file, line, get_task_name_from_id((int)get_current_task()));
    
    /* show files without full path in error messages (they are too big) */
    file = file_name_without_path(file);

    /* choose an allocator (a preferred memory pool to allocate memory from it) */
    int allocator_index = choose_allocator(size, flags);
    
    /* did we find one? */
    if (allocator_index >= 0 && allocator_index < COUNT(allocators))
    {
        /* yes, let's allocate */

        dbg_printf("using %s (%d blocks)\n", allocators[allocator_index].name, allocators[allocator_index].num_blocks);
        
        #ifdef MEM_DEBUG
        int t0 = get_ms_clock_value();
        #endif
        
        void* ptr = memcheck_malloc(size, file, line, allocator_index, flags);
        
        #ifdef MEM_DEBUG
        int t1 = get_ms_clock_value();
        #endif
        
        if (!ptr)
        {
            /* didn't work? */
            snprintf(last_error_msg_short, sizeof(last_error_msg_short), "%s(%s,%x)", allocators[allocator_index].name, format_memory_size_and_flags(size, flags));
            snprintf(last_error_msg, sizeof(last_error_msg), "%s(%s) failed at %s:%d, %s.", allocators[allocator_index].name, format_memory_size_and_flags(size, flags), file, line, get_task_name_from_id((int)get_current_task()));
            dbg_printf("alloc fail, took %s%d.%03d s\n", FMT_FIXEDPOINT3(t1-t0));
        }
        else
        {
            /* force the cacheable pointer to be the way user requested it */
            /* note: internally, this library must use the vanilla pointer (non-mangled) */
            ptr = (flags & MEM_DMA) ? UNCACHEABLE(ptr) : CACHEABLE(ptr);

            dbg_printf("alloc ok, took %s%d.%03d s\n", FMT_FIXEDPOINT3(t1-t0));
        }
        
        give_semaphore(mem_sem);
        return ptr;
    }
    
    /* could not find an allocator (maybe out of memory?) */
    snprintf(last_error_msg_short, sizeof(last_error_msg_short), "alloc(%s)", format_memory_size_and_flags(size, flags));
    snprintf(last_error_msg, sizeof(last_error_msg), "No allocator for %s at %s:%d, %s.", format_memory_size_and_flags(size, flags), file, line, get_task_name_from_id((int)get_current_task()));
    dbg_printf("alloc not found\n");
    give_semaphore(mem_sem);
    return 0;
}

void __mem_free(void* buf)
{
    take_semaphore(mem_sem, 0);

    unsigned int ptr = (unsigned int)buf - MEM_SEC_ZONE;

    int allocator_index = ((struct memcheck_hdr *)ptr)->allocator;
    unsigned int flags = ((struct memcheck_hdr *)ptr)->flags;

    /* make sure the caching flag is the same as returned by the allocator */
    buf = (flags & UNCACHEABLE_FLAG) ? UNCACHEABLE(buf) : CACHEABLE(buf);

    dbg_printf("free(%s) from task %s\n", format_memory_size_and_flags(((struct memcheck_hdr *)ptr)->length, flags), get_task_name_from_id((int)get_current_task()));
    
    if (allocator_index >= 0 && allocator_index < COUNT(allocators))
    {
        memcheck_free(buf, allocator_index, flags);
        dbg_printf("free ok\n");
    }
    else
    {
        dbg_printf("free fail\n");
    }
    
    give_semaphore(mem_sem);
}

/* thread-safe wrappers for exmem routines */
struct memSuite *shoot_malloc_suite(size_t size)
{
    take_semaphore(mem_sem, 0);
    void* ans = _shoot_malloc_suite(size);
    give_semaphore(mem_sem);
    return ans;
}

void shoot_free_suite(struct memSuite * hSuite)
{
    take_semaphore(mem_sem, 0);
    _shoot_free_suite(hSuite);
    give_semaphore(mem_sem);
}

struct memSuite * srm_malloc_suite(int num_requested_buffers)
{
    take_semaphore(mem_sem, 0);
    void* ans = _srm_malloc_suite(num_requested_buffers);
    give_semaphore(mem_sem);
    return ans;
}

void srm_free_suite(struct memSuite * suite)
{
    take_semaphore(mem_sem, 0);
    _srm_free_suite(suite);
    give_semaphore(mem_sem);
}

struct memSuite * shoot_malloc_suite_contig(size_t size)
{
    take_semaphore(mem_sem, 0);
    void* ans = _shoot_malloc_suite_contig(size);
    give_semaphore(mem_sem);
    return ans;
}


/* initialize memory pools, if any of them needs that */
/* (called as the first init func => mem.o should be first in the Makefile.src (well, after boot-hack) */
static void mem_init()
{
    mem_sem = create_named_semaphore("mem_sem", 1);

    for (int a = 0; a < COUNT(allocators); a++)
    {
        if (allocators[a].init)
        {
            allocators[a].init();
        }
    }
}

INIT_FUNC(__FILE__, mem_init);


/* GUI stuff */

#ifdef CONFIG_INSTALLER
#undef FEATURE_SHOW_FREE_MEMORY
#endif

#ifdef FEATURE_SHOW_FREE_MEMORY

static volatile int max_stack_ack = 0;

static void max_stack_try(void* size) { max_stack_ack = (int) size; }

static int stack_size_crit(int x)
{
    int size = x * 1024;
    task_create("stack_try", 0x1e, size, max_stack_try, (void*) size);
    msleep(50);
    if (max_stack_ack == size) return 1;
    return -1;
}

static int srm_num_buffers = 0;
static int srm_buffer_size = 0;
static int max_shoot_malloc_mem = 0;
static int max_shoot_malloc_frag_mem = 0;
static char shoot_malloc_frag_desc[70] = "";
static char memory_map[720];

#define MEMORY_MAP_ADDRESS_TO_INDEX(p) ((int)CACHEABLE(p)/1024 * 720 / 512/1024)
#define MEMORY_MAP_INDEX_TO_ADDRESS(i) ALIGN32((i) * 512 * 1024 / 720 * 1024)

/* fixme: find a way to read the free stack memory from DryOS */
/* current workaround: compute it by trial and error when you press SET on Free Memory menu item */
static volatile int guess_mem_running = 0;
static void guess_free_mem_task(void* priv, int delta)
{
    /* reset values */
    max_stack_ack = 0;
    max_shoot_malloc_mem = 0;
    max_shoot_malloc_frag_mem = 0;

    bin_search(1, 1024, stack_size_crit);

    /* we won't keep these things allocated much, so we can pause malloc activity while running this (just so nothing will fail) */
    /* note: we use the _underlined routines here, but please don't do that in user code */
    take_semaphore(mem_sem, 0);

    {
        struct memSuite * hSuite = _shoot_malloc_suite_contig(0);
        if (!hSuite)
        {
            beep();
            guess_mem_running = 0;
            give_semaphore(mem_sem);
            return;
        }
        ASSERT(hSuite->num_chunks == 1);
        max_shoot_malloc_mem = hSuite->size;
        _shoot_free_suite(hSuite);
    }

    struct memSuite * hSuite = _shoot_malloc_suite(0);
    if (!hSuite)
    {
        beep();
        guess_mem_running = 0;
        give_semaphore(mem_sem);
        return;
    }
    max_shoot_malloc_frag_mem = hSuite->size;

    struct memChunk *currentChunk;
    int chunkAvail;
    void* chunkAddress;
    int total = 0;

    currentChunk = GetFirstChunkFromSuite(hSuite);

    snprintf(shoot_malloc_frag_desc, sizeof(shoot_malloc_frag_desc), "");
    memset(memory_map, 0, sizeof(memory_map));

    while(currentChunk)
    {
        chunkAvail = GetSizeOfMemoryChunk(currentChunk);
        chunkAddress = (void*)GetMemoryAddressOfMemoryChunk(currentChunk);

        int mb = 10*chunkAvail/1024/1024;
        STR_APPEND(shoot_malloc_frag_desc, mb%10 ? "%s%d.%d" : "%s%d", total ? "+" : "", mb/10, mb%10);
        total += chunkAvail;

        int start = MEMORY_MAP_ADDRESS_TO_INDEX(chunkAddress);
        int width = MEMORY_MAP_ADDRESS_TO_INDEX(chunkAvail);
        memset(memory_map + start, COLOR_GREEN1, width);

        currentChunk = GetNextMemoryChunk(hSuite, currentChunk);
    }
    STR_APPEND(shoot_malloc_frag_desc, " MB.");
    ASSERT(max_shoot_malloc_frag_mem == total);

    exmem_clear(hSuite, 0);

    _shoot_free_suite(hSuite);

    /* test the new SRM job allocator */
    hSuite = _srm_malloc_suite(0);
    
    if (!hSuite)
    {
        beep();
        guess_mem_running = 0;
        give_semaphore(mem_sem);
        return;
    }
    
    srm_num_buffers = hSuite->num_chunks;
    currentChunk = GetFirstChunkFromSuite(hSuite);
    srm_buffer_size = GetSizeOfMemoryChunk(currentChunk);

    while(currentChunk)
    {
        chunkAvail = GetSizeOfMemoryChunk(currentChunk);
        chunkAddress = (void*)GetMemoryAddressOfMemoryChunk(currentChunk);
        ASSERT(chunkAvail == srm_buffer_size);

        int start = MEMORY_MAP_ADDRESS_TO_INDEX(chunkAddress);
        int width = MEMORY_MAP_ADDRESS_TO_INDEX(chunkAvail);
        memset(memory_map + start, COLOR_CYAN, width);

        currentChunk = GetNextMemoryChunk(hSuite, currentChunk);
    }

    ASSERT(srm_buffer_size * srm_num_buffers == hSuite->size);
    
    _srm_free_suite(hSuite);

    /* mallocs can resume now */
    give_semaphore(mem_sem);

    /* memory analysis: how much appears unused? */
    for (uint32_t i = 0; i < 720; i++)
    {
        if (memory_map[i])
            continue;

        uint32_t empty = 1;
        uint32_t start = MEMORY_MAP_INDEX_TO_ADDRESS(i);
        uint32_t end = MEMORY_MAP_INDEX_TO_ADDRESS(i+1);

        for (uint32_t p = start; p < end; p += 4)
        {
            uint32_t v = MEM(p);
            #ifdef CONFIG_MARK_UNUSED_MEMORY_AT_STARTUP
            if (v != 0x124B1DE0 /* RA(W)VIDEO*/)
            #else
            if (v != 0 && v != 0xFFFFFFFF)
            #endif
            {
                empty = 0;
                break;
            }
        }

        memory_map[i] = empty ? COLOR_BLUE : COLOR_RED;
    }

    menu_redraw();
    guess_mem_running = 0;
}

static void guess_free_mem()
{
    task_create("guess_mem", 0x1e, 0x4000, guess_free_mem_task, 0);
}

static MENU_UPDATE_FUNC(mem_error_display);

static struct { uint32_t addr; char* name; } common_addresses[] = {
    { RESTARTSTART,         "RST"},
    { YUV422_HD_BUFFER_1,   "HD1"},
    { YUV422_HD_BUFFER_1,   "HD2"},
    { YUV422_LV_BUFFER_1,   "LV1"},
    { YUV422_LV_BUFFER_2,   "LV2"},
    { YUV422_LV_BUFFER_3,   "LV3"},
};

static MENU_UPDATE_FUNC(meminfo_display)
{
    int M = GetFreeMemForAllocateMemory();
    int m = MALLOC_FREE_MEMORY;

#ifdef CONFIG_VXWORKS
    MENU_SET_VALUE(
        "%dK",
        M/1024
    );
    if (M < 1024*1024) MENU_SET_WARNING(MENU_WARN_ADVICE, "Not enough free memory.");
#else

    int guess_needed = 0;
    int info_type = (int) entry->priv;
    switch (info_type)
    {
        case 0: // main entry
            MENU_SET_VALUE(
                "%dK + %dK",
                m/1024, M/1024
            );
            //if (M < 1024*1024 || m < 128*1024) MENU_SET_WARNING(MENU_WARN_ADVICE, "Not enough free memory.");
            MENU_SET_ENABLED(1);
            MENU_SET_ICON(MNI_DICE, 0);
            mem_error_display(entry, info);
            break;
#if 0
        case 1: // malloc
            MENU_SET_VALUE("%s", format_memory_size(m));
            if (m < 128*1024) MENU_SET_WARNING(MENU_WARN_ADVICE, "Would be nice to have at least 128K free here.");
            break;

        case 2: // AllocateMemory
            MENU_SET_VALUE("%s", format_memory_size(M));
            if (M < 1024*1024) MENU_SET_WARNING(MENU_WARN_ADVICE, "Canon code requires around 1 MB from here.");
            break;
#endif

        case 3: // task stack
            MENU_SET_VALUE("%s", format_memory_size(max_stack_ack));
            guess_needed = 1;
            break;

        case 4: // shoot_malloc contig
            MENU_SET_VALUE("%s", format_memory_size(max_shoot_malloc_mem));
            guess_needed = 1;
            break;

        case 5: // shoot_malloc fragmented
            MENU_SET_VALUE("%s", format_memory_size(max_shoot_malloc_frag_mem));
            MENU_SET_WARNING(MENU_WARN_INFO, shoot_malloc_frag_desc);
            guess_needed = 1;
            
            /* paint memory map */
            for (int i = 0; i < 720; i++)
                if (memory_map[i])
                    draw_line(i, 400, i, 410, memory_map[i]);
            
            /* show some common addresses on the memory map */
            for (int i = 0; i < COUNT(common_addresses); i++)
            {
                int c = MEMORY_MAP_ADDRESS_TO_INDEX(common_addresses[i].addr);
                draw_line(c, 390, c, 400, COLOR_YELLOW);
                bmp_printf(FONT_SMALL, c, 385, common_addresses[i].name);
            }

            /* show EDMAC addresses on the memory map */
            for (int i = 0; i < 32; i++)
            {
                int a = edmac_get_address(i);
                if (a)
                {
                    int c = MEMORY_MAP_ADDRESS_TO_INDEX(a);
                    draw_line(c, 410, c, 420, COLOR_YELLOW);
                    int msg = i < 10 ? '0'+i : 'a'+i;  /* extended hex to fit in the single character */
                    bmp_printf(FONT_SMALL | FONT_ALIGN_CENTER, c, 415, "%s", (char*) &msg);
                }
            }
            break;

        case 6: // srm job
            MENU_SET_VALUE("%d" SYM_TIMES "%s", srm_num_buffers, format_memory_size(srm_buffer_size));
            guess_needed = 1;
            break;

        case 7: // autoexec size
        {
            extern uint32_t ml_reserved_mem;
            extern uint32_t ml_used_mem;

            if (ABS(ml_used_mem - ml_reserved_mem) < 1024)
            {
                MENU_SET_VALUE("%s", format_memory_size(ml_used_mem));
            }
            else
            {
                MENU_SET_VALUE("%s of ",format_memory_size(ml_used_mem));
                MENU_APPEND_VALUE("%s", format_memory_size(ml_reserved_mem));
            }
            
            if (ml_reserved_mem < ml_used_mem)
            {
                MENU_SET_WARNING(MENU_WARN_ADVICE, "ML uses too much memory!!");
            }

            break;
        }
    }

    if (guess_needed && !guess_mem_running)
    {
        /* check this once every 20 seconds (not more often) */
        static int aux = INT_MIN;
        if (should_run_polling_action(20000, &aux))
        {
            guess_mem_running = 1;
            guess_free_mem();
        }
    }

    if (guess_mem_running)
        MENU_SET_WARNING(MENU_WARN_ADVICE, "Trying to guess how much RAM we have...");
    else
        MENU_SET_HELP("GREEN=shoot, CYAN=srm, BLUE=maybe free, RED=maybe used.");
#endif
}

static MENU_UPDATE_FUNC(mem_pool_display)
{
    int index = (int) entry->priv;
    if (index < 0 || index >= COUNT(allocators))
    {
        /* invalid, do not display */
        entry->shidden = 1;
        return;
    }
    
    MENU_SET_NAME(allocators[index].name);

    int used = allocators[index].mem_used;
    int free_space = allocators[index].get_free_space ? allocators[index].get_free_space() : -1;

    if (free_space > 0)
    {
        MENU_SET_VALUE("%s", format_memory_size(free_space));
        MENU_APPEND_VALUE(", %s used", format_memory_size(used));
        MENU_SET_HELP("Free & used memory from %s. %d blocks allocated.", allocators[index].name, allocators[index].num_blocks);
    }
    else
    {
        MENU_SET_VALUE("%s used", format_memory_size(used));
        MENU_SET_HELP("Memory used from %s. %d blocks allocated.", allocators[index].name, allocators[index].num_blocks);
    }
    
    if (allocators[index].get_max_region)
    {
        MENU_SET_WARNING(MENU_WARN_INFO, "Max region: %s.", format_memory_size(allocators[index].get_max_region()));
    }
    else
    {
        MENU_SET_WARNING(MENU_WARN_ADVICE, "This allocator does not implement get_max_region.");
    }
    
    if (free_space > 0 && free_space < allocators[index].preferred_free_space)
    {
        MENU_SET_WARNING(MENU_WARN_ADVICE, "Would be nice to have at least %s free here.", format_memory_size(allocators[index].preferred_free_space));
    }
}

static MENU_UPDATE_FUNC(mem_error_display)
{
    if (strlen(last_error_msg) == 0)
    {
        /* no error caught yet? do a quick check of all allocated stuff */
        /* this will fill last_error strings if there's any error */
        memcheck_get_failed();
    }
    
    if (strlen(last_error_msg))
    {
        MENU_SET_NAME("Memory Error");
        MENU_SET_VALUE(last_error_msg_short);
        MENU_SET_WARNING(MENU_WARN_ADVICE, last_error_msg);
        MENU_SET_ICON(MNI_RECORD, 0); /* red dot */
        return;
    }
}

static int total_ram_detailed = 0;

static MENU_UPDATE_FUNC(mem_total_display)
{
    if (total_ram_detailed && info->can_custom_draw && entry->selected)
    {
        info->custom_drawing = CUSTOM_DRAW_THIS_MENU;
        bmp_fill(COLOR_BLACK, 0, 0, 720, 480);

        bmp_printf(FONT_LARGE, 10, 10, "Allocated memory");
        int x = 10;
        int y = 50;

        int small_blocks = 0;
        int small_blocks_size = 0;
        for(int buf_pos = 0; buf_pos < MEMCHECK_ENTRIES; buf_pos++)
        {
            void* ptr = (void*) memcheck_mallocbuf[buf_pos].ptr;
            if (!ptr) continue;
            
            int size = ((struct memcheck_hdr *)ptr)->length;
            int flags = ((struct memcheck_hdr *)ptr)->flags;
            int allocator = ((struct memcheck_hdr *)ptr)->allocator;
            
            if (size < 32768 || y > 300)
            {
                small_blocks++;
                small_blocks_size += size;
                continue;
            }

            char* file = (char*)memcheck_mallocbuf[buf_pos].file;
            int line = memcheck_mallocbuf[buf_pos].line;
            char* task_name = (char*) memcheck_mallocbuf[buf_pos].task_name;
            char* allocator_name = allocators[allocator].name;
            bmp_printf(FONT_MED, x, y, "%s%s", memcheck_mallocbuf[buf_pos].failed ? "[FAIL] " : "", format_memory_size_and_flags(size, flags));
            bmp_printf(FONT_MED, 180, y, "%s:%d task %s", file, line, task_name);
            bmp_printf(FONT_MED | FONT_ALIGN_RIGHT, 710, y, allocator_name);
            y += font_med.height;
        }
        
        if (small_blocks)
        {
            char msg[100];
            snprintf(msg, sizeof(msg), "%d small blocks, %s, ", small_blocks, format_memory_size(small_blocks_size));
            STR_APPEND(msg, "overhead %s", format_memory_size(small_blocks * 2 * MEM_SEC_ZONE));
            bmp_printf(FONT_MED, x, y, msg);
            y += font_med.height;
        }
        
        /* show history */
        
        int first_index = history_index + 1;
        while (history[first_index].timestamp == 0)
            first_index = MOD(first_index + 1, HISTORY_ENTRIES);
        
        int t0 = history[first_index].timestamp;
        int t_end = get_ms_clock_value();
        int peak_y = y+10;
        int peak = alloc_total_peak_with_memcheck;
        int total = alloc_total_with_memcheck;
        if (t_end > t0)
        {
            int maxh = 480 - peak_y;
            bmp_fill(COLOR_GRAY(20), 0, 480-maxh, 720, maxh);
            int next_i;
            for (int i = first_index; i != history_index; i = next_i)
            {
                next_i = MOD(i+1, HISTORY_ENTRIES);
                int t = history[i].timestamp;
                int t2 = (next_i != history_index) ? history[next_i].timestamp : t_end;
                if (t2 < t) continue;
                if (i == history_index-1) t2 = t_end;
                int x = 720 * (t - t0) / (t_end - t0);
                int x2 = 720 * (t2 - t0) / (t_end - t0);
                int h = MIN((uint64_t)history[i].alloc_total * maxh / peak, maxh);
                y = 480 - h;
                int w = MAX(x2-x, 2);
                bmp_fill(h == maxh ? COLOR_RED : COLOR_BLUE, x, y, w, h);
            }
        }

        bmp_printf(FONT_MED, 10, peak_y, "%s", format_memory_size(peak));
        bmp_printf(FONT_MED, 650, MAX(y-20, peak_y), "%s", format_memory_size(total));
    }
    else
    {
        total_ram_detailed = 0;
        MENU_SET_VALUE("%s", format_memory_size(alloc_total_with_memcheck));
        MENU_APPEND_VALUE(", peak %s", format_memory_size(alloc_total_peak_with_memcheck));
        int ovh = (alloc_total_with_memcheck + sizeof(memcheck_mallocbuf) - alloc_total) * 1000 / alloc_total;
        MENU_SET_WARNING(MENU_WARN_INFO, "Memcheck overhead: %d.%d%%.", ovh/10, ovh%10, 0);
    }
}


static struct menu_entry mem_menus[] = {
#ifdef CONFIG_VXWORKS
    {
        .name = "Free Memory",
        .update = meminfo_display,
        .icon_type = IT_ALWAYS_ON,
        .help = "Free memory, shared between ML and Canon firmware.",
    },
#else // dryos
    {
        .name = "Free Memory",
        .update = meminfo_display,
        .select = menu_open_submenu,
        .help = "Free memory, shared between ML and Canon firmware.",
        .help2 = "Press SET for detailed info.",
        .submenu_width = 710,
        .children =  (struct menu_entry[]) {
            {
                .name = "Allocated RAM",
                .update = mem_total_display,
                .priv = &total_ram_detailed,
                .max = 1,
                .help = "Total memory allocated by ML. Press SET for detailed info.",
                .icon_type = IT_ALWAYS_ON,
            },
            {
                .name = allocators[0].name,
                .icon_type = IT_ALWAYS_ON,
                .priv = (int*)0,
                .update = mem_pool_display,
            },
            {
                .name = allocators[1].name,
                .icon_type = IT_ALWAYS_ON,
                .priv = (int*)1,
                .update = mem_pool_display,
            },
            {
                .name = allocators[2].name,
                .icon_type = IT_ALWAYS_ON,
                .priv = (int*)2,
                .update = mem_pool_display,
            },
            {
                .name = "stack space",
                .icon_type = IT_ALWAYS_ON,
                .priv = (int*)3,
                .update = meminfo_display,
                .help = "Free memory available as stack space for user tasks.",
            },
            {
                .name = "shoot contig",
                .icon_type = IT_ALWAYS_ON,
                .priv = (int*)4,
                .update = meminfo_display,
                .help = "Largest contiguous block from shoot memory.",
            },
            {
                .name = "shoot total",
                .icon_type = IT_ALWAYS_ON,
                .priv = (int*)5,
                .update = meminfo_display,
                .help = "Largest fragmented block from shoot memory.",
            },
            {
                .name = "SRM job total",
                .icon_type = IT_ALWAYS_ON,
                .priv = (int*)6,
                .update = meminfo_display,
                .help = "Free memory from SRM_AllocateMemoryResourceFor1stJob.",
            },
            {
                .name = "AUTOEXEC.BIN",
                .icon_type = IT_ALWAYS_ON,
                .priv = (int*)7,
                .update = meminfo_display,
                .help = "Memory reserved statically at startup for ML binary.",
            },
            MENU_EOL
        },
    },
};
#endif

void mem_menu_init()
{
    menu_add("Debug", mem_menus, COUNT(mem_menus));
    //~ console_show();
}

#endif // FEATURE_SHOW_FREE_MEMORY
back to top