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