summary refs log tree commit diff
path: root/arch
diff options
context:
space:
mode:
authorHaavard Skinnemoen <hskinnemoen@atmel.com>2007-10-10 14:58:29 +0200
committerHaavard Skinnemoen <hskinnemoen@atmel.com>2008-01-25 08:31:43 +0100
commite7ba176b47db2ed53f258a6b4fe9d9fc6fa437a9 (patch)
treebeb9ffab7da0c24f11c04b6eb4ca29b23b1dd07b /arch
parentf6135d12db4bed3b992052020f1c50d749cd8dc6 (diff)
downloadlinux-e7ba176b47db2ed53f258a6b4fe9d9fc6fa437a9.tar.gz
[AVR32] NMI debugging
Change the NMI handler to use the die notifier chain to signal anyone
who cares. Add a simple "nmi debugger" which hooks into this chain and
that may dump registers, task state, etc. when it happens.

Signed-off-by: Haavard Skinnemoen <hskinnemoen@atmel.com>
Diffstat (limited to 'arch')
-rw-r--r--arch/avr32/Kconfig10
-rw-r--r--arch/avr32/kernel/Makefile1
-rw-r--r--arch/avr32/kernel/irq.c11
-rw-r--r--arch/avr32/kernel/nmi_debug.c82
-rw-r--r--arch/avr32/kernel/traps.c21
-rw-r--r--arch/avr32/mach-at32ap/at32ap700x.c1
-rw-r--r--arch/avr32/mach-at32ap/extint.c39
7 files changed, 155 insertions, 10 deletions
diff --git a/arch/avr32/Kconfig b/arch/avr32/Kconfig
index 516015b3293b..e34e2c9c94cb 100644
--- a/arch/avr32/Kconfig
+++ b/arch/avr32/Kconfig
@@ -170,6 +170,16 @@ config OWNERSHIP_TRACE
 	  enabling Nexus-compliant debuggers to keep track of the PID of the
 	  currently executing task.
 
+config NMI_DEBUGGING
+	bool "NMI Debugging"
+	default n
+	help
+	  Say Y here and pass the nmi_debug command-line parameter to
+	  the kernel to turn on NMI debugging. Depending on the value
+	  of the nmi_debug option, various pieces of information will
+	  be dumped to the console when a Non-Maskable Interrupt
+	  happens.
+
 # FPU emulation goes here
 
 source "kernel/Kconfig.hz"
diff --git a/arch/avr32/kernel/Makefile b/arch/avr32/kernel/Makefile
index bc224a4e39fe..e4b6d122b033 100644
--- a/arch/avr32/kernel/Makefile
+++ b/arch/avr32/kernel/Makefile
@@ -12,3 +12,4 @@ obj-y				+= init_task.o switch_to.o cpu.o
 obj-$(CONFIG_MODULES)		+= module.o avr32_ksyms.o
 obj-$(CONFIG_KPROBES)		+= kprobes.o
 obj-$(CONFIG_STACKTRACE)	+= stacktrace.o
+obj-$(CONFIG_NMI_DEBUGGING)	+= nmi_debug.o
diff --git a/arch/avr32/kernel/irq.c b/arch/avr32/kernel/irq.c
index 61f2de266f62..a8e767d836aa 100644
--- a/arch/avr32/kernel/irq.c
+++ b/arch/avr32/kernel/irq.c
@@ -25,6 +25,17 @@ void ack_bad_irq(unsigned int irq)
 	printk("unexpected IRQ %u\n", irq);
 }
 
+/* May be overridden by platform code */
+int __weak nmi_enable(void)
+{
+	return -ENOSYS;
+}
+
+void __weak nmi_disable(void)
+{
+
+}
+
 #ifdef CONFIG_PROC_FS
 int show_interrupts(struct seq_file *p, void *v)
 {
diff --git a/arch/avr32/kernel/nmi_debug.c b/arch/avr32/kernel/nmi_debug.c
new file mode 100644
index 000000000000..3414b8566c29
--- /dev/null
+++ b/arch/avr32/kernel/nmi_debug.c
@@ -0,0 +1,82 @@
+/*
+ * 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 <asm/irq.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 (nmi_enable()) {
+		printk(KERN_WARNING "Unable to enable NMI.\n");
+		return 0;
+	}
+
+	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/avr32/kernel/traps.c b/arch/avr32/kernel/traps.c
index 870c075e6314..cf6f686d9b0b 100644
--- a/arch/avr32/kernel/traps.c
+++ b/arch/avr32/kernel/traps.c
@@ -9,6 +9,7 @@
 #include <linux/bug.h>
 #include <linux/init.h>
 #include <linux/kallsyms.h>
+#include <linux/kdebug.h>
 #include <linux/module.h>
 #include <linux/notifier.h>
 #include <linux/sched.h>
@@ -107,9 +108,23 @@ void _exception(long signr, struct pt_regs *regs, int code,
 
 asmlinkage void do_nmi(unsigned long ecr, struct pt_regs *regs)
 {
-	printk(KERN_ALERT "Got Non-Maskable Interrupt, dumping regs\n");
-	show_regs_log_lvl(regs, KERN_ALERT);
-	show_stack_log_lvl(current, regs->sp, regs, KERN_ALERT);
+	int ret;
+
+	nmi_enter();
+
+	ret = notify_die(DIE_NMI, "NMI", regs, 0, ecr, SIGINT);
+	switch (ret) {
+	case NOTIFY_OK:
+	case NOTIFY_STOP:
+		return;
+	case NOTIFY_BAD:
+		die("Fatal Non-Maskable Interrupt", regs, SIGINT);
+	default:
+		break;
+	}
+
+	printk(KERN_ALERT "Got NMI, but nobody cared. Disabling...\n");
+	nmi_disable();
 }
 
 asmlinkage void do_critical_exception(unsigned long ecr, struct pt_regs *regs)
diff --git a/arch/avr32/mach-at32ap/at32ap700x.c b/arch/avr32/mach-at32ap/at32ap700x.c
index 9386e1f82fb8..14e61f05e1f6 100644
--- a/arch/avr32/mach-at32ap/at32ap700x.c
+++ b/arch/avr32/mach-at32ap/at32ap700x.c
@@ -13,6 +13,7 @@
 #include <linux/spi/spi.h>
 
 #include <asm/io.h>
+#include <asm/irq.h>
 
 #include <asm/arch/at32ap700x.h>
 #include <asm/arch/board.h>
diff --git a/arch/avr32/mach-at32ap/extint.c b/arch/avr32/mach-at32ap/extint.c
index f5bfd4c81fe7..e108e7bba8c0 100644
--- a/arch/avr32/mach-at32ap/extint.c
+++ b/arch/avr32/mach-at32ap/extint.c
@@ -26,16 +26,10 @@
 #define EIC_MODE				0x0014
 #define EIC_EDGE				0x0018
 #define EIC_LEVEL				0x001c
-#define EIC_TEST				0x0020
 #define EIC_NMIC				0x0024
 
-/* Bitfields in TEST */
-#define EIC_TESTEN_OFFSET			31
-#define EIC_TESTEN_SIZE				1
-
 /* Bitfields in NMIC */
-#define EIC_EN_OFFSET				0
-#define EIC_EN_SIZE				1
+#define EIC_NMIC_ENABLE				(1 << 0)
 
 /* Bit manipulation macros */
 #define EIC_BIT(name)					\
@@ -63,6 +57,9 @@ struct eic {
 	unsigned int first_irq;
 };
 
+static struct eic *nmi_eic;
+static bool nmi_enabled;
+
 static void eic_ack_irq(unsigned int irq)
 {
 	struct eic *eic = get_irq_chip_data(irq);
@@ -174,6 +171,24 @@ static void demux_eic_irq(unsigned int irq, struct irq_desc *desc)
 	}
 }
 
+int nmi_enable(void)
+{
+	nmi_enabled = true;
+
+	if (nmi_eic)
+		eic_writel(nmi_eic, NMIC, EIC_NMIC_ENABLE);
+
+	return 0;
+}
+
+void nmi_disable(void)
+{
+	if (nmi_eic)
+		eic_writel(nmi_eic, NMIC, 0);
+
+	nmi_enabled = false;
+}
+
 static int __init eic_probe(struct platform_device *pdev)
 {
 	struct eic *eic;
@@ -230,6 +245,16 @@ static int __init eic_probe(struct platform_device *pdev)
 	set_irq_chained_handler(int_irq, demux_eic_irq);
 	set_irq_data(int_irq, eic);
 
+	if (pdev->id == 0) {
+		nmi_eic = eic;
+		if (nmi_enabled)
+			/*
+			 * Someone tried to enable NMI before we were
+			 * ready. Do it now.
+			 */
+			nmi_enable();
+	}
+
 	dev_info(&pdev->dev,
 		 "External Interrupt Controller at 0x%p, IRQ %u\n",
 		 eic->regs, int_irq);