usb: xhci: fix Short Packet handling for isochronous
JC Kuo [Thu, 6 Dec 2012 12:16:19 +0000 (20:16 +0800)]
When Short Packet happens on a multiple-TRBs TD, xHCD needs to
calculate the exact amount of transferred data because upper layer
driver wants it. In order to achieve, xHCD has to:
1. set ISP bit for all TRBs belongs to a IN TD, and
2. set IOC bit for the last TRB of the IN TD.

Once HC detects a Short Transfer, HC will send Short Packet event for
the TRB which encountered Short Packet and also send Short Packet event
fot the last TRB which has IOC bit set.

With those two events, xHCD can calculate the exact amount of bytes which
xHC has completed for the TD. (4.10.1.1)

Bug 1158352

Change-Id: I38f04825ddc3e12f124e12a9abf05a36beb43886
Signed-off-by: JC Kuo <jckuo@nvidia.com>
Signed-off-by: Ajay Gupta <ajayg@nvidia.com>
Reviewed-on: http://git-master/r/192883
(cherry picked from commit 860031e312ecd6c5b384775ae31b7729d19b82a3)

Signed-off-by: JC Kuo <jckuo@nvidia.com>
Change-Id: I9d41f4c8f4d029b1032c74fbe080ac4a9bdaa8bc
Reviewed-on: http://git-master/r/194698
Reviewed-by: Automatic_Commit_Validation_User
GVS: Gerrit_Virtual_Submit
Reviewed-by: Bharath Yadav <byadav@nvidia.com>
Reviewed-by: Ajay Gupta <ajayg@nvidia.com>
Reviewed-by: Ashutosh Jha <ajha@nvidia.com>

drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h

index 69abe9c..ffd2386 100644 (file)
@@ -2113,6 +2113,12 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td,
                frame->actual_length = frame->length;
                td->urb->actual_length += frame->length;
        } else {
+               if (urb_priv->finishing_short_td &&
+                               (event_trb == td->last_trb)) {
+                       urb_priv->finishing_short_td = false;
+                       /* get event for last trb, can finish this short td */
+                       goto finish_td;
+               }
                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)) {
@@ -2127,8 +2133,17 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td,
                        frame->actual_length = len;
                        td->urb->actual_length += len;
                }
+               if ((trb_comp_code == COMP_SHORT_TX) &&
+                               (event_trb != td->last_trb)) {
+                       /* last trb has IOC, expect HC to send event for it */
+                       while (ep_ring->dequeue != td->last_trb)
+                               inc_deq(xhci, ep_ring);
+                       urb_priv->finishing_short_td = true;
+                       return 0;
+               }
        }
 
+finish_td:
        return finish_td(xhci, td, event_trb, event, ep, status, false);
 }
 
index d0e381f..37e82bf 100644 (file)
@@ -1154,6 +1154,7 @@ int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags)
 
        urb_priv->length = size;
        urb_priv->td_cnt = 0;
+       urb_priv->finishing_short_td = false;
        urb->hcpriv = urb_priv;
 
        if (usb_endpoint_xfer_control(&urb->ep->desc)) {
index e77d657..7eed733 100644 (file)
@@ -1329,6 +1329,7 @@ struct xhci_scratchpad {
 struct urb_priv {
        int     length;
        int     td_cnt;
+       bool    finishing_short_td;
        struct  xhci_td *td[0];
 };