diff options
Diffstat (limited to 'drivers/usb/host/xhci.c')
-rw-r--r-- | drivers/usb/host/xhci.c | 57 |
1 files changed, 56 insertions, 1 deletions
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 132b76fa7ca6..ae9d66e75f43 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -18,6 +18,8 @@ #include <linux/slab.h> #include <linux/dmi.h> #include <linux/dma-mapping.h> +#include <linux/dma-map-ops.h> +#include <linux/genalloc.h> #include "xhci.h" #include "xhci-trace.h" @@ -1273,6 +1275,55 @@ static void xhci_unmap_temp_buf(struct usb_hcd *hcd, struct urb *urb) urb->transfer_buffer = NULL; } +static void xhci_map_urb_local(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + void *buffer; + dma_addr_t dma_handle; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_lowmem_pool *lowmem_pool = &xhci->lowmem_pool; + + if (lowmem_pool->pool + && (usb_endpoint_type(&urb->ep->desc) == USB_ENDPOINT_XFER_BULK) + && (urb->transfer_buffer_length > PAGE_SIZE) + && urb->num_sgs && urb->sg && (sg_phys(urb->sg) > 0xffffffff)) { + buffer = gen_pool_dma_alloc(lowmem_pool->pool, + urb->transfer_buffer_length, &dma_handle); + if (buffer) { + urb->transfer_dma = dma_handle; + urb->transfer_buffer = buffer; + urb->transfer_flags |= URB_MAP_LOCAL; + if (usb_urb_dir_out(urb)) + sg_copy_to_buffer(urb->sg, urb->num_sgs, + (void *)buffer, + urb->transfer_buffer_length); + } + } + +} + +static void xhci_unmap_urb_local(struct usb_hcd *hcd, struct urb *urb) +{ + dma_addr_t dma_handle; + u64 cached_buffer; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_lowmem_pool *lowmem_pool = &xhci->lowmem_pool; + + if (urb->transfer_flags & URB_MAP_LOCAL) { + dma_handle = urb->transfer_dma; + cached_buffer = lowmem_pool->cached_base + + ((u32)urb->transfer_dma & (lowmem_pool->size - 1)); + if (usb_urb_dir_in(urb)) + sg_copy_from_buffer(urb->sg, urb->num_sgs, + (void *)cached_buffer, urb->transfer_buffer_length); + gen_pool_free(lowmem_pool->pool, (unsigned long)urb->transfer_buffer, + urb->transfer_buffer_length); + urb->transfer_flags &= ~URB_MAP_LOCAL; + urb->transfer_buffer = NULL; + } +} + + /* * Bypass the DMA mapping if URB is suitable for Immediate Transfer (IDT), * we'll copy the actual data into the TRB address register. This is limited to @@ -1293,9 +1344,11 @@ static int xhci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, if (xhci_urb_temp_buffer_required(hcd, urb)) return xhci_map_temp_buffer(hcd, urb); } + xhci_map_urb_local(hcd, urb, mem_flags); return usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); } + static void xhci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) { struct xhci_hcd *xhci; @@ -1308,8 +1361,10 @@ static void xhci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) if ((xhci->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK) && unmap_temp_buf) xhci_unmap_temp_buf(hcd, urb); - else + else { + xhci_unmap_urb_local(hcd, urb); usb_hcd_unmap_urb_for_dma(hcd, urb); + } } /** |