From 8036e30d9fab38a61f0da809f0b7e0733887345b Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Thu, 5 Jun 2025 18:09:45 +0200 Subject: [PATCH 1/3] kernel: expose kernel timeout record The kernel timeout record, currenly a private feature, is very useful for subsystems which require a very low overhead timeout with callback. Currently subsystems are forced to use the k_timer for any and all timeouts, which is not efficient, or do some tricks to include and use the kernel timeout record anyway, like #include <../kernel/include/timeout_q.h> like its done for RTIO. This commit exposes the kernel timeout in a zero overhead and backwards compatible manner, with added (and needed) documentation to help users use the kernel timeout record appropriately. It will hopefully encourage subsystems to use the kernel timeout record in place of k_timer where appropriate. In the future, we may even deprecate struct _timeout in favor of k_timeout_record. Signed-off-by: Bjarki Arge Andreasen --- include/zephyr/kernel.h | 86 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/include/zephyr/kernel.h b/include/zephyr/kernel.h index a407678bcaa4..3ab11a4f58c1 100644 --- a/include/zephyr/kernel.h +++ b/include/zephyr/kernel.h @@ -6513,6 +6513,92 @@ void k_sys_runtime_stats_enable(void); */ void k_sys_runtime_stats_disable(void); +struct k_timeout_record; + +/** + * @brief Kernel timeout record elapsed callback function template. + * + * @param record Address of kernel timeout record. + */ +typedef void (*k_timeout_record_fn_t)(struct k_timeout_record *record); + +/** + * @cond INTERNAL_HIDDEN + */ + +struct k_timeout_record { + struct _timeout to; +}; + +k_ticks_t z_add_timeout(struct _timeout *to, _timeout_func_t fn, k_timeout_t timeout); +int z_abort_timeout(struct _timeout *to); + +/** + * INTERNAL_HIDDEN @endcond + */ + +#ifdef CONFIG_SYS_CLOCK_EXISTS + +/** Initialize a kernel timeout record */ +static inline void k_timeout_record_init(struct k_timeout_record *record) +{ + sys_dnode_init(&record->to.node); +} + +/** + * @brief Add a kernel timeout record. + * + * @details The kernel timeout record is the most primitive timeout + * in the kernel. @p fn is called only once once the timeout elapses, + * at which point the kernel timeout record can be re-added. + * + * @warning @p fn is likely called from ISR context. + * + * @warning Timeout record can only be re-added once it has elapsed + * or has been aborted. + * + * @note The timeout is called at the next kernel tick if timeout + * is in the past or now. + * + * @see k_timeout_record_abort() + * + * @param record The timeout record structure. + * @param fn The function called when timeout elapses. + * @param timeout The time at which timeout elapses. + * + * @retval Absolute ticks at which timeout elapses. + */ +static inline k_ticks_t k_timeout_record_add(struct k_timeout_record *record, + k_timeout_record_fn_t fn, + k_timeout_t timeout) +{ + return z_add_timeout(&record->to, (_timeout_func_t)fn, timeout); +} + +/** + * @brief Abort a kernel timeout record. + * + * @details Abort a kernel timeout record and check if the + * kernel timeout record's timeout elapsed. + * + * @note This function must be called before re-adding the + * kernel timeout record if caller is unsure whether the + * timeout has elapsed. + * + * @see k_timeout_record_add() + * + * @param record The timeout record structure. + * + * @retval true if timeout record has not elapsed. + * @retval false if timeout record has elapsed or was never added. + */ +static inline bool k_timeout_record_abort(struct k_timeout_record *record) +{ + return z_abort_timeout(&record->to) == 0; +} + +#endif /* CONFIG_SYS_CLOCK_EXISTS */ + #ifdef __cplusplus } #endif From 91e6d55ec272b3978e8fdccb0a5857463fd33a47 Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Thu, 5 Jun 2025 18:50:57 +0200 Subject: [PATCH 2/3] tests: kernel: common: add timeout_record Add test of timeout record to the common kernel tests included if CONFIG_SYS_CLOCK_EXISTS. Signed-off-by: Bjarki Arge Andreasen --- tests/kernel/common/CMakeLists.txt | 6 +++ tests/kernel/common/src/timeout_record.c | 49 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/kernel/common/src/timeout_record.c diff --git a/tests/kernel/common/CMakeLists.txt b/tests/kernel/common/CMakeLists.txt index 83d566419166..f4cca2fe5296 100644 --- a/tests/kernel/common/CMakeLists.txt +++ b/tests/kernel/common/CMakeLists.txt @@ -33,3 +33,9 @@ target_sources_ifdef( app PRIVATE src/irq_offload.c ) + +target_sources_ifdef( + CONFIG_SYS_CLOCK_EXISTS + app PRIVATE + src/timeout_record.c +) diff --git a/tests/kernel/common/src/timeout_record.c b/tests/kernel/common/src/timeout_record.c new file mode 100644 index 000000000000..782b3ee01c27 --- /dev/null +++ b/tests/kernel/common/src/timeout_record.c @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static K_SEM_DEFINE(test_sem, 0, 1); +static struct k_timeout_record *test_record; + +extern void *common_setup(void); + +static void test_before(void *f) +{ + k_sem_reset(&test_sem); + test_record = NULL; +} + +ZTEST_SUITE(timeout_record, NULL, common_setup, test_before, NULL, NULL); + +static void test_timeout_handler(struct k_timeout_record *record) +{ + test_record = record; + k_sem_give(&test_sem); +} + +ZTEST(timeout_record, test_timeout_add_elapse_abort) +{ + struct k_timeout_record record; + + k_timeout_record_init(&record); + (void)k_timeout_record_add(&record, test_timeout_handler, K_NO_WAIT); + zassert_ok(k_sem_take(&test_sem, K_MSEC(100))); + zassert_equal(&record, test_record); + zassert_false(k_timeout_record_abort(&record)); +} + +ZTEST(timeout_record, test_timeout_add_abort) +{ + struct k_timeout_record record; + + k_timeout_record_init(&record); + (void)k_timeout_record_add(&record, test_timeout_handler, K_MSEC(1000)); + zassert_equal(k_sem_take(&test_sem, K_MSEC(500)), -EAGAIN); + zassert_true(k_timeout_record_abort(&record)); + zassert_equal(k_sem_take(&test_sem, K_MSEC(1000)), -EAGAIN); +} From 514167fe8819510e17cda1ac9624c0a7d9ff75ce Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Thu, 5 Jun 2025 18:54:26 +0200 Subject: [PATCH 3/3] rtio: update to use k_timeout_record for OP_DELAY Use newly exposed k_timeout_record instead of "hacking" a bit to use the internal _timeout. Signed-off-by: Bjarki Arge Andreasen --- include/zephyr/rtio/rtio.h | 2 +- subsys/rtio/rtio_sched.c | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/include/zephyr/rtio/rtio.h b/include/zephyr/rtio/rtio.h index fade8e5eec19..f145441d9719 100644 --- a/include/zephyr/rtio/rtio.h +++ b/include/zephyr/rtio/rtio.h @@ -348,7 +348,7 @@ struct rtio_sqe { /** OP_DELAY */ struct { k_timeout_t timeout; /**< Delay timeout. */ - struct _timeout to; /**< Timeout struct. Used internally. */ + struct k_timeout_record record; /**< Timeout record. Used internally. */ } delay; /** OP_I2C_CONFIGURE */ diff --git a/subsys/rtio/rtio_sched.c b/subsys/rtio/rtio_sched.c index e117b31d4589..cb6c4e4a49f8 100644 --- a/subsys/rtio/rtio_sched.c +++ b/subsys/rtio/rtio_sched.c @@ -7,19 +7,11 @@ #include #include -/** Required to access Timeout Queue APIs, which are used instead of the - * Timer APIs because of concerns on size on rtio_sqe (k_timer is more - * than double the size of _timeout). Users will have to instantiate a - * pool of SQE objects, thus its size directly impacts memory footprint - * of RTIO applications. - */ -#include <../kernel/include/timeout_q.h> - #include "rtio_sched.h" -static void rtio_sched_alarm_expired(struct _timeout *t) +static void rtio_sched_alarm_expired(struct k_timeout_record *record) { - struct rtio_sqe *sqe = CONTAINER_OF(t, struct rtio_sqe, delay.to); + struct rtio_sqe *sqe = CONTAINER_OF(record, struct rtio_sqe, delay.record); struct rtio_iodev_sqe *iodev_sqe = CONTAINER_OF(sqe, struct rtio_iodev_sqe, sqe); rtio_iodev_sqe_ok(iodev_sqe, 0); @@ -29,6 +21,6 @@ void rtio_sched_alarm(struct rtio_iodev_sqe *iodev_sqe, k_timeout_t timeout) { struct rtio_sqe *sqe = &iodev_sqe->sqe; - z_init_timeout(&sqe->delay.to); - z_add_timeout(&sqe->delay.to, rtio_sched_alarm_expired, timeout); + k_timeout_record_init(&sqe->delay.record); + k_timeout_record_add(&sqe->delay.record, rtio_sched_alarm_expired, timeout); }