
From: Ingo Molnar <mingo@elte.hu>

This patch adds a new kernel debug feature: CONFIG_DETECT_SOFTLOCKUP. 

When enabled then per-CPU watchdog threads are started, which try to run
once per second.  If they get delayed for more than 10 seconds then a
callback from the timer interrupt detects this condition and prints out a
warning message and a stack dump (once per lockup incident).  The feature
is otherwise non-intrusive, it doesnt try to unlock the box in any way, it
only gets the debug info out, automatically, and on all CPUs affected by
the lockup.

Tested this on x86, both with the feature enabled (in which case a provoked
lockup was correctly detected) and with the feature disabled.  It is
CPU-hotplug aware.  Should work on every architecture.

Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/arch/i386/kernel/time.c   |    1 
 25-akpm/arch/x86_64/kernel/time.c |    1 
 25-akpm/include/linux/sched.h     |   17 ++++
 25-akpm/init/main.c               |    1 
 25-akpm/kernel/Makefile           |    1 
 25-akpm/kernel/power/swsusp.c     |    1 
 25-akpm/kernel/softlockup.c       |  131 ++++++++++++++++++++++++++++++++++++++
 25-akpm/kernel/timer.c            |    1 
 25-akpm/lib/Kconfig.debug         |   19 +++++
 9 files changed, 173 insertions(+)

diff -puN include/linux/sched.h~detect-soft-lockups include/linux/sched.h
--- 25/include/linux/sched.h~detect-soft-lockups	2005-02-23 02:10:33.000000000 -0800
+++ 25-akpm/include/linux/sched.h	2005-02-23 02:10:41.000000000 -0800
@@ -175,6 +175,23 @@ extern void update_process_times(int use
 extern void scheduler_tick(void);
 extern unsigned long cache_decay_ticks;
 
+#ifdef CONFIG_DETECT_SOFTLOCKUP
+extern void softlockup_tick(struct pt_regs *regs);
+extern void spawn_softlockup_task(void);
+extern void touch_softlockup_watchdog(void);
+#else
+static inline void softlockup_tick(struct pt_regs *regs)
+{
+}
+static inline void spawn_softlockup_task(void)
+{
+}
+static inline void touch_softlockup_watchdog(void)
+{
+}
+#endif
+
+
 /* Attach to any functions which should be ignored in wchan output. */
 #define __sched		__attribute__((__section__(".sched.text")))
 /* Is this address in the __sched functions? */
diff -puN init/main.c~detect-soft-lockups init/main.c
--- 25/init/main.c~detect-soft-lockups	2005-02-23 02:10:33.000000000 -0800
+++ 25-akpm/init/main.c	2005-02-23 02:10:33.000000000 -0800
@@ -596,6 +596,7 @@ static void do_pre_smp_initcalls(void)
 	migration_init();
 #endif
 	spawn_ksoftirqd();
+	spawn_softlockup_task();
 }
 
 static void run_init_process(char *init_filename)
diff -puN kernel/Makefile~detect-soft-lockups kernel/Makefile
--- 25/kernel/Makefile~detect-soft-lockups	2005-02-23 02:10:33.000000000 -0800
+++ 25-akpm/kernel/Makefile	2005-02-23 02:10:33.000000000 -0800
@@ -25,6 +25,7 @@ obj-$(CONFIG_AUDIT) += audit.o
 obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
 obj-$(CONFIG_KPROBES) += kprobes.o
 obj-$(CONFIG_SYSFS) += ksysfs.o
+obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o
 obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
 
 ifneq ($(CONFIG_IA64),y)
diff -puN /dev/null kernel/softlockup.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/kernel/softlockup.c	2005-02-23 02:10:41.000000000 -0800
@@ -0,0 +1,131 @@
+/*
+ * Detect Soft Lockups
+ *
+ * started by Ingo Molnar, (C) 2005, Red Hat
+ *
+ * this code detects soft lockups: incidents in where on a CPU
+ * the kernel does not reschedule for 10 seconds or more.
+ */
+
+#include <linux/mm.h>
+#include <linux/cpu.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/notifier.h>
+
+static DEFINE_SPINLOCK(print_lock);
+
+static DEFINE_PER_CPU(unsigned long, timestamp) = 0;
+static DEFINE_PER_CPU(unsigned long, print_timestamp) = 0;
+static DEFINE_PER_CPU(struct task_struct *, watchdog_task);
+
+void touch_softlockup_watchdog(void)
+{
+	per_cpu(timestamp, _smp_processor_id()) = jiffies;
+}
+
+/*
+ * This callback runs from the timer interrupt, and checks
+ * whether the watchdog thread has hung or not:
+ */
+void softlockup_tick(struct pt_regs *regs)
+{
+	int this_cpu = smp_processor_id();
+	unsigned long timestamp = per_cpu(timestamp, this_cpu);
+
+	if (per_cpu(print_timestamp, this_cpu) == timestamp)
+		return;
+
+	if (time_after(jiffies, timestamp + 10*HZ)) {
+		per_cpu(print_timestamp, this_cpu) = timestamp;
+
+		spin_lock(&print_lock);
+		printk(KERN_ERR "BUG: soft lockup detected on CPU#%d!\n",
+			this_cpu);
+		show_regs(regs);
+		spin_unlock(&print_lock);
+	}
+}
+
+/*
+ * The watchdog thread - runs every second and touches the timestamp.
+ */
+static int watchdog(void * __bind_cpu)
+{
+	struct sched_param param = { .sched_priority = 99 };
+	int this_cpu = (long) __bind_cpu;
+
+	printk("softlockup thread %d started up.\n", this_cpu);
+
+	sched_setscheduler(current, SCHED_FIFO, &param);
+	current->flags |= PF_NOFREEZE;
+
+	set_current_state(TASK_INTERRUPTIBLE);
+
+	/*
+	 * Run briefly once per second - if this gets delayed for
+	 * more than 10 seconds then the debug-printout triggers
+	 * in softlockup_tick():
+	 */
+	while (!kthread_should_stop()) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		msleep_interruptible(HZ);
+		touch_softlockup_watchdog();
+	}
+	__set_current_state(TASK_RUNNING);
+
+	return 0;
+}
+
+/*
+ * Create/destroy watchdog threads as CPUs come and go:
+ */
+static int __devinit
+cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu)
+{
+	int hotcpu = (unsigned long)hcpu;
+	struct task_struct *p;
+
+	switch (action) {
+	case CPU_UP_PREPARE:
+		BUG_ON(per_cpu(watchdog_task, hotcpu));
+		p = kthread_create(watchdog, hcpu, "watchdog/%d", hotcpu);
+		if (IS_ERR(p)) {
+			printk("watchdog for %i failed\n", hotcpu);
+			return NOTIFY_BAD;
+		}
+  		per_cpu(watchdog_task, hotcpu) = p;
+		kthread_bind(p, hotcpu);
+ 		break;
+	case CPU_ONLINE:
+
+		wake_up_process(per_cpu(watchdog_task, hotcpu));
+		break;
+#ifdef CONFIG_HOTPLUG_CPU
+	case CPU_UP_CANCELED:
+		/* Unbind so it can run.  Fall thru. */
+		kthread_bind(per_cpu(watchdog_task, hotcpu), smp_processor_id());
+	case CPU_DEAD:
+		p = per_cpu(watchdog_task, hotcpu);
+		per_cpu(watchdog_task, hotcpu) = NULL;
+		kthread_stop(p);
+		break;
+#endif /* CONFIG_HOTPLUG_CPU */
+ 	}
+	return NOTIFY_OK;
+}
+
+static struct notifier_block __devinitdata cpu_nfb = {
+	.notifier_call = cpu_callback
+};
+
+__init void spawn_softlockup_task(void)
+{
+	void *cpu = (void *)(long)smp_processor_id();
+
+	cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);
+	cpu_callback(&cpu_nfb, CPU_ONLINE, cpu);
+	register_cpu_notifier(&cpu_nfb);
+}
+
diff -puN kernel/timer.c~detect-soft-lockups kernel/timer.c
--- 25/kernel/timer.c~detect-soft-lockups	2005-02-23 02:10:33.000000000 -0800
+++ 25-akpm/kernel/timer.c	2005-02-23 02:10:33.000000000 -0800
@@ -921,6 +921,7 @@ void do_timer(struct pt_regs *regs)
 {
 	jiffies_64++;
 	update_times();
+	softlockup_tick(regs);
 }
 
 #ifdef __ARCH_WANT_SYS_ALARM
diff -puN lib/Kconfig.debug~detect-soft-lockups lib/Kconfig.debug
--- 25/lib/Kconfig.debug~detect-soft-lockups	2005-02-23 02:10:33.000000000 -0800
+++ 25-akpm/lib/Kconfig.debug	2005-02-23 02:10:33.000000000 -0800
@@ -36,6 +36,25 @@ config LOG_BUF_SHIFT
 		     13 =>  8 KB
 		     12 =>  4 KB
 
+config DETECT_SOFTLOCKUP
+	bool "Detect Soft Lockups"
+	depends on DEBUG_KERNEL
+	default y
+	help
+	  Say Y here to enable the kernel to detect "soft lockups",
+	  which are bugs that cause the kernel to loop in kernel
+	  mode for more than 10 seconds, without giving other tasks a
+	  chance to run.
+
+	  When a soft-lockup is detected, the kernel will print the
+	  current stack trace (which you should report), but the
+	  system will stay locked up. This feature has negligible
+	  overhead.
+
+	  (Note that "hard lockups" are separate type of bugs that
+	   can be detected via the NMI-watchdog, on platforms that
+	   support it.)
+
 config SCHEDSTATS
 	bool "Collect scheduler statistics"
 	depends on DEBUG_KERNEL && PROC_FS
diff -puN arch/i386/kernel/time.c~detect-soft-lockups arch/i386/kernel/time.c
--- 25/arch/i386/kernel/time.c~detect-soft-lockups	2005-02-23 02:10:47.000000000 -0800
+++ 25-akpm/arch/i386/kernel/time.c	2005-02-23 02:10:47.000000000 -0800
@@ -378,6 +378,7 @@ static int timer_resume(struct sys_devic
 	write_sequnlock_irqrestore(&xtime_lock, flags);
 	jiffies += sleep_length;
 	wall_jiffies += sleep_length;
+	touch_softlockup_watchdog();
 	return 0;
 }
 
diff -puN arch/x86_64/kernel/time.c~detect-soft-lockups arch/x86_64/kernel/time.c
--- 25/arch/x86_64/kernel/time.c~detect-soft-lockups	2005-02-23 02:10:47.000000000 -0800
+++ 25-akpm/arch/x86_64/kernel/time.c	2005-02-23 02:10:47.000000000 -0800
@@ -988,6 +988,7 @@ static int timer_resume(struct sys_devic
 	write_sequnlock_irqrestore(&xtime_lock,flags);
 	jiffies += sleep_length;
 	wall_jiffies += sleep_length;
+	touch_softlockup_watchdog();
 	return 0;
 }
 
diff -puN kernel/power/swsusp.c~detect-soft-lockups kernel/power/swsusp.c
--- 25/kernel/power/swsusp.c~detect-soft-lockups	2005-02-23 02:10:47.000000000 -0800
+++ 25-akpm/kernel/power/swsusp.c	2005-02-23 02:10:47.000000000 -0800
@@ -868,6 +868,7 @@ int swsusp_suspend(void)
 	/* Restore control flow magically appears here */
 	restore_processor_state();
 	restore_highmem();
+	touch_softlockup_watchdog();
 	device_power_up();
 	local_irq_enable();
 	return error;
_
