Skip to content

Commit

Permalink
drm: Track drm_mm allocators and show leaks on shutdown
Browse files Browse the repository at this point in the history
We can use the kernel's stack tracer and depot to record the allocation
site of every drm_mm user. Then on shutdown, as well as warning that
allocated nodes still reside with the drm_mm range manager, we can
display who allocated them to aide tracking down the leak.

v2: Move Kconfig around so it lies underneath the DRM options submenu.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Reviewed-by: Christian König <christian.koenig@amd.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/20161031090806.20073-1-chris@chris-wilson.co.uk
  • Loading branch information
Chris Wilson authored and Daniel Vetter committed Nov 8, 2016
1 parent 55edf41 commit 5705670
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 3 deletions.
13 changes: 13 additions & 0 deletions drivers/gpu/drm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ config DRM_DP_AUX_CHARDEV
read and write values to arbitrary DPCD registers on the DP aux
channel.

config DRM_DEBUG_MM
bool "Insert extra checks and debug info into the DRM range managers"
default n
depends on DRM
select STACKDEPOT
help
Enable allocation tracking of memory manager and leak detection on
shutdown.

Recommended for driver developers only.

If in doubt, say "N".

config DRM_KMS_HELPER
tristate
depends on DRM
Expand Down
74 changes: 71 additions & 3 deletions drivers/gpu/drm/drm_mm.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,66 @@ static struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_
u64 end,
enum drm_mm_search_flags flags);

#ifdef CONFIG_DRM_DEBUG_MM
#define STACKDEPTH 32
#define BUFSZ 4096

static noinline void save_stack(struct drm_mm_node *node)
{
unsigned long entries[STACKDEPTH];
struct stack_trace trace = {
.entries = entries,
.max_entries = STACKDEPTH,
.skip = 1
};

save_stack_trace(&trace);
if (trace.nr_entries != 0 &&
trace.entries[trace.nr_entries-1] == ULONG_MAX)
trace.nr_entries--;

/* May be called under spinlock, so avoid sleeping */
node->stack = depot_save_stack(&trace, GFP_NOWAIT);
}

static void show_leaks(struct drm_mm *mm)
{
struct drm_mm_node *node;
unsigned long entries[STACKDEPTH];
char *buf;

buf = kmalloc(BUFSZ, GFP_KERNEL);
if (!buf)
return;

list_for_each_entry(node, &mm->head_node.node_list, node_list) {
struct stack_trace trace = {
.entries = entries,
.max_entries = STACKDEPTH
};

if (!node->stack) {
DRM_ERROR("node [%08llx + %08llx]: unknown owner\n",
node->start, node->size);
continue;
}

depot_fetch_stack(node->stack, &trace);
snprint_stack_trace(buf, BUFSZ, &trace, 0);
DRM_ERROR("node [%08llx + %08llx]: inserted at\n%s",
node->start, node->size, buf);
}

kfree(buf);
}

#undef STACKDEPTH
#undef BUFSZ
#else
static void save_stack(struct drm_mm_node *node) { }
static void show_leaks(struct drm_mm *mm) { }
#endif

#define START(node) ((node)->start)
#define LAST(node) ((node)->start + (node)->size - 1)

Expand Down Expand Up @@ -228,6 +288,8 @@ static void drm_mm_insert_helper(struct drm_mm_node *hole_node,
list_add(&node->hole_stack, &mm->hole_stack);
node->hole_follows = 1;
}

save_stack(node);
}

/**
Expand Down Expand Up @@ -293,6 +355,8 @@ int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node)
node->hole_follows = 1;
}

save_stack(node);

return 0;
}
EXPORT_SYMBOL(drm_mm_reserve_node);
Expand Down Expand Up @@ -397,6 +461,8 @@ static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node,
list_add(&node->hole_stack, &mm->hole_stack);
node->hole_follows = 1;
}

save_stack(node);
}

/**
Expand Down Expand Up @@ -861,10 +927,12 @@ EXPORT_SYMBOL(drm_mm_init);
* Note that it is a bug to call this function on an allocator which is not
* clean.
*/
void drm_mm_takedown(struct drm_mm * mm)
void drm_mm_takedown(struct drm_mm *mm)
{
WARN(!list_empty(&mm->head_node.node_list),
"Memory manager not clean during takedown.\n");
if (WARN(!list_empty(&mm->head_node.node_list),
"Memory manager not clean during takedown.\n"))
show_leaks(mm);

}
EXPORT_SYMBOL(drm_mm_takedown);

Expand Down
6 changes: 6 additions & 0 deletions include/drm/drm_mm.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
#ifdef CONFIG_DEBUG_FS
#include <linux/seq_file.h>
#endif
#ifdef CONFIG_DRM_DEBUG_MM
#include <linux/stackdepot.h>
#endif

enum drm_mm_search_flags {
DRM_MM_SEARCH_DEFAULT = 0,
Expand Down Expand Up @@ -74,6 +77,9 @@ struct drm_mm_node {
u64 size;
u64 __subtree_last;
struct drm_mm *mm;
#ifdef CONFIG_DRM_DEBUG_MM
depot_stack_handle_t stack;
#endif
};

struct drm_mm {
Expand Down

0 comments on commit 5705670

Please sign in to comment.