Commit 5705670d authored by Chris Wilson's avatar Chris Wilson Committed by Daniel Vetter

drm: Track drm_mm allocators and show leaks on shutdown

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: default avatarChris Wilson <chris@chris-wilson.co.uk>
Reviewed-by: default avatarChristian König <christian.koenig@amd.com>
Signed-off-by: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/20161031090806.20073-1-chris@chris-wilson.co.uk
parent 55edf41b
...@@ -33,6 +33,19 @@ config DRM_DP_AUX_CHARDEV ...@@ -33,6 +33,19 @@ config DRM_DP_AUX_CHARDEV
read and write values to arbitrary DPCD registers on the DP aux read and write values to arbitrary DPCD registers on the DP aux
channel. 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 config DRM_KMS_HELPER
tristate tristate
depends on DRM depends on DRM
......
...@@ -104,6 +104,66 @@ static struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_ ...@@ -104,6 +104,66 @@ static struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_
u64 end, u64 end,
enum drm_mm_search_flags flags); 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 START(node) ((node)->start)
#define LAST(node) ((node)->start + (node)->size - 1) #define LAST(node) ((node)->start + (node)->size - 1)
...@@ -228,6 +288,8 @@ static void drm_mm_insert_helper(struct drm_mm_node *hole_node, ...@@ -228,6 +288,8 @@ static void drm_mm_insert_helper(struct drm_mm_node *hole_node,
list_add(&node->hole_stack, &mm->hole_stack); list_add(&node->hole_stack, &mm->hole_stack);
node->hole_follows = 1; node->hole_follows = 1;
} }
save_stack(node);
} }
/** /**
...@@ -293,6 +355,8 @@ int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node) ...@@ -293,6 +355,8 @@ int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node)
node->hole_follows = 1; node->hole_follows = 1;
} }
save_stack(node);
return 0; return 0;
} }
EXPORT_SYMBOL(drm_mm_reserve_node); EXPORT_SYMBOL(drm_mm_reserve_node);
...@@ -397,6 +461,8 @@ static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node, ...@@ -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); list_add(&node->hole_stack, &mm->hole_stack);
node->hole_follows = 1; node->hole_follows = 1;
} }
save_stack(node);
} }
/** /**
...@@ -861,10 +927,12 @@ EXPORT_SYMBOL(drm_mm_init); ...@@ -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 * Note that it is a bug to call this function on an allocator which is not
* clean. * 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), if (WARN(!list_empty(&mm->head_node.node_list),
"Memory manager not clean during takedown.\n"); "Memory manager not clean during takedown.\n"))
show_leaks(mm);
} }
EXPORT_SYMBOL(drm_mm_takedown); EXPORT_SYMBOL(drm_mm_takedown);
......
...@@ -44,6 +44,9 @@ ...@@ -44,6 +44,9 @@
#ifdef CONFIG_DEBUG_FS #ifdef CONFIG_DEBUG_FS
#include <linux/seq_file.h> #include <linux/seq_file.h>
#endif #endif
#ifdef CONFIG_DRM_DEBUG_MM
#include <linux/stackdepot.h>
#endif
enum drm_mm_search_flags { enum drm_mm_search_flags {
DRM_MM_SEARCH_DEFAULT = 0, DRM_MM_SEARCH_DEFAULT = 0,
...@@ -74,6 +77,9 @@ struct drm_mm_node { ...@@ -74,6 +77,9 @@ struct drm_mm_node {
u64 size; u64 size;
u64 __subtree_last; u64 __subtree_last;
struct drm_mm *mm; struct drm_mm *mm;
#ifdef CONFIG_DRM_DEBUG_MM
depot_stack_handle_t stack;
#endif
}; };
struct drm_mm { struct drm_mm {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment