/* * Low-level exception handling * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2004 - 2008 by Tensilica Inc. * Copyright (C) 2015 Cadence Design Systems Inc. * * Chris Zankel <chris@zankel.net> *
*/
/* ----------------- DEFAULT FIRST LEVEL EXCEPTION HANDLERS ----------------- */
/* * First-level exception handler for user exceptions. * Save some special registers, extra states and all registers in the AR * register file that were in use in the user task, and jump to the common * exception code. * We save SAR (used to calculate WMASK), and WB and WS (we don't have to * save them for kernel exceptions). * * Entry condition for user_exception: * * a0: trashed, original value saved on stack (PT_AREG0) * a1: a1 * a2: new stack pointer, original value in depc * a3: a3 * depc: a2, original value saved on stack (PT_DEPC) * excsave1: dispatch table * * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception * * Entry condition for _user_exception: * * a0-a3 and depc have been saved to PT_AREG0...PT_AREG3 and PT_DEPC * excsave has been restored, and * stack pointer (a1) has been set. * * Note: _user_exception might be at an odd address. Don't use call0..call12
*/
.literal_position
#if defined(USER_SUPPORT_WINDOWED) /* If only one valid frame skip saving regs. */
beqi a2, 1, common_exception
/* Save the remaining registers. * We have to save all registers up to the first '1' from * the right, except the current frame (bit 0). * Assume a2 is: 001001000110001 * All register frames starting from the top field to the marked '1' * must be saved.
*/
.Lsave_window_registers:
addi a3, a2, -1 # eliminate '1' in bit 0: yyyyxxww0
neg a3, a3 # yyyyxxww0 -> YYYYXXWW1+1
and a3, a3, a2 # max. only one bit is set
/* Find number of frames to save */
ffs_ws a0, a3 # number of frames to the '1' from left
/* Store information into WMASK: * bits 0..3: xxx1 masked lower 4 bits of the rotated windowstart, * bits 4...: number of valid 4-register frames
*/
slli a3, a0, 4 # number of frames to save in bits 8..4
extui a2, a2, 0, 4 # mask for the first 16 registers
or a2, a3, a2
s32i a2, a1, PT_WMASK # needed when we restore the reg-file
rsr a2, sar # original WINDOWBASE
movi a3, 1
ssl a2 sll a3, a3
wsr a3, windowstart # set corresponding WINDOWSTART bit
wsr a2, windowbase # and WINDOWSTART
rsync
/* We are back to the original stack pointer (a1) */
#endif /* Now, jump to the common exception handler. */
j common_exception
ENDPROC(user_exception)
/* * First-level exit handler for kernel exceptions * Save special registers and the live window frame. * Note: Even though we changes the stack pointer, we don't have to do a * MOVSP here, as we do that when we return from the exception. * (See comment in the kernel exception exit code) * * Entry condition for kernel_exception: * * a0: trashed, original value saved on stack (PT_AREG0) * a1: a1 * a2: new stack pointer, original in DEPC * a3: a3 * depc: a2, original value saved on stack (PT_DEPC) * excsave_1: dispatch table * * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception * * Entry condition for _kernel_exception: * * a0-a3 and depc have been saved to PT_AREG0...PT_AREG3 and PT_DEPC * excsave has been restored, and * stack pointer (a1) has been set. * * Note: _kernel_exception might be at an odd address. Don't use call0..call12
*/
#if defined(__XTENSA_WINDOWED_ABI__) /* Rotate ws so that the current windowbase is at bit0. */ /* Assume ws = xxwww1yyyy. Rotate ws right, so that a2 = yyyyxxwww1 */
rsr a2, windowbase # don't need to save these, we only
rsr a3, windowstart # need shifted windowstart: windowmask
ssr a2
slli a2, a3, 32-WSBITS
src a2, a3, a2
srli a2, a2, 32-WSBITS
s32i a2, a1, PT_WMASK # needed for kernel_exception_exit
#endif
#ifdef __XTENSA_WINDOWED_ABI__
_bnei a2, 1, 1f /* Copy spill slots of a0 and a1 to imitate movsp * in order to keep exception stack continuous
*/
l32i a3, a1, PT_KERNEL_SIZE
l32i a0, a1, PT_KERNEL_SIZE + 4
s32e a3, a1, -16
s32e a0, a1, -12
#endif 1:
l32i a0, a1, PT_AREG0 # restore saved a0
wsr a0, depc
/* * This is the common exception handler. * We get here from the user exception handler or simply by falling through * from the kernel exception handler. * Save the remaining special registers, switch to kernel mode, and jump * to the second-level exception handler. *
*/
common_exception:
/* Save some registers, disable loops and clear the syscall flag. */
/* All unrecoverable states are saved on stack, now, and a1 is valid. * Now we can allow exceptions again. In case we've got an interrupt * PS.INTLEVEL is set to LOCKLEVEL disabling furhter interrupts, * otherwise it's left unchanged. * * Set PS(EXCM = 0, UM = 0, RING = 0, OWB = 0, WOE = 1, INTLEVEL = X)
*/
rsr a3, ps
s32i a3, a1, PT_PS # save ps
#if XTENSA_FAKE_NMI /* Correct PS needs to be saved in the PT_PS: * - in case of exception or level-1 interrupt it's in the PS, * and is already saved. * - in case of medium level interrupt it's in the excsave2.
*/
movi a0, EXCCAUSE_MAPPED_NMI
extui a3, a3, PS_INTLEVEL_SHIFT, PS_INTLEVEL_WIDTH
beq a2, a0, .Lmedium_level_irq
bnei a2, EXCCAUSE_LEVEL1_INTERRUPT, .Lexception
beqz a3, .Llevel1_irq # level-1 IRQ sets ps.intlevel to 0
/* Specific to a user exception exit: * We need to check some flags for signal handling and rescheduling, * and have to restore WB and WS, extra states, and all registers * in the register file that were in use in the user task. * Note that we don't disable interrupts here.
*/
/* Restore the state of the task and return from the exception. */
#if defined(USER_SUPPORT_WINDOWED) /* Switch to the user thread WINDOWBASE. Save SP temporarily in DEPC */
l32i a2, a1, PT_WINDOWBASE
l32i a3, a1, PT_WINDOWSTART
wsr a1, depc # use DEPC as temp storage
wsr a3, windowstart # restore WINDOWSTART
ssr a2 # preserve user's WB in the SAR
wsr a2, windowbase # switch to user's saved WB
rsync
rsr a1, depc # restore stack pointer
l32i a2, a1, PT_WMASK # register frames saved (in bits 4...9)
rotw -1 # we restore a4..a7
_bltui a6, 16, .Lclear_regs # only have to restore current window?
/* The working registers are a0 and a3. We are restoring to * a4..a7. Be careful not to destroy what we have just restored. * Note: wmask has the format YYYYM: * Y: number of registers saved in groups of 4 * M: 4 bit mask of first 16 registers
*/
/* We are back were we were when we started. * Note: a2 still contains WMASK (if we've returned to the original * frame where we had loaded a2), or at least the lower 4 bits * (if we have restored WSBITS-1 frames).
*/ 2:
#else
movi a2, 1
#endif
#if XCHAL_HAVE_THREADPTR
l32i a3, a1, PT_THREADPTR
wur a3, threadptr
#endif
j common_exception_exit
/* This is the kernel exception exit. * We avoided to do a MOVSP when we entered the exception, but we * have to do it here.
*/
kernel_exception_exit:
#if defined(__XTENSA_WINDOWED_ABI__) /* Check if we have to do a movsp. * * We only have to do a movsp if the previous window-frame has * been spilled to the *temporary* exception stack instead of the * task's stack. This is the case if the corresponding bit in * WINDOWSTART for the previous window-frame was set before * (not spilled) but is zero now (spilled). * If this bit is zero, all other bits except the one for the * current window frame are also zero. So, we can use a simple test: * 'and' WINDOWSTART and WINDOWSTART-1: * * (XXXXXX1[0]* - 1) AND XXXXXX1[0]* = XXXXXX0[0]* * * The result is zero only if one bit was set. * * (Note: We might have gone through several task switches before * we come back to the current task, so WINDOWBASE might be * different from the time the exception occurred.)
*/
/* Test WINDOWSTART before and after the exception. * We actually have WMASK, so we only have to test if it is 1 or not.
*/
/* Common exception exit. * We restore the special register and the current window frame, and * return from the exception. * * Note: We expect a2 to hold PT_WMASK
*/
#else
movi a2, 1
#endif
/* * Debug exception handler. * * Currently, we don't support KGDB, so only user application can be debugged. * * When we get here, a0 is trashed and saved to excsave[debuglevel]
*/
/* Debug exception is handled as an exception, so interrupts will * likely be enabled in the common exception handler. Disable * preemption if we have HW breakpoints to preserve DEBUGCAUSE.DBNUM * meaning.
*/
#if defined(CONFIG_PREEMPT_COUNT) && defined(CONFIG_HAVE_HW_BREAKPOINT)
GET_THREAD_INFO(a2, a1)
l32i a3, a2, TI_PRE_COUNT
addi a3, a3, 1
s32i a3, a2, TI_PRE_COUNT
#endif
.Ldebug_exception_in_exception:
#ifdef CONFIG_HAVE_HW_BREAKPOINT /* Debug exception while in exception mode. This may happen when * window overflow/underflow handler or fast exception handler hits * data breakpoint, in which case save and disable all data * breakpoints, single-step faulting instruction and restore data * breakpoints.
*/
bbci.l a0, PS_UM_BIT, .Ldebug_exception_in_exception # jump if kernel mode
l32i a0, a3, DT_DEBUG_SAVE
xsr a3, SREG_EXCSAVE + XCHAL_DEBUGLEVEL
rfi XCHAL_DEBUGLEVEL
#else /* Debug exception while in exception mode. Should not happen. */
j .Ldebug_exception_in_exception // FIXME!!
#endif
ENDPROC(debug_exception)
/* * We get here in case of an unrecoverable exception. * The only thing we can do is to be nice and print a panic message. * We only produce a single stack frame for panic, so ??? * * * Entry conditions: * * - a0 contains the caller address; original value saved in excsave1. * - the original a0 contains a valid return address (backtrace) or 0. * - a2 contains a valid stackpointer * * Notes: * * - If the stack pointer could be invalid, the caller has to setup a * dummy stack pointer (e.g. the stack of the init_task) * * - If the return address could be invalid, the caller has to set it * to 0, so the backtrace would stop. *
*/
.align4
unrecoverable_text:
.ascii"Unrecoverable error in exception handler\0"
/* -------------------------- FAST EXCEPTION HANDLERS ----------------------- */
__XTENSA_HANDLER
.literal_position
#ifdef SUPPORT_WINDOWED /* * Fast-handler for alloca exceptions * * The ALLOCA handler is entered when user code executes the MOVSP * instruction and the caller's frame is not in the register file. * * This algorithm was taken from the Ross Morley's RTOS Porting Layer: * * /home/ross/rtos/porting/XtensaRTOS-PortingLayer-20090507/xtensa_vectors.S * * It leverages the existing window spill/fill routines and their support for * double exceptions. The 'movsp' instruction will only cause an exception if * the next window needs to be loaded. In fact this ALLOCA exception may be * replaced at some point by changing the hardware to do a underflow exception * of the proper size instead. * * This algorithm simply backs out the register changes started by the user * exception handler, makes it appear that we have started a window underflow * by rotating the window back and then setting the old window base (OWB) in * the 'ps' register with the rolled back window base. The 'movsp' instruction * will be re-executed and this time since the next window frames is in the * active AR registers it won't cause an exception. * * If the WindowUnderflow code gets a TLB miss the page will get mapped * the partial WindowUnderflow will be handled in the double exception * handler. * * Entry condition: * * a0: trashed, original value saved on stack (PT_AREG0) * a1: a1 * a2: new stack pointer, original in DEPC * a3: a3 * depc: a2, original value saved on stack (PT_DEPC) * excsave_1: dispatch table * * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception
*/
#ifdef CONFIG_USER_ABI_CALL0_PROBE /* * fast illegal instruction handler. * * This is used to fix up user PS.WOE on the exception caused * by the first opcode related to register window. If PS.WOE is * already set it goes directly to the common user exception handler. * * Entry condition: * * a0: trashed, original value saved on stack (PT_AREG0) * a1: a1 * a2: new stack pointer, original in DEPC * a3: a3 * depc: a2, original value saved on stack (PT_DEPC) * excsave_1: dispatch table
*/
/* * fast system calls. * * WARNING: The kernel doesn't save the entire user context before * handling a fast system call. These functions are small and short, * usually offering some functionality not available to user tasks. * * BE CAREFUL TO PRESERVE THE USER'S CONTEXT. * * Entry condition: * * a0: trashed, original value saved on stack (PT_AREG0) * a1: a1 * a2: new stack pointer, original in DEPC * a3: a3 * depc: a2, original value saved on stack (PT_DEPC) * excsave_1: dispatch table
*/
/* fast_syscall_spill_registers. * * Entry condition: * * a0: trashed, original value saved on stack (PT_AREG0) * a1: a1 * a2: new stack pointer, original in DEPC * a3: a3 * depc: a2, original value saved on stack (PT_DEPC) * excsave_1: dispatch table * * Note: We assume the stack pointer is EXC_TABLE_KSTK in the fixup handler.
*/
/* * Rotate ws so that the current windowbase is at bit 0. * Assume ws = xxxwww1yy (www1 current window frame). * Rotate ws right so that a4 = yyxxxwww1.
*/
/* WB is now just one frame below the oldest frame in the register window. WS is shifted so the oldest frame is in bit 0, thus, WB
and WS differ by one 4-register frame. */
/* Save frames. Depending what call was used (call4, call8, call12), * we have to save 4,8. or 12 registers.
*/
/* The stack pointer for a4..a7 is out of reach, so we rotate the * window, grab the stackpointer, and rotate back. * Alternatively, we could also use the following approach, but that * makes the fixup routine much more complicated: * rotw 1 * s32e a0, a13, -16 * ... * rotw 2
*/
/* We get here because of an unrecoverable error in the window * registers, so set up a dummy frame and kill the user application. * Note: We assume EXC_TABLE_KSTK contains a valid stack pointer.
*/
wsr a0, excsave1
call0 unrecoverable_exception # should not return 1: j 1b
ENDPROC(fast_syscall_spill_registers)
/* Fixup handler. * * We get here if the spill routine causes an exception, e.g. tlb miss. * We basically restore WINDOWBASE and WINDOWSTART to the condition when * we entered the spill routine and jump to the user exception handler. * * Note that we only need to restore the bits in windowstart that have not * been spilled yet by the _spill_register routine. Luckily, a3 contains a * rotated windowstart with only those bits set for frames that haven't been * spilled yet. Because a3 is rotated such that bit 0 represents the register * frame for the current windowbase - 1, we need to rotate a3 left by the * value of the current windowbase + 1 and move it to windowstart. * * a0: value of depc, original value in depc * a2: trashed, original value in EXC_TABLE_DOUBLE_SAVE * a3: exctable, original value in excsave1
*/
ENTRY(fast_syscall_spill_registers_fixup)
rsr a2, windowbase # get current windowbase (a2 is saved)
xsr a0, depc # restore depc and a0
ssl a2 # set shift (32 - WB)
/* We need to make sure the current registers (a0-a3) are preserved. * To do this, we simply set the bit for the current window frame * in WS, so that the exception handlers save them to the task stack. * * Note: we use a3 to set the windowbase, so we take a special care * of it, saving it in the original _spill_registers frame across * the exception handler call.
*/
xsr a3, excsave1 # get spill-mask
slli a3, a3, 1 # shift left by one
addi a3, a3, 1 # set the bit for the current window frame
srli a3, a3, 1
rsr a2, excsave1
l32i a2, a2, EXC_TABLE_DOUBLE_SAVE # restore a2
xsr a2, excsave1
s32i a3, a2, EXC_TABLE_DOUBLE_SAVE # save a3
l32i a3, a2, EXC_TABLE_PARAM # original WB (in user task)
xsr a2, excsave1
/* Return to the original (user task) WINDOWBASE. * We leave the following frame behind: * a0, a1, a2 same * a3: trashed (saved in EXC_TABLE_DOUBLE_SAVE) * depc: depc (we have to return to that address) * excsave_1: exctable
*/
wsr a3, windowbase
rsync
/* We are now in the original frame when we entered _spill_registers: * a0: return address * a1: used, stack pointer * a2: kernel stack pointer * a3: available * depc: exception address * excsave: exctable * Note: This frame might be the same as above.
*/
#ifdef CONFIG_MMU /* * We should never get here. Bail out!
*/
ENTRY(fast_second_level_miss_double_kernel)
1:
call0 unrecoverable_exception # should not return 1: j 1b
ENDPROC(fast_second_level_miss_double_kernel)
/* First-level entry handler for user, kernel, and double 2nd-level * TLB miss exceptions. Note that for now, user and kernel miss * exceptions share the same entry point and are handled identically. * * An old, less-efficient C version of this function used to exist. * We include it below, interleaved as comments, for reference. * * Entry condition: * * a0: trashed, original value saved on stack (PT_AREG0) * a1: a1 * a2: new stack pointer, original in DEPC * a3: a3 * depc: a2, original value saved on stack (PT_DEPC) * excsave_1: dispatch table * * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception
*/
ENTRY(fast_second_level_miss)
/* Save a1 and a3. Note: we don't expect a double exception. */
s32i a1, a2, PT_AREG1
s32i a3, a2, PT_AREG3
/* We need to map the page of PTEs for the user task. Find * the pointer to that page. Also, it's possible for tsk->mm * to be NULL while tsk->active_mm is nonzero if we faulted on * a vmalloc address. In that rare case, we must use * active_mm instead to avoid a fault in this handler. See * * http://mail.nl.linux.org/linux-mm/2002-08/msg00258.html * (or search Internet on "mm vs. active_mm") * * if (!mm) * mm = tsk->active_mm; * pgd = pgd_offset (mm, regs->excvaddr); * pmd = pmd_offset (pgd, regs->excvaddr); * pmdval = *pmd;
*/
/* * We utilize all three wired-ways (7-9) to hold pmd translations. * Memory regions are mapped to the DTLBs according to bits 28 and 29. * This allows to map the three most common regions to three different * DTLBs: * 0,1 -> way 7 program (0040.0000) and virtual (c000.0000) * 2 -> way 8 shared libaries (2000.0000) * 3 -> way 0 stack (3000.0000)
*/
.Lfast_second_level_miss_no_mm:
l32i a0, a1, TASK_ACTIVE_MM # unlikely case mm == 0
bnez a0, .Lfast_second_level_miss_continue
/* Even more unlikely case active_mm == 0. * We can get here with NMI in the middle of context_switch that * touches vmalloc area.
*/
movi a0, init_mm
j .Lfast_second_level_miss_continue
/* Special case for cache aliasing. * We (should) only get here if a clear_user_page, copy_user_page * or the aliased cache flush functions got preemptively interrupted * by another task. Re-establish temporary mapping to the * TLBTEMP_BASE areas.
*/
/* We need to flush the cache if we have page coloring. */
#if (DCACHE_WAY_SIZE > PAGE_SIZE) && XCHAL_DCACHE_IS_WRITEBACK
dhwb a0, 0
#endif
pdtlb a0, a1
wdtlb a3, a0
/* * Spill live registers on the kernel stack macro. * * Entry condition: ps.woe is set, ps.excm is cleared * Exit condition: windowstart has single bit set * May clobber: a12, a13
*/
.macro spill_registers_kernel
/* Set kernel stack (and leave critical section) * Note: It's save to set it here. The stack will not be overwritten * because the kernel stack will only be loaded again after * we return from kernel space.
*/
/* * Kernel thread creation helper * On entry, set up by copy_thread: abi_saved0 = thread_fn, * abi_saved1 = thread_fn arg. Left from _switch_to: abi_arg0 = prev
*/
ENTRY(ret_from_kernel_thread)
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.