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:
committed by
Maureen Helm
parent
d94ed7b316
commit
83f1b8ba49
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 iterable‑section support for observing k_timer events.
|
||||
Supports extending timer behavior without kernel changes.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Other Kernel Object Options"
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
8
tests/kernel/timer/timer_observer/CMakeLists.txt
Normal file
8
tests/kernel/timer/timer_observer/CMakeLists.txt
Normal 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})
|
||||
2
tests/kernel/timer/timer_observer/prj.conf
Normal file
2
tests/kernel/timer/timer_observer/prj.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
CONFIG_ZTEST=y
|
||||
CONFIG_TIMER_OBSERVER=y
|
||||
101
tests/kernel/timer/timer_observer/src/main.c
Normal file
101
tests/kernel/timer/timer_observer/src/main.c
Normal 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);
|
||||
5
tests/kernel/timer/timer_observer/testcase.yaml
Normal file
5
tests/kernel/timer/timer_observer/testcase.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
tests:
|
||||
kernel.timer.timer_observer:
|
||||
tags:
|
||||
- kernel
|
||||
- timer
|
||||
Reference in New Issue
Block a user