summaryrefslogtreecommitdiff
path: root/kernel/timer.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/timer.c')
-rw-r--r--kernel/timer.c67
1 files changed, 42 insertions, 25 deletions
diff --git a/kernel/timer.c b/kernel/timer.c
index 6e7e23cb95ba..d3983cbfa8d7 100644
--- a/kernel/timer.c
+++ b/kernel/timer.c
@@ -53,11 +53,11 @@ typedef struct tvec_root_s {
struct list_head vec[TVR_SIZE];
} tvec_root_t;
-
struct tvec_t_base_s {
spinlock_t lock;
unsigned long timer_jiffies;
struct timer_list *running_timer;
+ struct list_head *run_timer_list_running;
tvec_root_t tv1;
tvec_t tv2;
tvec_t tv3;
@@ -67,6 +67,14 @@ struct tvec_t_base_s {
typedef struct tvec_t_base_s tvec_base_t;
+static inline void set_running_timer(tvec_base_t *base,
+ struct timer_list *timer)
+{
+#ifdef CONFIG_SMP
+ base->running_timer = timer;
+#endif
+}
+
/* Fake initialization */
static DEFINE_PER_CPU(tvec_base_t, tvec_bases) = { SPIN_LOCK_UNLOCKED };
@@ -94,13 +102,22 @@ static inline void check_timer(struct timer_list *timer)
check_timer_failed(timer);
}
-static inline void internal_add_timer(tvec_base_t *base, struct timer_list *timer)
+/*
+ * If a timer handler re-adds the timer with expires == jiffies, the timer
+ * running code can lock up. So here we detect that situation and park the
+ * timer onto base->run_timer_list_running. It will be added to the main timer
+ * structures later, by __run_timers().
+ */
+
+static void internal_add_timer(tvec_base_t *base, struct timer_list *timer)
{
unsigned long expires = timer->expires;
unsigned long idx = expires - base->timer_jiffies;
struct list_head *vec;
- if (idx < TVR_SIZE) {
+ if (base->run_timer_list_running) {
+ vec = base->run_timer_list_running;
+ } else if (idx < TVR_SIZE) {
int i = expires & TVR_MASK;
vec = base->tv1.vec + i;
} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
@@ -354,7 +371,7 @@ del_again:
static int cascade(tvec_base_t *base, tvec_t *tv)
{
/* cascade all the timers from tv up one level */
- struct list_head *head, *curr, *next;
+ struct list_head *head, *curr;
head = tv->vec + tv->index;
curr = head->next;
@@ -366,11 +383,9 @@ static int cascade(tvec_base_t *base, tvec_t *tv)
struct timer_list *tmp;
tmp = list_entry(curr, struct timer_list, entry);
- if (tmp->base != base)
- BUG();
- next = curr->next;
+ BUG_ON(tmp->base != base);
+ curr = curr->next;
internal_add_timer(base, tmp);
- curr = next;
}
INIT_LIST_HEAD(head);
@@ -386,9 +401,12 @@ static int cascade(tvec_base_t *base, tvec_t *tv)
*/
static inline void __run_timers(tvec_base_t *base)
{
+ struct timer_list *timer;
+
spin_lock_irq(&base->lock);
- while ((long)(jiffies - base->timer_jiffies) >= 0) {
- struct list_head *head, *curr;
+ while (time_after_eq(jiffies, base->timer_jiffies)) {
+ LIST_HEAD(deferred_timers);
+ struct list_head *head;
/*
* Cascade timers:
@@ -398,37 +416,36 @@ static inline void __run_timers(tvec_base_t *base)
(cascade(base, &base->tv3) == 1) &&
cascade(base, &base->tv4) == 1)
cascade(base, &base->tv5);
+ base->run_timer_list_running = &deferred_timers;
repeat:
head = base->tv1.vec + base->tv1.index;
- curr = head->next;
- if (curr != head) {
+ if (!list_empty(head)) {
void (*fn)(unsigned long);
unsigned long data;
- struct timer_list *timer;
- timer = list_entry(curr, struct timer_list, entry);
+ timer = list_entry(head->next,struct timer_list,entry);
fn = timer->function;
data = timer->data;
list_del(&timer->entry);
timer->base = NULL;
-#if CONFIG_SMP
- base->running_timer = timer;
-#endif
+ set_running_timer(base, timer);
spin_unlock_irq(&base->lock);
- if (!fn)
- printk("Bad: timer %p has NULL fn. (data: %08lx)\n", timer, data);
- else
- fn(data);
+ fn(data);
spin_lock_irq(&base->lock);
goto repeat;
}
+ base->run_timer_list_running = NULL;
++base->timer_jiffies;
base->tv1.index = (base->tv1.index + 1) & TVR_MASK;
+ while (!list_empty(&deferred_timers)) {
+ timer = list_entry(deferred_timers.prev,
+ struct timer_list, entry);
+ list_del(&timer->entry);
+ internal_add_timer(base, timer);
+ }
}
-#if CONFIG_SMP
- base->running_timer = NULL;
-#endif
+ set_running_timer(base, NULL);
spin_unlock_irq(&base->lock);
}
@@ -775,7 +792,7 @@ static void run_timer_softirq(struct softirq_action *h)
{
tvec_base_t *base = &per_cpu(tvec_bases, smp_processor_id());
- if ((long)(jiffies - base->timer_jiffies) >= 0)
+ if (time_after_eq(jiffies, base->timer_jiffies))
__run_timers(base);
}