diff options
author | Robin Getz <robin.getz@analog.com> | 2009-06-22 02:02:16 +0000 |
---|---|---|
committer | Mike Frysinger <vapier@gentoo.org> | 2009-09-16 21:28:28 -0400 |
commit | ae4f073c40bf677b03826262e6022b4a251fe437 (patch) | |
tree | 452c91be30a3970efbea5780d368945e7f63712c /arch | |
parent | d4b834c13940b5433d16ae3605794b3d74804348 (diff) | |
download | linux-ae4f073c40bf677b03826262e6022b4a251fe437.tar.bz2 |
Blackfin: make EVT3->EVT5 lowering more robust wrt IPEND[4]
We handle many exceptions at EVT5 (hardware error level) so that we can
catch exceptions in our exception handling code. Today - if the global
interrupt enable bit (IPEND[4]) is set (interrupts disabled) our trap
handling code goes into a infinite loop, since we need interrupts to be
on to defer things to EVT5.
Normal kernel code should not trigger this for any reason as IPEND[4] gets
cleared early (when doing an interrupt context save) and the kernel stack
there should be sane (or something much worse is happening in the system).
But there have been a few times where this has happened, so this change
makes sure we dump a proper crash message even when things have gone south.
Signed-off-by: Robin Getz <robin.getz@analog.com>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/blackfin/include/asm/pda.h | 1 | ||||
-rw-r--r-- | arch/blackfin/kernel/asm-offsets.c | 1 | ||||
-rw-r--r-- | arch/blackfin/kernel/traps.c | 19 | ||||
-rw-r--r-- | arch/blackfin/mach-common/entry.S | 86 |
4 files changed, 66 insertions, 41 deletions
diff --git a/arch/blackfin/include/asm/pda.h b/arch/blackfin/include/asm/pda.h index b42555c1431c..69b96b40c188 100644 --- a/arch/blackfin/include/asm/pda.h +++ b/arch/blackfin/include/asm/pda.h @@ -50,6 +50,7 @@ struct blackfin_pda { /* Per-processor Data Area */ unsigned long ex_optr; unsigned long ex_buf[4]; unsigned long ex_imask; /* Saved imask from exception */ + unsigned long ex_ipend; /* Saved IPEND from exception */ unsigned long *ex_stack; /* Exception stack space */ #ifdef ANOMALY_05000261 diff --git a/arch/blackfin/kernel/asm-offsets.c b/arch/blackfin/kernel/asm-offsets.c index b5df9459d6d5..8ad4f2c69961 100644 --- a/arch/blackfin/kernel/asm-offsets.c +++ b/arch/blackfin/kernel/asm-offsets.c @@ -145,6 +145,7 @@ int main(void) DEFINE(PDA_EXBUF, offsetof(struct blackfin_pda, ex_buf)); DEFINE(PDA_EXIMASK, offsetof(struct blackfin_pda, ex_imask)); DEFINE(PDA_EXSTACK, offsetof(struct blackfin_pda, ex_stack)); + DEFINE(PDA_EXIPEND, offsetof(struct blackfin_pda, ex_ipend)); #ifdef ANOMALY_05000261 DEFINE(PDA_LFRETX, offsetof(struct blackfin_pda, last_cplb_fault_retx)); #endif diff --git a/arch/blackfin/kernel/traps.c b/arch/blackfin/kernel/traps.c index bf2b2d1f8ae5..fccf741ed3b5 100644 --- a/arch/blackfin/kernel/traps.c +++ b/arch/blackfin/kernel/traps.c @@ -267,11 +267,6 @@ asmlinkage void trap_c(struct pt_regs *fp) * double faults if the stack has become corrupt */ -#ifndef CONFIG_KGDB - /* IPEND is skipped if KGDB isn't enabled (see entry code) */ - fp->ipend = bfin_read_IPEND(); -#endif - /* trap_c() will be called for exceptions. During exceptions * processing, the pc value should be set with retx value. * With this change we can cleanup some code in signal.c- TODO @@ -1116,10 +1111,16 @@ void show_regs(struct pt_regs *fp) verbose_printk(KERN_NOTICE "%s", linux_banner); - verbose_printk(KERN_NOTICE "\nSEQUENCER STATUS:\t\t%s\n", - print_tainted()); - verbose_printk(KERN_NOTICE " SEQSTAT: %08lx IPEND: %04lx SYSCFG: %04lx\n", - (long)fp->seqstat, fp->ipend, fp->syscfg); + verbose_printk(KERN_NOTICE "\nSEQUENCER STATUS:\t\t%s\n", print_tainted()); + verbose_printk(KERN_NOTICE " SEQSTAT: %08lx IPEND: %04lx IMASK: %04lx SYSCFG: %04lx\n", + (long)fp->seqstat, fp->ipend, cpu_pda[smp_processor_id()].ex_imask, fp->syscfg); + if (fp->ipend & EVT_IRPTEN) + verbose_printk(KERN_NOTICE " Global Interrupts Disabled (IPEND[4])\n"); + if (!(cpu_pda[smp_processor_id()].ex_imask & (EVT_IVG13 | EVT_IVG12 | EVT_IVG11 | + EVT_IVG10 | EVT_IVG9 | EVT_IVG8 | EVT_IVG7 | EVT_IVTMR))) + verbose_printk(KERN_NOTICE " Peripheral interrupts masked off\n"); + if (!(cpu_pda[smp_processor_id()].ex_imask & (EVT_IVG15 | EVT_IVG14))) + verbose_printk(KERN_NOTICE " Kernel interrupts masked off\n"); if ((fp->seqstat & SEQSTAT_EXCAUSE) == VEC_HWERR) { verbose_printk(KERN_NOTICE " HWERRCAUSE: 0x%lx\n", (fp->seqstat & SEQSTAT_HWERRCAUSE) >> 14); diff --git a/arch/blackfin/mach-common/entry.S b/arch/blackfin/mach-common/entry.S index fb1795d5be2a..4c07fcb356a2 100644 --- a/arch/blackfin/mach-common/entry.S +++ b/arch/blackfin/mach-common/entry.S @@ -301,25 +301,31 @@ ENTRY(_ex_replaceable) nop; ENTRY(_ex_trap_c) + /* The only thing that has been saved in this context is + * (R7:6,P5:4), ASTAT & SP - don't use anything else + */ + + GET_PDA(p5, r6); + /* Make sure we are not in a double fault */ p4.l = lo(IPEND); p4.h = hi(IPEND); r7 = [p4]; CC = BITTST (r7, 5); if CC jump _double_fault; + [p5 + PDA_EXIPEND] = r7; /* Call C code (trap_c) to handle the exception, which most * likely involves sending a signal to the current process. * To avoid double faults, lower our priority to IRQ5 first. */ - P5.h = _exception_to_level5; - P5.l = _exception_to_level5; + r7.h = _exception_to_level5; + r7.l = _exception_to_level5; p4.l = lo(EVT5); p4.h = hi(EVT5); - [p4] = p5; + [p4] = r7; csync; - GET_PDA(p5, r6); #ifndef CONFIG_DEBUG_DOUBLEFAULT /* @@ -349,8 +355,7 @@ ENTRY(_ex_trap_c) BITCLR(r6, SYSCFG_SSSTEP_P); SYSCFG = r6; - /* Disable all interrupts, but make sure level 5 is enabled so - * we can switch to that level. Save the old mask. */ + /* Save the current IMASK, since we change in order to jump to level 5 */ cli r6; [p5 + PDA_EXIMASK] = r6; @@ -358,9 +363,21 @@ ENTRY(_ex_trap_c) p4.h = hi(SAFE_USER_INSTRUCTION); retx = p4; + /* Disable all interrupts, but make sure level 5 is enabled so + * we can switch to that level. + */ r6 = 0x3f; sti r6; + /* In case interrupts are disabled IPEND[4] (global interrupt disable bit) + * clear it (re-enabling interrupts again) by the special sequence of pushing + * RETI onto the stack. This way we can lower ourselves to IVG5 even if the + * exception was taken after the interrupt handler was called but before it + * got a chance to enable global interrupts itself. + */ + [--sp] = reti; + sp += 4; + raise 5; jump.s _bfin_return_from_exception; ENDPROC(_ex_trap_c) @@ -420,47 +437,52 @@ ENDPROC(_double_fault) ENTRY(_exception_to_level5) SAVE_ALL_SYS - GET_PDA(p4, r7); /* Fetch current PDA */ - r6 = [p4 + PDA_RETX]; + GET_PDA(p5, r7); /* Fetch current PDA */ + r6 = [p5 + PDA_RETX]; [sp + PT_PC] = r6; - r6 = [p4 + PDA_SYSCFG]; + r6 = [p5 + PDA_SYSCFG]; [sp + PT_SYSCFG] = r6; - /* Restore interrupt mask. We haven't pushed RETI, so this - * doesn't enable interrupts until we return from this handler. */ - r6 = [p4 + PDA_EXIMASK]; - sti r6; - /* Restore the hardware error vector. */ - P5.h = _evt_ivhw; - P5.l = _evt_ivhw; + r7.h = _evt_ivhw; + r7.l = _evt_ivhw; p4.l = lo(EVT5); p4.h = hi(EVT5); - [p4] = p5; + [p4] = r7; csync; - p2.l = lo(IPEND); - p2.h = hi(IPEND); - csync; - r0 = [p2]; /* Read current IPEND */ - [sp + PT_IPEND] = r0; /* Store IPEND */ +#ifdef CONFIG_DEBUG_DOUBLEFAULT + /* Now that we have the hardware error vector programmed properly + * we can re-enable interrupts (IPEND[4]), so if the _trap_c causes + * another hardware error, we can catch it (self-nesting). + */ + [--sp] = reti; + sp += 4; +#endif + + r7 = [p5 + PDA_EXIPEND] /* Read the IPEND from the Exception state */ + [sp + PT_IPEND] = r7; /* Store IPEND onto the stack */ r0 = sp; /* stack frame pt_regs pointer argument ==> r0 */ SP += -12; call _trap_c; SP += 12; -#ifdef CONFIG_DEBUG_DOUBLEFAULT - /* Grab ILAT */ - p2.l = lo(ILAT); - p2.h = hi(ILAT); - r0 = [p2]; - r1 = 0x20; /* Did I just cause anther HW error? */ - r0 = r0 & r1; - CC = R0 == R1; - if CC JUMP _double_fault; -#endif + /* If interrupts were off during the exception (IPEND[4] = 1), turn them off + * before we return. + */ + CC = BITTST(r7, EVT_IRPTEN_P) + if !CC jump 1f; + /* this will load a random value into the reti register - but that is OK, + * since we do restore it to the correct value in the 'RESTORE_ALL_SYS' macro + */ + sp += -4; + reti = [sp++]; +1: + /* restore the interrupt mask (IMASK) */ + r6 = [p5 + PDA_EXIMASK]; + sti r6; call _ret_from_exception; RESTORE_ALL_SYS |