blob: c6615545fcf9b1571247c68fe1c92a801ecb595e [file]
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_KERNEL_TIMEOUT_LIST_H_
#define ZEPHYR_KERNEL_TIMEOUT_LIST_H_
/**
* @file
* @brief Sorted delta-list timeout backend (implementation).
*
* This is the original (and default) timeout queue: a doubly linked list
* sorted by expiry, where each node stores its expiry as a delta (dticks)
* relative to its predecessor. Insertion is O(n), head removal is O(1).
*
* Included only by kernel/timeout.c, after its shared state, so the queue
* instance and the z_timeout_q_*() operations below are private to that
* translation unit. The per-node helpers live in kernel/include/timeout_q.h.
*/
#include <zephyr/sys/dlist.h>
static sys_dlist_t timeout_list = SYS_DLIST_STATIC_INIT(&timeout_list);
static inline struct _timeout *z_timeout_q_first(void)
{
sys_dnode_t *t = sys_dlist_peek_head(&timeout_list);
return (t == NULL) ? NULL : CONTAINER_OF(t, struct _timeout, node);
}
static inline struct _timeout *z_timeout_q_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);
}
/* Insert @to so that it expires @dticks ticks after curr_tick. Returns true
* if @to is now the earliest pending timeout.
*/
static inline bool z_timeout_q_insert(struct _timeout *to, k_ticks_t dticks)
{
struct _timeout *t;
to->dticks = dticks;
for (t = z_timeout_q_first(); t != NULL; t = z_timeout_q_next(t)) {
if (t->dticks > to->dticks) {
t->dticks -= to->dticks;
sys_dlist_insert(&t->node, &to->node);
return z_timeout_q_first() == to;
}
to->dticks -= t->dticks;
}
sys_dlist_append(&timeout_list, &to->node);
return z_timeout_q_first() == to;
}
/* Remove an arbitrary timeout from the queue, absorbing its delta into the
* successor so the remaining deltas stay valid. Returns true if @to was the
* earliest pending timeout. Used only by the abort path, never from announce.
*/
static inline bool z_timeout_q_remove(struct _timeout *to)
{
bool was_first = (z_timeout_q_first() == to);
struct _timeout *n = z_timeout_q_next(to);
if (n != NULL) {
n->dticks += to->dticks;
}
sys_dlist_remove(&to->node);
return was_first;
}
/* Ticks from curr_tick until @to expires. Caller holds timeout_lock and has
* verified that @to is active.
*/
static inline k_ticks_t z_timeout_q_remainder(const struct _timeout *to)
{
k_ticks_t ticks = 0;
for (struct _timeout *t = z_timeout_q_first(); t != NULL;
t = z_timeout_q_next(t)) {
ticks += t->dticks;
if (to == t) {
break;
}
}
return ticks;
}
/* Ticks from (curr_tick + ticks_elapsed) until the next expiry, clamped to
* int32 / SYS_CLOCK_MAX_WAIT. Empty queue yields SYS_CLOCK_MAX_WAIT.
*/
/* Ticks from curr_tick to the earliest pending timeout, or K_TICKS_FOREVER if
* the queue is empty. The announce-range cap is applied centrally by
* next_timeout() in timeout.c.
*/
static inline k_ticks_t z_timeout_q_next_expiry(void)
{
struct _timeout *to = z_timeout_q_first();
return (to == NULL) ? K_TICKS_FOREVER : to->dticks;
}
/* --- announce-loop helpers (see sys_clock_announce_locked() in timeout.c) --- */
/* Ticks from curr_tick until the next event the announce loop must act on
* (here: the earliest expiry). A value larger than any announce window when
* the queue is empty.
*/
static inline int32_t z_timeout_q_next_gap(void)
{
struct _timeout *t = z_timeout_q_first();
return (t == NULL) ? INT32_MAX : (int32_t)MIN((int64_t)t->dticks, INT32_MAX);
}
/* Advance backend state by @dt ticks. For the delta list this shifts the head
* closer to expiry; a head whose gap is fully consumed reads dticks == 0 and
* is then drained by z_timeout_q_pop_due(). The caller advances curr_tick.
*/
static inline void z_timeout_q_advance(int32_t dt)
{
struct _timeout *t = z_timeout_q_first();
if (t != NULL) {
t->dticks -= dt;
}
}
/* Pop and return one timeout that is due at the current curr_tick, or NULL if
* none is due right now. Strict head-only removal with no delta absorption:
* z_timeout_q_advance() has already brought the head's dticks to 0, and
* curr_tick was advanced to match, so the successor's stored delta stays
* valid without propagation.
*/
static inline struct _timeout *z_timeout_q_pop_due(void)
{
struct _timeout *t = z_timeout_q_first();
if ((t == NULL) || (t->dticks != 0)) {
return NULL;
}
sys_dlist_remove(&t->node);
return t;
}
#endif /* ZEPHYR_KERNEL_TIMEOUT_LIST_H_ */