USB: xhci: fix lock-inversion problem
authorAlan Stern <stern@rowland.harvard.edu>
Wed, 17 May 2017 15:32:03 +0000 (18:32 +0300)
committerBen Hutchings <ben@decadent.org.uk>
Fri, 15 Sep 2017 17:30:45 +0000 (18:30 +0100)
commit 63aea0dbab90a2461faaae357cbc8cfd6c8de9fe upstream.

With threaded interrupts, bottom-half handlers are called with
interrupts enabled.  Therefore they can't safely use spin_lock(); they
have to use spin_lock_irqsave().  Lockdep warns about a violation
occurring in xhci_irq():

=========================================================
[ INFO: possible irq lock inversion dependency detected ]
4.11.0-rc8-dbg+ #1 Not tainted
---------------------------------------------------------
swapper/7/0 just changed the state of lock:
 (&(&ehci->lock)->rlock){-.-...}, at: [<ffffffffa0130a69>]
ehci_hrtimer_func+0x29/0xc0 [ehci_hcd]
but this lock took another, HARDIRQ-unsafe lock in the past:
 (hcd_urb_list_lock){+.....}

and interrupts could create inverse lock ordering between them.

other info that might help us debug this:
 Possible interrupt unsafe locking scenario:

       CPU0                    CPU1
       ----                    ----
  lock(hcd_urb_list_lock);
                               local_irq_disable();
                               lock(&(&ehci->lock)->rlock);
                               lock(hcd_urb_list_lock);
  <Interrupt>
    lock(&(&ehci->lock)->rlock);
 *** DEADLOCK ***

no locks held by swapper/7/0.
the shortest dependencies between 2nd lock and 1st lock:
 -> (hcd_urb_list_lock){+.....} ops: 252 {
    HARDIRQ-ON-W at:
                      __lock_acquire+0x602/0x1280
                      lock_acquire+0xd5/0x1c0
                      _raw_spin_lock+0x2f/0x40
                      usb_hcd_unlink_urb_from_ep+0x1b/0x60 [usbcore]
                      xhci_giveback_urb_in_irq.isra.45+0x70/0x1b0 [xhci_hcd]
                      finish_td.constprop.60+0x1d8/0x2e0 [xhci_hcd]
                      xhci_irq+0xdd6/0x1fa0 [xhci_hcd]
                      usb_hcd_irq+0x26/0x40 [usbcore]
                      irq_forced_thread_fn+0x2f/0x70
                      irq_thread+0x149/0x1d0
                      kthread+0x113/0x150
                      ret_from_fork+0x2e/0x40

This patch fixes the problem.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Reported-and-tested-by: Bart Van Assche <bart.vanassche@sandisk.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[bwh: Backported to 3.2: adjust context]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
drivers/usb/host/xhci-ring.c

index 69e8289..74ed210 100644 (file)
@@ -2645,11 +2645,12 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
        union xhci_trb *trb;
        union xhci_trb *event_ring_deq;
        irqreturn_t ret = IRQ_NONE;
        union xhci_trb *trb;
        union xhci_trb *event_ring_deq;
        irqreturn_t ret = IRQ_NONE;
+       unsigned long flags;
        dma_addr_t deq;
        u64 temp_64;
        u32 status;
 
        dma_addr_t deq;
        u64 temp_64;
        u32 status;
 
-       spin_lock(&xhci->lock);
+       spin_lock_irqsave(&xhci->lock, flags);
        trb = xhci->event_ring->dequeue;
        /* Check if the xHC generated the interrupt, or the irq is shared */
        status = xhci_readl(xhci, &xhci->op_regs->status);
        trb = xhci->event_ring->dequeue;
        /* Check if the xHC generated the interrupt, or the irq is shared */
        status = xhci_readl(xhci, &xhci->op_regs->status);
@@ -2724,7 +2725,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
        ret = IRQ_HANDLED;
 
 out:
        ret = IRQ_HANDLED;
 
 out:
-       spin_unlock(&xhci->lock);
+       spin_unlock_irqrestore(&xhci->lock, flags);
 
        return ret;
 }
 
        return ret;
 }