Skip to contentSkip to conversation

Allow multiple NMI handlers to register against NMIs

PATCH New Corey Minyard 1 participant 1 message Mar 7, 2005

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Allow multiple NMI handlers to register against NMIs

This patch has been posted to LKML a few times. I think it's ready for the mm series, but it needs to be looked over. This requires the previous nmicmos patch that I just posted for x86_64 and is in the mm kernel right now for i386.

-Corey

This patch allows multiple NMI handlers to dynamically bind and unbind to the NMI. The IPMI watchdog driver has the concept of a pretimeout where the hardware can issue an NMI a period of time before the actual watchdog reset. This lets a panic occur instead of a blind reset on a watchdog timeout. If used carefully, multiple users may use the NMI as long as they can correctly identify themselves as the watchdog source. This is possible with nmi_watchdog=2 and oprofile, but not with nmi_watchdog=1.

This is for both x86 and x86_64.

arch/i386/kernel/i386_ksyms.c +0 -3
Index: linux-2.6.11-mm1/arch/i386/kernel/i386_ksyms.c
===================================================================
@@ -167,9 +167,6 @@
 
 EXPORT_SYMBOL(rtc_lock);
 
-EXPORT_SYMBOL_GPL(set_nmi_callback);
-EXPORT_SYMBOL_GPL(unset_nmi_callback);
-
 #undef memcmp
 extern int memcmp(const void *,const void *,__kernel_size_t);
 EXPORT_SYMBOL(memcmp);
arch/i386/kernel/irq.c +4 -0
Index: linux-2.6.11-mm1/arch/i386/kernel/irq.c
===================================================================
@@ -204,6 +204,8 @@
  * /proc/interrupts printing:
  */
 
+extern void nmi_append_user_names(struct seq_file *p);
+
 int show_interrupts(struct seq_file *p, void *v)
 {
 	int i = *(loff_t *) v, j;
@@ -242,6 +244,8 @@
 		seq_printf(p, "NMI: ");
 		for_each_cpu(j)
 			seq_printf(p, "%10u ", nmi_count(j));
+		seq_printf(p, "                ");
+		nmi_append_user_names(p);
 		seq_putc(p, '\n');
 #ifdef CONFIG_X86_LOCAL_APIC
 		seq_printf(p, "LOC: ");
arch/i386/kernel/nmi.c +129 -77

304 lines not on this page · raw message

arch/i386/kernel/traps.c +202 -60

326 lines not on this page · raw message

arch/i386/oprofile/nmi_int.c +26 -4
Index: linux-2.6.11-mm1/arch/i386/oprofile/nmi_int.c
===================================================================
@@ -82,11 +82,23 @@
 #endif /* CONFIG_PM */
 
 
-static int nmi_callback(struct pt_regs * regs, int cpu)
+static int nmi_callback(void * dev_id, struct pt_regs * regs, int cpu, int handled)
 {
-	return model->check_ctrs(regs, &cpu_msrs[cpu]);
+	if (model->check_ctrs(regs, &cpu_msrs[cpu]))
+		return NOTIFY_OK;
+
+	return NOTIFY_DONE;
 }
  
+static struct nmi_handler nmi_handler =
+{
+	.link     = LIST_HEAD_INIT(nmi_handler.link),
+	.dev_name = "oprofile",
+	.dev_id   = NULL,
+	.handler  = nmi_callback,
+	.priority = NMI_HANDLER_MAX_PRIORITY /* Highest possible priority */
+};
+ 
  
 static void nmi_cpu_save_registers(struct op_msrs * msrs)
 {
@@ -173,8 +185,12 @@
 }
 
 
+static void nmi_cpu_shutdown(void * dummy);
+
 static int nmi_setup(void)
 {
+	int rv;
+
 	if (!allocate_msrs())
 		return -ENOMEM;
 
@@ -192,7 +208,13 @@
 	 */
 	on_each_cpu(nmi_save_registers, NULL, 0, 1);
 	on_each_cpu(nmi_cpu_setup, NULL, 0, 1);
-	set_nmi_callback(nmi_callback);
+	rv = request_nmi(&nmi_handler);
+	if (rv) {
+		smp_call_function(nmi_cpu_shutdown, NULL, 0, 1);
+		nmi_cpu_shutdown(0);
+		return rv;
+	}
+
 	nmi_enabled = 1;
 	return 0;
 }
@@ -243,7 +265,7 @@
 {
 	nmi_enabled = 0;
 	on_each_cpu(nmi_cpu_shutdown, NULL, 0, 1);
-	unset_nmi_callback();
+	release_nmi(&nmi_handler);
 	release_lapic_nmi();
 	free_msrs();
 }
arch/x86_64/kernel/nmi.c +88 -88

258 lines not on this page · raw message

drivers/acpi/osl.c +1 -1
Index: linux-2.6.11-mm1/drivers/acpi/osl.c
===================================================================
@@ -36,7 +36,7 @@
 #include <linux/kmod.h>
 #include <linux/delay.h>
 #include <linux/workqueue.h>
-#include <linux/nmi.h>
+#include <linux/nmi_watchdog.h>
 #include <acpi/acpi.h>
 #include <asm/io.h>
 #include <acpi/acpi_bus.h>
include/asm-i386/apic.h +0 -1
Index: linux-2.6.11-mm1/include/asm-i386/apic.h
===================================================================
@@ -117,7 +117,6 @@
 extern void release_lapic_nmi(void);
 extern void disable_timer_nmi_watchdog(void);
 extern void enable_timer_nmi_watchdog(void);
-extern void nmi_watchdog_tick (struct pt_regs * regs);
 extern int APIC_init_uniprocessor (void);
 extern void disable_APIC_timer(void);
 extern void enable_APIC_timer(void);
include/asm-i386/nmi.h +30 -14

54 lines not on this page · raw message

include/linux/nmi.h +7 -18
Index: linux-2.6.11-mm1/include/linux/nmi.h
===================================================================
@@ -1,22 +1,11 @@
 /*
- *  linux/include/linux/nmi.h
+ *	linux/include/linux/nmi.h
+ *
+ *	(C) 2002 Corey Minyard <[email protected]>
+ *
+ *	Include file for NMI handling.
  */
-#ifndef LINUX_NMI_H
-#define LINUX_NMI_H
-
-#include <asm/irq.h>
-
-/**
- * touch_nmi_watchdog - restart NMI watchdog timeout.
- * 
- * If the architecture supports the NMI watchdog, touch_nmi_watchdog()
- * may be used to reset the timeout - for code which intentionally
- * disables interrupts for a long time. This call is stateless.
- */
-#ifdef ARCH_HAS_NMI_WATCHDOG
-extern void touch_nmi_watchdog(void);
-#else
-# define touch_nmi_watchdog() do { } while(0)
-#endif
 
+#if defined(__i386__)
+#include <asm/nmi.h>
 #endif
include/linux/nmi_watchdog.h new file +22 -0
Index: linux-2.6.11-mm1/include/linux/nmi_watchdog.h
===================================================================
@@ -0,0 +1,22 @@
+/*
+ *  linux/include/linux/nmi.h
+ */
+#ifndef LINUX_NMI_WATCHDOG_H
+#define LINUX_NMI_WATCHDOG_H
+
+#include <asm/irq.h>
+
+/**
+ * touch_nmi_watchdog - restart NMI watchdog timeout.
+ * 
+ * If the architecture supports the NMI watchdog, touch_nmi_watchdog()
+ * may be used to reset the timeout - for code which intentionally
+ * disables interrupts for a long time. This call is stateless.
+ */
+#ifdef ARCH_HAS_NMI_WATCHDOG
+extern void touch_nmi_watchdog(void);
+#else
+# define touch_nmi_watchdog() do { } while(0)
+#endif
+
+#endif
kernel/panic.c +1 -1
Index: linux-2.6.11-mm1/kernel/panic.c
===================================================================
@@ -17,7 +17,7 @@
 #include <linux/init.h>
 #include <linux/sysrq.h>
 #include <linux/interrupt.h>
-#include <linux/nmi.h>
+#include <linux/nmi_watchdog.h>
 #include <linux/kexec.h>
 
 int panic_timeout;
kernel/sched.c +1 -1
Index: linux-2.6.11-mm1/kernel/sched.c
===================================================================
@@ -20,7 +20,7 @@
 
 #include <linux/mm.h>
 #include <linux/module.h>
-#include <linux/nmi.h>
+#include <linux/nmi_watchdog.h>
 #include <linux/init.h>
 #include <asm/uaccess.h>
 #include <linux/highmem.h>
kernel/sysctl.c +1 -3
Index: linux-2.6.11-mm1/kernel/sysctl.c
===================================================================
@@ -68,8 +68,6 @@
 
 #if defined(CONFIG_X86_LOCAL_APIC) && defined(CONFIG_X86)
 int unknown_nmi_panic;
-extern int proc_unknown_nmi_panic(ctl_table *, int, struct file *,
-				  void __user *, size_t *, loff_t *);
 #endif
 
 /* this is needed for the proc_dointvec_minmax for [fs_]overflow UID and GID */
@@ -621,7 +619,7 @@
 		.data           = &unknown_nmi_panic,
 		.maxlen         = sizeof (int),
 		.mode           = 0644,
-		.proc_handler   = &proc_unknown_nmi_panic,
+		.proc_handler   = &proc_dointvec,
 	},
 #endif
 #if defined(CONFIG_X86)
arch/i386/oprofile/nmi_timer_int.c +18 -3
Index: linux-2.6.11-mm1/arch/i386/oprofile/nmi_timer_int.c
===================================================================
@@ -18,16 +18,31 @@
 #include <asm/apic.h>
 #include <asm/ptrace.h>
  
-static int nmi_timer_callback(struct pt_regs * regs, int cpu)
+static int nmi_timer_callback(void *dev_id, struct pt_regs * regs, int cpu, int handled)
 {
 	oprofile_add_sample(regs, 0);
 	return 1;
 }
 
+static struct nmi_handler nmi_timer_handler =
+{
+	.link     = LIST_HEAD_INIT(nmi_timer_handler.link),
+	.dev_name = "oprofile_timer",
+	.dev_id   = NULL,
+	.handler  = nmi_timer_callback,
+	.priority = NMI_HANDLER_MAX_PRIORITY /* Highest possible priority */
+};
+ 
 static int timer_start(void)
 {
+	int rv;
+
 	disable_timer_nmi_watchdog();
-	set_nmi_callback(nmi_timer_callback);
+	rv = request_nmi(&nmi_timer_handler);
+	if (rv) {
+		enable_timer_nmi_watchdog();
+		return rv;
+	}
 	return 0;
 }
 
@@ -35,7 +50,7 @@
 static void timer_stop(void)
 {
 	enable_timer_nmi_watchdog();
-	unset_nmi_callback();
+	release_nmi(&nmi_timer_handler);
 	synchronize_kernel();
 }
 
include/asm-x86_64/apic.h +0 -1
Index: linux-2.6.11-mm1/include/asm-x86_64/apic.h
===================================================================
@@ -93,7 +93,6 @@
 extern void release_lapic_nmi(void);
 extern void disable_timer_nmi_watchdog(void);
 extern void enable_timer_nmi_watchdog(void);
-extern void nmi_watchdog_tick (struct pt_regs * regs, unsigned reason);
 extern int APIC_init_uniprocessor (void);
 extern void disable_APIC_timer(void);
 extern void enable_APIC_timer(void);
arch/x86_64/kernel/x8664_ksyms.c +0 -3
Index: linux-2.6.11-mm1/arch/x86_64/kernel/x8664_ksyms.c
===================================================================
@@ -129,9 +129,6 @@
 
 EXPORT_SYMBOL(rtc_lock);
 
-EXPORT_SYMBOL_GPL(set_nmi_callback);
-EXPORT_SYMBOL_GPL(unset_nmi_callback);
-
 /* Export string functions. We normally rely on gcc builtin for most of these,
    but gcc sometimes decides not to inline them. */    
 #undef memcpy
arch/x86_64/kernel/traps.c +213 -41

303 lines not on this page · raw message

arch/x86_64/kernel/io_apic.c +1 -1
Index: linux-2.6.11-mm1/arch/x86_64/kernel/io_apic.c
===================================================================
@@ -1670,7 +1670,7 @@
 	}
 	printk(" failed.\n");
 
-	if (nmi_watchdog) {
+	if (nmi_watchdog == NMI_IO_APIC) {
 		printk(KERN_WARNING "timer doesn't work through the IO-APIC - disabling NMI Watchdog!\n");
 		nmi_watchdog = 0;
 	}
arch/x86_64/kernel/irq.c +4 -0
Index: linux-2.6.11-mm1/arch/x86_64/kernel/irq.c
===================================================================
@@ -28,6 +28,8 @@
  * Generic, controller-independent functions:
  */
 
+extern void nmi_append_user_names(struct seq_file *p);
+
 int show_interrupts(struct seq_file *p, void *v)
 {
 	int i = *(loff_t *) v, j;
@@ -69,6 +71,8 @@
 		for (j = 0; j < NR_CPUS; j++)
 			if (cpu_online(j))
 				seq_printf(p, "%10u ", cpu_pda[j].__nmi_count);
+		seq_printf(p, "                ");
+		nmi_append_user_names(p);
 		seq_putc(p, '\n');
 #ifdef CONFIG_X86_LOCAL_APIC
 		seq_printf(p, "LOC: ");
include/asm-x86_64/nmi.h +30 -14

56 lines not on this page · raw message