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>
355 lines
7.7 KiB
ArmAsm
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
|