diff options
author | Sarah Sharp <sarah.a.sharp@linux.intel.com> | 2011-08-02 18:43:40 -0400 |
---|---|---|
committer | Sarah Sharp <sarah.a.sharp@linux.intel.com> | 2011-08-09 17:49:25 -0400 |
commit | 585df1d90cb07a02ca6c7a7d339e56e46d50dafb (patch) | |
tree | 62c5f003587d22dd28e4ba27332314765a285975 /drivers | |
parent | 522989a27c7badb608155b1f1dea3487ed431f74 (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')
-rw-r--r-- | drivers/usb/host/xhci-ring.c | 16 | ||||
-rw-r--r-- | drivers/usb/host/xhci.c | 7 |
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); |