From 419cedf58e4535e72bf5062aa23a5ce7976759b2 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Wed, 6 Feb 2019 12:33:02 +0100 Subject: [PATCH] cpu/msp430_common: add real malloc/free functions For this purpose, adapted AVR libc functions are used. When used, malloc and free functions require 304 additional bytes of code compared to the oneway_malloc module. --- cpu/msp430_common/Makefile.include | 4 +- cpu/msp430_common/malloc.c | 460 +++++++++++++++++++++++++++++ cpu/msp430_common/msp430-main.c | 2 +- 3 files changed, 464 insertions(+), 2 deletions(-) create mode 100644 cpu/msp430_common/malloc.c diff --git a/cpu/msp430_common/Makefile.include b/cpu/msp430_common/Makefile.include index ab6e0cf8c0d3..3e358b58715a 100644 --- a/cpu/msp430_common/Makefile.include +++ b/cpu/msp430_common/Makefile.include @@ -1,3 +1,5 @@ +PSEUDOMODULES += msp430_malloc + INCLUDES += -I$(RIOTCPU)/msp430_common/include/ # export the CPU model @@ -5,7 +7,7 @@ MODEL = $(shell echo $(CPU_MODEL) | tr 'a-z' 'A-Z') export CFLAGS += -DCPU_MODEL_$(MODEL) export UNDEF += $(BINDIR)/msp430_common/startup.o -export USEMODULE += msp430_common msp430_common_periph periph_common +export USEMODULE += msp430_common msp430_common_periph msp430_malloc periph_common DEFAULT_MODULE += oneway_malloc diff --git a/cpu/msp430_common/malloc.c b/cpu/msp430_common/malloc.c new file mode 100644 index 000000000000..354d94ec029d --- /dev/null +++ b/cpu/msp430_common/malloc.c @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2002, 2004, 2010 Joerg Wunsch + * Copyright (c) 2010 Gerben van den Broeke + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Modified by Gunar Schorcht + */ + +/** + * @ingroup cpu + * @{ + * + * @file + * @brief MSP430 malloc/free memory management functions + * + * AVR libc functions adapted for MSP430 CPUs. + * + * @author Gunar Schorcht + * + * @} + */ + +#include +#include + +#include "irq.h" + +#ifdef MODULE_MSP430_MALLOC + +#define asmv(arg) __asm__ __volatile__(arg) + +#define STACK_EXTRA 32 + +#define __brkval cur_break +#define __malloc_heap_end __heap_end + +/* May be changed by the user only before the first malloc() call. */ +extern int _end; /* provided by linker script */ +extern char *cur_break; +extern char *__heap_end; + +struct __freelist { + size_t sz; + struct __freelist *nx; +}; + +size_t __malloc_margin = STACK_EXTRA; +char *__malloc_heap_start = (char*)&_end; + +struct __freelist *__flp; + +void * +malloc(size_t len) +{ + struct __freelist *fp1, *fp2, *sfp1, *sfp2; + char *cp; + size_t s; + unsigned state; + + state = irq_disable(); + + /* + * Our minimum chunk size is the size of a pointer (plus the + * size of the "sz" field, but we don't need to account for + * this), otherwise we could not possibly fit a freelist entry + * into the chunk later. + */ + if (len < sizeof(struct __freelist) - sizeof(size_t)) { + len = sizeof(struct __freelist) - sizeof(size_t); + } + + /* + * First, walk the free list and try finding a chunk that + * would match exactly. If we found one, we are done. While + * walking, note down the smallest chunk we found that would + * still fit the request -- we need it for step 2. + * + */ + for (s = 0, fp1 = __flp, fp2 = 0; fp1; fp2 = fp1, fp1 = fp1->nx) { + if (fp1->sz < len) { + continue; + } + if (fp1->sz == len) { + /* + * Found it. Disconnect the chunk from the + * freelist, and return it. + */ + if (fp2) { + fp2->nx = fp1->nx; + } + else { + __flp = fp1->nx; + } + irq_restore(state); + return &(fp1->nx); + } + else { + if (s == 0 || fp1->sz < s) { + /* this is the smallest chunk found so far */ + s = fp1->sz; + sfp1 = fp1; + sfp2 = fp2; + } + } + } + /* + * Step 2: If we found a chunk on the freelist that would fit + * (but was too large), look it up again and use it, since it + * is our closest match now. Since the freelist entry needs + * to be split into two entries then, watch out that the + * difference between the requested size and the size of the + * chunk found is large enough for another freelist entry; if + * not, just enlarge the request size to what we have found, + * and use the entire chunk. + */ + if (s) { + if (s - len < sizeof(struct __freelist)) { + /* Disconnect it from freelist and return it. */ + if (sfp2) { + sfp2->nx = sfp1->nx; + } + else { + __flp = sfp1->nx; + } + irq_restore(state); + return &(sfp1->nx); + } + /* + * Split them up. Note that we leave the first part + * as the new (smaller) freelist entry, and return the + * upper portion to the caller. This saves us the + * work to fix up the freelist chain; we just need to + * fixup the size of the current entry, and note down + * the size of the new chunk before returning it to + * the caller. + */ + cp = (char *)sfp1; + s -= len; + cp += s; + sfp2 = (struct __freelist *)cp; + sfp2->sz = len; + sfp1->sz = s - sizeof(size_t); + irq_restore(state); + return &(sfp2->nx); + } + /* + * Step 3: If the request could not be satisfied from a + * freelist entry, just prepare a new chunk. This means we + * need to obtain more memory first. The largest address just + * not allocated so far is remembered in the brkval variable. + * Under Unix, the "break value" was the end of the data + * segment as dynamically requested from the operating system. + * Since we don't have an operating system, just make sure + * that we don't collide with the stack. + */ + size_t avail; + if (__brkval == NULL) { + __brkval = __malloc_heap_start; + } + cp = __malloc_heap_end; + if (cp == NULL) { + char *stack_pointer; + asmv("mov r1, %0" : "=r"(stack_pointer)); + cp = stack_pointer - __malloc_margin; + } + if (cp <= __brkval) { + /* + * Memory exhausted. + */ + irq_restore(state); + return 0; + } + avail = cp - __brkval; + /* + * Both tests below are needed to catch the case len >= 0xfffe. + */ + if (avail >= len && avail >= len + sizeof(size_t)) { + fp1 = (struct __freelist *)__brkval; + __brkval += len + sizeof(size_t); + fp1->sz = len; + irq_restore(state); + return &(fp1->nx); + } + /* + * Step 4: There's no help, just fail. :-/ + */ + irq_restore(state); + return 0; +} + +void +free(void *p) +{ + struct __freelist *fp1, *fp2, *fpnew; + char *cp1, *cp2, *cpnew; + unsigned state; + + state = irq_disable(); + + /* ISO C says free(NULL) must be a no-op */ + if (p == NULL) { + irq_restore(state); + return; + } + + cpnew = p; + cpnew -= sizeof(size_t); + fpnew = (struct __freelist *)cpnew; + fpnew->nx = 0; + + /* + * Trivial case first: if there's no freelist yet, our entry + * will be the only one on it. If this is the last entry, we + * can reduce __brkval instead. + */ + if (__flp == NULL) { + if ((char *)p + fpnew->sz == __brkval) { + __brkval = cpnew; + } + else { + __flp = fpnew; + } + irq_restore(state); + return; + } + + /* + * Now, find the position where our new entry belongs onto the + * freelist. Try to aggregate the chunk with adjacent chunks + * if possible. + */ + for (fp1 = __flp, fp2 = 0; + fp1; + fp2 = fp1, fp1 = fp1->nx) { + if (fp1 < fpnew) { + continue; + } + cp1 = (char *)fp1; + fpnew->nx = fp1; + if ((char *)&(fpnew->nx) + fpnew->sz == cp1) { + /* upper chunk adjacent, assimilate it */ + fpnew->sz += fp1->sz + sizeof(size_t); + fpnew->nx = fp1->nx; + } + if (fp2 == NULL) { + /* new head of freelist */ + __flp = fpnew; + irq_restore(state); + return; + } + break; + } + /* + * Note that we get here either if we hit the "break" above, + * or if we fell off the end of the loop. The latter means + * we've got a new topmost chunk. Either way, try aggregating + * with the lower chunk if possible. + */ + fp2->nx = fpnew; + cp2 = (char *)&(fp2->nx); + if (cp2 + fp2->sz == cpnew) { + /* lower junk adjacent, merge */ + fp2->sz += fpnew->sz + sizeof(size_t); + fp2->nx = fpnew->nx; + } + /* + * If there's a new topmost chunk, lower __brkval instead. + */ + for (fp1 = __flp, fp2 = 0; + fp1->nx != NULL; + fp2 = fp1, fp1 = fp1->nx) + /* advance to entry just before end of list */; + cp2 = (char *)&(fp1->nx); + if (cp2 + fp1->sz == __brkval) { + if (fp2 == NULL) { + /* Freelist is empty now. */ + __flp = NULL; + } + else { + fp2->nx = NULL; + } + __brkval = cp2 - sizeof(size_t); + } + + irq_restore(state); +} + +#include + +void * +realloc(void *ptr, size_t len) +{ + struct __freelist *fp1, *fp2, *fp3, *ofp3; + char *cp, *cp1; + void *memp; + size_t s, incr; + unsigned state; + + /* Trivial case, required by C standard. */ + if (ptr == NULL) { + return malloc(len); + } + + state = irq_disable(); + + cp1 = (char *)ptr; + cp1 -= sizeof(size_t); + fp1 = (struct __freelist *)cp1; + + cp = (char *)ptr + len; /* new next pointer */ + if (cp < cp1) { + /* Pointer wrapped across top of RAM, fail. */ + irq_restore(state); + return 0; + } + /* + * See whether we are growing or shrinking. When shrinking, + * we split off a chunk for the released portion, and call + * free() on it. Therefore, we can only shrink if the new + * size is at least sizeof(struct __freelist) smaller than the + * previous size. + */ + if (len <= fp1->sz) { + /* The first test catches a possible unsigned int + * rollover condition. */ + if (fp1->sz <= sizeof(struct __freelist) || + len > fp1->sz - sizeof(struct __freelist)) { + irq_restore(state); + return ptr; + } + fp2 = (struct __freelist *)cp; + fp2->sz = fp1->sz - len - sizeof(size_t); + fp1->sz = len; + free(&(fp2->nx)); + irq_restore(state); + return ptr; + } + + /* + * If we get here, we are growing. First, see whether there + * is space in the free list on top of our current chunk. + */ + incr = len - fp1->sz; + cp = (char *)ptr + fp1->sz; + fp2 = (struct __freelist *)cp; + for (s = 0, ofp3 = 0, fp3 = __flp; + fp3; + ofp3 = fp3, fp3 = fp3->nx) { + if (fp3 == fp2 && fp3->sz + sizeof(size_t) >= incr) { + /* found something that fits */ + if (fp3->sz + sizeof(size_t) - incr > sizeof(struct __freelist)) { + /* split off a new freelist entry */ + cp = (char *)ptr + len; + fp2 = (struct __freelist *)cp; + fp2->nx = fp3->nx; + fp2->sz = fp3->sz - incr; + fp1->sz = len; + } + else { + /* it just fits, so use it entirely */ + fp1->sz += fp3->sz + sizeof(size_t); + fp2 = fp3->nx; + } + if (ofp3) { + ofp3->nx = fp2; + } + else { + __flp = fp2; + } + irq_restore(state); + return ptr; + } + /* + * Find the largest chunk on the freelist while + * walking it. + */ + if (fp3->sz > s) { + s = fp3->sz; + } + } + /* + * If we are the topmost chunk in memory, and there was no + * large enough chunk on the freelist that could be re-used + * (by a call to malloc() below), quickly extend the + * allocation area if possible, without need to copy the old + * data. + */ + if (__brkval == (char *)ptr + fp1->sz && len > s) { + cp1 = __malloc_heap_end; + cp = (char *)ptr + len; + if (cp1 == NULL) { + char *stack_pointer; + asmv("mov r1, %0" : "=r"(stack_pointer)); + cp1 = stack_pointer - __malloc_margin; + } + if (cp < cp1) { + __brkval = cp; + fp1->sz = len; + irq_restore(state); + return ptr; + } + /* If that failed, we are out of luck. */ + irq_restore(state); + return 0; + } + + /* + * Call malloc() for a new chunk, then copy over the data, and + * release the old region. + */ + if ((memp = malloc(len)) == NULL) { + irq_restore(state); + return 0; + } + memcpy(memp, ptr, fp1->sz); + free(ptr); + irq_restore(state); + return memp; +} + +void * +calloc(size_t nele, size_t size) +{ + void *p; + + if ((p = malloc(nele * size)) == NULL) { + return 0; + } + memset(p, 0, nele * size); + return p; +} + +#endif /* MODULE_MSP430_MALLOC */ diff --git a/cpu/msp430_common/msp430-main.c b/cpu/msp430_common/msp430-main.c index aa171b68db5b..0dd702fa2fa3 100644 --- a/cpu/msp430_common/msp430-main.c +++ b/cpu/msp430_common/msp430-main.c @@ -107,7 +107,7 @@ init_ports(void) /*---------------------------------------------------------------------------*/ /* msp430-ld may align _end incorrectly. Workaround in cpu_init. */ extern int _end; /* Not in sys/unistd.h */ -static char *cur_break = (char *) &_end; +char *cur_break = (char *) &_end; void msp430_cpu_init(void) {