// // int_asm.S - assembly language interrupt utility routines // // $Id: //depot/rel/Foxhill/dot.8/Xtensa/OS/hal/int_asm.S#1 $ // Copyright (c) 2003-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. #include #if XCHAL_HAVE_INTERRUPTS /* Offsets of XtHalVPriState structure members (Xthal_vpri_state variable): */ #define XTHAL_VPRI_VPRI_OFS 0x00 #define XTHAL_VPRI_LOCKLEVEL_OFS 0x01 #define XTHAL_VPRI_LOCKVPRI_OFS 0x02 #define XTHAL_VPRI_PAD0_OFS 0x03 #define XTHAL_VPRI_ENABLED_OFS 0x04 #define XTHAL_VPRI_LOCKMASK_OFS 0x08 #define XTHAL_VPRI_PAD1_OFS 0x0C #define XTHAL_VPRI_ENABLEMAP_OFS 0x10 #define XTHAL_VPRI_RESOLVEMAP_OFS (0x10+0x40*(XCHAL_NUM_INTLEVELS+1)) #define XTHAL_VPRI_END_OFS (0x10+0x40*(XCHAL_NUM_INTLEVELS*2+1)) #endif /* XCHAL_HAVE_INTERRUPTS */ //---------------------------------------------------------------------- // Access INTENABLE register from C //---------------------------------------------------------------------- // unsigned xthal_get_intenable(void) // DECLFUNC(xthal_get_intenable) abi_entry # if XCHAL_HAVE_INTERRUPTS rsr.intenable a2 # else movi a2, 0 // if no INTENABLE (no interrupts), tell caller nothing is enabled # endif abi_return endfunc // void xthal_set_intenable(unsigned) // DECLFUNC(xthal_set_intenable) abi_entry # if XCHAL_HAVE_INTERRUPTS wsr.intenable a2 # endif abi_return endfunc //---------------------------------------------------------------------- // Access INTERRUPT, INTSET, INTCLEAR register from C //---------------------------------------------------------------------- // unsigned xthal_get_interrupt(void) // DECLFUNC (xthal_get_interrupt) abi_entry # if XCHAL_HAVE_INTERRUPTS rsr.interrupt a2 # else movi a2, 0 // if no INTERRUPT (no interrupts), tell caller nothing is pending # endif abi_return endfunc DECLFUNC (xthal_get_intread) abi_entry # if XCHAL_HAVE_INTERRUPTS rsr.interrupt a2 # else movi a2, 0 // if no INTERRUPT (no interrupts), tell caller nothing is pending # endif abi_return endfunc // void xthal_set_intset(unsigned) // DECLFUNC(xthal_set_intset) abi_entry # if XCHAL_HAVE_INTERRUPTS wsr.intset a2 # endif abi_return endfunc // void xthal_set_intclear(unsigned) // DECLFUNC(xthal_set_intclear) abi_entry # if XCHAL_HAVE_INTERRUPTS wsr.intclear a2 # endif abi_return endfunc //---------------------------------------------------------------------- // Virtual PS.INTLEVEL support: // allows running C code at virtual PS.INTLEVEL > 0 // using INTENABLE to simulate the masking that PS.INTLEVEL would do. //---------------------------------------------------------------------- // unsigned xthal_get_vpri(void); DECLFUNC(xthal_get_vpri) abi_entry # if XCHAL_HAVE_INTERRUPTS movi a2, Xthal_vpri_state l8ui a2, a2, XTHAL_VPRI_VPRI_OFS # else movi a2, 0 // no interrupts, report we're always at level 0 # endif abi_return endfunc // unsigned xthal_set_vpri_nw(unsigned) // // Must be called at PS.INTLEVEL <= 1. // Doesn't touch the stack (doesn't reference a1 at all). // Normally, PS should be restored with a6 after return from this call // (it isn't restored automatically because some exception handlers // want to keep ints locked for a while). // // On entry: // a2 = new virtual interrupt priority (0x00 .. 0x1F) // a3-a6 = undefined // PS.INTLEVEL <= 1 // On exit: // a2 = previous virtual interrupt priority (0x0F .. 0x1F, or 0 if no interrupts) // a3-a5 = clobbered // a6 = PS as it was on entry // PS.INTLEVEL = 1 // !!!!!!!!! PS.WOE = 0 (but not if there are no interrupts; is this really needed???) // INTENABLE = updated according to new vpri _SYM(xthal_set_vpri_nw) # if XCHAL_HAVE_INTERRUPTS /* Make sure a2 is in the range 0x0F .. 0x1F: */ movi a3, 0x1F // highest legal virtual interrupt priority sub a4, a2, a3 // (a4 = newlevel - maxlevel) movgez a2, a3, a4 // newlevel = maxlevel if (newlevel - maxlevel) >= 0 movi a3, 15 // lowest legal virtual interrupt priority sub a4, a2, a3 // (a4 = newlevel - 15) movltz a2, a3, a4 // newlevel = 15 if newlevel < 15 xthal_set_vpri_nw_common: movi a4, Xthal_vpri_state // address of vpri state structure /* * Lockout interrupts for exclusive access to virtual priority structure * while we examine and modify it. * Note that we accessed a4 and don't access any further than a6, * so we won't cause any spills, so we could leave WOE enabled (if it is), * but we clear it because that might be what the caller wants, * and is cleaner. */ // Get PS and mask off INTLEVEL: rsil a6, 1 // save a6 = PS, set PS.INTLEVEL = 1 // Clear PS.WOE. (Can we get rid of this?!!!!!): movi a3, ~0x00040000 // mask to... rsr.ps a5 // get and save a6 = PS //a2,a3,a4,a5,a6 and a5, a5, a3 // ... clear a5.WOE wsr.ps a5 // clear PS.WOE rsync //a2,a4,a6 /* Get mask of interrupts to be turned off at requested level: */ l32i a5, a4, XTHAL_VPRI_ENABLED_OFS // get the global mask addx4 a3, a2, a4 // a3 = a4 + a2*4 (index into enablemap[] array) //a2,a3,a4,a5,a6 l32i a3, a3, XTHAL_VPRI_ENABLEMAP_OFS // get the per-level mask and a3, a5, a3 // new INTENABLE value according to new intlevel wsr.intenable a3 // set it! //a2,a4,a6 l8ui a5, a4, XTHAL_VPRI_VPRI_OFS // previous virtual priority s8i a2, a4, XTHAL_VPRI_VPRI_OFS // new virtual priority // Let the caller restore PS: //wsr.ps a6 // restore PS.INTLEVEL //rsync mov a2, a5 // return previous virtual intlevel # else /* ! XCHAL_HAVE_INTERRUPTS */ xthal_set_vpri_nw_common: # if XCHAL_HAVE_EXCEPTIONS rsr.ps a6 // return PS for caller to restore # else movi a6, 0 # endif movi a2, 0 // no interrupts, report we're always at virtual priority 0 # endif /* XCHAL_HAVE_INTERRUPTS */ ret endfunc // unsigned xthal_set_vpri_intlevel_nw(unsigned); // // Same as xthal_set_vpri_nw() except that it accepts // an interrupt level rather than a virtual interrupt priority. // This just converts intlevel to vpri and jumps to xthal_set_vpri_nw. _SYM(xthal_set_vpri_intlevel_nw) # if XCHAL_HAVE_INTERRUPTS movi a3, 0x10 movnez a2, a3, a2 // a2 = (a2 ? 0x10 : 0) addi a2, a2, 0x0F // a2 += 0x0F # endif j xthal_set_vpri_nw_common // set vpri to a2 endfunc // unsigned xthal_set_vpri (unsigned newvpri); // // Normal windowed call (PS.INTLEVEL=0 and PS.WOE=1 on entry and exit). // (PS.UM = 0 or 1) // // Returns previous virtual interrupt priority // (0x0F .. 0x1F, or 0 if no interrupts). // // On entry: // a2 = new virtual interrupt priority (0x00 .. 0x1F) // On exit: // a2 = previous vpri // INTENABLE = updated according to new vpri DECLFUNC(xthal_set_vpri) abi_entry # if XCHAL_HAVE_INTERRUPTS /* Make sure a2 is in the range 0x0F .. 0x1F: */ movi a3, 0x1F // highest legal virtual interrupt priority sub a4, a2, a3 // (a4 = newlevel - maxlevel) movgez a2, a3, a4 // newlevel = maxlevel if (newlevel - maxlevel) >= 0 movi a3, 15 // lowest legal virtual interrupt priority sub a4, a2, a3 // (a4 = newlevel - 15) movltz a2, a3, a4 // newlevel = 15 if newlevel < 15 xthal_set_vpri_common1: movi a4, Xthal_vpri_state // address of vpri state structure /* * Lockout interrupts for exclusive access to virtual priority structure * while we examine and modify it. * Note that we accessed a4 and don't access any further than a6, * so we won't cause any spills, so we can leave WOE enabled. */ // Get PS and mask off INTLEVEL: rsil a6, 1 // save a6 = PS, set PS.INTLEVEL = 1 l8ui a7, a4, XTHAL_VPRI_VPRI_OFS // previous virtual priority (vpri) /* Get mask of interrupts to be turned off at requested level: */ l32i a5, a4, XTHAL_VPRI_ENABLED_OFS // get the global mask addx4 a3, a2, a4 // a3 = a4 + a2*4 (index into enablemap[] array) l32i a3, a3, XTHAL_VPRI_ENABLEMAP_OFS // get the per-level mask s8i a2, a4, XTHAL_VPRI_VPRI_OFS // new virtual priority (in load-slot) and a3, a5, a3 // new INTENABLE value according to new intlevel wsr.intenable a3 // set it! wsr.ps a6 // restore PS.INTLEVEL rsync mov a2, a7 // return previous vpri # else /* ! XCHAL_HAVE_INTERRUPTS */ movi a2, 0 // no interrupts, report we're always at virtual priority 0 # endif /* XCHAL_HAVE_INTERRUPTS */ abi_return endfunc // unsigned xthal_set_vpri_intlevel (unsigned intlevel); // // Equivalent to xthal_set_vpri(XTHAL_VPRI(intlevel,0xF)). // This just converts intlevel to vpri and jumps inside xthal_set_vpri. DECLFUNC(xthal_set_vpri_intlevel) abi_entry # if XCHAL_HAVE_INTERRUPTS movi a3, 0x10 movnez a2, a3, a2 // a2 = (a2 ? 0x10 : 0) addi a2, a2, 0x0F // a2 += 0x0F j xthal_set_vpri_common1 // set vpri to a2 # else movi a2, 0 // no interrupts, report we're always at virtual priority 0 abi_return # endif endfunc // unsigned xthal_set_vpri_lock (void); // // Equivalent to xthal_set_vpri(0x1F); // Returns previous virtual interrupt priority. // DECLFUNC(xthal_set_vpri_lock) abi_entry # if XCHAL_HAVE_INTERRUPTS movi a2, 0x1F // lock at intlevel 1 j xthal_set_vpri_common1 # else movi a2, 0 // no interrupts, report we're always at virtual priority 0 abi_return # endif endfunc // unsigned xthal_get_intpending_nw(void) // // Of the pending level-1 interrupts, returns // the bitmask of interrupts at the highest software priority, // and the index of the first of these. // It also disables interrupts of that software priority and lower // via INTENABLE. // // On entry: // a0 = return PC // a1 = sp // a2-a6 = (available) (undefined) // PS.INTLEVEL = 1 // PS.WOE = 0 // On exit: // a0 = return PC // a1 = sp (NOTE: stack is untouched, a1 is never referenced) // a2 = index of first highest-soft-pri pending l1 interrupt (0..31), or -1 if none // a3 = bitmask of highest-soft-pri pending l1 interrupts (0 if none) (may be deprecated) // a4 = (clobbered) // a5 = new vpri (not typically used by caller? so might get deprecated...?) // a6 = old vpri (eg. to be saved as part of interrupt context's state) // INTENABLE = updated according to new vpri // INTERRUPT bit cleared for interrupt returned in a2 (if any), if software or edge-triggered or write-error // all others = preserved _SYM(xthal_get_intpending_nw) # if XCHAL_HAVE_INTERRUPTS // Give us one more register to play with //wsr.excsave1 a4 // Figure out which interrupt to process /* Perform a binary search to find a mask of the interrupts that are ready at the highest virtual priority level. Xthal_vpri_resolvemap is a binary tree implemented within an array, sorted by priority: each node contains the set of interrupts in the range of priorities corresponding to the right half of its branch. The mask of enabled & pending interrupts is compared with each node to determine in which subbranch (left or right) the highest priority one is present. After 4 such masks and comparisons (for 16 priorities), we have determined the priority of the highest priority enabled&pending interrupt. Table entries for intlevel 'i' are bitmasks defined as follows (map=Xthal_vpri_resolvemap[i-1]): map[8+(x=0)] = ints at pri x + 8..15 (8-15) map[4+(x=0,8)] = ints at pri x + 4..7 (4-7,12-15) map[2+(x=0,4,8,12)] = ints at pri x + 2..3 (2-3,6-7,10-11,14-15) map[1+(x=0,2..12,14)] = ints at pri x + 1 (1,3,5,7,9,11,13,15) map[0] = 0 (unused; for alignment) */ rsr.interrupt a4 // a4 = mask of interrupts pending, including those disabled rsr.intenable a2 // a2 = mask of interrupts enabled movi a3, Xthal_vpri_state and a4, a2, a4 // a4 = mask of enabled interrupts pending beqz a4, gipfail // if none (can happen for spurious level-triggered interrupts, // or ???), we're done mov a5, a3 l32i a2, a5, XTHAL_VPRI_RESOLVEMAP_OFS+8*4 bnone a2, a4, 1f addi a5, a5, 8*4 1: l32i a2, a5, XTHAL_VPRI_RESOLVEMAP_OFS+4*4 bnone a2, a4, 1f addi a5, a5, 4*4 1: l32i a2, a5, XTHAL_VPRI_RESOLVEMAP_OFS+2*4 bnone a2, a4, 1f addi a5, a5, 2*4 1: l32i a2, a5, XTHAL_VPRI_RESOLVEMAP_OFS+1*4 bnone a2, a4, 1f addi a5, a5, 1*4 1: # if 0 a5 = address of map ... l32i a2, a5, XTHAL_VPRI_RESOLVEMAP_OFS+8*4 addi a?, a5, 8*4 and a2, a2, a4 movnez a5, a?, a2 l32i a2, a5, XTHAL_VPRI_RESOLVEMAP_OFS+4*4 addi a?, a5, 4*4 and a2, a2, a4 movnez a5, a?, a2 l32i a2, a5, XTHAL_VPRI_RESOLVEMAP_OFS+2*4 addi a?, a5, 2*4 and a2, a2, a4 movnez a5, a?, a2 l32i a2, a5, XTHAL_VPRI_RESOLVEMAP_OFS+1*4 addi a?, a5, 1*4 and a2, a2, a4 movnez a5, a?, a2 # endif // Here: // a3 = Xthal_vpri_state // a5 = Xthal_vpri_state + softpri*4 // a4 = mask of enabled interrupts pending // a2,a6 = available // Lock interrupts during virtual priority data structure transaction: //rsil a6, 1 // set PS.INTLEVEL = 1 (a6 ignored) // a2,a6 = available // The highest priority interrupt(s) in a4 is at softpri = (a5-a3) / 4. // So interrupts in enablemap[1][softpri] are not in a4 (they are higher priority). // The set of interrupts at softpri are: // enablemap[1][softpri-1] - enablemap[1][softpri] // So and'ing a4 with enablemap[1][softpri - 1] will give us // the set of interrupts pending at the highest soft priority. // l32i a2, a5, XTHAL_VPRI_ENABLEMAP_OFS + 16*4 - 4 // get enablemap[1][softpri-1] and a4, a2, a4 // only keep interrupts of highest pri (softpri) // a4 now has mask of pending interrupts at highest ready level (new vpri) // Update INTENABLE for this new virtual priority l32i a2, a5, XTHAL_VPRI_ENABLEMAP_OFS + 16*4 // get vpri-specific mask = enablemap[1][softpri] l32i a6, a3, XTHAL_VPRI_ENABLED_OFS // get global mask sub a5, a5, a3 // a5 = softpri * 4 (for below; here for efficiency) and a2, a2, a6 // and together wsr.intenable a2 // disable interrupts at or below new vpri // a2,a6 = available // Update new virtual priority: l8ui a6, a3, XTHAL_VPRI_VPRI_OFS // get old vpri (returned) srli a5, a5, 2 // a5 = softpri (0..15) addi a5, a5, 0x10 // a5 = 0x10 + softpri = new virtual priority s8i a5, a3, XTHAL_VPRI_VPRI_OFS // store new vpri (returned) // Undo the temporary lock (if was at PS.INTLEVEL > 1): //rsil a2, 1 mov a3, a4 // save for the caller (in case it wants it?) // Choose one of the set of highest-vpri pending interrupts to process. // For speed (and simplicity), use this simple two-instruction sequence // to select the least significant bit set in a4. This implies that // interrupts with a lower interrupt number take precedence over those // with a higher interrupt number (!!). // neg a2, a4 // keep only the least-significant bit that is set... and a4, a2, a4 // ... in a4 // Software, edge-triggered, and write-error interrupts are cleared by writing to the // INTCLEAR pseudo-reg (to clear relevant bits of the INTERRUPT register). // To simplify interrupt handlers (so they avoid tracking which type of // interrupt they handle and act accordingly), clear such interrupts here. // To avoid race conditions, the clearing must occur *after* we undertake // to process the interrupt, and *before* actually handling the interrupt. // Interrupt handlers may additionally clear the interrupt themselves // at appropriate points if needed to avoid unnecessary interrupts. // #define CLEARABLE_INTLEVEL1_MASK (XCHAL_INTLEVEL1_MASK & XCHAL_INTCLEARABLE_MASK) # if CLEARABLE_INTLEVEL1_MASK != 0 //movi a2, CLEARABLE_INTLEVEL1_MASK //and a2, a2, a4 //wsr.intclear a2 wsr.intclear a4 // no effect if a4 not a software or edge-triggered or write-error interrupt # endif // Convert the single-bit interrupt mask to an interrupt number. // (ie. compute log2 using either the NSAU instruction or a binary search) find_ms_setbit a2, a4, a2, 0 // set a2 to index of lsbit set in a4 (0..31) // NOTE: assumes a4 != 0 (otherwise a2 is undefined[?]) // a2 has vector number (0..31) //rsr.excsave1 a4 ret gipfail: l8ui a6, a3, XTHAL_VPRI_VPRI_OFS // get old vpri mov a5, a6 // is also new vpri (unchanged) # else /* XCHAL_HAVE_INTERRUPTS */ // No interrupts configured! movi a5, 0 // return zero new vpri movi a6, 0 // return zero old vpri # endif /* XCHAL_HAVE_INTERRUPTS */ movi a2, -1 // return bogus vector number (eg. can be quickly tested for negative) movi a3, 0 // return zero bitmask of interrupts pending ret endfunc // ----------------------------------------------------------------- // void xthal_vpri_lock() // // Used internally by the Core HAL to block interrupts of higher or equal // priority than Xthal_vpri_locklevel during virtual interrupt operations. // DECLFUNC(xthal_vpri_lock) abi_entry # if XCHAL_HAVE_INTERRUPTS rsil a6, 1 // save a6 = PS, set PS.INTLEVEL = 1 // if( Xthal_vpri_level < Xthal_vpri_locklevel ) // movi a2, Xthal_vpri_state // a2 := address of global var. Xthal_vpri_state //interlock l8ui a3, a2, XTHAL_VPRI_VPRI_OFS // a3 := Xthal_vpri_level == Xthal_vpri_state.vpri l8ui a5, a2, XTHAL_VPRI_LOCKLEVEL_OFS // a5 := Xthal_vpri_locklevel l32i a4, a2, XTHAL_VPRI_ENABLED_OFS // a4 := Xthal_vpri_enabled bgeu a3, a5, xthal_vpri_lock_done // xthal_set_intenable( Xthal_vpri_enablemap[0][Xthal_vpri_locklevel] & Xthal_vpri_enabled ); // addx4 a3, a5, a2 // a3 := a2 + a5*4 (index into enablemap[] array) l32i a3, a3, XTHAL_VPRI_ENABLEMAP_OFS // a3 := Xthal_vpri_enablemap[0][Xthal_vpri_locklevel] //interlock and a2, a4, a3 wsr.intenable a2 xthal_vpri_lock_done: wsr.ps a6 // restore PS.INTLEVEL rsync # endif abi_return endfunc // void xthal_vpri_unlock(void) // // Enable interrupts according to the current virtual interrupt priority. // This effectively "unlocks" interrupts disabled by xthal_vpri_lock() // (assuming the virtual interrupt priority hasn't changed). // DECLFUNC(xthal_vpri_unlock) abi_entry # if XCHAL_HAVE_INTERRUPTS // // This should be free of race-conditions. // // xthal_set_intenable( Xthal_vpri_enablemap[0][Xthal_vpri_level] & Xthal_vpri_enabled ); // movi a2, Xthal_vpri_state // a2 := address of global var. Xthal_vpri_state //interlock l8ui a3, a2, XTHAL_VPRI_VPRI_OFS // a3 := Xthal_vpri_level == Xthal_vpri_state.vpri l32i a4, a2, XTHAL_VPRI_ENABLED_OFS // a4 := Xthal_vpri_enabled addx4 a3, a3, a2 // a3 := a2 + a3*4 (index into enablemap[] array) l32i a3, a3, XTHAL_VPRI_ENABLEMAP_OFS // a3 := Xthal_vpri_enablemap[0][Xthal_vpri_level] //interlock and a2, a4, a3 wsr.intenable a2 # endif abi_return endfunc