lkml.org 
[lkml]   [2011]   [Aug]   [3]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[016/102] rtc: fix hrtimer deadlock
    3.0-stable review patch.  If anyone has any objections, please let us know.

    ------------------

    From: Thomas Gleixner <tglx@linutronix.de>

    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>

    ---
    drivers/rtc/interface.c | 56 +++++++++++++++++++++++++++++++-----------------
    1 file changed, 37 insertions(+), 19 deletions(-)

    --- a/drivers/rtc/interface.c
    +++ b/drivers/rtc/interface.c
    @@ -636,6 +636,29 @@ void rtc_irq_unregister(struct rtc_devic
    }
    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
    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 *

    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);



    \
     
     \ /
      Last update: 2011-08-04 01:49    [W:0.026 / U:61.172 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site