diff mbox

[1/3] driver core/platform: don't leak memory allocated for dma_mask

Message ID 1401122483-31603-1-git-send-email-emilgoode@gmail.com
State New
Headers show

Commit Message

Emil Goode May 26, 2014, 4:41 p.m. UTC
From: Yann Droneaud <ydroneaud@opteya.com>

Since commit 01dcc60a7cb8, platform_device_register_full() is
available to allocate and register a platform device.

If a dma_mask is provided as part of platform_device_info,
platform_device_register_full() allocate memory for a u64 using
kmalloc().

A comment in the code state that "[t]his memory isn't freed when the
device is put".

It's never a good thing to leak memory, but there's only very few
users of platform_device_info's dma_mask, and those are mostly
"static" devices that are not going to be plugged/unplugged often.

So memory leak is not really an issue, but allocating 8 bytes through
kmalloc() seems overkill.

To address this issue, this patch adds dma_mask to struct platform_object
and when using platform_device_alloc() or platform_device_register_full()
the .dma_mask pointer of struct device is assigned the address of this new
dma_mask member of struct platform_object.  And since it's within struct
platform_object, dma_mask won't be leaked anymore when struct platform_device
get released.

No more memory leak, no small allocation and a slight reduction in code
size.

Built on Debian jessie, using GCC 4.8, for ARM and x86_64 architectures,
the size of the object file and the data structure layout are updated
as follows, produced with commands:

$ size drivers/base/platform.o
$ pahole drivers/base/platform.o \
	--recursive		 \
	--class_name device,pdev_archdata,platform_device,platform_object

-- size+pahole_3.15-rc6_ARM
++ size+pahole_3.15-rc6_ARM+
@@ -2,7 +2,7 @@
    text	   data	    bss	    dec	    hex	filename
-   6530	   1008	      8	   7546	   1d7a	drivers/base/platform.o
+   6482	   1008	      8	   7498	   1d4a	drivers/base/platform.o

@@ -93,8 +93,13 @@ struct platform_object {
 	/* --- cacheline 12 boundary (768 bytes) was 40 bytes ago --- */
 	char                       name[1];            /*   808     1 */

-	/* size: 816, cachelines: 13, members: 2 */
-	/* padding: 7 */
+	/* XXX 7 bytes hole, try to pack */

+	u64                        dma_mask;           /*   816     8 */

+	/* size: 824, cachelines: 13, members: 3 */
+	/* sum members: 817, holes: 1, sum holes: 7 */
 	/* paddings: 1, sum paddings: 4 */
-	/* last cacheline: 48 bytes */
+	/* last cacheline: 56 bytes */
 };

-- size+pahole_3.15-rc6_x86_64
++ size+pahole_3.15-rc6_x86_64+
@@ -2,7 +2,7 @@
    text	   data	    bss	    dec	    hex	filename
-   8570	   5032	   3424	  17026	   4282	drivers/base/platform.o
+   8509	   5032	   3408	  16949	   4235	drivers/base/platform.o

@@ -95,7 +95,11 @@ struct platform_object {
 	/* --- cacheline 22 boundary (1408 bytes) was 32 bytes ago --- */
 	char                       name[1];             /*  1440     1 */

-	/* size: 1448, cachelines: 23, members: 2 */
-	/* padding: 7 */
-	/* last cacheline: 40 bytes */
+	/* XXX 7 bytes hole, try to pack */

+	u64                        dma_mask;            /*  1448     8 */

+	/* size: 1456, cachelines: 23, members: 3 */
+	/* sum members: 1449, holes: 1, sum holes: 7 */
+	/* last cacheline: 48 bytes */
 };

Changes from v4 [1]:
- Split v4 of the patch into two separate patches.
- Generated new object file size and data structure layout info.
- Updated the changelog message.

Changes from v3 [2]:
- fixed commit message so that git am doesn't fail.

Changes from v2 [3]:
- move 'dma_mask' to platform_object so that it's always
  allocated and won't leak on release; remove all previously
  added support functions.
- use C99 flexible array member for 'name' to remove padding
  at the end of platform_object.

Changes from v1 [4]:
- remove unneeded kfree() from error path
- add reference to author/commit adding allocation of dmamask

Changes from v0 [5]:
- small rewrite to squeeze the patch to a bare minimal

[1] http://lkml.kernel.org/r/1390817152-30898-1-git-send-email-ydroneaud@opteya.com
    https://patchwork.kernel.org/patch/3541871/

[2] http://lkml.kernel.org/r/1390771138-28348-1-git-send-email-ydroneaud@opteya.com
    https://patchwork.kernel.org/patch/3540081/

[3] http://lkml.kernel.org/r/1389683909-17495-1-git-send-email-ydroneaud@opteya.com
    https://patchwork.kernel.org/patch/3484411/

[4] http://lkml.kernel.org/r/1389649085-7365-1-git-send-email-ydroneaud@opteya.com
    https://patchwork.kernel.org/patch/3480961/

[5] http://lkml.kernel.org/r/1386886207-2735-1-git-send-email-ydroneaud@opteya.com

Cc: Yann Droneaud <ydroneaud@opteya.com>
Cc: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Yann Droneaud <ydroneaud@opteya.com>
[Emil: split v4 of the patch in two and updated changelog]
Signed-off-by: Emil Goode <emilgoode@gmail.com>
---
Hello Greg,

The first two patches in the series are created from v4 of the
original patch, since I have not changed how the code works I think
it is correct to keep the original author and Signed-off-by line.

Best regards,

Emil Goode

 drivers/base/platform.c |   13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

Comments

Russell King - ARM Linux May 26, 2014, 4:51 p.m. UTC | #1
On Mon, May 26, 2014 at 06:41:21PM +0200, Emil Goode wrote:
> @@ -211,6 +215,7 @@ struct platform_device *platform_device_alloc(const char *name, int id)
>  		strcpy(pa->name, name);
>  		pa->pdev.name = pa->name;
>  		pa->pdev.id = id;
> +		pa->pdev.dev.dma_mask = &pa->dma_mask;

There is code in the kernel which, rightly or wrongly, checks whether
dev->dma_mask is NULL to determine whether the device can do any kind
of DMA.  The above results in devices allocated via this interface
always having this member set, which is a change of core kernel behaviour.

How sure are you that this will not break anything?
Uwe Kleine-König May 26, 2014, 6:14 p.m. UTC | #2
On Mon, May 26, 2014 at 06:41:21PM +0200, Emil Goode wrote:
> From: Yann Droneaud <ydroneaud@opteya.com>
> 
> Since commit 01dcc60a7cb8, platform_device_register_full() is
> available to allocate and register a platform device.
> 
> If a dma_mask is provided as part of platform_device_info,
> platform_device_register_full() allocate memory for a u64 using
> kmalloc().
> 
> A comment in the code state that "[t]his memory isn't freed when the
> device is put".
> 
> It's never a good thing to leak memory, but there's only very few
> users of platform_device_info's dma_mask, and those are mostly
> "static" devices that are not going to be plugged/unplugged often.
> 
> So memory leak is not really an issue, but allocating 8 bytes through
> kmalloc() seems overkill.
> 
> To address this issue, this patch adds dma_mask to struct platform_object
> and when using platform_device_alloc() or platform_device_register_full()
> the .dma_mask pointer of struct device is assigned the address of this new
> dma_mask member of struct platform_object.  And since it's within struct
> platform_object, dma_mask won't be leaked anymore when struct platform_device
> get released.
> 
> No more memory leak, no small allocation and a slight reduction in code
> size.
> 
> Built on Debian jessie, using GCC 4.8, for ARM and x86_64 architectures,
> the size of the object file and the data structure layout are updated
> as follows, produced with commands:
> 
> $ size drivers/base/platform.o
> $ pahole drivers/base/platform.o \
> 	--recursive		 \
> 	--class_name device,pdev_archdata,platform_device,platform_object
> 
> -- size+pahole_3.15-rc6_ARM
> ++ size+pahole_3.15-rc6_ARM+
> @@ -2,7 +2,7 @@
>     text	   data	    bss	    dec	    hex	filename
> -   6530	   1008	      8	   7546	   1d7a	drivers/base/platform.o
> +   6482	   1008	      8	   7498	   1d4a	drivers/base/platform.o
> 
> @@ -93,8 +93,13 @@ struct platform_object {
>  	/* --- cacheline 12 boundary (768 bytes) was 40 bytes ago --- */
>  	char                       name[1];            /*   808     1 */
> 
> -	/* size: 816, cachelines: 13, members: 2 */
> -	/* padding: 7 */
> +	/* XXX 7 bytes hole, try to pack */
> 
> +	u64                        dma_mask;           /*   816     8 */
> 
> +	/* size: 824, cachelines: 13, members: 3 */
> +	/* sum members: 817, holes: 1, sum holes: 7 */
>  	/* paddings: 1, sum paddings: 4 */
> -	/* last cacheline: 48 bytes */
> +	/* last cacheline: 56 bytes */
>  };
> 
> -- size+pahole_3.15-rc6_x86_64
> ++ size+pahole_3.15-rc6_x86_64+
> @@ -2,7 +2,7 @@
>     text	   data	    bss	    dec	    hex	filename
> -   8570	   5032	   3424	  17026	   4282	drivers/base/platform.o
> +   8509	   5032	   3408	  16949	   4235	drivers/base/platform.o
> 
> @@ -95,7 +95,11 @@ struct platform_object {
>  	/* --- cacheline 22 boundary (1408 bytes) was 32 bytes ago --- */
>  	char                       name[1];             /*  1440     1 */
> 
> -	/* size: 1448, cachelines: 23, members: 2 */
> -	/* padding: 7 */
> -	/* last cacheline: 40 bytes */
> +	/* XXX 7 bytes hole, try to pack */
> 
> +	u64                        dma_mask;            /*  1448     8 */
> 
> +	/* size: 1456, cachelines: 23, members: 3 */
> +	/* sum members: 1449, holes: 1, sum holes: 7 */
> +	/* last cacheline: 48 bytes */
>  };
> 
> Changes from v4 [1]:
> - Split v4 of the patch into two separate patches.
> - Generated new object file size and data structure layout info.
> - Updated the changelog message.
> 
> Changes from v3 [2]:
> - fixed commit message so that git am doesn't fail.
> 
> Changes from v2 [3]:
> - move 'dma_mask' to platform_object so that it's always
>   allocated and won't leak on release; remove all previously
>   added support functions.
> - use C99 flexible array member for 'name' to remove padding
>   at the end of platform_object.
> 
> Changes from v1 [4]:
> - remove unneeded kfree() from error path
> - add reference to author/commit adding allocation of dmamask
> 
> Changes from v0 [5]:
> - small rewrite to squeeze the patch to a bare minimal
> 
> [1] http://lkml.kernel.org/r/1390817152-30898-1-git-send-email-ydroneaud@opteya.com
>     https://patchwork.kernel.org/patch/3541871/
> 
> [2] http://lkml.kernel.org/r/1390771138-28348-1-git-send-email-ydroneaud@opteya.com
>     https://patchwork.kernel.org/patch/3540081/
> 
> [3] http://lkml.kernel.org/r/1389683909-17495-1-git-send-email-ydroneaud@opteya.com
>     https://patchwork.kernel.org/patch/3484411/
> 
> [4] http://lkml.kernel.org/r/1389649085-7365-1-git-send-email-ydroneaud@opteya.com
>     https://patchwork.kernel.org/patch/3480961/
> 
> [5] http://lkml.kernel.org/r/1386886207-2735-1-git-send-email-ydroneaud@opteya.com
> 
> Cc: Yann Droneaud <ydroneaud@opteya.com>
> Cc: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Signed-off-by: Yann Droneaud <ydroneaud@opteya.com>
> [Emil: split v4 of the patch in two and updated changelog]
> Signed-off-by: Emil Goode <emilgoode@gmail.com>
> ---
> Hello Greg,
> 
> The first two patches in the series are created from v4 of the
> original patch, since I have not changed how the code works I think
> it is correct to keep the original author and Signed-off-by line.
> 
> Best regards,
> 
> Emil Goode
> 
>  drivers/base/platform.c |   13 +++++--------
>  1 file changed, 5 insertions(+), 8 deletions(-)
> 
> diff --git a/drivers/base/platform.c b/drivers/base/platform.c
> index 9e9227e..dd1fa07 100644
> --- a/drivers/base/platform.c
> +++ b/drivers/base/platform.c
> @@ -166,6 +166,7 @@ EXPORT_SYMBOL_GPL(platform_add_devices);
>  struct platform_object {
>  	struct platform_device pdev;
>  	char name[1];
> +	u64 dma_mask;
This is broken. .name is used as flexible array, so .name and dma_mask
overlap.

Uwe
Emil Goode May 26, 2014, 6:31 p.m. UTC | #3
Hello Russell,

On Mon, May 26, 2014 at 05:51:05PM +0100, Russell King - ARM Linux wrote:
> On Mon, May 26, 2014 at 06:41:21PM +0200, Emil Goode wrote:
> > @@ -211,6 +215,7 @@ struct platform_device *platform_device_alloc(const char *name, int id)
> >  		strcpy(pa->name, name);
> >  		pa->pdev.name = pa->name;
> >  		pa->pdev.id = id;
> > +		pa->pdev.dev.dma_mask = &pa->dma_mask;
> 
> There is code in the kernel which, rightly or wrongly, checks whether
> dev->dma_mask is NULL to determine whether the device can do any kind
> of DMA.  The above results in devices allocated via this interface
> always having this member set, which is a change of core kernel behaviour.
> 
> How sure are you that this will not break anything?

Thank you for pointing this out, considering the number of calls made to
platform_device_alloc it would be easy to miss an occurrance of this problem.
I would say that the risk heavily outweighs the gain and it's better to 
not apply this series.

Best regards,

Emil Goode
Yann Droneaud May 26, 2014, 7:14 p.m. UTC | #4
Hi Emil,

Le lundi 26 mai 2014 à 18:41 +0200, Emil Goode a écrit :

> The first two patches in the series are created from v4 of the
> original patch, since I have not changed how the code works I think
> it is correct to keep the original author and Signed-off-by line.
> 
> Best regards,

Thanks for the update.

I wasn't interested in splitting the patch in two separate chunks,
thinking that shrinking the size of the structure then increasing it of
roughly the same amount was not the best way to sell the changes :)

Unfortunately, as noted by Uwe, you not only split the patch but also
broke it ;)

If we're going to split the patch, it should be split in:
1) replace name[1] by name[] (or name[0]) to remove the implicit padding
from platform_device structure
2) add dma_mask to platform_object structure and use it to initialize
   dev.dma_mask.

Anyway, as Russel explained in another mail, unconditionally set
dev.dma_mask pointer is probably going to break, so this part (2) need
some rework, I'm gonna try to do.

Thanks for reminding me about this patch.

Regards.
Dan Carpenter May 26, 2014, 7:30 p.m. UTC | #5
On Mon, May 26, 2014 at 06:41:21PM +0200, Emil Goode wrote:
> diff --git a/drivers/base/platform.c b/drivers/base/platform.c
> index 9e9227e..dd1fa07 100644
> --- a/drivers/base/platform.c
> +++ b/drivers/base/platform.c
> @@ -166,6 +166,7 @@ EXPORT_SYMBOL_GPL(platform_add_devices);
>  struct platform_object {
>  	struct platform_device pdev;
>  	char name[1];
> +	u64 dma_mask;
>  };

Heh.  No this doesn't work as patch #1.  You have to have name at the
end of the struct.

regards,
dan carpenter
Emil Goode May 26, 2014, 7:40 p.m. UTC | #6
Hello,

On Mon, May 26, 2014 at 10:30:46PM +0300, Dan Carpenter wrote:
> On Mon, May 26, 2014 at 06:41:21PM +0200, Emil Goode wrote:
> > diff --git a/drivers/base/platform.c b/drivers/base/platform.c
> > index 9e9227e..dd1fa07 100644
> > --- a/drivers/base/platform.c
> > +++ b/drivers/base/platform.c
> > @@ -166,6 +166,7 @@ EXPORT_SYMBOL_GPL(platform_add_devices);
> >  struct platform_object {
> >  	struct platform_device pdev;
> >  	char name[1];
> > +	u64 dma_mask;
> >  };
> 
> Heh.  No this doesn't work as patch #1.  You have to have name at the
> end of the struct.

Yes I missed that one, obviously the order is important here.

Best regards,

Emil Goode
diff mbox

Patch

diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index 9e9227e..dd1fa07 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -166,6 +166,7 @@  EXPORT_SYMBOL_GPL(platform_add_devices);
 struct platform_object {
 	struct platform_device pdev;
 	char name[1];
+	u64 dma_mask;
 };
 
 /**
@@ -201,6 +202,9 @@  static void platform_device_release(struct device *dev)
  *
  * Create a platform device object which can have other objects attached
  * to it, and which will have attached objects freed when it is released.
+ *
+ * The associated struct device will be set up so that its dma_mask field
+ * is a valid pointer to an u64. This pointer must not be free'd manually.
  */
 struct platform_device *platform_device_alloc(const char *name, int id)
 {
@@ -211,6 +215,7 @@  struct platform_device *platform_device_alloc(const char *name, int id)
 		strcpy(pa->name, name);
 		pa->pdev.name = pa->name;
 		pa->pdev.id = id;
+		pa->pdev.dev.dma_mask = &pa->dma_mask;
 		device_initialize(&pa->pdev.dev);
 		pa->pdev.dev.release = platform_device_release;
 		arch_setup_pdev_archdata(&pa->pdev);
@@ -444,16 +449,9 @@  struct platform_device *platform_device_register_full(
 
 	if (pdevinfo->dma_mask) {
 		/*
-		 * This memory isn't freed when the device is put,
-		 * I don't have a nice idea for that though.  Conceptually
 		 * dma_mask in struct device should not be a pointer.
 		 * See http://thread.gmane.org/gmane.linux.kernel.pci/9081
 		 */
-		pdev->dev.dma_mask =
-			kmalloc(sizeof(*pdev->dev.dma_mask), GFP_KERNEL);
-		if (!pdev->dev.dma_mask)
-			goto err;
-
 		*pdev->dev.dma_mask = pdevinfo->dma_mask;
 		pdev->dev.coherent_dma_mask = pdevinfo->dma_mask;
 	}
@@ -472,7 +470,6 @@  struct platform_device *platform_device_register_full(
 	if (ret) {
 err:
 		ACPI_COMPANION_SET(&pdev->dev, NULL);
-		kfree(pdev->dev.dma_mask);
 
 err_alloc:
 		platform_device_put(pdev);