From c644da1769bb0638f06ab7327f1242ab5b8bdb61 Mon Sep 17 00:00:00 2001 From: Henrik Stickann <4376396-Mithradir@users.noreply.gitlab.com> Date: Fri, 20 Jan 2023 00:14:49 +0100 Subject: [PATCH] Add Dave Nadler's malloc implementation --- src/heap_useNewlib_ST.c | 287 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 src/heap_useNewlib_ST.c diff --git a/src/heap_useNewlib_ST.c b/src/heap_useNewlib_ST.c new file mode 100644 index 0000000..1fb3987 --- /dev/null +++ b/src/heap_useNewlib_ST.c @@ -0,0 +1,287 @@ +/** + * \file heap_useNewlib_ST.c + * \brief Wrappers required to use newlib malloc-family within FreeRTOS. + * + * \par Overview + * Route FreeRTOS memory management functions to newlib's malloc family. + * Thus newlib and FreeRTOS share memory-management routines and memory pool, + * and all newlib's internal memory-management requirements are supported. + * + * \author Dave Nadler + * \date 20-August-2019 + * \version 27-Jun-2020 Correct "FreeRTOS.h" capitalization, commentary + * \version 24-Jun-2020 commentary only + * \version 11-Sep-2019 malloc accounting, comments, newlib version check + * + * \see http://www.nadler.com/embedded/newlibAndFreeRTOS.html + * \see https://sourceware.org/newlib/libc.html#Reentrancy + * \see https://sourceware.org/newlib/libc.html#malloc + * \see https://sourceware.org/newlib/libc.html#index-_005f_005fenv_005flock + * \see https://sourceware.org/newlib/libc.html#index-_005f_005fmalloc_005flock + * \see https://sourceforge.net/p/freertos/feature-requests/72/ + * \see http://www.billgatliff.com/newlib.html + * \see http://wiki.osdev.org/Porting_Newlib + * \see http://www.embecosm.com/appnotes/ean9/ean9-howto-newlib-1.0.html + * + * + * \copyright + * (c) Dave Nadler 2017-2020, All Rights Reserved. + * Web: http://www.nadler.com + * email: drn@nadler.com + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * - Use or redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * - Use or redistributions of source code must retain ALL ORIGINAL COMMENTS, AND + * ANY CHANGES MUST BE DOCUMENTED, INCLUDING: + * - Reason for change (purpose) + * - Functional change + * - Date and author contact + * + * - 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. + * + * 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 HOLDER 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. + */ + +// ================================================================================================ +// ======================================= Configuration ======================================== +// These configuration symbols could be provided by from build... +#define STM_VERSION // Replace sane LD symbols with STM CubeMX's poor standard exported LD symbols +#define ISR_STACK_LENGTH_BYTES (configISR_STACK_SIZE_WORDS*4) // bytes to reserve for ISR (MSP) stack +// ======================================= Configuration ======================================== +// ================================================================================================ + + +#include // maps to newlib... +#include // mallinfo... +#include // ENOMEM +#include +#include + +#include "newlib.h" +#if ((__NEWLIB__ == 2) && (__NEWLIB_MINOR__ < 5)) ||((__NEWLIB__ == 3) && (__NEWLIB_MINOR__ > 1)) + #warning "This wrapper was verified for newlib versions 2.5 - 3.1; please ensure newlib's external requirements for malloc-family are unchanged!" +#endif + +#include "FreeRTOS.h" // defines public interface we're implementing here +#if !defined(configUSE_NEWLIB_REENTRANT) || (configUSE_NEWLIB_REENTRANT!=1) + #warning "#define configUSE_NEWLIB_REENTRANT 1 // Required for thread-safety of newlib sprintf, dtoa, strtok, etc..." + // If you're *REALLY* sure you don't need FreeRTOS's newlib reentrancy support, comment out the above warning... +#endif +#include "task.h" + +// ================================================================================================ +// External routines required by newlib's malloc (sbrk/_sbrk, __malloc_lock/unlock) +// ================================================================================================ + +// Simplistic sbrk implementations assume stack grows downwards from top of memory, +// and heap grows upwards starting just after BSS. +// FreeRTOS normally allocates task stacks from a pool placed within BSS or DATA. +// Thus within a FreeRTOS task, stack pointer is always below end of BSS. +// When using this module, stacks are allocated from malloc pool, still always prior +// current unused heap area... + +// Doesn't work with FreeRTOS: STM CubeMX 2018-2019 Incorrect Implementation +#if 0 + caddr_t _sbrk(int incr) + { + extern char end asm("end"); // From linker: lowest unused RAM address, just beyond end of BSS. + static char *heap_end; + char *prev_heap_end; + if (heap_end == 0) heap_end = &end; + prev_heap_end = heap_end; + if (heap_end + incr > stack_ptr) // Fails here: always true for FreeRTOS task stacks + { + errno = ENOMEM; // ...so first call inside a FreeRTOS task lands here + return (caddr_t) -1; + } + heap_end += incr; + return (caddr_t) prev_heap_end; + } +#endif + +register char * stack_ptr asm("sp"); + +#ifdef STM_VERSION // Use STM CubeMX LD symbols for heap+stack area + // To avoid modifying STM LD file (and then having CubeMX trash it), use available STM symbols + // Unfortunately STM does not provide standardized markers for RAM suitable for heap! + // STM CubeMX-generated LD files provide the following symbols: + // end /* aligned first word beyond BSS */ + // _estack /* one word beyond end of "RAM" Ram type memory, for STM32F429 0x20030000 */ + // Kludge below uses CubeMX-generated symbols instead of sane LD definitions + #define __HeapBase end + #define __HeapLimit _estack // In K64F LD this is already adjusted for ISR stack space... + static int heapBytesRemaining; + // no DRN HEAP_SIZE symbol from LD... // that's (&__HeapLimit)-(&__HeapBase) + uint32_t TotalHeapSize; // publish for diagnostic routines; filled in first _sbrk call. +#else + // Note: DRN's K64F LD provided: __StackTop (byte beyond end of memory), __StackLimit, HEAP_SIZE, STACK_SIZE + // __HeapLimit was already adjusted to be below reserved stack area. + extern char HEAP_SIZE; // make sure to define this symbol in linker LD command file + static int heapBytesRemaining = (int)&HEAP_SIZE; // that's (&__HeapLimit)-(&__HeapBase) +#endif + + +#ifdef MALLOCS_INSIDE_ISRs // STM code to avoid malloc within ISR (USB CDC stack) + // We can't use vTaskSuspendAll() within an ISR. + // STM's stunningly bad coding malpractice calls malloc within ISRs (for example, on USB connect function USBD_CDC_Init) + // So, we must just suspend/resume interrupts, lengthening max interrupt response time, aarrggg... + #define DRN_ENTER_CRITICAL_SECTION(_usis) { _usis = taskENTER_CRITICAL_FROM_ISR(); } // Disables interrupts (after saving prior state) + #define DRN_EXIT_CRITICAL_SECTION(_usis) { taskEXIT_CRITICAL_FROM_ISR(_usis); } // Re-enables interrupts (unless already disabled prior taskENTER_CRITICAL) +#else + #define DRN_ENTER_CRITICAL_SECTION(_usis) vTaskSuspendAll(); // Note: safe to use before FreeRTOS scheduler started, but not in ISR + #define DRN_EXIT_CRITICAL_SECTION(_usis) xTaskResumeAll(); // Note: safe to use before FreeRTOS scheduler started, but not in ISR +#endif + +#ifndef NDEBUG + static int totalBytesProvidedBySBRK = 0; +#endif +extern char __HeapBase, __HeapLimit; // symbols from linker LD command file + +// Use of vTaskSuspendAll() in _sbrk_r() is normally redundant, as newlib malloc family routines call +// __malloc_lock before calling _sbrk_r(). Note vTaskSuspendAll/xTaskResumeAll support nesting. + +//! _sbrk_r version supporting reentrant newlib (depends upon above symbols defined by linker control file). +void * _sbrk_r(struct _reent *pReent, int incr) { + #ifdef MALLOCS_INSIDE_ISRs // block interrupts during free-storage use + UBaseType_t usis; // saved interrupt status + #endif + static char *currentHeapEnd = &__HeapBase; + #ifdef STM_VERSION // Use STM CubeMX LD symbols for heap + if(TotalHeapSize==0) { + TotalHeapSize = heapBytesRemaining = (int)((&__HeapLimit)-(&__HeapBase))-ISR_STACK_LENGTH_BYTES; + }; + #endif + char* limit = (xTaskGetSchedulerState()==taskSCHEDULER_NOT_STARTED) ? + stack_ptr : // Before scheduler is started, limit is stack pointer (risky!) + &__HeapLimit-ISR_STACK_LENGTH_BYTES; // Once running, OK to reuse all remaining RAM except ISR stack (MSP) stack + DRN_ENTER_CRITICAL_SECTION(usis); + if (currentHeapEnd + incr > limit) { + // Ooops, no more memory available... + #if( configUSE_MALLOC_FAILED_HOOK == 1 ) + { + extern void vApplicationMallocFailedHook( void ); + DRN_EXIT_CRITICAL_SECTION(usis); + vApplicationMallocFailedHook(); + } + #elif defined(configHARD_STOP_ON_MALLOC_FAILURE) + // If you want to alert debugger or halt... + // WARNING: brkpt instruction may prevent watchdog operation... + while(1) { __asm("bkpt #0"); }; // Stop in GUI as if at a breakpoint (if debugging, otherwise loop forever) + #else + // Default, if you prefer to believe your application will gracefully trap out-of-memory... + pReent->_errno = ENOMEM; // newlib's thread-specific errno + DRN_EXIT_CRITICAL_SECTION(usis); + #endif + return (char *)-1; // the malloc-family routine that called sbrk will return 0 + } + // 'incr' of memory is available: update accounting and return it. + char *previousHeapEnd = currentHeapEnd; + currentHeapEnd += incr; + heapBytesRemaining -= incr; + #ifndef NDEBUG + totalBytesProvidedBySBRK += incr; + #endif + DRN_EXIT_CRITICAL_SECTION(usis); + return (char *) previousHeapEnd; +} +//! non-reentrant sbrk uses is actually reentrant by using current context +// ... because the current _reent structure is pointed to by global _impure_ptr +char * sbrk(int incr) { return _sbrk_r(_impure_ptr, incr); } +//! _sbrk is a synonym for sbrk. +char * _sbrk(int incr) { return sbrk(incr); }; + +#ifdef MALLOCS_INSIDE_ISRs // block interrupts during free-storage use + static UBaseType_t malLock_uxSavedInterruptStatus; +#endif +void __malloc_lock(struct _reent *r) { + (void)(r); + #if defined(MALLOCS_INSIDE_ISRs) + DRN_ENTER_CRITICAL_SECTION(malLock_uxSavedInterruptStatus); + #else + bool insideAnISR = xPortIsInsideInterrupt(); + configASSERT( !insideAnISR ); // Make damn sure no more mallocs inside ISRs!! + vTaskSuspendAll(); + #endif +}; +void __malloc_unlock(struct _reent *r) { + (void)(r); + #if defined(MALLOCS_INSIDE_ISRs) + DRN_EXIT_CRITICAL_SECTION(malLock_uxSavedInterruptStatus); + #else + (void)xTaskResumeAll(); + #endif +}; + +// newlib also requires implementing locks for the application's environment memory space, +// accessed by newlib's setenv() and getenv() functions. +// As these are trivial functions, momentarily suspend task switching (rather than semaphore). +// Not required (and trimmed by linker) in applications not using environment variables. +// ToDo: Move __env_lock/unlock to a separate newlib helper file. +void __env_lock() { vTaskSuspendAll(); }; +void __env_unlock() { (void)xTaskResumeAll(); }; + +#if 1 // Provide malloc debug and accounting wrappers + /// /brief Wrap malloc/malloc_r to help debug who requests memory and why. + /// To use these, add linker options: -Xlinker --wrap=malloc -Xlinker --wrap=_malloc_r + // Note: These functions are normally unused and stripped by linker. + size_t TotalMallocdBytes; + int MallocCallCnt; + static bool inside_malloc; + void *__wrap_malloc(size_t nbytes) { + extern void * __real_malloc(size_t nbytes); + MallocCallCnt++; + TotalMallocdBytes += nbytes; + inside_malloc = true; + void *p = __real_malloc(nbytes); // will call malloc_r... + inside_malloc = false; + return p; + }; + void *__wrap__malloc_r(void *reent, size_t nbytes) { + (void)(reent); + extern void * __real__malloc_r(size_t nbytes); + if(!inside_malloc) { + MallocCallCnt++; + TotalMallocdBytes += nbytes; + }; + void *p = __real__malloc_r(nbytes); + return p; + }; +#endif + +// ================================================================================================ +// Implement FreeRTOS's memory API using newlib-provided malloc family. +// ================================================================================================ + +void *pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION { + void *p = malloc(xSize); + return p; +} +void vPortFree( void *pv ) PRIVILEGED_FUNCTION { + free(pv); +}; + +size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION { + struct mallinfo mi = mallinfo(); // available space now managed by newlib + return mi.fordblks + heapBytesRemaining; // plus space not yet handed to newlib by sbrk +} + +// GetMinimumEverFree is not available in newlib's malloc implementation. +// So, no implementation is provided: size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION; + +//! No implementation needed, but stub provided in case application already calls vPortInitialiseBlocks +void vPortInitialiseBlocks( void ) PRIVILEGED_FUNCTION {};