Skip to content

Commit

Permalink
Blackfin: make EVT3->EVT5 lowering more robust wrt IPEND[4]
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Robin Getz authored and Mike Frysinger committed Sep 17, 2009
1 parent d4b834c commit ae4f073
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 41 deletions.
1 change: 1 addition & 0 deletions arch/blackfin/include/asm/pda.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions arch/blackfin/kernel/asm-offsets.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 10 additions & 9 deletions arch/blackfin/kernel/traps.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
86 changes: 54 additions & 32 deletions arch/blackfin/mach-common/entry.S
Original file line number Diff line number Diff line change
Expand Up @@ -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

/*
Expand Down Expand Up @@ -349,18 +355,29 @@ 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;

p4.l = lo(SAFE_USER_INSTRUCTION);
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)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit ae4f073

Please sign in to comment.