diff --git a/README.md b/README.md index 944672d59..c92c31a60 100644 --- a/README.md +++ b/README.md @@ -101,3 +101,5 @@ Espressif SDK included in this build is under Espressif MIT License. ESP8266 core files are licensed under LGPL. [SPI Flash File System (SPIFFS)](https://github.com/pellepl/spiffs) written by Peter Andersson is used in this project. It is distributed under MIT license. + +[umm_malloc](https://github.com/rhempel/umm_malloc) memory management library written by Ralph Hempel is used in this project. It is distributed under MIT license. diff --git a/cores/esp8266/heap.c b/cores/esp8266/heap.c new file mode 100644 index 000000000..ac79bcad9 --- /dev/null +++ b/cores/esp8266/heap.c @@ -0,0 +1,43 @@ +/* heap.c - overrides of SDK heap handling functions + * Copyright (c) 2016 Ivan Grokhotkov. All rights reserved. + * This file is distributed under MIT license. + */ + +#include <stdlib.h> +#include "umm_malloc/umm_malloc.h" +#include <c_types.h> + +void* ICACHE_RAM_ATTR pvPortMalloc(size_t size, const char* file, int line) +{ + return malloc(size); +} + +void ICACHE_RAM_ATTR vPortFree(void *ptr, const char* file, int line) +{ + free(ptr); +} + +void* ICACHE_RAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file, int line) +{ + return calloc(count, size); +} + +void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, int line) +{ + return realloc(ptr, size); +} + +void* ICACHE_RAM_ATTR pvPortZalloc(size_t size, const char* file, int line) +{ + return calloc(1, size); +} + +size_t ICACHE_RAM_ATTR xPortGetFreeHeapSize(void) +{ + return umm_free_heap_size(); +} + +void system_show_malloc(void) +{ + umm_info(NULL, 1); +} diff --git a/cores/esp8266/libc_replacements.c b/cores/esp8266/libc_replacements.c index f7d1420e3..104e4564a 100644 --- a/cores/esp8266/libc_replacements.c +++ b/cores/esp8266/libc_replacements.c @@ -39,22 +39,8 @@ #include "user_interface.h" #include "debug.h" -void* ICACHE_RAM_ATTR malloc(size_t size) { - size = ((size + 3) & ~((size_t)0x3)); - return os_malloc(size); -} - -void ICACHE_RAM_ATTR free(void* ptr) { - os_free(ptr); -} - -void* ICACHE_RAM_ATTR realloc(void* ptr, size_t size) { - size = ((size + 3) & ~((size_t)0x3)); - return os_realloc(ptr, size); -} - int ICACHE_RAM_ATTR puts(const char * str) { - return os_printf("%s", str); + return ets_printf("%s", str); } int ICACHE_RAM_ATTR printf(const char* format, ...) { diff --git a/cores/esp8266/umm_malloc/LICENSE b/cores/esp8266/umm_malloc/LICENSE new file mode 100644 index 000000000..c1b5a9cae --- /dev/null +++ b/cores/esp8266/umm_malloc/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Ralph Hempel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/cores/esp8266/umm_malloc/README.md b/cores/esp8266/umm_malloc/README.md new file mode 100644 index 000000000..1c7805a1c --- /dev/null +++ b/cores/esp8266/umm_malloc/README.md @@ -0,0 +1,480 @@ +# umm_malloc - Memory Manager For Small(ish) Microprocessors + +This is a memory management library specifically designed to work with the +ARM7 embedded processor, but it should work on many other 32 bit processors, +as well as 16 and 8 bit devices. + +You can even use it on a bigger project where a single process might want +to manage a large number of smaller objects, and using the system heap +might get expensive. + +## Acknowledgements + +Joerg Wunsch and the avr-libc provided the first malloc() implementation +that I examined in detail. + +http://www.nongnu.org/avr-libc + +Doug Lea's paper on malloc() was another excellent reference and provides +a lot of detail on advanced memory management techniques such as binning. + +http://g.oswego.edu/dl/html/malloc.html + +Bill Dittman provided excellent suggestions, including macros to support +using these functions in critical sections, and for optimizing realloc() +further by checking to see if the previous block was free and could be +used for the new block size. This can help to reduce heap fragmentation +significantly. + +Yaniv Ankin suggested that a way to dump the current heap condition +might be useful. I combined this with an idea from plarroy to also +allow checking a free pointer to make sure it's valid. + +## Background + +The memory manager assumes the following things: + +1. The standard POSIX compliant malloc/realloc/free semantics are used +1. All memory used by the manager is allocated at link time, it is aligned +on a 32 bit boundary, it is contiguous, and its extent (start and end +address) is filled in by the linker. +1. All memory used by the manager is initialized to 0 as part of the +runtime startup routine. No other initialization is required. + +The fastest linked list implementations use doubly linked lists so that +its possible to insert and delete blocks in constant time. This memory +manager keeps track of both free and used blocks in a doubly linked list. + +Most memory managers use some kind of list structure made up of pointers +to keep track of used - and sometimes free - blocks of memory. In an +embedded system, this can get pretty expensive as each pointer can use +up to 32 bits. + +In most embedded systems there is no need for managing large blocks +of memory dynamically, so a full 32 bit pointer based data structure +for the free and used block lists is wasteful. A block of memory on +the free list would use 16 bytes just for the pointers! + +This memory management library sees the malloc heap as an array of blocks, +and uses block numbers to keep track of locations. The block numbers are +15 bits - which allows for up to 32767 blocks of memory. The high order +bit marks a block as being either free or in use, which will be explained +later. + +The result is that a block of memory on the free list uses just 8 bytes +instead of 16. + +In fact, we go even one step futher when we realize that the free block +index values are available to store data when the block is allocated. + +The overhead of an allocated block is therefore just 4 bytes. + +Each memory block holds 8 bytes, and there are up to 32767 blocks +available, for about 256K of heap space. If that's not enough, you +can always add more data bytes to the body of the memory block +at the expense of free block size overhead. + +There are a lot of little features and optimizations in this memory +management system that makes it especially suited to small embedded, but +the best way to appreciate them is to review the data structures and +algorithms used, so let's get started. + +## Detailed Description + +We have a general notation for a block that we'll use to describe the +different scenarios that our memory allocation algorithm must deal with: + +``` + +----+----+----+----+ +c |* n | p | nf | pf | + +----+----+----+----+ +``` + +Where: + +- c is the index of this block +- * is the indicator for a free block +- n is the index of the next block in the heap +- p is the index of the previous block in the heap +- nf is the index of the next block in the free list +- pf is the index of the previous block in the free list + +The fact that we have forward and backward links in the block descriptors +means that malloc() and free() operations can be very fast. It's easy +to either allocate the whole free item to a new block or to allocate part +of the free item and leave the rest on the free list without traversing +the list from front to back first. + +The entire block of memory used by the heap is assumed to be initialized +to 0. The very first block in the heap is special - it't the head of the +free block list. It is never assimilated with a free block (more on this +later). + +Once a block has been allocated to the application, it looks like this: + +``` + +----+----+----+----+ +c | n | p | ... | + +----+----+----+----+ +``` + +Where: + +- c is the index of this block +- n is the index of the next block in the heap +- p is the index of the previous block in the heap + +Note that the free list information is gone, because it's now being used to +store actual data for the application. It would have been nice to store +the next and previous free list indexes as well, but that would be a waste +of space. If we had even 500 items in use, that would be 2,000 bytes for +free list information. We simply can't afford to waste that much. + +The address of the `...` area is what is returned to the application +for data storage. + +The following sections describe the scenarios encountered during the +operation of the library. There are two additional notation conventions: + +`??` inside a pointer block means that the data is irrelevant. We don't care +about it because we don't read or modify it in the scenario being +described. + +`...` between memory blocks indicates zero or more additional blocks are +allocated for use by the upper block. + +And while we're talking about "upper" and "lower" blocks, we should make +a comment about adresses. In the diagrams, a block higher up in the +picture is at a lower address. And the blocks grow downwards their +block index increases as does their physical address. + +Finally, there's one very important characteristic of the individual +blocks that make up the heap - there can never be two consecutive free +memory blocks, but there can be consecutive used memory blocks. + +The reason is that we always want to have a short free list of the +largest possible block sizes. By always assimilating a newly freed block +with adjacent free blocks, we maximize the size of each free memory area. + +### Operation of malloc right after system startup + +As part of the system startup code, all of the heap has been cleared. + +During the very first malloc operation, we start traversing the free list +starting at index 0. The index of the next free block is 0, which means +we're at the end of the list! + +At this point, the malloc has a special test that checks if the current +block index is 0, which it is. This special case initializes the free +list to point at block index 1. + +``` + BEFORE AFTER + + +----+----+----+----+ +----+----+----+----+ +0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ + 1 | 0 | 0 | 0 | 0 | + +----+----+----+----+ +``` + +The heap is now ready to complete the first malloc operation. + + +### Operation of malloc when we have reached the end of the free list and +there is no block large enough to accommodate the request. + +This happens at the very first malloc operation, or any time the free +list is traversed and no free block large enough for the request is +found. + +The current block pointer will be at the end of the free list, and we +know we're at the end of the list because the nf index is 0, like this: + +``` + BEFORE AFTER + + +----+----+----+----+ +----+----+----+----+ +pf |*?? | ?? | cf | ?? | pf |*?? | ?? | lf | ?? | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ + p | cf | ?? | ... | p | cf | ?? | ... | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ +cf | 0 | p | 0 | pf | c | lf | p | ... | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ + lf | 0 | cf | 0 | pf | + +----+----+----+----+ +``` + +As we walk the free list looking for a block of size b or larger, we get +to cf, which is the last item in the free list. We know this because the +next index is 0. + +So we're going to turn cf into the new block of memory, and then create +a new block that represents the last free entry (lf) and adjust the prev +index of lf to point at the block we just created. We also need to adjust +the next index of the new block (c) to point to the last free block. + +Note that the next free index of the pf block must point to the new lf +because cf is no longer a free block! + +### Operation of malloc when we have found a block (cf) that will fit the current request of b units exactly + +This one is pretty easy, just clear the free list bit in the current +block and unhook it from the free list. + +``` + BEFORE AFTER + + +----+----+----+----+ +----+----+----+----+ +pf |*?? | ?? | cf | ?? | pf |*?? | ?? | nf | ?? | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ + p | cf | ?? | ... | p | cf | ?? | ... | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ Clear the free +cf |* n | p | nf | pf | cf | n | p | .. | list bit here + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ + n | ?? | cf | ... | n | ?? | cf | ... | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ +nf |*?? | ?? | ?? | cf | nf | ?? | ?? | ?? | pf | + +----+----+----+----+ +----+----+----+----+ +``` + +Unhooking from the free list is accomplished by adjusting the next and +prev free list index values in the pf and nf blocks. + +### Operation of malloc when we have found a block that will fit the current request of b units with some left over + +We'll allocate the new block at the END of the current free block so we +don't have to change ANY free list pointers. + +``` + BEFORE AFTER + + +----+----+----+----+ +----+----+----+----+ +pf |*?? | ?? | cf | ?? | pf |*?? | ?? | cf | ?? | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ + p | cf | ?? | ... | p | cf | ?? | ... | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ +cf |* n | p | nf | pf | cf |* c | p | nf | pf | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ This is the new + c | n | cf | .. | block at cf+b + +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ + n | ?? | cf | ... | n | ?? | c | ... | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ +nf |*?? | ?? | ?? | cf | nf | ?? | ?? | ?? | pf | + +----+----+----+----+ +----+----+----+----+ +``` + +This one is prety easy too, except we don't need to mess with the +free list indexes at all becasue we'll allocate the new block at the +end of the current free block. We do, however have to adjust the +indexes in cf, c, and n. + +That covers the initialization and all possible malloc scenarios, so now +we need to cover the free operation possibilities... + +### Free Scenarios + +The operation of free depends on the position of the current block being +freed relative to free list items immediately above or below it. The code +works like this: + +``` +if next block is free + assimilate with next block already on free list +if prev block is free + assimilate with prev block already on free list +else + put current block at head of free list +``` + +Step 1 of the free operation checks if the next block is free, and if it +is then insert this block into the free list and assimilate the next block +with this one. + +Note that c is the block we are freeing up, cf is the free block that +follows it. + +``` + BEFORE AFTER + + +----+----+----+----+ +----+----+----+----+ +pf |*?? | ?? | cf | ?? | pf |*?? | ?? | nf | ?? | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ + p | c | ?? | ... | p | c | ?? | ... | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ This block is + c | cf | p | ... | c | nn | p | ... | disconnected + +----+----+----+----+ +----+----+----+----+ from free list, + +----+----+----+----+ assimilated with +cf |*nn | c | nf | pf | the next, and + +----+----+----+----+ ready for step 2 + +----+----+----+----+ +----+----+----+----+ +nn | ?? | cf | ?? | ?? | nn | ?? | c | ... | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ +nf |*?? | ?? | ?? | cf | nf |*?? | ?? | ?? | pf | + +----+----+----+----+ +----+----+----+----+ +``` + +Take special note that the newly assimilated block (c) is completely +disconnected from the free list, and it does not have its free list +bit set. This is important as we move on to step 2 of the procedure... + +Step 2 of the free operation checks if the prev block is free, and if it +is then assimilate it with this block. + +Note that c is the block we are freeing up, pf is the free block that +precedes it. + +``` + BEFORE AFTER + + +----+----+----+----+ +----+----+----+----+ This block has +pf |* c | ?? | nf | ?? | pf |* n | ?? | nf | ?? | assimilated the + +----+----+----+----+ +----+----+----+----+ current block + +----+----+----+----+ + c | n | pf | ... | + +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ + n | ?? | c | ... | n | ?? | pf | ?? | ?? | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ +nf |*?? | ?? | ?? | pf | nf |*?? | ?? | ?? | pf | + +----+----+----+----+ +----+----+----+----+ +``` + +Nothing magic here, except that when we're done, the current block (c) +is gone since it's been absorbed into the previous free block. Note that +the previous step guarantees that the next block (n) is not free. + +Step 3 of the free operation only runs if the previous block is not free. +it just inserts the current block to the head of the free list. + +Remember, 0 is always the first block in the memory heap, and it's always +head of the free list! + +``` + BEFORE AFTER + + +----+----+----+----+ +----+----+----+----+ + 0 | ?? | ?? | nf | 0 | 0 | ?? | ?? | c | 0 | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ + p | c | ?? | ... | p | c | ?? | ... | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ + c | n | p | .. | c |* n | p | nf | 0 | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ + n | ?? | c | ... | n | ?? | c | ... | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ +nf |*?? | ?? | ?? | 0 | nf |*?? | ?? | ?? | c | + +----+----+----+----+ +----+----+----+----+ +``` + +Again, nothing spectacular here, we're simply adjusting a few pointers +to make the most recently freed block the first item in the free list. + +That's because finding the previous free block would mean a reverse +traversal of blocks until we found a free one, and it's just easier to +put it at the head of the list. No traversal is needed. + +### Realloc Scenarios + +Finally, we can cover realloc, which has the following basic operation. + +The first thing we do is assimilate up with the next free block of +memory if possible. This step might help if we're resizing to a bigger +block of memory. It also helps if we're downsizing and creating a new +free block with the leftover memory. + +First we check to see if the next block is free, and we assimilate it +to this block if it is. If the previous block is also free, and if +combining it with the current block would satisfy the request, then we +assimilate with that block and move the current data down to the new +location. + +Assimilating with the previous free block and moving the data works +like this: + +``` + BEFORE AFTER + + +----+----+----+----+ +----+----+----+----+ +pf |*?? | ?? | cf | ?? | pf |*?? | ?? | nf | ?? | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ +cf |* c | ?? | nf | pf | c | n | ?? | ... | The data gets + +----+----+----+----+ +----+----+----+----+ moved from c to + +----+----+----+----+ the new data area + c | n | cf | ... | in cf, then c is + +----+----+----+----+ adjusted to cf + +----+----+----+----+ +----+----+----+----+ + n | ?? | c | ... | n | ?? | c | ?? | ?? | + +----+----+----+----+ +----+----+----+----+ + ... ... + +----+----+----+----+ +----+----+----+----+ +nf |*?? | ?? | ?? | cf | nf |*?? | ?? | ?? | pf | + +----+----+----+----+ +----+----+----+----+ +``` + +Once we're done that, there are three scenarios to consider: + +1. The current block size is exactly the right size, so no more work is +needed. + +2. The current block is bigger than the new required size, so carve off +the excess and add it to the free list. + +3. The current block is still smaller than the required size, so malloc +a new block of the correct size and copy the current data into the new +block before freeing the current block. + +The only one of these scenarios that involves an operation that has not +yet been described is the second one, and it's shown below: + +``` +BEFORE AFTER + + +----+----+----+----+ +----+----+----+----+ + p | c | ?? | ... | p | c | ?? | ... | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ +----+----+----+----+ + c | n | p | ... | c | s | p | ... | + +----+----+----+----+ +----+----+----+----+ + +----+----+----+----+ This is the + s | n | c | .. | new block at + +----+----+----+----+ c+blocks + +----+----+----+----+ +----+----+----+----+ + n | ?? | c | ... | n | ?? | s | ... | + +----+----+----+----+ +----+----+----+----+ +``` + +Then we call free() with the adress of the data portion of the new +block (s) which adds it to the free list. diff --git a/cores/esp8266/umm_malloc/umm_malloc.c b/cores/esp8266/umm_malloc/umm_malloc.c new file mode 100644 index 000000000..1988dde53 --- /dev/null +++ b/cores/esp8266/umm_malloc/umm_malloc.c @@ -0,0 +1,1736 @@ +/* ---------------------------------------------------------------------------- + * umm_malloc.c - a memory allocator for embedded systems (microcontrollers) + * + * See copyright notice in LICENSE.TXT + * ---------------------------------------------------------------------------- + * + * R.Hempel 2007-09-22 - Original + * R.Hempel 2008-12-11 - Added MIT License biolerplate + * - realloc() now looks to see if previous block is free + * - made common operations functions + * R.Hempel 2009-03-02 - Added macros to disable tasking + * - Added function to dump heap and check for valid free + * pointer + * R.Hempel 2009-03-09 - Changed name to umm_malloc to avoid conflicts with + * the mm_malloc() library functions + * - Added some test code to assimilate a free block + * with the very block if possible. Complicated and + * not worth the grief. + * D.Frank 2014-04-02 - Fixed heap configuration when UMM_TEST_MAIN is NOT set, + * added user-dependent configuration file umm_malloc_cfg.h + * ---------------------------------------------------------------------------- + * + * Note: when upgrading this file with upstream code, replace all %i with %d in + * printf format strings. ets_printf doesn't handle %i. + * + * ---------------------------------------------------------------------------- + * + * This is a memory management library specifically designed to work with the + * ARM7 embedded processor, but it should work on many other 32 bit processors, + * as well as 16 and 8 bit devices. + * + * ACKNOWLEDGEMENTS + * + * Joerg Wunsch and the avr-libc provided the first malloc() implementation + * that I examined in detail. + * + * http: *www.nongnu.org/avr-libc + * + * Doug Lea's paper on malloc() was another excellent reference and provides + * a lot of detail on advanced memory management techniques such as binning. + * + * http: *g.oswego.edu/dl/html/malloc.html + * + * Bill Dittman provided excellent suggestions, including macros to support + * using these functions in critical sections, and for optimizing realloc() + * further by checking to see if the previous block was free and could be + * used for the new block size. This can help to reduce heap fragmentation + * significantly. + * + * Yaniv Ankin suggested that a way to dump the current heap condition + * might be useful. I combined this with an idea from plarroy to also + * allow checking a free pointer to make sure it's valid. + * + * ---------------------------------------------------------------------------- + * + * The memory manager assumes the following things: + * + * 1. The standard POSIX compliant malloc/realloc/free semantics are used + * 2. All memory used by the manager is allocated at link time, it is aligned + * on a 32 bit boundary, it is contiguous, and its extent (start and end + * address) is filled in by the linker. + * 3. All memory used by the manager is initialized to 0 as part of the + * runtime startup routine. No other initialization is required. + * + * The fastest linked list implementations use doubly linked lists so that + * its possible to insert and delete blocks in constant time. This memory + * manager keeps track of both free and used blocks in a doubly linked list. + * + * Most memory managers use some kind of list structure made up of pointers + * to keep track of used - and sometimes free - blocks of memory. In an + * embedded system, this can get pretty expensive as each pointer can use + * up to 32 bits. + * + * In most embedded systems there is no need for managing large blocks + * of memory dynamically, so a full 32 bit pointer based data structure + * for the free and used block lists is wasteful. A block of memory on + * the free list would use 16 bytes just for the pointers! + * + * This memory management library sees the malloc heap as an array of blocks, + * and uses block numbers to keep track of locations. The block numbers are + * 15 bits - which allows for up to 32767 blocks of memory. The high order + * bit marks a block as being either free or in use, which will be explained + * later. + * + * The result is that a block of memory on the free list uses just 8 bytes + * instead of 16. + * + * In fact, we go even one step futher when we realize that the free block + * index values are available to store data when the block is allocated. + * + * The overhead of an allocated block is therefore just 4 bytes. + * + * Each memory block holds 8 bytes, and there are up to 32767 blocks + * available, for about 256K of heap space. If that's not enough, you + * can always add more data bytes to the body of the memory block + * at the expense of free block size overhead. + * + * There are a lot of little features and optimizations in this memory + * management system that makes it especially suited to small embedded, but + * the best way to appreciate them is to review the data structures and + * algorithms used, so let's get started. + * + * ---------------------------------------------------------------------------- + * + * We have a general notation for a block that we'll use to describe the + * different scenarios that our memory allocation algorithm must deal with: + * + * +----+----+----+----+ + * c |* n | p | nf | pf | + * +----+----+----+----+ + * + * Where - c is the index of this block + * * is the indicator for a free block + * n is the index of the next block in the heap + * p is the index of the previous block in the heap + * nf is the index of the next block in the free list + * pf is the index of the previous block in the free list + * + * The fact that we have forward and backward links in the block descriptors + * means that malloc() and free() operations can be very fast. It's easy + * to either allocate the whole free item to a new block or to allocate part + * of the free item and leave the rest on the free list without traversing + * the list from front to back first. + * + * The entire block of memory used by the heap is assumed to be initialized + * to 0. The very first block in the heap is special - it't the head of the + * free block list. It is never assimilated with a free block (more on this + * later). + * + * Once a block has been allocated to the application, it looks like this: + * + * +----+----+----+----+ + * c | n | p | ... | + * +----+----+----+----+ + * + * Where - c is the index of this block + * n is the index of the next block in the heap + * p is the index of the previous block in the heap + * + * Note that the free list information is gone, because it's now being used to + * store actual data for the application. It would have been nice to store + * the next and previous free list indexes as well, but that would be a waste + * of space. If we had even 500 items in use, that would be 2,000 bytes for + * free list information. We simply can't afford to waste that much. + * + * The address of the ... area is what is returned to the application + * for data storage. + * + * The following sections describe the scenarios encountered during the + * operation of the library. There are two additional notation conventions: + * + * ?? inside a pointer block means that the data is irrelevant. We don't care + * about it because we don't read or modify it in the scenario being + * described. + * + * ... between memory blocks indicates zero or more additional blocks are + * allocated for use by the upper block. + * + * And while we're talking about "upper" and "lower" blocks, we should make + * a comment about adresses. In the diagrams, a block higher up in the + * picture is at a lower address. And the blocks grow downwards their + * block index increases as does their physical address. + * + * Finally, there's one very important characteristic of the individual + * blocks that make up the heap - there can never be two consecutive free + * memory blocks, but there can be consecutive used memory blocks. + * + * The reason is that we always want to have a short free list of the + * largest possible block sizes. By always assimilating a newly freed block + * with adjacent free blocks, we maximize the size of each free memory area. + * + *--------------------------------------------------------------------------- + * + * Operation of malloc right after system startup + * + * As part of the system startup code, all of the heap has been cleared. + * + * During the very first malloc operation, we start traversing the free list + * starting at index 0. The index of the next free block is 0, which means + * we're at the end of the list! + * + * At this point, the malloc has a special test that checks if the current + * block index is 0, which it is. This special case initializes the free + * list to point at block index 1. + * + * BEFORE AFTER + * + * +----+----+----+----+ +----+----+----+----+ + * 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ + * 1 | 0 | 0 | 0 | 0 | + * +----+----+----+----+ + * + * The heap is now ready to complete the first malloc operation. + * + * ---------------------------------------------------------------------------- + * + * Operation of malloc when we have reached the end of the free list and + * there is no block large enough to accommodate the request. + * + * This happens at the very first malloc operation, or any time the free + * list is traversed and no free block large enough for the request is + * found. + * + * The current block pointer will be at the end of the free list, and we + * know we're at the end of the list because the nf index is 0, like this: + * + * BEFORE AFTER + * + * +----+----+----+----+ +----+----+----+----+ + * pf |*?? | ?? | cf | ?? | pf |*?? | ?? | lf | ?? | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * p | cf | ?? | ... | p | cf | ?? | ... | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ + * cf | 0 | p | 0 | pf | c | lf | p | ... | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ + * lf | 0 | cf | 0 | pf | + * +----+----+----+----+ + * + * As we walk the free list looking for a block of size b or larger, we get + * to cf, which is the last item in the free list. We know this because the + * next index is 0. + * + * So we're going to turn cf into the new block of memory, and then create + * a new block that represents the last free entry (lf) and adjust the prev + * index of lf to point at the block we just created. We also need to adjust + * the next index of the new block (c) to point to the last free block. + * + * Note that the next free index of the pf block must point to the new lf + * because cf is no longer a free block! + * + * ---------------------------------------------------------------------------- + * + * Operation of malloc when we have found a block (cf) that will fit the + * current request of b units exactly. + * + * This one is pretty easy, just clear the free list bit in the current + * block and unhook it from the free list. + * + * BEFORE AFTER + * + * +----+----+----+----+ +----+----+----+----+ + * pf |*?? | ?? | cf | ?? | pf |*?? | ?? | nf | ?? | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * p | cf | ?? | ... | p | cf | ?? | ... | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ Clear the free + * cf |* n | p | nf | pf | cf | n | p | .. | list bit here + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ + * n | ?? | cf | ... | n | ?? | cf | ... | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * nf |*?? | ?? | ?? | cf | nf | ?? | ?? | ?? | pf | + * +----+----+----+----+ +----+----+----+----+ + * + * Unhooking from the free list is accomplished by adjusting the next and + * prev free list index values in the pf and nf blocks. + * + * ---------------------------------------------------------------------------- + * + * Operation of malloc when we have found a block that will fit the current + * request of b units with some left over. + * + * We'll allocate the new block at the END of the current free block so we + * don't have to change ANY free list pointers. + * + * BEFORE AFTER + * + * +----+----+----+----+ +----+----+----+----+ + * pf |*?? | ?? | cf | ?? | pf |*?? | ?? | cf | ?? | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * p | cf | ?? | ... | p | cf | ?? | ... | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ + * cf |* n | p | nf | pf | cf |* c | p | nf | pf | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ This is the new + * c | n | cf | .. | block at cf+b + * +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ + * n | ?? | cf | ... | n | ?? | c | ... | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * nf |*?? | ?? | ?? | cf | nf | ?? | ?? | ?? | pf | + * +----+----+----+----+ +----+----+----+----+ + * + * This one is prety easy too, except we don't need to mess with the + * free list indexes at all becasue we'll allocate the new block at the + * end of the current free block. We do, however have to adjust the + * indexes in cf, c, and n. + * + * ---------------------------------------------------------------------------- + * + * That covers the initialization and all possible malloc scenarios, so now + * we need to cover the free operation possibilities... + * + * The operation of free depends on the position of the current block being + * freed relative to free list items immediately above or below it. The code + * works like this: + * + * if next block is free + * assimilate with next block already on free list + * if prev block is free + * assimilate with prev block already on free list + * else + * put current block at head of free list + * + * ---------------------------------------------------------------------------- + * + * Step 1 of the free operation checks if the next block is free, and if it + * is then insert this block into the free list and assimilate the next block + * with this one. + * + * Note that c is the block we are freeing up, cf is the free block that + * follows it. + * + * BEFORE AFTER + * + * +----+----+----+----+ +----+----+----+----+ + * pf |*?? | ?? | cf | ?? | pf |*?? | ?? | nf | ?? | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * p | c | ?? | ... | p | c | ?? | ... | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ This block is + * c | cf | p | ... | c | nn | p | ... | disconnected + * +----+----+----+----+ +----+----+----+----+ from free list, + * +----+----+----+----+ assimilated with + * cf |*nn | c | nf | pf | the next, and + * +----+----+----+----+ ready for step 2 + * +----+----+----+----+ +----+----+----+----+ + * nn | ?? | cf | ?? | ?? | nn | ?? | c | ... | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * nf |*?? | ?? | ?? | cf | nf |*?? | ?? | ?? | pf | + * +----+----+----+----+ +----+----+----+----+ + * + * Take special note that the newly assimilated block (c) is completely + * disconnected from the free list, and it does not have its free list + * bit set. This is important as we move on to step 2 of the procedure... + * + * ---------------------------------------------------------------------------- + * + * Step 2 of the free operation checks if the prev block is free, and if it + * is then assimilate it with this block. + * + * Note that c is the block we are freeing up, pf is the free block that + * precedes it. + * + * BEFORE AFTER + * + * +----+----+----+----+ +----+----+----+----+ This block has + * pf |* c | ?? | nf | ?? | pf |* n | ?? | nf | ?? | assimilated the + * +----+----+----+----+ +----+----+----+----+ current block + * +----+----+----+----+ + * c | n | pf | ... | + * +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ + * n | ?? | c | ... | n | ?? | pf | ?? | ?? | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * nf |*?? | ?? | ?? | pf | nf |*?? | ?? | ?? | pf | + * +----+----+----+----+ +----+----+----+----+ + * + * Nothing magic here, except that when we're done, the current block (c) + * is gone since it's been absorbed into the previous free block. Note that + * the previous step guarantees that the next block (n) is not free. + * + * ---------------------------------------------------------------------------- + * + * Step 3 of the free operation only runs if the previous block is not free. + * it just inserts the current block to the head of the free list. + * + * Remember, 0 is always the first block in the memory heap, and it's always + * head of the free list! + * + * BEFORE AFTER + * + * +----+----+----+----+ +----+----+----+----+ + * 0 | ?? | ?? | nf | 0 | 0 | ?? | ?? | c | 0 | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * p | c | ?? | ... | p | c | ?? | ... | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ + * c | n | p | .. | c |* n | p | nf | 0 | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ + * n | ?? | c | ... | n | ?? | c | ... | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * nf |*?? | ?? | ?? | 0 | nf |*?? | ?? | ?? | c | + * +----+----+----+----+ +----+----+----+----+ + * + * Again, nothing spectacular here, we're simply adjusting a few pointers + * to make the most recently freed block the first item in the free list. + * + * That's because finding the previous free block would mean a reverse + * traversal of blocks until we found a free one, and it's just easier to + * put it at the head of the list. No traversal is needed. + * + * ---------------------------------------------------------------------------- + * + * Finally, we can cover realloc, which has the following basic operation. + * + * The first thing we do is assimilate up with the next free block of + * memory if possible. This step might help if we're resizing to a bigger + * block of memory. It also helps if we're downsizing and creating a new + * free block with the leftover memory. + * + * First we check to see if the next block is free, and we assimilate it + * to this block if it is. If the previous block is also free, and if + * combining it with the current block would satisfy the request, then we + * assimilate with that block and move the current data down to the new + * location. + * + * Assimilating with the previous free block and moving the data works + * like this: + * + * BEFORE AFTER + * + * +----+----+----+----+ +----+----+----+----+ + * pf |*?? | ?? | cf | ?? | pf |*?? | ?? | nf | ?? | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * cf |* c | ?? | nf | pf | c | n | ?? | ... | The data gets + * +----+----+----+----+ +----+----+----+----+ moved from c to + * +----+----+----+----+ the new data area + * c | n | cf | ... | in cf, then c is + * +----+----+----+----+ adjusted to cf + * +----+----+----+----+ +----+----+----+----+ + * n | ?? | c | ... | n | ?? | c | ?? | ?? | + * +----+----+----+----+ +----+----+----+----+ + * ... ... + * +----+----+----+----+ +----+----+----+----+ + * nf |*?? | ?? | ?? | cf | nf |*?? | ?? | ?? | pf | + * +----+----+----+----+ +----+----+----+----+ + * + * + * Once we're done that, there are three scenarios to consider: + * + * 1. The current block size is exactly the right size, so no more work is + * needed. + * + * 2. The current block is bigger than the new required size, so carve off + * the excess and add it to the free list. + * + * 3. The current block is still smaller than the required size, so malloc + * a new block of the correct size and copy the current data into the new + * block before freeing the current block. + * + * The only one of these scenarios that involves an operation that has not + * yet been described is the second one, and it's shown below: + * + * BEFORE AFTER + * + * +----+----+----+----+ +----+----+----+----+ + * p | c | ?? | ... | p | c | ?? | ... | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ +----+----+----+----+ + * c | n | p | ... | c | s | p | ... | + * +----+----+----+----+ +----+----+----+----+ + * +----+----+----+----+ This is the + * s | n | c | .. | new block at + * +----+----+----+----+ c+blocks + * +----+----+----+----+ +----+----+----+----+ + * n | ?? | c | ... | n | ?? | s | ... | + * +----+----+----+----+ +----+----+----+----+ + * + * Then we call free() with the adress of the data portion of the new + * block (s) which adds it to the free list. + * + * ---------------------------------------------------------------------------- + */ + +#include <stdio.h> +#include <string.h> + +#include "umm_malloc.h" + +#include "umm_malloc_cfg.h" /* user-dependent */ + +#ifndef UMM_FIRST_FIT +# ifndef UMM_BEST_FIT +# define UMM_BEST_FIT +# endif +#endif + +#ifndef DBG_LOG_LEVEL +# undef DBG_LOG_LEVEL +# define DBG_LOG_LEVEL 0 +#else +# undef DBG_LOG_LEVEL +# define DBG_LOG_LEVEL DBG_LOG_LEVEL +#endif + +/* -- dbglog {{{ */ + +/* ---------------------------------------------------------------------------- + * A set of macros that cleans up code that needs to produce debug + * or log information. + * + * See copyright notice in LICENSE.TXT + * ---------------------------------------------------------------------------- + * + * There are macros to handle the following decreasing levels of detail: + * + * 6 = TRACE + * 5 = DEBUG + * 4 = CRITICAL + * 3 = ERROR + * 2 = WARNING + * 1 = INFO + * 0 = FORCE - The printf is always compiled in and is called only when + * the first parameter to the macro is non-0 + * + * ---------------------------------------------------------------------------- + * + * The following #define should be set up before this file is included so + * that we can be sure that the correct macros are defined. + * + * #define DBG_LOG_LEVEL x + * ---------------------------------------------------------------------------- + */ + +#undef DBG_LOG_TRACE +#undef DBG_LOG_DEBUG +#undef DBG_LOG_CRITICAL +#undef DBG_LOG_ERROR +#undef DBG_LOG_WARNING +#undef DBG_LOG_INFO +#undef DBG_LOG_FORCE + +/* ------------------------------------------------------------------------- */ + +#if DBG_LOG_LEVEL >= 6 +# define DBG_LOG_TRACE( format, ... ) printf( format, ## __VA_ARGS__ ) +#else +# define DBG_LOG_TRACE( format, ... ) +#endif + +#if DBG_LOG_LEVEL >= 5 +# define DBG_LOG_DEBUG( format, ... ) printf( format, ## __VA_ARGS__ ) +#else +# define DBG_LOG_DEBUG( format, ... ) +#endif + +#if DBG_LOG_LEVEL >= 4 +# define DBG_LOG_CRITICAL( format, ... ) printf( format, ## __VA_ARGS__ ) +#else +# define DBG_LOG_CRITICAL( format, ... ) +#endif + +#if DBG_LOG_LEVEL >= 3 +# define DBG_LOG_ERROR( format, ... ) printf( format, ## __VA_ARGS__ ) +#else +# define DBG_LOG_ERROR( format, ... ) +#endif + +#if DBG_LOG_LEVEL >= 2 +# define DBG_LOG_WARNING( format, ... ) printf( format, ## __VA_ARGS__ ) +#else +# define DBG_LOG_WARNING( format, ... ) +#endif + +#if DBG_LOG_LEVEL >= 1 +# define DBG_LOG_INFO( format, ... ) printf( format, ## __VA_ARGS__ ) +#else +# define DBG_LOG_INFO( format, ... ) +#endif + +#define DBG_LOG_FORCE( force, format, ... ) {if(force) {printf( format, ## __VA_ARGS__ );}} + +/* }}} */ + +/* ------------------------------------------------------------------------- */ + +UMM_H_ATTPACKPRE typedef struct umm_ptr_t { + unsigned short int next; + unsigned short int prev; +} UMM_H_ATTPACKSUF umm_ptr; + + +UMM_H_ATTPACKPRE typedef struct umm_block_t { + union { + umm_ptr used; + } header; + union { + umm_ptr free; + unsigned char data[4]; + } body; +} UMM_H_ATTPACKSUF umm_block; + +#define UMM_FREELIST_MASK (0x8000) +#define UMM_BLOCKNO_MASK (0x7FFF) + +/* ------------------------------------------------------------------------- */ + +#ifdef UMM_REDEFINE_MEM_FUNCTIONS +# define umm_free free +# define umm_malloc malloc +# define umm_calloc calloc +# define umm_realloc realloc +#endif + +umm_block *umm_heap = NULL; +unsigned short int umm_numblocks = 0; + +#define UMM_NUMBLOCKS (umm_numblocks) + +/* ------------------------------------------------------------------------ */ + +#define UMM_BLOCK(b) (umm_heap[b]) + +#define UMM_NBLOCK(b) (UMM_BLOCK(b).header.used.next) +#define UMM_PBLOCK(b) (UMM_BLOCK(b).header.used.prev) +#define UMM_NFREE(b) (UMM_BLOCK(b).body.free.next) +#define UMM_PFREE(b) (UMM_BLOCK(b).body.free.prev) +#define UMM_DATA(b) (UMM_BLOCK(b).body.data) + +/* integrity check (UMM_INTEGRITY_CHECK) {{{ */ +#if defined(UMM_INTEGRITY_CHECK) +/* + * Perform integrity check of the whole heap data. Returns 1 in case of + * success, 0 otherwise. + * + * First of all, iterate through all free blocks, and check that all backlinks + * match (i.e. if block X has next free block Y, then the block Y should have + * previous free block set to X). + * + * Additionally, we check that each free block is correctly marked with + * `UMM_FREELIST_MASK` on the `next` pointer: during iteration through free + * list, we mark each free block by the same flag `UMM_FREELIST_MASK`, but + * on `prev` pointer. We'll check and unmark it later. + * + * Then, we iterate through all blocks in the heap, and similarly check that + * all backlinks match (i.e. if block X has next block Y, then the block Y + * should have previous block set to X). + * + * But before checking each backlink, we check that the `next` and `prev` + * pointers are both marked with `UMM_FREELIST_MASK`, or both unmarked. + * This way, we ensure that the free flag is in sync with the free pointers + * chain. + */ +static int integrity_check(void) { + int ok = 1; + unsigned short int prev; + unsigned short int cur; + + if (umm_heap == NULL) { + umm_init(); + } + + /* Iterate through all free blocks */ + prev = 0; + while(1) { + cur = UMM_NFREE(prev); + + /* Check that next free block number is valid */ + if (cur >= UMM_NUMBLOCKS) { + printf("heap integrity broken: too large next free num: %d " + "(in block %d, addr 0x%lx)\n", cur, prev, + (unsigned long)&UMM_NBLOCK(prev)); + ok = 0; + goto clean; + } + if (cur == 0) { + /* No more free blocks */ + break; + } + + /* Check if prev free block number matches */ + if (UMM_PFREE(cur) != prev) { + printf("heap integrity broken: free links don't match: " + "%d -> %d, but %d -> %d\n", + prev, cur, cur, UMM_PFREE(cur)); + ok = 0; + goto clean; + } + + UMM_PBLOCK(cur) |= UMM_FREELIST_MASK; + + prev = cur; + } + + /* Iterate through all blocks */ + prev = 0; + while(1) { + cur = UMM_NBLOCK(prev) & UMM_BLOCKNO_MASK; + + /* Check that next block number is valid */ + if (cur >= UMM_NUMBLOCKS) { + printf("heap integrity broken: too large next block num: %d " + "(in block %d, addr 0x%lx)\n", cur, prev, + (unsigned long)&UMM_NBLOCK(prev)); + ok = 0; + goto clean; + } + if (cur == 0) { + /* No more blocks */ + break; + } + + /* make sure the free mark is appropriate, and unmark it */ + if ((UMM_NBLOCK(cur) & UMM_FREELIST_MASK) + != (UMM_PBLOCK(cur) & UMM_FREELIST_MASK)) + { + printf("heap integrity broken: mask wrong at addr 0x%lx: n=0x%x, p=0x%x\n", + (unsigned long)&UMM_NBLOCK(cur), + (UMM_NBLOCK(cur) & UMM_FREELIST_MASK), + (UMM_PBLOCK(cur) & UMM_FREELIST_MASK) + ); + ok = 0; + goto clean; + } + + /* unmark */ + UMM_PBLOCK(cur) &= UMM_BLOCKNO_MASK; + + /* Check if prev block number matches */ + if (UMM_PBLOCK(cur) != prev) { + printf("heap integrity broken: block links don't match: " + "%d -> %d, but %d -> %d\n", + prev, cur, cur, UMM_PBLOCK(cur)); + ok = 0; + goto clean; + } + + prev = cur; + } + +clean: + if (!ok){ + UMM_HEAP_CORRUPTION_CB(); + } + return ok; +} + +#define INTEGRITY_CHECK() integrity_check() +#else +/* + * Integrity check is disabled, so just define stub macro + */ +#define INTEGRITY_CHECK() 1 +#endif +/* }}} */ + +/* poisoning (UMM_POISON) {{{ */ +#if defined(UMM_POISON) +#define POISON_BYTE (0xa5) + +/* + * Yields a size of the poison for the block of size `s`. + * If `s` is 0, returns 0. + */ +#define POISON_SIZE(s) ( \ + (s) ? \ + (UMM_POISON_SIZE_BEFORE + UMM_POISON_SIZE_AFTER + \ + sizeof(UMM_POISONED_BLOCK_LEN_TYPE) \ + ) : 0 \ + ) + +/* + * Print memory contents starting from given `ptr` + */ +static void dump_mem ( const unsigned char *ptr, size_t len ) { + while (len--) { + printf(" 0x%.2x", (unsigned int)(*ptr++)); + } +} + +/* + * Put poison data at given `ptr` and `poison_size` + */ +static void put_poison( unsigned char *ptr, size_t poison_size ) { + memset(ptr, POISON_BYTE, poison_size); +} + +/* + * Check poison data at given `ptr` and `poison_size`. `where` is a pointer to + * a string, either "before" or "after", meaning, before or after the block. + * + * If poison is there, returns 1. + * Otherwise, prints the appropriate message, and returns 0. + */ +static int check_poison( const unsigned char *ptr, size_t poison_size, + const char *where) { + size_t i; + int ok = 1; + + for (i = 0; i < poison_size; i++) { + if (ptr[i] != POISON_BYTE) { + ok = 0; + break; + } + } + + if (!ok) { + printf("there is no poison %s the block. " + "Expected poison address: 0x%lx, actual data:", + where, (unsigned long)ptr); + dump_mem(ptr, poison_size); + printf("\n"); + } + + return ok; +} + +/* + * Check if a block is properly poisoned. Must be called only for non-free + * blocks. + */ +static int check_poison_block( umm_block *pblock ) { + int ok = 1; + + if (pblock->header.used.next & UMM_FREELIST_MASK) { + printf("check_poison_block is called for free block 0x%lx\n", + (unsigned long)pblock); + } else { + /* the block is used; let's check poison */ + unsigned char *pc = (unsigned char *)pblock->body.data; + unsigned char *pc_cur; + + pc_cur = pc + sizeof(UMM_POISONED_BLOCK_LEN_TYPE); + if (!check_poison(pc_cur, UMM_POISON_SIZE_BEFORE, "before")) { + UMM_HEAP_CORRUPTION_CB(); + ok = 0; + goto clean; + } + + pc_cur = pc + *((UMM_POISONED_BLOCK_LEN_TYPE *)pc) - UMM_POISON_SIZE_AFTER; + if (!check_poison(pc_cur, UMM_POISON_SIZE_AFTER, "after")) { + UMM_HEAP_CORRUPTION_CB(); + ok = 0; + goto clean; + } + } + +clean: + return ok; +} + +/* + * Iterates through all blocks in the heap, and checks poison for all used + * blocks. + */ +static int check_poison_all_blocks(void) { + int ok = 1; + unsigned short int blockNo = 0; + + if (umm_heap == NULL) { + umm_init(); + } + + /* Now iterate through the blocks list */ + blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; + + while( UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK ) { + if ( !(UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK) ) { + /* This is a used block (not free), so, check its poison */ + ok = check_poison_block(&UMM_BLOCK(blockNo)); + if (!ok){ + break; + } + } + + blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; + } + + return ok; +} + +/* + * Takes a pointer returned by actual allocator function (`_umm_malloc` or + * `_umm_realloc`), puts appropriate poison, and returns adjusted pointer that + * should be returned to the user. + * + * `size_w_poison` is a size of the whole block, including a poison. + */ +static void *get_poisoned( unsigned char *ptr, size_t size_w_poison ) { + if (size_w_poison != 0 && ptr != NULL) { + + /* Put exact length of the user's chunk of memory */ + memcpy(ptr, &size_w_poison, sizeof(UMM_POISONED_BLOCK_LEN_TYPE)); + + /* Poison beginning and the end of the allocated chunk */ + put_poison(ptr + sizeof(UMM_POISONED_BLOCK_LEN_TYPE), + UMM_POISON_SIZE_BEFORE); + put_poison(ptr + size_w_poison - UMM_POISON_SIZE_AFTER, + UMM_POISON_SIZE_AFTER); + + /* Return pointer at the first non-poisoned byte */ + return ptr + sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE; + } else { + return ptr; + } +} + +/* + * Takes "poisoned" pointer (i.e. pointer returned from `get_poisoned()`), + * and checks that the poison of this particular block is still there. + * + * Returns unpoisoned pointer, i.e. actual pointer to the allocated memory. + */ +static void *get_unpoisoned( unsigned char *ptr ) { + if (ptr != NULL) { + unsigned short int c; + + ptr -= (sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE); + + /* Figure out which block we're in. Note the use of truncated division... */ + c = (((char *)ptr)-(char *)(&(umm_heap[0])))/sizeof(umm_block); + + check_poison_block(&UMM_BLOCK(c)); + } + + return ptr; +} + +#define CHECK_POISON_ALL_BLOCKS() check_poison_all_blocks() +#define GET_POISONED(ptr, size) get_poisoned(ptr, size) +#define GET_UNPOISONED(ptr) get_unpoisoned(ptr) + +#else +/* + * Integrity check is disabled, so just define stub macros + */ +#define POISON_SIZE(s) 0 +#define CHECK_POISON_ALL_BLOCKS() 1 +#define GET_POISONED(ptr, size) (ptr) +#define GET_UNPOISONED(ptr) (ptr) +#endif +/* }}} */ + +/* ---------------------------------------------------------------------------- + * One of the coolest things about this little library is that it's VERY + * easy to get debug information about the memory heap by simply iterating + * through all of the memory blocks. + * + * As you go through all the blocks, you can check to see if it's a free + * block by looking at the high order bit of the next block index. You can + * also see how big the block is by subtracting the next block index from + * the current block number. + * + * The umm_info function does all of that and makes the results available + * in the ummHeapInfo structure. + * ---------------------------------------------------------------------------- + */ + +UMM_HEAP_INFO ummHeapInfo; + +void *umm_info( void *ptr, int force ) { + + unsigned short int blockNo = 0; + + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(); + + /* + * Clear out all of the entries in the ummHeapInfo structure before doing + * any calculations.. + */ + memset( &ummHeapInfo, 0, sizeof( ummHeapInfo ) ); + + DBG_LOG_FORCE( force, "\n\nDumping the umm_heap...\n" ); + + DBG_LOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5d|NF %5d|PF %5d|\n", + (unsigned long)(&UMM_BLOCK(blockNo)), + blockNo, + UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, + UMM_PBLOCK(blockNo), + (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo, + UMM_NFREE(blockNo), + UMM_PFREE(blockNo) ); + + /* + * Now loop through the block lists, and keep track of the number and size + * of used and free blocks. The terminating condition is an nb pointer with + * a value of zero... + */ + + blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; + + while( UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK ) { + size_t curBlocks = (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo; + + ++ummHeapInfo.totalEntries; + ummHeapInfo.totalBlocks += curBlocks; + + /* Is this a free block? */ + + if( UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK ) { + ++ummHeapInfo.freeEntries; + ummHeapInfo.freeBlocks += curBlocks; + + if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) { + ummHeapInfo.maxFreeContiguousBlocks = curBlocks; + } + + DBG_LOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|NF %5d|PF %5d|\n", + (unsigned long)(&UMM_BLOCK(blockNo)), + blockNo, + UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, + UMM_PBLOCK(blockNo), + (unsigned int)curBlocks, + UMM_NFREE(blockNo), + UMM_PFREE(blockNo) ); + + /* Does this block address match the ptr we may be trying to free? */ + + if( ptr == &UMM_BLOCK(blockNo) ) { + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); + + return( ptr ); + } + } else { + ++ummHeapInfo.usedEntries; + ummHeapInfo.usedBlocks += curBlocks; + + DBG_LOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|\n", + (unsigned long)(&UMM_BLOCK(blockNo)), + blockNo, + UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, + UMM_PBLOCK(blockNo), + (unsigned int)curBlocks ); + } + + blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; + } + + /* + * Update the accounting totals with information from the last block, the + * rest must be free! + */ + + { + size_t curBlocks = UMM_NUMBLOCKS-blockNo; + ummHeapInfo.freeBlocks += curBlocks; + ummHeapInfo.totalBlocks += curBlocks; + + if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) { + ummHeapInfo.maxFreeContiguousBlocks = curBlocks; + } + } + + DBG_LOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5d|NF %5d|PF %5d|\n", + (unsigned long)(&UMM_BLOCK(blockNo)), + blockNo, + UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, + UMM_PBLOCK(blockNo), + UMM_NUMBLOCKS-blockNo, + UMM_NFREE(blockNo), + UMM_PFREE(blockNo) ); + + DBG_LOG_FORCE( force, "Total Entries %5d Used Entries %5d Free Entries %5d\n", + ummHeapInfo.totalEntries, + ummHeapInfo.usedEntries, + ummHeapInfo.freeEntries ); + + DBG_LOG_FORCE( force, "Total Blocks %5d Used Blocks %5d Free Blocks %5d\n", + ummHeapInfo.totalBlocks, + ummHeapInfo.usedBlocks, + ummHeapInfo.freeBlocks ); + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); + + return( NULL ); +} + +/* ------------------------------------------------------------------------ */ + +static unsigned short int umm_blocks( size_t size ) { + + /* + * The calculation of the block size is not too difficult, but there are + * a few little things that we need to be mindful of. + * + * When a block removed from the free list, the space used by the free + * pointers is available for data. That's what the first calculation + * of size is doing. + */ + + if( size <= (sizeof(((umm_block *)0)->body)) ) + return( 1 ); + + /* + * If it's for more than that, then we need to figure out the number of + * additional whole blocks the size of an umm_block are required. + */ + + size -= ( 1 + (sizeof(((umm_block *)0)->body)) ); + + return( 2 + size/(sizeof(umm_block)) ); +} + +/* ------------------------------------------------------------------------ */ + +/* + * Split the block `c` into two blocks: `c` and `c + blocks`. + * + * - `cur_freemask` should be `0` if `c` used, or `UMM_FREELIST_MASK` + * otherwise. + * - `new_freemask` should be `0` if `c + blocks` used, or `UMM_FREELIST_MASK` + * otherwise. + * + * Note that free pointers are NOT modified by this function. + */ +static void umm_make_new_block( unsigned short int c, + unsigned short int blocks, + unsigned short int cur_freemask, unsigned short int new_freemask ) { + + UMM_NBLOCK(c+blocks) = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) | new_freemask; + UMM_PBLOCK(c+blocks) = c; + + UMM_PBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) = (c+blocks); + UMM_NBLOCK(c) = (c+blocks) | cur_freemask; +} + +/* ------------------------------------------------------------------------ */ + +static void umm_disconnect_from_free_list( unsigned short int c ) { + /* Disconnect this block from the FREE list */ + + UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c); + UMM_PFREE(UMM_NFREE(c)) = UMM_PFREE(c); + + /* And clear the free block indicator */ + + UMM_NBLOCK(c) &= (~UMM_FREELIST_MASK); +} + +/* ------------------------------------------------------------------------ */ + +static void umm_assimilate_up( unsigned short int c ) { + + if( UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK ) { + /* + * The next block is a free block, so assimilate up and remove it from + * the free list + */ + + DBG_LOG_DEBUG( "Assimilate up to next block, which is FREE\n" ); + + /* Disconnect the next block from the FREE list */ + + umm_disconnect_from_free_list( UMM_NBLOCK(c) ); + + /* Assimilate the next block with this one */ + + UMM_PBLOCK(UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) = c; + UMM_NBLOCK(c) = UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK; + } +} + +/* ------------------------------------------------------------------------ */ + +static unsigned short int umm_assimilate_down( unsigned short int c, unsigned short int freemask ) { + + UMM_NBLOCK(UMM_PBLOCK(c)) = UMM_NBLOCK(c) | freemask; + UMM_PBLOCK(UMM_NBLOCK(c)) = UMM_PBLOCK(c); + + return( UMM_PBLOCK(c) ); +} + +/* ------------------------------------------------------------------------- */ + +void umm_init( void ) { + /* init heap pointer and size, and memset it to 0 */ + umm_heap = (umm_block *)UMM_MALLOC_CFG__HEAP_ADDR; + umm_numblocks = (UMM_MALLOC_CFG__HEAP_SIZE / sizeof(umm_block)); + memset(umm_heap, 0x00, UMM_MALLOC_CFG__HEAP_SIZE); + + /* setup initial blank heap structure */ + { + /* index of the 0th `umm_block` */ + const unsigned short int block_0th = 0; + /* index of the 1st `umm_block` */ + const unsigned short int block_1th = 1; + /* index of the latest `umm_block` */ + const unsigned short int block_last = UMM_NUMBLOCKS - 1; + + /* setup the 0th `umm_block`, which just points to the 1st */ + UMM_NBLOCK(block_0th) = block_1th; + UMM_NFREE(block_0th) = block_1th; + + /* + * Now, we need to set the whole heap space as a huge free block. We should + * not touch the 0th `umm_block`, since it's special: the 0th `umm_block` + * is the head of the free block list. It's a part of the heap invariant. + * + * See the detailed explanation at the beginning of the file. + */ + + /* + * 1th `umm_block` has pointers: + * + * - next `umm_block`: the latest one + * - prev `umm_block`: the 0th + * + * Plus, it's a free `umm_block`, so we need to apply `UMM_FREELIST_MASK` + * + * And it's the last free block, so the next free block is 0. + */ + UMM_NBLOCK(block_1th) = block_last | UMM_FREELIST_MASK; + UMM_NFREE(block_1th) = 0; + UMM_PBLOCK(block_1th) = block_0th; + UMM_PFREE(block_1th) = block_0th; + + /* + * latest `umm_block` has pointers: + * + * - next `umm_block`: 0 (meaning, there are no more `umm_blocks`) + * - prev `umm_block`: the 1st + * + * It's not a free block, so we don't touch NFREE / PFREE at all. + */ + UMM_NBLOCK(block_last) = 0; + UMM_PBLOCK(block_last) = block_1th; + } +} + +/* ------------------------------------------------------------------------ */ + +static void _umm_free( void *ptr ) { + + unsigned short int c; + + /* If we're being asked to free a NULL pointer, well that's just silly! */ + + if( (void *)0 == ptr ) { + DBG_LOG_DEBUG( "free a null pointer -> do nothing\n" ); + + return; + } + + /* + * FIXME: At some point it might be a good idea to add a check to make sure + * that the pointer we're being asked to free up is actually within + * the umm_heap! + * + * NOTE: See the new umm_info() function that you can use to see if a ptr is + * on the free list! + */ + + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(); + + /* Figure out which block we're in. Note the use of truncated division... */ + + c = (((char *)ptr)-(char *)(&(umm_heap[0])))/sizeof(umm_block); + + DBG_LOG_DEBUG( "Freeing block %6d\n", c ); + + /* Now let's assimilate this block with the next one if possible. */ + + umm_assimilate_up( c ); + + /* Then assimilate with the previous block if possible */ + + if( UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK ) { + + DBG_LOG_DEBUG( "Assimilate down to next block, which is FREE\n" ); + + c = umm_assimilate_down(c, UMM_FREELIST_MASK); + } else { + /* + * The previous block is not a free block, so add this one to the head + * of the free list + */ + + DBG_LOG_DEBUG( "Just add to head of free list\n" ); + + UMM_PFREE(UMM_NFREE(0)) = c; + UMM_NFREE(c) = UMM_NFREE(0); + UMM_PFREE(c) = 0; + UMM_NFREE(0) = c; + + UMM_NBLOCK(c) |= UMM_FREELIST_MASK; + } + +#if 0 + /* + * The following is experimental code that checks to see if the block we just + * freed can be assimilated with the very last block - it's pretty convoluted in + * terms of block index manipulation, and has absolutely no effect on heap + * fragmentation. I'm not sure that it's worth including but I've left it + * here for posterity. + */ + + if( 0 == UMM_NBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK ) ) { + + if( UMM_PBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) != UMM_PFREE(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) ) { + UMM_NFREE(UMM_PFREE(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK)) = c; + UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c); + UMM_PFREE(UMM_NFREE(c)) = UMM_PFREE(c); + UMM_PFREE(c) = UMM_PFREE(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK); + } + + UMM_NFREE(c) = 0; + UMM_NBLOCK(c) = 0; + } +#endif + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); +} + +/* ------------------------------------------------------------------------ */ + +static void *_umm_malloc( size_t size ) { + unsigned short int blocks; + unsigned short int blockSize = 0; + + unsigned short int bestSize; + unsigned short int bestBlock; + + unsigned short int cf; + + if (umm_heap == NULL) { + umm_init(); + } + + /* + * the very first thing we do is figure out if we're being asked to allocate + * a size of 0 - and if we are we'll simply return a null pointer. if not + * then reduce the size by 1 byte so that the subsequent calculations on + * the number of blocks to allocate are easier... + */ + + if( 0 == size ) { + DBG_LOG_DEBUG( "malloc a block of 0 bytes -> do nothing\n" ); + + return( (void *)NULL ); + } + + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(); + + blocks = umm_blocks( size ); + + /* + * Now we can scan through the free list until we find a space that's big + * enough to hold the number of blocks we need. + * + * This part may be customized to be a best-fit, worst-fit, or first-fit + * algorithm + */ + + cf = UMM_NFREE(0); + + bestBlock = UMM_NFREE(0); + bestSize = 0x7FFF; + + while( cf ) { + blockSize = (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK) - cf; + + DBG_LOG_TRACE( "Looking at block %6d size %6d\n", cf, blockSize ); + +#if defined UMM_FIRST_FIT + /* This is the first block that fits! */ + if( (blockSize >= blocks) ) + break; +#elif defined UMM_BEST_FIT + if( (blockSize >= blocks) && (blockSize < bestSize) ) { + bestBlock = cf; + bestSize = blockSize; + } +#endif + + cf = UMM_NFREE(cf); + } + + if( 0x7FFF != bestSize ) { + cf = bestBlock; + blockSize = bestSize; + } + + if( UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK && blockSize >= blocks ) { + /* + * This is an existing block in the memory heap, we just need to split off + * what we need, unlink it from the free list and mark it as in use, and + * link the rest of the block back into the freelist as if it was a new + * block on the free list... + */ + + if( blockSize == blocks ) { + /* It's an exact fit and we don't neet to split off a block. */ + DBG_LOG_DEBUG( "Allocating %6d blocks starting at %6d - exact\n", blocks, cf ); + + /* Disconnect this block from the FREE list */ + + umm_disconnect_from_free_list( cf ); + + } else { + /* It's not an exact fit and we need to split off a block. */ + DBG_LOG_DEBUG( "Allocating %6d blocks starting at %6d - existing\n", blocks, cf ); + + /* + * split current free block `cf` into two blocks. The first one will be + * returned to user, so it's not free, and the second one will be free. + */ + umm_make_new_block( cf, blocks, + 0/*`cf` is not free*/, + UMM_FREELIST_MASK/*new block is free*/); + + /* + * `umm_make_new_block()` does not update the free pointers (it affects + * only free flags), but effectively we've just moved beginning of the + * free block from `cf` to `cf + blocks`. So we have to adjust pointers + * to and from adjacent free blocks. + */ + + /* previous free block */ + UMM_NFREE( UMM_PFREE(cf) ) = cf + blocks; + UMM_PFREE( cf + blocks ) = UMM_PFREE(cf); + + /* next free block */ + UMM_PFREE( UMM_NFREE(cf) ) = cf + blocks; + UMM_NFREE( cf + blocks ) = UMM_NFREE(cf); + } + } else { + /* Out of memory */ + + DBG_LOG_DEBUG( "Can't allocate %5d blocks\n", blocks ); + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); + + return( (void *)NULL ); + } + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); + + return( (void *)&UMM_DATA(cf) ); +} + +/* ------------------------------------------------------------------------ */ + +static void *_umm_realloc( void *ptr, size_t size ) { + + unsigned short int blocks; + unsigned short int blockSize; + + unsigned short int c; + + size_t curSize; + + if (umm_heap == NULL) { + umm_init(); + } + + /* + * This code looks after the case of a NULL value for ptr. The ANSI C + * standard says that if ptr is NULL and size is non-zero, then we've + * got to work the same a malloc(). If size is also 0, then our version + * of malloc() returns a NULL pointer, which is OK as far as the ANSI C + * standard is concerned. + */ + + if( ((void *)NULL == ptr) ) { + DBG_LOG_DEBUG( "realloc the NULL pointer - call malloc()\n" ); + + return( _umm_malloc(size) ); + } + + /* + * Now we're sure that we have a non_NULL ptr, but we're not sure what + * we should do with it. If the size is 0, then the ANSI C standard says that + * we should operate the same as free. + */ + + if( 0 == size ) { + DBG_LOG_DEBUG( "realloc to 0 size, just free the block\n" ); + + _umm_free( ptr ); + + return( (void *)NULL ); + } + + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(); + + /* + * Otherwise we need to actually do a reallocation. A naiive approach + * would be to malloc() a new block of the correct size, copy the old data + * to the new block, and then free the old block. + * + * While this will work, we end up doing a lot of possibly unnecessary + * copying. So first, let's figure out how many blocks we'll need. + */ + + blocks = umm_blocks( size ); + + /* Figure out which block we're in. Note the use of truncated division... */ + + c = (((char *)ptr)-(char *)(&(umm_heap[0])))/sizeof(umm_block); + + /* Figure out how big this block is... */ + + blockSize = (UMM_NBLOCK(c) - c); + + /* Figure out how many bytes are in this block */ + + curSize = (blockSize*sizeof(umm_block))-(sizeof(((umm_block *)0)->header)); + + /* + * Ok, now that we're here, we know the block number of the original chunk + * of memory, and we know how much new memory we want, and we know the original + * block size... + */ + + if( blockSize == blocks ) { + /* This space intentionally left blank - return the original pointer! */ + + DBG_LOG_DEBUG( "realloc the same size block - %d, do nothing\n", blocks ); + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); + + return( ptr ); + } + + /* + * Now we have a block size that could be bigger or smaller. Either + * way, try to assimilate up to the next block before doing anything... + * + * If it's still too small, we have to free it anyways and it will save the + * assimilation step later in free :-) + */ + + umm_assimilate_up( c ); + + /* + * Now check if it might help to assimilate down, but don't actually + * do the downward assimilation unless the resulting block will hold the + * new request! If this block of code runs, then the new block will + * either fit the request exactly, or be larger than the request. + */ + + if( (UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK) && + (blocks <= (UMM_NBLOCK(c)-UMM_PBLOCK(c))) ) { + + /* Check if the resulting block would be big enough... */ + + DBG_LOG_DEBUG( "realloc() could assimilate down %d blocks - fits!\n\r", c-UMM_PBLOCK(c) ); + + /* Disconnect the previous block from the FREE list */ + + umm_disconnect_from_free_list( UMM_PBLOCK(c) ); + + /* + * Connect the previous block to the next block ... and then + * realign the current block pointer + */ + + c = umm_assimilate_down(c, 0); + + /* + * Move the bytes down to the new block we just created, but be sure to move + * only the original bytes. + */ + + memmove( (void *)&UMM_DATA(c), ptr, curSize ); + + /* And don't forget to adjust the pointer to the new block location! */ + + ptr = (void *)&UMM_DATA(c); + } + + /* Now calculate the block size again...and we'll have three cases */ + + blockSize = (UMM_NBLOCK(c) - c); + + if( blockSize == blocks ) { + /* This space intentionally left blank - return the original pointer! */ + + DBG_LOG_DEBUG( "realloc the same size block - %d, do nothing\n", blocks ); + + } else if (blockSize > blocks ) { + /* + * New block is smaller than the old block, so just make a new block + * at the end of this one and put it up on the free list... + */ + + DBG_LOG_DEBUG( "realloc %d to a smaller block %d, shrink and free the leftover bits\n", blockSize, blocks ); + + umm_make_new_block( c, blocks, 0, 0 ); + _umm_free( (void *)&UMM_DATA(c+blocks) ); + } else { + /* New block is bigger than the old block... */ + + void *oldptr = ptr; + + DBG_LOG_DEBUG( "realloc %d to a bigger block %d, make new, copy, and free the old\n", blockSize, blocks ); + + /* + * Now _umm_malloc() a new/ one, copy the old data to the new block, and + * free up the old block, but only if the malloc was sucessful! + */ + + if( (ptr = _umm_malloc( size )) ) { + memcpy( ptr, oldptr, curSize ); + } + + _umm_free( oldptr ); + } + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); + + return( ptr ); +} + +/* ------------------------------------------------------------------------ */ + +void *umm_malloc( size_t size ) { + void *ret; + + /* check poison of each blocks, if poisoning is enabled */ + if (!CHECK_POISON_ALL_BLOCKS()) { + return NULL; + } + + /* check full integrity of the heap, if this check is enabled */ + if (!INTEGRITY_CHECK()) { + return NULL; + } + + size += POISON_SIZE(size); + + ret = _umm_malloc( size ); + + ret = GET_POISONED(ret, size); + + return ret; +} + +/* ------------------------------------------------------------------------ */ + +void *umm_calloc( size_t num, size_t item_size ) { + void *ret; + size_t size = item_size * num; + + /* check poison of each blocks, if poisoning is enabled */ + if (!CHECK_POISON_ALL_BLOCKS()) { + return NULL; + } + + /* check full integrity of the heap, if this check is enabled */ + if (!INTEGRITY_CHECK()) { + return NULL; + } + + size += POISON_SIZE(size); + ret = _umm_malloc(size); + memset(ret, 0x00, size); + + ret = GET_POISONED(ret, size); + + return ret; +} + +/* ------------------------------------------------------------------------ */ + +void *umm_realloc( void *ptr, size_t size ) { + void *ret; + + ptr = GET_UNPOISONED(ptr); + + /* check poison of each blocks, if poisoning is enabled */ + if (!CHECK_POISON_ALL_BLOCKS()) { + return NULL; + } + + /* check full integrity of the heap, if this check is enabled */ + if (!INTEGRITY_CHECK()) { + return NULL; + } + + size += POISON_SIZE(size); + ret = _umm_realloc( ptr, size ); + + ret = GET_POISONED(ret, size); + + return ret; +} + +/* ------------------------------------------------------------------------ */ + +void umm_free( void *ptr ) { + + ptr = GET_UNPOISONED(ptr); + + /* check poison of each blocks, if poisoning is enabled */ + if (!CHECK_POISON_ALL_BLOCKS()) { + return; + } + + /* check full integrity of the heap, if this check is enabled */ + if (!INTEGRITY_CHECK()) { + return; + } + + _umm_free( ptr ); +} + +/* ------------------------------------------------------------------------ */ + +size_t umm_free_heap_size( void ) { + umm_info(NULL, 0); + return (size_t)ummHeapInfo.freeBlocks * sizeof(umm_block); +} + +/* ------------------------------------------------------------------------ */ diff --git a/cores/esp8266/umm_malloc/umm_malloc.h b/cores/esp8266/umm_malloc/umm_malloc.h new file mode 100644 index 000000000..7f1c05849 --- /dev/null +++ b/cores/esp8266/umm_malloc/umm_malloc.h @@ -0,0 +1,44 @@ +/* ---------------------------------------------------------------------------- + * umm_malloc.h - a memory allocator for embedded systems (microcontrollers) + * + * See copyright notice in LICENSE.TXT + * ---------------------------------------------------------------------------- + */ + +#ifndef UMM_MALLOC_H +#define UMM_MALLOC_H + +/* ------------------------------------------------------------------------ */ + +#include "umm_malloc_cfg.h" /* user-dependent */ + +typedef struct UMM_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; +} +UMM_HEAP_INFO; + +extern UMM_HEAP_INFO ummHeapInfo; + +void umm_init( void ); + +void *umm_info( void *ptr, int force ); + +void *umm_malloc( size_t size ); +void *umm_calloc( size_t num, size_t size ); +void *umm_realloc( void *ptr, size_t size ); +void umm_free( void *ptr ); + +size_t umm_free_heap_size( void ); + + +/* ------------------------------------------------------------------------ */ + +#endif /* UMM_MALLOC_H */ diff --git a/cores/esp8266/umm_malloc/umm_malloc_cfg.h b/cores/esp8266/umm_malloc/umm_malloc_cfg.h new file mode 100644 index 000000000..632480024 --- /dev/null +++ b/cores/esp8266/umm_malloc/umm_malloc_cfg.h @@ -0,0 +1,136 @@ +/* + * Configuration for umm_malloc + */ + +#ifndef _UMM_MALLOC_CFG_H +#define _UMM_MALLOC_CFG_H + +#include <debug.h> +#include <ets_sys.h> +#define printf ets_printf +/* + * There are a number of defines you can set at compile time that affect how + * the memory allocator will operate. + * You can set them in your config file umm_malloc_cfg.h. + * In GNU C, you also can set these compile time defines like this: + * + * -D UMM_TEST_MAIN + * + * Set this if you want to compile in the test suite at the end of this file. + * + * If you leave this define unset, then you might want to set another one: + * + * -D UMM_REDEFINE_MEM_FUNCTIONS + * + * If you leave this define unset, then the function names are left alone as + * umm_malloc() umm_free() and umm_realloc() so that they cannot be confused + * with the C runtime functions malloc() free() and realloc() + * + * If you do set this define, then the function names become malloc() + * free() and realloc() so that they can be used as the C runtime functions + * in an embedded environment. + * + * -D UMM_BEST_FIT (defualt) + * + * Set this if you want to use a best-fit algorithm for allocating new + * blocks + * + * -D UMM_FIRST_FIT + * + * Set this if you want to use a first-fit algorithm for allocating new + * blocks + * + * -D UMM_DBG_LOG_LEVEL=n + * + * Set n to a value from 0 to 6 depending on how verbose you want the debug + * log to be + * + * ---------------------------------------------------------------------------- + * + * Support for this library in a multitasking environment is provided when + * you add bodies to the UMM_CRITICAL_ENTRY and UMM_CRITICAL_EXIT macros + * (see below) + * + * ---------------------------------------------------------------------------- + */ + + #define UMM_REDEFINE_MEM_FUNCTIONS + + #define UMM_BEST_FIT + +/* Start addresses and the size of the heap */ +extern char _heap_start; +#define UMM_MALLOC_CFG__HEAP_ADDR ((uint32_t)&_heap_start) +#define UMM_MALLOC_CFG__HEAP_SIZE ((size_t)(0x3fffc000 - UMM_MALLOC_CFG__HEAP_ADDR)) + +/* A couple of macros to make packing structures less compiler dependent */ + +#define UMM_H_ATTPACKPRE +#define UMM_H_ATTPACKSUF __attribute__((__packed__)) + +/* + * A couple of macros to make it easier to protect the memory allocator + * in a multitasking system. You should set these macros up to use whatever + * your system uses for this purpose. You can disable interrupts entirely, or + * just disable task switching - it's up to you + * + * NOTE WELL that these macros MUST be allowed to nest, because umm_free() is + * called from within umm_malloc() + */ + +#define UMM_CRITICAL_ENTRY() ets_intr_lock() +#define UMM_CRITICAL_EXIT() ets_intr_unlock() + +/* + * -D UMM_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 UMM_INTEGRITY_CHECK +*/ + +/* + * -D UMM_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 UMM_POISON + +#define UMM_POISON_SIZE_BEFORE 4 +#define UMM_POISON_SIZE_AFTER 4 +#define UMM_POISONED_BLOCK_LEN_TYPE uint32_t + +#define UMM_HEAP_CORRUPTION_CB() panic() +#endif /* _UMM_MALLOC_CFG_H */ diff --git a/tools/sdk/ld/eagle.app.v6.common.ld b/tools/sdk/ld/eagle.app.v6.common.ld index 08d8e5523..c15376233 100644 --- a/tools/sdk/ld/eagle.app.v6.common.ld +++ b/tools/sdk/ld/eagle.app.v6.common.ld @@ -150,7 +150,8 @@ SECTIONS .irom0.text : ALIGN(4) { _irom0_text_start = ABSOLUTE(.); - *.c.o(.literal*, .text*) + *.c.o( EXCLUDE_FILE (umm_malloc.c.o) .literal*, \ + EXCLUDE_FILE (umm_malloc.c.o) .text*) *.cpp.o(.literal*, .text*) *libm.a:(.literal .text .literal.* .text.*) *libsmartconfig.a:(.literal .text .literal.* .text.*) diff --git a/tools/sdk/lib/README.md b/tools/sdk/lib/README.md new file mode 100644 index 000000000..5e24e1952 --- /dev/null +++ b/tools/sdk/lib/README.md @@ -0,0 +1,13 @@ +## Updating SDK libraries + +After updating SDK libraries to a new version, do the following changes. + + +1. Weaken symbols from mem_manager.o to use custom heap implementation + + ```bash + xtensa-lx106-elf-ar -x libmain.a mem_manager.o + xtensa-lx106-elf-objcopy --weaken mem_manager.o + xtensa-lx106-elf-ar -r libmain.a mem_manager.o + rm mem_manager.o + ``` diff --git a/tools/sdk/lib/libmain.a b/tools/sdk/lib/libmain.a index 21b335157..e410f2404 100644 Binary files a/tools/sdk/lib/libmain.a and b/tools/sdk/lib/libmain.a differ