Is there a reason you do not replace printf interface with something saner?
Yes - lack of time (I am working on this alone, getting help from people on forums) and the need for a full standard printf lib because the product will have modules done by other people later.
If it was a one off embedded job, I would use itoa() and the other variants. OTOH I have 1MB FLASH available and the code is only 300k currently (and half of that is that pile called MbedTLS), so, why not? I am using snprintf for outputting uint64_t, and sscanf for reading uint64_t, and floats, etc.
I have it nearly done but it was a very long marathon. I could not work out how to do the wrapping mentioned here
https://www.eevblog.com/forum/programming/st-cube-gcc-how-to-override-a-function-not-defined-as-weak-(no-sources)/ (https://www.eevblog.com/forum/programming/st-cube-gcc-how-to-override-a-function-not-defined-as-weak-(no-sources)/)
without it being very messy because it looks like it needs to be on the compiler command line. So I used the --weaken-symbol option on objcopy. I will post details later in that thread but it was awfully complicated even just to locate the right libc.a file. Then getting the linker (in Cube) to see the modified lib took all day of trial and error.
I have managed to intercept the mutex calls and got most of it running but it isn't quite there.
It looks like all the mutexes do need initialisation. In FreeRTOS there isn't a "self initialising" mutex. I collected them into init_newlib_mutexes, here
/*
* newlib_locking.c
*
* Created on: 24 Jul 2022
* Author: peter
*
* These functions replace the empty ones in the newlib (printf etc) code.
* The printf family uses the heap for floats and longs (regardless of
* whether newlib-nano is selected) and when floats or longs are used, these
* functions use the heap, and use mutexes to make that thread-safe.
*
* Based around
* [url]https://gist.github.com/thomask77/3a2d54a482c294beec5d87730e163bdd[/url]
*
* Also see
* [url]https://www.eevblog.com/forum/programming/st-cube-gcc-how-to-override-a-function-not-defined-as-weak-(no-sources)[/url]
*
*
*/
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
#include <newlib_locking.h>
struct __lock {
SemaphoreHandle_t sem;
};
struct __lock __lock___sinit_recursive_mutex;
struct __lock __lock___sfp_recursive_mutex;
struct __lock __lock___atexit_recursive_mutex;
struct __lock __lock___at_quick_exit_mutex;
struct __lock __lock___malloc_mutex;
struct __lock __lock___env_recursive_mutex;
struct __lock __lock___tz_mutex;
struct __lock __lock___dd_hash_mutex;
struct __lock __lock___arc4random_mutex;
// This mutex init file is called from main.c
//__attribute__((constructor))
void init_newlib_mutexes(void)
{
__lock___sinit_recursive_mutex.sem = xSemaphoreCreateRecursiveMutex();
__lock___sfp_recursive_mutex.sem = xSemaphoreCreateRecursiveMutex();
__lock___atexit_recursive_mutex.sem = xSemaphoreCreateRecursiveMutex();
__lock___at_quick_exit_mutex.sem = xSemaphoreCreateMutex();
__lock___malloc_mutex.sem = xSemaphoreCreateMutex();
__lock___env_recursive_mutex.sem = xSemaphoreCreateRecursiveMutex();
__lock___tz_mutex.sem = xSemaphoreCreateMutex();
__lock___dd_hash_mutex.sem = xSemaphoreCreateMutex();
__lock___arc4random_mutex.sem = xSemaphoreCreateMutex();
}
// The mutex lock is *not* recursive (as is shown in many examples) because
// that would be really stupid.
// These two functions are used by both malloc and free.
void __malloc_lock(_LOCK_T lock)
{
xSemaphoreTake(lock->sem, portMAX_DELAY);
}
void __malloc_unlock (_LOCK_T lock)
{
xSemaphoreGive(lock->sem);
}
// This one is not used
void __malloc_lock_acquire(_LOCK_T lock)
{
xSemaphoreTake(lock->sem, portMAX_DELAY);
}
void __retarget_lock_init(_LOCK_T *lock_ptr)
{
*lock_ptr = pvPortMalloc(sizeof(struct __lock));
(*lock_ptr)->sem = xSemaphoreCreateMutex();
}
void __retarget_lock_init_recursive(_LOCK_T *lock_ptr)
{
*lock_ptr = pvPortMalloc(sizeof(struct __lock));
(*lock_ptr)->sem = xSemaphoreCreateRecursiveMutex();
}
// This one seems to exist as a symbol only; not even as an empty function
void __retarget_lock_close(_LOCK_T lock)
{
vSemaphoreDelete(lock->sem);
vPortFree(lock);
}
// This one is never actually called
void __retarget_lock_close_recursive(_LOCK_T lock)
{
vSemaphoreDelete(lock->sem);
vPortFree(lock);
}
void __retarget_lock_acquire(_LOCK_T lock)
{
xSemaphoreTake(lock->sem, portMAX_DELAY);
}
void __retarget_lock_acquire_recursive(_LOCK_T lock)
{
xSemaphoreTakeRecursive(lock->sem, portMAX_DELAY);
}
// This one seems to exist as a symbol only; not even as an empty function
int __retarget_lock_try_acquire(_LOCK_T lock)
{
return xSemaphoreTake(lock->sem, 0);
}
// This one seems to exist as a symbol only; not even as an empty function
int __retarget_lock_try_acquire_recursive(_LOCK_T lock)
{
return xSemaphoreTakeRecursive(lock->sem, 0);
}
void __retarget_lock_release(_LOCK_T lock)
{
xSemaphoreGive(lock->sem);
}
void __retarget_lock_release_recursive(_LOCK_T lock)
{
xSemaphoreGiveRecursive(lock->sem);
}
I think there is still something wrong with some handle somewhere but this stuff is on the limit of my knowledge of C, given all the typedefs and structures :) So if anyone can see anything wrong with the above, I am all ears :)
One thing I concluded is that it is dumb to use a recursive mutex for the heap because the heap cannot possibly be multi threaded. Yet this is what the newlib code was doing. Malloc calls __retarget_lock_acquire_recursive, r0= 0x2000c725 = __lock___malloc_recursive_mutex, then on the way out it calls __retarget_lock_release_recursive. I changed this for a normal mutex but maybe it is meant to work after all?
This was the conversion process:
arm-none-eabi-objcopy @locksyms.txt libc.a libc-weakened.a
arm-none-eabi-objdump -t libc-weakened.a > listing.txt
I could have just weakened the entire library and that would have been fine. Maybe I should (it takes about 1 second) because then one can replace the other functions like printf one day.
One consequence of this method is that as Cube is updated, the libc.a will not be. New libs are loaded into the c:\st\... tree and then according to your Cube linker settings (e.g. which version of the printf lib you want, and according to the CPU, whether it has hardware double floats etc) one of these many libc.a files is picked up and copied to another "working" part of the tree. All this stuff gets overwritten with each Cube update, but my weakened libc.a will never change. That should be ok since that code seems to be from 1990. The linker, having got a pointer to the weakened libc.a does indeed load it in preference to the stock one.