zephyr/arch/arm64/core/switch.S
Andy Ross b89e427bd6 kernel/sched: Rename/redocument wait_for_switch() -> z_sched_switch_spin()
This trick turns out also to be needed by the abort/join code.
Promote it to a more formal-looking internal API and clean up the
documentation to (hopefully) clarify the exact behavior and better
explain the need.

This is one of the more... enchanted bits of the scheduler, and while
the trick is IMHO pretty clean, it remains a big SMP footgun.

Signed-off-by: Andy Ross <andyross@google.com>
2023-05-26 17:09:35 -04:00

219 lines
5.5 KiB
ArmAsm

/*
* Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Thread context switching for ARM64 Cortex-A (AArch64)
*
* This module implements the routines necessary for thread context switching
* on ARM64 Cortex-A (AArch64)
*/
#include <zephyr/toolchain.h>
#include <zephyr/linker/sections.h>
#include <offsets_short.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/syscall.h>
#include "macro_priv.inc"
_ASM_FILE_PROLOGUE
/*
* Routine to handle context switches
*
* This function is directly called either by _isr_wrapper() in case of
* preemption, or arch_switch() in case of cooperative switching.
*
* void z_arm64_context_switch(struct k_thread *new, struct k_thread *old);
*/
GTEXT(z_arm64_context_switch)
SECTION_FUNC(TEXT, z_arm64_context_switch)
#ifndef CONFIG_ARM64_SAFE_EXCEPTION_STACK
/* Save the current SP_EL0 */
mrs x4, sp_el0
#endif
stp x19, x20, [x1, #_thread_offset_to_callee_saved_x19_x20]
stp x21, x22, [x1, #_thread_offset_to_callee_saved_x21_x22]
stp x23, x24, [x1, #_thread_offset_to_callee_saved_x23_x24]
stp x25, x26, [x1, #_thread_offset_to_callee_saved_x25_x26]
stp x27, x28, [x1, #_thread_offset_to_callee_saved_x27_x28]
#ifndef CONFIG_ARM64_SAFE_EXCEPTION_STACK
stp x29, x4, [x1, #_thread_offset_to_callee_saved_x29_sp_el0]
#else
str x29, [x1, #_thread_offset_to_callee_saved_x29_sp_el0]
#endif
/* Save the current SP_ELx and return address */
mov x4, sp
stp x4, lr, [x1, #_thread_offset_to_callee_saved_sp_elx_lr]
/* save current thread's exception depth */
mrs x4, tpidrro_el0
lsr x2, x4, #TPIDRROEL0_EXC_SHIFT
strb w2, [x1, #_thread_offset_to_exception_depth]
/* retrieve next thread's exception depth */
ldrb w2, [x0, #_thread_offset_to_exception_depth]
bic x4, x4, #TPIDRROEL0_EXC_DEPTH
orr x4, x4, x2, lsl #TPIDRROEL0_EXC_SHIFT
msr tpidrro_el0, x4
#ifdef CONFIG_FPU_SHARING
/*
* Do this after tpidrro_el0 is updated with the new exception
* depth value, and before old->switch_handle is updated (making
* it available for grab by another CPU) as we still use its stack.
*/
stp x0, x1, [sp, #-16]!
bl z_arm64_fpu_thread_context_switch
ldp x0, x1, [sp], #16
#endif
/* save old thread into switch handle which is required by
* z_sched_switch_spin()
*/
str x1, [x1, #___thread_t_switch_handle_OFFSET]
#ifdef CONFIG_THREAD_LOCAL_STORAGE
/* Grab the TLS pointer */
ldr x2, [x0, #_thread_offset_to_tls]
/* Store in the "Thread ID" register.
* This register is used as a base pointer to all
* thread variables with offsets added by toolchain.
*/
msr tpidr_el0, x2
#endif
ldp x19, x20, [x0, #_thread_offset_to_callee_saved_x19_x20]
ldp x21, x22, [x0, #_thread_offset_to_callee_saved_x21_x22]
ldp x23, x24, [x0, #_thread_offset_to_callee_saved_x23_x24]
ldp x25, x26, [x0, #_thread_offset_to_callee_saved_x25_x26]
ldp x27, x28, [x0, #_thread_offset_to_callee_saved_x27_x28]
#ifndef CONFIG_ARM64_SAFE_EXCEPTION_STACK
ldp x29, x4, [x0, #_thread_offset_to_callee_saved_x29_sp_el0]
/* Restore SP_EL0 */
msr sp_el0, x4
#else
ldr x29, [x0, #_thread_offset_to_callee_saved_x29_sp_el0]
#endif
/* Restore SP_EL1 and return address */
ldp x4, lr, [x0, #_thread_offset_to_callee_saved_sp_elx_lr]
mov sp, x4
#if defined(CONFIG_ARM64_SAFE_EXCEPTION_STACK)
/* arch_curr_cpu()->arch.current_stack_limit = thread->arch.stack_limit */
get_cpu x4
ldr x2, [x0, #_thread_offset_to_stack_limit]
str x2, [x4, #_cpu_offset_to_current_stack_limit]
#endif
#ifdef CONFIG_USERSPACE
str lr, [sp, #-16]!
bl z_arm64_swap_mem_domains
ldr lr, [sp], #16
#endif
#ifdef CONFIG_INSTRUMENT_THREAD_SWITCHING
str lr, [sp, #-16]!
bl z_thread_mark_switched_in
ldr lr, [sp], #16
#endif
/* Return to arch_switch() or _isr_wrapper() */
ret
/*
* Synchronous exceptions handler
*
* The service call (SVC) is used in the following occasions:
* - Cooperative context switching
* - IRQ offloading
*/
GTEXT(z_arm64_sync_exc)
SECTION_FUNC(TEXT, z_arm64_sync_exc)
mrs x0, esr_el1
lsr x1, x0, #26
#ifdef CONFIG_FPU_SHARING
cmp x1, #0x07 /*Access to SIMD or floating-point */
bne 1f
mov x0, sp
bl z_arm64_fpu_trap
b z_arm64_exit_exc_fpu_done
1:
#endif
cmp x1, #0x15 /* 0x15 = SVC */
bne inv
/* Demux the SVC call */
and x1, x0, #0xff
cmp x1, #_SVC_CALL_RUNTIME_EXCEPT
beq oops
#ifdef CONFIG_USERSPACE
cmp x1, #_SVC_CALL_SYSTEM_CALL
beq z_arm64_do_syscall
#endif
#ifdef CONFIG_IRQ_OFFLOAD
cmp x1, #_SVC_CALL_IRQ_OFFLOAD
beq offload
b inv
offload:
/*
* Retrieve provided routine and argument from the stack.
* Routine pointer is in saved x0, argument in saved x1
* so we load them with x1/x0 (reversed).
*/
ldp x1, x0, [sp, ___esf_t_x0_x1_OFFSET]
/* ++_current_cpu->nested to be checked by arch_is_in_isr() */
get_cpu x2
ldr w3, [x2, #___cpu_t_nested_OFFSET]
add w4, w3, #1
str w4, [x2, #___cpu_t_nested_OFFSET]
/* If not nested: switch to IRQ stack and save current sp on it. */
cbnz w3, 1f
ldr x3, [x2, #___cpu_t_irq_stack_OFFSET]
mov x4, sp
mov sp, x3
str x4, [sp, #-16]!
#if defined(CONFIG_ARM64_SAFE_EXCEPTION_STACK)
/* update the stack limit with IRQ stack limit */
sub x3, x3, #CONFIG_ISR_STACK_SIZE
str x3, [x2, #_cpu_offset_to_current_stack_limit]
#endif
1:
/* Execute provided routine (argument is in x0 already). */
blr x1
/* Exit through regular IRQ exit path */
b z_arm64_irq_done
#endif
b inv
oops:
mov x0, sp
b z_arm64_do_kernel_oops
inv:
mov x0, #0 /* K_ERR_CPU_EXCEPTION */
mov x1, sp
bl z_arm64_fatal_error
/* Return here only in case of recoverable error */
b z_arm64_exit_exc