[v2,2/3] usb: chipidea: Simplify Tegra DMA alignment code

Message ID a0d917d492b1f91ee0019e68b8e8bca9c585393f.1695934946.git.mirq-linux@rere.qmqm.pl
State New
Headers
Series usb: chipidea: An USB DMA fix + cleanups for Tegra |

Commit Message

Michał Mirosław Sept. 28, 2023, 9:06 p.m. UTC
  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

Thierry Reding Oct. 11, 2023, 9:53 p.m. UTC | #1
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
>
  
Peter Chen Oct. 12, 2023, 2:28 p.m. UTC | #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>
  
Michał Mirosław Oct. 12, 2023, 9:20 p.m. UTC | #3
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
  

Patch

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;
 
 	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