diff --git a/Makefile.dep b/Makefile.dep index 90c7fd239285..4dd8b77443dc 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -1,6 +1,10 @@ # pull dependencies from drivers include $(RIOTBASE)/drivers/Makefile.dep +ifneq (,$(filter newlib_thread_safe,$(USEMODULE))) + USEMODULE += newlib_nano +endif + ifneq (,$(filter libcoap,$(USEPKG))) USEMODULE += posix_sockets USEMODULE += gnrc_conn_udp diff --git a/core/include/thread.h b/core/include/thread.h index f6fd31fee8ae..b39071f5442f 100644 --- a/core/include/thread.h +++ b/core/include/thread.h @@ -22,6 +22,10 @@ #ifndef THREAD_H #define THREAD_H +#if USE_NEWLIB_THREAD_SAFE +#include +#endif + #include "clist.h" #include "cib.h" #include "msg.h" @@ -93,6 +97,9 @@ struct _thread { msg_t *msg_array; /**< memory holding messages */ #endif +#ifdef USE_NEWLIB_THREAD_SAFE + struct _reent newlib_reent; /**< thread's re-entrent object */ +#endif #if defined DEVELHELP || defined(SCHED_TEST_STACK) char *stack_start; /**< thread's stack start address */ #endif diff --git a/core/sched.c b/core/sched.c index 151649aad2b3..93478725ac0a 100644 --- a/core/sched.c +++ b/core/sched.c @@ -115,6 +115,10 @@ int sched_run(void) sched_active_pid = next_thread->pid; sched_active_thread = (volatile thread_t *) next_thread; +#ifdef USE_NEWLIB_THREAD_SAFE + _impure_ptr = &(next_thread->newlib_reent); +#endif + DEBUG("sched_run: done, changed sched_active_thread.\n"); return 1; diff --git a/core/thread.c b/core/thread.c index f1ce96b06e9e..25da2e241bed 100644 --- a/core/thread.c +++ b/core/thread.c @@ -21,6 +21,10 @@ #include #include +#ifdef USE_NEWLIB_THREAD_SAFE +#include +#endif + #include "assert.h" #include "thread.h" #include "irq.h" @@ -232,6 +236,11 @@ kernel_pid_t thread_create(char *stack, int stacksize, char priority, int flags, cb->msg_array = NULL; #endif +#ifdef USE_NEWLIB_THREAD_SAFE + /* initialize the reent context */ + _REENT_INIT_PTR(&(cb->newlib_reent)); +#endif + sched_num_threads++; DEBUG("Created thread %s. PID: %" PRIkernel_pid ". Priority: %u.\n", name, cb->pid, priority); diff --git a/cpu/cortexm_common/include/cpu_conf_common.h b/cpu/cortexm_common/include/cpu_conf_common.h index 27a32ddd51b6..4331b393770a 100644 --- a/cpu/cortexm_common/include/cpu_conf_common.h +++ b/cpu/cortexm_common/include/cpu_conf_common.h @@ -41,11 +41,19 @@ extern "C" { #define THREAD_EXTRA_STACKSIZE_PRINTF (512) #endif #ifndef THREAD_STACKSIZE_DEFAULT +#if defined(USE_NEWLIB_THREAD_SAFE) +#define THREAD_STACKSIZE_DEFAULT (1152) +#else #define THREAD_STACKSIZE_DEFAULT (1024) #endif +#endif #ifndef THREAD_STACKSIZE_IDLE +#if defined(USE_NEWLIB_THREAD_SAFE) +#define THREAD_STACKSIZE_IDLE (384) +#else #define THREAD_STACKSIZE_IDLE (256) #endif +#endif /** @} */ /** diff --git a/sys/Makefile b/sys/Makefile index c6ffeb132adc..79d04b0bed7a 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -92,6 +92,9 @@ endif ifneq (,$(filter sema,$(USEMODULE))) DIRS += sema endif +ifneq (,$(filter newlib_thread_safe,$(USEMODULE))) + DIRS += newlib/thread_safe +endif DIRS += $(dir $(wildcard $(addsuffix /Makefile, ${USEMODULE}))) diff --git a/sys/Makefile.include b/sys/Makefile.include index ae1b736b2d9f..001617b26bab 100644 --- a/sys/Makefile.include +++ b/sys/Makefile.include @@ -59,6 +59,10 @@ ifneq (,$(filter newlib_syscalls_default,$(USEMODULE))) include $(RIOTBASE)/sys/newlib/Makefile.include endif +ifneq (,$(filter newlib_thread_safe,$(USEMODULE))) + include $(RIOTBASE)/sys/newlib/thread_safe/Makefile.include +endif + ifneq (,$(filter arduino,$(USEMODULE))) include $(RIOTBASE)/sys/arduino/Makefile.include endif diff --git a/sys/newlib/thread_safe/Makefile b/sys/newlib/thread_safe/Makefile new file mode 100644 index 000000000000..3404d1463a40 --- /dev/null +++ b/sys/newlib/thread_safe/Makefile @@ -0,0 +1,3 @@ +MODULE = newlib_thread_safe + +include $(RIOTBASE)/Makefile.base diff --git a/sys/newlib/thread_safe/Makefile.include b/sys/newlib/thread_safe/Makefile.include new file mode 100644 index 000000000000..8c92889ee413 --- /dev/null +++ b/sys/newlib/thread_safe/Makefile.include @@ -0,0 +1,22 @@ +# define the compile test file +define __has_reent_small_test +#include \n +#ifndef _REENT_SMALL\n +#error wasting 1KB\n +#endif +endef + +# compile the tests +__has_reent := $(shell echo "\#include \n" | $(CC) -E $(CFLAGS) $(INCLUDES) - 2> /dev/null > /dev/null; echo $$?) +__has_reent_small := $(shell echo "$(__has_reent_small_test)" | $(CC) -E $(CFLAGS) $(INCLUDES) - 2> /dev/null > /dev/null; echo $$?) + +# check if we use REENT_SMALL +ifeq (0, $(__has_reent)) + ifeq (0, $(__has_reent_small)) + export USE_NEWLIB_THREAD_SAFE := 1 + else + $(shell $(COLOR_ECHO) "\n$(COLOR_YELLOW)_REENT_SMALL not defined, thread-safe newlib disabled!\n$(COLOR_RESET)" 1>&2) + endif +else + $(shell $(COLOR_ECHO) "\n$(COLOR_YELLOW)MODULE_NEWLIB_THREAD_SAFE defined but not found in toolchain path, thread-safe newlib is disabled!\n\n$(COLOR_RESET)\n" 1>&2) +endif diff --git a/sys/newlib/thread_safe/env_lock.c b/sys/newlib/thread_safe/env_lock.c new file mode 100644 index 000000000000..7bed7f6c6fc5 --- /dev/null +++ b/sys/newlib/thread_safe/env_lock.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 Engineering-Spirit + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_newlib + * @{ + * + * @file + * @brief Newlib thread-safe env implementation + * + * @author Nick v. IJzendoorn + * + * @} + */ + +#include "newlib_lock.h" + +#if USE_NEWLIB_THREAD_SAFE +static recursive_mutex_t _env = RECURSIVE_MUTEX_INIT; +#endif + +/** + * @brief __env_lock needs to provide recursive mutex locking + */ +void __env_lock(struct _reent *_r) +{ + (void) _r; + + /* TODO another way would be to avoid rescheduling other tasks */ +#if USE_NEWLIB_THREAD_SAFE + __recursive_lock(&_env); +#endif +} + +void __env_unlock(struct _reent *_r) +{ + (void) _r; + +#if USE_NEWLIB_THREAD_SAFE + __recursive_unlock(&_env); +#endif +} diff --git a/sys/newlib/thread_safe/malloc_lock.c b/sys/newlib/thread_safe/malloc_lock.c new file mode 100644 index 000000000000..bc7ac32ccaeb --- /dev/null +++ b/sys/newlib/thread_safe/malloc_lock.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 Engineering-Spirit + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_newlib + * @{ + * + * @file + * @brief Newlib thread-safe malloc implementation + * + * @author Nick v. IJzendoorn + * + * @} + */ + +#include "newlib_lock.h" + +#if USE_NEWLIB_THREAD_SAFE +static recursive_mutex_t _malloc = RECURSIVE_MUTEX_INIT; +#endif + +/** + * @brief __malloc_lock needs to provide recursive mutex locking + */ +void __malloc_lock(struct _reent *_r) +{ + (void) _r; + + /* TODO another way would be to avoid rescheduling other tasks */ +#if USE_NEWLIB_THREAD_SAFE + __recursive_lock(&_malloc); +#endif +} + +void __malloc_unlock(struct _reent *_r) +{ + (void) _r; + +#if USE_NEWLIB_THREAD_SAFE + __recursive_unlock(&_malloc); +#endif +} diff --git a/sys/newlib/thread_safe/newlib_lock.c b/sys/newlib/thread_safe/newlib_lock.c new file mode 100644 index 000000000000..c4e4ff3231d4 --- /dev/null +++ b/sys/newlib/thread_safe/newlib_lock.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 Engineering-Spirit + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_newlib + * @{ + * + * @file + * @brief Newlib thread-safe recursive implementation + * + * @author Nick v. IJzendoorn + * + * @} + */ + +#include + +#include "arch/irq_arch.h" +#include "newlib_lock.h" + +void __recursive_init(recursive_mutex_t *rm) +{ + recursive_mutex_t empty_mutex = RECURSIVE_MUTEX_INIT; + *rm = empty_mutex; +} + +/** + * @brief __recursive_lock needs to provide recursive mutex locking + */ +void __recursive_lock(recursive_mutex_t *rm) +{ + /* TODO another way would be to avoid rescheduling other tasks */ +#if USE_NEWLIB_THREAD_SAFE + + /* check if the mutex lock is not called from an interrupt */ + assert(!(irq_arch_in())); + + /* try to lock the recursive mutex */ + switch (mutex_trylock(&rm->mutex)) + { + case 0: + /* mutex is already locked */ + if (rm->owner != thread_getpid()) { + /* we are not the owner, so we wait till it's released and + * fall-trough the case statement */ + mutex_lock(&rm->mutex); + } + + /* fall-trough */ + + case 1: + /* mutex now locked by us */ + atomic_inc(&rm->recursion); + rm->owner = thread_getpid(); + break; + } +#else + (void) rm; +#endif +} + +void __recursive_unlock(recursive_mutex_t *rm) +{ +#if USE_NEWLIB_THREAD_SAFE + /* check if the mutex unlock is not called from an interrupt */ + assert(!(irq_arch_in())); + + /* check if the unlocking thread is the same as the locking thread */ + assert(rm->owner == thread_getpid()); + + /* decrease the recursion counter */ + if (atomic_dec(&rm->recursion) == 1) { + /* we just released the last recursion lock call */ + + rm->owner = KERNEL_PID_UNDEF; + mutex_unlock(&rm->mutex); + } +#else + (void) rm; +#endif +} diff --git a/sys/newlib/thread_safe/newlib_lock.h b/sys/newlib/thread_safe/newlib_lock.h new file mode 100644 index 000000000000..73328148bdc6 --- /dev/null +++ b/sys/newlib/thread_safe/newlib_lock.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 Engineering-Spirit + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_newlib + * @{ + * + * @file + * @brief Newlib thread-safe recursive lock implementation + * + * @author Nick v. IJzendoorn + * + * @} + */ + +#ifndef THREAD_SAFE_NEWLIB_LOCK_H_ +#define THREAD_SAFE_NEWLIB_LOCK_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief An recursive mutex implementation that can be used for newlib's + * thread-safe implementation + */ +typedef struct recursive_mutex +{ + volatile kernel_pid_t owner; /**< owner of the recursive mutex */ + atomic_int_t recursion; /**< number of recursive lock calls */ + mutex_t mutex; /**< mutex that is locked recursively */ +} recursive_mutex_t; + +/** + * @brief Initialization values for a recursive mutex + */ +#define RECURSIVE_MUTEX_INIT { KERNEL_PID_UNDEF, { 0 }, MUTEX_INIT } + +/** + * @brief initialize the recursive mutex if it's not yet initialized + * + * @param[in] rm recursive mutex structure + */ +void __recursive_init(recursive_mutex_t *rm); + +/** + * @brief lock the mutex recursively, try to lock the mutex. + * - If the mutex is free claim ownership. + * - If the mutex is locked by us already; increase the recursion count + * - If the mutex is locked by another task; wait for the mutex to be freed + * + * @param[in] rm recursive mutex structure + */ +void __recursive_lock(recursive_mutex_t *rm); + +/** + * @brief release the mutex recursively, if the last lock is freeed release the mutex + * + * @param[in] rm recursive mutex structure + */ +void __recursive_unlock(recursive_mutex_t *rm); + +#ifdef __cplusplus +}; +#endif + +#endif /* THREAD_SAFE_NEWLIB_LOCK_H_ */ diff --git a/tests/sizeof_tcb_newlib_threadsafe/Makefile b/tests/sizeof_tcb_newlib_threadsafe/Makefile new file mode 100644 index 000000000000..c26e715a47fd --- /dev/null +++ b/tests/sizeof_tcb_newlib_threadsafe/Makefile @@ -0,0 +1,9 @@ +APPLICATION = sizeof_tcb_newlib_threadsafe +include ../Makefile.tests_common + +USEMODULE = newlib_thread_safe + +BOARD_BLACKLIST := arduino-mega2560 chronos msb-430 msb-430h native \ + qemu-i386 telosb wsn430-v1_3b wsn430-v1_4 z1 + +include $(RIOTBASE)/Makefile.include diff --git a/tests/sizeof_tcb_newlib_threadsafe/main.c b/tests/sizeof_tcb_newlib_threadsafe/main.c new file mode 100644 index 000000000000..098b4d9c62a4 --- /dev/null +++ b/tests/sizeof_tcb_newlib_threadsafe/main.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 Engineering-Spirit + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Print the size of thread_t. + * + * @author Nick v. IJzendoorn + * + * @} + */ + +#include +#include + +#include "thread.h" + +#define P(NAME) printf("\t%-*s%4zu%4zu\n", 11, #NAME, sizeof(((thread_t *) 0)->NAME), offsetof(thread_t, NAME)); + +int main(void) +{ + puts("\tmember, sizeof, offsetof"); + + printf("sizeof(thread_t): %zu\n", sizeof(thread_t)); + + P(sp); + P(status); + P(priority); + P(pid); + P(rq_entry); + P(wait_data); + P(msg_waiters); + P(msg_queue); + P(msg_array); +#ifdef USE_NEWLIB_THREAD_SAFE + P(newlib_reent); +#endif +#ifdef DEVELHELP + P(name); +#endif +#if defined(DEVELHELP) || defined(SCHED_TEST_STACK) + P(stack_start); +#endif + +#ifdef DEVELHELP + P(stack_size); +#endif + + puts("Done."); + return 0; +}