summaryrefslogtreecommitdiff
path: root/drivers/usb/host/xhci-hcd.c
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2009-04-29 19:02:31 -0700
committerGreg Kroah-Hartman <gregkh@suse.de>2009-06-15 21:44:49 -0700
commitae636747146ea97efa18e04576acd3416e2514f5 (patch)
tree22e392df7126974c0ac4dc2fe516dc9e16a49873 /drivers/usb/host/xhci-hcd.c
parent8a96c052283e68fe91a6c657c175b39bfed80bed (diff)
USB: xhci: URB cancellation support.
Add URB cancellation support to the xHCI host controller driver. This currently supports cancellation for endpoints that do not have streams enabled. An URB is represented by a number of Transaction Request Buffers (TRBs), that are chained together to make one (or more) Transaction Descriptors (TDs) on an endpoint ring. The ring is comprised of contiguous segments, linked together with Link TRBs (which may or may not be chained into a TD). To cancel an URB, we must stop the endpoint ring, make the hardware skip over the TDs in the URB (either by turning them into No-op TDs, or by moving the hardware's ring dequeue pointer past the last TRB in the last TD), and then restart the ring. There are times when we must drop the xHCI lock during this process, like when we need to complete cancelled URBs. We must ensure that additional URBs can be marked as cancelled, and that new URBs can be enqueued (since the URB completion handlers can do either). The new endpoint ring variables cancels_pending and state (which can only be modified while holding the xHCI lock) ensure that future cancellation and enqueueing do not interrupt any pending cancellation code. To facilitate cancellation, we must keep track of the starting ring segment, first TRB, and last TRB for each URB. We also need to keep track of the list of TDs that have been marked as cancelled, separate from the list of TDs that are queued for this endpoint. The new variables and cancellation list are stored in the xhci_td structure. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/xhci-hcd.c')
-rw-r--r--drivers/usb/host/xhci-hcd.c64
1 files changed, 61 insertions, 3 deletions
diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c
index e5fbdcdbf676..36e440ce88e5 100644
--- a/drivers/usb/host/xhci-hcd.c
+++ b/drivers/usb/host/xhci-hcd.c
@@ -613,12 +613,70 @@ exit:
return ret;
}
-/* Remove from hardware lists
- * completions normally happen asynchronously
+/*
+ * Remove the URB's TD from the endpoint ring. This may cause the HC to stop
+ * USB transfers, potentially stopping in the middle of a TRB buffer. The HC
+ * should pick up where it left off in the TD, unless a Set Transfer Ring
+ * Dequeue Pointer is issued.
+ *
+ * The TRBs that make up the buffers for the canceled URB will be "removed" from
+ * the ring. Since the ring is a contiguous structure, they can't be physically
+ * removed. Instead, there are two options:
+ *
+ * 1) If the HC is in the middle of processing the URB to be canceled, we
+ * simply move the ring's dequeue pointer past those TRBs using the Set
+ * Transfer Ring Dequeue Pointer command. This will be the common case,
+ * when drivers timeout on the last submitted URB and attempt to cancel.
+ *
+ * 2) If the HC is in the middle of a different TD, we turn the TRBs into a
+ * series of 1-TRB transfer no-op TDs. (No-ops shouldn't be chained.) The
+ * HC will need to invalidate the any TRBs it has cached after the stop
+ * endpoint command, as noted in the xHCI 0.95 errata.
+ *
+ * 3) The TD may have completed by the time the Stop Endpoint Command
+ * completes, so software needs to handle that case too.
+ *
+ * This function should protect against the TD enqueueing code ringing the
+ * doorbell while this code is waiting for a Stop Endpoint command to complete.
+ * It also needs to account for multiple cancellations on happening at the same
+ * time for the same endpoint.
+ *
+ * Note that this function can be called in any context, or so says
+ * usb_hcd_unlink_urb()
*/
int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
{
- return -ENOSYS;
+ unsigned long flags;
+ int ret;
+ struct xhci_hcd *xhci;
+ struct xhci_td *td;
+ unsigned int ep_index;
+ struct xhci_ring *ep_ring;
+
+ xhci = hcd_to_xhci(hcd);
+ spin_lock_irqsave(&xhci->lock, flags);
+ /* Make sure the URB hasn't completed or been unlinked already */
+ ret = usb_hcd_check_unlink_urb(hcd, urb, status);
+ if (ret || !urb->hcpriv)
+ goto done;
+
+ xhci_dbg(xhci, "Cancel URB 0x%x\n", (unsigned int) urb);
+ ep_index = xhci_get_endpoint_index(&urb->ep->desc);
+ ep_ring = xhci->devs[urb->dev->slot_id]->ep_rings[ep_index];
+ td = (struct xhci_td *) urb->hcpriv;
+
+ ep_ring->cancels_pending++;
+ list_add_tail(&td->cancelled_td_list, &ep_ring->cancelled_td_list);
+ /* Queue a stop endpoint command, but only if this is
+ * the first cancellation to be handled.
+ */
+ if (ep_ring->cancels_pending == 1) {
+ queue_stop_endpoint(xhci, urb->dev->slot_id, ep_index);
+ ring_cmd_db(xhci);
+ }
+done:
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ return ret;
}
/* Drop an endpoint from a new bandwidth configuration for this device.