Commit graph zephyr/kernel/sched.c
Author SHA1 Message Date
Jinming Zhao
22d76caba5 kernel: sched: optimize scheduling IPIs for idle CPU wakeups
Add CONFIG_IPI_OPTIMIZE_IDLE to avoid waking multiple idle CPUs for a
single runnable thread. Select one active, affinity-compatible idle CPU
and record which thread its outstanding scheduling opportunity covers.

Track coverage with a reserved CPU bitmap and a per-CPU target array.
Each idle CPU and runnable thread can have at most one reservation. The
mapping represents coverage rather than binding: any CPU may select any
eligible runnable thread.

When a reserved CPU selects a different thread, transfer the selected
thread's existing reservation to the original target when possible.
Otherwise, issue a replacement IPI for the still-runnable target. Clear
reservations when their target leaves the run queue.

Apply the same optimization to MetaIRQ wakeups. The MetaIRQ can preempt
its local waker while one idle CPU is notified to run the displaced
preemptible work.

Signed-off-by: Jinming Zhao <jinmzhao@qti.qualcomm.com>
2026-09-01 11:03:59 +01:00
Peter Mitsis
a9b21fb354 kernel: userspace: Add z_waitq_head_locked()
Splits z_waitq_head() into two versions: z_waitq_head_locked() and
z_waitq_head(). When the scheduler's spinlock is known to be already
held, z_waitq_head_locked() should be used--otherwise, z_waitq_head()
is to be used.

However, this approach uncovered a path where the scheduler spinlock
could be recursively taken when a thread is aborted. To work around the
recursion (see k_thread_perms_all_clear), knowledge of the scheduler's
spinlock state must be passed to the lower layers for use in the
cleanup routines for message queues, stacks and timers.

Fixes #115756

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2026-08-21 16:17:54 +02:00
Peter Mitsis
36f1a8515a kernel: Update _sched_spinlock comment references
Updates the comments that reference _sched_spinlock to refer instead
to the scheduler's spinlock.

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2026-08-21 16:17:54 +02:00
Peter Mitsis
52301ff761 kernel: Add and use z_reschedule_locked()
As part of an effort to abstract away the use of _sched_spinlock in
the kernel, this commit introduces z_reschedule_locked(). Callers
must already have _sched_spinlock held.

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2026-08-21 16:17:54 +02:00
Peter Mitsis
285361130e kernel: Add and use z_swap_locked()
As part of an effort to abstract away the use of _sched_spinlock in
the kernel, this commit introduces z_swap_locked(). Callers must
already have _sched_spinlock held.

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2026-08-21 16:17:54 +02:00
Peter Mitsis
772e73c8aa kernel: Add private kernel header file kspinlock.h
The scheduler's spinlock was originally confined to sched.c. However,
as the kernel has evolved and functionality has been moved around,
not only have its references proliferated, but the header files that
reference it have become somewhat brittle. This commits adds a new
private header that will abstract away most of the scheduler's
spinlock references to help keep things cleaner and applies them to
the kernel.

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2026-08-21 16:17:54 +02:00
Daniel Leung
03e4556134 kernel: sched: use function aliases for simple wrappers
There are a few functions inside kernel/sched.c that are simply
one line wrappers to functions having the exact same signatures.
Unifying the naming would be ideal but the underlying functions
are being called by others under different kernel namespace.
So for now, the simplest is to use function aliasing and let
linker do its magic to avoid unnecessary trampoline. Saves
a few CPU cycles too.

Signed-off-by: Daniel Leung <daniel.leung@intel.com>
2026-08-02 11:43:47 -04:00
Tibor Kiss
4e5a1c1f99 kernel: sched: panic on blocking pend from ISR context
A blocking pend from ISR context is a programming error with no safe
recovery: it attempts to sleep whatever thread was interrupted and, on
CONFIG_SWAP_NONATOMIC, corrupts the scheduler (the qnode_dlist
double-link / NULL-deref traced in #111518).

Catch it at the funnel instead of per-API: every blocking primitive
(sem, mutex, msgq, queue, stack, pipe, poll, events, mailbox, mem_slab,
kheap, futex, ...) routes through z_pend_curr, and reaching it always
means a real block (callers handle K_NO_WAIT beforehand). So one check
there covers them all, with no per-API return-contract churn:

  if (arch_is_in_isr()) {
      __ASSERT(false, "blocking pend from ISR context");
      k_panic();
  }

__ASSERT(false, ...) gives the message and backtrace in debug builds;
k_panic() makes it fatal in every build, closing the production gap
(CONFIG_ASSERT defaults to n) that let the misuse ship and run for
hours. Located before _sched_spinlock is acquired, so no lock is held
on the panic path.

Also assert in add_to_waitq_locked() that a thread is not already on a
wait queue when added to a new one (pended_on == NULL) -- belt-and-
suspenders for a double-pend that reaches the scheduler despite the
above, e.g. a non-current thread re-pended without an intervening
unpend.

Suggested-by: Nicolas Pitre <npitre@baylibre.com>
Signed-off-by: Tibor Kiss <kiss.tibor@gmail.com>
2026-07-30 07:44:12 -05:00
Emil Hammarström
f6e012d1bc kernel: sched: Remove redundant timeout check
z_add_timeout returns without any effect if timeout is equal to K_FOREVER,
and thus z_add_thread_timeout does this by extension and there is no need
to guard its timeout parameter from a K_FOREVER.

Signed-off-by: Emil Hammarström <emil.a.hammarstrom@gmail.com>
2026-07-28 11:34:34 +01:00
Anas Nashif
9722f39203 kernel: fix doxygen warnings exposed by adding kernel/ to docs INPUT
Bringing the kernel/ sources into the Doxygen requirements-traceability
INPUT (for @satisfies links) surfaces a handful of latent documentation
warnings that would break the -W docs build. Fix them.

With these, the kernel/ tree is clean under doxygen 1.17.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-07-01 17:05:28 -04:00
Nicolas Pitre
d7059e67e8 kernel: sched: ready unpended threads unconditionally in *unpend_all
z_unpend_all() and unpend_all() skipped readying a woken thread when
z_try_abort_thread_timeout() returned -EAGAIN, on the assumption that
an in-flight timeout handler on another CPU would ready the thread
itself once the scheduler lock was dropped.

That assumption no longer holds. z_thread_timeout() was changed to bail
out when it observes the timeout has been marked superseded, and
z_try_abort_thread_timeout() sets exactly that superseded mark on the
-EAGAIN path. So on SMP, when a waiter's timeout fires on one CPU at
the same moment *unpend_all() processes it on another:

  - *unpend_all() unpends the thread and, seeing -EAGAIN, does NOT
    ready it.
  - the in-flight z_thread_timeout() finds the timeout superseded and
    bails, so it does NOT ready it either.

The thread ends up off every wait queue and on no run queue: orphaned,
with no remaining wake source. (k_heap / sys_mempool waiters with a
finite timeout are the reachable callers.)

Fix: ready the thread unconditionally after aborting its timeout, the
same idiom z_unpend_first_thread_locked() and kernel/events.c already
use. The abort still marks the in-flight handler superseded so it bails
and cannot double-wake, and ready_thread() is idempotent regardless.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-06-26 22:29:48 -04:00
Nicolas Pitre
ce23f1d734 kernel: sched: switch z_thread_timeout() to the superseded check
With every caller of z_abort_thread_timeout() now migrated to
z_try_abort_thread_timeout() (sched.c, thread.c, scheduler.c,
events.c, pipe.c) or to z_unpend_first_thread_locked() (sem.c,
mutex.c, mem_slab.c, stack.c, condvar.c, msg_q.c, queue.c, futex.c),
remove the inline wrapper (and !CONFIG_SYS_CLOCK_EXISTS stub) for
z_abort_thread_timeout().

z_thread_timeout() still needs a cancellation check, but it no longer
relies on the dticks=ANNOUNCING sentinel: switch it to
z_timeout_inflight_superseded(). The check is still required, and for
the same reason 1b8c7a3 added it. A concurrent waker on another CPU
(e.g. a sem give via z_unpend_first_thread_locked()) can unpend and
ready the thread while this timeout's handler is blocked on
_sched_spinlock; the thread may then run and re-pend on a different
object -- possibly with no timeout (K_FOREVER). The waker aborts this
timeout, which flags it superseded, and z_thread_timeout() bails on
that flag so it does not wake the thread from its new wait. The
atomic wake-under-_sched_spinlock closes the swap_retval window; the
superseded check closes this re-pend window.

The remaining TIMEOUT_DTICKS_ANNOUNCING sentinel and
z_is_timeout_handler_canceled() helper still have other users
(kernel/timer.c, kernel/poll.c, kernel/work.c) and are removed in the
later cleanup commit once those subsystems have been migrated as well.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-06-15 18:35:49 -04:00
Nicolas Pitre
387b387479 kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout()
Migrate scheduler-internal callers of z_abort_thread_timeout() to the
new z_try_abort_thread_timeout(). This covers the abort sites in
sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c.

The patterns used:

  z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout
  handler is in flight on another CPU, drop _sched_spinlock so the
  handler can run to completion and retry. This preserves 1b8c7a3's
  unpend+abort atomicity from the caller's perspective.

  halt_thread (sched.c) takes the caller's sched-lock key as a pointer
  so its direct abort on the dying thread can retry on -EAGAIN.
  Waiting for the handler is mandatory: a caller may free the thread's
  storage as soon as halt_thread() returns, and without waiting, the
  still-in-flight handler would later dereference freed memory.
  _THREAD_DEAD is set before the abort, so the handler bails via the
  killed check in z_sched_wake_thread_locked().

  For next_up() (the scheduler-context caller), the key is not cleanly
  available: K_SPINLOCK in z_get_next_switch_handle and do_swap's
  (void)k_spin_lock both discard it. halt_thread is invoked on
  _current with NULL key and no abort is performed -- _current is
  running, so its base.timeout cannot be linked. The gap is closed at
  the other end: z_thread_halt() spins on z_try_abort_thread_timeout()
  outside any lock after the halt-queue wait completes, before
  returning to the caller of k_thread_abort().

  z_unpend_all_locked / unpend_all (sched.c) skip the local
  ready_thread() on -EAGAIN and let the still-blocked handler ready
  the thread when _sched_spinlock drops. The threads being woken are
  not freed, so no UAF risk; end state is identical.

  z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and
  event_walk_op (events.c) perform the wake entirely under
  _sched_spinlock, so a (void) abort is race-free -- a racing in-flight
  handler is blocked on the same lock during the wake.

  copy_to_pending_readers (pipe.c) is restructured to also wake the
  reader under the scheduler lock instead of after it, so the
  return-value set, unpend, abort, and ready all happen atomically.

The dticks-cancel check in z_thread_timeout() is preserved for now
because other callers (sem.c, mutex.c, ... via z_unpend_first_thread())
still use z_abort_thread_timeout() and rely on it for race protection.
A follow-up commit migrates those, and a final commit drops the
cancel check and removes z_abort_thread_timeout() itself.

Add z_try_abort_thread_timeout() as an inline wrapper.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-06-15 18:35:49 -04:00
Jinming Zhao
d844a4a861 kernel: spinlock: add z_assert_can_swap() for context-switch validation
Replace the single in-place assertion in do_swap/z_swap_irqlock with a
z_assert_can_swap() helper performing three checks: held spinlock
pointer, hold count, and IRQ state.

To support the hold-count check, add per-CPU tracking arrays
(z_held_spinlock[] and z_held_spinlock_count[]).

Add z_spin_lock_transfer_owner() to update lock ownership after a
context switch, z_spinlock_abort_sentinel to exempt threads aborted by
a ztest expected-fault scenario, and z_spin_validate_reset() to reset
stale per-CPU lock tracking left by such an abort so subsequent tests
can proceed cleanly.

Signed-off-by: Jinming Zhao <jinmzhao@qti.qualcomm.com>
2026-06-15 18:35:34 -04:00
Peter Mitsis
1b8c7a3038 kernel: thread timeout: Fix race condition
This fixes a subtle race condition in the thread timeout expiration
handler z_thread_timeout(). There was a small window of opportunity
between when sys_clock_announce() unlocked interrupts and that the
handler re-locked them that one or more higher priority interrupts
(or threads running on another CPU if in an SMP environment) could
abort the thread's timeout.

The fix has two parts. Part one ensures that _sched_spinlock is held
in every location before a thread's time can be canceled. Of the
various locations, only z_unpend_thread() was found to need updating.
Part two updates the timeout handler z_thread_timeout() to bail early
if the thread's timeout has been found to be canceled (or re-used)
during that aforementioned window.

Fixes #106653

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2026-05-11 04:04:02 +02:00
Nicolas Pitre
01b3821fd9 kernel: spinlock: provide z_spin_is_locked() for UP builds
Extend z_spin_is_locked() to non-SMP configurations so assertions like
the one in z_unpend_all_locked() can validate lock ownership in UP
builds too. In UP a spinlock reduces to an IRQ lock, so the check
samples the current IRQ state via arch_irq_lock() / arch_irq_unlock().

Drop the now-unnecessary CONFIG_SMP guard around the sched spinlock
assertion in z_unpend_all_locked().

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-01 11:18:04 -05:00
Anas Nashif
26e88cee76 toolchain: iar: suppress Go004 via ALWAYS_INLINE override
IAR emits diagnostic Go004 ("function cannot be inlined") for
every ALWAYS_INLINE function when optimisation is disabled, e.g.
in debug builds.  The previous workaround wrapped each affected
function in per-function preprocessor guard pairs:

  #ifdef IAR_SUPPRESS_ALWAYS_INLINE_WARNING_FLAG
  TOOLCHAIN_DISABLE_WARNING(TOOLCHAIN_WARNING_ALWAYS_INLINE)
  #endif
  static ALWAYS_INLINE void foo(...) { ... }
  #ifdef IAR_SUPPRESS_ALWAYS_INLINE_WARNING_FLAG
  TOOLCHAIN_ENABLE_WARNING(TOOLCHAIN_WARNING_ALWAYS_INLINE)
  #endif

This pattern is highly intrusive, scatters toolchain-specific
knowledge across generic source files, and requires a guard pair
every time a new ALWAYS_INLINE function is added for IAR.

Replace it with a single override of ALWAYS_INLINE inside
iccarm.h, using the C99 _Pragma operator to embed the diagnostic
suppression in the macro itself.

Assisted-by: GitHub Copilot:claude-sonnet-4.6
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-29 10:00:10 +02:00
Anas Nashif
7edd8834f6 kernel: sched.c: remove useless return on void function
Remove useless return on void function.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
d94fbad890 kernel: sched: rename z_reset_time_slice() to z_time_slice_reset()
Align the function name with the z_<subsystem>_<verb> convention
used elsewhere in the kernel.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
d24cfd96f5 kernel: sched: move public scheduler API to scheduler.c/scheduler.h
Migrate scheduler API implementations (k_sched_lock/unlock,
z_reschedule, z_yield_current, etc.) and their private declarations
from ksched.h/sched.c into scheduler.c and scheduler.h.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
05cbc7c98a kernel: sched: extract timeslice declarations to timeslicing.h
Move time-slice related declarations from ksched.h into the
dedicated kernel/include/timeslicing.h header.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
28f157beae kernel: sched: move core schedule/deschedule functions to scheduler.c
Migrate z_add_thread_to_ready_q(), z_remove_thread_from_ready_q(),
and related helpers from sched.c to scheduler.c.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
bde2ba8901 kernel: sched: simplify z_sched_init using run_q.h helpers
Move z_sched_init to scheduler.c and somplify implementation getting rid
of single use init_ready_q.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
78993d7020 kernel: sched: reorder z_unready_thread before its callers in sched.c
Move z_unready_thread next to related functions.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
cf3f8331e5 kernel: sched: group z_ready_thread and z_unready_thread in sched.c
Reorder so that z_ready_thread and z_unready_thread are adjacent,
improving code locality for related queue operations.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
a564d82a04 kernel: sched: extract run-queue helpers to run_q.h
Move run-queue management functions (add/remove/peek thread,
choose_next_thread) from sched.c into the new
kernel/include/run_q.h header.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
4b525273bf kernel: sched: simplify thread_runq()
Simplify code and make it more readable.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
fc11a9166e kernel: sched: extract meta-IRQ handling to metairq.h
Move meta-IRQ (highest-priority cooperative queue) scheduling
functions from sched.c into a new kernel/include/metairq.h header
to reduce sched.c size and group related logic.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
2ea5924943 kernel: k_yield: move code to thread.c
Move k_yield() from sched.c to thread.c alongside other thread
lifecycle calls.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
222eba03d7 kernel: sleep: move sleep code into own file
Reduce complexity of sched.c by encapsulating sleep handling code
(k_sleep, k_usleep, k_msleep) into its own file.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
908169d9df kernel: deadline: move deadline handling to own file
Move deadline scheduling to deadline.c, reducing complexity and
clutter in sched.c.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Anas Nashif
0659dc18b3 kernel: sched: move thread lifecycle calls to thread.c
Relocate k_thread_start(), k_thread_abort(), k_thread_suspend(), and
k_thread_resume() from sched.c to thread.c alongside related thread
lifecycle code.

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2026-04-24 15:39:20 -04:00
Nicolas Pitre
184b5a3804 kernel: assert scheduler lock is held in z_unpend_all_locked()
Add a runtime assertion in z_unpend_all_locked() to verify that
_sched_spinlock is actually held by the caller. This catches misuse
early given the function call depth involved.

Extend the availability of z_spin_is_locked() from CONFIG_SMP &&
CONFIG_TEST to also include CONFIG_ASSERT, so the check can be
used in __ASSERT() outside of test builds.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-04-07 08:40:28 -05:00
Nicolas Pitre
9cef0da05c kernel: avoid recursive scheduler lock in k_heap_free path
When halt_thread() calls k_thread_perms_all_clear() under
_sched_spinlock, the permission cleanup can trigger k_free() on
dynamic objects. k_heap_free() then calls z_unpend_all() which
attempts to take _sched_spinlock again, causing a recursive lock.

Fix this by introducing k_heap_free_sched_locked() and
k_free_sched_locked() variants that use z_unpend_all_locked()
to operate on the wait queue without re-acquiring the scheduler
lock. The existing z_unpend_all() becomes a wrapper that takes
the lock and delegates to z_unpend_all_locked().

unref_check() gains a sched_locked parameter: the abort path
(clear_perms_cb) passes true to use the locked free variant,
while k_thread_perms_clear() passes false for the normal path.

Fixes #106659

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-04-07 08:40:28 -05:00
Andrew Bresticker
1666066082 kernel/sched: fix race in consuming self-directed IPIs
Move signal_pending_ipi() inside the K_SPINLOCK block in
z_get_next_switch_handle(). Calling it after the lock release creates a
window where a CPU can consume its own pending IPI bit via atomic_clear
in signal_pending_ipi(), then silently drop it in
arch_sched_directed_ipi() which skips the calling CPU (i == id).

In configurations where secondary CPUs have a single pinned thread and
take no timer or external interrupts, this can lead to a permanent hang:
the idle CPU can only be woken by IPIs, but no IPIs are pending and no
timeslicing IPIs will be generated since the idle thread is not sliceable.
This was reproduced when running under QEMU with the following sequence
of events observed:

  CPU 0                                  CPU 1
  ─────                                  ─────

                                         Thread calls k_poll(K_MSEC(1))
                                           z_pend_curr():
                                             mark thread PENDING
                                             z_add_timeout(1ms)
                                             do_swap() to idle thread
                                         WFI

  Timer tick fires
  sys_clock_announce():
    slice_timeout(cpu1):
      flag_ipi(BIT(1))
    signal_pending_ipi():
      MSIP[cpu1] = 1

                                         CPU1 wakes from WFI
                                         z_get_next_switch_handle():
                                           acquire _sched_spinlock
                                           next_up() → idle
                                             (thread still PENDING,
                                              timeout hasn't fired yet)
                                           release _sched_spinlock

  Timer tick fires
  sys_clock_announce():
    z_thread_timeout(thread):
      z_unpend_thread(thread)
      z_ready_thread(thread):
        flag_ipi(BIT(1))

                                         signal_pending_ipi():
                                           atomic_clear(pending_ipi)
                                             returns BIT(1)
                                           arch_sched_directed_ipi(BIT(1))
                                             skips self, IPI silently lost
                                         return to idle thread
                                           WFI
                                             thread still on ready queue

Such an interleaving of events is, of course, likely only reproducible in
practice in virtualized environments where (v)CPUs can be descheduled.

With signal_pending_ipi() inside the lock, next_up() and the IPI
dispatch are atomic. Either the concurrent flag_ipi lands before the
lock is acquired (and next_up sees the thread), or it lands after the
lock is released (and the caller dispatches the IPI). There is no
window where a CPU can consume its own bit for a thread it hasn't seen.

Similar races exist in reschedule() and z_reschedule_irqlock() as well.
Although they won't cause the same permanent hang described above, it
can result in unnecessary rescheduling latency. Fix reschedule(), and
add a TODO to z_reschedule_irqlock(); it doesn't not currently take
the sched spinlock.

Signed-off-by: Andrew Bresticker <abrestic@meta.com>
2026-04-04 10:57:11 -05:00
Tharaka Jayasena
f6141e5ccf doc: kernel: fix incorrect Doxygen @retval/@return usage
Fix several incorrect uses of the Doxygen `@retval` and @return command in
kernel sources.

- Convert @return to structured @retval where functions return
  discrete values.
- Replace incorrect @retval usage with @return for non-discrete
  return types.

Signed-off-by: Tharaka Jayasena <9dmpires2k17.tuj@gmail.com>
2026-03-17 18:24:33 -04:00
Cheng-Yang Chou
d67038c7e7 kernel: fix z_tick_sleep unsigned comparison when !CONFIG_TIMEOUT_64BIT
When CONFIG_TIMEOUT_64BIT is not set, k_ticks_t is uint32_t. The previous
code cast left_ticks through int32_t but then stored the result back in
k_ticks_t (uint32_t), losing the sign. The subsequent ticks > 0 check was
therefore an unsigned comparison, causing a past-due wakeup (where the
subtraction wraps to a large uint32_t) to be misread as a large positive
remainder and propagated up through k_sleep() as INT_MAX ms.

Fix by retaining the signed intermediate and comparing it directly as
int32_t so negative remainders (past-due) correctly fall through to
return 0.

Signed-off-by: Cheng-Yang Chou <yphbchou0911@gmail.com>
2026-03-17 18:18:28 -04:00
Andy Ross
a04a30c6d1 kernel/sched: Move reschedule under lock in k_sched_unlock()
This function was a little clumsy, taking the scheduler lock,
releasing it, and then calling z_reschedule_unlocked() instead of the
normal locked variant of reschedule.  Don't take the lock twice.

Mostly this is a code size and hygiene win.  Obviously the sched lock
is not normally a performance path, but I happened to have picked this
API for my own microbenchmark in tests/benchmarks/swap and so noticed
the double-lock while staring at disassembly.

Signed-off-by: Andy Ross <andyross@google.com>
2026-03-10 17:24:10 +01:00
Andy Ross
2bbcece6ee kernel/sched: Refactor reschedule to permit better code generation
z_reschedule() is the basic kernel entry point for context switch,
wrapping z_swap(), and thence arch_switch().  It's currently defined
as a first class function for entry from other files in the kernel and
elsewhere (e.g. IPC library code).

But in practice it's actually a very thin wrapper without a lot of
logic of its own, and the context switch layers of some of the more
obnoxiously clever architectures are designed to interoperate with the
compiler's own spill/fill logic to avoid double saving.  And with a
small z_reschedule() there's not a lot to work with.

Make reschedule() an inlinable static, so the compiler has more
options.

Signed-off-by: Andy Ross <andyross@google.com>
2026-03-10 17:24:10 +01:00
Andy Ross
d535d17cbc kernel: Minor optimizations to z_swap()
Pick some low hanging fruit on non-SMP code paths:

+ The scheduler spinlock is always taken, but as we're already in an
  irqlocked state that's a noop.  But the optmizer can't tell, because
  arch_irq_lock() involves an asm block it can't see inside.  Elide
  the call when possible.

+ The z_swap_next_thread() function evaluates to just a single load of
  _kernel.ready_q.cache when !SMP, but wasn't being inlined because of
  function location.  Move that test up into do_swap() so it's always
  done correctly.

Signed-off-by: Andy Ross <andyross@google.com>
2026-03-10 17:24:10 +01:00
Mathieu Choplain
5a0f73f045 kernel: events: wake threads atomically using waitq post-walk callback
When CONFIG_WAITQ_SCALABLE=y, wake up all threads from a post-waitq-walk
callback which is invoked while the scheduler spinlock is still held. This
solves the race condition that was worked around via `no_wake_in_timeout`
flag in k_thread and `is_timeout` parameter of z_sched_wake_thread_locked()
which can now both be dropped.

Signed-off-by: Mathieu Choplain <mathieu.choplain-ext@st.com>
2026-02-18 14:43:10 +00:00
Mathieu Choplain
73feef8b20 kernel: sched: add post-walk callback argument to z_sched_waitq_walk()
Modify z_sched_waitq_walk() to accept an optional callback invoked after
the walk while still holding the scheduler spinlock. This can be used to
perform post-walk operations "atomically". Update all callers to work with
this new function signature.

While at it, create dedicated (private) typedefs for the callbacks and
clean up/improve the routine and callbacks' documentation.

Signed-off-by: Mathieu Choplain <mathieu.choplain-ext@st.com>
2026-02-18 14:43:10 +00:00
Mathieu Choplain
e725225489 kernel: sched: perform safe waitq walk inside z_sched_waitq_walk()
z_sched_waitq_walk() used _WAIT_Q_FOR_EACH, a wrapper around the
"unsafe" SYS_DLIST_FOR_EACH_CONTAINER which does not allow detaching
elements from the list during the walk. As a result, attempting to
detach threads from the wait queue as part of the callback provided
to z_sched_waitq_walk() would result in breakage.

Introduce new _WAIT_Q_FOR_EACH_SAFE macro as wrapper around the "safe"
SYS_DLIST_FOR_EACH_CONTAINER_SAFE which allows detaching nodes from
the list during the walk, and use it inside z_sched_waitq_walk().
While at it:
- add documentation on the _WAIT_Q_FOR_EACH macro, including a warning
  about detaching elements as part of the loop not being allowed
- add note to documentation of z_sched_waitq_walk() indicating that
  the callback can safely remove the thread from wait queue as this
  will no longer break the FOR_EACH loop
- add _WAIT_Q_FOR_EACH_SAFE to the list of ForEachMacros in .clang-format

NOTE: this new "safe removal inside callback" behavior is only available
when CONFIG_WAITQ_SCALABLE=n. When the option is 'y', red-black trees are
used instead of doubly-linked lists which prevent mutation of the list
while it is being walked. This limitation is explicitly documented.

Signed-off-by: Mathieu Choplain <mathieu.choplain-ext@st.com>
2026-02-18 14:43:10 +00:00
Mathieu Choplain
eac6c7cb24 kernel: sched: don't acquire scheduler spinlock in z_sched_wake_thread()
Don't acquire the _sched_spinlock in z_sched_wake_thread(). This allows
calling the function from callbacks which already own the spinlock. The
function is renamed to z_sched_wake_thread_locked() to reflect this new
behavior, and all existing callers are updated to ensure they hold the
_sched_spinlock as is now required.

Signed-off-by: Mathieu Choplain <mathieu.choplain-ext@st.com>
2026-02-18 14:43:10 +00:00
Yong Cong Sin
e843931194 kernel: check if interrupt is disabled in k_can_yield
`k_yield()` can't be called when interrupt is disabled, update
`k_can_yield()` to reflect that.

Signed-off-by: Yong Cong Sin <ycsin@meta.com>
Signed-off-by: Yong Cong Sin <yongcong.sin@gmail.com>
2026-02-16 00:13:38 +00:00
Benjamin Cabé
a87520bd04 kernel: use proper essential type to initialize boolean variables
As per Zephyr coding guideline #59, "operands shall not be of an
inappropriate essential type". This makes sure boolean variables are
initialized with true/false, not 1/0.

Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
2026-02-04 13:52:38 +01:00
Peter Mitsis
669a8d0704 kernel: O(1) search for threads among CPUs
Instead of performing a linear search to determine if a given
thread is running on another CPU, or if it is marked as being
preempted by a metaIRQ on any CPU do this in O(1) time.

On SMP systems, Zephyr already tracks the CPU on which a thread
executes (or lasted executed). This information is leveraged to
do the search in O(1) time.

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2026-01-08 17:34:14 -06:00
Thinh Le Cong
15cdf90bee include: zephyr: toolchain: suppress Go004 warning for inline functions
IAR compiler may emit Error[Go004]: Could not inline function
when handling functions marked as always_inline or inline=forced,
especially in complex kernel code

Signed-off-by: Thinh Le Cong <thinh.le.xr@bp.renesas.com>
2026-01-08 12:00:29 +00:00
Peter Mitsis
3a8c9797ca kernel: Re-instate metaIRQ z_is_thread_ready() check
Re-instate a z_is_thread_ready() check on the preempted metaIRQ
thread before selecting it as the preferred next thread to
schedule. This code exists because of a corner case where it is
possible for the thread that was recorded as being pre-empted
by a meta-IRQ thread can be marked as not 'ready to run' when
the meta-IRQ thread(s) complete.

Such a scenario may occur if an interrupt ...
  1. suspends the interrupted thread, then
  2. readies a meta-IRQ thread, then
  3. exits
The resulting reschedule can result in the suspended interrupted
thread being recorded as being interrupted by a meta-IRQ thread.
There may be other scenarios too.

Fixes #101296

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2025-12-22 22:33:18 +01:00
Peter Mitsis
5dd36854fd kernel: Update clearing metairq_preempted record
If the thread being aborted or suspended was preempted by a metaIRQ
thread then clear the metairq_preempted record. In the case of
aborting a thread, this prevents a re-used thread from being
mistaken for a preempted thread. Furthermore, it removes the need
to test the recorded thread for readiness in next_up().

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2025-12-10 10:32:50 +00:00