kernel: timer: Add timer observer hooks for extensibility

Introduce lifecycle observer callbacks (init, start, stop, expiry)
for k_timer using Zephyr's iterable sections pattern. This enables
external modules to extend timer functionality without modifying
kernel internals.

Signed-off-by: Vijay Sharma <vijshar@qti.qualcomm.com>
This commit is contained in:
Vijay Sharma
2025-12-23 15:30:01 +05:30
committed by Maureen Helm
parent d94ed7b316
commit 83f1b8ba49
8 changed files with 231 additions and 0 deletions

View File

@@ -1801,6 +1801,23 @@ struct k_timer {
*/
};
#ifdef CONFIG_TIMER_OBSERVER
struct k_timer_observer {
/* Invoked upon completion of k_timer initialization */
void (*on_init)(struct k_timer *timer);
/* Invoked after the timer transitions to the running state */
void (*on_start)(struct k_timer *timer, k_timeout_t duration,
k_timeout_t period);
/* Invoked when the active timer is explicitly stopped */
void (*on_stop)(struct k_timer *timer);
/* Executes in ISR context, keep minimal and non-blocking */
void (*on_expiry)(struct k_timer *timer);
};
#endif /* CONFIG_TIMER_OBSERVER */
/**
* @cond INTERNAL_HIDDEN
*/
@@ -1872,6 +1889,42 @@ typedef void (*k_timer_stop_t)(struct k_timer *timer);
STRUCT_SECTION_ITERABLE(k_timer, name) = \
Z_TIMER_INITIALIZER(name, expiry_fn, stop_fn)
#ifdef CONFIG_TIMER_OBSERVER
/**
* @cond INTERNAL_HIDDEN
*/
#define Z_TIMER_OBSERVER_INITIALIZER(name, init, start, stop, expiry) \
{ \
.on_init = init, \
.on_start = start, \
.on_stop = stop, \
.on_expiry = expiry \
}
/**
* INTERNAL_HIDDEN @endcond
*/
/**
* @brief Statically define and initialize a timer observer.
*
* Iterable-section based observer interface for k_timer lifecycle
* events (init/start/stop/expiry). External modules can register
* additional functionality without modifying kernel internals.
*
* @param name Name of the k_timer_observer variable.
* @param init Pointer to initialization callback (or NULL).
* @param start Pointer to start callback (or NULL).
* @param stop Pointer to stop callback (or NULL).
* @param expiry Pointer to expiry callback (or NULL).
*/
#define K_TIMER_OBSERVER_DEFINE(name, init, start, stop, expiry) \
static const STRUCT_SECTION_ITERABLE(k_timer_observer, name) = \
Z_TIMER_OBSERVER_INITIALIZER(name, init, start, stop, expiry)
#endif /* CONFIG_TIMER_OBSERVER */
/**
* @brief Initialize a timer.
*

View File

@@ -105,4 +105,8 @@
ITERABLE_SECTION_ROM(_stack_to_hw_shadow_stack_arr, Z_LINK_ITERABLE_SUBALIGN)
#endif
#if defined(CONFIG_TIMER_OBSERVER)
ITERABLE_SECTION_ROM(k_timer_observer, Z_LINK_ITERABLE_SUBALIGN)
#endif /* CONFIG_TIMER_OBSERVER */
#include <device-api-sections.ld>

View File

@@ -717,6 +717,12 @@ config TIMESLICE_PER_THREAD
a per-thread basis, with an application callback invoked when
a thread reaches the end of its timeslice.
config TIMER_OBSERVER
bool "Support extension points for k_timer lifecycle"
help
Adds iterablesection support for observing k_timer events.
Supports extending timer behavior without kernel changes.
endmenu
menu "Other Kernel Object Options"

View File

@@ -19,6 +19,50 @@ static struct k_spinlock lock;
static struct k_obj_type obj_type_timer;
#endif /* CONFIG_OBJ_CORE_TIMER */
#if defined(CONFIG_TIMER_OBSERVER)
static inline void z_timer_observer_on_init(struct k_timer *timer)
{
STRUCT_SECTION_FOREACH(k_timer_observer, obs) {
if (obs->on_init != NULL) {
obs->on_init(timer);
}
}
}
static inline void z_timer_observer_on_start(struct k_timer *timer, k_timeout_t duration,
k_timeout_t period)
{
STRUCT_SECTION_FOREACH(k_timer_observer, obs) {
if (obs->on_start != NULL) {
obs->on_start(timer, duration, period);
}
}
}
static inline void z_timer_observer_on_stop(struct k_timer *timer)
{
STRUCT_SECTION_FOREACH(k_timer_observer, obs) {
if (obs->on_stop != NULL) {
obs->on_stop(timer);
}
}
}
static inline void z_timer_observer_on_expiry(struct k_timer *timer)
{
STRUCT_SECTION_FOREACH(k_timer_observer, obs) {
if (obs->on_expiry != NULL) {
obs->on_expiry(timer);
}
}
}
#else
#define z_timer_observer_on_init(timer) (void)0
#define z_timer_observer_on_start(timer, duration, period) (void)0
#define z_timer_observer_on_stop(timer) (void)0
#define z_timer_observer_on_expiry(timer) (void)0
#endif /* CONFIG_TIMER_OBSERVER */
/**
* @brief Handle expiration of a kernel timer object.
*
@@ -80,6 +124,8 @@ void z_timer_expiration_handler(struct _timeout *t)
/* update timer's status */
timer->status += 1U;
z_timer_observer_on_expiry(timer);
/* invoke timer expiry function */
if (timer->expiry_fn != NULL) {
/* Unlock for user handler. */
@@ -139,6 +185,8 @@ void k_timer_init(struct k_timer *timer,
#ifdef CONFIG_OBJ_CORE_TIMER
k_obj_core_init_and_link(K_OBJ_CORE(timer), &obj_type_timer);
#endif /* CONFIG_OBJ_CORE_TIMER */
z_timer_observer_on_init(timer);
}
@@ -188,6 +236,8 @@ void z_impl_k_timer_start(struct k_timer *timer, k_timeout_t duration,
z_add_timeout(&timer->timeout, z_timer_expiration_handler,
duration);
z_timer_observer_on_start(timer, duration, period);
k_spin_unlock(&lock, key);
}
@@ -212,6 +262,8 @@ void z_impl_k_timer_stop(struct k_timer *timer)
return;
}
z_timer_observer_on_stop(timer);
if (timer->stop_fn != NULL) {
SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_timer, stop_fn_expiry, timer);

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_observer)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

View File

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

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2025 Qualcomm Technologies, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/ztest.h>
#include <zephyr/sys/atomic.h>
static struct k_timer test_periodic_timer;
static int expiry_cnt;
static int stop_cnt;
struct obs_state_s {
int init_cnt;
int start_cnt;
int stop_cnt;
int expiry_cnt;
};
static struct obs_state_s obs;
static void timer_expiry_cb(struct k_timer *timer)
{
expiry_cnt++;
}
static void timer_stop_cb(struct k_timer *timer)
{
stop_cnt++;
}
__boot_func
static void obs_on_init(struct k_timer *timer)
{
if (timer == &test_periodic_timer) {
obs.init_cnt++;
}
}
__boot_func
static void obs_on_start(struct k_timer *timer, k_timeout_t duration, k_timeout_t period)
{
if (timer == &test_periodic_timer) {
obs.start_cnt++;
}
}
__boot_func
static void obs_on_stop(struct k_timer *timer)
{
if (timer == &test_periodic_timer) {
obs.stop_cnt++;
}
}
__boot_func
static void obs_on_expiry(struct k_timer *timer)
{
if (timer == &test_periodic_timer) {
obs.expiry_cnt++;
}
}
ZTEST(timer_observer, test_periodic_expiry_and_explicit_stop)
{
const int dur_ms = 80;
const int per_ms = 40;
/* Initialize the timer to trigger on_init path */
k_timer_init(&test_periodic_timer, timer_expiry_cb, timer_stop_cb);
/* Start periodic timer*/
k_timer_start(&test_periodic_timer, K_MSEC(dur_ms), K_MSEC(per_ms));
/* Allow a few expiries to happen */
k_busy_wait((dur_ms + (per_ms * 3)) * 1000);
/* Now explicitly stop; observer should see on_stop */
k_timer_stop(&test_periodic_timer);
/* Small delay to ensure stop path completes */
k_busy_wait(10 * 1000);
/* Verify timer on_init was called and updated for the test timer */
zassert_equal(obs.init_cnt, 1, "obs init count mismatch");
/* Observer should have seen start */
zassert_equal(obs.start_cnt, 1, "obs start count mismatch");
zassert_equal(obs.stop_cnt, stop_cnt, "obs stop count mismatch");
/* Observer should have seen multiple expiry callbacks */
zassert_equal(obs.expiry_cnt, expiry_cnt, "obs expiry count mismatch");
}
K_TIMER_OBSERVER_DEFINE(observer, obs_on_init, obs_on_start, obs_on_stop, obs_on_expiry);
ZTEST_SUITE(timer_observer, NULL, NULL, NULL, NULL, NULL);

View File

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