aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/host
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2011-08-02 18:43:40 -0400
committerSarah Sharp <sarah.a.sharp@linux.intel.com>2011-08-09 17:49:25 -0400
commit585df1d90cb07a02ca6c7a7d339e56e46d50dafb (patch)
tree62c5f003587d22dd28e4ba27332314765a285975 /drivers/usb/host
parent522989a27c7badb608155b1f1dea3487ed431f74 (diff)
xhci: Remove TDs from TD lists when URBs are canceled.
When a driver tries to cancel an URB, and the host controller is dying, xhci_urb_dequeue will giveback the URB without removing the xhci_tds that comprise that URB from the td_list or the cancelled_td_list. This can cause a race condition between the driver calling URB dequeue and the stop endpoint command watchdog timer. If the timer fires on a dying host, and a driver attempts to resubmit while the watchdog timer has dropped the xhci->lock to giveback a cancelled URB, URBs may be given back by the xhci_urb_dequeue() function. At that point, the URB's priv pointer will be freed and set to NULL, but the TDs will remain on the td_list. This will cause an oops in xhci_giveback_urb_in_irq() when the watchdog timer attempts to loop through the endpoints' td_lists, giving back killed URBs. Make sure that xhci_urb_dequeue() removes TDs from the TD lists and canceled TD lists before it gives back the URB. This patch should be backported to kernels as old as 2.6.36. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Cc: Andiry Xu <andiry.xu@amd.com> Cc: stable@kernel.org
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/xhci-ring.c16
-rw-r--r--drivers/usb/host/xhci.c7
2 files changed, 15 insertions, 8 deletions
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index f72149b666b1..b2d654b7477e 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -741,7 +741,7 @@ remove_finished_td:
741 * so remove it from the endpoint ring's TD list. Keep it in 741 * so remove it from the endpoint ring's TD list. Keep it in
742 * the cancelled TD list for URB completion later. 742 * the cancelled TD list for URB completion later.
743 */ 743 */
744 list_del(&cur_td->td_list); 744 list_del_init(&cur_td->td_list);
745 } 745 }
746 last_unlinked_td = cur_td; 746 last_unlinked_td = cur_td;
747 xhci_stop_watchdog_timer_in_irq(xhci, ep); 747 xhci_stop_watchdog_timer_in_irq(xhci, ep);
@@ -769,7 +769,7 @@ remove_finished_td:
769 do { 769 do {
770 cur_td = list_entry(ep->cancelled_td_list.next, 770 cur_td = list_entry(ep->cancelled_td_list.next,
771 struct xhci_td, cancelled_td_list); 771 struct xhci_td, cancelled_td_list);
772 list_del(&cur_td->cancelled_td_list); 772 list_del_init(&cur_td->cancelled_td_list);
773 773
774 /* Clean up the cancelled URB */ 774 /* Clean up the cancelled URB */
775 /* Doesn't matter what we pass for status, since the core will 775 /* Doesn't matter what we pass for status, since the core will
@@ -877,9 +877,9 @@ void xhci_stop_endpoint_command_watchdog(unsigned long arg)
877 cur_td = list_first_entry(&ring->td_list, 877 cur_td = list_first_entry(&ring->td_list,
878 struct xhci_td, 878 struct xhci_td,
879 td_list); 879 td_list);
880 list_del(&cur_td->td_list); 880 list_del_init(&cur_td->td_list);
881 if (!list_empty(&cur_td->cancelled_td_list)) 881 if (!list_empty(&cur_td->cancelled_td_list))
882 list_del(&cur_td->cancelled_td_list); 882 list_del_init(&cur_td->cancelled_td_list);
883 xhci_giveback_urb_in_irq(xhci, cur_td, 883 xhci_giveback_urb_in_irq(xhci, cur_td,
884 -ESHUTDOWN, "killed"); 884 -ESHUTDOWN, "killed");
885 } 885 }
@@ -888,7 +888,7 @@ void xhci_stop_endpoint_command_watchdog(unsigned long arg)
888 &temp_ep->cancelled_td_list, 888 &temp_ep->cancelled_td_list,
889 struct xhci_td, 889 struct xhci_td,
890 cancelled_td_list); 890 cancelled_td_list);
891 list_del(&cur_td->cancelled_td_list); 891 list_del_init(&cur_td->cancelled_td_list);
892 xhci_giveback_urb_in_irq(xhci, cur_td, 892 xhci_giveback_urb_in_irq(xhci, cur_td,
893 -ESHUTDOWN, "killed"); 893 -ESHUTDOWN, "killed");
894 } 894 }
@@ -1580,10 +1580,10 @@ td_cleanup:
1580 else 1580 else
1581 *status = 0; 1581 *status = 0;
1582 } 1582 }
1583 list_del(&td->td_list); 1583 list_del_init(&td->td_list);
1584 /* Was this TD slated to be cancelled but completed anyway? */ 1584 /* Was this TD slated to be cancelled but completed anyway? */
1585 if (!list_empty(&td->cancelled_td_list)) 1585 if (!list_empty(&td->cancelled_td_list))
1586 list_del(&td->cancelled_td_list); 1586 list_del_init(&td->cancelled_td_list);
1587 1587
1588 urb_priv->td_cnt++; 1588 urb_priv->td_cnt++;
1589 /* Giveback the urb when all the tds are completed */ 1589 /* Giveback the urb when all the tds are completed */
@@ -3362,7 +3362,7 @@ cleanup:
3362 /* Clean up a partially enqueued isoc transfer. */ 3362 /* Clean up a partially enqueued isoc transfer. */
3363 3363
3364 for (i--; i >= 0; i--) 3364 for (i--; i >= 0; i--)
3365 list_del(&urb_priv->td[i]->td_list); 3365 list_del_init(&urb_priv->td[i]->td_list);
3366 3366
3367 /* Use the first TD as a temporary variable to turn the TDs we've queued 3367 /* Use the first TD as a temporary variable to turn the TDs we've queued
3368 * into No-ops with a software-owned cycle bit. That way the hardware 3368 * into No-ops with a software-owned cycle bit. That way the hardware
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 8e84acff1134..3a0f695138f4 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -1252,6 +1252,13 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
1252 if (temp == 0xffffffff || (xhci->xhc_state & XHCI_STATE_HALTED)) { 1252 if (temp == 0xffffffff || (xhci->xhc_state & XHCI_STATE_HALTED)) {
1253 xhci_dbg(xhci, "HW died, freeing TD.\n"); 1253 xhci_dbg(xhci, "HW died, freeing TD.\n");
1254 urb_priv = urb->hcpriv; 1254 urb_priv = urb->hcpriv;
1255 for (i = urb_priv->td_cnt; i < urb_priv->length; i++) {
1256 td = urb_priv->td[i];
1257 if (!list_empty(&td->td_list))
1258 list_del_init(&td->td_list);
1259 if (!list_empty(&td->cancelled_td_list))
1260 list_del_init(&td->cancelled_td_list);
1261 }
1255 1262
1256 usb_hcd_unlink_urb_from_ep(hcd, urb); 1263 usb_hcd_unlink_urb_from_ep(hcd, urb);
1257 spin_unlock_irqrestore(&xhci->lock, flags); 1264 spin_unlock_irqrestore(&xhci->lock, flags);