If these are not met use a bounce buffer. This is done by adding a member dma_align_shift to struct hc_driver. Defaulting to zero causes unmodified HCDs to be assumed to be byte aligned DMA capable. Signed-off-by: Martin Fuzzey <mfuzzey@xxxxxxxxx> --- drivers/usb/core/hcd.c | 54 +++++++++++++++++++++++++++++++++++++---------- include/linux/usb.h | 1 + include/linux/usb/hcd.h | 2 ++ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 12742f1..8ad9367 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1286,12 +1286,19 @@ static void unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) urb->transfer_dma, urb->transfer_buffer_length, dir); - else if (urb->transfer_flags & URB_DMA_MAP_SINGLE) + else if (urb->transfer_flags & URB_DMA_MAP_SINGLE) { dma_unmap_single(hcd->self.controller, urb->transfer_dma, urb->transfer_buffer_length, dir); - else if (urb->transfer_flags & URB_MAP_LOCAL) + if (urb->bounce_buffer) { + if (dir == DMA_FROM_DEVICE) + memcpy(urb->transfer_buffer, + urb->bounce_buffer, + urb->transfer_buffer_length); + kfree(urb->bounce_buffer); + } + } else if (urb->transfer_flags & URB_MAP_LOCAL) hcd_free_coherent(urb->dev->bus, &urb->transfer_dma, &urb->transfer_buffer, @@ -1373,16 +1380,39 @@ static int map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, else urb->transfer_flags |= URB_DMA_MAP_PAGE; } else { - urb->transfer_dma = dma_map_single( - hcd->self.controller, - urb->transfer_buffer, - urb->transfer_buffer_length, - dir); - if (dma_mapping_error(hcd->self.controller, - urb->transfer_dma)) - ret = -EAGAIN; - else - urb->transfer_flags |= URB_DMA_MAP_SINGLE; + void *buffer = urb->transfer_buffer; + + if (IS_ALIGNED((unsigned long)buffer, + 1 << hcd->driver->dma_align_shift)) + urb->bounce_buffer = NULL; + else { + if (dir == DMA_TO_DEVICE) + buffer = kmemdup( + buffer, + urb->transfer_buffer_length, + mem_flags); + else + buffer = kmalloc( + urb->transfer_buffer_length, + mem_flags); + + if (!buffer) + ret = -ENOMEM; + urb->bounce_buffer = buffer; + } + + if (buffer) { + urb->transfer_dma = dma_map_single( + hcd->self.controller, + buffer, + urb->transfer_buffer_length, + dir); + if (dma_mapping_error(hcd->self.controller, + urb->transfer_dma)) + ret = -EAGAIN; + else + urb->transfer_flags |= URB_DMA_MAP_SINGLE; + } } } else if (hcd->driver->flags & HCD_LOCAL_MEM) { ret = hcd_alloc_coherent( diff --git a/include/linux/usb.h b/include/linux/usb.h index d5922a8..4c53145 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1180,6 +1180,7 @@ struct urb { atomic_t use_count; /* concurrent submissions counter */ atomic_t reject; /* submissions will fail */ int unlinked; /* unlink error code */ + void *bounce_buffer; /* DMA bounce buffer for alignment */ /* public: documented fields in the urb that can be used by drivers */ struct list_head urb_list; /* list head for use by the urb's diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 2e3a4ea..a27df53 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -178,6 +178,8 @@ struct hc_driver { const char *description; /* "ehci-hcd" etc */ const char *product_desc; /* product/vendor string */ size_t hcd_priv_size; /* size of private data */ + unsigned dma_align_shift; /* Aligment requirement for DMA: + 0=byte 1=2 byte, 2=4 byte... */ /* irq handler */ irqreturn_t (*irq) (struct usb_hcd *hcd); -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html