xhci: fix bounce buffer usage for non-sg list case
authorMathias Nyman <mathias.nyman@linux.intel.com>
Wed, 3 Feb 2021 11:37:02 +0000 (13:37 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 3 Feb 2021 12:14:45 +0000 (13:14 +0100)
xhci driver may in some special cases need to copy small amounts
of payload data to a bounce buffer in order to meet the boundary
and alignment restrictions set by the xHCI specification.

In the majority of these cases the data is in a sg list, and
driver incorrectly assumed data is always in urb->sg when using
the bounce buffer.

If data instead is contiguous, and in urb->transfer_buffer, we may still
need to bounce buffer a small part if data starts very close (less than
packet size) to a 64k boundary.

Check if sg list is used before copying data to/from it.

Fixes: f9c589e142d0 ("xhci: TD-fragment, align the unsplittable case with a bounce buffer")
Cc: stable@vger.kernel.org
Reported-by: Andreas Hartmann <andihartmann@01019freenet.de>
Tested-by: Andreas Hartmann <andihartmann@01019freenet.de>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20210203113702.436762-2-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci-ring.c

index cf0c93a..89c3be9 100644 (file)
@@ -699,11 +699,16 @@ static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci,
        dma_unmap_single(dev, seg->bounce_dma, ring->bounce_buf_len,
                         DMA_FROM_DEVICE);
        /* for in tranfers we need to copy the data from bounce to sg */
-       len = sg_pcopy_from_buffer(urb->sg, urb->num_sgs, seg->bounce_buf,
-                            seg->bounce_len, seg->bounce_offs);
-       if (len != seg->bounce_len)
-               xhci_warn(xhci, "WARN Wrong bounce buffer read length: %zu != %d\n",
-                               len, seg->bounce_len);
+       if (urb->num_sgs) {
+               len = sg_pcopy_from_buffer(urb->sg, urb->num_sgs, seg->bounce_buf,
+                                          seg->bounce_len, seg->bounce_offs);
+               if (len != seg->bounce_len)
+                       xhci_warn(xhci, "WARN Wrong bounce buffer read length: %zu != %d\n",
+                                 len, seg->bounce_len);
+       } else {
+               memcpy(urb->transfer_buffer + seg->bounce_offs, seg->bounce_buf,
+                      seg->bounce_len);
+       }
        seg->bounce_len = 0;
        seg->bounce_offs = 0;
 }
@@ -3277,12 +3282,16 @@ static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len,
 
        /* create a max max_pkt sized bounce buffer pointed to by last trb */
        if (usb_urb_dir_out(urb)) {
-               len = sg_pcopy_to_buffer(urb->sg, urb->num_sgs,
-                                  seg->bounce_buf, new_buff_len, enqd_len);
-               if (len != new_buff_len)
-                       xhci_warn(xhci,
-                               "WARN Wrong bounce buffer write length: %zu != %d\n",
-                               len, new_buff_len);
+               if (urb->num_sgs) {
+                       len = sg_pcopy_to_buffer(urb->sg, urb->num_sgs,
+                                                seg->bounce_buf, new_buff_len, enqd_len);
+                       if (len != new_buff_len)
+                               xhci_warn(xhci, "WARN Wrong bounce buffer write length: %zu != %d\n",
+                                         len, new_buff_len);
+               } else {
+                       memcpy(seg->bounce_buf, urb->transfer_buffer + enqd_len, new_buff_len);
+               }
+
                seg->bounce_dma = dma_map_single(dev, seg->bounce_buf,
                                                 max_pkt, DMA_TO_DEVICE);
        } else {