diff --git a/Makefile.pseudomodules b/Makefile.pseudomodules index ffd345d63..875c570de 100644 --- a/Makefile.pseudomodules +++ b/Makefile.pseudomodules @@ -2,6 +2,7 @@ PSEUDOMODULES += conn PSEUDOMODULES += conn_ip PSEUDOMODULES += conn_tcp PSEUDOMODULES += conn_udp +PSEUDOMODULES += core_thread_flags PSEUDOMODULES += gnrc_netdev_default PSEUDOMODULES += gnrc_ipv6_default PSEUDOMODULES += gnrc_ipv6_router diff --git a/core/include/thread.h b/core/include/thread.h index fdcb444a0..c59ea9c90 100644 --- a/core/include/thread.h +++ b/core/include/thread.h @@ -29,9 +29,12 @@ #include "arch/thread_arch.h" #include "cpu_conf.h" #include "sched.h" -#include "list.h" #include "clist.h" +#ifdef MODULE_CORE_THREAD_FLAGS +#include "thread_flags.h" +#endif + #ifdef __cplusplus extern "C" { #endif @@ -46,12 +49,14 @@ * @brief Blocked states. * @{ */ -#define STATUS_STOPPED 0 /**< has terminated */ -#define STATUS_SLEEPING 1 /**< sleeping */ -#define STATUS_MUTEX_BLOCKED 2 /**< waiting for a locked mutex */ -#define STATUS_RECEIVE_BLOCKED 3 /**< waiting for a message */ -#define STATUS_SEND_BLOCKED 4 /**< waiting for message to be delivered*/ -#define STATUS_REPLY_BLOCKED 5 /**< waiting for a message response */ +#define STATUS_STOPPED 0 /**< has terminated */ +#define STATUS_SLEEPING 1 /**< sleeping */ +#define STATUS_MUTEX_BLOCKED 2 /**< waiting for a locked mutex */ +#define STATUS_RECEIVE_BLOCKED 3 /**< waiting for a message */ +#define STATUS_SEND_BLOCKED 4 /**< waiting for message to be delivered*/ +#define STATUS_REPLY_BLOCKED 5 /**< waiting for a message response */ +#define STATUS_FLAG_BLOCKED_ANY 6 /**< waiting for any flag from flag_mask*/ +#define STATUS_FLAG_BLOCKED_ALL 7 /**< waiting for all flags in flag_mask */ /** @} */ /** @@ -59,8 +64,8 @@ * @{*/ #define STATUS_ON_RUNQUEUE STATUS_RUNNING /**< to check if on run queue: `st >= STATUS_ON_RUNQUEUE` */ -#define STATUS_RUNNING 6 /**< currently running */ -#define STATUS_PENDING 7 /**< waiting to be scheduled to run */ +#define STATUS_RUNNING 8 /**< currently running */ +#define STATUS_PENDING 9 /**< waiting to be scheduled to run */ /** @} */ /** @} */ @@ -74,6 +79,10 @@ struct _thread { kernel_pid_t pid; /**< thread's process id */ +#ifdef MODULE_CORE_THREAD_FLAGS + thread_flags_t flags; /**< currently set flags */ +#endif + clist_node_t rq_entry; /**< run queue entry */ void *wait_data; /**< holding messages */ diff --git a/core/include/thread_flags.h b/core/include/thread_flags.h new file mode 100644 index 000000000..173267676 --- /dev/null +++ b/core/include/thread_flags.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 Kaspar Schleiser + * + * 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 core_thread + * @brief Thread flags + * @{ + * + * @file + * @brief Thread flags API + * + * This API can be used to notify threads of conditions in a race-free + * and allocation-less way. + * + * Each thread can handle up to 16 boolean flags, stored as a bitmask in the + * flags field of its thread. Those flags can be set or unset, using + * thread_flags_set(), from ISR's, other threads or even by the thread itself. + * + * A thread can wait for any combination of its flags to become set, using + * thread_flags_wait_any() or thread_flags_wait_all(). + * Those functions clear flags that caused them to return. + * It is not possible to wait for flags to become unset. + * + * Thread flags have certain properties that make them the preferred choice + * over messages or mutexes in some circumstances: + * + * - setting thread flags cannot fail + * If messages are used to notify a thread of a condition from within an ISR, + * and the receiving thread is not waiting, has no queue or the queue is + * full, the ISR cannot deliver the message. A thread flag can always be set. + * + * - thread flags are very flexible + * With thread flags it is possible to wait for multiple conditions and + * messages at the same time. When mutexes are used to notify about events, + * only one event can be waited for. + * + * Usually, if it is only of interest that an event occurred, but not how many + * of them, thread flags should be considered. + * + * Note that some flags (currently the three most significant bits) are used by + * core functions and should not be set by the user. They can be waited for. + * + * @author Kaspar Schleiser + */ +#ifndef THREAD_FLAG_H +#define THREAD_FLAG_H + +#include "kernel_types.h" +#include "sched.h" /* for thread_t typedef */ + +#ifdef __cplusplus + extern "C" { +#endif + +/** + * @name reserved thread flags + * @{ + */ +#define THREAD_FLAG_MSG_WAITING (0x1<<15) +#define THREAD_FLAG_MUTEX_UNLOCKED (0x1<<14) +#define THREAD_FLAG_TIMEOUT (0x1<<13) +/** @} */ + +/** + * @name Define type of thread_flags_t + */ +typedef uint16_t thread_flags_t; + +/** + * @brief Set thread flags, possibly waking it up + * + * @param[in] thread thread to work on + * @param[in] mask additional flags to be set for the current thread, + * represented as a bitmask + */ +void thread_flags_set(thread_t *thread, thread_flags_t mask); + +/** + * @brief Clear current thread's flags + * + * @param[in] mask unset flags for the current thread, + * represented as a bitmask + * + * @returns flags that have actually been cleared (mask & thread->flags before clear) + */ +thread_flags_t thread_flags_clear(thread_flags_t mask); + +/** + * @brief Wait for any flag in mask to become set (blocking) + * + * If any of the flags in mask are already set, this function will return + * immediately, otherwise, it will suspend the thread (as + * THREAD_STATUS_WAIT_ANY) until any of the flags in mask get set. + * + * Both ways, it will clear and return (sched_active_thread-flags & mask). + * + * @param[in] mask mask of flags to wait for + * + * @returns flags that caused return/wakeup ((sched_active_thread-flags & mask). + */ +thread_flags_t thread_flags_wait_any(thread_flags_t mask); + +/** + * @brief Wait for all flags in mask to become set (blocking) + * + * If all the flags in mask are already set, this function will return + * immediately, otherwise, it will suspend the thread (as + * THREAD_STATUS_WAIT_ALL) until all of the flags in mask have been set. + * + * Both ways, it will clear and return (sched_active_thread-flags & mask). + * + * @param[in] mask mask of flags to wait for + * + * @returns mask + */ +thread_flags_t thread_flags_wait_all(thread_flags_t mask); + +/** + * @brief Wait for any flags in mask to become set (blocking), one at a time + * + * This function is like thread_flags_wait_any(), but will only clear and return + * one flag at a time, most significant set bit first. + * + * @param[in] mask mask of flags to wait for + * + * @returns flag that triggered the return / wait + */ +thread_flags_t thread_flags_wait_one(thread_flags_t mask); + +/** + * @brief Possibly Wake up thread waiting for flags + * + * Wakes up a thread if it is thread flag blocked and its condition is met. + * Has to be called with interrupts disabled. + * Does not trigger yield. + * + * @internal + * + * @param[in] thread thread to possibly wake up + * @return 1 if @p thread has been woken up + * 0 otherwise + */ +int thread_flags_wake(thread_t *thread); + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif /* THREAD_FLAG_H */ diff --git a/core/thread_flags.c b/core/thread_flags.c new file mode 100644 index 000000000..23d4e12a6 --- /dev/null +++ b/core/thread_flags.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2016 Kaspar Schleiser + * + * 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 core_thread + * @{ + * + * @file + * @brief thread flags implementation + * + * @author Kaspar Schleiser + * + * @} + */ + + +#include "bitarithm.h" +#include "thread_flags.h" +#include "irq.h" +#include "thread.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#ifdef MODULE_CORE_THREAD_FLAGS +static thread_flags_t _thread_flags_clear_atomic(thread_t *thread, thread_flags_t mask) +{ + unsigned state = irq_disable(); + mask &= thread->flags; + thread->flags &= ~mask; + irq_restore(state); + return mask; +} + +static void _thread_flags_wait(thread_flags_t mask, thread_t *thread, unsigned threadstate, unsigned irqstate) +{ + DEBUG("_thread_flags_wait: me->flags=0x%08x me->mask=0x%08x. going blocked.\n", + (unsigned)thread->flags, (unsigned)mask); + + thread->wait_data = (void *)(unsigned)mask; + sched_set_status(thread, threadstate); + irq_restore(irqstate); + thread_yield_higher(); +} + +thread_flags_t thread_flags_clear(thread_flags_t mask) +{ + thread_t *me = (thread_t*) sched_active_thread; + mask = _thread_flags_clear_atomic(me, mask); + DEBUG("thread_flags_clear(): pid %"PRIkernel_pid" clearing 0x%08x\n", thread_getpid(), mask); + return mask; +} + +static void _thread_flags_wait_any(thread_flags_t mask) +{ + thread_t *me = (thread_t*) sched_active_thread; + unsigned state = irq_disable(); + if (!(me->flags & mask)) { + _thread_flags_wait(mask, me, STATUS_FLAG_BLOCKED_ANY, state); + } + else { + irq_restore(state); + } +} + +thread_flags_t thread_flags_wait_any(thread_flags_t mask) +{ + thread_t *me = (thread_t*) sched_active_thread; + _thread_flags_wait_any(mask); + return _thread_flags_clear_atomic(me, mask); +} + +thread_flags_t thread_flags_wait_one(thread_flags_t mask) +{ + _thread_flags_wait_any(mask); + thread_t *me = (thread_t*) sched_active_thread; + unsigned tmp = me->flags & mask; + return _thread_flags_clear_atomic(me, thread_flags_clear(1 << bitarithm_lsb(tmp))); +} + +thread_flags_t thread_flags_wait_all(thread_flags_t mask) +{ + unsigned state = irq_disable(); + thread_t *me = (thread_t*) sched_active_thread; + if (!((me->flags & mask) == mask)) { + DEBUG("thread_flags_wait_all(): pid %"PRIkernel_pid" waiting for %08x\n", thread_getpid(), (unsigned)mask); + _thread_flags_wait(mask, me, STATUS_FLAG_BLOCKED_ALL, state); + } + else { + irq_restore(state); + } + + return _thread_flags_clear_atomic(me, mask); +} + +inline int __attribute__((always_inline)) thread_flags_wake(thread_t *thread) +{ + unsigned wakeup = 0; + thread_flags_t mask = (uint16_t)(unsigned)thread->wait_data; + switch(thread->status) { + case STATUS_FLAG_BLOCKED_ANY: + wakeup = (thread->flags & mask); + break; + case STATUS_FLAG_BLOCKED_ALL: + wakeup = ((thread->flags & mask) == mask); + break; + } + + if (wakeup) { + DEBUG("_thread_flags_wake(): wakeing up pid %"PRIkernel_pid"\n", thread->pid); + sched_set_status(thread, STATUS_RUNNING); + } + + return wakeup; +} + +void thread_flags_set(thread_t *thread, thread_flags_t mask) +{ + DEBUG("thread_flags_set(): setting 0x%08x for pid %"PRIkernel_pid"\n", mask, thread->pid); + unsigned state = irq_disable(); + thread->flags |= mask; + if (thread_flags_wake(thread)) { + irq_restore(state); + thread_yield_higher(); + } + else { + irq_restore(state); + } +} +#endif /* MODULE_CORE_THREAD_FLAGS */ diff --git a/tests/sizeof_tcb/Makefile b/tests/sizeof_tcb/Makefile index 72f8fb352..c7ead79a8 100644 --- a/tests/sizeof_tcb/Makefile +++ b/tests/sizeof_tcb/Makefile @@ -1,4 +1,7 @@ APPLICATION = sizeof_tcb include ../Makefile.tests_common +# optional thread_t modifying modules: +# USEMODULE += core_thread_flags + include $(RIOTBASE)/Makefile.include diff --git a/tests/sizeof_tcb/main.c b/tests/sizeof_tcb/main.c index fc760a20a..2be898ecd 100644 --- a/tests/sizeof_tcb/main.c +++ b/tests/sizeof_tcb/main.c @@ -35,6 +35,9 @@ int main(void) P(status); P(priority); P(pid); +#ifdef MODULE_CORE_THREAD_FLAGS + P(flags); +#endif P(rq_entry); P(wait_data); P(msg_waiters);