diff --git a/cpu/atmega_common/include/cpu_conf.h b/cpu/atmega_common/include/cpu_conf.h index d20bea4eb2..43d43afcd4 100644 --- a/cpu/atmega_common/include/cpu_conf.h +++ b/cpu/atmega_common/include/cpu_conf.h @@ -49,7 +49,7 @@ extern "C" { * to avoid not printing of debug in interrupts */ #ifndef THREAD_STACKSIZE_IDLE -#ifdef MODULE_XTIMER +#if MODULE_XTIMER || MODULE_ZTIMER64 /* xtimer's 64 bit arithmetic doesn't perform well on 8 bit archs. In order to * prevent a stack overflow when an timer triggers while the idle thread is * running, we have to increase the stack size then diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 753b1708f7..e9d0e3ae9b 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -165,7 +165,9 @@ PSEUDOMODULES += wakaama_objects_% PSEUDOMODULES += wifi_enterprise PSEUDOMODULES += xtimer_on_ztimer PSEUDOMODULES += zptr -PSEUDOMODULES += ztimer% +PSEUDOMODULES += ztimer +PSEUDOMODULES += ztimer_% +PSEUDOMODULES += ztimer64_% # ztimer's main module is called "ztimer_core" NO_PSEUDOMODULES += ztimer_core diff --git a/sys/Kconfig b/sys/Kconfig index 30e9feeb62..7e95f913ef 100644 --- a/sys/Kconfig +++ b/sys/Kconfig @@ -84,6 +84,7 @@ rsource "usb_board_reset/Kconfig" rsource "vfs/Kconfig" rsource "xtimer/Kconfig" rsource "ztimer/Kconfig" +rsource "ztimer64/Kconfig" config MODULE_CPP bool "Use CPP compiler" diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 92f75e2c01..bd0c4f3a89 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -755,10 +755,15 @@ ifneq (,$(filter suit_%,$(USEMODULE))) endif # include ztimer dependencies -ifneq (,$(filter ztimer% %ztimer,$(USEMODULE))) +ifneq (,$(filter ztimer ztimer_% %ztimer,$(USEMODULE))) include $(RIOTBASE)/sys/ztimer/Makefile.dep endif +# include ztimer64 dependencies +ifneq (,$(filter ztimer64%,$(USEMODULE))) + include $(RIOTBASE)/sys/ztimer64/Makefile.dep +endif + ifneq (,$(filter evtimer,$(USEMODULE))) ifneq (,$(filter evtimer_on_ztimer,$(USEMODULE))) USEMODULE += ztimer_msec diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index 062c9a3fe8..cb86264c6e 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -33,6 +33,11 @@ void auto_init(void) void ztimer_init(void); ztimer_init(); } + if (IS_USED(MODULE_AUTO_INIT_ZTIMER64)) { + LOG_DEBUG("Auto init ztimer64.\n"); + void ztimer64_init(void); + ztimer64_init(); + } if (IS_USED(MODULE_AUTO_INIT_XTIMER) && !IS_USED(MODULE_ZTIMER_XTIMER_COMPAT)) { LOG_DEBUG("Auto init xtimer.\n"); diff --git a/sys/include/ztimer64.h b/sys/include/ztimer64.h new file mode 100644 index 0000000000..80868e9e7c --- /dev/null +++ b/sys/include/ztimer64.h @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2021 Inria + * 2021 Freie Universität Berlin + * 2021 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 sys_ztimer + * @defgroup sys_ztimer64 ztimer 64bit version + * @brief ztimer 64bit version + * + * # Introduction + * + * This module implements a 64bit version of the ztimer API. + * All ztimer_* functions have a ztimer64_* equivalent that behaves the same, + * but allowing offsets >= 2^32. + * + * In addition to that, ztimer64 offers functions taking absolute target times + * as parameter. + * If the absolute target time is in the past, a timer would trigger right away. + * + * Here's a table for absolute variants: + * + * | relative version | absolute version | + * |--------------------------------|--------------------------------| + * | ztimer64_set() | ztimer64_set_at() | + * | ztimer64_sleep() | ztimer64_sleep_until() | + * | ztimer64_set_msg() | ztimer64_set_msg_at() | + * | ztimer64_set_wakeup() | ztimer64_set_wakeup_at() | + * | ztimer64_set_timeout_flag() | ztimer64_set_timeout_flag_at() | + * | ztimer64_msg_receive_timeout() | ztimer64_msg_receive_until() | + * | ztimer64_mutex_lock_timeout() | ztimer64_mutex_lock_until() | + * | ztimer64_rmutex_lock_timeout() | ztimer64_rmutex_lock_until() | + * + * ## ztimer64 clocks + * + * ztimer64 provides automatic setup of ZTIMER64_SEC, ZTIMER64_MSEC and + * ZTIMER64_USEC, using the ztimer(32) clocks as base clocks. + * Enable them by depending on ztimer64_sec, ztimer64_msec and / or ztimer64_usec. + * + * @warning ztimer64 always keeps a timer running on the base clock. Depending + * on the base clock, this blocks low-power sleep modes. If the hardware + * supports it, the msec and sec timers should be using a low-power capable + * timer (e.g., periph_rtt). ztimer64_usec will almost certainly block + * low-power sleep. + * + * TODO: + * - some explicit power management + * - implement adjust_set and adjust_sleep API + * - implement setting a 64bit reference time (epoch) + * + * @{ + * + * @file + * @brief ztimer 64bit API + * + * @author Kaspar Schleiser + */ + +#ifndef ZTIMER64_H +#define ZTIMER64_H + +#include + +#include "mutex.h" +#include "msg.h" +#include "ztimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ztimer64_t forward declaration + */ +typedef struct ztimer64_base ztimer64_base_t; +/** + * @brief ztimer64_clock_t forward declaration + */ +typedef struct ztimer64_clock ztimer64_clock_t; + +/** + * @brief Minimum information for each timer + */ +struct ztimer64_base { + ztimer64_base_t *next; /**< next timer in list */ + uint64_t target; /**< absolute target time */ +}; + +/** + * @brief ztimer64 structure + * + * This type represents an instance of a timer, which is set on an + * underlying clock object + */ +typedef struct { + ztimer64_base_t base; /**< clock list entry */ + void (*callback)(void *arg); /**< timer callback function pointer */ + void *arg; /**< timer callback argument */ +} ztimer64_t; + +/** + * @brief ztimer64 clock structure + */ +struct ztimer64_clock { + ztimer64_base_t *first; /**< list of active timers */ + ztimer_clock_t *base_clock; /**< 32bit clock backend */ + ztimer_t base_timer; /**< 32bit backend timer */ + uint64_t checkpoint; /**< lower timer checkpoint offset */ + uint16_t adjust_set; /**< will be subtracted on every set() */ + uint16_t adjust_sleep; /**< will be subtracted on every sleep(), + in addition to adjust_set */ +#if MODULE_PM_LAYERED || DOXYGEN + uint8_t block_pm_mode; /**< min. pm mode to block for the clock to run */ +#endif +}; + +/** + * @brief Get the current time from a clock + * + * @param[in] clock ztimer clock to operate on + * + * @return Current count on @p clock + */ +uint64_t ztimer64_now(ztimer64_clock_t *clock); + +/** + * @brief Get absolute target time for a clock given offset + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] offset relative target time + * + * @returns absolute target time + */ +static inline uint64_t ztimer64_offset2absolute(ztimer64_clock_t *clock, + uint64_t offset) +{ + unsigned state = irq_disable(); + uint64_t result = ztimer64_now(clock) + offset; + + irq_restore(state); + return result; +} + +/** + * @brief Set a timer on a clock (absolute version) + * + * This will place @p timer in the timer targets queue of @p clock. + * + * @note The memory pointed to by @p timer is not copied and must + * remain in scope until the callback is fired or the timer + * is removed via @ref ztimer64_remove + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] timer timer entry to set + * @param[in] target absolute target time + */ +void ztimer64_set_at(ztimer64_clock_t *clock, ztimer64_t *timer, + uint64_t target); + +/** + * @brief Set a timer on a clock (relative version) + * + * This will place @p timer in the timer targets queue of @p clock. + * + * @note The memory pointed to by @p timer is not copied and must + * remain in scope until the callback is fired or the timer + * is removed via @ref ztimer64_remove + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] timer timer entry to set + * @param[in] offset relative target time + */ +static inline void ztimer64_set(ztimer64_clock_t *clock, ztimer64_t *timer, + uint64_t offset) +{ + ztimer64_set_at(clock, timer, + ztimer64_offset2absolute(clock, offset)); +} + +/** + * @brief Check if a timer is currently active + * + * @param[in] timer timer to check + * + * @return 1 if timer is active + * @return 0 if timer is not active + */ +unsigned ztimer64_is_set(const ztimer64_t *timer); + +/** + * @brief Remove a timer from a clock + * + * This will place @p timer in the timer targets queue for @p clock. + * + * This function does nothing if @p timer is not found in the timer queue of + * @p clock. + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] timer timer entry to remove + */ +void ztimer64_remove(ztimer64_clock_t *clock, ztimer64_t *timer); + +/** + * @brief Post a message at a specified time + * + * This function sets a timer that will send a message at time @p target. + * + * @note The memory pointed to by @p timer and @p msg will not be copied, i.e. + * `*timer` and `*msg` needs to remain valid until the timer has triggered. + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] timer ztimer64 timer struct to use + * @param[in] target absolute target time + * @param[in] msg pointer to msg that will be sent + * @param[in] target_pid pid the message will be sent to + */ +void ztimer64_set_msg_at(ztimer64_clock_t *clock, ztimer64_t *timer, + uint64_t target, + msg_t *msg, kernel_pid_t target_pid); + +/** + * @brief Post a message after a delay (relative version) + * + * This function sets a timer that will send a message @p offset ticks + * from now. + * + * @note The memory pointed to by @p timer and @p msg will not be copied, i.e. + * `*timer` and `*msg` needs to remain valid until the timer has triggered. + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] timer ztimer64 timer struct to use + * @param[in] offset relative target time + * @param[in] msg pointer to msg that will be sent + * @param[in] target_pid pid the message will be sent to + */ +static inline void ztimer64_set_msg(ztimer64_clock_t *clock, ztimer64_t *timer, + uint64_t offset, + msg_t *msg, kernel_pid_t target_pid) +{ + ztimer64_set_msg_at(clock, timer, + ztimer64_offset2absolute(clock, offset), + msg, target_pid); +} + +/** + * @brief receive a message (blocking, with absolute timeout) + * + * Similar to msg_receive(), but with a timeout parameter. + * The function will return after waiting at most until @p target. + * + * @note: This might function might leave a message with type MSG_ZTIMER64 in the + * thread's message queue, which must be handled (ignored). + * + * @param[in] clock ztimer64 clock to operate on + * @param[out] msg pointer to buffer which will be filled if a + * message is received + * @param[in] target absolute target, in @p clock time + * + * @return >=0 if a message was received + * @return -ETIME on timeout + */ +int ztimer64_msg_receive_until(ztimer64_clock_t *clock, msg_t *msg, + uint64_t target); + +/** + * @brief receive a message (blocking, with relative timeout) + * + * Similar to msg_receive(), but with a timeout parameter. + * The function will return after waiting at most @p timeout ticks. + * + * @note: This might function might leave a message with type MSG_ZTIMER64 in the + * thread's message queue, which must be handled (ignored). + * + * @param[in] clock ztimer64 clock to operate on + * @param[out] msg pointer to buffer which will be filled if a + * message is received + * @param[in] timeout relative timeout, in @p clock time units + * + * @return >=0 if a message was received + * @return -ETIME on timeout + */ +static inline int ztimer64_msg_receive_timeout(ztimer64_clock_t *clock, + msg_t *msg, + uint64_t timeout) +{ + return ztimer64_msg_receive_until(clock, msg, + ztimer64_offset2absolute(clock, timeout)); +} + +#define MSG_ZTIMER64 0xc83f /**< msg type used by ztimer64_msg_receive_timeout */ + +/** + * @brief Suspend the calling thread until the time (@p last_wakeup + @p period) + * + * This function can be used to create periodic wakeups. + * + * When the function returns, @p last_wakeup is set to + * (@p last_wakeup + @p period). + * + * @c last_wakeup should be set to ztimer64_now(@p clock) before first call of the + * function. + * + * If the time (@p last_wakeup + @p period) has already passed, the function + * sets @p last_wakeup to @p last_wakeup + @p period and returns immediately. + * + * @note if you period is smaller than 2^32 ticks, consider using the 32bit version. + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] last_wakeup base time stamp for the wakeup + * @param[in] period time in ticks that will be added to @p last_wakeup + */ +void ztimer64_periodic_wakeup(ztimer64_clock_t *clock, uint64_t *last_wakeup, + uint64_t period); + +/** + * @brief Put the calling thread to sleep until the specified time + * + * @param[in] clock ztimer64 clock to use + * @param[in] target wakeup time, in @p clock time + */ +void ztimer64_sleep_until(ztimer64_clock_t *clock, uint64_t target); + +/** + * @brief Put the calling thread to sleep for the specified number of ticks + * + * @param[in] clock ztimer64 clock to use + * @param[in] duration duration of sleep, in @p ztimer time units + */ +static inline void ztimer64_sleep(ztimer64_clock_t *clock, uint64_t duration) +{ + ztimer64_sleep_until(clock, ztimer64_offset2absolute(clock, duration)); +} + +/** + * @brief Busy-wait until specified target time + * + * @note: This blocks lower priority threads. Use only for *very* short delays. + * + * @param[in] clock ztimer64 clock to use + * @param[in] target time when spinning should end, in @p clock time + */ +static inline void ztimer64_spin_until(ztimer64_clock_t *clock, uint64_t target) +{ + while (ztimer64_now(clock) <= target) {} +} + +/** + * @brief Set a timer that wakes up a thread (absolute version) + * + * This function sets a timer that will wake up a thread when the timer has + * expired. + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] timer timer struct to work with. + * @param[in] target wakeup time + * @param[in] pid pid of the thread that will be woken up + */ +void ztimer64_set_wakeup_at(ztimer64_clock_t *clock, ztimer64_t *timer, + uint64_t target, + kernel_pid_t pid); + +/** + * @brief Set a timer that wakes up a thread (relative version) + * + * This function sets a timer that will wake up a thread when the timer has + * expired. + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] timer timer struct to work with. + * @param[in] offset clock ticks from now + * @param[in] pid pid of the thread that will be woken up + */ +static inline void ztimer64_set_wakeup(ztimer64_clock_t *clock, + ztimer64_t *timer, uint64_t offset, + kernel_pid_t pid) +{ + ztimer64_set_wakeup_at(clock, timer, + ztimer64_offset2absolute(clock, offset), pid); +} + +/** + * @brief Set timeout thread flag at @p target time + * + * This function will set THREAD_FLAG_TIMEOUT on the current thread at time + * @p target. + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] timer timer struct to use + * @param[in] target target in @p clock time + */ +void ztimer64_set_timeout_flag_at(ztimer64_clock_t *clock, ztimer64_t *timer, + uint64_t target); + +/** + * @brief Set timeout thread flag after @p timeout + * + * This function will set THREAD_FLAG_TIMEOUT on the current thread after @p + * timeout usec have passed. + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] timer timer struct to use + * @param[in] timeout timeout in ztimer64_clock's ticks + */ +static inline void ztimer64_set_timeout_flag(ztimer64_clock_t *clock, + ztimer64_t *timer, + uint64_t timeout) +{ + ztimer64_set_timeout_flag_at(clock, timer, + ztimer64_offset2absolute(clock, timeout)); +} + +/** + * @brief Try to lock the given mutex, but give up at @p target time + * + * @param[in] clock ztimer64 clock to operate on + * @param[in,out] mutex Mutex object to lock + * @param[in] target time after which to give up + * + * @retval 0 Success, caller has the mutex + * @retval -ECANCELED Failed to obtain mutex within @p timeout + */ +int ztimer64_mutex_lock_until(ztimer64_clock_t *clock, mutex_t *mutex, + uint64_t target); + +/** + * @brief Try to lock the given mutex, but give up after @p timeout + * + * @param[in] clock ztimer64 clock to operate on + * @param[in,out] mutex Mutex object to lock + * @param[in] timeout timeout after which to give up + * + * @retval 0 Success, caller has the mutex + * @retval -ECANCELED Failed to obtain mutex within @p timeout + */ +static inline int ztimer64_mutex_lock_timeout(ztimer64_clock_t *clock, + mutex_t *mutex, + uint64_t timeout) +{ + return ztimer64_mutex_lock_until(clock, mutex, + ztimer64_offset2absolute(clock, timeout)); +} + +/** + * @brief Try to lock the given rmutex, but give up at @p time + * + * @param[in] clock ztimer64 clock to operate on + * @param[in,out] rmutex rmutex object to lock + * @param[in] target time after which to give up + * + * @retval 0 Success, caller has the rmutex + * @retval -ECANCELED Failed to obtain rmutex before @p time + */ +int ztimer64_rmutex_lock_until(ztimer64_clock_t *clock, rmutex_t *rmutex, + uint64_t target); + +/** + * @brief Try to lock the given rmutex, but give up after @p timeout + * + * @param[in] clock ztimer64 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 + */ +static inline int ztimer64_rmutex_lock_timeout(ztimer64_clock_t *clock, + rmutex_t *rmutex, + uint64_t timeout) +{ + return ztimer64_rmutex_lock_until(clock, rmutex, + ztimer64_offset2absolute(clock, timeout)); +} + +/** + * @brief Update ztimer clock head list offset + * + * @internal + * + * @param[in] clock ztimer clock to work on + */ +void ztimer64_update_head_offset(ztimer64_clock_t *clock); + +/** + * @brief Initialize the board-specific default ztimer configuration + */ +void ztimer64_init(void); + +/** + * @brief Initialize @p clock to be run from @p base_clock + * @param[in,out] clock Clock to initialize + * @param[in] base_clock Base clock to use + */ +void ztimer64_clock_init(ztimer64_clock_t *clock, ztimer_clock_t *base_clock); + +/* default ztimer virtual devices */ +/** + * @brief Default ztimer microsecond clock + */ +extern ztimer64_clock_t *const ZTIMER64_USEC; + +/** + * @brief Default ztimer millisecond clock + */ +extern ztimer64_clock_t *const ZTIMER64_MSEC; + +/** + * @brief Default ztimer second clock + */ +extern ztimer64_clock_t *const ZTIMER64_SEC; + +/** + * @brief Measure ztimer64 overhead + * + * This function can be used to measure the overhead incurred by ztimer64. + * It will configure a callback to trigger after @p base ticks, then return the + * number of ticks that have passed, minus @p base. + * + * @param[in] clock ztimer64 clock to operate on + * @param[in] base base interval to use + * @return (time from ztimer64_set() until callback) - base + */ +int64_t ztimer64_overhead(ztimer64_clock_t *clock, uint64_t base); + +#ifdef __cplusplus +} +#endif + +#endif /* ZTIMER64_H */ +/** @} */ diff --git a/sys/ztimer/periph_timer.c b/sys/ztimer/periph_timer.c index 29847ec2fa..9cd565b611 100644 --- a/sys/ztimer/periph_timer.c +++ b/sys/ztimer/periph_timer.c @@ -24,6 +24,17 @@ #include "irq.h" #include "ztimer/periph_timer.h" +#ifndef ZTIMER_PERIPH_TIMER_OFFSET +/* This can be used for testing. E.g., + * + * CFLAGS="-DZTIMER_PERIPH_TIMER_OFFSET=4000000000LU" make ... + * + * The value will be added to every lower level timer read. + * @note this breaks if the low-level timer doesn't have 32bit width! + */ +#define ZTIMER_PERIPH_TIMER_OFFSET 0LU +#endif + static void _ztimer_periph_timer_set(ztimer_clock_t *clock, uint32_t val) { ztimer_periph_timer_t *ztimer_periph = (ztimer_periph_timer_t *)clock; @@ -51,7 +62,7 @@ static uint32_t _ztimer_periph_timer_now(ztimer_clock_t *clock) { ztimer_periph_timer_t *ztimer_periph = (ztimer_periph_timer_t *)clock; - return timer_read(ztimer_periph->dev); + return timer_read(ztimer_periph->dev) + ZTIMER_PERIPH_TIMER_OFFSET; } static void _ztimer_periph_timer_cancel(ztimer_clock_t *clock) diff --git a/sys/ztimer64/Kconfig b/sys/ztimer64/Kconfig new file mode 100644 index 0000000000..bb0238c6f7 --- /dev/null +++ b/sys/ztimer64/Kconfig @@ -0,0 +1,47 @@ +# Copyright (c) 2021 Inria +# +# 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. +# + +menu "ztimer64 - High level timer abstraction layer" + +# TODO: this extra indirection in the entry point for ztimer_usec is required +# to allow xtimer compatibility modules to depend on ztimer being there while +# still selecting ztimer_usec. +config ZTIMER64_USEC + bool "Microseconds 64bit Timer" + select ZTIMER_USEC + select MODULE_ZTIMER64 + select MODULE_ZTIMER64_USEC + +config MODULE_ZTIMER64_USEC + bool + select MODULE_ZTIMER_USEC + +config MODULE_ZTIMER64_MSEC + bool "Milliseconds 64bit Timer" + select MODULE_ZTIMER_MSEC + +config MODULE_ZTIMER64_SEC + bool "Milliseconds 64bit Timer" + select MODULE_ZTIMER_SEC + +config MODULE_ZTIMER64_INIT + bool + +config MODULE_ZTIMER64 + bool + depends on TEST_KCONFIG + +if MODULE_ZTIMER64 +config MODULE_AUTO_INIT_ZTIMER64 + bool "Auto initialize ztimer64" + depends on MODULE_AUTO_INIT + select MODULE_ZTIMER_INIT + select MODULE_ZTIMER64_INIT + default y +endif # MODULE_ZTIMER64 + +endmenu diff --git a/sys/ztimer64/Makefile b/sys/ztimer64/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/ztimer64/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/ztimer64/Makefile.dep b/sys/ztimer64/Makefile.dep new file mode 100644 index 0000000000..30474c8a15 --- /dev/null +++ b/sys/ztimer64/Makefile.dep @@ -0,0 +1,20 @@ +# +# ztimer64 dependencies +# +USEMODULE += ztimer +USEMODULE += ztimer64 + +ifneq (,$(filter ztimer64_usec,$(USEMODULE))) + DEFAULT_MODULE += auto_init_ztimer64 + USEMODULE += ztimer_usec +endif + +ifneq (,$(filter ztimer64_msec,$(USEMODULE))) + DEFAULT_MODULE += auto_init_ztimer64 + USEMODULE += ztimer_msec +endif + +ifneq (,$(filter ztimer64_sec,$(USEMODULE))) + DEFAULT_MODULE += auto_init_ztimer64 + USEMODULE += ztimer_sec +endif diff --git a/sys/ztimer64/overhead.c b/sys/ztimer64/overhead.c new file mode 100644 index 0000000000..0885565153 --- /dev/null +++ b/sys/ztimer64/overhead.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Kaspar Schleiser + * 2020 Freie Universität Berlin + * 2020 Inria + * + * 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_ztimer64 + * @{ + * + * @file + * @brief ztimer64 overhead measurement functions + * + * @author Kaspar Schleiser + * + * @} + */ + +#include "atomic_utils.h" +#include "ztimer64.h" + +typedef struct { + ztimer64_clock_t *clock; + volatile uint64_t *val; +} callback_arg_t; + +static void _callback(void *arg) +{ + callback_arg_t *callback_arg = (callback_arg_t *)arg; + + atomic_store_u64(callback_arg->val, ztimer64_now(callback_arg->clock)); +} + +int64_t ztimer64_overhead(ztimer64_clock_t *clock, uint64_t base) +{ + volatile uint64_t after = 0; + uint64_t pre; + + callback_arg_t arg = { .clock = clock, .val = &after }; + ztimer64_t t = { .callback = _callback, .arg = &arg }; + + pre = ztimer64_now(clock); + ztimer64_set(clock, &t, base); + while (!atomic_load_u64(&after)) {} + return after - pre - base; +} diff --git a/sys/ztimer64/util.c b/sys/ztimer64/util.c new file mode 100644 index 0000000000..3e36accde5 --- /dev/null +++ b/sys/ztimer64/util.c @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2021 Kaspar Schleiser + * 2021 Inria + * 2021 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. + */ + +/** + * @defgroup sys_ztimer64_util ztimer64 utility functions + * @ingroup sys_ztimer64 + * @{ + * + * @file + * @brief ztimer64 high-level utility function implementations + * + * @author Kaspar Schleiser + * + * @} + */ +#include +#include + +#include "irq.h" +#include "mutex.h" +#include "rmutex.h" +#include "thread.h" +#include "ztimer64.h" + +static void _callback_unlock_mutex(void *arg) +{ + mutex_t *mutex = (mutex_t *)arg; + + mutex_unlock(mutex); +} + +void ztimer64_sleep_until(ztimer64_clock_t *clock, uint64_t target) +{ + assert(!irq_is_in()); + mutex_t mutex = MUTEX_INIT_LOCKED; + + ztimer64_t timer = { + .callback = _callback_unlock_mutex, + .arg = (void *)&mutex, + }; + + ztimer64_set_at(clock, &timer, target); + mutex_lock(&mutex); +} + +void ztimer64_periodic_wakeup(ztimer64_clock_t *clock, uint64_t *last_wakeup, + uint64_t period) +{ + unsigned state = irq_disable(); + uint64_t now = ztimer64_now(clock); + uint64_t target = *last_wakeup + period; + + irq_restore(state); + + if (target > now) { + ztimer64_sleep_until(clock, target); + *last_wakeup = target; + } + else { + *last_wakeup = now; + } +} + +#ifdef MODULE_CORE_MSG +static void _callback_msg(void *arg) +{ + msg_t *msg = (msg_t *)arg; + + msg_send_int(msg, msg->sender_pid); +} + +static inline void _setup_msg(ztimer64_t *timer, msg_t *msg, + kernel_pid_t target_pid) +{ + timer->callback = _callback_msg; + timer->arg = (void *)msg; + + /* use sender_pid field to get target_pid into callback function */ + msg->sender_pid = target_pid; +} + +void ztimer64_set_msg_at(ztimer64_clock_t *clock, ztimer64_t *timer, + uint64_t target, + msg_t *msg, kernel_pid_t target_pid) +{ + _setup_msg(timer, msg, target_pid); + ztimer64_set_at(clock, timer, target); +} + +int ztimer64_msg_receive_until(ztimer64_clock_t *clock, msg_t *msg, + uint64_t target) +{ + if (msg_try_receive(msg) == 1) { + return 1; + } + + ztimer64_t t; + msg_t m = { .type = MSG_ZTIMER, .content.ptr = &m }; + + ztimer64_set_msg_at(clock, &t, target, &m, thread_getpid()); + + msg_receive(msg); + ztimer64_remove(clock, &t); + if (msg->type == MSG_ZTIMER64 && msg->content.ptr == &m) { + /* we hit the timeout */ + return -ETIME; + } + else { + return 1; + } +} + +#endif /* MODULE_CORE_MSG */ + +#ifdef MODULE_CORE_THREAD_FLAGS +static void _set_timeout_flag_callback(void *arg) +{ + thread_flags_set(arg, THREAD_FLAG_TIMEOUT); +} + +void ztimer64_set_timeout_flag_at(ztimer64_clock_t *clock, ztimer64_t *t, + uint64_t target) +{ + t->callback = _set_timeout_flag_callback; + t->arg = thread_get_active(); + thread_flags_clear(THREAD_FLAG_TIMEOUT); + ztimer64_set(clock, t, target); +} +#endif + +static void _callback_wakeup(void *arg) +{ + thread_wakeup((kernel_pid_t)((intptr_t)arg)); +} + +void ztimer64_set_wakeup_at(ztimer64_clock_t *clock, ztimer64_t *timer, + uint64_t target, + kernel_pid_t pid) +{ + ztimer64_remove(clock, timer); + + timer->callback = _callback_wakeup; + timer->arg = (void *)((intptr_t)pid); + + ztimer64_set_at(clock, timer, target); +} + +static void timeout_cb(void *arg) +{ + mutex_cancel(arg); +} + +int ztimer64_mutex_lock_until(ztimer64_clock_t *clock, mutex_t *mutex, + uint64_t target) +{ + if (mutex_trylock(mutex)) { + return 0; + } + + mutex_cancel_t mc = mutex_cancel_init(mutex); + ztimer64_t t = { .callback = timeout_cb, .arg = &mc }; + + ztimer64_set_at(clock, &t, target); + if (mutex_lock_cancelable(&mc)) { + return -ECANCELED; + } + + ztimer64_remove(clock, &t); + return 0; +} + +int ztimer64_rmutex_lock_until(ztimer64_clock_t *clock, rmutex_t *rmutex, + uint64_t target) +{ + if (rmutex_trylock(rmutex)) { + return 0; + } + if (ztimer64_mutex_lock_until(clock, &rmutex->mutex, target) == 0) { + atomic_store_explicit(&rmutex->owner, + thread_getpid(), memory_order_relaxed); + rmutex->refcount++; + return 0; + } + return -ECANCELED; +} diff --git a/sys/ztimer64/ztimer64.c b/sys/ztimer64/ztimer64.c new file mode 100644 index 0000000000..71baea29c8 --- /dev/null +++ b/sys/ztimer64/ztimer64.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2021 Kaspar Schleiser + * 2021 Freie Universität Berlin + * 2021 Inria + * + * 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_ztimer64 + * @{ + * + * @file + * @brief ztimer64 core functionality + * + * This file contains ztimer64's main API implementation and functionality + * + * @author Kaspar Schleiser + * + * @} + */ +#include +#include +#include + +#include "ztimer64.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/* Checkpointing interval, don't change. See `ztimer64_now()` for an explanation + * of the checkpointing. + */ +#define ZTIMER64_CHECKPOINT_INTERVAL (1LU << 31) + +static int _add_entry_to_list(ztimer64_clock_t *clock, ztimer64_base_t *entry); +static int _del_entry_from_list(ztimer64_clock_t *clock, + ztimer64_base_t *entry); +static void _ztimer64_update(ztimer64_clock_t *clock); + +/* debug aid */ +void ztimer64_clock_print(const ztimer64_clock_t *clock); + +static unsigned _is_set(const ztimer64_t *t) +{ + /* no timer can ever trigger at `target==0`, so we use that + * as marker */ + return !!(t->base.target); +} + +unsigned ztimer64_is_set(const ztimer64_t *timer) +{ + return _is_set(timer); +} + +void ztimer64_remove(ztimer64_clock_t *clock, ztimer64_t *timer) +{ + unsigned state = irq_disable(); + + if (_is_set(timer)) { + if (_del_entry_from_list(clock, &timer->base) == 0) { + _ztimer64_update(clock); + } + } + + irq_restore(state); +} + +void ztimer64_set_at(ztimer64_clock_t *clock, ztimer64_t *timer, uint64_t val) +{ + unsigned state = irq_disable(); + + if (_is_set(timer)) { + _del_entry_from_list(clock, &timer->base); + } + + /* optionally subtract a configurable adjustment value */ + /* there is no API to set this ATM */ + if (val > clock->adjust_set) { + val -= clock->adjust_set; + } + else { + val = 0; + } + + timer->base.target = val; + + /* NOTE: if val is 0 at this point, this will be fixed to "now()" in _ztimer64_update() */ + + if (_add_entry_to_list(clock, &timer->base)) { + _ztimer64_update(clock); + } + + irq_restore(state); +} + +static int _add_entry_to_list(ztimer64_clock_t *clock, ztimer64_base_t *entry) +{ + ztimer64_base_t *pos = clock->first; + + if (pos) { + if (pos->target > entry->target) { + /* special case: new entry's target is earlier than old list head */ + entry->next = pos; + clock->first = entry; + return 1; + } + else { + /* Jump past all entries which are set to an earlier target than the new entry */ + while (pos->next && pos->next->target <= entry->target) { + pos = pos->next; + } + entry->next = pos->next; + pos->next = entry; + return 0; + } + } + else { + /* adding first entry */ + entry->next = 0; + clock->first = entry; + return 1; + } +} + +static int _del_entry_from_list(ztimer64_clock_t *clock, ztimer64_base_t *entry) +{ + DEBUG("_del_entry_from_list()\n"); + assert(_is_set((ztimer64_t *)entry)); + if (clock->first == entry) { + /* special case: removing first entry */ + clock->first = entry->next; + entry->next = 0; + return 1; + } + else { + ztimer64_base_t *pos = clock->first; + + while (pos->next) { + if (pos->next == entry) { + pos->next = entry->next; + break; + } + pos = pos->next; + } + + entry->next = 0; + + return 0; + } +} + +uint64_t ztimer64_now(ztimer64_clock_t *clock) +{ + uint64_t now; + unsigned state = irq_disable(); + uint32_t base_now = ztimer_now(clock->base_clock); + + /* ztimer64 checkpointing works by storing + * the upper 33 bits in clock->checkpoint. + * The final time is the lower 32bit time ORed + * with that checkpoint. + * The highest bit of the lower 32bit is used + * as control bit. If the checkpoint and 32bit + * time both have that bit set (or not), no correction is necessary. + * If checkpoint and 32bit time disagree on that bit, + * the checkpoint lags behind, and needs to be updated. + * This works as long as the checkpoint is at most (2**32-1) behind, + * which is ensured by setting the base clock timer at most + * ZTIMER64_CHECKPOINT_INTERVAL into the future. + */ + if ((clock->checkpoint & ZTIMER64_CHECKPOINT_INTERVAL) + ^ (base_now & ZTIMER64_CHECKPOINT_INTERVAL)) { + clock->checkpoint += ZTIMER64_CHECKPOINT_INTERVAL; + } + + now = clock->checkpoint | base_now; + + irq_restore(state); + return now; +} + +static void _ztimer64_update(ztimer64_clock_t *clock) +{ + uint64_t now = ztimer64_now(clock); + uint64_t next_checkpoint = clock->checkpoint + ZTIMER64_CHECKPOINT_INTERVAL; + uint64_t target; + + if (next_checkpoint < now) { + next_checkpoint = now; + } + + if (!clock->first) { + target = next_checkpoint; + } + else { + uint64_t next = clock->first->target; + if (next > now) { + if (next < next_checkpoint) { + /* timer within range */ + target = next; + } + else { + /* timer out of range */ + target = next_checkpoint; + } + } + else { + /* timer has passed already, trigger ASAP */ + target = now; + + /* if we arrive here due to a timer with "target==0" being set, + * fix this here to consider it "set". + */ + if (next == 0) { + clock->first->target = now; + } + } + } + + ztimer_set(clock->base_clock, &clock->base_timer, target - now); +} + +void ztimer64_handler(void *arg) +{ + ztimer64_clock_t *clock = arg; + + while (clock->first) { + uint64_t now = ztimer64_now(clock); + if (clock->first->target <= now) { + ztimer64_t *timer = (ztimer64_t *)clock->first; + /* remove timer from list */ + clock->first = clock->first->next; + /* clear timer struct so it can be re-set */ + timer->base.next = NULL; + timer->base.target = 0; + + /* shoot callback */ + timer->callback(timer->arg); + } + else { + /* timer target is in the future */ + break; + } + } + + _ztimer64_update(clock); +} + +void ztimer64_clock_init(ztimer64_clock_t *clock, ztimer_clock_t *base_clock) +{ + *clock = + (ztimer64_clock_t){ .base_clock = base_clock, + .base_timer = + { .callback = ztimer64_handler, .arg = clock } }; + + /* initialize checkpointing */ + _ztimer64_update(clock); +} + +#if MODULE_ZTIMER64_USEC +static ztimer64_clock_t _ztimer64_usec; +ztimer64_clock_t *const ZTIMER64_USEC = &_ztimer64_usec; +#endif +#if MODULE_ZTIMER64_MSEC +static ztimer64_clock_t _ztimer64_msec; +ztimer64_clock_t *const ZTIMER64_MSEC = &_ztimer64_msec; +#endif +#if MODULE_ZTIMER64_SEC +static ztimer64_clock_t _ztimer64_sec; +ztimer64_clock_t *const ZTIMER64_SEC = &_ztimer64_sec; +#endif + +void ztimer64_init(void) +{ +#if MODULE_ZTIMER64_USEC + ztimer64_clock_init(ZTIMER64_USEC, ZTIMER_USEC); +#endif +#if MODULE_ZTIMER64_MSEC + ztimer64_clock_init(ZTIMER64_MSEC, ZTIMER_MSEC); +#endif +#if MODULE_ZTIMER64_SEC + ztimer64_clock_init(ZTIMER64_SEC, ZTIMER_SEC); +#endif +} + +void ztimer64_clock_print(const ztimer64_clock_t *clock) +{ + const ztimer64_base_t *entry = clock->first; + + while (entry) { + printf("0x%08x:%" PRIu64 "\n", (unsigned)entry, + entry->target); + + entry = entry->next; + } + puts(""); +} diff --git a/tests/unittests/tests-ztimer64/Makefile b/tests/unittests/tests-ztimer64/Makefile new file mode 100644 index 0000000000..6c572ed15c --- /dev/null +++ b/tests/unittests/tests-ztimer64/Makefile @@ -0,0 +1,6 @@ +# avoid clang warning in tests-ztimer/tests-ztimer-extend.c:141 +ifeq (llvm,$(TOOLCHAIN)) + CFLAGS += -Wno-gnu-folding-constant +endif + +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-ztimer64/Makefile.include b/tests/unittests/tests-ztimer64/Makefile.include new file mode 100644 index 0000000000..104c4d02db --- /dev/null +++ b/tests/unittests/tests-ztimer64/Makefile.include @@ -0,0 +1,3 @@ +USEMODULE += ztimer_core +USEMODULE += ztimer_mock +USEMODULE += ztimer64 diff --git a/tests/unittests/tests-ztimer64/tests-ztimer64-core.c b/tests/unittests/tests-ztimer64/tests-ztimer64-core.c new file mode 100644 index 0000000000..f5fd837085 --- /dev/null +++ b/tests/unittests/tests-ztimer64/tests-ztimer64-core.c @@ -0,0 +1,216 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Unittests for ztimer64 + * + */ + +#include +#include + +#include "ztimer.h" +#include "ztimer64.h" +#include "ztimer/mock.h" + +#include "embUnit/embUnit.h" + +#include "tests-ztimer64.h" + +#define ZTIMER64_CHECKPOINT_INTERVAL (1LLU << 31) + +/** + * @brief Simple callback for counting alarms + */ +static void cb_incr(void *arg) +{ + uint32_t *ptr = arg; + + *ptr += 1; +} + +static ztimer_mock_t zmock; +static ztimer_clock_t *z = &zmock.super; +static ztimer64_clock_t z64mock; +static ztimer64_clock_t *z64 = &z64mock; + +static void setup(void) +{ + memset(&zmock, '\0', sizeof(ztimer_mock_t)); + memset(&z64mock, '\0', sizeof(ztimer64_clock_t)); + /* ztimer base clock is already extended to 32bit */ + ztimer_mock_init(&zmock, 32); + ztimer64_clock_init(z64, z); +} + +/** + * @brief + */ +static void test_ztimer64_now(void) +{ + uint64_t now64 = ztimer64_now(z64); + + TEST_ASSERT_EQUAL_INT(0, now64); + + ztimer_mock_advance(&zmock, 123); + now64 = ztimer64_now(z64); + TEST_ASSERT_EQUAL_INT(123, now64); + + ztimer_mock_jump(&zmock, 0x10000000ul); + now64 = ztimer64_now(z64); + TEST_ASSERT_EQUAL_INT(0x10000000ul, now64); + + ztimer_mock_advance(&zmock, 0x98765432ul); + now64 = ztimer64_now(z64); + TEST_ASSERT_EQUAL_INT(0xa8765432ul, now64); + /* 32bit overflow */ + ztimer_mock_advance(&zmock, 0x41234567ul); + ztimer_mock_advance(&zmock, 0x40000000ul); + now64 = ztimer64_now(z64); + TEST_ASSERT_EQUAL_INT(0x129999999ul, now64); +} + +/** + * @brief Testing 32 bit wide mock clock set functionality + */ +static void test_ztimer64_set(void) +{ + uint64_t now64 = ztimer64_now(z64); + + TEST_ASSERT_EQUAL_INT(0, now64); + + uint32_t count = 0; + ztimer64_t alarm = { .callback = cb_incr, .arg = &count, }; + + ztimer64_set(z64, &alarm, 1000); + ztimer_mock_advance(&zmock, 1); /* now = 1*/ + TEST_ASSERT_EQUAL_INT(0, count); + ztimer_mock_advance(&zmock, 100); /* now = 101 */ + TEST_ASSERT_EQUAL_INT(0, count); + ztimer_mock_advance(&zmock, 898); /* now = 999 */ + now64 = ztimer64_now(z64); + TEST_ASSERT_EQUAL_INT(999, now64); + TEST_ASSERT_EQUAL_INT(0, count); + ztimer_mock_advance(&zmock, 1); /* now = 1000*/ + TEST_ASSERT_EQUAL_INT(1, count); + ztimer_mock_advance(&zmock, 1); /* now = 1001*/ + TEST_ASSERT_EQUAL_INT(1, count); + ztimer_mock_advance(&zmock, 1000); /* now = 2001*/ + TEST_ASSERT_EQUAL_INT(1, count); + ztimer64_set(z64, &alarm, 3); + ztimer_mock_advance(&zmock, 999); /* now = 3000*/ + TEST_ASSERT_EQUAL_INT(2, count); + ztimer64_set(z64, &alarm, 4000001000ul); + ztimer_mock_advance(&zmock, 1000); /* now = 4000*/ + TEST_ASSERT_EQUAL_INT(2, count); + ztimer_mock_advance(&zmock, 4000000000ul); /* now = 4000004000*/ + now64 = ztimer64_now(z64); + TEST_ASSERT_EQUAL_INT(4000004000ul, now64); + TEST_ASSERT_EQUAL_INT(3, count); + /* 32bit overflow */ + ztimer64_set(z64, &alarm, 8000000000ul); + ztimer_mock_advance(&zmock, 4000000000ul); + ztimer_mock_advance(&zmock, 3999999999ul); /* now = 12000003999*/ + TEST_ASSERT_EQUAL_INT(3, count); + ztimer_mock_advance(&zmock, 1ul); /* now = 12000004000*/ + TEST_ASSERT_EQUAL_INT(4, count); + ztimer64_set(z64, &alarm, 15); + ztimer_mock_advance(&zmock, 14); + ztimer64_remove(z64, &alarm); + ztimer_mock_advance(&zmock, 1000); + TEST_ASSERT_EQUAL_INT(4, count); +} + +/** + * @brief Testing 32 bit wide mock clock set functionality + */ +static void test_ztimer64_set_0(void) +{ + uint64_t now64 = ztimer64_now(z64); + TEST_ASSERT_EQUAL_INT(0, now64); + + uint32_t count = 0; + ztimer64_t alarm = { .callback = cb_incr, .arg = &count, }; + + /* should trigger asap, note that for a real timer this will be + to minimal value that guarantees the ISR not to be missed, + e.g: CONFIG_ZTIMER_USEC_MIN or RTT_MIN_OFFSET */ + ztimer64_set(z64, &alarm, 0); + ztimer_mock_advance(&zmock, 1); /* now = 1*/ + TEST_ASSERT_EQUAL_INT(1, count); +} + +/** + * @brief Testing timers set in the past + */ +static void test_ztimer64_set_at(void) +{ + uint64_t now64 = ztimer64_now(z64); + + TEST_ASSERT_EQUAL_INT(0, now64); + + uint32_t count = 0; + ztimer64_t alarm = { .callback = cb_incr, .arg = &count, }; + + /* test setting an offset that is now in the past, should trigger asap */ + ztimer_mock_advance(&zmock, 1010); /* now = 1010 */ + ztimer64_set_at(z64, &alarm, 1000); + /* should trigger on next tick */ + ztimer_mock_advance(&zmock, 1); /* now = 1011 */ + TEST_ASSERT_EQUAL_INT(1, count); +} + +static void test_ztimer64_checkpoint(void) +{ + uint64_t now64 = ztimer64_now(z64); + + TEST_ASSERT_EQUAL_INT(0, now64); + + /* base_now is 0x7fff_ffff */ + ztimer_mock_jump(&zmock, ZTIMER64_CHECKPOINT_INTERVAL - 1); + now64 = ztimer64_now(z64); + TEST_ASSERT_EQUAL_INT(ZTIMER64_CHECKPOINT_INTERVAL - 1, now64); + /* base_now is 0x8000_0000 */ + ztimer_mock_jump(&zmock, ZTIMER64_CHECKPOINT_INTERVAL); + now64 = ztimer64_now(z64); + TEST_ASSERT_EQUAL_INT(ZTIMER64_CHECKPOINT_INTERVAL, now64); + /* base_now is 0x0000_0000 */ + ztimer_mock_jump(&zmock, 0); + now64 = ztimer64_now(z64); + /* overflow is caught in checkpoint */ + TEST_ASSERT_EQUAL_INT(2 * ZTIMER64_CHECKPOINT_INTERVAL, now64); + /* base_now is 0xffff_ffff, max jump that can be caught */ + ztimer_mock_jump(&zmock, UINT32_MAX); + now64 = ztimer64_now(z64); + /* overflow is caught in checkpoint */ + TEST_ASSERT_EQUAL_INT(2 * ZTIMER64_CHECKPOINT_INTERVAL + UINT32_MAX, now64); + /* overflow is missed, 2**32 ticks elapsed + - microseconds: 4293 seconds + - nanoseconds: 4.29 seconds (e.g.: ptp) -> should not use ztimer as base. + */ + ztimer_mock_jump(&zmock, UINT32_MAX); + TEST_ASSERT_EQUAL_INT(2 * ZTIMER64_CHECKPOINT_INTERVAL + UINT32_MAX, now64); +} + +Test *tests_ztimer64_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_ztimer64_now), + new_TestFixture(test_ztimer64_set), + new_TestFixture(test_ztimer64_set_0), + new_TestFixture(test_ztimer64_set_at), + new_TestFixture(test_ztimer64_checkpoint), + }; + + EMB_UNIT_TESTCALLER(ztimer64_tests, setup, NULL, fixtures); + + return (Test *)&ztimer64_tests; +} + +/** @} */ diff --git a/tests/unittests/tests-ztimer64/tests-ztimer64.c b/tests/unittests/tests-ztimer64/tests-ztimer64.c new file mode 100644 index 0000000000..6fd2339cdd --- /dev/null +++ b/tests/unittests/tests-ztimer64/tests-ztimer64.c @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Unittest entry point for the ztimer64 test group + * + */ + +#include "embUnit/embUnit.h" + +#include "tests-ztimer64.h" + +Test *tests_ztimer64_tests(void); + +void tests_ztimer64(void) +{ + TESTS_RUN(tests_ztimer64_tests()); +} +/** @} */ diff --git a/tests/unittests/tests-ztimer64/tests-ztimer64.h b/tests/unittests/tests-ztimer64/tests-ztimer64.h new file mode 100644 index 0000000000..97a7a86cdd --- /dev/null +++ b/tests/unittests/tests-ztimer64/tests-ztimer64.h @@ -0,0 +1,34 @@ +/* + * 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 ztimer64 + * + */ +#ifndef TESTS_ZTIMER64_H +#define TESTS_ZTIMER64_H + +#include "embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + */ +void tests_ztimer(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_ZTIMER64_H */ +/** @} */ diff --git a/tests/ztimer64_msg/Makefile b/tests/ztimer64_msg/Makefile new file mode 100644 index 0000000000..1a28151454 --- /dev/null +++ b/tests/ztimer64_msg/Makefile @@ -0,0 +1,11 @@ +include ../Makefile.tests_common + +USEMODULE += ztimer64_usec + +# uncomment this to test using ztimer64 msec on rtt +#USEMODULE += ztimer64_msec ztimer_periph_rtt + +# uncomment this to test using ztimer64 sec on rtc +#USEMODULE += ztimer64_sec ztimer_periph_rtc + +include $(RIOTBASE)/Makefile.include diff --git a/tests/ztimer64_msg/Makefile.ci b/tests/ztimer64_msg/Makefile.ci new file mode 100644 index 0000000000..d5368f025e --- /dev/null +++ b/tests/ztimer64_msg/Makefile.ci @@ -0,0 +1,12 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + nucleo-f031k6 \ + nucleo-l011k4 \ + samd10-xmini \ + stm32f030f4-demo \ + # diff --git a/tests/ztimer64_msg/README.md b/tests/ztimer64_msg/README.md new file mode 100644 index 0000000000..11f77be44b --- /dev/null +++ b/tests/ztimer64_msg/README.md @@ -0,0 +1,9 @@ +# Overview + +This test application is a direct translation of ztimer_msg to the ztimer64API. +It is meant mostly as a means to do size comparisons, thus tries to be as close +as possible to the original. + +One notable change is the option to choose a different ztimer clock. +By default, the test will use ZTIMER64_USEC, unless ZTIMER64_MSEC is compiled in, +which will be used in that case. diff --git a/tests/ztimer64_msg/app.config.test b/tests/ztimer64_msg/app.config.test new file mode 100644 index 0000000000..aba183dfcb --- /dev/null +++ b/tests/ztimer64_msg/app.config.test @@ -0,0 +1,3 @@ +# this file enables modules defined in Kconfig. Do not use this file for +# application configuration. This is only needed during migration. +CONFIG_ZTIMER64_USEC=y diff --git a/tests/ztimer64_msg/main.c b/tests/ztimer64_msg/main.c new file mode 100644 index 0000000000..a27dd9cc5d --- /dev/null +++ b/tests/ztimer64_msg/main.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2015-19 Kaspar Schleiser + * 2013 INRIA + * 2017 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. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief ztimer64_msg test application + * + * @author Kaspar Schleiser + * @author Oliver Hahm + * @author Christian Mehlis + * @author Sebastian Meiling + * @} + */ + +#include + +#include "ztimer64.h" +#include "thread.h" +#include "msg.h" +#include "timex.h" + +#include "test_utils/expect.h" + +#ifdef MODULE_ZTIMER64_SEC +#define ZTIMER64 ZTIMER64_SEC +#define TICKS_PER_SEC 1 +#elif MODULE_ZTIMER64_MSEC +#define ZTIMER64 ZTIMER64_MSEC +#define TICKS_PER_SEC MS_PER_SEC +#else +#define ZTIMER64 ZTIMER64_USEC +#define TICKS_PER_SEC US_PER_SEC +#endif + +char timer_stack[THREAD_STACKSIZE_DEFAULT]; +char timer_stack_local[THREAD_STACKSIZE_DEFAULT]; + +struct timer_msg { + ztimer64_t timer; + uint64_t interval; + char *text; + msg_t msg; +}; + +struct timer_msg msg_a = { .interval = (2 * TICKS_PER_SEC), + .text = "Hello World" }; +struct timer_msg msg_b = { .interval = (5 * TICKS_PER_SEC), + .text = "This is a Test" }; + +void *timer_thread(void *arg) +{ + (void)arg; + + printf("This is thread %" PRIkernel_pid "\n", thread_getpid()); + + /* The queue is required to avoid loss of a 2nd message, when the 1st is + * still processed. The timing ensures that at most 1 message is queued. + */ + msg_t msgq[1]; + + msg_init_queue(msgq, 1); + + while (1) { + msg_t m; + msg_receive(&m); + struct timer_msg *tmsg = m.content.ptr; + uint64_t now = ztimer64_now(ZTIMER64); + /* casts are needed to solve for sometimes TICKS_PER_SEC being UL + * result of / and % of uint32_t will always fit into uint32_t + */ + printf( + "now=%" PRIu32 ":%" PRIu32 " -> every %" PRIu32 ".%" PRIu32 "s: %s\n", + (uint32_t)(now / TICKS_PER_SEC), + (uint32_t)(now % TICKS_PER_SEC), + (uint32_t)(tmsg->interval / TICKS_PER_SEC), + (uint32_t)(tmsg->interval % TICKS_PER_SEC), + tmsg->text); + + tmsg->msg.type = 12345; + tmsg->msg.content.ptr = tmsg; + ztimer64_set_msg(ZTIMER64, &tmsg->timer, tmsg->interval, &tmsg->msg, + thread_getpid()); + } +} + +void *timer_thread_local(void *arg) +{ + (void)arg; + + printf("This is thread %" PRIkernel_pid "\n", thread_getpid()); + + while (1) { + msg_t m; + msg_receive(&m); + + uint64_t now = ztimer64_now(ZTIMER64); + uint32_t sec = now / TICKS_PER_SEC; + uint32_t min = sec / 60; + uint32_t hr = sec / 3600; + printf("sec=%" PRIu32 " min=%" PRIu32 " hour=%" PRIu32 "\n", sec, min, + hr); + } +} + +int main(void) +{ + msg_t m; + kernel_pid_t pid = thread_create( + timer_stack, + sizeof(timer_stack), + THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, + timer_thread, + NULL, + "timer"); + + expect(pid_is_valid(pid)); + + puts("sending 1st msg"); + m.content.ptr = &msg_a; + msg_try_send(&m, pid); + + puts("sending 2nd msg"); + m.content.ptr = &msg_b; + msg_try_send(&m, pid); + + kernel_pid_t pid2 = thread_create( + timer_stack_local, + sizeof(timer_stack_local), + THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, + timer_thread_local, + NULL, + "timer local"); + + expect(pid_is_valid(pid2)); + + while (1) { + ztimer64_sleep(ZTIMER64, 1LLU * TICKS_PER_SEC); + msg_try_send(&m, pid2); + } +} diff --git a/tests/ztimer64_msg/tests/01-run.py b/tests/ztimer64_msg/tests/01-run.py new file mode 100755 index 0000000000..136b6443a4 --- /dev/null +++ b/tests/ztimer64_msg/tests/01-run.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2017 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. + +import sys +from testrunner import run + + +def testfunc(child): + # 1st check for periodic 2s Hello World message, i.e., 2 output + 1 msg + for _ in range(7): + child.expect(r"sec=\d+ min=\d+ hour=\d+") + child.expect(r"sec=\d+ min=\d+ hour=\d+") + child.expect(r"now=\d+:\d+ -> every 2.0s: Hello World") + # 2nd check for periodic 5s test message, i.e., 5 output + 1 msg + for _ in range(3): + child.expect(r"sec=\d+ min=\d+ hour=\d+") + child.expect(r"sec=\d+ min=\d+ hour=\d+") + child.expect(r"sec=\d+ min=\d+ hour=\d+") + child.expect(r"sec=\d+ min=\d+ hour=\d+") + child.expect(r"sec=\d+ min=\d+ hour=\d+") + child.expect(r"now=\d+:\d+ -> every 5.0s: This is a Test") + + +if __name__ == "__main__": + sys.exit(run(testfunc))