[v2,2/3] usb: chipidea: Simplify Tegra DMA alignment code
Commit Message
The USB host on Tegra3 works with 32-bit alignment. Previous code tried
to align the buffer, but it did align the wrapper struct instead, so
the buffer was at a constant offset of 8 bytes (two pointers) from
expected alignment. Since kmalloc() guarantees at least 8-byte
alignment already, the alignment-extending is removed.
Fixes: fc53d5279094 ("usb: chipidea: tegra: Support host mode")
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
---
drivers/usb/chipidea/host.c | 45 +++++++++++++++----------------------
1 file changed, 18 insertions(+), 27 deletions(-)
Comments
On Thu, Sep 28, 2023 at 11:06:03PM +0200, Michał Mirosław wrote:
> The USB host on Tegra3 works with 32-bit alignment. Previous code tried
> to align the buffer, but it did align the wrapper struct instead, so
> the buffer was at a constant offset of 8 bytes (two pointers) from
> expected alignment. Since kmalloc() guarantees at least 8-byte
> alignment already, the alignment-extending is removed.
>
> Fixes: fc53d5279094 ("usb: chipidea: tegra: Support host mode")
> Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> ---
> drivers/usb/chipidea/host.c | 45 +++++++++++++++----------------------
> 1 file changed, 18 insertions(+), 27 deletions(-)
>
> diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
> index abddd39d1ff1..0cce19208370 100644
> --- a/drivers/usb/chipidea/host.c
> +++ b/drivers/usb/chipidea/host.c
> @@ -30,8 +30,7 @@ struct ehci_ci_priv {
> };
>
> struct ci_hdrc_dma_aligned_buffer {
> - void *kmalloc_ptr;
> - void *old_xfer_buffer;
> + void *original_buffer;
> u8 data[];
> };
>
> @@ -380,60 +379,52 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
> return 0;
> }
>
> -static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb)
> +static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb, bool copy_back)
> {
> struct ci_hdrc_dma_aligned_buffer *temp;
> - size_t length;
>
> if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
> return;
> + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
This threw me off a bit until I realized it was already there
previously, just in a different place. Is there a particular reason why
this is moved?
Regardless, this looks fine, so:
Acked-by: Thierry Reding <treding@nvidia.com>
>
> temp = container_of(urb->transfer_buffer,
> struct ci_hdrc_dma_aligned_buffer, data);
> + urb->transfer_buffer = temp->original_buffer;
> +
> + if (copy_back && usb_urb_dir_in(urb)) {
> + size_t length;
>
> - if (usb_urb_dir_in(urb)) {
> if (usb_pipeisoc(urb->pipe))
> length = urb->transfer_buffer_length;
> else
> length = urb->actual_length;
>
> - memcpy(temp->old_xfer_buffer, temp->data, length);
> + memcpy(temp->original_buffer, temp->data, length);
> }
> - urb->transfer_buffer = temp->old_xfer_buffer;
> - kfree(temp->kmalloc_ptr);
>
> - urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
> + kfree(temp);
> }
>
> static int ci_hdrc_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
> {
> - struct ci_hdrc_dma_aligned_buffer *temp, *kmalloc_ptr;
> - const unsigned int ci_hdrc_usb_dma_align = 32;
> - size_t kmalloc_size;
> + struct ci_hdrc_dma_aligned_buffer *temp;
>
> if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0)
> return 0;
> - if (!((uintptr_t)urb->transfer_buffer & (ci_hdrc_usb_dma_align - 1)) && !(urb->transfer_buffer_length & 3))
> + if (IS_ALIGNED((uintptr_t)urb->transfer_buffer, 4)
> + && IS_ALIGNED(urb->transfer_buffer_length, 4))
> return 0;
>
> - /* Allocate a buffer with enough padding for alignment */
> - kmalloc_size = ALIGN(urb->transfer_buffer_length, 4) +
> - sizeof(struct ci_hdrc_dma_aligned_buffer) +
> - ci_hdrc_usb_dma_align - 1;
> -
> - kmalloc_ptr = kmalloc(kmalloc_size, mem_flags);
> - if (!kmalloc_ptr)
> + temp = kmalloc(sizeof(*temp) + ALIGN(urb->transfer_buffer_length, 4), mem_flags);
> + if (!temp)
> return -ENOMEM;
>
> - /* Position our struct dma_aligned_buffer such that data is aligned */
> - temp = PTR_ALIGN(kmalloc_ptr + 1, ci_hdrc_usb_dma_align) - 1;
> - temp->kmalloc_ptr = kmalloc_ptr;
> - temp->old_xfer_buffer = urb->transfer_buffer;
> if (usb_urb_dir_out(urb))
> memcpy(temp->data, urb->transfer_buffer,
> urb->transfer_buffer_length);
> +
> + temp->original_buffer = urb->transfer_buffer;
> urb->transfer_buffer = temp->data;
> -
> urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER;
>
> return 0;
> @@ -450,7 +441,7 @@ static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
>
> ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
> if (ret)
> - ci_hdrc_free_dma_aligned_buffer(urb);
> + ci_hdrc_free_dma_aligned_buffer(urb, false);
>
> return ret;
> }
> @@ -458,7 +449,7 @@ static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
> static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
> {
> usb_hcd_unmap_urb_for_dma(hcd, urb);
> - ci_hdrc_free_dma_aligned_buffer(urb);
> + ci_hdrc_free_dma_aligned_buffer(urb, true);
> }
>
> #ifdef CONFIG_PM_SLEEP
> --
> 2.39.2
>
On 23-10-11 23:53:28, Thierry Reding wrote:
> On Thu, Sep 28, 2023 at 11:06:03PM +0200, Michał Mirosław wrote:
> > The USB host on Tegra3 works with 32-bit alignment. Previous code tried
> > to align the buffer, but it did align the wrapper struct instead, so
> > the buffer was at a constant offset of 8 bytes (two pointers) from
> > expected alignment. Since kmalloc() guarantees at least 8-byte
> > alignment already, the alignment-extending is removed.
> >
> > Fixes: fc53d5279094 ("usb: chipidea: tegra: Support host mode")
> > Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > ---
> > drivers/usb/chipidea/host.c | 45 +++++++++++++++----------------------
> > 1 file changed, 18 insertions(+), 27 deletions(-)
> >
> > diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
> > index abddd39d1ff1..0cce19208370 100644
> > --- a/drivers/usb/chipidea/host.c
> > +++ b/drivers/usb/chipidea/host.c
> > @@ -30,8 +30,7 @@ struct ehci_ci_priv {
> > };
> >
> > struct ci_hdrc_dma_aligned_buffer {
> > - void *kmalloc_ptr;
> > - void *old_xfer_buffer;
> > + void *original_buffer;
> > u8 data[];
> > };
> >
> > @@ -380,60 +379,52 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
> > return 0;
> > }
> >
> > -static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb)
> > +static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb, bool copy_back)
> > {
> > struct ci_hdrc_dma_aligned_buffer *temp;
> > - size_t length;
> >
> > if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
> > return;
> > + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
>
> This threw me off a bit until I realized it was already there
> previously, just in a different place. Is there a particular reason why
> this is moved?
>
> Regardless, this looks fine, so:
>
> Acked-by: Thierry Reding <treding@nvidia.com>
Acked-by: Peter Chen <peter.chen@kernel.org>
On Wed, Oct 11, 2023 at 11:53:28PM +0200, Thierry Reding wrote:
> On Thu, Sep 28, 2023 at 11:06:03PM +0200, Michał Mirosław wrote:
> > The USB host on Tegra3 works with 32-bit alignment. Previous code tried
> > to align the buffer, but it did align the wrapper struct instead, so
> > the buffer was at a constant offset of 8 bytes (two pointers) from
> > expected alignment. Since kmalloc() guarantees at least 8-byte
> > alignment already, the alignment-extending is removed.
> >
> > Fixes: fc53d5279094 ("usb: chipidea: tegra: Support host mode")
> > Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > ---
> > drivers/usb/chipidea/host.c | 45 +++++++++++++++----------------------
> > 1 file changed, 18 insertions(+), 27 deletions(-)
> >
> > diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
> > index abddd39d1ff1..0cce19208370 100644
> > --- a/drivers/usb/chipidea/host.c
> > +++ b/drivers/usb/chipidea/host.c
> > @@ -30,8 +30,7 @@ struct ehci_ci_priv {
> > };
> >
> > struct ci_hdrc_dma_aligned_buffer {
> > - void *kmalloc_ptr;
> > - void *old_xfer_buffer;
> > + void *original_buffer;
> > u8 data[];
> > };
> >
> > @@ -380,60 +379,52 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
> > return 0;
> > }
> >
> > -static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb)
> > +static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb, bool copy_back)
> > {
> > struct ci_hdrc_dma_aligned_buffer *temp;
> > - size_t length;
> >
> > if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
> > return;
> > + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
>
> This threw me off a bit until I realized it was already there
> previously, just in a different place. Is there a particular reason why
> this is moved?
I figured that it is easier to understand if the test and clear of the
URB_ALIGNED_TEMP_BUFFER bit is in a single place. Seeing it again, I
think it could be replaced with __test_and_clear_bit() in a future commit.
Best Regards
Michał Mirosław
@@ -30,8 +30,7 @@ struct ehci_ci_priv {
};
struct ci_hdrc_dma_aligned_buffer {
- void *kmalloc_ptr;
- void *old_xfer_buffer;
+ void *original_buffer;
u8 data[];
};
@@ -380,60 +379,52 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
return 0;
}
-static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb)
+static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb, bool copy_back)
{
struct ci_hdrc_dma_aligned_buffer *temp;
- size_t length;
if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
return;
+ urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
temp = container_of(urb->transfer_buffer,
struct ci_hdrc_dma_aligned_buffer, data);
+ urb->transfer_buffer = temp->original_buffer;
+
+ if (copy_back && usb_urb_dir_in(urb)) {
+ size_t length;
- if (usb_urb_dir_in(urb)) {
if (usb_pipeisoc(urb->pipe))
length = urb->transfer_buffer_length;
else
length = urb->actual_length;
- memcpy(temp->old_xfer_buffer, temp->data, length);
+ memcpy(temp->original_buffer, temp->data, length);
}
- urb->transfer_buffer = temp->old_xfer_buffer;
- kfree(temp->kmalloc_ptr);
- urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
+ kfree(temp);
}
static int ci_hdrc_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
{
- struct ci_hdrc_dma_aligned_buffer *temp, *kmalloc_ptr;
- const unsigned int ci_hdrc_usb_dma_align = 32;
- size_t kmalloc_size;
+ struct ci_hdrc_dma_aligned_buffer *temp;
if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0)
return 0;
- if (!((uintptr_t)urb->transfer_buffer & (ci_hdrc_usb_dma_align - 1)) && !(urb->transfer_buffer_length & 3))
+ if (IS_ALIGNED((uintptr_t)urb->transfer_buffer, 4)
+ && IS_ALIGNED(urb->transfer_buffer_length, 4))
return 0;
- /* Allocate a buffer with enough padding for alignment */
- kmalloc_size = ALIGN(urb->transfer_buffer_length, 4) +
- sizeof(struct ci_hdrc_dma_aligned_buffer) +
- ci_hdrc_usb_dma_align - 1;
-
- kmalloc_ptr = kmalloc(kmalloc_size, mem_flags);
- if (!kmalloc_ptr)
+ temp = kmalloc(sizeof(*temp) + ALIGN(urb->transfer_buffer_length, 4), mem_flags);
+ if (!temp)
return -ENOMEM;
- /* Position our struct dma_aligned_buffer such that data is aligned */
- temp = PTR_ALIGN(kmalloc_ptr + 1, ci_hdrc_usb_dma_align) - 1;
- temp->kmalloc_ptr = kmalloc_ptr;
- temp->old_xfer_buffer = urb->transfer_buffer;
if (usb_urb_dir_out(urb))
memcpy(temp->data, urb->transfer_buffer,
urb->transfer_buffer_length);
+
+ temp->original_buffer = urb->transfer_buffer;
urb->transfer_buffer = temp->data;
-
urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER;
return 0;
@@ -450,7 +441,7 @@ static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
if (ret)
- ci_hdrc_free_dma_aligned_buffer(urb);
+ ci_hdrc_free_dma_aligned_buffer(urb, false);
return ret;
}
@@ -458,7 +449,7 @@ static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
{
usb_hcd_unmap_urb_for_dma(hcd, urb);
- ci_hdrc_free_dma_aligned_buffer(urb);
+ ci_hdrc_free_dma_aligned_buffer(urb, true);
}
#ifdef CONFIG_PM_SLEEP