[AVR32] NMI debugging
Haavard Skinnemoen [Wed, 10 Oct 2007 12:58:29 +0000 (14:58 +0200)]
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>

Documentation/kernel-parameters.txt
arch/avr32/Kconfig
arch/avr32/kernel/Makefile
arch/avr32/kernel/irq.c
arch/avr32/kernel/nmi_debug.c [new file with mode: 0644]
arch/avr32/kernel/traps.c
arch/avr32/mach-at32ap/at32ap700x.c
arch/avr32/mach-at32ap/extint.c
include/asm-avr32/irq.h
include/asm-avr32/kdebug.h

index c417877..17fc60e 100644 (file)
@@ -34,6 +34,7 @@ parameter is applicable:
        ALSA    ALSA sound support is enabled.
        APIC    APIC support is enabled.
        APM     Advanced Power Management support is enabled.
+       AVR32   AVR32 architecture is enabled.
        AX25    Appropriate AX.25 support is enabled.
        BLACKFIN Blackfin architecture 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.
                        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
 
        no387           [BUGS=X86-32] Tells the kernel to use the 387 maths
index 516015b..e34e2c9 100644 (file)
@@ -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"
index bc224a4..e4b6d12 100644 (file)
@@ -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
index 61f2de2..a8e767d 100644 (file)
@@ -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 (file)
index 0000000..3414b85
--- /dev/null
@@ -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);
index 870c075..cf6f686 100644 (file)
@@ -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)
index 9386e1f..14e61f0 100644 (file)
@@ -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>
index f5bfd4c..e108e7b 100644 (file)
 #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);
index 83e6549..9315724 100644 (file)
@@ -11,4 +11,9 @@
 
 #define irq_canonicalize(i)    (i)
 
+#ifndef __ASSEMBLER__
+int nmi_enable(void);
+void nmi_disable(void);
+#endif
+
 #endif /* __ASM_AVR32_IOCTLS_H */
index fd7e990..ca4f954 100644 (file)
@@ -5,6 +5,7 @@
 enum die_val {
        DIE_BREAKPOINT,
        DIE_SSTEP,
+       DIE_NMI,
 };
 
 #endif /* __ASM_AVR32_KDEBUG_H */