blob: bf27eaae45e60833fe89361844fd76c6bfa91e4f [file] [log] [blame]
From b18f75c721d3461c0c21bc9d3bb709a4735070a4 Mon Sep 17 00:00:00 2001
From: Ingo Molnar <mingo@elte.hu>
Date: Fri, 3 Jul 2009 08:29:34 -0500
Subject: [PATCH] timers: prepare for full preemption
commit 6dce6f3f6e182484e48164246f4a268aa42fd19c in tip.
When softirqs can be preempted we need to make sure that cancelling
the timer from the active thread can not deadlock vs. a running timer
callback. Add a waitqueue to resolve that.
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com>
diff --git a/include/linux/timer.h b/include/linux/timer.h
index a2d1eb6..af92a91 100644
--- a/include/linux/timer.h
+++ b/include/linux/timer.h
@@ -225,10 +225,12 @@ static inline void timer_stats_timer_clear_start_info(struct timer_list *timer)
extern void add_timer(struct timer_list *timer);
-#ifdef CONFIG_SMP
+#if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT_SOFTIRQS)
+ extern int timer_pending_sync(struct timer_list *timer);
extern int try_to_del_timer_sync(struct timer_list *timer);
extern int del_timer_sync(struct timer_list *timer);
#else
+# define timer_pending_sync(t) timer_pending(t)
# define try_to_del_timer_sync(t) del_timer(t)
# define del_timer_sync(t) del_timer(t)
#endif
diff --git a/kernel/timer.c b/kernel/timer.c
index c61a794..fc3a98c 100644
--- a/kernel/timer.c
+++ b/kernel/timer.c
@@ -34,6 +34,7 @@
#include <linux/posix-timers.h>
#include <linux/cpu.h>
#include <linux/syscalls.h>
+#include <linux/kallsyms.h>
#include <linux/delay.h>
#include <linux/tick.h>
#include <linux/kallsyms.h>
@@ -74,6 +75,7 @@ struct tvec_root {
struct tvec_base {
spinlock_t lock;
struct timer_list *running_timer;
+ wait_queue_head_t wait_for_running_timer;
unsigned long timer_jiffies;
unsigned long next_timer;
struct tvec_root tv1;
@@ -322,9 +324,7 @@ EXPORT_SYMBOL_GPL(round_jiffies_up_relative);
static inline void set_running_timer(struct tvec_base *base,
struct timer_list *timer)
{
-#ifdef CONFIG_SMP
base->running_timer = timer;
-#endif
}
static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
@@ -656,6 +656,7 @@ __mod_timer(struct timer_list *timer, unsigned long expires,
debug_activate(timer, expires);
+ preempt_disable();
cpu = smp_processor_id();
#if defined(CONFIG_NO_HZ) && defined(CONFIG_SMP)
@@ -666,6 +667,8 @@ __mod_timer(struct timer_list *timer, unsigned long expires,
cpu = preferred_cpu;
}
#endif
+ preempt_enable();
+
new_base = per_cpu(tvec_bases, cpu);
if (base != new_base) {
@@ -825,6 +828,18 @@ void add_timer_on(struct timer_list *timer, int cpu)
}
EXPORT_SYMBOL_GPL(add_timer_on);
+/*
+ * Wait for a running timer
+ */
+void wait_for_running_timer(struct timer_list *timer)
+{
+ struct tvec_base *base = timer->base;
+
+ if (base->running_timer == timer)
+ wait_event(base->wait_for_running_timer,
+ base->running_timer != timer);
+}
+
/**
* del_timer - deactive a timer.
* @timer: the timer to be deactivated
@@ -859,7 +874,34 @@ int del_timer(struct timer_list *timer)
}
EXPORT_SYMBOL(del_timer);
-#ifdef CONFIG_SMP
+#if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT_SOFTIRQS)
+/*
+ * This function checks whether a timer is active and not running on any
+ * CPU. Upon successful (ret >= 0) exit the timer is not queued and the
+ * handler is not running on any CPU.
+ *
+ * It must not be called from interrupt contexts.
+ */
+int timer_pending_sync(struct timer_list *timer)
+{
+ struct tvec_base *base;
+ unsigned long flags;
+ int ret = -1;
+
+ base = lock_timer_base(timer, &flags);
+
+ if (base->running_timer == timer)
+ goto out;
+
+ ret = 0;
+ if (timer_pending(timer))
+ ret = 1;
+out:
+ spin_unlock_irqrestore(&base->lock, flags);
+
+ return ret;
+}
+
/**
* try_to_del_timer_sync - Try to deactivate a timer
* @timer: timer do del
@@ -927,7 +969,7 @@ int del_timer_sync(struct timer_list *timer)
int ret = try_to_del_timer_sync(timer);
if (ret >= 0)
return ret;
- cpu_relax();
+ wait_for_running_timer(timer);
}
}
EXPORT_SYMBOL(del_timer_sync);
@@ -972,6 +1014,20 @@ static inline void __run_timers(struct tvec_base *base)
struct list_head *head = &work_list;
int index = base->timer_jiffies & TVR_MASK;
+ if (softirq_need_resched()) {
+ spin_unlock_irq(&base->lock);
+ wake_up(&base->wait_for_running_timer);
+ cond_resched_softirq_context();
+ cpu_relax();
+ spin_lock_irq(&base->lock);
+ /*
+ * We can simply continue after preemption, nobody
+ * else can touch timer_jiffies so 'index' is still
+ * valid. Any new jiffy will be taken care of in
+ * subsequent loops:
+ */
+ }
+
/*
* Cascade timers:
*/
@@ -1027,18 +1083,17 @@ static inline void __run_timers(struct tvec_base *base)
lock_map_release(&lockdep_map);
if (preempt_count != preempt_count()) {
- printk(KERN_ERR "huh, entered %p "
- "with preempt_count %08x, exited"
- " with %08x?\n",
- fn, preempt_count,
- preempt_count());
- BUG();
+ print_symbol("BUG: unbalanced timer-handler preempt count in %s!\n", (unsigned long) fn);
+ printk("entered with %08x, exited with %08x.\n", preempt_count, preempt_count());
+ preempt_count() = preempt_count;
}
}
+ set_running_timer(base, NULL);
+ cond_resched_softirq_context();
spin_lock_irq(&base->lock);
}
}
- set_running_timer(base, NULL);
+ wake_up(&base->wait_for_running_timer);
spin_unlock_irq(&base->lock);
}
@@ -1204,12 +1259,30 @@ void update_process_times(int user_tick)
}
/*
+ * Time of day handling:
+ */
+static inline void update_times(void)
+{
+ static unsigned long last_tick = INITIAL_JIFFIES;
+ unsigned long ticks, flags;
+
+ write_raw_seqlock_irqsave(&xtime_lock, flags);
+ ticks = jiffies - last_tick;
+ if (ticks) {
+ last_tick += ticks;
+ update_wall_time();
+ }
+ write_raw_sequnlock_irqrestore(&xtime_lock, flags);
+}
+
+/*
* This function runs timers and the timer-tq in bottom half context.
*/
static void run_timer_softirq(struct softirq_action *h)
{
struct tvec_base *base = __get_cpu_var(tvec_bases);
+ update_times();
hrtimer_run_pending();
if (time_after_eq(jiffies, base->timer_jiffies))
@@ -1235,7 +1308,6 @@ void run_local_timers(void)
void do_timer(unsigned long ticks)
{
jiffies_64 += ticks;
- update_wall_time();
calc_global_load();
}
@@ -1550,6 +1622,7 @@ static int __cpuinit init_timers_cpu(int cpu)
}
spin_lock_init(&base->lock);
+ init_waitqueue_head(&base->wait_for_running_timer);
for (j = 0; j < TVN_SIZE; j++) {
INIT_LIST_HEAD(base->tv5.vec + j);
--
1.7.1.1