diff options
author | Thomas Gleixner <tglx@linutronix.de> | 2011-07-26 16:08:20 -0700 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2011-08-04 21:58:32 -0700 |
commit | e9c5291ba063473a5560d3387773e9a6997bad65 (patch) | |
tree | b9293ac3cf2da429a302e82fdc84fa08662f4329 /drivers | |
parent | cc4b6e7755a8c351d7ddf32b5f2e7f101cea5aa6 (diff) |
rtc: fix hrtimer deadlock
commit b830ac1d9a2262093bb0f3f6a2fd2a1c8278daf5 upstream.
Ben reported a lockup related to rtc. The lockup happens due to:
CPU0 CPU1
rtc_irq_set_state() __run_hrtimer()
spin_lock_irqsave(&rtc->irq_task_lock) rtc_handle_legacy_irq();
spin_lock(&rtc->irq_task_lock);
hrtimer_cancel()
while (callback_running);
So the running callback never finishes as it's blocked on
rtc->irq_task_lock.
Use hrtimer_try_to_cancel() instead and drop rtc->irq_task_lock while
waiting for the callback. Fix this for both rtc_irq_set_state() and
rtc_irq_set_freq().
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reported-by: Ben Greear <greearb@candelatech.com>
Cc: John Stultz <john.stultz@linaro.org>
Cc: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/rtc/interface.c | 56 |
1 files changed, 37 insertions, 19 deletions
diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c index b6bf57f25cc..0900dfea3ae 100644 --- a/drivers/rtc/interface.c +++ b/drivers/rtc/interface.c @@ -636,6 +636,29 @@ void rtc_irq_unregister(struct rtc_device *rtc, struct rtc_task *task) } EXPORT_SYMBOL_GPL(rtc_irq_unregister); +static int rtc_update_hrtimer(struct rtc_device *rtc, int enabled) +{ + /* + * We unconditionally cancel the timer here, because otherwise + * we could run into BUG_ON(timer->state != HRTIMER_STATE_CALLBACK); + * when we manage to start the timer before the callback + * returns HRTIMER_RESTART. + * + * We cannot use hrtimer_cancel() here as a running callback + * could be blocked on rtc->irq_task_lock and hrtimer_cancel() + * would spin forever. + */ + if (hrtimer_try_to_cancel(&rtc->pie_timer) < 0) + return -1; + + if (enabled) { + ktime_t period = ktime_set(0, NSEC_PER_SEC / rtc->irq_freq); + + hrtimer_start(&rtc->pie_timer, period, HRTIMER_MODE_REL); + } + return 0; +} + /** * rtc_irq_set_state - enable/disable 2^N Hz periodic IRQs * @rtc: the rtc device @@ -651,24 +674,21 @@ int rtc_irq_set_state(struct rtc_device *rtc, struct rtc_task *task, int enabled int err = 0; unsigned long flags; +retry: spin_lock_irqsave(&rtc->irq_task_lock, flags); if (rtc->irq_task != NULL && task == NULL) err = -EBUSY; if (rtc->irq_task != task) err = -EACCES; - if (err) - goto out; - - if (enabled) { - ktime_t period = ktime_set(0, NSEC_PER_SEC/rtc->irq_freq); - hrtimer_start(&rtc->pie_timer, period, HRTIMER_MODE_REL); - } else { - hrtimer_cancel(&rtc->pie_timer); + if (!err) { + if (rtc_update_hrtimer(rtc, enabled) < 0) { + spin_unlock_irqrestore(&rtc->irq_task_lock, flags); + cpu_relax(); + goto retry; + } + rtc->pie_enabled = enabled; } - rtc->pie_enabled = enabled; -out: spin_unlock_irqrestore(&rtc->irq_task_lock, flags); - return err; } EXPORT_SYMBOL_GPL(rtc_irq_set_state); @@ -690,20 +710,18 @@ int rtc_irq_set_freq(struct rtc_device *rtc, struct rtc_task *task, int freq) if (freq <= 0) return -EINVAL; - +retry: spin_lock_irqsave(&rtc->irq_task_lock, flags); if (rtc->irq_task != NULL && task == NULL) err = -EBUSY; if (rtc->irq_task != task) err = -EACCES; - if (err == 0) { + if (!err) { rtc->irq_freq = freq; - if (rtc->pie_enabled) { - ktime_t period; - hrtimer_cancel(&rtc->pie_timer); - period = ktime_set(0, NSEC_PER_SEC/rtc->irq_freq); - hrtimer_start(&rtc->pie_timer, period, - HRTIMER_MODE_REL); + if (rtc->pie_enabled && rtc_update_hrtimer(rtc, 1) < 0) { + spin_unlock_irqrestore(&rtc->irq_task_lock, flags); + cpu_relax(); + goto retry; } } spin_unlock_irqrestore(&rtc->irq_task_lock, flags); |