summaryrefslogtreecommitdiff
path: root/drivers/usb/host/xhci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/xhci.c')
-rw-r--r--drivers/usb/host/xhci.c57
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);
+ }
}
/**