瀏覽代碼

[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>
Haavard Skinnemoen 17 年之前
父節點
當前提交
e7ba176b47

+ 5 - 0
Documentation/kernel-parameters.txt

@@ -34,6 +34,7 @@ parameter is applicable:
 	ALSA	ALSA sound support is enabled.
 	ALSA	ALSA sound support is enabled.
 	APIC	APIC support is enabled.
 	APIC	APIC support is enabled.
 	APM	Advanced Power Management support is enabled.
 	APM	Advanced Power Management support is enabled.
+	AVR32	AVR32 architecture is enabled.
 	AX25	Appropriate AX.25 support is enabled.
 	AX25	Appropriate AX.25 support is enabled.
 	BLACKFIN Blackfin architecture is enabled.
 	BLACKFIN Blackfin architecture is enabled.
 	DRM	Direct Rendering Management support is enabled.
 	DRM	Direct Rendering Management support is enabled.
@@ -1123,6 +1124,10 @@ and is between 256 and 4096 characters. It is defined in the file
 			of returning the full 64-bit number.
 			of returning the full 64-bit number.
 			The default is to return 64-bit inode numbers.
 			The default is to return 64-bit inode numbers.
 
 
+	nmi_debug=	[KNL,AVR32] Specify one or more actions to take
+			when a NMI is triggered.
+			Format: [state][,regs][,debounce][,die]
+
 	nmi_watchdog=	[KNL,BUGS=X86-32] Debugging features for SMP kernels
 	nmi_watchdog=	[KNL,BUGS=X86-32] Debugging features for SMP kernels
 
 
 	no387		[BUGS=X86-32] Tells the kernel to use the 387 maths
 	no387		[BUGS=X86-32] Tells the kernel to use the 387 maths

+ 10 - 0
arch/avr32/Kconfig

@@ -170,6 +170,16 @@ config OWNERSHIP_TRACE
 	  enabling Nexus-compliant debuggers to keep track of the PID of the
 	  enabling Nexus-compliant debuggers to keep track of the PID of the
 	  currently executing task.
 	  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
 # FPU emulation goes here
 
 
 source "kernel/Kconfig.hz"
 source "kernel/Kconfig.hz"

+ 1 - 0
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_MODULES)		+= module.o avr32_ksyms.o
 obj-$(CONFIG_KPROBES)		+= kprobes.o
 obj-$(CONFIG_KPROBES)		+= kprobes.o
 obj-$(CONFIG_STACKTRACE)	+= stacktrace.o
 obj-$(CONFIG_STACKTRACE)	+= stacktrace.o
+obj-$(CONFIG_NMI_DEBUGGING)	+= nmi_debug.o

+ 11 - 0
arch/avr32/kernel/irq.c

@@ -25,6 +25,17 @@ void ack_bad_irq(unsigned int irq)
 	printk("unexpected IRQ %u\n", 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
 #ifdef CONFIG_PROC_FS
 int show_interrupts(struct seq_file *p, void *v)
 int show_interrupts(struct seq_file *p, void *v)
 {
 {

+ 82 - 0
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);

+ 18 - 3
arch/avr32/kernel/traps.c

@@ -9,6 +9,7 @@
 #include <linux/bug.h>
 #include <linux/bug.h>
 #include <linux/init.h>
 #include <linux/init.h>
 #include <linux/kallsyms.h>
 #include <linux/kallsyms.h>
+#include <linux/kdebug.h>
 #include <linux/module.h>
 #include <linux/module.h>
 #include <linux/notifier.h>
 #include <linux/notifier.h>
 #include <linux/sched.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)
 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)
 asmlinkage void do_critical_exception(unsigned long ecr, struct pt_regs *regs)

+ 1 - 0
arch/avr32/mach-at32ap/at32ap700x.c

@@ -13,6 +13,7 @@
 #include <linux/spi/spi.h>
 #include <linux/spi/spi.h>
 
 
 #include <asm/io.h>
 #include <asm/io.h>
+#include <asm/irq.h>
 
 
 #include <asm/arch/at32ap700x.h>
 #include <asm/arch/at32ap700x.h>
 #include <asm/arch/board.h>
 #include <asm/arch/board.h>

+ 32 - 7
arch/avr32/mach-at32ap/extint.c

@@ -26,16 +26,10 @@
 #define EIC_MODE				0x0014
 #define EIC_MODE				0x0014
 #define EIC_EDGE				0x0018
 #define EIC_EDGE				0x0018
 #define EIC_LEVEL				0x001c
 #define EIC_LEVEL				0x001c
-#define EIC_TEST				0x0020
 #define EIC_NMIC				0x0024
 #define EIC_NMIC				0x0024
 
 
-/* Bitfields in TEST */
-#define EIC_TESTEN_OFFSET			31
-#define EIC_TESTEN_SIZE				1
-
 /* Bitfields in NMIC */
 /* Bitfields in NMIC */
-#define EIC_EN_OFFSET				0
-#define EIC_EN_SIZE				1
+#define EIC_NMIC_ENABLE				(1 << 0)
 
 
 /* Bit manipulation macros */
 /* Bit manipulation macros */
 #define EIC_BIT(name)					\
 #define EIC_BIT(name)					\
@@ -63,6 +57,9 @@ struct eic {
 	unsigned int first_irq;
 	unsigned int first_irq;
 };
 };
 
 
+static struct eic *nmi_eic;
+static bool nmi_enabled;
+
 static void eic_ack_irq(unsigned int irq)
 static void eic_ack_irq(unsigned int irq)
 {
 {
 	struct eic *eic = get_irq_chip_data(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)
 static int __init eic_probe(struct platform_device *pdev)
 {
 {
 	struct eic *eic;
 	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_chained_handler(int_irq, demux_eic_irq);
 	set_irq_data(int_irq, eic);
 	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,
 	dev_info(&pdev->dev,
 		 "External Interrupt Controller at 0x%p, IRQ %u\n",
 		 "External Interrupt Controller at 0x%p, IRQ %u\n",
 		 eic->regs, int_irq);
 		 eic->regs, int_irq);

+ 5 - 0
include/asm-avr32/irq.h

@@ -11,4 +11,9 @@
 
 
 #define irq_canonicalize(i)	(i)
 #define irq_canonicalize(i)	(i)
 
 
+#ifndef __ASSEMBLER__
+int nmi_enable(void);
+void nmi_disable(void);
+#endif
+
 #endif /* __ASM_AVR32_IOCTLS_H */
 #endif /* __ASM_AVR32_IOCTLS_H */

+ 1 - 0
include/asm-avr32/kdebug.h

@@ -5,6 +5,7 @@
 enum die_val {
 enum die_val {
 	DIE_BREAKPOINT,
 	DIE_BREAKPOINT,
 	DIE_SSTEP,
 	DIE_SSTEP,
+	DIE_NMI,
 };
 };
 
 
 #endif /* __ASM_AVR32_KDEBUG_H */
 #endif /* __ASM_AVR32_KDEBUG_H */