diff options
Diffstat (limited to 'kernel/timer.c')
| -rw-r--r-- | kernel/timer.c | 67 |
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); } |
