blob: cdd01cc7ee980b758efe034a57c98b781cc97801 [file]
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/minmax.h>
#include <zephyr/kernel.h>
#include <zephyr/spinlock.h>
#include <ksched.h>
#include <timeout_q.h>
#include <zephyr/internal/syscall_handler.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/sys/clock.h>
#include <zephyr/llext/symbol.h>
#include <timeslicing.h>
/* Absolute tick counter: ticks since boot. */
static uint64_t curr_tick;
/*
* The timeout code shall take no locks other than its own (timeout_lock), nor
* shall it call any other subsystem while holding this lock. Code outside this
* file takes it through sys_clock_lock()/sys_clock_unlock().
*/
static struct k_spinlock timeout_lock;
/* Ticks left to process in the currently-executing sys_clock_announce() */
static uint32_t announce_remaining;
/* CPU id currently inside sys_clock_announce_locked()'s firing loop, or -1
* when no CPU is. The SMP early-return below ensures at most one CPU is in
* the loop at a time, so a single int suffices. Used by code that needs to
* know whether *this* CPU is at a tick edge (announcing) versus somewhere
* within a tick (any other context, including running on a CPU while a
* different CPU is the announcer).
*/
static int announcing_cpu = -1;
static inline bool this_cpu_announcing(void)
{
return announcing_cpu == CPU_ID;
}
static inline bool any_cpu_announcing(void)
{
return announcing_cpu != -1;
}
/* Timeout whose handler is currently being dispatched, or NULL when no
* handler is in flight. The announcing CPU sets the pointer before
* calling the handler and clears it afterwards; any aborter may set the
* low "superseded" bit (struct _timeout is pointer-aligned so bit 0 is
* free). Accessors below mask the bit.
*/
static struct _timeout *inflight_timeout;
#define INFLIGHT_SUPERSEDED_BIT 1UL
static inline struct _timeout *inflight_ptr(void)
{
return (struct _timeout *)((uintptr_t)inflight_timeout & ~INFLIGHT_SUPERSEDED_BIT);
}
static inline void inflight_mark_superseded(void)
{
inflight_timeout = (struct _timeout *)((uintptr_t)inflight_timeout |
INFLIGHT_SUPERSEDED_BIT);
}
static uint32_t elapsed(void)
{
/*
* While *this* CPU is executing sys_clock_announce_locked()'s firing
* loop, new relative timeouts scheduled from a callback (or from a
* higher-priority ISR that preempted one) are anchored to the currently
* firing tick (curr_tick), so we report 0.
*
* On any other CPU the picture is different: we are not at a tick edge,
* and curr_tick is partway through being advanced by the announcing
* CPU's loop. The invariant we want to preserve is
*
* T_real - curr_tick = announce_remaining + sys_clock_elapsed()
*
* because the driver bumped its internal announced-cycle baseline to
* (curr_tick_initial + N) * CYC_PER_TICK at ISR entry, while the kernel
* has only advanced curr_tick by the K ticks processed so far -- so
* announce_remaining (= N - K) is the residual that must be added to
* sys_clock_elapsed() to get the real-time delta from curr_tick. This
* keeps sys_clock_tick_get() monotonic across the announce window.
*/
if (this_cpu_announcing()) {
return 0U;
}
return sys_clock_elapsed() +
(IS_ENABLED(CONFIG_SMP) ? announce_remaining : 0);
}
/*
* Backend queue implementation. The selected backend's queue instance and its
* z_timeout_q_*() operations are private to this file: the backend is a header
* included only here, after the shared state above, so those operations can
* reach curr_tick / announce_remaining / inflight_timeout / timeout_lock
* directly. The per-node helpers (z_init_timeout / z_is_inactive_timeout) are
* tree-wide and live in timeout_q.h.
*/
#if defined(CONFIG_TIMEOUT_BACKEND_MINHEAP)
#include "timeout_minheap.h"
#elif defined(CONFIG_TIMEOUT_BACKEND_WHEEL)
#include "timeout_wheel.h"
#elif defined(CONFIG_TIMEOUT_BACKEND_BUCKET)
#include "timeout_bucket.h"
#else /* CONFIG_TIMEOUT_BACKEND_DLIST */
#include "timeout_list.h"
#endif
/*
* Ticks the driver may wait before the next sys_clock_announce(). The
* announce-range cap lives here, in the backend-independent front end, so no
* backend has to reproduce it: the backend only reports the delta to its
* earliest pending timeout via z_timeout_q_next_expiry().
*/
static uint32_t next_timeout(uint32_t ticks_elapsed)
{
k_ticks_t next = z_timeout_q_next_expiry();
uint32_t dticks;
/*
* sys_clock_announce() reports the ticks elapsed since the previous
* announce, so the gap between two announces must not exceed
* SYS_CLOCK_MAX_WAIT for the announced count to fit. The budget left
* from now on is SYS_CLOCK_MAX_WAIT - ticks_elapsed; if it is already
* spent ask for an announce right away.
*/
if (ticks_elapsed >= SYS_CLOCK_MAX_WAIT) {
return 0;
}
/*
* No deadline, or one further out than can be scheduled in a single
* step: wait the capped budget and re-evaluate at the next announce.
* Testing next (which may be 64-bit) against the cap keeps the
* remaining arithmetic in 32 bits. The empty case still returns the
* budget so a driver keeps waking to maintain uptime.
*/
if (next == K_TICKS_FOREVER || next >= SYS_CLOCK_MAX_WAIT) {
return SYS_CLOCK_MAX_WAIT - ticks_elapsed;
}
/* Otherwise wait until the timeout, relative to now (0 if due). */
dticks = (uint32_t)next;
return (dticks > ticks_elapsed) ? (dticks - ticks_elapsed) : 0;
}
/*
* Reprogram the timer for the next pending timeout, or, when nothing is
* pending and CONFIG_SYSTEM_CLOCK_SLOPPY_IDLE tolerates a drifting uptime, tell
* the driver its clock is unused so it may stop. Only meaningful where the
* timeout queue may have just drained (abort, end of announce); the add path
* always has a pending timeout and calls sys_clock_set_timeout() directly.
*/
static void reprogram_next(uint32_t ticks_elapsed)
{
if (IS_ENABLED(CONFIG_SYSTEM_CLOCK_SLOPPY_IDLE) &&
z_timeout_q_next_expiry() == K_TICKS_FOREVER) {
sys_clock_no_timeout();
} else {
sys_clock_set_timeout(next_timeout(ticks_elapsed), false);
}
}
k_ticks_t z_add_timeout(struct _timeout *to, _timeout_func_t fn, k_timeout_t timeout)
{
k_ticks_t ticks = 0;
if (K_TIMEOUT_EQ(timeout, K_FOREVER)) {
return 0;
}
#ifdef CONFIG_KERNEL_COHERENCE
__ASSERT_NO_MSG(sys_cache_is_mem_coherent(to));
#endif /* CONFIG_KERNEL_COHERENCE */
__ASSERT(z_is_inactive_timeout(to), "");
to->fn = fn;
K_SPINLOCK(&timeout_lock) {
uint32_t ticks_elapsed;
bool has_elapsed = false;
k_ticks_t dticks;
if (Z_IS_TIMEOUT_RELATIVE(timeout)) {
ticks_elapsed = elapsed();
has_elapsed = true;
/*
* In the general case, "now" may be anywhere within
* the current tick. Rounding up by one tick guarantees
* "at least N ticks" semantics -- otherwise a request
* made partway through a tick would fire on the next
* tick edge, yielding less than N full ticks.
*
* The one moment we know *this* CPU is at a tick edge
* is while it is processing timeouts inside its own
* sys_clock_announce_locked() loop. Periodic timers
* rely on this when rescheduling themselves from the
* timer ISR: the round-up would otherwise accumulate
* and make every period one tick late. The check is
* per-CPU: a thread on another CPU running while we
* announce is *not* at a tick edge and still needs
* the round-up.
*/
dticks = timeout.ticks + ticks_elapsed +
(this_cpu_announcing() ? 0 : 1);
ticks = curr_tick + dticks;
} else {
dticks = Z_TICK_ABS(timeout.ticks) - curr_tick;
dticks = max(1, dticks);
ticks = timeout.ticks;
}
if (z_timeout_q_insert(to, dticks) && !any_cpu_announcing()) {
if (!has_elapsed) {
/* In case of absolute timeout that is first to expire
* elapsed need to be read from the system clock.
*/
ticks_elapsed = elapsed();
}
sys_clock_set_timeout(next_timeout(ticks_elapsed), false);
}
}
return ticks;
}
int z_try_abort_timeout(struct _timeout *to)
{
int ret = -EINVAL;
K_SPINLOCK(&timeout_lock) {
if (!z_is_inactive_timeout(to)) {
bool was_first = z_timeout_q_remove(to);
ret = 0;
if (was_first) {
reprogram_next(elapsed());
}
} else if (IS_ENABLED(CONFIG_SMP) && inflight_ptr() == to &&
!this_cpu_announcing()) {
/* Handler in flight on another CPU. Free-safety
* callers retry on -EAGAIN to wait it out; others
* rely on the superseded mark below and don't.
*/
ret = -EAGAIN;
}
/* Record that the in-flight timeout has been aborted, so a
* handler that checks (z_timeout_inflight_superseded) can
* tell its dispatch was overtaken.
*/
if (inflight_ptr() == to) {
inflight_mark_superseded();
}
}
if (IS_ENABLED(CONFIG_SMP) && ret == -EAGAIN) {
arch_spin_relax();
}
return ret;
}
bool z_timeout_inflight_superseded(const struct _timeout *to)
{
bool superseded = false;
K_SPINLOCK(&timeout_lock) {
superseded = inflight_timeout ==
(struct _timeout *)((uintptr_t)to | INFLIGHT_SUPERSEDED_BIT);
}
return superseded;
}
k_ticks_t z_timeout_remaining(const struct _timeout *timeout)
{
k_ticks_t ticks = 0;
K_SPINLOCK(&timeout_lock) {
if (!z_is_inactive_timeout(timeout)) {
ticks = z_timeout_q_remainder(timeout) - elapsed();
}
}
return ticks;
}
EXPORT_SYMBOL(z_timeout_remaining);
k_ticks_t z_timeout_expires(const struct _timeout *timeout)
{
k_ticks_t ticks = 0;
K_SPINLOCK(&timeout_lock) {
ticks = curr_tick;
if (!z_is_inactive_timeout(timeout)) {
ticks += z_timeout_q_remainder(timeout);
}
}
return ticks;
}
EXPORT_SYMBOL(z_timeout_expires);
uint32_t z_get_next_timeout_expiry(void)
{
uint32_t ret = (uint32_t)K_TICKS_FOREVER;
K_SPINLOCK(&timeout_lock) {
/*
* Same decision as reprogram_next(). Sloppy idle lets the
* uptime drift, so an empty list means nothing to wake up for
* and that is reported as K_TICKS_FOREVER. Otherwise the
* answer is a wait: either a real deadline, or the synthetic
* one that keeps the announce range covered.
*/
if (IS_ENABLED(CONFIG_SYSTEM_CLOCK_SLOPPY_IDLE) &&
z_timeout_q_next_expiry() == K_TICKS_FOREVER) {
ret = (uint32_t)K_TICKS_FOREVER;
} else {
ret = next_timeout(elapsed());
}
}
return ret;
}
void sys_clock_announce_locked(uint32_t ticks, k_spinlock_key_t key)
{
/* We release the lock around the callbacks below, so on SMP
* systems someone might be already running the loop. Don't
* race (which will cause parallel execution of "sequential"
* timeouts and confuse apps), just increment the tick count
* and return.
*/
if (IS_ENABLED(CONFIG_SMP) && any_cpu_announcing()) {
announce_remaining += ticks;
k_spin_unlock(&timeout_lock, key);
return;
}
announce_remaining = ticks;
announcing_cpu = CPU_ID;
#ifdef _TIMEOUT_BACKEND_OWNS_ANNOUNCE
/* The backend (timer wheel) runs the firing loop itself: its per-tick
* advance is a bitmap jump over empty ticks and its sift is a
* time-driven event with no single timeout to drive a generic loop.
* It advances curr_tick / announce_remaining and fires handlers via
* the same inflight_timeout dance as below.
*/
key = z_timeout_q_announce(key);
#else
while (announce_remaining > 0) {
struct _timeout *t;
int32_t dt = z_timeout_q_next_gap();
if ((uint32_t)dt > announce_remaining) {
/* Next event is past this announce window: advance the
* backend by the residual ticks without firing anything.
*/
dt = (int32_t)announce_remaining;
}
/* Advance curr_tick and decrement announce_remaining together
* under the lock so non-announcing CPUs observe a consistent
* (curr_tick + announce_remaining + sys_clock_elapsed()) ==
* T_real even while we drop the lock around handlers. The "we
* are announcing" state is carried by announcing_cpu, so
* announce_remaining reaching 0 mid-loop is harmless: same-tick
* handlers' z_add_timeout() still anchors via
* this_cpu_announcing(), and another CPU's announce still folds
* into ours via any_cpu_announcing() in the SMP early-return.
*/
z_timeout_q_advance(dt);
curr_tick += dt;
announce_remaining -= dt;
/* Drain everything due at the new curr_tick. */
while ((t = z_timeout_q_pop_due()) != NULL) {
_timeout_func_t handler = t->fn;
inflight_timeout = t;
k_spin_unlock(&timeout_lock, key);
handler(t);
key = k_spin_lock(&timeout_lock);
inflight_timeout = NULL;
}
}
#endif /* _TIMEOUT_BACKEND_OWNS_ANNOUNCE */
announcing_cpu = -1;
reprogram_next(0);
k_spin_unlock(&timeout_lock, key);
#ifdef CONFIG_TIMESLICING
z_time_slice();
#endif /* CONFIG_TIMESLICING */
}
#if defined(CONFIG_SMP) || defined(CONFIG_SPIN_VALIDATE)
k_spinlock_key_t sys_clock_lock(void)
{
return k_spin_lock(&timeout_lock);
}
void sys_clock_unlock(k_spinlock_key_t key)
{
k_spin_unlock(&timeout_lock, key);
}
#endif
#if defined(CONFIG_TEST) || defined(CONFIG_ASSERT)
bool sys_clock_is_locked(void)
{
return z_spin_is_locked(&timeout_lock);
}
#endif
int64_t sys_clock_tick_get(void)
{
uint64_t t = 0U;
K_SPINLOCK(&timeout_lock) {
t = curr_tick + elapsed();
}
return t;
}
uint32_t sys_clock_tick_get_32(void)
{
#ifdef CONFIG_TICKLESS_KERNEL
return (uint32_t)sys_clock_tick_get();
#else
return (uint32_t)curr_tick;
#endif /* CONFIG_TICKLESS_KERNEL */
}
int64_t z_impl_k_uptime_ticks(void)
{
return sys_clock_tick_get();
}
#ifdef CONFIG_USERSPACE
static inline int64_t z_vrfy_k_uptime_ticks(void)
{
return z_impl_k_uptime_ticks();
}
#include <zephyr/syscalls/k_uptime_ticks_mrsh.c>
#endif /* CONFIG_USERSPACE */
k_timepoint_t sys_timepoint_calc(k_timeout_t timeout)
{
k_timepoint_t timepoint;
if (K_TIMEOUT_EQ(timeout, K_FOREVER)) {
timepoint.tick = UINT64_MAX;
} else if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
timepoint.tick = 0;
} else {
k_ticks_t dt = timeout.ticks;
if (Z_IS_TIMEOUT_RELATIVE(timeout)) {
timepoint.tick = sys_clock_tick_get() + max(1, dt);
} else {
timepoint.tick = Z_TICK_ABS(dt);
}
}
return timepoint;
}
k_timeout_t sys_timepoint_timeout(k_timepoint_t timepoint)
{
uint64_t now, remaining;
if (timepoint.tick == UINT64_MAX) {
return K_FOREVER;
}
if (timepoint.tick == 0) {
return K_NO_WAIT;
}
now = sys_clock_tick_get();
remaining = (timepoint.tick > now) ? (timepoint.tick - now) : 0;
return K_TICKS(remaining);
}
#ifdef CONFIG_ZTEST
void z_impl_sys_clock_tick_set(uint64_t tick)
{
curr_tick = tick;
}
void z_vrfy_sys_clock_tick_set(uint64_t tick)
{
z_impl_sys_clock_tick_set(tick);
}
#endif /* CONFIG_ZTEST */