summaryrefslogtreecommitdiff
path: root/arch/sparc/kernel
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2009-01-29 21:22:47 -0800
committerDavid S. Miller <davem@davemloft.net>2009-01-30 00:03:53 -0800
commite5553a6d04421eec326a629571d696e8e745a0e4 (patch)
treeb6fe49a18135dbe27a464fb78828b2150c679689 /arch/sparc/kernel
parentc3cf5e8cc56d272f828a66610bb78bbb727b2ce1 (diff)
sparc64: Implement NMI watchdog on capable cpus.
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'arch/sparc/kernel')
-rw-r--r--arch/sparc/kernel/Makefile1
-rw-r--r--arch/sparc/kernel/irq_64.c68
-rw-r--r--arch/sparc/kernel/nmi.c224
-rw-r--r--arch/sparc/kernel/pcr.c17
4 files changed, 245 insertions, 65 deletions
diff --git a/arch/sparc/kernel/Makefile b/arch/sparc/kernel/Makefile
index cb182d9c2f2b..54742e58831c 100644
--- a/arch/sparc/kernel/Makefile
+++ b/arch/sparc/kernel/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_SPARC64) += hvapi.o
obj-$(CONFIG_SPARC64) += sstate.o
obj-$(CONFIG_SPARC64) += mdesc.o
obj-$(CONFIG_SPARC64) += pcr.o
+obj-$(CONFIG_SPARC64) += nmi.o
# sparc32 do not use GENERIC_HARDIRQS but uses the generic devres implementation
obj-$(CONFIG_SPARC32) += devres.o
diff --git a/arch/sparc/kernel/irq_64.c b/arch/sparc/kernel/irq_64.c
index cab8e0286871..e289376198eb 100644
--- a/arch/sparc/kernel/irq_64.c
+++ b/arch/sparc/kernel/irq_64.c
@@ -196,6 +196,11 @@ int show_interrupts(struct seq_file *p, void *v)
seq_putc(p, '\n');
skip:
spin_unlock_irqrestore(&irq_desc[i].lock, flags);
+ } else if (i == NR_IRQS) {
+ seq_printf(p, "NMI: ");
+ for_each_online_cpu(j)
+ seq_printf(p, "%10u ", cpu_data(j).__nmi_count);
+ seq_printf(p, " Non-maskable interrupts\n");
}
return 0;
}
@@ -778,69 +783,6 @@ void do_softirq(void)
local_irq_restore(flags);
}
-static void unhandled_perf_irq(struct pt_regs *regs)
-{
- unsigned long pcr, pic;
-
- read_pcr(pcr);
- read_pic(pic);
-
- write_pcr(0);
-
- printk(KERN_EMERG "CPU %d: Got unexpected perf counter IRQ.\n",
- smp_processor_id());
- printk(KERN_EMERG "CPU %d: PCR[%016lx] PIC[%016lx]\n",
- smp_processor_id(), pcr, pic);
-}
-
-/* Almost a direct copy of the powerpc PMC code. */
-static DEFINE_SPINLOCK(perf_irq_lock);
-static void *perf_irq_owner_caller; /* mostly for debugging */
-static void (*perf_irq)(struct pt_regs *regs) = unhandled_perf_irq;
-
-/* Invoked from level 15 PIL handler in trap table. */
-void perfctr_irq(int irq, struct pt_regs *regs)
-{
- clear_softint(1 << irq);
- perf_irq(regs);
-}
-
-int register_perfctr_intr(void (*handler)(struct pt_regs *))
-{
- int ret;
-
- if (!handler)
- return -EINVAL;
-
- spin_lock(&perf_irq_lock);
- if (perf_irq != unhandled_perf_irq) {
- printk(KERN_WARNING "register_perfctr_intr: "
- "perf IRQ busy (reserved by caller %p)\n",
- perf_irq_owner_caller);
- ret = -EBUSY;
- goto out;
- }
-
- perf_irq_owner_caller = __builtin_return_address(0);
- perf_irq = handler;
-
- ret = 0;
-out:
- spin_unlock(&perf_irq_lock);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(register_perfctr_intr);
-
-void release_perfctr_intr(void (*handler)(struct pt_regs *))
-{
- spin_lock(&perf_irq_lock);
- perf_irq_owner_caller = NULL;
- perf_irq = unhandled_perf_irq;
- spin_unlock(&perf_irq_lock);
-}
-EXPORT_SYMBOL_GPL(release_perfctr_intr);
-
#ifdef CONFIG_HOTPLUG_CPU
void fixup_irqs(void)
{
diff --git a/arch/sparc/kernel/nmi.c b/arch/sparc/kernel/nmi.c
new file mode 100644
index 000000000000..ffc690093343
--- /dev/null
+++ b/arch/sparc/kernel/nmi.c
@@ -0,0 +1,224 @@
+/* Pseudo NMI support on sparc64 systems.
+ *
+ * Copyright (C) 2009 David S. Miller <davem@davemloft.net>
+ *
+ * The NMI watchdog support and infrastructure is based almost
+ * entirely upon the x86 NMI support code.
+ */
+#include <linux/kernel.h>
+#include <linux/param.h>
+#include <linux/init.h>
+#include <linux/percpu.h>
+#include <linux/nmi.h>
+#include <linux/module.h>
+#include <linux/kprobes.h>
+#include <linux/kernel_stat.h>
+#include <linux/slab.h>
+#include <linux/kdebug.h>
+#include <linux/delay.h>
+#include <linux/smp.h>
+
+#include <asm/ptrace.h>
+#include <asm/local.h>
+#include <asm/pcr.h>
+
+/* We don't have a real NMI on sparc64, but we can fake one
+ * up using profiling counter overflow interrupts and interrupt
+ * levels.
+ *
+ * The profile overflow interrupts at level 15, so we use
+ * level 14 as our IRQ off level.
+ */
+
+static int nmi_watchdog_active;
+static int panic_on_timeout;
+
+int nmi_usable;
+EXPORT_SYMBOL_GPL(nmi_usable);
+
+static unsigned int nmi_hz = HZ;
+
+static DEFINE_PER_CPU(unsigned int, last_irq_sum);
+static DEFINE_PER_CPU(local_t, alert_counter);
+static DEFINE_PER_CPU(int, nmi_touch);
+
+void touch_nmi_watchdog(void)
+{
+ if (nmi_watchdog_active) {
+ int cpu;
+
+ for_each_present_cpu(cpu) {
+ if (per_cpu(nmi_touch, cpu) != 1)
+ per_cpu(nmi_touch, cpu) = 1;
+ }
+ }
+
+ touch_softlockup_watchdog();
+}
+EXPORT_SYMBOL(touch_nmi_watchdog);
+
+static void die_nmi(const char *str, struct pt_regs *regs, int do_panic)
+{
+ if (notify_die(DIE_NMIWATCHDOG, str, regs, 0,
+ pt_regs_trap_type(regs), SIGINT) == NOTIFY_STOP)
+ return;
+
+ console_verbose();
+ bust_spinlocks(1);
+
+ printk(KERN_EMERG "%s", str);
+ printk(" on CPU%d, ip %08lx, registers:\n",
+ smp_processor_id(), regs->tpc);
+ show_regs(regs);
+
+ bust_spinlocks(0);
+
+ if (do_panic || panic_on_oops)
+ panic("Non maskable interrupt");
+
+ local_irq_enable();
+ do_exit(SIGBUS);
+}
+
+notrace __kprobes void perfctr_irq(int irq, struct pt_regs *regs)
+{
+ unsigned int sum, touched = 0;
+ int cpu = smp_processor_id();
+
+ clear_softint(1 << irq);
+ pcr_ops->write(PCR_PIC_PRIV);
+
+ local_cpu_data().__nmi_count++;
+
+ if (notify_die(DIE_NMI, "nmi", regs, 0,
+ pt_regs_trap_type(regs), SIGINT) == NOTIFY_STOP)
+ touched = 1;
+
+ sum = kstat_cpu(cpu).irqs[0];
+ if (__get_cpu_var(nmi_touch)) {
+ __get_cpu_var(nmi_touch) = 0;
+ touched = 1;
+ }
+ if (!touched && __get_cpu_var(last_irq_sum) == sum) {
+ local_inc(&__get_cpu_var(alert_counter));
+ if (local_read(&__get_cpu_var(alert_counter)) == 5 * nmi_hz)
+ die_nmi("BUG: NMI Watchdog detected LOCKUP",
+ regs, panic_on_timeout);
+ } else {
+ __get_cpu_var(last_irq_sum) = sum;
+ local_set(&__get_cpu_var(alert_counter), 0);
+ }
+ if (nmi_usable) {
+ write_pic(picl_value(nmi_hz));
+ pcr_ops->write(pcr_enable);
+ }
+}
+
+static inline unsigned int get_nmi_count(int cpu)
+{
+ return cpu_data(cpu).__nmi_count;
+}
+
+static int endflag __initdata;
+
+static __init void nmi_cpu_busy(void *data)
+{
+ local_irq_enable_in_hardirq();
+ while (endflag == 0)
+ mb();
+}
+
+static void report_broken_nmi(int cpu, int *prev_nmi_count)
+{
+ printk(KERN_CONT "\n");
+
+ printk(KERN_WARNING
+ "WARNING: CPU#%d: NMI appears to be stuck (%d->%d)!\n",
+ cpu, prev_nmi_count[cpu], get_nmi_count(cpu));
+
+ printk(KERN_WARNING
+ "Please report this to bugzilla.kernel.org,\n");
+ printk(KERN_WARNING
+ "and attach the output of the 'dmesg' command.\n");
+
+ nmi_usable = 0;
+}
+
+static void stop_watchdog(void *unused)
+{
+ pcr_ops->write(PCR_PIC_PRIV);
+}
+
+static int __init check_nmi_watchdog(void)
+{
+ unsigned int *prev_nmi_count;
+ int cpu, err;
+
+ prev_nmi_count = kmalloc(nr_cpu_ids * sizeof(unsigned int), GFP_KERNEL);
+ if (!prev_nmi_count) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ printk(KERN_INFO "Testing NMI watchdog ... ");
+
+ smp_call_function(nmi_cpu_busy, (void *)&endflag, 0);
+
+ for_each_possible_cpu(cpu)
+ prev_nmi_count[cpu] = get_nmi_count(cpu);
+ local_irq_enable();
+ mdelay((20 * 1000) / nmi_hz); /* wait 20 ticks */
+
+ for_each_online_cpu(cpu) {
+ if (get_nmi_count(cpu) - prev_nmi_count[cpu] <= 5)
+ report_broken_nmi(cpu, prev_nmi_count);
+ }
+ endflag = 1;
+ if (!nmi_usable) {
+ kfree(prev_nmi_count);
+ err = -ENODEV;
+ goto error;
+ }
+ printk("OK.\n");
+
+ nmi_hz = 1;
+
+ kfree(prev_nmi_count);
+ return 0;
+error:
+ on_each_cpu(stop_watchdog, NULL, 1);
+ return err;
+}
+
+static void start_watchdog(void *unused)
+{
+ pcr_ops->write(PCR_PIC_PRIV);
+ write_pic(picl_value(nmi_hz));
+
+ pcr_ops->write(pcr_enable);
+}
+
+void nmi_adjust_hz(unsigned int new_hz)
+{
+ nmi_hz = new_hz;
+ on_each_cpu(start_watchdog, NULL, 1);
+}
+EXPORT_SYMBOL_GPL(nmi_adjust_hz);
+
+int __init nmi_init(void)
+{
+ nmi_usable = 1;
+
+ on_each_cpu(start_watchdog, NULL, 1);
+
+ return check_nmi_watchdog();
+}
+
+static int __init setup_nmi_watchdog(char *str)
+{
+ if (!strncmp(str, "panic", 5))
+ panic_on_timeout = 1;
+
+ return 0;
+}
+__setup("nmi_watchdog=", setup_nmi_watchdog);
diff --git a/arch/sparc/kernel/pcr.c b/arch/sparc/kernel/pcr.c
index c4f24703b165..92e0dda141a4 100644
--- a/arch/sparc/kernel/pcr.c
+++ b/arch/sparc/kernel/pcr.c
@@ -9,12 +9,22 @@
#include <asm/pil.h>
#include <asm/pcr.h>
+#include <asm/nmi.h>
/* This code is shared between various users of the performance
* counters. Users will be oprofile, pseudo-NMI watchdog, and the
* perf_counter support layer.
*/
+#define PCR_SUN4U_ENABLE (PCR_PIC_PRIV | PCR_STRACE | PCR_UTRACE)
+#define PCR_N2_ENABLE (PCR_PIC_PRIV | PCR_STRACE | PCR_UTRACE | \
+ PCR_N2_TOE_OV1 | \
+ (2 << PCR_N2_SL1_SHIFT) | \
+ (0xff << PCR_N2_MASK1_SHIFT))
+
+u64 pcr_enable;
+unsigned int picl_shift;
+
/* Performance counter interrupts run unmasked at PIL level 15.
* Therefore we can't do things like wakeups and other work
* that expects IRQ disabling to be adhered to in locking etc.
@@ -117,12 +127,15 @@ int __init pcr_arch_init(void)
switch (tlb_type) {
case hypervisor:
pcr_ops = &n2_pcr_ops;
+ pcr_enable = PCR_N2_ENABLE;
+ picl_shift = 2;
break;
- case spitfire:
case cheetah:
case cheetah_plus:
+ case spitfire:
pcr_ops = &direct_pcr_ops;
+ pcr_enable = PCR_SUN4U_ENABLE;
break;
default:
@@ -130,7 +143,7 @@ int __init pcr_arch_init(void)
goto out_unregister;
}
- return 0;
+ return nmi_init();
out_unregister:
unregister_perf_hsvc();