From fb4d2e509a934340cb85690256c319bf1ce95e4e Mon Sep 17 00:00:00 2001 From: Vincent Dupont Date: Fri, 29 Jan 2021 17:04:01 +0100 Subject: [PATCH] ztimer: add ztimer_rmutex_lock_timeout() Similar to xtimer_rmutex_lock_timeout() --- sys/include/ztimer.h | 14 + sys/ztimer/util.c | 16 + tests/ztimer_rmutex_lock_timeout/Makefile | 6 + tests/ztimer_rmutex_lock_timeout/Makefile.ci | 7 + .../app.config.test | 6 + tests/ztimer_rmutex_lock_timeout/main.c | 381 ++++++++++++++++++ .../tests/01-run.py | 40 ++ 7 files changed, 470 insertions(+) create mode 100644 tests/ztimer_rmutex_lock_timeout/Makefile create mode 100644 tests/ztimer_rmutex_lock_timeout/Makefile.ci create mode 100644 tests/ztimer_rmutex_lock_timeout/app.config.test create mode 100644 tests/ztimer_rmutex_lock_timeout/main.c create mode 100755 tests/ztimer_rmutex_lock_timeout/tests/01-run.py diff --git a/sys/include/ztimer.h b/sys/include/ztimer.h index f1f93e3e90..de3288c36e 100644 --- a/sys/include/ztimer.h +++ b/sys/include/ztimer.h @@ -237,6 +237,7 @@ #include "sched.h" #include "msg.h" #include "mutex.h" +#include "rmutex.h" #ifdef __cplusplus extern "C" { @@ -532,6 +533,19 @@ void ztimer_set_timeout_flag(ztimer_clock_t *clock, ztimer_t *timer, int ztimer_mutex_lock_timeout(ztimer_clock_t *clock, mutex_t *mutex, uint32_t timeout); +/** + * @brief Try to lock the given rmutex, but give up after @p timeout + * + * @param[in] clock ztimer clock to operate on + * @param[in,out] rmutex rmutex object to lock + * @param[in] timeout timeout after which to give up + * + * @retval 0 Success, caller has the rmutex + * @retval -ECANCELED Failed to obtain rmutex within @p timeout + */ +int ztimer_rmutex_lock_timeout(ztimer_clock_t *clock, rmutex_t *rmutex, + uint32_t timeout); + /** * @brief Update ztimer clock head list offset * diff --git a/sys/ztimer/util.c b/sys/ztimer/util.c index 280f4e19fa..7ab94acfbe 100644 --- a/sys/ztimer/util.c +++ b/sys/ztimer/util.c @@ -25,6 +25,7 @@ #include "irq.h" #include "mutex.h" +#include "rmutex.h" #include "thread.h" #include "ztimer.h" @@ -180,3 +181,18 @@ int ztimer_mutex_lock_timeout(ztimer_clock_t *clock, mutex_t *mutex, ztimer_remove(clock, &t); return 0; } + +int ztimer_rmutex_lock_timeout(ztimer_clock_t *clock, rmutex_t *rmutex, + uint32_t timeout) +{ + if (rmutex_trylock(rmutex)) { + return 0; + } + if (ztimer_mutex_lock_timeout(clock, &rmutex->mutex, timeout) == 0) { + atomic_store_explicit(&rmutex->owner, + thread_getpid(), memory_order_relaxed); + rmutex->refcount++; + return 0; + } + return -ECANCELED; +} diff --git a/tests/ztimer_rmutex_lock_timeout/Makefile b/tests/ztimer_rmutex_lock_timeout/Makefile new file mode 100644 index 0000000000..a240628e83 --- /dev/null +++ b/tests/ztimer_rmutex_lock_timeout/Makefile @@ -0,0 +1,6 @@ +include ../Makefile.tests_common + +USEMODULE += ztimer_usec +USEMODULE += shell + +include $(RIOTBASE)/Makefile.include diff --git a/tests/ztimer_rmutex_lock_timeout/Makefile.ci b/tests/ztimer_rmutex_lock_timeout/Makefile.ci new file mode 100644 index 0000000000..ff454e3604 --- /dev/null +++ b/tests/ztimer_rmutex_lock_timeout/Makefile.ci @@ -0,0 +1,7 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-nano \ + arduino-uno \ + atmega328p \ + nucleo-l011k4 \ + # diff --git a/tests/ztimer_rmutex_lock_timeout/app.config.test b/tests/ztimer_rmutex_lock_timeout/app.config.test new file mode 100644 index 0000000000..df672d7153 --- /dev/null +++ b/tests/ztimer_rmutex_lock_timeout/app.config.test @@ -0,0 +1,6 @@ +# this file enables modules defined in Kconfig. Do not use this file for +# application configuration. This is only needed during migration. +CONFIG_MODULE_ZTIMER=y +CONFIG_MODULE_ZTIMER_PERIPH_TIMER=y +CONFIG_MODULE_ZTIMER_USEC=y +CONFIG_MODULE_SHELL=y diff --git a/tests/ztimer_rmutex_lock_timeout/main.c b/tests/ztimer_rmutex_lock_timeout/main.c new file mode 100644 index 0000000000..48c6bf49d7 --- /dev/null +++ b/tests/ztimer_rmutex_lock_timeout/main.c @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2020 Freie Universität Berlin, + * + * 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 testing ztimer_rmutex_lock_timeout function + * + * + * @author Julian Holzwarth + * + */ + +#include +#include +#include "shell.h" +#include "ztimer.h" +#include "thread.h" +#include "msg.h" +#include "irq.h" + +/* timeout at one millisecond (1000 us) to make sure it does not spin. */ +#define LONG_RMUTEX_TIMEOUT 1000 + +/* timeout set to 1us */ +#define SHORT_RMUTEX_TIMEOUT (1) + +/** + * Forward declarations + */ +static int cmd_test_ztimer_rmutex_lock_timeout_long_unlocked(int argc, + char **argv); +static int cmd_test_ztimer_rmutex_lock_timeout_long_locked(int argc, + char **argv); +static int cmd_test_ztimer_rmutex_lock_timeout_low_prio_thread( + int argc, char **argv); + +static int cmd_test_ztimer_rmutex_lock_timeout_short_unlocked(int argc, + char **argv); +static int cmd_test_ztimer_rmutex_lock_timeout_short_locked(int argc, + char **argv); + +#define error_wrong_variables "Error: rmutex wrong variables in struct" +#define error_rmutex_taken "Error: rmutex taken" +#define error_timeout "Error: rmutex timed out" +#define shell_help_output "See main.c" + +/** + * @brief List of command for this application. + */ +static const shell_command_t shell_commands[] = { + { "t1", shell_help_output, + cmd_test_ztimer_rmutex_lock_timeout_long_unlocked, }, + { "t2", shell_help_output, + cmd_test_ztimer_rmutex_lock_timeout_long_locked, }, + { "t3", shell_help_output, + cmd_test_ztimer_rmutex_lock_timeout_low_prio_thread, }, + { "t4", shell_help_output, + cmd_test_ztimer_rmutex_lock_timeout_short_unlocked, }, + { "t5", shell_help_output, + cmd_test_ztimer_rmutex_lock_timeout_short_locked, }, + { NULL, NULL, NULL } +}; + +/* main Thread PID */ +static kernel_pid_t main_thread_pid; + +/** + * @brief stack for + * cmd_test_ztimer_rmutex_lock_timeout_low_prio_thread + */ +static char t_stack[THREAD_STACKSIZE_SMALL]; + +/** + * @brief send message and suicide thread + * + * This function will send a message to a thread without yielding + * and terminates the calling thread. This can be used to wakeup a + * thread and terminating yourself. + * This function calls sched_task_exit() + * + * @param[in] m Pointer to preallocated @ref msg_t structure, must + * not be NULL. + * @param[in] target_pid PID of target thread + * + */ +static NORETURN void msg_send_sched_task_exit(msg_t *m, kernel_pid_t target_pid) +{ + (void)irq_disable(); + msg_send_int(m, target_pid); + sched_task_exit(); +} + +/** + * @brief this thread locks a rmutex + */ +static void *lock_rmutex_thread(void *arg) +{ + rmutex_t *test_rmutex = (rmutex_t *)arg; + + rmutex_lock(test_rmutex); + return NULL; +} + +/** + * @brief thread function for + * cmd_test_ztimer_rmutex_lock_timeout_low_prio_thread + */ +void *test_thread(void *arg) +{ + rmutex_t *test_rmutex = (rmutex_t *)arg; + msg_t msg; + + rmutex_lock(test_rmutex); + thread_wakeup(main_thread_pid); + + rmutex_unlock(test_rmutex); + + msg_send_sched_task_exit(&msg, main_thread_pid); +} + +/** + * @brief shell command to test ztimer_rmutex_lock_timeout + * + * the rmutex is not locked before the function call and + * the timer long. Meaning the timer will get removed + * before triggering. + * + * @param[in] argc Number of arguments + * @param[in] argv Array of arguments + * + * @return 0 on success + */ +static int cmd_test_ztimer_rmutex_lock_timeout_long_unlocked(int argc, + char **argv) +{ + (void)argc; + (void)argv; + + rmutex_t test_rmutex = RMUTEX_INIT; + + if (ztimer_rmutex_lock_timeout(ZTIMER_USEC, &test_rmutex, LONG_RMUTEX_TIMEOUT) == 0) { + /* rmutex has to be locked once */ + if (atomic_load_explicit(&test_rmutex.owner, + memory_order_relaxed) == thread_getpid() && + test_rmutex.refcount == 1 && + mutex_trylock(&test_rmutex.mutex) == 0) { + puts("OK"); + } + else { + puts(error_wrong_variables); + } + } + else { + puts(error_timeout); + } + /* to make the test easier to read */ + printf("\n"); + + return 0; +} + +/** + * @brief shell command to test ztimer_rmutex_lock_timeout + * + * the rmutex is locked from another thread before the + * function call and the timer is long. + * Meaning the timer will trigger + * and remove the thread from the rmutex waiting list. + * To loc + * + * @param[in] argc Number of arguments + * @param[in] argv Array of arguments + * + * @return 0 on success + */ +static int cmd_test_ztimer_rmutex_lock_timeout_long_locked(int argc, + char **argv) +{ + (void)argc; + (void)argv; + + rmutex_t test_rmutex = RMUTEX_INIT; + + /* lock rmutex from different thread */ + kernel_pid_t second_t_pid = thread_create( t_stack, sizeof(t_stack), + THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, + lock_rmutex_thread, + (void *)&test_rmutex, + "lock_thread"); + + if (ztimer_rmutex_lock_timeout(ZTIMER_USEC, &test_rmutex, LONG_RMUTEX_TIMEOUT) == 0) { + puts(error_rmutex_taken); + } + else { + /* rmutex has to be locked once */ + if (atomic_load_explicit(&test_rmutex.owner, + memory_order_relaxed) == second_t_pid && + test_rmutex.refcount == 1 && + mutex_trylock(&test_rmutex.mutex) == 0) { + puts("OK"); + } + else { + puts(error_wrong_variables); + } + } + /* to make the test easier to read */ + printf("\n"); + + return 0; +} + +/** + * @brief shell command to test ztimer_rmutex_lock_timeout + * + * This function will create a new thread with lower prio + * than the main thread (this function should be called from + * the main thread). The new thread will get a rmutex and will + * lock it. This function (main thread) calls ztimer_rmutex_lock_timeout. + * The other thread will then unlock the rmutex. The main + * thread gets the rmutex and wakes up. The timer will not + * trigger because the main threads gets the rmutex. + * + * @param[in] argc Number of arguments + * @param[in] argv Array of arguments + * + * @return 0 on success + */ +static int cmd_test_ztimer_rmutex_lock_timeout_low_prio_thread( + int argc, char **argv) +{ + (void)argc; + (void)argv; + + main_thread_pid = thread_getpid(); + rmutex_t test_rmutex = RMUTEX_INIT; + + kernel_pid_t second_t_pid = thread_create( t_stack, sizeof(t_stack), + THREAD_PRIORITY_MAIN + 1, + THREAD_CREATE_STACKTEST, + test_thread, + (void *)&test_rmutex, + "test_thread"); + (void)second_t_pid; + thread_sleep(); + + if (ztimer_rmutex_lock_timeout(ZTIMER_USEC, &test_rmutex, LONG_RMUTEX_TIMEOUT) == 0) { + /* rmutex has to be locked once */ + if (atomic_load_explicit(&test_rmutex.owner, + memory_order_relaxed) == thread_getpid() && + test_rmutex.refcount == 1 && + mutex_trylock(&test_rmutex.mutex) == 0) { + puts("OK"); + } + else { + puts(error_wrong_variables); + } + } + else { + puts(error_timeout); + } + + /* to end the created thread */ + msg_t msg; + msg_receive(&msg); + + /* to make the test easier to read */ + printf("\n"); + + return 0; +} + +/** + * @brief shell command to test ztimer_rmutex_lock_timeout when spinning + * + * The rmutex is locked before the function call and + * the timer long. Meaning the timer will trigger before + * ztimer_rmutex_lock_timeout tries to acquire the rmutex. + * + * @param[in] argc Number of arguments + * @param[in] argv Array of arguments + * + * @return 0 on success + */ +static int cmd_test_ztimer_rmutex_lock_timeout_short_locked(int argc, + char **argv) +{ + (void)argc; + (void)argv; + + rmutex_t test_rmutex = RMUTEX_INIT; + + kernel_pid_t second_t_pid = thread_create( t_stack, sizeof(t_stack), + THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, + lock_rmutex_thread, + (void *)&test_rmutex, + "lock_thread"); + + if (ztimer_rmutex_lock_timeout(ZTIMER_USEC, &test_rmutex, SHORT_RMUTEX_TIMEOUT) == 0) { + puts(error_rmutex_taken); + } + else { + /* rmutex has to be locked once */ + if (atomic_load_explicit(&test_rmutex.owner, + memory_order_relaxed) == second_t_pid && + test_rmutex.refcount == 1 && + mutex_trylock(&test_rmutex.mutex) == 0) { + puts("OK"); + } + else { + puts(error_wrong_variables); + } + } + /* to make the test easier to read */ + printf("\n"); + + return 0; +} + +/** + * @brief shell command to test ztimer_rmutex_lock_timeout when spinning + * + * the rmutex is not locked before the function is called and + * the timer is short. Meaning the timer will trigger before + * ztimer_rmutex_lock_timeout tries to acquire the rmutex. + * + * @param[in] argc Number of arguments + * @param[in] argv Array of arguments + * + * @return 0 on success + */ +static int cmd_test_ztimer_rmutex_lock_timeout_short_unlocked(int argc, + char **argv) +{ + (void)argc; + (void)argv; + + rmutex_t test_rmutex = RMUTEX_INIT; + + if (ztimer_rmutex_lock_timeout(ZTIMER_USEC, &test_rmutex, SHORT_RMUTEX_TIMEOUT) == 0) { + /* rmutex has to be locked once */ + if (atomic_load_explicit(&test_rmutex.owner, + memory_order_relaxed) == thread_getpid() && + test_rmutex.refcount == 1 && + mutex_trylock(&test_rmutex.mutex) == 0) { + puts("OK"); + } + else { + puts(error_wrong_variables); + } + } + else { + puts(error_timeout); + } + /* to make the test easier to read */ + printf("\n"); + + return 0; +} + +/** + * @brief main function starting shell + * + * @return 0 on success + */ +int main(void) +{ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + return 0; +} diff --git a/tests/ztimer_rmutex_lock_timeout/tests/01-run.py b/tests/ztimer_rmutex_lock_timeout/tests/01-run.py new file mode 100755 index 0000000000..72a4e764f3 --- /dev/null +++ b/tests/ztimer_rmutex_lock_timeout/tests/01-run.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2020 Freie Universität Berlin, +# +# 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. + +# @author Julian Holzwarth + +import sys +import pexpect +from testrunner import run + + +def testfunc(child): + # Try to wait for the shell + for _ in range(0, 10): + child.sendline("help") + if child.expect_exact(["> ", pexpect.TIMEOUT], timeout=1) == 0: + break + child.sendline("t1") + child.expect("OK") + child.expect_exact("> ") + child.sendline("t2") + child.expect("OK") + child.expect_exact("> ") + child.sendline("t3") + child.expect("OK") + child.expect_exact("> ") + child.sendline("t4") + child.expect("OK") + child.expect_exact("> ") + child.sendline("t5") + child.expect("OK") + child.expect_exact("> ") + + +if __name__ == "__main__": + sys.exit(run(testfunc))