| /* |
| * 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> |
| |
| static uint64_t curr_tick; |
| |
| static sys_dlist_t timeout_list = SYS_DLIST_STATIC_INIT(&timeout_list); |
| |
| /* |
| * The timeout code shall take no locks other than its own (timeout_lock), nor |
| * shall it call any other subsystem while holding this lock. |
| */ |
| static struct k_spinlock timeout_lock; |
| |
| /* Ticks left to process in the currently-executing sys_clock_announce() */ |
| static int 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; |
| } |
| |
| static struct _timeout *first(void) |
| { |
| sys_dnode_t *t = sys_dlist_peek_head(&timeout_list); |
| |
| return (t == NULL) ? NULL : CONTAINER_OF(t, struct _timeout, node); |
| } |
| |
| static struct _timeout *next(struct _timeout *t) |
| { |
| sys_dnode_t *n = sys_dlist_peek_next(&timeout_list, &t->node); |
| |
| return (n == NULL) ? NULL : CONTAINER_OF(n, struct _timeout, node); |
| } |
| |
| static void remove_timeout(struct _timeout *t) |
| { |
| if (next(t) != NULL) { |
| next(t)->dticks += t->dticks; |
| } |
| |
| sys_dlist_remove(&t->node); |
| } |
| |
| static int32_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); |
| } |
| |
| static int32_t next_timeout(int32_t ticks_elapsed) |
| { |
| struct _timeout *to = first(); |
| int32_t ret; |
| |
| if ((to == NULL) || |
| ((int64_t)(to->dticks - ticks_elapsed) > (int64_t)INT_MAX)) { |
| ret = SYS_CLOCK_MAX_WAIT; |
| } else { |
| ret = max(0, to->dticks - ticks_elapsed); |
| } |
| |
| return ret; |
| } |
| |
| 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(!sys_dnode_is_linked(&to->node), ""); |
| to->fn = fn; |
| |
| K_SPINLOCK(&timeout_lock) { |
| struct _timeout *t; |
| int32_t ticks_elapsed; |
| bool has_elapsed = false; |
| |
| 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. |
| */ |
| to->dticks = timeout.ticks + ticks_elapsed + |
| (this_cpu_announcing() ? 0 : 1); |
| ticks = curr_tick + to->dticks; |
| } else { |
| k_ticks_t dticks = Z_TICK_ABS(timeout.ticks) - curr_tick; |
| |
| to->dticks = max(1, dticks); |
| ticks = timeout.ticks; |
| } |
| |
| for (t = first(); t != NULL; t = next(t)) { |
| if (t->dticks > to->dticks) { |
| t->dticks -= to->dticks; |
| sys_dlist_insert(&t->node, &to->node); |
| break; |
| } |
| to->dticks -= t->dticks; |
| } |
| |
| if (t == NULL) { |
| sys_dlist_append(&timeout_list, &to->node); |
| } |
| |
| if (to == first() && !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_abort_timeout(struct _timeout *to) |
| { |
| int ret = -EINVAL; |
| |
| K_SPINLOCK(&timeout_lock) { |
| if (sys_dnode_is_linked(&to->node)) { |
| bool is_first = (to == first()); |
| |
| remove_timeout(to); |
| to->dticks = TIMEOUT_DTICKS_ABORTED; |
| ret = 0; |
| if (is_first) { |
| sys_clock_set_timeout(next_timeout(elapsed()), false); |
| } |
| } else if (to->dticks == TIMEOUT_DTICKS_ANNOUNCING) { |
| to->dticks = TIMEOUT_DTICKS_ABORTED; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* must be locked */ |
| static k_ticks_t timeout_rem(const struct _timeout *timeout) |
| { |
| k_ticks_t ticks = 0; |
| |
| for (struct _timeout *t = first(); t != NULL; t = next(t)) { |
| ticks += t->dticks; |
| if (timeout == t) { |
| break; |
| } |
| } |
| |
| return ticks; |
| } |
| |
| 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 = timeout_rem(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 += timeout_rem(timeout); |
| } |
| } |
| |
| return ticks; |
| } |
| EXPORT_SYMBOL(z_timeout_expires); |
| |
| int32_t z_get_next_timeout_expiry(void) |
| { |
| int32_t ret = (int32_t) K_TICKS_FOREVER; |
| |
| K_SPINLOCK(&timeout_lock) { |
| ret = next_timeout(elapsed()); |
| } |
| return ret; |
| } |
| |
| void sys_clock_announce_locked(int32_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; |
| |
| struct _timeout *t; |
| |
| for (t = first(); |
| (t != NULL) && (t->dticks <= announce_remaining); |
| t = first()) { |
| _timeout_func_t handler = t->fn; |
| |
| /* 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 the handler. 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. |
| */ |
| curr_tick += t->dticks; |
| announce_remaining -= t->dticks; |
| |
| sys_dlist_remove(&t->node); |
| t->dticks = TIMEOUT_DTICKS_ANNOUNCING; |
| |
| k_spin_unlock(&timeout_lock, key); |
| handler(t); |
| key = k_spin_lock(&timeout_lock); |
| } |
| |
| if (t != NULL) { |
| t->dticks -= announce_remaining; |
| } |
| |
| curr_tick += announce_remaining; |
| announce_remaining = 0; |
| announcing_cpu = -1; |
| |
| sys_clock_set_timeout(next_timeout(0), false); |
| |
| 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 */ |