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 <peter.mitsis@intel.com>
This commit is contained in:
Peter Mitsis
2026-01-14 15:23:00 -08:00
committed by Maureen Helm
parent 29493ced6f
commit e18a048c84
6 changed files with 246 additions and 1 deletions

View File

@@ -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
/**

View File

@@ -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

View File

@@ -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})

View File

@@ -0,0 +1 @@
CONFIG_ZTEST=y

View File

@@ -0,0 +1,173 @@
/*
* Copyright (c) 2026 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/ztest.h>
#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);

View File

@@ -0,0 +1,5 @@
tests:
kernel.timer.timeout:
tags:
- kernel
- timer