blob: 0d52884ee4bfcb09687719a08895cacaa0e7c6ef [file] [log] [blame]
// High-Priority Interrupt Dispatcher Template
// $Id: //depot/rel/Eaglenest/Xtensa/OS/xtos/int-highpri-dispatcher.S#1 $
// Copyright (c) 2004-2010 Tensilica Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// This file allows writing high-priority interrupt handlers in C,
// providing convenience at a significant cost in performance.
//
// By default, this file is included by inth-template.S .
// The default Makefile defines _INTERRUPT_LEVEL when assembling
// inth-template.S for each medium and high priority interrupt level.
//
// To use this template file, define a macro called _INTERRUPT_LEVEL
// to be the interrupt priority level of the vector, then include this file.
#include <xtensa/coreasm.h>
#include "xtos-internal.h"
#if XCHAL_HAVE_INTERRUPTS
#define INTERRUPT_MASK XCHAL_INTLEVEL_MASK(_INTERRUPT_LEVEL)
#define SINGLE_INTERRUPT ((INTERRUPT_MASK & (INTERRUPT_MASK - 1)) == 0)
#define SINGLE_INT_NUM _INTERRUPT_LEVEL
#define INTLEVEL_N_MASK INTERRUPT_MASK // mask of interrupts at this priority
#define INTLEVEL_N_NUM SINGLE_INT_NUM // interrupt number if there is only one
#define INTLEVEL_N_BELOW_MASK XCHAL_INTLEVEL_ANDBELOW_MASK(_INTERRUPT_LEVEL)
/* Indicates whether there are multiple interrupts at this interrupt
* priority, ie. mapped to this interrupt vector.
* If there is only one, its number is INTLEVEL_N_NUM
*/
#define MULTIPLE_INTERRUPTS (!SINGLE_INTERRUPT)
/*
* High priority interrupt stack frame:
*/
STRUCT_BEGIN
STRUCT_FIELD (long,4,HESF_,SAR)
STRUCT_FIELD (long,4,HESF_,WINDOWSTART)
STRUCT_FIELD (long,4,HESF_,WINDOWBASE)
STRUCT_FIELD (long,4,HESF_,EPC1)
STRUCT_FIELD (long,4,HESF_,EXCCAUSE)
STRUCT_FIELD (long,4,HESF_,EXCVADDR)
STRUCT_FIELD (long,4,HESF_,EXCSAVE1)
STRUCT_FIELD (long,4,HESF_,VPRI) /* (XEA1 only) */
#if XCHAL_HAVE_MAC16
STRUCT_FIELD (long,4,HESF_,ACCLO)
STRUCT_FIELD (long,4,HESF_,ACCHI)
/*STRUCT_AFIELD(long,4,HESF_,MR, 4)*/
#endif
#if XCHAL_HAVE_LOOPS
STRUCT_FIELD (long,4,HESF_,LCOUNT)
STRUCT_FIELD (long,4,HESF_,LBEG)
STRUCT_FIELD (long,4,HESF_,LEND)
#endif
STRUCT_AFIELD(long,4,HESF_,AREG, 64) /* address registers ar0..ar63 */
#define HESF_AR(n) HESF_AREG+((n)*4)
STRUCT_END(HighPriFrame)
#define HESF_TOTALSIZE HighPriFrameSize+32 /* 32 bytes for interrupted code's save areas under SP */
#if XCHAL_HAVE_XEA1 && HAVE_XSR /* could be made true for T1040 and T1050 */
# error "high-priority interrupt stack frame needs adjustment if HAVE_XSR is allowed with XEA1"
#endif
#define PRI_N_STACK_SIZE 1024 /* default to 1 kB stack for each level-N handling */
// Allocate save area and stack:
// (must use .bss, not .comm, because the subsequent .set does not work otherwise)
.section .bss, "aw"
.align 16
LABEL(_Pri_,_Stack): .space PRI_N_STACK_SIZE + HESF_TOTALSIZE
#if HAVE_XSR
.data
.global LABEL(_Pri_,_HandlerAddress)
LABEL(_Pri_,_HandlerAddress): .space 4
#endif
.text
.align 4
.global LABEL(_Level,FromVector)
LABEL(_Level,FromVector):
movi a2, LABEL(_Pri_,_Stack) + PRI_N_STACK_SIZE // get ptr to save area
// interlock
// Save a few registers so we can do some work:
s32i a0, a2, HESF_AR(0)
#if HAVE_XSR
//movi a0, LABEL(_Level,FromVector) // this dispatcher's address
movi a0, LABEL(_Pri_,_HandlerAddress) // dispatcher address var.
s32i a1, a2, HESF_AR(1)
l32i a0, a0, 0 // get dispatcher address
s32i a3, a2, HESF_AR(3)
xsr a0, EXCSAVE_LEVEL // get saved a2, restore dispatcher address
#else
rsr a0, EXCSAVE_LEVEL // get saved a2
s32i a1, a2, HESF_AR(1)
s32i a3, a2, HESF_AR(3)
#endif
s32i a4, a2, HESF_AR(4)
s32i a0, a2, HESF_AR(2)
// Save/restore all exception state
// (IMPORTANT: this code assumes no general exceptions occur
// during the execution of this dispatcher until this state
// is completely saved and from the point it is restored.)
//
// Exceptions that may normally occur within the C handler
// include window exceptions (affecting EPC1), alloca exceptions
// (affecting EPC1/EXCCAUSE and its handling uses EXCSAVE1),
// and possibly others depending on the particular C handler
// (possibly needing save/restore of EXCVADDR; and EXCVADDR
// is also possibly corrupted by any access thru an auto-refill
// way on a processor with a full MMU).
//
rsr a3, EPC1
rsr a4, EXCCAUSE
s32i a3, a2, HESF_EPC1
s32i a4, a2, HESF_EXCCAUSE
#if !XCHAL_HAVE_XEA1
rsr a3, EXCVADDR
s32i a3, a2, HESF_EXCVADDR
#endif
rsr a4, EXCSAVE1
s32i a4, a2, HESF_EXCSAVE1
#ifdef __XTENSA_WINDOWED_ABI__
// Save remainder of entire address register file (!):
movi a0, XCHAL_NUM_AREGS - 8 // how many saved so far
#endif
s32i a5, a2, HESF_AR(5)
s32i a6, a2, HESF_AR(6)
s32i a7, a2, HESF_AR(7)
1: s32i a8, a2, HESF_AR(8)
s32i a9, a2, HESF_AR(9)
s32i a10, a2, HESF_AR(10)
s32i a11, a2, HESF_AR(11)
s32i a12, a2, HESF_AR(12)
s32i a13, a2, HESF_AR(13)
s32i a14, a2, HESF_AR(14)
s32i a15, a2, HESF_AR(15)
#ifdef __XTENSA_WINDOWED_ABI__
addi a8, a0, -8
addi a10, a2, 8*4
rotw 2
bnez a0, 1b // loop until done
rotw 2
// back to original a2 ...
// Save a few other registers required for C:
rsr a3, WINDOWSTART
rsr a4, WINDOWBASE
s32i a3, a2, HESF_WINDOWSTART
s32i a4, a2, HESF_WINDOWBASE
// Setup window registers for first caller:
movi a3, 1
movi a4, 0
wsr a3, WINDOWSTART
wsr a4, WINDOWBASE
rsync
// Note: register window has rotated, ie. a0..a15 clobbered.
#endif /* __XTENSA_WINDOWED_ABI__ */
movi a1, LABEL(_Pri_,_Stack) + PRI_N_STACK_SIZE // get ptr to save area (is also initial stack ptr)
movi a0, 0 // mark start of call frames in stack
// Critical state saved, a bit more to do to allow window exceptions...
// We now have a C-coherent stack and window state.
// Still have to fix PS while making sure interrupts stay disabled
// at the appropriate level (ie. level 2 and below are disabled in this case).
#if XCHAL_HAVE_XEA1
movi a7, _xtos_intstruct // address of interrupt management globals
rsilft a3, _INTERRUPT_LEVEL, XTOS_LOCKLEVEL // lockout
movi a4, ~INTLEVEL_N_BELOW_MASK // mask out all interrupts at this level or lower
l32i a3, a7, XTOS_VPRI_ENABLED_OFS // read previous _xtos_vpri_enabled
l32i a5, a7, XTOS_ENABLED_OFS // read _xtos_enabled
s32i a4, a7, XTOS_VPRI_ENABLED_OFS // set new _xtos_vpri_enabled (mask interrupts as if at _INTERRUPT_LEVEL)
s32i a3, a1, HESF_VPRI // save previous vpri
movi a2, 0x50020 // WOE=1, UM=1, INTLEVEL=0
and a3, a5, a4 // mask out selected interrupts
wsr a3, INTENABLE // disable all low-priority interrupts
#else
// Load PS for C code, clear EXCM (NOTE: this step is different for XEA1):
# ifdef __XTENSA_CALL0_ABI__
movi a2, 0x00020 + _INTERRUPT_LEVEL // WOE=0, CALLINC=0, UM=1, INTLEVEL=N, EXCM=0, RING=0
# else
movi a2, 0x50020 + _INTERRUPT_LEVEL // WOE=1, CALLINC=1, UM=1, INTLEVEL=N, EXCM=0, RING=0
# endif
#endif
wsr a2, PS // update PS to enable window exceptions, etc as per above
rsync
// Okay, window exceptions can now happen (although we have to call
// deep before any will happen because we've reset WINDOWSTART).
// Save other state that might get clobbered by C code:
////////////////// COMMON DISPATCH CODE BEGIN
rsr a14, SAR
s32i a14, a1, HESF_SAR
#if XCHAL_HAVE_LOOPS
rsr a14, LCOUNT
s32i a14, a1, HESF_LCOUNT
rsr a14, LBEG
s32i a14, a1, HESF_LBEG
rsr a14, LEND
s32i a14, a1, HESF_LEND
#endif
#if XCHAL_HAVE_MAC16
rsr a14, ACCLO
s32i a14, a1, HESF_ACCLO
rsr a14, ACCHI
s32i a14, a1, HESF_ACCHI
#endif
#if MULTIPLE_INTERRUPTS /* > 1 interrupts at this priority */ // _split_ multi_setup
#define TABLE_OFS 0
rsr a15, INTERRUPT // mask of pending interrupts
# if XCHAL_HAVE_XEA1
l32i a12, a7, XTOS_ENABLED_OFS // mask of enabled interrupts
# else
rsr a12, INTENABLE // mask of enabled interrupts
# endif
movi a13, INTLEVEL_N_MASK // mask of interrupts at this priority level
and a15, a15, a12
and a15, a15, a13 // enabled & pending interrupts at this priority
_beqz a15, LABEL(Pri_,_spurious) // handle spurious interrupts (eg. level-trig.)
LABEL(Pri_,_loop): // handle all enabled & pending interrupts
neg a14, a15
and a14, a14, a15 // single-out least-significant bit set in mask
wsr a14, INTCLEAR // clear if edge-trig. or s/w or wr/err (else no effect)
// Compute pointer to interrupt table entry, given mask a14 with single bit set:
# if XCHAL_HAVE_NSA
movi a12, _xtos_interrupt_table - (32-XCHAL_NUM_INTERRUPTS)*8
nsau a14, a14 // get index of bit in a14, numbered from msbit
addx8 a12, a14, a12
# else /* XCHAL_HAVE_NSA */
movi a12, _xtos_interrupt_table // pointer to interrupt table
bltui a14, 0x10000, 1f // in 16 lsbits? (if so, check them)
addi a12, a12, 16*8 // no, index is at least 16 entries further
// (the above ADDI expands to an ADDI+ADDMI sequence, +128 is outside its range)
extui a14, a14, 16,16 // shift right upper 16 bits
1: bltui a14, 0x100, 1f // in 8 lsbits? (if so, check them)
addi a12, a12, 8*8 // no, index is at least 8 entries further
srli a14, a14, 8 // shift right upper 8 bits
1: bltui a14, 0x10, 1f // in 4 lsbits? (if so, check them)
addi a12, a12, 4*8 // no, index is at least 4 entries further
srli a14, a14, 4 // shift right 4 bits
1: bltui a14, 0x4, 1f // in 2 lsbits? (if so, check them)
addi a12, a12, 2*8 // no, index is at least 2 entries further
srli a14, a14, 2 // shift right 2 bits
1: bltui a14, 0x2, 1f // is it the lsbit?
addi a12, a12, 1*8 // no, index is one entry further
1: // done! a12 points to interrupt's table entry
# endif /* XCHAL_HAVE_NSA */
#else /* !MULTIPLE_INTERRUPTS */
# if XCHAL_HAVE_NSA
# define TABLE_OFS 8 * (XCHAL_NUM_INTERRUPTS - 1 - INTLEVEL_N_NUM)
# else
# define TABLE_OFS 8 * INTLEVEL_N_NUM
# endif
movi a13, INTLEVEL_N_MASK // (if interrupt is s/w or edge-triggered or write/err only)
movi a12, _xtos_interrupt_table // get pointer to its interrupt table entry
wsr a13, INTCLEAR // clear the interrupt (if s/w or edge or wr/err only)
#endif /* ifdef MULTIPLE_INTERRUPTS */
l32i a13, a12, TABLE_OFS + 0 // get pointer to handler from table entry
#ifdef __XTENSA_CALL0_ABI__
l32i a2, a12, TABLE_OFS + 4 // pass single argument to C handler
callx0 a13 // call interrupt's C handler
#else
l32i a6, a12, TABLE_OFS + 4 // pass single argument to C handler
callx4 a13 // call interrupt's C handler
#endif
#if XCHAL_HAVE_XEA1
movi a7, _xtos_intstruct // address of interrupt management globals
#endif
#if MULTIPLE_INTERRUPTS /* > 1 interrupts at this priority */
rsr a15, INTERRUPT // get pending interrupts
# if XCHAL_HAVE_XEA1
l32i a12, a7, XTOS_ENABLED_OFS // get enabled interrupts
# else
rsr a12, INTENABLE // get enabled interrupts
# endif
movi a13, INTLEVEL_N_MASK // get mask of interrupts at this priority level
and a15, a15, a12
and a15, a15, a13 // pending+enabled interrupts at this priority
_bnez a15, LABEL(Pri_,_loop) // if any remain, dispatch one
LABEL(Pri_,_spurious):
#endif /* MULTIPLE_INTERRUPTS */
// Restore everything, and return.
// Three temp registers are required for this code to be optimal (no interlocks) in
// T2xxx microarchitectures with 7-stage pipe; otherwise only two
// registers would be needed.
//
#if XCHAL_HAVE_LOOPS
l32i a13, a1, HESF_LCOUNT
l32i a14, a1, HESF_LBEG
l32i a15, a1, HESF_LEND
wsr a13, LCOUNT
wsr a14, LBEG
wsr a15, LEND
#endif
#if XCHAL_HAVE_MAC16
l32i a13, a1, HESF_ACCLO
l32i a14, a1, HESF_ACCHI
wsr a13, ACCLO
wsr a14, ACCHI
#endif
l32i a15, a1, HESF_SAR
wsr a15, SAR
////////////////// COMMON DISPATCH CODE END
#if XCHAL_HAVE_XEA1
// Here, a7 = address of interrupt management globals
l32i a4, a1, HESF_VPRI // restore previous vpri
rsil a3, XTOS_LOCKLEVEL // lockout
l32i a5, a7, XTOS_ENABLED_OFS // read _xtos_enabled
s32i a4, a7, XTOS_VPRI_ENABLED_OFS // set new _xtos_vpri_enabled
movi a2, 0x00020 + _INTERRUPT_LEVEL // WOE=0, UM=1, INTLEVEL=N
and a3, a5, a4 // mask out selected interrupts
wsr a3, INTENABLE // disable all low-priority interrupts
#else
// Load PS for interrupt exit, set EXCM:
movi a2, 0x00030 + _INTERRUPT_LEVEL // WOE=0, CALLINC=0, UM=1, INTLEVEL=N, EXCM=1, RING=0
#endif
wsr a2, PS // update PS to disable window exceptions, etc as per above
rsync
// NOTE: here for XEA1, restore INTENABLE etc...
#ifdef __XTENSA_WINDOWED_ABI__
// Restore window registers:
l32i a2, a1, HESF_WINDOWSTART
l32i a3, a1, HESF_WINDOWBASE
wsr a2, WINDOWSTART
wsr a3, WINDOWBASE
rsync
// Note: register window has rotated, ie. a0..a15 clobbered.
// Reload initial stack pointer:
movi a1, LABEL(_Pri_,_Stack) + PRI_N_STACK_SIZE // - 16
movi a6, XCHAL_NUM_AREGS - 8 // how many saved so far
addi a7, a1, -8*4
// Restore entire register file (!):
1:
addi a14, a6, -8
addi a15, a7, 8*4
l32i a4, a15, HESF_AR(4)
l32i a5, a15, HESF_AR(5)
l32i a6, a15, HESF_AR(6)
l32i a7, a15, HESF_AR(7)
l32i a8, a15, HESF_AR(8)
l32i a9, a15, HESF_AR(9)
l32i a10,a15, HESF_AR(10)
l32i a11,a15, HESF_AR(11)
rotw 2
bnez a6, 1b // loop until done
l32i a4, a7, HESF_AR(12)
l32i a5, a7, HESF_AR(13)
l32i a6, a7, HESF_AR(14)
l32i a7, a7, HESF_AR(15)
rotw 2
// back to original a1 ...
#else /* Call0 ABI: */
l32i a4, a1, HESF_AR(4) // restore general registers
l32i a5, a1, HESF_AR(5)
l32i a6, a1, HESF_AR(6)
l32i a7, a1, HESF_AR(7)
l32i a8, a1, HESF_AR(8)
l32i a9, a1, HESF_AR(9)
l32i a10, a1, HESF_AR(10)
l32i a11, a1, HESF_AR(11)
l32i a12, a1, HESF_AR(12)
l32i a13, a1, HESF_AR(13)
l32i a14, a1, HESF_AR(14)
l32i a15, a1, HESF_AR(15)
#endif /* __XTENSA_WINDOWED_ABI__ */
// Restore exception state:
l32i a2, a1, HESF_EPC1
l32i a3, a1, HESF_EXCCAUSE
wsr a2, EPC1
wsr a3, EXCCAUSE
#if !XCHAL_HAVE_XEA1
l32i a2, a1, HESF_EXCVADDR
wsr a2, EXCVADDR
#endif
l32i a3, a1, HESF_EXCSAVE1
wsr a3, EXCSAVE1
l32i a0, a1, HESF_AR(0)
l32i a2, a1, HESF_AR(2)
l32i a3, a1, HESF_AR(3)
l32i a1, a1, HESF_AR(1)
rfi _INTERRUPT_LEVEL
.size LABEL(_Level,FromVector), . - LABEL(_Level,FromVector)
// This symbol exists solely for the purpose of being able to pull-in this
// dispatcher using _xtos_dispatch_level<n>() routines with the tiny-rt LSP:
.global LABEL(_Level,HandlerLabel)
.set LABEL(_Level,HandlerLabel), 0
#endif /* XCHAL_HAVE_INTERRUPTS */