xhci: Fix immediate data transfer if buffer is already DMA mapped [Linux 5.2]

xhci: Fix immediate data transfer if buffer is already DMA mapped [Linux 5.2]

This Linux kernel change "xhci: Fix immediate data transfer if buffer is already DMA mapped" is included in the Linux 5.2 release. This change is authored by Mathias Nyman <mathias.nyman [at] linux.intel.com> on Wed May 22 14:34:00 2019 +0300. The commit for this change in Linux stable tree is 13b82b7 (patch).

xhci: Fix immediate data transfer if buffer is already DMA mapped

xhci immediate data transfer (IDT) support in 5.2-rc1 caused regression
on various Samsung Exynos boards with ASIX USB 2.0 ethernet dongle.

If the transfer buffer in the URB is already DMA mapped then IDT should
not be used. urb->transfer_dma will already contain a valid dma address,
and there is no guarantee the data in urb->transfer_buffer is valid.

The IDT support patch used urb->transfer_dma as a temporary storage,
copying data from urb->transfer_buffer into it.

Issue was solved by preventing IDT if transfer buffer is already dma
mapped, and by not using urb->transfer_dma as temporary storage.

Fixes: 33e39350ebd2 ("usb: xhci: add Immediate Data Transfer support")
Reported-by: Marek Szyprowski <[email protected]>
Tested-by: Marek Szyprowski <[email protected]>
CC: Nicolas Saenz Julienne <[email protected]>
Signed-off-by: Mathias Nyman <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>

There are 12 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/usb/host/xhci-ring.c | 9 ++++++---
 drivers/usb/host/xhci.h      | 3 ++-
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index ef7c869..88392aa 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -3432,11 +3432,14 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,

    if (urb->transfer_buffer_length > 0) {
        u32 length_field, remainder;
+       u64 addr;

        if (xhci_urb_suitable_for_idt(urb)) {
-           memcpy(&urb->transfer_dma, urb->transfer_buffer,
+           memcpy(&addr, urb->transfer_buffer,
                   urb->transfer_buffer_length);
            field |= TRB_IDT;
+       } else {
+           addr = (u64) urb->transfer_dma;
        }

        remainder = xhci_td_remainder(xhci, 0,
@@ -3449,8 +3452,8 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
        if (setup->bRequestType & USB_DIR_IN)
            field |= TRB_DIR_IN;
        queue_trb(xhci, ep_ring, true,
-               lower_32_bits(urb->transfer_dma),
-               upper_32_bits(urb->transfer_dma),
+               lower_32_bits(addr),
+               upper_32_bits(addr),
                length_field,
                field | ep_ring->cycle_state);
    }
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index a450a99..7f8b950 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -2160,7 +2160,8 @@ static inline bool xhci_urb_suitable_for_idt(struct urb *urb)
 {
    if (!usb_endpoint_xfer_isoc(&urb->ep->desc) && usb_urb_dir_out(urb) &&
        usb_endpoint_maxp(&urb->ep->desc) >= TRB_IDT_MAX_SIZE &&
-       urb->transfer_buffer_length <= TRB_IDT_MAX_SIZE)
+       urb->transfer_buffer_length <= TRB_IDT_MAX_SIZE &&
+       !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP))
        return true;

    return false;

Leave a Reply

Your email address will not be published. Required fields are marked *