diff --git a/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/EmbeddedData/heap_useNewlib.c b/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/EmbeddedData/heap_useNewlib.c new file mode 100644 index 0000000..065060d --- /dev/null +++ b/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/EmbeddedData/heap_useNewlib.c @@ -0,0 +1,273 @@ +/** + * \file heap_useNewlib.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 7-August-2019 + * \version 23-Sep-2019 comments, check no malloc call inside ISR + * + * \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-2019, 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, ALL ORIGINAL COMMENTS, and the following disclaimer. + * + * - 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 // use STM standard exported LD symbols +//#define SUPPORT_MALLOCS_INSIDE_ISRs // #define iff you have this crap code (ie unrepaired STM USB CDC) +#define SUPPORT_ISR_STACK_MONITOR // #define to enable ISR (MSP) stack diagnostics +#define ISR_STACK_LENGTH_BYTES 512 // #define bytes to reserve for ISR (MSP) stack +// ======================================= Configuration ======================================== +// ================================================================================================ + + +#include // maps to newlib... +#include // mallinfo... +#include // ENOMEM +#include +#include + +#include "newlib.h" +#if (__NEWLIB__ != 3) || (__NEWLIB_MINOR__ != 0) +#warning "This wrapper was verified for newlib version 3.0.0; 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, strtok, etc..." +// If you're *REALLY* sure you don't need FreeRTOS's newlib reentrancy support, remove this 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... +#if 0 // STM CubeMX 2018-2019 Incorrect Implementation (fails for FreeRTOS) +caddr_t _sbrk(int incr) +{ + extern char end asm("end"); // 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) // of course, 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 // except in K64F this was already adjusted in LD for stack... +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; // make sure to define these symbols in linker LD command file + +//! _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); + char* previousHeapEnd = currentHeapEnd; + 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. + 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) { +#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) { +#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). +// 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. +int 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) { + 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 {}; diff --git a/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/Options.cs b/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/Options.cs index 516d67c..6493ce9 100644 --- a/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/Options.cs +++ b/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/Options.cs @@ -6,5 +6,8 @@ public class Options { [Option('p', "path", Required = true, HelpText = "The folder path to your project (where your .cproject file is)")] public string Path { get; set; } + + [Option('n', "newlib", HelpText = "Set this flag to exclude automatic fix of newlib from the patch. Details here: http://www.nadler.com/embedded/newlibAndFreeRTOS.html")] + public bool ExcludeNewLibFix { get; set; } } } diff --git a/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/Program.cs b/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/Program.cs index d219294..85cf01e 100644 --- a/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/Program.cs +++ b/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/Program.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Xml; using System.Xml.Linq; @@ -38,6 +39,7 @@ class Program static void Main(string[] args) { // usage: -p [PATH to project containing .cproject file] + args = new string[] { @"-pB:\gitrepo\personalcode\Lux\Projects\STM32\F7\Source\TouchGFX\LuxSTM32-PWMTest" }; Console.WriteLine("STM32CubeIDE-TouchGFX-Fix Project Patcher"); Parser.Default.ParseArguments(args) @@ -85,55 +87,12 @@ private static int RunOptionsAndReturnErrorCode(Options options) .Descendants("sourceEntries").First(); // add TouchGfx/simulator ignore - var touchGfxEntries = sourceEntries.Descendants("entry").Where(x => x.Attribute("name").Value == "TouchGFX"); - if (touchGfxEntries.Count() > 1) - { - // delete extra entries, the IDE is messed up. - var entriesToRemove = new List(); - foreach (var entry in touchGfxEntries) - { - if (entry.Attributes("excluding").Any()) - entriesToRemove.Add(entry); - } - foreach (var entry in entriesToRemove) - entry.Remove(); + if (EnsureExclusion(sourceEntries, "TouchGFX", "simulator")) hasModifications = true; - } - if (!touchGfxEntries.Any()) - { - sourceEntries.Add(new XElement("entry", new XAttribute("flags", "VALUE_WORKSPACE_PATH|RESOLVED"), new XAttribute("kind", "sourcePath"), new XAttribute("name", "TouchGFX"))); - hasModifications = true; - } - if (touchGfxEntries.Any() && !touchGfxEntries.Attributes("excluding").Any()) - { - touchGfxEntries.First().Add(new XAttribute("excluding", "simulator")); - hasModifications = true; - } // add Middlewares ignores - var halSimulatorEntries = sourceEntries.Descendants("entry").Where(x => x.Attribute("name").Value == "Middlewares"); - if (halSimulatorEntries.Count() > 1) - { - // delete extra entries, the IDE is messed up. - var entriesToRemove = new List(); - foreach (var entry in halSimulatorEntries) - { - if (entry.Attributes("excluding").Any()) - entriesToRemove.Add(entry); - } - foreach (var entry in entriesToRemove) - entry.Remove(); - } - if (!halSimulatorEntries.Any()) - { - sourceEntries.Add(new XElement("entry", new XAttribute("flags", "VALUE_WORKSPACE_PATH|RESOLVED"), new XAttribute("kind", "sourcePath"), new XAttribute("name", "Middlewares"))); + if (EnsureExclusion(sourceEntries, "Middlewares", "ST/TouchGFX/touchgfx/framework/source/platform/driver/touch/SDL2TouchController.cpp", "ST/TouchGFX/touchgfx/framework/source/platform/hal/simulator")) hasModifications = true; - } - if (halSimulatorEntries.Any() && !halSimulatorEntries.Attributes("excluding").Any()) - { - halSimulatorEntries.First().Add(new XAttribute("excluding", "ST/TouchGFX/touchgfx/framework/source/platform/driver/touch/SDL2TouchController.cpp|ST/TouchGFX/touchgfx/framework/source/platform/hal/simulator")); - hasModifications = true; - } // add additional source entries foreach (var sourcePath in SourcePaths) @@ -163,6 +122,26 @@ private static int RunOptionsAndReturnErrorCode(Options options) } } + // perform newlib/freertos patch provided by Dave Nadler + // http://www.nadler.com/embedded/newlibAndFreeRTOS.html + if (!options.ExcludeNewLibFix) + { + // Exclude sysmem.c + if (EnsureExclusion(sourceEntries, "Src", "sysmem.c")) + hasModifications = true; + // Exclude sysmem.c + if (EnsureExclusion(sourceEntries, "Middlewares", "Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c")) + hasModifications = true; + // Add the patch if it's missing + var patchFile = "heap_useNewlib.c"; + var patchFilePath = Path.Combine(options.Path, "Src", patchFile); + if (!File.Exists(patchFilePath)) + { + var contents = LoadResourceByName(patchFile); + File.WriteAllText(patchFilePath, contents); + } + } + // backup project file Console.WriteLine($"Patching project '{projectName}':"); if (hasModifications) @@ -204,6 +183,67 @@ private static int RunOptionsAndReturnErrorCode(Options options) return 0; } + private static bool EnsureExclusion(XElement sourceEntries, string name, params string[] exclusions) + { + var hasModifications = false; + var entries = sourceEntries.Descendants("entry").Where(x => x.Attribute("name").Value == name); + if (entries.Count() > 1) + { + // delete extra entries, the IDE is messed up. + var entriesToRemove = new List(); + foreach (var entry in entries) + { + if (entry.Attributes("excluding").Any()) + entriesToRemove.Add(entry); + } + foreach (var entry in entriesToRemove) + entry.Remove(); + hasModifications = true; + } + if (!entries.Any()) + { + sourceEntries.Add(new XElement("entry", new XAttribute("flags", "VALUE_WORKSPACE_PATH|RESOLVED"), new XAttribute("kind", "sourcePath"), new XAttribute("name", name))); + hasModifications = true; + } + // make sure it has an exclusions attribute + if (entries.Any() && !entries.Attributes("excluding").Any() && exclusions.Any()) + { + // add all exclusions while creating it + var exclusionsStr = string.Join("|", exclusions); + entries.First().Add(new XAttribute("excluding", exclusionsStr)); + hasModifications = true; + } + // also make sure it has all requested exclusions + if (exclusions.Any()) + { + foreach (var exclusion in exclusions) + { + if (entries.Any() && entries.Attributes("excluding").Any(x => x.Value != null && !x.Value.Contains(exclusion))) + { + // it's missing this exclusion, add it + var attr = entries.Attributes("excluding").First(); + attr.Value = string.Join("|", attr.Value, exclusion); + hasModifications = true; + } + } + } + return hasModifications; + } + + private static string LoadResourceByName(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = $"{assembly.GetName().Name.Replace("-","_")}.EmbeddedData.{name}"; + + using (var stream = assembly.GetManifestResourceStream(resourceName)) + { + using (var reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } + } + } + private static void HandleParseErrors(IEnumerable errors) { Console.WriteLine(); diff --git a/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix.csproj b/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix.csproj index 0409b0e..2b7d200 100644 --- a/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix.csproj +++ b/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix/STM32CubeIDE-TouchGFX-Fix.csproj @@ -6,8 +6,19 @@ STM32CubeIDE_TouchGFX_Fix Refactor Software Michael Brown + 1.0.1.0 + 1.0.1.0 + 1.0.1 + + + + + + + +