drivers: dwc2: better handle hardware length & alignment issues

The version of the dwc-otg core used in BCM2835 through BCM2712 only does
whole-word writes, as well as needing the documented requirement for DMA
buffers to start on a word boundary.

Also, the alignment method used in the dwc2 driver doesn't handle the
case where the URB has the NO_TRANSFER_DMA_MAP flag set, so reject
buffers that have unaligned DMA start addresses. At least one whole page
should be mapped, so the BCM283x whole-word-write bug should be benign
in this case.

Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
This commit is contained in:
Jonathan Bell
2025-06-23 16:42:25 +01:00
committed by Dom Cobley
parent 245f894baf
commit 1adfa3fec3

View File

@@ -2448,7 +2448,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));
@@ -2471,17 +2471,36 @@ static int dwc2_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
void *kmalloc_ptr;
size_t kmalloc_size;
if (urb->num_sgs || urb->sg ||
urb->transfer_buffer_length == 0 ||
if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0)
return 0;
/*
* 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?
*/
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);
@@ -2493,7 +2512,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));