blob: c906ea8e62ff68ea21be68aade63a70a603d7606 [file]
/*
* Copyright (c) 2016-2017 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_KERNEL_INCLUDE_KSCHED_H_
#define ZEPHYR_KERNEL_INCLUDE_KSCHED_H_
#include <zephyr/kernel_structs.h>
#include <kernel_internal.h>
#include <timeout_q.h>
#include <kthread.h>
#include <zephyr/tracing/tracing.h>
#include <stdbool.h>
#include <priority_q.h>
BUILD_ASSERT(K_LOWEST_APPLICATION_THREAD_PRIO
>= K_HIGHEST_APPLICATION_THREAD_PRIO);
#ifdef CONFIG_MULTITHREADING
#define Z_VALID_PRIO(prio, entry_point) \
(((prio) == K_IDLE_PRIO && z_is_idle_thread_entry(entry_point)) || \
((K_LOWEST_APPLICATION_THREAD_PRIO \
>= K_HIGHEST_APPLICATION_THREAD_PRIO) \
&& (prio) >= K_HIGHEST_APPLICATION_THREAD_PRIO \
&& (prio) <= K_LOWEST_APPLICATION_THREAD_PRIO))
#define Z_ASSERT_VALID_PRIO(prio, entry_point) do { \
__ASSERT(Z_VALID_PRIO((prio), (entry_point)), \
"invalid priority (%d); allowed range: %d to %d", \
(prio), \
K_LOWEST_APPLICATION_THREAD_PRIO, \
K_HIGHEST_APPLICATION_THREAD_PRIO); \
} while (false)
#else
#define Z_VALID_PRIO(prio, entry_point) ((prio) == -1)
#define Z_ASSERT_VALID_PRIO(prio, entry_point) __ASSERT((prio) == -1, "")
#endif /* CONFIG_MULTITHREADING */
#if (CONFIG_MP_MAX_NUM_CPUS == 1)
#define LOCK_SCHED_SPINLOCK
#else
#define LOCK_SCHED_SPINLOCK K_SPINLOCK(&_sched_spinlock)
#endif
#ifdef __cplusplus
extern "C" {
#endif
extern struct k_spinlock _sched_spinlock;
extern struct k_thread _thread_dummy;
/**
* @brief Unpend thread, do not abort its timeout (if it exists).
*/
void z_unpend_thread_no_timeout(struct k_thread *thread);
/**
* @brief Unpend thread and abort its timeout (if it exists).
*/
void z_unpend_thread(struct k_thread *thread);
struct k_thread *z_unpend1_no_timeout(_wait_q_t *wait_q);
int z_pend_curr(struct k_spinlock *lock, k_spinlock_key_t key,
_wait_q_t *wait_q, k_timeout_t timeout);
void z_pend_thread(struct k_thread *thread, _wait_q_t *wait_q,
k_timeout_t timeout);
void z_reschedule(struct k_spinlock *lock, k_spinlock_key_t key);
void z_reschedule_irqlock(uint32_t key);
int z_unpend_all(_wait_q_t *wait_q);
bool z_thread_prio_set(struct k_thread *thread, int prio);
void *z_get_next_switch_handle(void *interrupted);
void z_thread_suspend_current(struct k_thread *thread);
/* Wrapper around z_get_next_switch_handle() for the benefit of
* non-SMP platforms that always pass a NULL interrupted handle.
* Exposes the (extremely) common early exit case in a context that
* can be (much) better optimized in surrounding C code. Takes a
* _current pointer that the outer scope surely already has to avoid
* having to refetch it across a lock.
*
* Mystic arts for cycle nerds, basically. Replace with
* z_get_next_switch_handle() if this becomes a maintenance hassle.
*/
static ALWAYS_INLINE void *z_sched_next_handle(struct k_thread *curr)
{
#ifndef CONFIG_SMP
if (curr == _kernel.ready_q.cache) {
return NULL;
}
#endif
return z_get_next_switch_handle(NULL);
}
void z_sched_start(struct k_thread *thread);
void z_ready_thread(struct k_thread *thread);
struct k_thread *z_swap_next_thread(void);
void move_current_to_end_of_prio_q(void);
/*
* Internal scheduler functions exposed for use by thread lifecycle code
* (thread.c). These operate under _sched_spinlock and must not be called
* without holding it.
*/
/**
* @brief Halt a thread, suspending or terminating it.
*
* Shared implementation for k_thread_suspend() and k_thread_abort(). The
* caller must hold _sched_spinlock; this function releases it before
* returning (possibly after a context switch).
*
* @param thread Thread to halt.
* @param key Scheduler spinlock key held by the caller.
* @param terminate true to abort (kill) the thread, false to suspend it.
*/
void z_thread_halt(struct k_thread *thread, k_spinlock_key_t key, bool terminate);
/**
* @brief Ready a thread while the scheduler spinlock is already held.
*
* Equivalent to the internal ready_thread() helper. Callers must hold
* _sched_spinlock.
*
* @param thread Thread to make ready.
*/
void z_sched_ready_locked(struct k_thread *thread);
/**
* @brief Add a thread to a wait queue while the scheduler spinlock is held.
*
* Moves the thread out of the run queue and onto the specified wait queue.
* Callers must hold _sched_spinlock.
*
* @param thread Thread to pend.
* @param wait_q Wait queue to add the thread to (may be NULL).
*/
void z_sched_add_to_waitq_locked(struct k_thread *thread, _wait_q_t *wait_q);
/**
* @brief Remove a thread from the run queue (scheduler spinlock must be held).
*
* Called by sleep.c to unready the current thread before arming its wakeup
* timeout. Callers must already hold _sched_spinlock.
*
* @param thread Thread to remove from the run queue.
*/
void z_sched_unready_locked(struct k_thread *thread);
/**
* @brief Yield the current thread's remaining time slice.
*
* Moves _current to the end of its priority group in the run queue, updates
* the scheduler cache, and context-switches away. Equivalent to the body
* of k_yield(); exposed so that thread.c can implement k_yield() without
* depending on sched.c internals.
*/
void z_sched_yield(void);
static inline void z_reschedule_unlocked(void)
{
(void) z_reschedule_irqlock(arch_irq_lock());
}
static inline bool z_is_under_prio_ceiling(int prio)
{
return prio >= CONFIG_PRIORITY_CEILING;
}
static inline int z_get_new_prio_with_ceiling(int prio)
{
return z_is_under_prio_ceiling(prio) ? prio : CONFIG_PRIORITY_CEILING;
}
static inline bool z_is_prio1_higher_than_or_equal_to_prio2(int prio1, int prio2)
{
return prio1 <= prio2;
}
static inline bool z_is_prio_higher_or_equal(int prio1, int prio2)
{
return z_is_prio1_higher_than_or_equal_to_prio2(prio1, prio2);
}
static inline bool z_is_prio1_lower_than_or_equal_to_prio2(int prio1, int prio2)
{
return prio1 >= prio2;
}
static inline bool z_is_prio1_higher_than_prio2(int prio1, int prio2)
{
return prio1 < prio2;
}
static inline bool z_is_prio_higher(int prio, int test_prio)
{
return z_is_prio1_higher_than_prio2(prio, test_prio);
}
static inline bool z_is_prio_lower_or_equal(int prio1, int prio2)
{
return z_is_prio1_lower_than_or_equal_to_prio2(prio1, prio2);
}
static inline bool _is_valid_prio(int prio, k_thread_entry_t entry_point)
{
if ((prio == K_IDLE_PRIO) && z_is_idle_thread_entry(entry_point)) {
return true;
}
if (!z_is_prio_higher_or_equal(prio,
K_LOWEST_APPLICATION_THREAD_PRIO)) {
return false;
}
if (!z_is_prio_lower_or_equal(prio,
K_HIGHEST_APPLICATION_THREAD_PRIO)) {
return false;
}
return true;
}
static ALWAYS_INLINE _wait_q_t *pended_on_thread(struct k_thread *thread)
{
__ASSERT_NO_MSG(thread->base.pended_on);
return thread->base.pended_on;
}
static inline void unpend_thread_no_timeout(struct k_thread *thread)
{
_priq_wait_remove(&pended_on_thread(thread)->waitq, thread);
z_mark_thread_as_not_pending(thread);
thread->base.pended_on = NULL;
}
/*
* Pick the best-priority waiter from @p wait_q, unpend it, and abort any
* timeout it had pending. Returns the thread, or NULL if the wait_q was
* empty.
*
* Caller MUST hold _sched_spinlock and MUST complete the wake (set return
* value, ready the thread) under the same lock acquisition before releasing
* it. Otherwise a racing in-flight timeout handler can ready the thread
* before the caller's wake state is in place, exposing the woken thread
* to a stale swap_retval. The pre-1b8c7a3 dticks-cancel check used to
* close this window; doing all the wake work atomically under the sched
* lock is now the way.
*
* z_sched_wake() is the convenient wrapper for the common case of
* "wake one waiter with this retval and this swap_data".
*/
static ALWAYS_INLINE struct k_thread *z_unpend_first_thread_locked(_wait_q_t *wait_q)
{
struct k_thread *thread = _priq_wait_best(&wait_q->waitq);
if (unlikely(thread != NULL)) {
unpend_thread_no_timeout(thread);
/* Abort the thread's timeout. If its handler is in flight on
* another CPU it is blocked on the sched lock; the abort flags
* it superseded, and z_thread_timeout() bails on that flag when
* it finally runs -- so it won't wake the thread from whatever
* it has since re-pended on. We don't wait for it here.
*/
(void)z_try_abort_thread_timeout(thread);
}
return thread;
}
/**
* Wake up a thread pending on the provided wait queue
*
* Given a wait_q, wake up the highest priority thread on the queue. If the
* queue was empty just return false.
*
* Otherwise, do the following, in order, holding _sched_spinlock the entire
* time so that the thread state is guaranteed not to change:
* - Set the thread's swap return values to swap_retval and swap_data
* - un-pend and ready the thread, but do not invoke the scheduler.
*
* Repeated calls to this function until it returns false is a suitable
* way to wake all threads on the queue.
*
* It is up to the caller to implement locking such that the return value of
* this function (whether a thread was woken up or not) does not immediately
* become stale. Calls to wait and wake on the same wait_q object must have
* synchronization. Calling this without holding any spinlock is a sign that
* this API is not being used properly.
*
* @param wait_q Wait queue to wake up the highest prio thread
* @param swap_retval Swap return value for woken thread
* @param swap_data Data return value to supplement swap_retval. May be NULL.
* @retval true If a thread was woken up
* @retval false If the wait_q was empty
*/
static ALWAYS_INLINE bool z_sched_wake(_wait_q_t *wait_q, int swap_retval, void *swap_data)
{
struct k_thread *thread;
bool ret = false;
LOCK_SCHED_SPINLOCK {
thread = z_unpend_first_thread_locked(wait_q);
if (thread != NULL) {
z_thread_return_value_set_with_data(thread,
swap_retval,
swap_data);
z_sched_ready_locked(thread);
ret = true;
}
}
return ret;
}
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_KERNEL_INCLUDE_KSCHED_H_ */