zephyr/kernel/idle.c
Andy Ross 0ca7150f90 kernel/idle: Fix !SCHED_IPI_SUPPORTED
The requirement for k_yield() to handle "yielding" in the idle thread
was removed a while back, but it missed a spot where we'd try to yield
in the fallback loop on bringup platforms that lack an IPI.  This now
crashes, because yield now unconditionally tries to reschedule the
current thread, which doesn't work for idle threads that don't live in
the run queue.

Just make it a busy loop calling swap(), even simpler.

Fixes #50119

Signed-off-by: Andy Ross <andyross@google.com>
2022-09-19 09:19:02 +02:00

107 lines
3.1 KiB
C

/*
* Copyright (c) 2016 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/toolchain.h>
#include <zephyr/linker/sections.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/wait_q.h>
#include <zephyr/pm/pm.h>
#include <stdbool.h>
#include <zephyr/logging/log.h>
#include <ksched.h>
#include <kswap.h>
LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL);
void z_pm_save_idle_exit(void)
{
#ifdef CONFIG_PM
/* Some CPU low power states require notification at the ISR
* to allow any operations that needs to be done before kernel
* switches task or processes nested interrupts.
* This can be simply ignored if not required.
*/
pm_system_resume();
#endif /* CONFIG_PM */
sys_clock_idle_exit();
}
void idle(void *unused1, void *unused2, void *unused3)
{
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
ARG_UNUSED(unused3);
__ASSERT_NO_MSG(_current->base.prio >= 0);
while (true) {
/* SMP systems without a working IPI can't actual
* enter an idle state, because they can't be notified
* of scheduler changes (i.e. threads they should
* run). They just spin instead, with a minimal
* relaxation loop to prevent hammering the scheduler
* lock and/or timer driver. This is intended as a
* fallback configuration for new platform bringup.
*/
if (IS_ENABLED(CONFIG_SMP) &&
!IS_ENABLED(CONFIG_SCHED_IPI_SUPPORTED)) {
for (volatile int i = 0; i < 100000; i++)
;
z_swap_unlocked();
}
/* Note weird API: k_cpu_idle() is called with local
* CPU interrupts masked, and returns with them
* unmasked. It does not take a spinlock or other
* higher level construct.
*/
(void) arch_irq_lock();
#ifdef CONFIG_PM
_kernel.idle = z_get_next_timeout_expiry();
/*
* Call the suspend hook function of the soc interface
* to allow entry into a low power state. The function
* returns false if low power state was not entered, in
* which case, kernel does normal idle processing.
*
* This function is entered with interrupts disabled.
* If a low power state was entered, then the hook
* function should enable inerrupts before exiting.
* This is because the kernel does not do its own idle
* processing in those cases i.e. skips k_cpu_idle().
* The kernel's idle processing re-enables interrupts
* which is essential for the kernel's scheduling
* logic.
*/
if (k_is_pre_kernel() || !pm_system_suspend(_kernel.idle)) {
k_cpu_idle();
}
#else
k_cpu_idle();
#endif
#if !defined(CONFIG_PREEMPT_ENABLED)
# if !defined(CONFIG_USE_SWITCH) || defined(CONFIG_SPARC)
/* A legacy mess: the idle thread is by definition
* preemptible as far as the modern scheduler is
* concerned, but older platforms use
* CONFIG_PREEMPT_ENABLED=n as an optimization hint
* that interrupt exit always returns to the
* interrupted context. So in that setup we need to
* explicitly yield in the idle thread otherwise
* nothing else will run once it starts.
*/
if (_kernel.ready_q.cache != _current) {
z_swap_unlocked();
}
# endif
#endif
}
}