summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Mundt <lethal@linux-sh.org>2009-09-01 12:38:32 +0400
committerPaul Mundt <lethal@linux-sh.org>2009-09-01 12:38:32 +0400
commit1e1030dccb1084c8a38976d3656aab1d50d762da (patch)
tree18c62bd2a12dbb8e8aae56d771f0561784af186b
parentac6a0cf6716bb46813d0161024c66c2af66e53d1 (diff)
downloadlinux-1e1030dccb1084c8a38976d3656aab1d50d762da.tar.xz
sh: nmi_debug support.
This implements support for NMI debugging that was shamelessly copied from the avr32 port. A bit of special magic is needed in the interrupt exception path given that the NMI exception handler is stubbed in to the regular exception handling table despite being reported in INTEVT. So we mangle the lookup and kick off an EXPEVT-style exception dispatch from the INTEVT path for exceptions that do_IRQ() has no chance of handling. As a result, we also drop the evt2irq() conversion from the do_IRQ() path and just do it in assembly. Signed-off-by: Paul Mundt <lethal@linux-sh.org>
-rw-r--r--Documentation/kernel-parameters.txt2
-rw-r--r--arch/sh/include/asm/kdebug.h1
-rw-r--r--arch/sh/include/asm/system.h2
-rw-r--r--arch/sh/kernel/Makefile7
-rw-r--r--arch/sh/kernel/cpu/sh3/entry.S26
-rw-r--r--arch/sh/kernel/cpu/sh3/ex.S4
-rw-r--r--arch/sh/kernel/irq.c2
-rw-r--r--arch/sh/kernel/nmi_debug.c77
-rw-r--r--arch/sh/kernel/traps.c21
9 files changed, 133 insertions, 9 deletions
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt
index 7936b801fe6a..76c355214dc3 100644
--- a/Documentation/kernel-parameters.txt
+++ b/Documentation/kernel-parameters.txt
@@ -1514,7 +1514,7 @@ and is between 256 and 4096 characters. It is defined in the file
of returning the full 64-bit number.
The default is to return 64-bit inode numbers.
- nmi_debug= [KNL,AVR32] Specify one or more actions to take
+ nmi_debug= [KNL,AVR32,SH] Specify one or more actions to take
when a NMI is triggered.
Format: [state][,regs][,debounce][,die]
diff --git a/arch/sh/include/asm/kdebug.h b/arch/sh/include/asm/kdebug.h
index 0b9f896f203c..985219f9759e 100644
--- a/arch/sh/include/asm/kdebug.h
+++ b/arch/sh/include/asm/kdebug.h
@@ -4,6 +4,7 @@
/* Grossly misnamed. */
enum die_val {
DIE_TRAP,
+ DIE_NMI,
DIE_OOPS,
};
diff --git a/arch/sh/include/asm/system.h b/arch/sh/include/asm/system.h
index 6b272238a46e..b5c5acdc8c0e 100644
--- a/arch/sh/include/asm/system.h
+++ b/arch/sh/include/asm/system.h
@@ -169,7 +169,7 @@ BUILD_TRAP_HANDLER(breakpoint);
BUILD_TRAP_HANDLER(singlestep);
BUILD_TRAP_HANDLER(fpu_error);
BUILD_TRAP_HANDLER(fpu_state_restore);
-BUILD_TRAP_HANDLER(unwinder);
+BUILD_TRAP_HANDLER(nmi);
#ifdef CONFIG_BUG
extern void handle_BUG(struct pt_regs *);
diff --git a/arch/sh/kernel/Makefile b/arch/sh/kernel/Makefile
index f37cf02ad9be..a2d0a40f3848 100644
--- a/arch/sh/kernel/Makefile
+++ b/arch/sh/kernel/Makefile
@@ -10,9 +10,10 @@ CFLAGS_REMOVE_ftrace.o = -pg
endif
obj-y := debugtraps.o dumpstack.o idle.o io.o io_generic.o irq.o \
- machvec.o process_$(BITS).o ptrace_$(BITS).o setup.o \
- signal_$(BITS).o sys_sh.o sys_sh$(BITS).o syscalls_$(BITS).o \
- time.o topology.o traps.o traps_$(BITS).o unwinder.o
+ machvec.o nmi_debug.o process_$(BITS).o ptrace_$(BITS).o \
+ setup.o signal_$(BITS).o sys_sh.o sys_sh$(BITS).o \
+ syscalls_$(BITS).o time.o topology.o traps.o \
+ traps_$(BITS).o unwinder.o
obj-y += cpu/
obj-$(CONFIG_VSYSCALL) += vsyscall/
diff --git a/arch/sh/kernel/cpu/sh3/entry.S b/arch/sh/kernel/cpu/sh3/entry.S
index aebd33d18ff7..d1142d365925 100644
--- a/arch/sh/kernel/cpu/sh3/entry.S
+++ b/arch/sh/kernel/cpu/sh3/entry.S
@@ -532,7 +532,33 @@ ENTRY(handle_interrupt)
mov.l 2f, r4
mov.l 3f, r9
mov.l @r4, r4 ! pass INTEVT vector as arg0
+
+ shlr2 r4
+ shlr r4
+ mov r4, r0 ! save vector->jmp table offset for later
+
+ shlr2 r4 ! vector to IRQ# conversion
+ add #-0x10, r4
+
+ cmp/pz r4 ! is it a valid IRQ?
+ bt 10f
+
+ /*
+ * We got here as a result of taking the INTEVT path for something
+ * that isn't a valid hard IRQ, therefore we bypass the do_IRQ()
+ * path and special case the event dispatch instead. This is the
+ * expected path for the NMI (and any other brilliantly implemented
+ * exception), which effectively wants regular exception dispatch
+ * but is unfortunately reported through INTEVT rather than
+ * EXPEVT. Grr.
+ */
+ mov.l 6f, r9
+ mov.l @(r0, r9), r9
jmp @r9
+ mov r15, r8 ! trap handlers take saved regs in r8
+
+10:
+ jmp @r9 ! Off to do_IRQ() we go.
mov r15, r5 ! pass saved registers as arg1
ENTRY(exception_none)
diff --git a/arch/sh/kernel/cpu/sh3/ex.S b/arch/sh/kernel/cpu/sh3/ex.S
index e5a0de39a2db..46610c35c232 100644
--- a/arch/sh/kernel/cpu/sh3/ex.S
+++ b/arch/sh/kernel/cpu/sh3/ex.S
@@ -48,9 +48,7 @@ ENTRY(exception_handling_table)
.long system_call ! Unconditional Trap /* 160 */
.long exception_error ! reserved_instruction (filled by trap_init) /* 180 */
.long exception_error ! illegal_slot_instruction (filled by trap_init) /*1A0*/
-ENTRY(nmi_slot)
- .long kgdb_handle_exception /* 1C0 */ ! Allow trap to debugger
-ENTRY(user_break_point_trap)
+ .long nmi_trap_handler /* 1C0 */ ! Allow trap to debugger
.long break_point_trap /* 1E0 */
/*
diff --git a/arch/sh/kernel/irq.c b/arch/sh/kernel/irq.c
index d1053392e287..60f8af4497c7 100644
--- a/arch/sh/kernel/irq.c
+++ b/arch/sh/kernel/irq.c
@@ -114,7 +114,7 @@ asmlinkage int do_IRQ(unsigned int irq, struct pt_regs *regs)
#endif
irq_enter();
- irq = irq_demux(evt2irq(irq));
+ irq = irq_demux(irq);
#ifdef CONFIG_IRQSTACKS
curctx = (union irq_ctx *)current_thread_info();
diff --git a/arch/sh/kernel/nmi_debug.c b/arch/sh/kernel/nmi_debug.c
new file mode 100644
index 000000000000..ff0abbd1e652
--- /dev/null
+++ b/arch/sh/kernel/nmi_debug.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/delay.h>
+#include <linux/kdebug.h>
+#include <linux/notifier.h>
+#include <linux/sched.h>
+#include <linux/hardirq.h>
+
+enum nmi_action {
+ NMI_SHOW_STATE = 1 << 0,
+ NMI_SHOW_REGS = 1 << 1,
+ NMI_DIE = 1 << 2,
+ NMI_DEBOUNCE = 1 << 3,
+};
+
+static unsigned long nmi_actions;
+
+static int nmi_debug_notify(struct notifier_block *self,
+ unsigned long val, void *data)
+{
+ struct die_args *args = data;
+
+ if (likely(val != DIE_NMI))
+ return NOTIFY_DONE;
+
+ if (nmi_actions & NMI_SHOW_STATE)
+ show_state();
+ if (nmi_actions & NMI_SHOW_REGS)
+ show_regs(args->regs);
+ if (nmi_actions & NMI_DEBOUNCE)
+ mdelay(10);
+ if (nmi_actions & NMI_DIE)
+ return NOTIFY_BAD;
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block nmi_debug_nb = {
+ .notifier_call = nmi_debug_notify,
+};
+
+static int __init nmi_debug_setup(char *str)
+{
+ char *p, *sep;
+
+ register_die_notifier(&nmi_debug_nb);
+
+ if (*str != '=')
+ return 0;
+
+ for (p = str + 1; *p; p = sep + 1) {
+ sep = strchr(p, ',');
+ if (sep)
+ *sep = 0;
+ if (strcmp(p, "state") == 0)
+ nmi_actions |= NMI_SHOW_STATE;
+ else if (strcmp(p, "regs") == 0)
+ nmi_actions |= NMI_SHOW_REGS;
+ else if (strcmp(p, "debounce") == 0)
+ nmi_actions |= NMI_DEBOUNCE;
+ else if (strcmp(p, "die") == 0)
+ nmi_actions |= NMI_DIE;
+ else
+ printk(KERN_WARNING "NMI: Unrecognized action `%s'\n",
+ p);
+ if (!sep)
+ break;
+ }
+
+ return 0;
+}
+__setup("nmi_debug", nmi_debug_setup);
diff --git a/arch/sh/kernel/traps.c b/arch/sh/kernel/traps.c
index f69bd968fcca..a8396f36bd14 100644
--- a/arch/sh/kernel/traps.c
+++ b/arch/sh/kernel/traps.c
@@ -5,6 +5,7 @@
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
+#include <linux/hardirq.h>
#include <asm/unwinder.h>
#include <asm/system.h>
@@ -91,3 +92,23 @@ BUILD_TRAP_HANDLER(bug)
force_sig(SIGTRAP, current);
}
+
+BUILD_TRAP_HANDLER(nmi)
+{
+ TRAP_HANDLER_DECL;
+
+ nmi_enter();
+
+ switch (notify_die(DIE_NMI, "NMI", regs, 0, vec & 0xff, SIGINT)) {
+ case NOTIFY_OK:
+ case NOTIFY_STOP:
+ break;
+ case NOTIFY_BAD:
+ die("Fatal Non-Maskable Interrupt", regs, SIGINT);
+ default:
+ printk(KERN_ALERT "Got NMI, but nobody cared. Ignoring...\n");
+ break;
+ }
+
+ nmi_exit();
+}