TD fragments section 4.11.7.1 in xhci specs have additional requirements
on how trbs in TDs must be organized.

TD fragments shall not span transfer ring segments and TD fragments must
be packet aligned. Normally we don't care about TD fragments, on TD is one
big fragment, but if a TD spans ring segments it will be treated as two
fragments, and we need to comply with the alignment requirements.

For us this means that the payload data must be packet aligned in the
last trb before a link trb.
In most mass storage bulk tranfers we are lucky as the block size aligns
nicely with packet size, and there are no issues.
However, usb network adapters using scatterlists can hit this alignment
issue, and usbtest in kernel triggers this in minutes.

This patch is a partial solution, it solves the easy case when the last
trb before the link trb contains a packet boundary.
If that is the case then just split the trb at the boundary.
If not, then just print a debug message and continue as we have always
done, hoping for the best

Signed-off-by: Mathias Nyman <mathias.ny...@linux.intel.com>
---
 drivers/usb/host/xhci-ring.c | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index d86da81..c7c9521 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -3098,6 +3098,27 @@ static u32 xhci_td_remainder(struct xhci_hcd *xhci, int 
transferred,
        return (total_packet_count - ((transferred + trb_buff_len) / maxp));
 }
 
+static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len,
+                        u32 *trb_buff_len)
+{
+       unsigned int unalign;
+       unsigned int max_pkt;
+
+       max_pkt = usb_endpoint_maxp(&urb->ep->desc); /*FIXME MATTU GET_MAX..? */
+       unalign = (enqd_len + *trb_buff_len) % max_pkt;
+
+       /* we got lucky, last normal TRB data on segment is packet aligned */
+       if (unalign == 0)
+               return 0;
+
+       /* is the last nornal TRB alignable by splitting it */
+       if (*trb_buff_len > unalign) {
+               *trb_buff_len -= unalign;
+               return 0;
+       }
+       return 1;
+}
+
 /* This is very similar to what ehci-q.c qtd_fill() does */
 int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
                struct urb *urb, int slot_id, unsigned int ep_index)
@@ -3180,6 +3201,12 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t 
mem_flags,
                 */
                if (enqd_len + trb_buff_len < full_len) {
                        field |= TRB_CHAIN;
+                       if (last_trb(xhci, ring, ring->enq_seg,
+                                    ring->enqueue + 1)) {
+                               if (xhci_align_td(xhci, urb, enqd_len,
+                                                &trb_buff_len))
+                                       xhci_dbg(xhci, "TRB align fail\n");
+                       }
                } else {
                        field |= TRB_IOC;
                        more_trbs_coming = false;
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to