doc: kernel: clocks: define "current time"

Scheduling relative timeouts from within timer callbacks (=sys clock ISR
context) differs from scheduling relative timeouts from an application
context.

This change documents and explains the rationale of this distinction.

Signed-off-by: Florian Grandel <fgrandel@code-for-humans.de>
This commit is contained in:
Florian Grandel 2023-06-28 23:17:45 +02:00 committed by Carles Cufí
commit 5fa5534afc
2 changed files with 34 additions and 4 deletions

View file

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

View file

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