From e18a048c8499d859163a25c89a7cc98b15431833 Mon Sep 17 00:00:00 2001 From: Peter Mitsis Date: Wed, 14 Jan 2026 15:23:00 -0800 Subject: [PATCH] kernel: Add K_TIMEOUT_SUM() macro The K_TIMEOUT_SUM() macro is intended as a means to add two k_timeout_t values together. This may be useful for a developer applying an exponential backoff algorithm. Signed-off-by: Peter Mitsis --- include/zephyr/kernel.h | 17 ++- include/zephyr/sys/clock.h | 43 ++++++ tests/kernel/timer/timeout/CMakeLists.txt | 8 + tests/kernel/timer/timeout/prj.conf | 1 + tests/kernel/timer/timeout/src/main.c | 173 ++++++++++++++++++++++ tests/kernel/timer/timeout/testcase.yaml | 5 + 6 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 tests/kernel/timer/timeout/CMakeLists.txt create mode 100644 tests/kernel/timer/timeout/prj.conf create mode 100644 tests/kernel/timer/timeout/src/main.c create mode 100644 tests/kernel/timer/timeout/testcase.yaml diff --git a/include/zephyr/kernel.h b/include/zephyr/kernel.h index bfde2db1100..17ca435f63b 100644 --- a/include/zephyr/kernel.h +++ b/include/zephyr/kernel.h @@ -1665,6 +1665,22 @@ const char *k_thread_state_str(k_tid_t thread_id, char *buf, size_t buf_size); */ #define K_FOREVER Z_FOREVER +/** + * @brief Add two k_timeout_t values together + * + * This macro adds two k_timeout_t values together. If only one value is an + * absolute timeout, the result will be an absolute timeout. If both are + * relative timeouts, the result will be a relative timeout. If the calculation + * overflows, underflows or if both values are absolute timeouts, K_FOREVER + * is returned. + * + * @param timeout1 First k_timeout_t value + * @param timeout2 Second k_timeout_t value + * + * @return Sum of the two timeout values, or K_FOREVER if incalculable + */ +#define K_TIMEOUT_SUM(timeout1, timeout2) K_TICKS(z_timeout_sum(timeout1, timeout2)) + #ifdef CONFIG_TIMEOUT_64BIT /** @@ -1748,7 +1764,6 @@ const char *k_thread_state_str(k_tid_t thread_id, char *buf, size_t buf_size); * @return Timeout delay value */ #define K_TIMEOUT_ABS_CYC(t) K_TIMEOUT_ABS_TICKS(k_cyc_to_ticks_ceil64(t)) - #endif /** diff --git a/include/zephyr/sys/clock.h b/include/zephyr/sys/clock.h index c1193599583..d977fa4ff3f 100644 --- a/include/zephyr/sys/clock.h +++ b/include/zephyr/sys/clock.h @@ -172,6 +172,49 @@ typedef struct { /* The maximum duration in ticks strictly and semantically "less than" K_FOREVER */ #define K_TICK_MAX ((k_ticks_t)(IS_ENABLED(CONFIG_TIMEOUT_64BIT) ? INT64_MAX : UINT32_MAX - 1)) +/** + * @brief Sum the ticks from two timeout values + * + * This routine determines the resulting tick value when adding two k_timeout_t + * values together. If only one k_timeout_t value is an absolute timeout, the + * result will be an absolute timeout. If both are relative timeouts, the + * result will be a relative timeout. If the calculated tick value overflows, + * underflows or if both values are absolute timeouts, it returns K_TICKS_FOREVER. + * + * @param t1 First k_timeout_t value + * @param t2 Second k_timeout_t value + * + * @return Sum of the two timeout values in ticks, or val K_TICKS_FOREVER if incalculable + */ +static inline k_ticks_t z_timeout_sum(k_timeout_t t1, k_timeout_t t2) +{ + k_ticks_t ticks1 = t1.ticks; + k_ticks_t ticks2 = t2.ticks; + +#ifdef CONFIG_TIMEOUT_64BIT + if ((ticks1 == K_TICKS_FOREVER) || (ticks2 == K_TICKS_FOREVER)) { + return K_TICKS_FOREVER; + } + + if (ticks1 < 0) { + if (ticks2 < 0) { + return K_TICKS_FOREVER; /* Both absolute timeouts */ + } + + return ((ticks1 - INT64_MIN) < ticks2) ? + K_TICKS_FOREVER : (ticks1 - ticks2); + } else if (ticks2 < 0) { + return ((ticks2 - INT64_MIN) < ticks1) ? + K_TICKS_FOREVER : (ticks2 - ticks1); + } else { + return ((INT64_MAX - ticks1) < ticks2) ? + K_TICKS_FOREVER : ticks1 + ticks2; + } +#else + return ((UINT32_MAX - ticks1) < ticks2) ? K_TICKS_FOREVER : ticks1 + ticks2; +#endif +} + /** @endcond */ #ifndef CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME diff --git a/tests/kernel/timer/timeout/CMakeLists.txt b/tests/kernel/timer/timeout/CMakeLists.txt new file mode 100644 index 00000000000..ba9a308c5d3 --- /dev/null +++ b/tests/kernel/timer/timeout/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(timer_timeout) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/kernel/timer/timeout/prj.conf b/tests/kernel/timer/timeout/prj.conf new file mode 100644 index 00000000000..9467c292689 --- /dev/null +++ b/tests/kernel/timer/timeout/prj.conf @@ -0,0 +1 @@ +CONFIG_ZTEST=y diff --git a/tests/kernel/timer/timeout/src/main.c b/tests/kernel/timer/timeout/src/main.c new file mode 100644 index 00000000000..b598e381bf4 --- /dev/null +++ b/tests/kernel/timer/timeout/src/main.c @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2026 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#ifdef CONFIG_TIMEOUT_64BIT +/** + * Verify that absolute timeout sums are handled correctly + */ +ZTEST(timeout, test_timeout_sum_absolute) +{ + k_timeout_t abs_timeout = K_TIMEOUT_ABS_TICKS(1000); + k_timeout_t result; + + /* Two absolute timeouts should result in K_FOREVER */ + + result = K_TIMEOUT_SUM(abs_timeout, abs_timeout); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + /* Absolute with K_FOREVER should result in K_FOREVER */ + + result = K_TIMEOUT_SUM(abs_timeout, K_FOREVER); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_FOREVER, abs_timeout); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + /* Absolute with K_NO_WAIT should return the absolute */ + + result = K_TIMEOUT_SUM(abs_timeout, K_NO_WAIT); + zassert_true(K_TIMEOUT_EQ(result, abs_timeout), + "Expected K_TIMEOUT_ABS_TICKS(1000)"); + + result = K_TIMEOUT_SUM(K_NO_WAIT, abs_timeout); + zassert_true(K_TIMEOUT_EQ(result, abs_timeout), + "Expected K_TIMEOUT_ABS_TICKS(1000)"); + + /* Absolute + relative (no underflow) should return a new absolute */ + + result = K_TIMEOUT_SUM(abs_timeout, K_TICKS(100)); + zassert_true(K_TIMEOUT_EQ(result, K_TIMEOUT_ABS_TICKS(1100)), + "Expected K_TIMEOUT_ABS_TICKS(1100)"); + + result = K_TIMEOUT_SUM(K_TICKS(100), abs_timeout); + zassert_true(K_TIMEOUT_EQ(result, K_TIMEOUT_ABS_TICKS(1100)), + "Expected K_TIMEOUT_ABS_TICKS(1100)"); + + /* Limit testing: small absolute + large relative -- absolute 1st */ + + result = K_TIMEOUT_SUM(K_TIMEOUT_ABS_TICKS(5), K_TICKS(INT64_MAX - 4)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_TIMEOUT_ABS_TICKS(5), K_TICKS(INT64_MAX - 5)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_TIMEOUT_ABS_TICKS(5), K_TICKS(INT64_MAX - 6)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(INT64_MIN)), + "Expected INT64_MIN ticks"); + + result = K_TIMEOUT_SUM(K_TIMEOUT_ABS_TICKS(5), K_TICKS(INT64_MAX - 7)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(INT64_MIN + 1)), + "Expected INT64_MIN + 1 ticks"); + + /* Limit testing: small absolute + large relative -- relative 1st */ + + result = K_TIMEOUT_SUM(K_TICKS(INT64_MAX - 4), K_TIMEOUT_ABS_TICKS(5)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_TICKS(INT64_MAX - 5), K_TIMEOUT_ABS_TICKS(5)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_TICKS(INT64_MAX - 6), K_TIMEOUT_ABS_TICKS(5)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(INT64_MIN)), + "Expected INT64_MIN ticks"); + + result = K_TIMEOUT_SUM(K_TICKS(INT64_MAX - 7), K_TIMEOUT_ABS_TICKS(5)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(INT64_MIN + 1)), + "Expected INT64_MIN + 1 ticks"); + + /* Limit testing large absolute + small relative -- absolute 1st */ + + result = K_TIMEOUT_SUM(K_TIMEOUT_ABS_TICKS(INT64_MAX - 5), K_TICKS(6)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_TIMEOUT_ABS_TICKS(INT64_MAX - 6), K_TICKS(6)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_TIMEOUT_ABS_TICKS(INT64_MAX - 7), K_TICKS(6)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(INT64_MIN)), + "Expected INT64_MIN ticks"); + + result = K_TIMEOUT_SUM(K_TIMEOUT_ABS_TICKS(INT64_MAX - 8), K_TICKS(6)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(INT64_MIN + 1)), + "Expected INT64_MIN + 1 ticks"); + + /* Limit testing large absolute + small relative -- relative 1st */ + + result = K_TIMEOUT_SUM(K_TICKS(6), K_TIMEOUT_ABS_TICKS(INT64_MAX - 5)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_TICKS(6), K_TIMEOUT_ABS_TICKS(INT64_MAX - 6)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_TICKS(6), K_TIMEOUT_ABS_TICKS(INT64_MAX - 7)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(INT64_MIN)), + "Expected INT64_MIN ticks"); + + result = K_TIMEOUT_SUM(K_TICKS(6), K_TIMEOUT_ABS_TICKS(INT64_MAX - 8)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(INT64_MIN + 1)), + "Expected INT64_MIN + 1 ticks"); +} +#endif + +/** + * Verify that relative timeout sums are handled correctly + */ +ZTEST(timeout, test_timeout_sum_relative) +{ + k_timeout_t result; + + /* Verify that normal sums work as expected */ + + result = K_TIMEOUT_SUM(K_TICKS(1), K_TICKS(2)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(3)), "Expected 3 ticks"); + + /* K_NO_WAIT + X should return X */ + + result = K_TIMEOUT_SUM(K_NO_WAIT, K_TICKS(1)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(1)), "Expected 1 tick"); + + result = K_TIMEOUT_SUM(K_TICKS(1), K_NO_WAIT); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(1)), "Expected 1 tick"); + + result = K_TIMEOUT_SUM(K_NO_WAIT, K_NO_WAIT); + zassert_true(K_TIMEOUT_EQ(result, K_NO_WAIT), "Expected K_NO_WAIT"); + + /* K_FOREVER + anything should return K_FOREVER */ + + result = K_TIMEOUT_SUM(K_TICKS(1), K_FOREVER); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_FOREVER, K_TICKS(1)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_FOREVER, K_NO_WAIT); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_NO_WAIT, K_FOREVER); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_FOREVER, K_FOREVER); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + /* Behavior at limits */ + + result = K_TIMEOUT_SUM(K_TICKS(K_TICK_MAX - 1), K_TICKS(1)); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(K_TICK_MAX)), "Expected K_TICK_MAX ticks"); + + result = K_TIMEOUT_SUM(K_TICKS(K_TICK_MAX - 1), K_TICKS(2)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); + + result = K_TIMEOUT_SUM(K_TICKS(K_TICK_MAX), K_NO_WAIT); + zassert_true(K_TIMEOUT_EQ(result, K_TICKS(K_TICK_MAX)), "Expected K_TICK_MAX ticks"); + + result = K_TIMEOUT_SUM(K_TICKS(K_TICK_MAX), K_TICKS(1)); + zassert_true(K_TIMEOUT_EQ(result, K_FOREVER), "Expected K_FOREVER"); +} + +ZTEST_SUITE(timeout, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/kernel/timer/timeout/testcase.yaml b/tests/kernel/timer/timeout/testcase.yaml new file mode 100644 index 00000000000..3b9637ca229 --- /dev/null +++ b/tests/kernel/timer/timeout/testcase.yaml @@ -0,0 +1,5 @@ +tests: + kernel.timer.timeout: + tags: + - kernel + - timer