Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions drivers/usb/dwc2/hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -2447,7 +2447,7 @@ static void dwc2_free_dma_aligned_buffer(struct urb *urb)

/* Restore urb->transfer_buffer from the end of the allocated area */
memcpy(&stored_xfer_buffer,
PTR_ALIGN(urb->transfer_buffer + urb->transfer_buffer_length,
PTR_ALIGN(urb->transfer_buffer + urb->transfer_buffer_length + DWC2_USB_DMA_ALIGN,
dma_get_cache_alignment()),
sizeof(urb->transfer_buffer));

Expand All @@ -2469,31 +2469,37 @@ static int dwc2_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
{
void *kmalloc_ptr;
size_t kmalloc_size;
bool small_ctrl;

if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0)
return 0;

/*
* Hardware bug: small IN packets with length < 4 cause a
* 4-byte write to memory. This is only an issue for drivers that
* insist on packing a device's various properties into a struct
* and filling them one at a time with Control transfers (uvcvideo).
* Force the use of align_buf so that the subsequent memcpy puts
* the right number of bytes in the URB's buffer.
* Hardware bug: the core will only do DMA writes of full words
* in length, and DMA buffers must start at a word boundary.
* TODO: is this limited to BCM2835 and friends, or other core variants?
*/
small_ctrl = (urb->setup_packet &&
le16_to_cpu(((struct usb_ctrlrequest *)(urb->setup_packet))->wLength) < 4);

if (!small_ctrl && !((uintptr_t)urb->transfer_buffer & (DWC2_USB_DMA_ALIGN - 1)))
if (!(usb_urb_dir_in(urb) && (urb->transfer_buffer_length & (DWC2_USB_DMA_ALIGN - 1))) &&
!((uintptr_t)urb->transfer_buffer & (DWC2_USB_DMA_ALIGN - 1)))
return 0;

/*
* If the URB already has a DMA mapping, this alignment mechanism won't
* work - the replacement buffer won't be used by the core, as the HCD layer
* skips mapping. Mappings have the granularity of a page, so it's unlikely that the
* DMA length bug will cause data trampling. In any case, warn if there's a driver
* submitting unaligned mapped buffers.
*/
if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) {
if (urb->transfer_dma & (DWC2_USB_DMA_ALIGN - 1))
WARN_ONCE(1, "Unaligned DMA-mapped buffer");
return 0;
}
/*
* Allocate a buffer with enough padding for original transfer_buffer
* pointer. This allocation is guaranteed to be aligned properly for
* DMA
*/
kmalloc_size = urb->transfer_buffer_length +
kmalloc_size = urb->transfer_buffer_length + DWC2_USB_DMA_ALIGN +
(dma_get_cache_alignment() - 1) +
sizeof(urb->transfer_buffer);

Expand All @@ -2505,7 +2511,7 @@ static int dwc2_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
* Position value of original urb->transfer_buffer pointer to the end
* of allocation for later referencing
*/
memcpy(PTR_ALIGN(kmalloc_ptr + urb->transfer_buffer_length,
memcpy(PTR_ALIGN(kmalloc_ptr + urb->transfer_buffer_length + DWC2_USB_DMA_ALIGN,
dma_get_cache_alignment()),
&urb->transfer_buffer, sizeof(urb->transfer_buffer));

Expand Down
Loading