From c9e3e0d9569b05c9cad29e0e18be1bcce4d4be9a Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 15 Mar 2022 22:36:20 -0400 Subject: [PATCH] sched: formalize the passing of NULL to z_get_next_switch_handle() This is an attempt at formally distinguishing and supporting the case described in 40795 where an architecture doesn't preserve/restore the complete thread state upon entering/exiting interrupt exception state. This is mainly about promoting the current behavior from the accepted workaround to a formal API specification. This workaround is currently used on ARM64 but RISC-V requires it too. Signed-off-by: Nicolas Pitre --- doc/reference/kernel/smp/smp.rst | 26 ++++++++++++++++++++++---- kernel/sched.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/doc/reference/kernel/smp/smp.rst b/doc/reference/kernel/smp/smp.rst index df6da436f24..395d1874f58 100644 --- a/doc/reference/kernel/smp/smp.rst +++ b/doc/reference/kernel/smp/smp.rst @@ -283,15 +283,33 @@ exactly two arguments. The first is an opaque (architecture defined) handle to the context to which it should switch, and the second is a pointer to such a handle into which it should store the handle resulting from the thread that is being switched out. - The kernel then implements a portable :c:func:`z_swap` implementation on top of this primitive which includes the relevant scheduler logic in a location where the architecture doesn't need to understand it. + Similarly, on interrupt exit, switch-based architectures are expected to call :c:func:`z_get_next_switch_handle` to retrieve the next thread to -run from the scheduler, passing in an "interrupted" handle reflecting -the same opaque type used by switch, which the kernel will then save -in the interrupted thread struct. +run from the scheduler. The argument to :c:func:`z_get_next_switch_handle` +is either the interrupted thread's "handle" reflecting the same opaque type +used by :c:func:`arch_switch`, or NULL if that thread cannot be released +to the scheduler just yet. The choice between a handle value or NULL +depends on the way CPU interrupt mode is implemented. + +Architectures with a large CPU register file would typically preserve only +the caller-saved registers on the current thread's stack when interrupted +in order to minimize interrupt latency, and preserve the callee-saved +registers only when :c:func:`arch_switch` is called to minimize context +switching latency. Such architectures must use NULL as the argument to +:c:func:`z_get_next_switch_handle` to determine if there is a new thread +to schedule, and follow through with their own :c:func:`arch_switch` or +derrivative if so, or directly leave interrupt mode otherwise. +In the former case it is up to that switch code to store the handle +resulting from the thread that is being switched out in that thread's +"switch_handle" field after its context has fully been saved. + +Architectures whose entry in interrupt mode already preserves the entire +thread state may pass that thread's handle directly to +:c:func:`z_get_next_switch_handle` and be done in one step. Note that while SMP requires :kconfig:option:`CONFIG_USE_SWITCH`, the reverse is not true. A uniprocessor architecture built with :kconfig:option:`CONFIG_SMP` set to No might diff --git a/kernel/sched.c b/kernel/sched.c index b94ae6b349c..ef70f825fbf 100644 --- a/kernel/sched.c +++ b/kernel/sched.c @@ -984,6 +984,37 @@ static inline void set_current(struct k_thread *new_thread) _current_cpu->current = new_thread; } +/** + * @brief Determine next thread to execute upon completion of an interrupt + * + * Thread preemption is performed by context switching after the completion + * of a non-recursed interrupt. This function determines which thread to + * switch to if any. This function accepts as @p interrupted either: + * + * - The handle for the interrupted thread in which case the thread's context + * must already be fully saved and ready to be picked up by a different CPU. + * + * - NULL if more work is required to fully save the thread's state after + * it is known that a new thread is to be scheduled. It is up to the caller + * to store the handle resulting from the thread that is being switched out + * in that thread's "switch_handle" field after its + * context has fully been saved, following the same requirements as with + * the @ref arch_switch() function. + * + * If a new thread needs to be scheduled then its handle is returned. + * Otherwise the same value provided as @p interrupted is returned back. + * Those handles are the same opaque types used by the @ref arch_switch() + * function. + * + * @warning + * The @ref _current value may have changed after this call and not refer + * to the interrupted thread anymore. It might be necessary to make a local + * copy before calling this function. + * + * @param interrupted Handle for the thread that was interrupted or NULL. + * @retval Handle for the next thread to execute, or @p interrupted when + * no new thread is to be scheduled. + */ void *z_get_next_switch_handle(void *interrupted) { z_check_stack_sentinel();