Commit b830ac1d authored by Thomas Gleixner's avatar Thomas Gleixner Committed by Linus Torvalds

rtc: fix hrtimer deadlock

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: default avatarThomas Gleixner <tglx@linutronix.de>
Reported-by: default avatarBen Greear <greearb@candelatech.com>
Cc: John Stultz <john.stultz@linaro.org>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: <stable@kernel.org>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 431e2bcc
...@@ -636,6 +636,29 @@ void rtc_irq_unregister(struct rtc_device *rtc, struct rtc_task *task) ...@@ -636,6 +636,29 @@ void rtc_irq_unregister(struct rtc_device *rtc, struct rtc_task *task)
} }
EXPORT_SYMBOL_GPL(rtc_irq_unregister); 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_irq_set_state - enable/disable 2^N Hz periodic IRQs
* @rtc: the rtc device * @rtc: the rtc device
...@@ -651,24 +674,21 @@ int rtc_irq_set_state(struct rtc_device *rtc, struct rtc_task *task, int enabled ...@@ -651,24 +674,21 @@ int rtc_irq_set_state(struct rtc_device *rtc, struct rtc_task *task, int enabled
int err = 0; int err = 0;
unsigned long flags; unsigned long flags;
retry:
spin_lock_irqsave(&rtc->irq_task_lock, flags); spin_lock_irqsave(&rtc->irq_task_lock, flags);
if (rtc->irq_task != NULL && task == NULL) if (rtc->irq_task != NULL && task == NULL)
err = -EBUSY; err = -EBUSY;
if (rtc->irq_task != task) if (rtc->irq_task != task)
err = -EACCES; err = -EACCES;
if (err) if (!err) {
goto out; if (rtc_update_hrtimer(rtc, enabled) < 0) {
spin_unlock_irqrestore(&rtc->irq_task_lock, flags);
if (enabled) { cpu_relax();
ktime_t period = ktime_set(0, NSEC_PER_SEC/rtc->irq_freq); goto retry;
hrtimer_start(&rtc->pie_timer, period, HRTIMER_MODE_REL);
} else {
hrtimer_cancel(&rtc->pie_timer);
} }
rtc->pie_enabled = enabled; rtc->pie_enabled = enabled;
out: }
spin_unlock_irqrestore(&rtc->irq_task_lock, flags); spin_unlock_irqrestore(&rtc->irq_task_lock, flags);
return err; return err;
} }
EXPORT_SYMBOL_GPL(rtc_irq_set_state); 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) ...@@ -690,20 +710,18 @@ int rtc_irq_set_freq(struct rtc_device *rtc, struct rtc_task *task, int freq)
if (freq <= 0 || freq > 5000) if (freq <= 0 || freq > 5000)
return -EINVAL; return -EINVAL;
retry:
spin_lock_irqsave(&rtc->irq_task_lock, flags); spin_lock_irqsave(&rtc->irq_task_lock, flags);
if (rtc->irq_task != NULL && task == NULL) if (rtc->irq_task != NULL && task == NULL)
err = -EBUSY; err = -EBUSY;
if (rtc->irq_task != task) if (rtc->irq_task != task)
err = -EACCES; err = -EACCES;
if (err == 0) { if (!err) {
rtc->irq_freq = freq; rtc->irq_freq = freq;
if (rtc->pie_enabled) { if (rtc->pie_enabled && rtc_update_hrtimer(rtc, 1) < 0) {
ktime_t period; spin_unlock_irqrestore(&rtc->irq_task_lock, flags);
hrtimer_cancel(&rtc->pie_timer); cpu_relax();
period = ktime_set(0, NSEC_PER_SEC/rtc->irq_freq); goto retry;
hrtimer_start(&rtc->pie_timer, period,
HRTIMER_MODE_REL);
} }
} }
spin_unlock_irqrestore(&rtc->irq_task_lock, flags); spin_unlock_irqrestore(&rtc->irq_task_lock, flags);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment