aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2011-05-05 22:08:09 -0400
committerSarah Sharp <sarah.a.sharp@linux.intel.com>2011-05-11 19:17:59 -0400
commit3abeca998a44205cfd837fa0bf1f7c24f8294acb (patch)
tree032760459104ecc80c604e6f0cf1b0377c268370 /drivers/usb
parenta9df304cf78d76108196da1ff1dad4d9a5737c2e (diff)
xhci: Fix bug in control transfer cancellation.
When the xHCI driver attempts to cancel a transfer, it issues a Stop Endpoint command and waits for the host controller to indicate which TRB it was in the middle of processing. The host will put an event TRB with completion code COMP_STOP on the event ring if it stops on a control transfer TRB (or other types of transfer TRBs). The ring handling code is supposed to set ep->stopped_trb to the TRB that the host stopped on when this happens. Unfortunately, there is a long-standing bug in the control transfer completion code. It doesn't actually check to see if COMP_STOP is set before attempting to process the transfer based on which part of the control TD completed. So when we get an event on the data phase of the control TRB with COMP_STOP set, it thinks it's a normal completion of the transfer and doesn't set ep->stopped_td or ep->stopped_trb. When the ring handling code goes on to process the completion of the Stop Endpoint command, it sees that ep->stopped_trb is not a part of the TD it's trying to cancel. It thinks the hardware has its enqueue pointer somewhere further up in the ring, and thinks it's safe to turn the control TRBs into no-op TRBs. Since the hardware was in the middle of the control TRBs to be cancelled, the proper software behavior is to issue a Set TR dequeue pointer command. It turns out that the NEC host controllers can handle active TRBs being set to no-op TRBs after a stop endpoint command, but other host controllers have issues with this out-of-spec software behavior. Fix this behavior. This patch should be backported to kernels as far back as 2.6.31, but it may be a bit challenging, since process_ctrl_td() was introduced in some refactoring done in 2.6.36, and some endian-safe patches added in 2.6.40 that touch the same lines. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com> Cc: stable@kernel.org
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/host/xhci-ring.c18
1 files changed, 9 insertions, 9 deletions
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index c35058b94de6..237a765f8d18 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -1641,6 +1641,9 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td,
1641 else 1641 else
1642 *status = 0; 1642 *status = 0;
1643 break; 1643 break;
1644 case COMP_STOP_INVAL:
1645 case COMP_STOP:
1646 return finish_td(xhci, td, event_trb, event, ep, status, false);
1644 default: 1647 default:
1645 if (!xhci_requires_manual_halt_cleanup(xhci, 1648 if (!xhci_requires_manual_halt_cleanup(xhci,
1646 ep_ctx, trb_comp_code)) 1649 ep_ctx, trb_comp_code))
@@ -1685,15 +1688,12 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td,
1685 } 1688 }
1686 } else { 1689 } else {
1687 /* Maybe the event was for the data stage? */ 1690 /* Maybe the event was for the data stage? */
1688 if (trb_comp_code != COMP_STOP_INVAL) { 1691 td->urb->actual_length =
1689 /* We didn't stop on a link TRB in the middle */ 1692 td->urb->transfer_buffer_length -
1690 td->urb->actual_length = 1693 TRB_LEN(le32_to_cpu(event->transfer_len));
1691 td->urb->transfer_buffer_length - 1694 xhci_dbg(xhci, "Waiting for status "
1692 TRB_LEN(le32_to_cpu(event->transfer_len)); 1695 "stage event\n");
1693 xhci_dbg(xhci, "Waiting for status " 1696 return 0;
1694 "stage event\n");
1695 return 0;
1696 }
1697 } 1697 }
1698 } 1698 }
1699 1699