diff --git a/doc/kernel/services/timing/clocks.rst b/doc/kernel/services/timing/clocks.rst index 901fe88477d97f..3a016b3f351671 100644 --- a/doc/kernel/services/timing/clocks.rst +++ b/doc/kernel/services/timing/clocks.rst @@ -100,9 +100,23 @@ For example: All these values are specified using a :c:struct:`k_timeout_t` value. This is an opaque struct type that must be initialized using one of a family -of kernel timeout macros. The most common, :c:macro:`K_MSEC` , defines -a time in milliseconds after the current time (strictly: the time at -which the kernel receives the timeout value). +of kernel timeout macros. The most common, :c:macro:`K_MSEC`, defines +a time in milliseconds after the current time. + +What is meant by "current time" for relative timeouts depends on the context: + +* When scheduling a relative timeout from within a timeout callback (e.g. from + within the expiry function passed to :c:func:`k_timer_init` or the work handler + passed to :c:func:`k_work_init_delayable`), "current time" is the exact time at + which the currently firing timeout was originally scheduled even if the "real + time" will already have advanced. This is to ensure that timers scheduled from + within another timer's callback will always be calculated with a precise offset + to the firing timer. It is thereby possible to fire at regular intervals without + introducing systematic clock drift over time. + +* When scheduling a timeout from application context, "current time" means the + value returned by :c:func:`k_uptime_ticks` at the time at which the kernel + receives the timeout value. Other options for timeout initialization follow the unit conventions described above: :c:macro:`K_NSEC()`, :c:macro:`K_USEC`, :c:macro:`K_TICKS` and diff --git a/kernel/timeout.c b/kernel/timeout.c index f5daf82225963f..a539fb5d670152 100644 --- a/kernel/timeout.c +++ b/kernel/timeout.c @@ -21,7 +21,7 @@ static struct k_spinlock timeout_lock; #define MAX_WAIT (IS_ENABLED(CONFIG_SYSTEM_CLOCK_SLOPPY_IDLE) \ ? K_TICKS_FOREVER : INT_MAX) -/* Cycles left to process in the currently-executing sys_clock_announce() */ +/* Ticks left to process in the currently-executing sys_clock_announce() */ static int announce_remaining; #if defined(CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME) @@ -61,6 +61,22 @@ static void remove_timeout(struct _timeout *t) static int32_t elapsed(void) { + /* While sys_clock_announce() is executing, new relative timeouts will be + * scheduled relatively to the currently firing timeout's original tick + * value (=curr_tick) rather than relative to the current + * sys_clock_elapsed(). + * + * This means that timeouts being scheduled from within timeout callbacks + * will be scheduled at well-defined offsets from the currently firing + * timeout. + * + * As a side effect, the same will happen if an ISR with higher priority + * preempts a timeout callback and schedules a timeout. + * + * The distinction is implemented by looking at announce_remaining which + * will be non-zero while sys_clock_announce() is executing and zero + * otherwise. + */ return announce_remaining == 0 ? sys_clock_elapsed() : 0U; }