blob: 9638c870e22026ee24ac2a252d320aa057f235c2 [file] [log] [blame]
/*
* Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief New thread creation for ARM64 Cortex-A
*
* Core thread related primitives for the ARM64 Cortex-A
*/
#include <kernel.h>
#include <ksched.h>
#include <wait_q.h>
#include <arch/cpu.h>
/*
* Note about stack usage:
*
* [ see also comments in include/arch/arm64/thread_stack.h ]
*
* - kernel threads are running in EL1 using SP_EL1 as stack pointer during
* normal execution and during exceptions. They are by definition already
* running in a privileged stack that is their own.
*
* - user threads are running in EL0 using SP_EL0 as stack pointer during
* normal execution. When at exception is taken or a syscall is called the
* stack pointer switches to SP_EL1 and the execution starts using the
* privileged portion of the user stack without touching SP_EL0. This portion
* is marked as not user accessible in the MMU.
*
* Kernel threads:
*
* +---------------+ <- stack_ptr
* E | ESF |
* L |<<<<<<<<<<<<<<<| <- SP_EL1
* 1 | |
* +---------------+
*
* User threads:
*
* +---------------+ <- stack_ptr
* E | |
* L |<<<<<<<<<<<<<<<| <- SP_EL0
* 0 | |
* +---------------+ ..............|
* E | ESF | | Privileged portion of the stack
* L +>>>>>>>>>>>>>>>+ <- SP_EL1 |_ used during exceptions and syscalls
* 1 | | | of size ARCH_THREAD_STACK_RESERVED
* +---------------+ <- stack_obj..|
*
* When a new user thread is created or when a kernel thread switches to user
* mode the initial ESF is relocated to the privileged portion of the stack
* and the values of stack_ptr, SP_EL0 and SP_EL1 are correctly reset when
* going through arch_user_mode_enter() and z_arm64_userspace_enter()
*
*/
#ifdef CONFIG_USERSPACE
static bool is_user(struct k_thread *thread)
{
return (thread->base.user_options & K_USER) != 0;
}
#endif
void arch_new_thread(struct k_thread *thread, k_thread_stack_t *stack,
char *stack_ptr, k_thread_entry_t entry,
void *p1, void *p2, void *p3)
{
z_arch_esf_t *pInitCtx;
/*
* The ESF is now hosted at the top of the stack. For user threads this
* is also fine because at this stage they are still running in EL1.
* The context will be relocated by arch_user_mode_enter() before
* dropping into EL0.
*/
pInitCtx = Z_STACK_PTR_TO_FRAME(struct __esf, stack_ptr);
pInitCtx->x0 = (uint64_t)entry;
pInitCtx->x1 = (uint64_t)p1;
pInitCtx->x2 = (uint64_t)p2;
pInitCtx->x3 = (uint64_t)p3;
/*
* - ELR_ELn: to be used by eret in z_arm64_exit_exc() to return
* to z_thread_entry() with entry in x0(entry_point) and the
* parameters already in place in x1(arg1), x2(arg2), x3(arg3).
* - SPSR_ELn: to enable IRQs (we are masking FIQs).
*/
#ifdef CONFIG_USERSPACE
/*
* If the new thread is a user thread we jump into
* arch_user_mode_enter() when still in EL1.
*/
if (is_user(thread)) {
pInitCtx->elr = (uint64_t)arch_user_mode_enter;
} else {
pInitCtx->elr = (uint64_t)z_thread_entry;
}
#else
pInitCtx->elr = (uint64_t)z_thread_entry;
#endif
/* Keep using SP_EL1 */
pInitCtx->spsr = SPSR_MODE_EL1H | DAIF_FIQ_BIT;
/* thread birth happens through the exception return path */
thread->arch.exception_depth = 1;
/*
* We are saving SP_EL1 to pop out entry and parameters when going
* through z_arm64_exit_exc(). For user threads the definitive location
* of SP_EL1 will be set implicitly when going through
* z_arm64_userspace_enter() (see comments there)
*/
thread->callee_saved.sp_elx = (uint64_t)pInitCtx;
thread->switch_handle = thread;
}
void *z_arch_get_next_switch_handle(struct k_thread **old_thread)
{
/*
* When returning from this function we will have the current thread
* onto the stack to be popped in x1 and the next thread in x0 returned
* from z_get_next_switch_handle() (see isr_wrapper.S)
*/
*old_thread = _current;
#ifdef CONFIG_SMP
/*
* XXX: see thread in #41840 and #40795
*
* The scheduler API requires a complete switch handle here, but arm64
* optimizes things such that the callee-save registers are still
* unsaved here (they get written out in z_arm64_context_switch()
* below). So pass a NULL instead, which the scheduler will store into
* the thread switch_handle field. The resulting thread won't be
* switched into until we write that ourselves.
*/
return z_get_next_switch_handle(NULL);
#else
return z_get_next_switch_handle(*old_thread);
#endif
}
#ifdef CONFIG_USERSPACE
FUNC_NORETURN void arch_user_mode_enter(k_thread_entry_t user_entry,
void *p1, void *p2, void *p3)
{
z_arch_esf_t *pInitCtx;
uintptr_t stack_ptr;
/* Map the thread stack */
z_arm64_thread_pt_init(_current);
/*
* Reset the SP_EL0 stack pointer to the stack top discarding any old
* context. The actual register is written in z_arm64_userspace_enter()
*/
stack_ptr = Z_STACK_PTR_ALIGN(_current->stack_info.start +
_current->stack_info.size -
_current->stack_info.delta);
/*
* Reconstruct the ESF from scratch to leverage the z_arm64_exit_exc()
* macro that will simulate a return from exception to move from EL1h
* to EL0t. On return we will be in userspace using SP_EL0.
*
* We relocate the ESF to the beginning of the privileged stack in the
* not user accessible part of the stack
*/
pInitCtx = (struct __esf *) (_current->stack_obj + ARCH_THREAD_STACK_RESERVED -
sizeof(struct __esf));
pInitCtx->spsr = DAIF_FIQ_BIT | SPSR_MODE_EL0T;
pInitCtx->elr = (uint64_t)z_thread_entry;
pInitCtx->x0 = (uint64_t)user_entry;
pInitCtx->x1 = (uint64_t)p1;
pInitCtx->x2 = (uint64_t)p2;
pInitCtx->x3 = (uint64_t)p3;
/* All the needed information is already in the ESF */
z_arm64_userspace_enter(pInitCtx, stack_ptr);
CODE_UNREACHABLE;
}
#endif