[PATCH 1/2] drm/gk20a/fb: fix huge memory leak

From: Alexandre Courbot
Date: Mon May 19 2014 - 02:52:09 EST


CMA-allocated memory must be freed by an exact mirror call to
dma_release_from_contiguous(). It cannot be freed page-by-page as was
previously believed without severe memory leakage.

This page records the address and size of every allocated memory chunk
so they can be properly freed when needed.

Signed-off-by: Alexandre Courbot <acourbot@xxxxxxxxxx>
---
drivers/gpu/drm/nouveau/core/subdev/fb/ramgk20a.c | 74 ++++++++++++++---------
1 file changed, 46 insertions(+), 28 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/core/subdev/fb/ramgk20a.c b/drivers/gpu/drm/nouveau/core/subdev/fb/ramgk20a.c
index 7effd1a63458..5904af52e6d6 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/fb/ramgk20a.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/fb/ramgk20a.c
@@ -28,28 +28,34 @@
#include <linux/types.h>
#include <linux/dma-contiguous.h>

+struct gk20a_mem_chunk {
+ struct list_head list;
+ struct page *pages;
+ u32 npages;
+};
+
+struct gk20a_mem {
+ struct nouveau_mem base;
+ struct list_head head;
+};
+
static void
gk20a_ram_put(struct nouveau_fb *pfb, struct nouveau_mem **pmem)
{
struct device *dev = nv_device_base(nv_device(pfb));
- struct nouveau_mem *mem = *pmem;
- int i;
+ struct gk20a_mem *mem = container_of(*pmem, struct gk20a_mem, base);
+ struct gk20a_mem_chunk *chunk, *n;

*pmem = NULL;
if (unlikely(mem == NULL))
return;

- for (i = 0; i < mem->size; i++) {
- struct page *page;
-
- if (mem->pages[i] == 0)
- break;
-
- page = pfn_to_page(mem->pages[i] >> PAGE_SHIFT);
- dma_release_from_contiguous(dev, page, 1);
+ list_for_each_entry_safe(chunk, n, &mem->head, list) {
+ dma_release_from_contiguous(dev, chunk->pages, chunk->npages);
+ kfree(chunk);
}

- kfree(mem->pages);
+ kfree(mem->base.pages);
kfree(mem);
}

@@ -58,9 +64,8 @@ gk20a_ram_get(struct nouveau_fb *pfb, u64 size, u32 align, u32 ncmin,
u32 memtype, struct nouveau_mem **pmem)
{
struct device *dev = nv_device_base(nv_device(pfb));
- struct nouveau_mem *mem;
+ struct gk20a_mem *mem;
int type = memtype & 0xff;
- dma_addr_t dma_addr;
int npages;
int order;
int i;
@@ -95,44 +100,57 @@ gk20a_ram_get(struct nouveau_fb *pfb, u64 size, u32 align, u32 ncmin,
if (!mem)
return -ENOMEM;

- mem->size = npages;
- mem->memtype = type;
+ mem->base.size = npages;
+ mem->base.memtype = type;

- mem->pages = kzalloc(sizeof(dma_addr_t) * npages, GFP_KERNEL);
- if (!mem) {
+ mem->base.pages = kzalloc(sizeof(dma_addr_t) * npages, GFP_KERNEL);
+ if (!mem->base.pages) {
kfree(mem);
return -ENOMEM;
}

+ INIT_LIST_HEAD(&mem->head);
+
+ *pmem = &mem->base;
+
while (npages) {
- struct page *pages;
+ struct gk20a_mem_chunk *chunk;
+ dma_addr_t addr;
int pos = 0;

/* don't overflow in case size is not a multiple of ncmin */
if (ncmin > npages)
ncmin = npages;

- pages = dma_alloc_from_contiguous(dev, ncmin, order);
- if (!pages) {
- gk20a_ram_put(pfb, &mem);
+ chunk = kzalloc(sizeof(*chunk), GFP_KERNEL);
+ if (!chunk) {
+ gk20a_ram_put(pfb, pmem);
return -ENOMEM;
}

- dma_addr = (dma_addr_t)(page_to_pfn(pages) << PAGE_SHIFT);
+ chunk->pages = dma_alloc_from_contiguous(dev, ncmin, order);
+ if (!chunk->pages) {
+ kfree(chunk);
+ gk20a_ram_put(pfb, pmem);
+ return -ENOMEM;
+ }

- nv_debug(pfb, " alloc count: %x, order: %x, addr: %pad\n", ncmin,
- order, &dma_addr);
+ chunk->npages = ncmin;
+ list_add_tail(&chunk->list, &mem->head);
+
+ addr = (dma_addr_t)(page_to_pfn(chunk->pages) << PAGE_SHIFT);
+
+ nv_debug(pfb, " alloc count: %x, order: %x, addr: %pad\n",
+ ncmin, order, &addr);

for (i = 0; i < ncmin; i++)
- mem->pages[pos + i] = dma_addr + (PAGE_SIZE * i);
+ mem->base.pages[pos + i] = addr + (PAGE_SIZE * i);

pos += ncmin;
npages -= ncmin;
}

- mem->offset = (u64)mem->pages[0];
-
- *pmem = mem;
+ mem->base.offset = (u64)mem->base.pages[0];

return 0;
}
--
1.9.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/