| From d2e6d62c9cbbc2da4211f672dbeea08960e29a80 Mon Sep 17 00:00:00 2001 |
| From: Thomas Gleixner <tglx@linutronix.de> |
| Date: Thu, 2 Oct 2014 17:32:16 +0200 |
| Subject: usb: musb: cppi41: restart hrtimer only if not yet done |
| |
| From: Thomas Gleixner <tglx@linutronix.de> |
| |
| commit d2e6d62c9cbbc2da4211f672dbeea08960e29a80 upstream. |
| |
| commit c58d80f52 ("usb: musb: Ensure that cppi41 timer gets armed on |
| premature DMA TX irq") fixed hrtimer scheduling bug. There is one left |
| which does not trigger that often. |
| The following scenario is still possible: |
| |
| lock(&x->lock); |
| hrtimer_start(&x->t); |
| unlock(&x->lock); |
| |
| expires: |
| t->function(); |
| lock(&x->lock); |
| lock(&x->lock); if (!hrtimer_queued(&x->t)) |
| hrtimer_start(&x->t); |
| unlock(&x->lock); |
| |
| if (!list_empty(x->early_tx_list)) |
| ret = HRTIMER_RESTART; |
| -> hrtimer_forward_now(...) |
| } else |
| ret = HRTIMER_NORESTART; |
| |
| unlock(&x->lock); |
| |
| and the timer callback returns HRTIMER_RESTART for an armed timer. This |
| is wrong and we run into the BUG_ON() in __run_hrtimer(). |
| This can happens on SMP or PREEMPT-RT. |
| The patch fixes the problem by only starting the timer if the timer is |
| not yet queued. |
| |
| Reported-by: Torben Hohn <torbenh@linutronix.de> |
| Signed-off-by: Thomas Gleixner <tglx@linutronix.de> |
| [bigeasy: collected information and created a patch + description based |
| on it] |
| Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| Signed-off-by: Felipe Balbi <balbi@ti.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/usb/musb/musb_cppi41.c | 3 ++- |
| 1 file changed, 2 insertions(+), 1 deletion(-) |
| |
| --- a/drivers/usb/musb/musb_cppi41.c |
| +++ b/drivers/usb/musb/musb_cppi41.c |
| @@ -209,7 +209,8 @@ static enum hrtimer_restart cppi41_reche |
| } |
| } |
| |
| - if (!list_empty(&controller->early_tx_list)) { |
| + if (!list_empty(&controller->early_tx_list) && |
| + !hrtimer_is_queued(&controller->early_tx)) { |
| ret = HRTIMER_RESTART; |
| hrtimer_forward_now(&controller->early_tx, |
| ktime_set(0, 50 * NSEC_PER_USEC)); |