aboutsummaryrefslogtreecommitdiffstats
path: root/usb/usb-xhci-isochronous-transfer-implementation.patch
diff options
Diffstat (limited to 'usb/usb-xhci-isochronous-transfer-implementation.patch')
-rw-r--r--usb/usb-xhci-isochronous-transfer-implementation.patch408
1 files changed, 408 insertions, 0 deletions
diff --git a/usb/usb-xhci-isochronous-transfer-implementation.patch b/usb/usb-xhci-isochronous-transfer-implementation.patch
new file mode 100644
index 00000000000000..dc05da22983e2c
--- /dev/null
+++ b/usb/usb-xhci-isochronous-transfer-implementation.patch
@@ -0,0 +1,408 @@
+From sarah.a.sharp@linux.intel.com Thu Jul 22 16:17:49 2010
+Date: Thu, 22 Jul 2010 15:23:39 -0700
+From: Andiry Xu <andiry.xu@amd.com>
+To: Greg KH <gregkh@suse.de>
+Cc: linux-usb@vger.kernel.org, Andiry Xu <andiry.xu@amd.com>
+Subject: USB: xHCI: Isochronous transfer implementation
+Message-ID: <20100722222339.GA21591@xanatos>
+Content-Disposition: inline
+
+From: Andiry Xu <andiry.xu@amd.com>
+
+This patch implements isochronous urb enqueue and interrupt handler part.
+
+When an isochronous urb is passed to xHCI driver, first check the transfer
+ring to guarantee there is enough room for the whole urb. Then update the
+start_frame and interval field of the urb. Always assume URB_ISO_ASAP
+is set, and never use urb->start_frame as input.
+
+The number of isoc TDs is equal to urb->number_of_packets. One isoc TD is
+consumed every Interval. Each isoc TD consists of an Isoch TRB chained to
+zero or more Normal TRBs.
+
+Call prepare_transfer for each TD to do initialization; then calculate the
+number of TRBs needed for each TD. If the data required by an isoc TD is
+physically contiguous (not crosses a page boundary), then only one isoc TRB
+is needed; otherwise one or more additional normal TRB shall be chained to
+the isoc TRB by the host.
+
+Set TRB_IOC to the last TRB of each isoc TD. Do not ring endpoint doorbell
+to start xHC procession until all the TDs are inserted to the endpoint
+transer ring.
+
+In irq handler, update urb status and actual_length, increase
+urb_priv->td_cnt. When all the TDs are completed(td_cnt is equal to
+urb_priv->length), giveback the urb to usbcore.
+
+Signed-off-by: Andiry Xu <andiry.xu@amd.com>
+Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+---
+ drivers/usb/host/xhci-ring.c | 319 +++++++++++++++++++++++++++++++++++++++++++
+ drivers/usb/host/xhci.h | 5
+ 2 files changed, 324 insertions(+)
+
+--- a/drivers/usb/host/xhci-ring.c
++++ b/drivers/usb/host/xhci-ring.c
+@@ -1472,6 +1472,104 @@ static int process_ctrl_td(struct xhci_h
+ }
+
+ /*
++ * Process isochronous tds, update urb packet status and actual_length.
++ */
++static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td,
++ union xhci_trb *event_trb, struct xhci_transfer_event *event,
++ struct xhci_virt_ep *ep, int *status)
++{
++ struct xhci_ring *ep_ring;
++ struct urb_priv *urb_priv;
++ int idx;
++ int len = 0;
++ int skip_td = 0;
++ union xhci_trb *cur_trb;
++ struct xhci_segment *cur_seg;
++ u32 trb_comp_code;
++
++ ep_ring = xhci_dma_to_transfer_ring(ep, event->buffer);
++ trb_comp_code = GET_COMP_CODE(event->transfer_len);
++ urb_priv = td->urb->hcpriv;
++ idx = urb_priv->td_cnt;
++
++ if (ep->skip) {
++ /* The transfer is partly done */
++ *status = -EXDEV;
++ td->urb->iso_frame_desc[idx].status = -EXDEV;
++ } else {
++ /* handle completion code */
++ switch (trb_comp_code) {
++ case COMP_SUCCESS:
++ td->urb->iso_frame_desc[idx].status = 0;
++ xhci_dbg(xhci, "Successful isoc transfer!\n");
++ break;
++ case COMP_SHORT_TX:
++ if (td->urb->transfer_flags & URB_SHORT_NOT_OK)
++ td->urb->iso_frame_desc[idx].status =
++ -EREMOTEIO;
++ else
++ td->urb->iso_frame_desc[idx].status = 0;
++ break;
++ case COMP_BW_OVER:
++ td->urb->iso_frame_desc[idx].status = -ECOMM;
++ skip_td = 1;
++ break;
++ case COMP_BUFF_OVER:
++ case COMP_BABBLE:
++ td->urb->iso_frame_desc[idx].status = -EOVERFLOW;
++ skip_td = 1;
++ break;
++ case COMP_STALL:
++ td->urb->iso_frame_desc[idx].status = -EPROTO;
++ skip_td = 1;
++ break;
++ case COMP_STOP:
++ case COMP_STOP_INVAL:
++ break;
++ default:
++ td->urb->iso_frame_desc[idx].status = -1;
++ break;
++ }
++ }
++
++ /* calc actual length */
++ if (ep->skip) {
++ td->urb->iso_frame_desc[idx].actual_length = 0;
++ return finish_td(xhci, td, event_trb, event, ep, status, true);
++ }
++
++ if (trb_comp_code == COMP_SUCCESS || skip_td == 1) {
++ td->urb->iso_frame_desc[idx].actual_length =
++ td->urb->iso_frame_desc[idx].length;
++ td->urb->actual_length +=
++ td->urb->iso_frame_desc[idx].length;
++ } else {
++ for (cur_trb = ep_ring->dequeue,
++ cur_seg = ep_ring->deq_seg; cur_trb != event_trb;
++ next_trb(xhci, ep_ring, &cur_seg, &cur_trb)) {
++ if ((cur_trb->generic.field[3] &
++ TRB_TYPE_BITMASK) != TRB_TYPE(TRB_TR_NOOP) &&
++ (cur_trb->generic.field[3] &
++ TRB_TYPE_BITMASK) != TRB_TYPE(TRB_LINK))
++ len +=
++ TRB_LEN(cur_trb->generic.field[2]);
++ }
++ len += TRB_LEN(cur_trb->generic.field[2]) -
++ TRB_LEN(event->transfer_len);
++
++ if (trb_comp_code != COMP_STOP_INVAL) {
++ td->urb->iso_frame_desc[idx].actual_length = len;
++ td->urb->actual_length += len;
++ }
++ }
++
++ if ((idx == urb_priv->length - 1) && *status == -EINPROGRESS)
++ *status = 0;
++
++ return finish_td(xhci, td, event_trb, event, ep, status, false);
++}
++
++/*
+ * Process bulk and interrupt tds, update urb status and actual_length.
+ */
+ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td,
+@@ -1768,6 +1866,9 @@ static int handle_tx_event(struct xhci_h
+ if (usb_endpoint_xfer_control(&td->urb->ep->desc))
+ ret = process_ctrl_td(xhci, td, event_trb, event, ep,
+ &status);
++ else if (usb_endpoint_xfer_isoc(&td->urb->ep->desc))
++ ret = process_isoc_td(xhci, td, event_trb, event, ep,
++ &status);
+ else
+ ret = process_bulk_intr_td(xhci, td, event_trb, event,
+ ep, &status);
+@@ -2553,6 +2654,224 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *
+ return 0;
+ }
+
++static int count_isoc_trbs_needed(struct xhci_hcd *xhci,
++ struct urb *urb, int i)
++{
++ int num_trbs = 0;
++ u64 addr, td_len, running_total;
++
++ addr = (u64) (urb->transfer_dma + urb->iso_frame_desc[i].offset);
++ td_len = urb->iso_frame_desc[i].length;
++
++ running_total = TRB_MAX_BUFF_SIZE -
++ (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1));
++ if (running_total != 0)
++ num_trbs++;
++
++ while (running_total < td_len) {
++ num_trbs++;
++ running_total += TRB_MAX_BUFF_SIZE;
++ }
++
++ return num_trbs;
++}
++
++/* This is for isoc transfer */
++static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
++ struct urb *urb, int slot_id, unsigned int ep_index)
++{
++ struct xhci_ring *ep_ring;
++ struct urb_priv *urb_priv;
++ struct xhci_td *td;
++ int num_tds, trbs_per_td;
++ struct xhci_generic_trb *start_trb;
++ bool first_trb;
++ int start_cycle;
++ u32 field, length_field;
++ int running_total, trb_buff_len, td_len, td_remain_len, ret;
++ u64 start_addr, addr;
++ int i, j;
++
++ ep_ring = xhci->devs[slot_id]->eps[ep_index].ring;
++
++ num_tds = urb->number_of_packets;
++ if (num_tds < 1) {
++ xhci_dbg(xhci, "Isoc URB with zero packets?\n");
++ return -EINVAL;
++ }
++
++ if (!in_interrupt())
++ dev_dbg(&urb->dev->dev, "ep %#x - urb len = %#x (%d),"
++ " addr = %#llx, num_tds = %d\n",
++ urb->ep->desc.bEndpointAddress,
++ urb->transfer_buffer_length,
++ urb->transfer_buffer_length,
++ (unsigned long long)urb->transfer_dma,
++ num_tds);
++
++ start_addr = (u64) urb->transfer_dma;
++ start_trb = &ep_ring->enqueue->generic;
++ start_cycle = ep_ring->cycle_state;
++
++ /* Queue the first TRB, even if it's zero-length */
++ for (i = 0; i < num_tds; i++) {
++ first_trb = true;
++
++ running_total = 0;
++ addr = start_addr + urb->iso_frame_desc[i].offset;
++ td_len = urb->iso_frame_desc[i].length;
++ td_remain_len = td_len;
++
++ trbs_per_td = count_isoc_trbs_needed(xhci, urb, i);
++
++ ret = prepare_transfer(xhci, xhci->devs[slot_id], ep_index,
++ urb->stream_id, trbs_per_td, urb, i, mem_flags);
++ if (ret < 0)
++ return ret;
++
++ urb_priv = urb->hcpriv;
++ td = urb_priv->td[i];
++
++ for (j = 0; j < trbs_per_td; j++) {
++ u32 remainder = 0;
++ field = 0;
++
++ if (first_trb) {
++ /* Queue the isoc TRB */
++ field |= TRB_TYPE(TRB_ISOC);
++ /* Assume URB_ISO_ASAP is set */
++ field |= TRB_SIA;
++ if (i > 0)
++ field |= ep_ring->cycle_state;
++ first_trb = false;
++ } else {
++ /* Queue other normal TRBs */
++ field |= TRB_TYPE(TRB_NORMAL);
++ field |= ep_ring->cycle_state;
++ }
++
++ /* Chain all the TRBs together; clear the chain bit in
++ * the last TRB to indicate it's the last TRB in the
++ * chain.
++ */
++ if (j < trbs_per_td - 1) {
++ field |= TRB_CHAIN;
++ } else {
++ td->last_trb = ep_ring->enqueue;
++ field |= TRB_IOC;
++ }
++
++ /* Calculate TRB length */
++ trb_buff_len = TRB_MAX_BUFF_SIZE -
++ (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1));
++ if (trb_buff_len > td_remain_len)
++ trb_buff_len = td_remain_len;
++
++ remainder = xhci_td_remainder(td_len - running_total);
++ length_field = TRB_LEN(trb_buff_len) |
++ remainder |
++ TRB_INTR_TARGET(0);
++ queue_trb(xhci, ep_ring, false, false,
++ lower_32_bits(addr),
++ upper_32_bits(addr),
++ length_field,
++ /* We always want to know if the TRB was short,
++ * or we won't get an event when it completes.
++ * (Unless we use event data TRBs, which are a
++ * waste of space and HC resources.)
++ */
++ field | TRB_ISP);
++ running_total += trb_buff_len;
++
++ addr += trb_buff_len;
++ td_remain_len -= trb_buff_len;
++ }
++
++ /* Check TD length */
++ if (running_total != td_len) {
++ xhci_err(xhci, "ISOC TD length unmatch\n");
++ return -EINVAL;
++ }
++ }
++
++ wmb();
++ start_trb->field[3] |= start_cycle;
++
++ ring_ep_doorbell(xhci, slot_id, ep_index, urb->stream_id);
++ return 0;
++}
++
++/*
++ * Check transfer ring to guarantee there is enough room for the urb.
++ * Update ISO URB start_frame and interval.
++ * Update interval as xhci_queue_intr_tx does. Just use xhci frame_index to
++ * update the urb->start_frame by now.
++ * Always assume URB_ISO_ASAP set, and NEVER use urb->start_frame as input.
++ */
++int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags,
++ struct urb *urb, int slot_id, unsigned int ep_index)
++{
++ struct xhci_virt_device *xdev;
++ struct xhci_ring *ep_ring;
++ struct xhci_ep_ctx *ep_ctx;
++ int start_frame;
++ int xhci_interval;
++ int ep_interval;
++ int num_tds, num_trbs, i;
++ int ret;
++
++ xdev = xhci->devs[slot_id];
++ ep_ring = xdev->eps[ep_index].ring;
++ ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index);
++
++ num_trbs = 0;
++ num_tds = urb->number_of_packets;
++ for (i = 0; i < num_tds; i++)
++ num_trbs += count_isoc_trbs_needed(xhci, urb, i);
++
++ /* Check the ring to guarantee there is enough room for the whole urb.
++ * Do not insert any td of the urb to the ring if the check failed.
++ */
++ ret = prepare_ring(xhci, ep_ring, ep_ctx->ep_info & EP_STATE_MASK,
++ num_trbs, mem_flags);
++ if (ret)
++ return ret;
++
++ start_frame = xhci_readl(xhci, &xhci->run_regs->microframe_index);
++ start_frame &= 0x3fff;
++
++ urb->start_frame = start_frame;
++ if (urb->dev->speed == USB_SPEED_LOW ||
++ urb->dev->speed == USB_SPEED_FULL)
++ urb->start_frame >>= 3;
++
++ xhci_interval = EP_INTERVAL_TO_UFRAMES(ep_ctx->ep_info);
++ ep_interval = urb->interval;
++ /* Convert to microframes */
++ if (urb->dev->speed == USB_SPEED_LOW ||
++ urb->dev->speed == USB_SPEED_FULL)
++ ep_interval *= 8;
++ /* FIXME change this to a warning and a suggestion to use the new API
++ * to set the polling interval (once the API is added).
++ */
++ if (xhci_interval != ep_interval) {
++ if (!printk_ratelimit())
++ dev_dbg(&urb->dev->dev, "Driver uses different interval"
++ " (%d microframe%s) than xHCI "
++ "(%d microframe%s)\n",
++ ep_interval,
++ ep_interval == 1 ? "" : "s",
++ xhci_interval,
++ xhci_interval == 1 ? "" : "s");
++ urb->interval = xhci_interval;
++ /* Convert back to frames for LS/FS devices */
++ if (urb->dev->speed == USB_SPEED_LOW ||
++ urb->dev->speed == USB_SPEED_FULL)
++ urb->interval /= 8;
++ }
++ return xhci_queue_isoc_tx(xhci, GFP_ATOMIC, urb, slot_id, ep_index);
++}
++
+ /**** Command Ring Operations ****/
+
+ /* Generic function for queueing a command TRB on the command ring.
+--- a/drivers/usb/host/xhci.h
++++ b/drivers/usb/host/xhci.h
+@@ -919,6 +919,9 @@ struct xhci_event_cmd {
+ /* Control transfer TRB specific fields */
+ #define TRB_DIR_IN (1<<16)
+
++/* Isochronous TRB specific fields */
++#define TRB_SIA (1<<31)
++
+ struct xhci_generic_trb {
+ u32 field[4];
+ };
+@@ -1416,6 +1419,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *
+ int slot_id, unsigned int ep_index);
+ int xhci_queue_intr_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb,
+ int slot_id, unsigned int ep_index);
++int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags,
++ struct urb *urb, int slot_id, unsigned int ep_index);
+ int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr,
+ u32 slot_id, bool command_must_succeed);
+ int xhci_queue_evaluate_context(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr,