zephyr/arch/xtensa/core/userspace.S
Daniel Leung d344a6bc85 xtensa: make arch_user_string_nlen actually work
arch_user_string_nlen() did not exactly work correctly as any
invalid pointers being passed are de-referenced naively, which
results in DTLB misses (MMU) or access errors (MPU). However,
arch_user_string_nlen() should always return to the caller
with appropriate error code set, and should never result in
thread termination. Since we are usually going through syscalls
when arch_user_string_nlen() is called, for MMU, the DTLB miss
goes through double exception. Since the pointer is invalid,
there is a high chance there is not even a L2 page table
associated with that bad address. So the DTLB miss cannot be
handled and it just keeps looping in double exception until
there is another exception type where we get to the C handler.
However, the stack frame is no longer the frame associated
with the call to arch_user_string_nlen(), and the call return
address would be incorrect. Forcing this incorrect address as
the next PC would result in some other exceptions, e.g.
illegal instruction, which would go to the C handler again.
This time it will go to the end of handler and would result
in thread termination. For MPU systems, access errors would
simply result in thread terminal in the C handler. Because of
these reasons, change the arch_user_string_nlen() to check if
the memory region can be accessed under kernel mode first
before feeding it to strnlen().

Also remove the exception fixup arrays as there is nothing
there anymore.

Signed-off-by: Daniel Leung <daniel.leung@intel.com>
2024-06-15 04:44:48 -04:00

355 lines
7.7 KiB
ArmAsm

/*
* Copyright (c) 2022, Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <xtensa_asm2_s.h>
#include <zephyr/offsets.h>
#include <offsets_short.h>
#include <zephyr/syscall.h>
#include <zephyr/zsr.h>
#include <xtensa/config/core-isa.h>
/**
* syscall number arg1, arg2, arg3, arg4, arg5, arg6
* -------------- ----------------------------------
* a2 a6, a3, a4, a5, a8, a9
*
**/
.pushsection .text.xtensa_do_syscall, "ax"
.global xtensa_do_syscall
.align 4
xtensa_do_syscall:
#if XCHAL_HAVE_THREADPTR == 0
wsr a2, ZSR_SYSCALL_SCRATCH
rsync
movi a0, xtensa_is_user_context_epc
rsr.epc1 a2
bne a0, a2, _not_checking_user_context
addi a2, a2, 3
wsr.epc1 a2
movi a0, PS_RING_MASK
rsr.ps a2
and a2, a2, a0
/* Need to set return to 1 if RING != 0,
* so we won't be leaking which ring we are in
* right now.
*/
beqz a2, _is_user_context_return
movi a2, 1
_is_user_context_return:
rsr a0, ZSR_A0SAVE
rfe
_not_checking_user_context:
rsr a2, ZSR_SYSCALL_SCRATCH
#endif
rsr a0, ZSR_CPU
l32i a0, a0, ___cpu_t_current_OFFSET
l32i a0, a0, _thread_offset_to_psp
addi a0, a0, -___xtensa_irq_bsa_t_SIZEOF
s32i a1, a0, ___xtensa_irq_bsa_t_scratch_OFFSET
s32i a2, a0, ___xtensa_irq_bsa_t_a2_OFFSET
s32i a3, a0, ___xtensa_irq_bsa_t_a3_OFFSET
rsr a2, ZSR_A0SAVE
s32i a2, a0, ___xtensa_irq_bsa_t_a0_OFFSET
rsr.ps a2
movi a3, ~PS_OWB_MASK
and a2, a2, a3
s32i a2, a0, ___xtensa_irq_bsa_t_ps_OFFSET
rsr.epc1 a2
s32i a2, a0, ___xtensa_irq_bsa_t_pc_OFFSET
#if XCHAL_HAVE_NMI
movi a2, PS_WOE|PS_INTLEVEL(XCHAL_NMILEVEL)
#elif XCHAL_HAVE_INTERRUPTS
movi a2, PS_WOE|PS_INTLEVEL(XCHAL_NUM_INTLEVELS)
#else
#error Xtensa core with no interrupt support is used
#endif
rsr.ps a3
or a3, a3, a2
movi a2, ~(PS_EXCM | PS_RING_MASK)
and a3, a3, a2
wsr.ps a3
rsync
l32i a2, a0, ___xtensa_irq_bsa_t_a2_OFFSET
l32i a3, a0, ___xtensa_irq_bsa_t_a3_OFFSET
SPILL_ALL_WINDOWS
rsr a0, ZSR_CPU
l32i a0, a0, ___cpu_t_current_OFFSET
l32i a0, a0, _thread_offset_to_psp
addi a0, a0, -___xtensa_irq_bsa_t_SIZEOF
mov a1, a0
l32i a3, a1, ___xtensa_irq_bsa_t_pc_OFFSET
#if XCHAL_HAVE_LOOPS
/* If the syscall instruction was the last instruction in the body of
* a zero-overhead loop, and the loop will execute again, decrement
* the loop count and resume execution at the head of the loop.
*/
rsr.lend a2
addi a3, a3, 3
bne a2, a3, end_loop
rsr.lcount a2
beqz a2, end_loop
addi a2, a2, -1
wsr.lcount a2
rsr.lbeg a3
end_loop:
#else
/* EPC1 (and now a3) contains the address that invoked syscall.
* We need to increment it to execute the next instruction when
* we return. The instruction size is 3 bytes, so lets just add it.
*/
addi a3, a3, 3
#endif
s32i a3, a1, ___xtensa_irq_bsa_t_pc_OFFSET
ODD_REG_SAVE
#if defined(CONFIG_XTENSA_HIFI_SHARING)
call0 _xtensa_hifi_save
#endif
call0 xtensa_save_high_regs
l32i a2, a1, 0
l32i a2, a2, ___xtensa_irq_bsa_t_a2_OFFSET
movi a0, K_SYSCALL_LIMIT
bgeu a2, a0, _bad_syscall
_id_ok:
/* Find the function handler for the given syscall id. */
movi a3, _k_syscall_table
slli a2, a2, 2
add a2, a2, a3
l32i a2, a2, 0
#if XCHAL_HAVE_THREADPTR
/* Clear up the threadptr because it is used
* to check if a thread is running on user mode. Since
* we are in a interruption we don't want the system
* thinking it is possibly running in user mode.
*/
#ifdef CONFIG_THREAD_LOCAL_STORAGE
movi a0, is_user_mode@tpoff
rur.THREADPTR a3
add a0, a3, a0
movi a3, 0
s32i a3, a0, 0
#else
movi a0, 0
wur.THREADPTR a0
#endif
#endif /* XCHAL_HAVE_THREADPTR */
/* Set syscall parameters by moving them into place before we do
* a call4 for the syscall function itself.
* arg1 = a6
* arg2 = a3 (clobbered above, so we need to reload it)
* arg3 = a4
* arg4 = a5
* arg5 = a8
* arg6 = a9
*/
mov a10, a8
mov a11, a9
mov a8, a4
mov a9, a5
/* Stack frame pointer is the 7th argument to z_mrsh_*()
* as ssf, and must be put on stack to be consumed.
*/
mov a3, a1
addi a1, a1, -4
s32i a3, a1, 0
l32i a3, a1, 4
l32i a7, a3, ___xtensa_irq_bsa_t_a3_OFFSET
/* Since we are unmasking EXCM, we need to set RING bits to kernel
* mode, otherwise we won't be able to run the exception handler in C.
*/
movi a0, PS_WOE|PS_CALLINC(0)|PS_UM|PS_INTLEVEL(0)
wsr.ps a0
rsync
callx4 a2
/* Going back before stack frame pointer on stack to
* actual the stack frame. So restoration of registers
* can be done properly when finishing syscalls.
*/
addi a1, a1, 4
/* copy return value. Lets put it in the top of stack
* because registers will be clobbered in
* xtensa_restore_high_regs
*/
l32i a3, a1, 0
s32i a6, a3, ___xtensa_irq_bsa_t_a2_OFFSET
j _syscall_returned
_syscall_returned:
call0 xtensa_restore_high_regs
l32i a3, a1, ___xtensa_irq_bsa_t_sar_OFFSET
wsr a3, SAR
#if XCHAL_HAVE_LOOPS
l32i a3, a1, ___xtensa_irq_bsa_t_lbeg_OFFSET
wsr a3, LBEG
l32i a3, a1, ___xtensa_irq_bsa_t_lend_OFFSET
wsr a3, LEND
l32i a3, a1, ___xtensa_irq_bsa_t_lcount_OFFSET
wsr a3, LCOUNT
#endif
#if XCHAL_HAVE_S32C1I
l32i a3, a1, ___xtensa_irq_bsa_t_scompare1_OFFSET
wsr a3, SCOMPARE1
#endif
#if XCHAL_HAVE_THREADPTR
#ifdef CONFIG_THREAD_LOCAL_STORAGE
l32i a3, a1, ___xtensa_irq_bsa_t_threadptr_OFFSET
movi a0, is_user_mode@tpoff
add a0, a3, a0
movi a3, 1
s32i a3, a0, 0
#else
rsr a3, ZSR_CPU
l32i a3, a3, ___cpu_t_current_OFFSET
wur.THREADPTR a3
#endif
#endif /* XCHAL_HAVE_THREADPTR */
l32i a3, a1, ___xtensa_irq_bsa_t_ps_OFFSET
wsr.ps a3
l32i a3, a1, ___xtensa_irq_bsa_t_pc_OFFSET
wsr.epc1 a3
l32i a0, a1, ___xtensa_irq_bsa_t_a0_OFFSET
l32i a2, a1, ___xtensa_irq_bsa_t_a2_OFFSET
l32i a3, a1, ___xtensa_irq_bsa_t_a3_OFFSET
l32i a1, a1, ___xtensa_irq_bsa_t_scratch_OFFSET
rsync
rfe
_bad_syscall:
movi a2, K_SYSCALL_BAD
j _id_ok
.popsection
/* FUNC_NORETURN void xtensa_userspace_enter(k_thread_entry_t user_entry,
* void *p1, void *p2, void *p3,
* uint32_t stack_end,
* uint32_t stack_start)
*
* A one-way trip to userspace.
*/
.global xtensa_userspace_enter
.type xtensa_userspace_enter, @function
.align 4
xtensa_userspace_enter:
/* Call entry to set a bit in the windowstart and
* do the rotation, but we are going to set our own
* stack.
*/
entry a1, 16
SPILL_ALL_WINDOWS
/* We have to switch to kernel stack before spill kernel data and
* erase user stack to avoid leak from previous context.
*/
mov a1, a7 /* stack start (low address) */
rsr a0, ZSR_CPU
l32i a0, a0, ___cpu_t_current_OFFSET
addi a1, a1, -28
s32i a0, a1, 24
s32i a2, a1, 20
s32i a3, a1, 16
s32i a4, a1, 12
s32i a5, a1, 8
s32i a6, a1, 4
s32i a7, a1, 0
l32i a6, a1, 24
call4 xtensa_user_stack_perms
l32i a6, a1, 24
#ifdef CONFIG_XTENSA_MMU
call4 xtensa_swap_update_page_tables
#endif
#ifdef CONFIG_XTENSA_MPU
call4 xtensa_mpu_map_write
#endif
#if XCHAL_HAVE_THREADPTR
#ifdef CONFIG_THREAD_LOCAL_STORAGE
rur.threadptr a3
movi a0, is_user_mode@tpoff
add a0, a3, a0
movi a3, 1
s32i a3, a0, 0
#else
rsr a3, ZSR_CPU
l32i a3, a3, ___cpu_t_current_OFFSET
wur.THREADPTR a3
#endif
#endif /* XCHAL_HAVE_THREADPTR */
/* Set now z_thread_entry parameters, we are simulating a call4
* call, so parameters start at a6, a7, ...
*/
l32i a6, a1, 20
l32i a7, a1, 16
l32i a8, a1, 12
l32i a9, a1, 8
/* Go back to user stack */
l32i a1, a1, 4
movi a0, z_thread_entry
wsr.epc2 a0
/* Configuring PS register.
* We have to set callinc as well, since the called
* function will do "entry"
*/
#ifdef CONFIG_XTENSA_MMU
movi a0, PS_WOE|PS_CALLINC(1)|PS_UM|PS_RING(2)
#endif
#ifdef CONFIG_XTENSA_MPU
/* MPU only has RING 0 and 1. */
movi a0, PS_WOE|PS_CALLINC(1)|PS_UM|PS_RING(1)
#endif
wsr a0, EPS2
/* Wipe out a0 (thre is no return from this function */
movi a0, 0
rfi 2