|
| 1 | +/** |
| 2 | + * \file heap_useNewlib.c |
| 3 | + * \brief Wrappers required to use newlib malloc-family within FreeRTOS. |
| 4 | + * |
| 5 | + * \par Overview |
| 6 | + * Route FreeRTOS memory management functions to newlib's malloc family. |
| 7 | + * Thus newlib and FreeRTOS share memory-management routines and memory pool, |
| 8 | + * and all newlib's internal memory-management requirements are supported. |
| 9 | + * |
| 10 | + * \author Dave Nadler |
| 11 | + * \date 7-August-2019 |
| 12 | + * \version 23-Sep-2019 comments, check no malloc call inside ISR |
| 13 | + * |
| 14 | + * \see http://www.nadler.com/embedded/newlibAndFreeRTOS.html |
| 15 | + * \see https://sourceware.org/newlib/libc.html#Reentrancy |
| 16 | + * \see https://sourceware.org/newlib/libc.html#malloc |
| 17 | + * \see https://sourceware.org/newlib/libc.html#index-_005f_005fenv_005flock |
| 18 | + * \see https://sourceware.org/newlib/libc.html#index-_005f_005fmalloc_005flock |
| 19 | + * \see https://sourceforge.net/p/freertos/feature-requests/72/ |
| 20 | + * \see http://www.billgatliff.com/newlib.html |
| 21 | + * \see http://wiki.osdev.org/Porting_Newlib |
| 22 | + * \see http://www.embecosm.com/appnotes/ean9/ean9-howto-newlib-1.0.html |
| 23 | + * |
| 24 | + * |
| 25 | + * \copyright |
| 26 | + * (c) Dave Nadler 2017-2019, All Rights Reserved. |
| 27 | + * Web: http://www.nadler.com |
| 28 | + * email: drn@nadler.com |
| 29 | + * |
| 30 | + * Redistribution and use in source and binary forms, with or without modification, |
| 31 | + * are permitted provided that the following conditions are met: |
| 32 | + * |
| 33 | + * - Use or redistributions of source code must retain the above copyright notice, |
| 34 | + * this list of conditions, ALL ORIGINAL COMMENTS, and the following disclaimer. |
| 35 | + * |
| 36 | + * - Redistributions in binary form must reproduce the above copyright notice, this |
| 37 | + * list of conditions and the following disclaimer in the documentation and/or |
| 38 | + * other materials provided with the distribution. |
| 39 | + * |
| 40 | + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 41 | + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 42 | + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 43 | + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
| 44 | + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 45 | + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 46 | + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| 47 | + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 48 | + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 49 | + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 50 | + */ |
| 51 | + |
| 52 | + // ================================================================================================ |
| 53 | + // ======================================= Configuration ======================================== |
| 54 | + // These configuration symbols could be provided by from build... |
| 55 | +#define STM_VERSION // use STM standard exported LD symbols |
| 56 | +//#define SUPPORT_MALLOCS_INSIDE_ISRs // #define iff you have this crap code (ie unrepaired STM USB CDC) |
| 57 | +#define SUPPORT_ISR_STACK_MONITOR // #define to enable ISR (MSP) stack diagnostics |
| 58 | +#define ISR_STACK_LENGTH_BYTES 512 // #define bytes to reserve for ISR (MSP) stack |
| 59 | +// ======================================= Configuration ======================================== |
| 60 | +// ================================================================================================ |
| 61 | + |
| 62 | + |
| 63 | +#include <stdlib.h> // maps to newlib... |
| 64 | +#include <malloc.h> // mallinfo... |
| 65 | +#include <errno.h> // ENOMEM |
| 66 | +#include <stdbool.h> |
| 67 | +#include <stddef.h> |
| 68 | + |
| 69 | +#include "newlib.h" |
| 70 | +#if (__NEWLIB__ != 3) || (__NEWLIB_MINOR__ != 0) |
| 71 | +#warning "This wrapper was verified for newlib version 3.0.0; please ensure newlib's external requirements for malloc-family are unchanged!" |
| 72 | +#endif |
| 73 | + |
| 74 | +#include "freeRTOS.h" // defines public interface we're implementing here |
| 75 | +#if !defined(configUSE_NEWLIB_REENTRANT) || (configUSE_NEWLIB_REENTRANT!=1) |
| 76 | +#warning "#define configUSE_NEWLIB_REENTRANT 1 // Required for thread-safety of newlib sprintf, strtok, etc..." |
| 77 | +// If you're *REALLY* sure you don't need FreeRTOS's newlib reentrancy support, remove this warning... |
| 78 | +#endif |
| 79 | +#include "task.h" |
| 80 | + |
| 81 | +// ================================================================================================ |
| 82 | +// External routines required by newlib's malloc (sbrk/_sbrk, __malloc_lock/unlock) |
| 83 | +// ================================================================================================ |
| 84 | + |
| 85 | +// Simplistic sbrk implementations assume stack grows downwards from top of memory, |
| 86 | +// and heap grows upwards starting just after BSS. |
| 87 | +// FreeRTOS normally allocates task stacks from a pool placed within BSS or DATA. |
| 88 | +// Thus within a FreeRTOS task, stack pointer is always below end of BSS. |
| 89 | +// When using this module, stacks are allocated from malloc pool, still always prior |
| 90 | +// current unused heap area... |
| 91 | +#if 0 // STM CubeMX 2018-2019 Incorrect Implementation (fails for FreeRTOS) |
| 92 | +caddr_t _sbrk(int incr) |
| 93 | +{ |
| 94 | + extern char end asm("end"); // lowest unused RAM address, just beyond end of BSS. |
| 95 | + static char* heap_end; |
| 96 | + char* prev_heap_end; |
| 97 | + |
| 98 | + if (heap_end == 0) heap_end = &end; |
| 99 | + prev_heap_end = heap_end; |
| 100 | + if (heap_end + incr > stack_ptr) // of course, always true for FreeRTOS task stacks |
| 101 | + { |
| 102 | + errno = ENOMEM; // ...so first call inside a FreeRTOS task lands here |
| 103 | + return (caddr_t)-1; |
| 104 | + } |
| 105 | + heap_end += incr; |
| 106 | + return (caddr_t)prev_heap_end; |
| 107 | +} |
| 108 | +#endif |
| 109 | + |
| 110 | +register char* stack_ptr asm("sp"); |
| 111 | + |
| 112 | +#ifdef STM_VERSION // Use STM CubeMX LD symbols for heap+stack area |
| 113 | +// To avoid modifying STM LD file (and then having CubeMX trash it), use available STM symbols |
| 114 | +// Unfortunately STM does not provide standardized markers for RAM suitable for heap! |
| 115 | +// STM CubeMX-generated LD files provide the following symbols: |
| 116 | +// end /* aligned first word beyond BSS */ |
| 117 | +// _estack /* one word beyond end of "RAM" Ram type memory, for STM32F429 0x20030000 */ |
| 118 | +// Kludge below uses CubeMX-generated symbols instead of sane LD definitions |
| 119 | +#define __HeapBase end |
| 120 | +#define __HeapLimit _estack // except in K64F this was already adjusted in LD for stack... |
| 121 | +static int heapBytesRemaining; |
| 122 | +// no DRN HEAP_SIZE symbol from LD... // that's (&__HeapLimit)-(&__HeapBase) |
| 123 | +uint32_t TotalHeapSize; // publish for diagnostic routines; filled in first _sbrk call. |
| 124 | +#else |
| 125 | +// Note: DRN's K64F LD provided: __StackTop (byte beyond end of memory), __StackLimit, HEAP_SIZE, STACK_SIZE |
| 126 | +// __HeapLimit was already adjusted to be below reserved stack area. |
| 127 | +extern char HEAP_SIZE; // make sure to define this symbol in linker LD command file |
| 128 | +static int heapBytesRemaining = (int)& HEAP_SIZE; // that's (&__HeapLimit)-(&__HeapBase) |
| 129 | +#endif |
| 130 | + |
| 131 | + |
| 132 | +#ifdef MALLOCS_INSIDE_ISRs // STM code to avoid malloc within ISR (USB CDC stack) |
| 133 | + // We can't use vTaskSuspendAll() within an ISR. |
| 134 | + // STM's stunningly bad coding malpractice calls malloc within ISRs (for example, on USB connect function USBD_CDC_Init) |
| 135 | + // So, we must just suspend/resume interrupts, lengthening max interrupt response time, aarrggg... |
| 136 | +#define DRN_ENTER_CRITICAL_SECTION(_usis) { _usis = taskENTER_CRITICAL_FROM_ISR(); } // Disables interrupts (after saving prior state) |
| 137 | +#define DRN_EXIT_CRITICAL_SECTION(_usis) { taskEXIT_CRITICAL_FROM_ISR(_usis); } // Re-enables interrupts (unless already disabled prior taskENTER_CRITICAL) |
| 138 | +#else |
| 139 | +#define DRN_ENTER_CRITICAL_SECTION(_usis) vTaskSuspendAll(); // Note: safe to use before FreeRTOS scheduler started, but not in ISR |
| 140 | +#define DRN_EXIT_CRITICAL_SECTION(_usis) xTaskResumeAll(); // Note: safe to use before FreeRTOS scheduler started, but not in ISR |
| 141 | +#endif |
| 142 | + |
| 143 | +#ifndef NDEBUG |
| 144 | +static int totalBytesProvidedBySBRK = 0; |
| 145 | +#endif |
| 146 | +extern char __HeapBase, __HeapLimit; // make sure to define these symbols in linker LD command file |
| 147 | + |
| 148 | +//! _sbrk_r version supporting reentrant newlib (depends upon above symbols defined by linker control file). |
| 149 | +void* _sbrk_r(struct _reent* pReent, int incr) { |
| 150 | +#ifdef MALLOCS_INSIDE_ISRs // block interrupts during free-storage use |
| 151 | + UBaseType_t usis; // saved interrupt status |
| 152 | +#endif |
| 153 | + static char* currentHeapEnd = &__HeapBase; |
| 154 | +#ifdef STM_VERSION // Use STM CubeMX LD symbols for heap |
| 155 | + if (TotalHeapSize == 0) { |
| 156 | + TotalHeapSize = heapBytesRemaining = (int)((&__HeapLimit) - (&__HeapBase)) - ISR_STACK_LENGTH_BYTES; |
| 157 | + }; |
| 158 | +#endif |
| 159 | + char* limit = (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) ? |
| 160 | + stack_ptr : // Before scheduler is started, limit is stack pointer (risky!) |
| 161 | + &__HeapLimit - ISR_STACK_LENGTH_BYTES; // Once running, OK to reuse all remaining RAM except ISR stack (MSP) stack |
| 162 | + DRN_ENTER_CRITICAL_SECTION(usis); |
| 163 | + char* previousHeapEnd = currentHeapEnd; |
| 164 | + if (currentHeapEnd + incr > limit) { |
| 165 | + // Ooops, no more memory available... |
| 166 | +#if( configUSE_MALLOC_FAILED_HOOK == 1 ) |
| 167 | + { |
| 168 | + extern void vApplicationMallocFailedHook(void); |
| 169 | + DRN_EXIT_CRITICAL_SECTION(usis); |
| 170 | + vApplicationMallocFailedHook(); |
| 171 | + } |
| 172 | +#elif defined(configHARD_STOP_ON_MALLOC_FAILURE) |
| 173 | + // If you want to alert debugger or halt... |
| 174 | + // WARNING: brkpt instruction may prevent watchdog operation... |
| 175 | + while (1) { __asm("bkpt #0"); }; // Stop in GUI as if at a breakpoint (if debugging, otherwise loop forever) |
| 176 | +#else |
| 177 | + // Default, if you prefer to believe your application will gracefully trap out-of-memory... |
| 178 | + pReent->_errno = ENOMEM; // newlib's thread-specific errno |
| 179 | + DRN_EXIT_CRITICAL_SECTION(usis); |
| 180 | +#endif |
| 181 | + return (char*)-1; // the malloc-family routine that called sbrk will return 0 |
| 182 | + } |
| 183 | + // 'incr' of memory is available: update accounting and return it. |
| 184 | + currentHeapEnd += incr; |
| 185 | + heapBytesRemaining -= incr; |
| 186 | +#ifndef NDEBUG |
| 187 | + totalBytesProvidedBySBRK += incr; |
| 188 | +#endif |
| 189 | + DRN_EXIT_CRITICAL_SECTION(usis); |
| 190 | + return (char*)previousHeapEnd; |
| 191 | +} |
| 192 | +//! non-reentrant sbrk uses is actually reentrant by using current context |
| 193 | +// ... because the current _reent structure is pointed to by global _impure_ptr |
| 194 | +char* sbrk(int incr) { return _sbrk_r(_impure_ptr, incr); } |
| 195 | +//! _sbrk is a synonym for sbrk. |
| 196 | +char* _sbrk(int incr) { return sbrk(incr); }; |
| 197 | + |
| 198 | +#ifdef MALLOCS_INSIDE_ISRs // block interrupts during free-storage use |
| 199 | +static UBaseType_t malLock_uxSavedInterruptStatus; |
| 200 | +#endif |
| 201 | +void __malloc_lock(struct _reent* r) { |
| 202 | +#if defined(MALLOCS_INSIDE_ISRs) |
| 203 | + DRN_ENTER_CRITICAL_SECTION(malLock_uxSavedInterruptStatus); |
| 204 | +#else |
| 205 | + bool insideAnISR = xPortIsInsideInterrupt(); |
| 206 | + configASSERT(!insideAnISR); // Make damn sure no more mallocs inside ISRs!! |
| 207 | + vTaskSuspendAll(); |
| 208 | +#endif |
| 209 | +}; |
| 210 | +void __malloc_unlock(struct _reent* r) { |
| 211 | +#if defined(MALLOCS_INSIDE_ISRs) |
| 212 | + DRN_EXIT_CRITICAL_SECTION(malLock_uxSavedInterruptStatus); |
| 213 | +#else |
| 214 | + (void)xTaskResumeAll(); |
| 215 | +#endif |
| 216 | +}; |
| 217 | + |
| 218 | +// newlib also requires implementing locks for the application's environment memory space, |
| 219 | +// accessed by newlib's setenv() and getenv() functions. |
| 220 | +// As these are trivial functions, momentarily suspend task switching (rather than semaphore). |
| 221 | +// ToDo: Move __env_lock/unlock to a separate newlib helper file. |
| 222 | +void __env_lock() { vTaskSuspendAll(); }; |
| 223 | +void __env_unlock() { (void)xTaskResumeAll(); }; |
| 224 | + |
| 225 | +#if 1 // Provide malloc debug and accounting wrappers |
| 226 | +/// /brief Wrap malloc/malloc_r to help debug who requests memory and why. |
| 227 | +/// To use these, add linker options: -Xlinker --wrap=malloc -Xlinker --wrap=_malloc_r |
| 228 | +// Note: These functions are normally unused and stripped by linker. |
| 229 | +int TotalMallocdBytes; |
| 230 | +int MallocCallCnt; |
| 231 | +static bool inside_malloc; |
| 232 | +void* __wrap_malloc(size_t nbytes) { |
| 233 | + extern void* __real_malloc(size_t nbytes); |
| 234 | + MallocCallCnt++; |
| 235 | + TotalMallocdBytes += nbytes; |
| 236 | + inside_malloc = true; |
| 237 | + void* p = __real_malloc(nbytes); // will call malloc_r... |
| 238 | + inside_malloc = false; |
| 239 | + return p; |
| 240 | +}; |
| 241 | +void* __wrap__malloc_r(void* reent, size_t nbytes) { |
| 242 | + extern void* __real__malloc_r(size_t nbytes); |
| 243 | + if (!inside_malloc) { |
| 244 | + MallocCallCnt++; |
| 245 | + TotalMallocdBytes += nbytes; |
| 246 | + }; |
| 247 | + void* p = __real__malloc_r(nbytes); |
| 248 | + return p; |
| 249 | +}; |
| 250 | +#endif |
| 251 | + |
| 252 | +// ================================================================================================ |
| 253 | +// Implement FreeRTOS's memory API using newlib-provided malloc family. |
| 254 | +// ================================================================================================ |
| 255 | + |
| 256 | +void* pvPortMalloc(size_t xSize) PRIVILEGED_FUNCTION { |
| 257 | + void* p = malloc(xSize); |
| 258 | + return p; |
| 259 | +} |
| 260 | +void vPortFree(void* pv) PRIVILEGED_FUNCTION { |
| 261 | + free(pv); |
| 262 | +}; |
| 263 | + |
| 264 | +size_t xPortGetFreeHeapSize(void) PRIVILEGED_FUNCTION { |
| 265 | + struct mallinfo mi = mallinfo(); // available space now managed by newlib |
| 266 | + return mi.fordblks + heapBytesRemaining; // plus space not yet handed to newlib by sbrk |
| 267 | +} |
| 268 | + |
| 269 | +// GetMinimumEverFree is not available in newlib's malloc implementation. |
| 270 | +// So, no implementation is provided: size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION; |
| 271 | + |
| 272 | +//! No implementation needed, but stub provided in case application already calls vPortInitialiseBlocks |
| 273 | +void vPortInitialiseBlocks(void) PRIVILEGED_FUNCTION {}; |
0 commit comments