Commit ae4f073c authored by Robin Getz's avatar Robin Getz Committed by Mike Frysinger
Browse files

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: default avatarRobin Getz <robin.getz@analog.com>
Signed-off-by: default avatarMike Frysinger <vapier@gentoo.org>
parent d4b834c1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+10 −9
Original line number Diff line number Diff line
@@ -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);
+54 −32
Original line number Diff line number Diff line
@@ -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