/* Interrupt driver + dispatcher for the discovery host controller */ /* Author: T. Straumann, 2005-2007 * * Acknowledgements: * Valuable information was obtained from the following drivers * netbsd: (C) Allegro Networks Inc; Wasabi Systems Inc. * linux: (C) MontaVista, Software, Inc; Chris Zankel, Mark A. Greer. * rtems: (C) Brookhaven National Laboratory; K. Feng * but this implementation is original work by the author. */ /* * Authorship * ---------- * This software ('beatnik' RTEMS BSP for MVME6100 and MVME5500) was * created by Till Straumann , 2005-2007, * Stanford Linear Accelerator Center, Stanford University. * * Acknowledgement of sponsorship * ------------------------------ * The 'beatnik' BSP was produced by * the Stanford Linear Accelerator Center, Stanford University, * under Contract DE-AC03-76SFO0515 with the Department of Energy. * * Government disclaimer of liability * ---------------------------------- * Neither the United States nor the United States Department of Energy, * nor any of their employees, makes any warranty, express or implied, or * assumes any legal liability or responsibility for the accuracy, * completeness, or usefulness of any data, apparatus, product, or process * disclosed, or represents that its use would not infringe privately owned * rights. * * Stanford disclaimer of liability * -------------------------------- * Stanford University makes no representations or warranties, express or * implied, nor assumes any liability for the use of this software. * * Stanford disclaimer of copyright * -------------------------------- * Stanford University, owner of the copyright, hereby disclaims its * copyright and all other rights in this software. Hence, anyone may * freely use it for any purpose without restriction. * * Maintenance of notices * ---------------------- * In the interest of clarity regarding the origin and status of this * SLAC software, this and all the preceding Stanford University notices * are to remain affixed to any copy or derivative of this software made * or distributed by the recipient and are to be affixed to any copy of * software made or distributed by the recipient that contains a copy or * derivative of this software. * * ------------------ SLAC Software Notices, Set 4 OTT.002a, 2004 FEB 03 */ #include #include #include #include #include #include #include #include #include /* dont change the order (main_lo, main_hi, gpp) which * matches the interrupt numbers! */ #define MAIN_LO_IDX 0 #define MAIN_HI_IDX 1 #define GPP_IDX 2 #define NUM_INTR_REGS 3 #define SYNC() __asm__ volatile("sync") /* How many times should the ISR dispatcher check for * pending interrupts until it decides that something's * fishy (i.e., a user ISR fails to clear the interrupt * source) */ #define MAX_SPIN_LOOPS 100 /* If FASTER is defined, a few obscure I/O statements found in the linux * driver are removed */ #define FASTER /* Array helper */ #define NumberOf(arr) (sizeof(arr)/sizeof((arr)[0])) /* MVME6100 specific; re-define watchdog NMI pin to be a normal output * so we have a way to raise an interrupt in software (GPP[26] is wired to * GPP[6] on the MVME6100). */ #define MVME6100_IRQ_DEBUG 4 #define GPP_WIRED_OUT_BIT_6100 26 /* CAVEAT: this is bit 26 on the 6100 */ #define GPP_WIRED_OUT_BIT_5500 24 /* CAVEAT: this is bit 24 on the 5500 */ #define GPP_WIRED_IN_BIT 6 /* Ored mask of debugging features to enable */ #define IRQ_DEBUG_BASIC 1 /* This is _very_ lowlevel */ #define IRQ_DEBUG_DISPATCHER 2 /* Record maximal dispatching latency */ #define IRQ_DEBUG_MAXLAT 8 /* PPC only */ #define IRQ_DEBUG (0 /*|(IRQ_DEBUG_BASIC)*/|(MVME6100_IRQ_DEBUG)|(IRQ_DEBUG_MAXLAT)) /********** * Typedefs **********/ /* Set of the three relevant cause registers */ typedef volatile unsigned IrqMask[NUM_INTR_REGS]; #define REGP(x) ((volatile uint32_t *)(x)) /* Information we keep about the PIC */ typedef struct _Mv64x60PicRec { /* base address as seen from CPU */ uintptr_t reg_base; /* addresses of 'cause' registers */ volatile uint32_t *causes[NUM_INTR_REGS]; /* addresses of 'mask' registers */ volatile uint32_t *masks[NUM_INTR_REGS]; /* masks for all priorities. If an * interrupt source has priority X, * its corresponding bit is set * (enabled) in mcache[i] for all * i < X and cleared for i >= X */ volatile IrqMask mcache[BSP_IRQ_MAX_PRIO+1]; /* Priority we're executing at. * Thread-level is priority 0, * ISRs range from 1..MAX_PRIO */ volatile rtems_irq_prio current_priority; } Mv64x60PicRec, *Mv64x60Pic; /********** * Globals **********/ /* Copy of the configuration */ static rtems_irq_global_settings theConfig; /* PIC description */ static Mv64x60PicRec thePic; #if (IRQ_DEBUG) & MVME6100_IRQ_DEBUG static unsigned long gpp_out_bit = 0; #endif #if (IRQ_DEBUG) & IRQ_DEBUG_MAXLAT unsigned long discovery_pic_max_dispatching_latency = 0; #ifdef __PPC__ static inline unsigned long mftb(void) { unsigned long rval; asm volatile("mftb %0":"=r"(rval)); return rval; } #else #define mftb() 0 #endif #endif /********** * Functions **********/ /* Debugging helper routines */ static void pregs(volatile uint32_t **p) { int i; for (i=NUM_INTR_REGS-1; i>=0; i--) { printk(" 0x%08x", ld_le32(p[i])); printk( i ? " --":"\n"); } } static void pmsks(volatile IrqMask p) { int i; for (i=NUM_INTR_REGS-1; i>=0; i--) { printk(" 0x%08x", p[i]); printk( i ? " --":"\n"); } } void discovery_dump_picregs(void) { printk(" ..GPP_IRQ. -- ..MAIN_HI. -- ..MAIN_LO.\n"); printk("Cause:"); pregs(thePic.causes); printk("Mask: "); pregs(thePic.masks); } /* Small inline helpers */ /* return 0 if this PIC is not 'responsible' for a given irq number * we also 'ignore' the GPP summary bits - these must always remain * enabled. */ static inline int validIrqNo(rtems_irq_number irq) { return irq >= BSP_PCI_IRQ_LOWEST_OFFSET && irq <= BSP_PCI_IRQ_MAX_OFFSET && ! (IMH_GPP_SUM & (1<<(irq-32))); } /* return 0 if a given priority is outside the valid range */ static inline int validPri(rtems_irq_prio pri) { /* silence compiler warning about limited range of type; * hope it never changes... */ return /* pri>=0 && */ pri <=BSP_IRQ_MAX_PRIO; } /* Return position of the most significant bit that is set in 'x' */ static inline int __ilog2(unsigned x) { asm volatile("cntlzw %0, %0":"=&r"(x):"0"(x)); return 31-x; } /* Convert irq number to cause register index * (array of handles in the PicRec). * ASSUMES: 'irq' within valid range. */ static inline unsigned irqDiv32(unsigned irq) { return (irq-BSP_PCI_IRQ_LOWEST_OFFSET)>>5; } /* Convert irq number to cause/mask bit number. * ASSUMES: 'irq' within valid range. */ static inline unsigned irqMod32(unsigned irq) { return (irq-BSP_PCI_IRQ_LOWEST_OFFSET)&31; } /* NON-ATOMICALLY set/clear bits in a MV64x60 register * * register contents at offset 'off' are ANDed with * complement of the 'clr' mask and ORed with 'set' mask: * * *off = (*off & ~clr) | set * * ASSUMES: executed from IRQ-disabled section */ static inline void gt_bitmod(unsigned off, unsigned set, unsigned clr) { st_le32(REGP(thePic.reg_base + off), (ld_le32(REGP(thePic.reg_base+off)) & ~clr) | set); } static inline unsigned gt_read(unsigned off) { return ld_le32(REGP(thePic.reg_base + off)); } static inline void gt_write(unsigned off, unsigned val) { st_le32(REGP(thePic.reg_base + off), val); } /* Enable interrupt number 'irq' at the PIC. * * Checks for valid arguments but has no way of * communicating violation; prints to console * if illegal arguments are given. * * This routine may be called from ISR level. * * Algorithm: set corresponding bit in masks * for all priorities lower than the * target irq's priority and push * mask for the currently executing * priority out to the PIC. */ void BSP_enable_irq_at_pic(rtems_irq_number irq) { unsigned i,j; unsigned long flags; volatile uint32_t *p; uint32_t v,m; if ( !validIrqNo(irq) ) { /* API change - must silently ignore... printk("BSP_enable_irq_at_pic: Invalid argument (irq #%i)\n",irq); */ return; } #if (IRQ_DEBUG) & IRQ_DEBUG_BASIC printk("IRQ: Enable #%i;",irq); #endif if ( (i=irqDiv32(irq)) > NUM_INTR_REGS ) { /* This is probably a more serious error; don't ignore silently */ printk("BSP_enable_irq_at_pic: illegal argument\n"); return; } /* compute register pointer and bit mask */ p = thePic.masks[i]; m = 1< 0x%08x\n",i,v,ld_le32(p)); #endif } /* Disable interrupt number 'irq' at the PIC. * * Checks for valid arguments but has no way of * communicating violation; prints to console * if illegal arguments are given. * * This routine may be called from ISR level. * * Algorithm: clear corresponding bit in masks * for all priorities and push the * mask for the currently executing * priority out to the PIC. */ int BSP_disable_irq_at_pic(rtems_irq_number irq) { unsigned i,j; unsigned long flags; volatile uint32_t *p; uint32_t v,m; int rval; if ( !validIrqNo(irq) ) { /* API change - must silently ignore... printk("BSP_disable_irq_at_pic: Invalid argument (irq #%i)\n",irq); */ return -1; } #if (IRQ_DEBUG) & IRQ_DEBUG_BASIC printk("IRQ: Disable #%i;",irq); #endif if ( (i=irqDiv32(irq)) > NUM_INTR_REGS ) { /* This is probably a more serious error; don't ignore silently */ printk("BSP_enable_irq_at_pic: illegal argument\n"); return -1; } /* compute register pointer and bit mask */ p = thePic.masks[i]; m = (1< 0x%08x\n",i,v,ld_le32(p)); #endif return rval ? 1 : 0; } int BSP_irq_is_enabled_at_pic(rtems_irq_number irq) { unsigned i; if ( !validIrqNo(irq) ) { printk("BSP_irq_is_enabled_at_pic: Invalid argument (irq #%i)\n",irq); return -1; } if ( (i=irqDiv32(irq)) > NUM_INTR_REGS ) { printk("BSP_enable_irq_at_pic: illegal argument\n"); return -1; } return ld_le32(thePic.masks[i]) & (1<=pri */ int BSP_irq_set_priority(rtems_irq_number irq, rtems_irq_prio pri) { unsigned long flags; volatile uint32_t *p; uint32_t v,m; unsigned i,j; if ( thePic.current_priority > 0 ) { printk("BSP_irq_set_priority: must not be called from ISR level\n"); return -1; } if ( !validPri(pri) ) { printk("BSP_irq_set_priority: invalid argument (pri #%i)\n",pri); return -1; } if ( BSP_DECREMENTER != irq ) { if ( !validIrqNo(irq) ) { printk("BSP_irq_set_priority: invalid argument (irq #%i)\n",irq); return -1; } if ( (i=irqDiv32(irq)) > NUM_INTR_REGS ) { printk("BSP_irq_set_priority: illegal argument (irq #%i not PCI?)\n", irq); return -1; } } #if (IRQ_DEBUG) & IRQ_DEBUG_BASIC printk("IRQ: Set Priority #%i -> %i;",irq,pri); #endif if ( BSP_DECREMENTER == irq ) { theConfig.irqPrioTbl[irq] = pri; return 0; } /* compute register pointer and bit mask */ p = thePic.masks[i]; m = 1< 0x%08x\n",i,v,ld_le32(p)); #endif return 0; } /* Initialize the PIC; routine needed by BSP framework * * RETURNS: NONZERO on SUCCESS, 0 on error! */ int BSP_setup_the_pic(rtems_irq_global_settings* config) { int i; /* * Store copy of configuration */ theConfig = *config; /* check config */ if ( theConfig.irqNb <= BSP_PCI_IRQ_MAX_OFFSET ) { printk("BSP_setup_the_pic: FATAL ERROR: configured IRQ table too small???\n"); return 0; } for ( i=0; i highest priority source is found quickly. It takes at most * * BSP_IRQ_MAX_PRIO * ( ~3 reg-only instructions + 2 memory access ) * + 2 reg-only instructions + 1 I/O + 1 memory access. * * */ static unsigned mlc, mhc, gpc; static int decrementerPending = 0; #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER int decrementerIrqs = 0; #endif static inline unsigned find_highest_priority_pending_irq(rtems_irq_prio *ppri) { register int rval = -1; register rtems_irq_prio *pt = theConfig.irqPrioTbl + BSP_PCI_IRQ_LOWEST_OFFSET; register rtems_irq_prio pmax = *ppri; register unsigned cse,ocse; #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER discovery_dump_picregs(); #endif if ( decrementerPending ) { /* Don't flood #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("Decrementer IRQ pending\n"); #endif */ if ( theConfig.irqPrioTbl[BSP_DECREMENTER] > pmax ) { pmax = theConfig.irqPrioTbl[BSP_DECREMENTER]; rval = BSP_DECREMENTER; } } mlc = cse = ld_le32(thePic.causes[MAIN_LO_IDX]); #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("MAIN_LO; cse: 0x%08x, msk 0x%08x\n", cse ,thePic.mcache[pmax][MAIN_LO_IDX]); #endif while ( cse &= thePic.mcache[pmax][MAIN_LO_IDX] ) { rval = __ilog2(cse); pmax = pt[rval]; #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("Max pri IRQ now %i\n",rval); #endif } mhc = cse = ocse = ld_le32(thePic.causes[MAIN_HI_IDX]); #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("MAIN_HI; cse: 0x%08x, msk 0x%08x\n", cse, thePic.mcache[pmax][MAIN_HI_IDX]); #endif /* don't look at the GPP summary; only check for 'real' MAIN_HI sources */ cse &= ~IMH_GPP_SUM; while ( cse &= thePic.mcache[pmax][MAIN_HI_IDX] ) { rval = __ilog2(cse) + 32; pmax = pt[rval]; #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("Max pri IRQ now %i\n",rval); #endif } gpc = cse = ld_le32(thePic.causes[GPP_IDX ]); /* if there were GPP ints, scan the GPP cause now */ if ( ocse & IMH_GPP_SUM ) { #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("GPP; cse: 0x%08x, msk 0x%08x\n", cse, thePic.mcache[pmax][GPP_IDX ]); #endif cse &= thePic.mcache[pmax][GPP_IDX ]; ocse = cse; while ( cse ) { rval = __ilog2(cse) + 64; pmax = pt[rval]; cse &= thePic.mcache[pmax][GPP_IDX ]; #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("Max pri IRQ now %i\n",rval); #endif } #ifndef FASTER /* this doesn't seem to be necessary -- however, the linux people do it... */ out_le32(thePic.causes[GPP_IDX], ~ocse); #endif } #ifndef FASTER /* this doesn't seem to be necessary -- however, the linux people do it... */ (void)in_le32(thePic.causes[GPP_IDX]); #endif *ppri = pmax; if ( BSP_DECREMENTER == rval ) { #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER decrementerIrqs++; #endif decrementerPending = 0; } return rval; } #if 0 /* TODO: should this be cleaned up ? */ #define _IRQ_DEBUG IRQ_DEBUG_DISPATCHER static inline unsigned ffind_highest_priority_pending_irq(rtems_irq_prio *ppri) { register int rval = -1; register rtems_irq_prio *pt = theConfig.irqPrioTbl + BSP_PCI_IRQ_LOWEST_OFFSET; register rtems_irq_prio pmax = *ppri; register unsigned cse,ocse; #if (_IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER discovery_dump_picregs(); #endif cse = in_le32(thePic.causes[MAIN_LO_IDX]); #if (_IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("MAIN_LO; cse: 0x%08x, msk 0x%08x\n", cse ,thePic.mcache[pmax][MAIN_LO_IDX]); #endif while ( cse &= thePic.mcache[pmax][MAIN_LO_IDX] ) { rval = __ilog2(cse); pmax = pt[rval]; #if (_IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("Max pri IRQ now %i\n",rval); #endif } cse = ocse = in_le32(thePic.causes[MAIN_HI_IDX]); #if (_IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("MAIN_HI; cse: 0x%08x, msk 0x%08x\n", cse, thePic.mcache[pmax][MAIN_HI_IDX]); #endif /* don't look at the GPP summary; only check for 'real' MAIN_HI sources */ cse &= ~IMH_GPP_SUM; while ( cse &= thePic.mcache[pmax][MAIN_HI_IDX] ) { rval = __ilog2(cse) + 32; pmax = pt[rval]; #if (_IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("Max pri IRQ now %i\n",rval); #endif } /* if there were GPP ints, scan the GPP cause now */ if ( ocse & IMH_GPP_SUM ) { cse = in_le32(thePic.causes[GPP_IDX ]); #if (_IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("GPP; cse: 0x%08x, msk 0x%08x\n", cse, thePic.mcache[pmax][GPP_IDX ]); #endif cse &= thePic.mcache[pmax][GPP_IDX ]; ocse = cse; while ( cse ) { rval = __ilog2(cse) + 64; pmax = pt[rval]; cse &= thePic.mcache[pmax][GPP_IDX ]; #if (_IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER printk("Max pri IRQ now %i\n",rval); #endif } /* this doesn't seem to be necessary -- however, the linux people do it... */ out_le32(thePic.causes[GPP_IDX], ~ocse); } /* this doesn't seem to be necessary -- however, the linux people do it... */ (void)in_le32(thePic.causes[GPP_IDX]); *ppri = pmax; return rval; } #endif /* Here's our dispatcher; the BSP framework uses the same one for EE and decrementer * exceptions... */ int C_dispatch_irq_handler (BSP_Exception_frame *frame, unsigned int excNum) { register int irq; int loop, last_irq; rtems_irq_prio pri; #if (IRQ_DEBUG) & IRQ_DEBUG_MAXLAT unsigned long diff; #endif #if (IRQ_DEBUG) & IRQ_DEBUG_MAXLAT diff = mftb(); #endif if (excNum == ASM_DEC_VECTOR) { decrementerPending = 1; } /* Tradeoff: EITHER we loop as long as interrupts are pending * incurring the overhead of one extra run of the 'find_pending_irq' routine. * OR we do rely on the handler just being invoked again if multiple * interrupts are pending. * * The first solution gives better worst-case behavior * the second slightly better average performance. * --> we go for the first solution. This also enables us to catch * runaway interrupts, i.e., bad drivers that don't clear interrupts * at the device. Can be very handy during driver development... */ for ( loop=0, last_irq=-1, pri = thePic.current_priority; (irq=find_highest_priority_pending_irq(&pri)) >=0; loop++, last_irq = irq ) { /* raise priority level and remember current one */ pri = change_executing_prio_level(pri); SYNC(); #if (IRQ_DEBUG) & IRQ_DEBUG_MAXLAT if ( 0 == loop ) { diff = mftb()-diff; if ( diff > discovery_pic_max_dispatching_latency ) discovery_pic_max_dispatching_latency = diff; } #endif #if (IRQ_DEBUG) & IRQ_DEBUG_DISPATCHER if ( BSP_DECREMENTER == irq ) { printk("IRQ: dispatching DECREMENTER\n"); } else { int idx = irqDiv32(irq); printk("IRQ: dispatching #%i; causes[%i]=0x%08x\n", irq, idx, ld_le32(thePic.causes[idx])); } #endif bsp_irq_dispatch_list( theConfig.irqHdlTbl, irq, theConfig.defaultEntry.hdl ); /* restore executing priority level */ (void)change_executing_prio_level(pri); if ( (loop > MAX_SPIN_LOOPS) && (last_irq == irq) ) { /* try to catch run-away interrupts without disabling a 'legal' one; * this should never happen with the decrementer (and * BSP_disable_irq_at_pic(BSP_DECREMENTER) would fail) */ printk("Runaway IRQ #%i; disabling\n", irq); BSP_disable_irq_at_pic(irq); loop = 0; } } if (!loop) { if ( decrementerPending && pri >= theConfig.irqPrioTbl[BSP_DECREMENTER] ) { /* we cannot mask the decrementer interrupt so it is possible that it * gets delivered even though it has a lower priority than what we're * currently executing at. * In this case, we ignore the zero loop count and return; * the interrupted instance of C_dispatch_irq_handler() will eventually * lower the executing priority and catch the 'decrementerPending' flag * we just set. */ } else { printk("Discovery: Spurious interrupt; causes were gpp: 0x%x, mhc: 0x%x, mlc: 0x%x\n", gpc, mhc, mlc); printk("Current priority level %i, decrementerPending %i\n", pri, decrementerPending); { rtems_irq_prio p=pri; printk("PIC register dump:\n"); discovery_dump_picregs(); printk("Current Priority: %i, found %i\n",pri,find_highest_priority_pending_irq(&p)); discovery_dump_picregs(); for (p=0; p<=BSP_IRQ_MAX_PRIO; p++) { printk("M[%i] :",p);pmsks(thePic.mcache[p]); } } } } else if (loop>discovery_pic_max_loops) discovery_pic_max_loops = loop; return 0; } #if (IRQ_DEBUG) & MVME6100_IRQ_DEBUG void discovery_pic_install_debug_irq(void) { switch ( BSP_getBoardType() ) { case MVME6100: gpp_out_bit = GPP_WIRED_OUT_BIT_6100; break; case MVME5500: gpp_out_bit = GPP_WIRED_OUT_BIT_5500; break; default: gpp_out_bit = 0; break; break; } if ( gpp_out_bit ) { unsigned mppoff; switch (gpp_out_bit / 8) { default: /* silence warning; this is never reached */ case 0: mppoff = GT_MPP_Control0; break; case 1: mppoff = GT_MPP_Control1; break; case 2: mppoff = GT_MPP_Control2; break; case 3: mppoff = GT_MPP_Control3; break; } /* switch GPP pin allocated to watchdog (value 4) to * GPP I/O (value 0 ??; have no doc, found out by experimenting) */ gt_bitmod(mppoff, 0, (0xf<<(4*(gpp_out_bit % 8)))); /* make it an output */ gt_bitmod(GT_GPP_IO_Control, (1< GPP_WIRED_IN pins */ void discovery_pic_set_debug_irq(int on) { unsigned long flags, clr; if ( !gpp_out_bit ) { printk("discovery_pic_set_debug_irq(): unknown wire output\n"); return; } if (on) { on = 1<