[ARM] Add ability to dump exception stacks to kernel backtraces

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
diff --git a/arch/arm/kernel/irq.c b/arch/arm/kernel/irq.c
index e101846..a72b82e 100644
--- a/arch/arm/kernel/irq.c
+++ b/arch/arm/kernel/irq.c
@@ -109,7 +109,7 @@
  * come via this function.  Instead, they should provide their
  * own 'handler'
  */
-asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
+asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
 {
 	struct pt_regs *old_regs = set_irq_regs(regs);
 	struct irq_desc *desc = irq_desc + irq;
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c
index 2409560..ba1c188 100644
--- a/arch/arm/kernel/traps.c
+++ b/arch/arm/kernel/traps.c
@@ -45,7 +45,18 @@
 __setup("user_debug=", user_debug_setup);
 #endif
 
-void dump_backtrace_entry(unsigned long where, unsigned long from)
+static void dump_mem(const char *str, unsigned long bottom, unsigned long top);
+
+static inline int in_exception_text(unsigned long ptr)
+{
+	extern char __exception_text_start[];
+	extern char __exception_text_end[];
+
+	return ptr >= (unsigned long)&__exception_text_start &&
+	       ptr < (unsigned long)&__exception_text_end;
+}
+
+void dump_backtrace_entry(unsigned long where, unsigned long from, unsigned long frame)
 {
 #ifdef CONFIG_KALLSYMS
 	printk("[<%08lx>] ", where);
@@ -55,6 +66,9 @@
 #else
 	printk("Function entered at [<%08lx>] from [<%08lx>]\n", where, from);
 #endif
+
+	if (in_exception_text(where))
+		dump_mem("Exception stack", frame + 4, frame + 4 + sizeof(struct pt_regs));
 }
 
 /*
@@ -266,7 +280,7 @@
 	spin_unlock_irqrestore(&undef_lock, flags);
 }
 
-asmlinkage void do_undefinstr(struct pt_regs *regs)
+asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
 {
 	unsigned int correction = thumb_mode(regs) ? 2 : 4;
 	unsigned int instr;
diff --git a/arch/arm/kernel/vmlinux.lds.S b/arch/arm/kernel/vmlinux.lds.S
index ddbdad4..b295f6a 100644
--- a/arch/arm/kernel/vmlinux.lds.S
+++ b/arch/arm/kernel/vmlinux.lds.S
@@ -83,6 +83,9 @@
 
 	.text : {			/* Real text segment		*/
 		_text = .;		/* Text and read-only data	*/
+			__exception_text_start = .;
+			*(.exception.text)
+			__exception_text_end = .;
 			*(.text)
 			SCHED_TEXT
 			LOCK_TEXT
diff --git a/arch/arm/lib/backtrace.S b/arch/arm/lib/backtrace.S
index 7423008..84dc890 100644
--- a/arch/arm/lib/backtrace.S
+++ b/arch/arm/lib/backtrace.S
@@ -17,8 +17,8 @@
 @ fp is 0 or stack frame
 
 #define frame	r4
-#define next	r5
-#define save	r6
+#define sv_fp	r5
+#define sv_pc	r6
 #define mask	r7
 #define offset	r8
 
@@ -31,108 +31,106 @@
 #if !defined(CONFIG_FRAME_POINTER) || !defined(CONFIG_PRINTK)
 		mov	pc, lr
 #else
-
 		stmfd	sp!, {r4 - r8, lr}	@ Save an extra register so we have a location...
-		tst	r1, #0x10		@ 26 or 32-bit?
-		moveq	mask, #0xfc000003
-		movne	mask, #0
-		tst	mask, r0
-		movne	r0, #0
-		movs	frame, r0
-1:		moveq	r0, #-2
-		ldmeqfd	sp!, {r4 - r8, pc}
+		movs	frame, r0		@ if frame pointer is zero
+		beq	no_frame		@ we have no stack frames
 
-2:		stmfd	sp!, {pc}		@ calculate offset of PC in STMIA instruction
-		ldr	r0, [sp], #4
-		adr	r1, 2b - 4
+		tst	r1, #0x10		@ 26 or 32-bit mode?
+		moveq	mask, #0xfc000003	@ mask for 26-bit
+		movne	mask, #0		@ mask for 32-bit
+
+1:		stmfd	sp!, {pc}		@ calculate offset of PC stored
+		ldr	r0, [sp], #4		@ by stmfd for this CPU
+		adr	r1, 1b
 		sub	offset, r0, r1
 
-3:		tst	frame, mask		@ Check for address exceptions...
-		bne	1b
+/*
+ * Stack frame layout:
+ *             optionally saved caller registers (r4 - r10)
+ *             saved fp
+ *             saved sp
+ *             saved lr
+ *    frame => saved pc
+ *             optionally saved arguments (r0 - r3)
+ * saved sp => <next word>
+ *
+ * Functions start with the following code sequence:
+ *                  mov   ip, sp
+ *                  stmfd sp!, {r0 - r3} (optional)
+ * corrected pc =>  stmfd sp!, {..., fp, ip, lr, pc}
+ */
+for_each_frame:	tst	frame, mask		@ Check for address exceptions
+		bne	no_frame
 
-1001:		ldr	next, [frame, #-12]	@ get fp
-1002:		ldr	r2, [frame, #-4]	@ get lr
-1003:		ldr	r3, [frame, #0]		@ get pc
-		sub	save, r3, offset	@ Correct PC for prefetching
-		bic	save, save, mask
-1004:		ldr	r1, [save, #0]		@ get instruction at function
-		mov	r1, r1, lsr #10
-		ldr	r3, .Ldsi+4
-		teq	r1, r3
-		subeq	save, save, #4
-		mov	r0, save
-		bic	r1, r2, mask
+1001:		ldr	sv_pc, [frame, #0]	@ get saved pc
+1002:		ldr	sv_fp, [frame, #-12]	@ get saved fp
+
+		sub	sv_pc, sv_pc, offset	@ Correct PC for prefetching
+		bic	sv_pc, sv_pc, mask	@ mask PC/LR for the mode
+
+1003:		ldr	r2, [sv_pc, #-4]	@ if stmfd sp!, {args} exists,
+		ldr	r3, .Ldsi+4		@ adjust saved 'pc' back one
+		teq	r3, r2, lsr #10		@ instruction
+		subne	r0, sv_pc, #4		@ allow for mov
+		subeq	r0, sv_pc, #8		@ allow for mov + stmia
+
+		ldr	r1, [frame, #-4]	@ get saved lr
+		mov	r2, frame
+		bic	r1, r1, mask		@ mask PC/LR for the mode
 		bl	dump_backtrace_entry
 
-		ldr	r0, [frame, #-8]	@ get sp
-		sub	r0, r0, #4
-1005:		ldr	r1, [save, #4]		@ get instruction at function+4
-		mov	r3, r1, lsr #10
-		ldr	r2, .Ldsi+4
-		teq	r3, r2			@ Check for stmia sp!, {args}
-		addeq	save, save, #4		@ next instruction
-		bleq	.Ldumpstm
+		ldr	r1, [sv_pc, #-4]	@ if stmfd sp!, {args} exists,
+		ldr	r3, .Ldsi+4
+		teq	r3, r1, lsr #10
+		ldreq	r0, [frame, #-8]	@ get sp
+		subeq	r0, r0, #4		@ point at the last arg
+		bleq	.Ldumpstm		@ dump saved registers
 
-		sub	r0, frame, #16
-1006:		ldr	r1, [save, #4]		@ Get 'stmia sp!, {rlist, fp, ip, lr, pc}' instruction
-		mov	r3, r1, lsr #10
-		ldr	r2, .Ldsi
-		teq	r3, r2
-		bleq	.Ldumpstm
+1004:		ldr	r1, [sv_pc, #0]		@ if stmfd sp!, {..., fp, ip, lr, pc}
+		ldr	r3, .Ldsi		@ instruction exists,
+		teq	r3, r1, lsr #10
+		subeq	r0, frame, #16
+		bleq	.Ldumpstm		@ dump saved registers
 
-		/*
-		 * A zero next framepointer means we're done.
-		 */
-		teq	next, #0
-		ldmeqfd	sp!, {r4 - r8, pc}
+		teq	sv_fp, #0		@ zero saved fp means
+		beq	no_frame		@ no further frames
 
-		/*
-		 * The next framepointer must be above the
-		 * current framepointer.
-		 */
-		cmp	next, frame
-		mov	frame, next
-		bhi	3b
-		b	1007f
+		cmp	sv_fp, frame		@ next frame must be
+		mov	frame, sv_fp		@ above the current frame
+		bhi	for_each_frame
 
-/*
- * Fixup for LDMDB.  Note that this must not be in the fixup section.
- */
-1007:		ldr	r0, =.Lbad
+1006:		adr	r0, .Lbad
 		mov	r1, frame
 		bl	printk
-		ldmfd	sp!, {r4 - r8, pc}
-		.ltorg
+no_frame:	ldmfd	sp!, {r4 - r8, pc}
 		
 		.section __ex_table,"a"
 		.align	3
-		.long	1001b, 1007b
-		.long	1002b, 1007b
-		.long	1003b, 1007b
-		.long	1004b, 1007b
-		.long	1005b, 1007b
-		.long	1006b, 1007b
+		.long	1001b, 1006b
+		.long	1002b, 1006b
+		.long	1003b, 1006b
+		.long	1004b, 1006b
 		.previous
 
 #define instr r4
 #define reg   r5
 #define stack r6
 
-.Ldumpstm:	stmfd	sp!, {instr, reg, stack, r7, r8, lr}
+.Ldumpstm:	stmfd	sp!, {instr, reg, stack, r7, lr}
 		mov	stack, r0
 		mov	instr, r1
-		mov	reg, #9
+		mov	reg, #10
 		mov	r7, #0
 1:		mov	r3, #1
 		tst	instr, r3, lsl reg
 		beq	2f
 		add	r7, r7, #1
-		teq	r7, #4
-		moveq	r7, #0
-		moveq	r3, #'\n'
-		movne	r3, #' '
-		ldr	r2, [stack], #-4
-		mov	r1, reg
+		teq	r7, #6
+		moveq	r7, #1
+		moveq	r1, #'\n'
+		movne	r1, #' '
+		ldr	r3, [stack], #-4
+		mov	r2, reg
 		adr	r0, .Lfp
 		bl	printk
 2:		subs	reg, reg, #1
@@ -140,14 +138,13 @@
 		teq	r7, #0
 		adrne	r0, .Lcr
 		blne	printk
-		mov	r0, stack
-		ldmfd	sp!, {instr, reg, stack, r7, r8, pc}
+		ldmfd	sp!, {instr, reg, stack, r7, pc}
 
-.Lfp:		.asciz	" r%d = %08X%c"
+.Lfp:		.asciz	"%cr%d:%08x"
 .Lcr:		.asciz	"\n"
 .Lbad:		.asciz	"Backtrace aborted due to bad frame pointer <%p>\n"
 		.align
-.Ldsi:		.word	0x00e92dd8 >> 2
-		.word	0x00e92d00 >> 2
+.Ldsi:		.word	0xe92dd800 >> 10	@ stmfd sp!, {... fp, ip, lr, pc}
+		.word	0xe92d0000 >> 10	@ stmfd sp!, {}
 
 #endif
diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c
index 9fd6d2e..fa2d107 100644
--- a/arch/arm/mm/fault.c
+++ b/arch/arm/mm/fault.c
@@ -438,7 +438,7 @@
 /*
  * Dispatch a data abort to the relevant handler.
  */
-asmlinkage void
+asmlinkage void __exception
 do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 {
 	const struct fsr_info *inf = fsr_info + (fsr & 15) + ((fsr & (1 << 10)) >> 6);
@@ -457,7 +457,7 @@
 	notify_die("", regs, &info, fsr, 0);
 }
 
-asmlinkage void
+asmlinkage void __exception
 do_PrefetchAbort(unsigned long addr, struct pt_regs *regs)
 {
 	do_translation_fault(addr, 0, regs);