diff --git a/Makefile.dep b/Makefile.dep index faa290be4b9f..f65861ff960c 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -28,6 +28,10 @@ ifneq (,$(filter ndn-riot,$(USEPKG))) USEPKG += micro-ecc endif +ifneq (,$(filter core_tasklet,$(USEMODULE))) + USEMODULE += core_thread_flags +endif + ifneq (,$(filter csma_sender,$(USEMODULE))) USEMODULE += random USEMODULE += xtimer diff --git a/core/Makefile b/core/Makefile index 4f12366947dd..bae70558b6db 100644 --- a/core/Makefile +++ b/core/Makefile @@ -1,5 +1,5 @@ # exclude submodule sources from *.c wildcard source selection -SRC := $(filter-out mbox.c msg.c thread_flags.c,$(wildcard *.c)) +SRC := $(filter-out mbox.c msg.c tasklet.c thread_flags.c,$(wildcard *.c)) # enable submodules SUBMODULES := 1 diff --git a/core/include/tasklet.h b/core/include/tasklet.h new file mode 100644 index 000000000000..a6c11dc0ff0c --- /dev/null +++ b/core/include/tasklet.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * 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. + */ + +/** + * @defgroup core_tasklet Tasklets + * @ingroup core + * @{ + * + * @file + * @brief Support for kernel tasklets + * + * A tasklet is a function that is scheduled to run at a kernel-determined + * safe time. When scheduled, the OS will try to run the task as soon as possible. + * + * They might be scheduled several times, but will only run once + * (after that it's possible to schedule the same task again). + * Scheduling tasks is a thread safe operation. + * + * Internally the OS creates a thread with the highest priority that listens + * to a thread flag with type @ref THREAD_FLAG_TASKLET. + * + * Tasklets are ideal for offloading ISR (radio drivers, timers, etc). + * + * Tasklets are not ISR nor thread safe (although they run in the highest + * priority). Thus, synchronization between a module and its tasklets is up to + * the implementer. + * + * This API is optional and must be enabled by adding "core_tasklet" to USEMODULE. + * + * The current implementation doesn't support (yet) prorities for different + * tasklets. + * + * @author Jose I. Alamos + */ + +#ifndef TASKLET_H +#define TASKLET_H + +#include + +#include "clist.h" + +/** + * @brief Stack size of the tasklets thread + */ +#ifndef THREAD_STACKSIZE_TASKLET +#define THREAD_STACKSIZE_TASKLET (THREAD_STACKSIZE_DEFAULT) +#endif + +#ifdef __cplusplus + extern "C" { +#endif + + /** + * @brief Tasklet descriptor + */ +typedef struct { + clist_node_t tasklet_node; /**< circular list node of the tasklet */ + void (*task)(void *arg); /**< the function to be called when processing the tasklet */ + void *arg; /**< the argument of the task */ +} tasklet_t; + +/** + * @brief Init a tasklet with a given function and argument. + * + * @param[in] tasklet Tasklet to be initialized. + * @param[in] task Function to be called when processing the tasklet. + * @param[in] arg The argument of the task. + */ +static inline void tasklet_init(tasklet_t *tasklet, void (*task)(void *arg), void *arg) +{ + memset(tasklet, '\0', sizeof(tasklet_t)); + tasklet->task = task; + tasklet->arg = arg; +} + +/** + * @brief Schedule a tasklet. + * + * The task function assigned to @p tasklet will be called as soon as possible + * in kernel thread context. It's possible to schedule a task from within an + * Interrupt Service Routine. + * + * This function is thread safe. + * + * @note A tasklet must be initialized with @ref tasklet_init!. + * + * @param[in] tasklet Tasklet to be scheduled. + */ +void tasklet_schedule(tasklet_t *tasklet); + +/** + * @brief Create the tasklet thread. + * + * This function is internally called by the kernel in order to + * initialize the tasklet mechanism. + */ +void tasklet_thread_create(void); + +/** + * @brief Pop a tasklet from the tasklet FIFO list. + * + * This function is thread safe. + * + * @return First (leftmost) element of the tasklet list. + * @return NULL if the list is empty. + */ +tasklet_t *tasklet_pop(void); + +/** + * @brief Add a tasklet to the tasklet list. + * + * This function is thread safe + * + * @note This function is exposed only for testing reasons. To schedule + * a tasklet, use @ref tasklet_schedule instead. + * + * @param[in] tasklet Tasklet to be added. + */ +void tasklet_add(tasklet_t *tasklet); + +/** + * @brief Reset the tasklet list + * + * @note This function is exposed only for testing reasons. It shouldn't be + * called outside of the kernel! + */ +void tasklet_list_reset(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TASKLET_H */ +/** @} */ diff --git a/core/include/thread.h b/core/include/thread.h index eff7c1805fa7..fc61b7995dea 100644 --- a/core/include/thread.h +++ b/core/include/thread.h @@ -286,6 +286,14 @@ struct _thread { #define THREAD_PRIORITY_MAIN (THREAD_PRIORITY_MIN - (SCHED_PRIO_LEVELS/2)) #endif +/** + * @def THREAD_PRIORITY_TASKLET + * @brief Priority of the tasklet thread (see @ref core_tasklet) + */ +#ifndef THREAD_PRIORITY_TASKLET +#define THREAD_PRIORITY_TASKLET (0) +#endif + /** * @name Optional flags for controlling a threads initial state * @{ diff --git a/core/include/thread_flags.h b/core/include/thread_flags.h index fce32615da8d..1b1291a9c4aa 100644 --- a/core/include/thread_flags.h +++ b/core/include/thread_flags.h @@ -103,6 +103,11 @@ extern "C" { * @see xtimer_set_timeout_flag */ #define THREAD_FLAG_TIMEOUT (1u << 14) + +/** + * @brief Set by @ref tasklet_schedule + */ +#define THREAD_FLAG_TASKLET (1u << 13) /** @} */ /** diff --git a/core/kernel_init.c b/core/kernel_init.c index cab06a3ec123..eb690ce2fe63 100644 --- a/core/kernel_init.c +++ b/core/kernel_init.c @@ -36,6 +36,10 @@ #include #endif +#ifdef MODULE_CORE_TASKLET +#include "tasklet.h" +#endif + extern int main(void); static void *main_trampoline(void *arg) { @@ -72,6 +76,10 @@ void kernel_init(void) { (void) irq_disable(); +#ifdef MODULE_CORE_TASKLET + tasklet_thread_create(); +#endif + thread_create(idle_stack, sizeof(idle_stack), THREAD_PRIORITY_IDLE, THREAD_CREATE_WOUT_YIELD | THREAD_CREATE_STACKTEST, diff --git a/core/tasklet.c b/core/tasklet.c new file mode 100644 index 000000000000..196b5586f11a --- /dev/null +++ b/core/tasklet.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * 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. + */ + +#include + +#include "irq.h" +#include "tasklet.h" +#include "thread.h" +#include "thread_flags.h" + +static thread_t *__tasklet_thread; +static clist_node_t tasklet_list; + +static char tasklet_stack[THREAD_STACKSIZE_TASKLET]; +const char *tasklet_name = "tasklet"; + +void tasklet_add(tasklet_t *tasklet) +{ + unsigned state = irq_disable(); + if (!tasklet->tasklet_node.next) { + clist_rpush(&tasklet_list, &tasklet->tasklet_node); + } + irq_restore(state); +} + +void tasklet_schedule(tasklet_t *tasklet) +{ + assert(tasklet); + assert(__tasklet_thread); + + tasklet_add(tasklet); + thread_flags_set(__tasklet_thread, THREAD_FLAG_TASKLET); +} + +tasklet_t *tasklet_pop(void) +{ + tasklet_t *res; + unsigned state = irq_disable(); + res = (tasklet_t *)clist_lpop(&tasklet_list); + if(res) { + res->tasklet_node.next = NULL; + } + irq_restore(state); + return res; +} + +static void *tasklet_thread(void *arg) +{ + (void) arg; + tasklet_t *res; + while(thread_flags_wait_any(THREAD_FLAG_TASKLET)) { + while((res = tasklet_pop())) { + res->task(res->arg); + } + } + return NULL; +} + +void tasklet_list_reset(void) +{ + memset(&tasklet_list, '\0', sizeof(clist_node_t)); +} + +void tasklet_thread_create(void) +{ + __tasklet_thread = (thread_t*) thread_get(thread_create(tasklet_stack, sizeof(tasklet_stack), + THREAD_PRIORITY_TASKLET, + THREAD_CREATE_WOUT_YIELD | THREAD_CREATE_STACKTEST, + tasklet_thread, NULL, tasklet_name)); +} diff --git a/tests/unittests/tests-tasklet/Makefile b/tests/unittests/tests-tasklet/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/tests/unittests/tests-tasklet/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-tasklet/Makefile.include b/tests/unittests/tests-tasklet/Makefile.include new file mode 100644 index 000000000000..957e80dbe9e0 --- /dev/null +++ b/tests/unittests/tests-tasklet/Makefile.include @@ -0,0 +1 @@ +USEMODULE += core_tasklet diff --git a/tests/unittests/tests-tasklet/tests-tasklet.c b/tests/unittests/tests-tasklet/tests-tasklet.c new file mode 100644 index 000000000000..2065e80e115b --- /dev/null +++ b/tests/unittests/tests-tasklet/tests-tasklet.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * 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. + */ + +#include "embUnit.h" +#include "tests-tasklet.h" + +#include "tasklet.h" + +#define TASKLET_FAIL (0U) +#define TASKLET_SUCCESS (1U) +#define TASKLET_SUCCESS_ALT (2U) + +static int tasklet_arg = TASKLET_FAIL; +static int tasklet_arg_alt = TASKLET_FAIL; +static tasklet_t tasklet; +static tasklet_t tasklet_alt; +static tasklet_t tasklet_sched; + +static void tasklet_func(void *arg) +{ + int *t = arg; + *t = TASKLET_SUCCESS; +} + +static void tasklet_func_alt(void *arg) +{ + int *t = arg; + *t = TASKLET_SUCCESS_ALT; +} + +static void tasklet_func_schedule(void *arg) +{ + (void) arg; + tasklet_schedule(&tasklet); +} + +static void set_up(void) +{ + tasklet_list_reset(); + memset(&tasklet, '\0', sizeof(tasklet_t)); + memset(&tasklet_alt, '\0', sizeof(tasklet_t)); + memset(&tasklet_sched, '\0', sizeof(tasklet_t)); + + tasklet_init(&tasklet, tasklet_func, &tasklet_arg); + tasklet_init(&tasklet_alt, tasklet_func_alt, &tasklet_arg_alt); + tasklet_init(&tasklet_sched, tasklet_func_schedule, NULL); + + tasklet_arg = TASKLET_FAIL; + tasklet_arg_alt = TASKLET_FAIL; +} + +static void test_tasklet_init(void) +{ + TEST_ASSERT_NULL(tasklet.tasklet_node.next); + TEST_ASSERT(tasklet.task == tasklet_func); + TEST_ASSERT(tasklet.arg == &tasklet_arg); +} + +static void test_tasklet_schedule(void) +{ + tasklet_schedule(&tasklet); + TEST_ASSERT(tasklet_arg == TASKLET_SUCCESS); + TEST_ASSERT_NULL(tasklet.tasklet_node.next); +} + +static void test_tasklet_schedule_task_is_removed(void) +{ + tasklet_schedule(&tasklet); + TEST_ASSERT_NULL(tasklet_pop()); +} + +static void test_tasklet_add_pop(void) +{ + tasklet_t tasklet = {0}; + TEST_ASSERT_NULL(tasklet_pop()); + + tasklet_add(&tasklet); + TEST_ASSERT(((tasklet_t*) tasklet.tasklet_node.next) == &tasklet); + + tasklet_t *pop = tasklet_pop(); + + TEST_ASSERT(pop == &tasklet); + TEST_ASSERT_NULL(pop->tasklet_node.next); + TEST_ASSERT_NULL(tasklet_pop()); +} + +static void test_tasklet_schedule_multiple(void) +{ + tasklet_schedule(&tasklet); + TEST_ASSERT(tasklet_arg == TASKLET_SUCCESS); + TEST_ASSERT_NULL(tasklet.tasklet_node.next); + + tasklet_schedule(&tasklet_alt); + TEST_ASSERT(tasklet_arg_alt == TASKLET_SUCCESS_ALT); + TEST_ASSERT_NULL(tasklet_alt.tasklet_node.next); +} + +static void test_tasklet_schedule_in_tasklet(void) +{ + tasklet_schedule(&tasklet_sched); + TEST_ASSERT(tasklet_arg == TASKLET_SUCCESS); +} + +static void test_tasklet_add_pop_multiple(void) +{ + tasklet_t tasklet = {0}; + tasklet_t tasklet_alt = {0}; + + tasklet_add(&tasklet); + tasklet_add(&tasklet_alt); + TEST_ASSERT(((tasklet_t*) tasklet.tasklet_node.next) == &tasklet_alt); + TEST_ASSERT(((tasklet_t*) tasklet_alt.tasklet_node.next) == &tasklet); + + TEST_ASSERT(tasklet_pop() == &tasklet); + TEST_ASSERT(tasklet_pop() == &tasklet_alt); + TEST_ASSERT_NULL(tasklet_pop()); + TEST_ASSERT_NULL(tasklet.tasklet_node.next); + TEST_ASSERT_NULL(tasklet_alt.tasklet_node.next); +} + +Test *test_tasklet_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_tasklet_init), + new_TestFixture(test_tasklet_schedule), + new_TestFixture(test_tasklet_schedule_task_is_removed), + new_TestFixture(test_tasklet_add_pop), + new_TestFixture(test_tasklet_add_pop_multiple), + new_TestFixture(test_tasklet_schedule_multiple), + new_TestFixture(test_tasklet_schedule_in_tasklet), + }; + + EMB_UNIT_TESTCALLER(tasklet_tests, set_up, NULL, + fixtures); + + return (Test *)&tasklet_tests; +} + +void tests_tasklet(void) +{ + TESTS_RUN(test_tasklet_tests()); +} diff --git a/tests/unittests/tests-tasklet/tests-tasklet.h b/tests/unittests/tests-tasklet/tests-tasklet.h new file mode 100644 index 000000000000..622a1c216cb0 --- /dev/null +++ b/tests/unittests/tests-tasklet/tests-tasklet.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * 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. + */ + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unittests for the ``core_tasklet`` module + * + * @author Jose I. Alamos + */ +#ifndef TESTS_TASKLET_H +#define TESTS_TASKLET_H +#include "embUnit/embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +* @brief The entry point of this test suite. +*/ +void tests_tasklet(void); + +/** + * @brief Generates tests for tasklet + * + * @return embUnit tests if successful, NULL if not. + */ +Test *tests_tasklet_tests(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_TASKLET_H */ +/** @} */