Skip to content

Commit

Permalink
system: implement retargetable locks for newlib
Browse files Browse the repository at this point in the history
  • Loading branch information
rasky committed Sep 1, 2024
1 parent 83dadfd commit 5d2f559
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/kernel/kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,18 @@ static int __kthread_idle(void *arg)
kthread_t* kernel_init(void)
{
assert(!__kernel);
#ifdef __NEWLIB__
// Check if __malloc_lock is a nop. This happens with old toolchains where
// newlib was compiled with --disable-threads.
extern void __malloc_lock(void);
assertf(*(uint32_t*)__malloc_lock != 0x03e00008,
"Toolchain is outdated and does not support multithreading -- upgrade toolchain to the latest version");
// Check that retargetable locks are correctly implemented. Since a simple change
// in linker command can cause them to disappear, better have an assert here.
extern void __retarget_lock_acquire_recursive(_LOCK_T lock);
assertf(*(uint32_t*)__retarget_lock_acquire_recursive != 0x03e00008,
"Build error: retargetable locks not defined -- check that the linker invokation is correct");
#endif
th_ready = NULL; // empty ready list
th_count = 1; // start with the main thread

Expand Down
3 changes: 3 additions & 0 deletions src/system.c
Original file line number Diff line number Diff line change
Expand Up @@ -1562,3 +1562,6 @@ void __assert_func(const char *file, int line, const char *func, const char *fai
__assert_func_ptr(file, line, func, failedexpr);
abort();
}


#include "system_newlib_locks.c"
112 changes: 112 additions & 0 deletions src/system_newlib_locks.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* @file system_newlib_locks.c
* @brief newlib lock implementation
* @ingroup system
*
* This file contains the lock implementation used in libc (newlib). They are
* used to guarantee thread safety in the C library functions like malloc
* or file descriptors.
*
* All functions in this file must be defined, so that it takes precedence
* over libc's default implementation (which is empty).
*/
#include "kernel.h"
#include <stdio.h>

/** Lock structure: use a kmutex from our kernel */
struct __lock {
kmutex_t mut;
};

///@cond
struct __lock __lock___sfp_recursive_mutex;
struct __lock __lock___atexit_recursive_mutex;
struct __lock __lock___at_quick_exit_mutex;
struct __lock __lock___malloc_recursive_mutex;
struct __lock __lock___env_recursive_mutex;
struct __lock __lock___tz_mutex;
struct __lock __lock___dd_hash_mutex;
struct __lock __lock___arc4random_mutex;
///@endcond

/** Pool of dynamically allocated lists */
static struct __lock __libc_mutexes[64];
static uint64_t __libc_mutexes_bitmap = 0;
extern bool __kernel;

/** Alloca a dynamic lock from our static pool */
static struct __lock* __alloc_libc_mutex(void) {
for (int i = 0; i < 64; i++) {
if (!(__libc_mutexes_bitmap & (1ull << i))) {
__libc_mutexes_bitmap |= (1ull << i);
return &__libc_mutexes[i];
}
}
assert(0);
}

/** Free a dynamic lock from our static pool */
static void __free_libc_mutex(struct __lock* lock) {
int i = lock - __libc_mutexes;
__libc_mutexes_bitmap &= ~(1ull << i);
}

///@cond
// Not part of libdragon public API, no need to document these

void __retarget_lock_init(_LOCK_T *lock) {
// Allow to allocate the locks even if the kernel is not initialized.
// For instance, this will happen for stderr. If later the kernel is initialized,
// the mutex will start working, otherwise they are nops.
*lock = __alloc_libc_mutex();
kmutex_init(&(*lock)->mut, KMUTEX_STANDARD);
}
void __retarget_lock_init_recursive(_LOCK_T *lock) {
*lock = __alloc_libc_mutex();
kmutex_init(&(*lock)->mut, KMUTEX_RECURSIVE);
}

void __retarget_lock_close(_LOCK_T lock) {
kmutex_destroy(&lock->mut);
__free_libc_mutex(lock);
}
void __retarget_lock_close_recursive(_LOCK_T lock) {
kmutex_destroy(&lock->mut);
__free_libc_mutex(lock);
}

extern void isviewer_write(const char *data, int len);

void __retarget_lock_acquire(_LOCK_T lock) {
if (!__kernel) return;
kmutex_lock(&lock->mut);
}
void __retarget_lock_acquire_recursive(_LOCK_T lock) {
if (!__kernel) return;
// HACK: some libc locks (eg: malloc) are recursive, but they are statically
// defined and are designed to be zero-initialized. Since in our implementation
// zero means non-recursive, we need to set the flag here.
lock->mut.flags |= KMUTEX_RECURSIVE;
kmutex_lock(&lock->mut);
}

int __retarget_lock_try_acquire(_LOCK_T lock) {
if (!__kernel) return 1;
return kmutex_try_lock(&lock->mut, 0);
}
int __retarget_lock_try_acquire_recursive(_LOCK_T lock) {
if (!__kernel) return 1;
lock->mut.flags |= KMUTEX_RECURSIVE;
return kmutex_try_lock(&lock->mut, 0);
}

void __retarget_lock_release(_LOCK_T lock) {
if (!__kernel) return;
kmutex_unlock(&lock->mut);
}
void __retarget_lock_release_recursive (_LOCK_T lock) {
if (!__kernel) return;
kmutex_unlock(&lock->mut);
}

///@endcond

0 comments on commit 5d2f559

Please sign in to comment.