Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 311d82ce authored by Patrick Daly's avatar Patrick Daly
Browse files

dma-buf: Add reference count leak tracing



CONFIG_DEBUG_DMA_BUF_REF will collect stacktraces for
each call to dma_buf_get and dma_buf_put to help identify
leaks.

Change-Id: If299d73fbde80fc41d2a2150c4b6b8e7223734fa
Signed-off-by: default avatarPatrick Daly <pdaly@codeaurora.org>
parent ceb432de
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -30,4 +30,15 @@ config SW_SYNC
	  WARNING: improper use of this can result in deadlocking kernel
	  drivers from userspace. Intended for test and debug only.

config DEBUG_DMA_BUF_REF
	bool "DEBUG Reference Count"
	depends on STACKDEPOT
	depends on DMA_SHARED_BUFFER
	default n
	---help---
	  Save stack traces for every call to dma_buf_get and dma_buf_put, to
	  help debug memory leaks. Potential leaks may be found by manually
	  matching the get/put call stacks.  This feature consumes extra memory
	  in order to save the stack traces using STACKDEPOT.

endmenu
+1 −0
Original line number Diff line number Diff line
obj-y := dma-buf.o dma-fence.o dma-fence-array.o reservation.o seqno-fence.o
obj-$(CONFIG_SYNC_FILE)		+= sync_file.o
obj-$(CONFIG_SW_SYNC)		+= sw_sync.o sync_debug.o
obj-$(CONFIG_DEBUG_DMA_BUF_REF)	+= dma-buf-ref.o
+123 −0
Original line number Diff line number Diff line
/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/dma-buf.h>
#include <linux/slab.h>
#include <linux/stackdepot.h>
#include <linux/stacktrace.h>
#include <linux/seq_file.h>

#define DMA_BUF_STACK_DEPTH (16)

struct dma_buf_ref {
	struct list_head list;
	depot_stack_handle_t handle;
	int count;
};

void dma_buf_ref_init(struct dma_buf *dmabuf)
{
	INIT_LIST_HEAD(&dmabuf->refs);
}

void dma_buf_ref_destroy(struct dma_buf *dmabuf)
{
	struct dma_buf_ref *r, *n;

	mutex_lock(&dmabuf->lock);
	list_for_each_entry_safe(r, n, &dmabuf->refs, list) {
		list_del(&r->list);
		kfree(r);
	}
	mutex_unlock(&dmabuf->lock);
}

static void dma_buf_ref_insert_handle(struct dma_buf *dmabuf,
				      depot_stack_handle_t handle,
				      int count)
{
	struct dma_buf_ref *r;

	mutex_lock(&dmabuf->lock);
	list_for_each_entry(r, &dmabuf->refs, list) {
		if (r->handle == handle) {
			r->count += count;
			goto out;
		}
	}

	r = kzalloc(sizeof(*r), GFP_KERNEL);
	if (!r)
		goto out;

	INIT_LIST_HEAD(&r->list);
	r->handle = handle;
	r->count = count;
	list_add(&r->list, &dmabuf->refs);

out:
	mutex_unlock(&dmabuf->lock);
}

void dma_buf_ref_mod(struct dma_buf *dmabuf, int nr)
{
	unsigned long entries[DMA_BUF_STACK_DEPTH];
	struct stack_trace trace = {
		.nr_entries = 0,
		.entries = entries,
		.max_entries = DMA_BUF_STACK_DEPTH,
		.skip = 1
	};
	depot_stack_handle_t handle;

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

	handle = depot_save_stack(&trace, GFP_KERNEL);
	if (!handle)
		return;

	dma_buf_ref_insert_handle(dmabuf, handle, nr);
}

/**
 * Called with dmabuf->lock held
 */
int dma_buf_ref_show(struct seq_file *s, struct dma_buf *dmabuf)
{
	char *buf;
	struct dma_buf_ref *ref;
	int count = 0;
	struct stack_trace trace;

	buf = (void *)__get_free_page(GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	list_for_each_entry(ref, &dmabuf->refs, list) {
		count += ref->count;

		seq_printf(s, "References: %d\n", ref->count);
		depot_fetch_stack(ref->handle, &trace);
		snprint_stack_trace(buf, PAGE_SIZE, &trace, 0);
		seq_puts(s, buf);
		seq_putc(s, '\n');
	}

	seq_printf(s, "Total references: %d\n\n\n", count);
	free_page((unsigned long)buf);

	return 0;
}
+9 −0
Original line number Diff line number Diff line
@@ -93,6 +93,8 @@ static int dma_buf_release(struct inode *inode, struct file *file)
	list_del(&dmabuf->list_node);
	mutex_unlock(&db_list.lock);

	dma_buf_ref_destroy(dmabuf);

	if (dmabuf->resv == (struct reservation_object *)&dmabuf[1])
		reservation_object_fini(dmabuf->resv);

@@ -495,6 +497,9 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
	mutex_init(&dmabuf->lock);
	INIT_LIST_HEAD(&dmabuf->attachments);

	dma_buf_ref_init(dmabuf);
	dma_buf_ref_mod(dmabuf, 1);

	mutex_lock(&db_list.lock);
	list_add(&dmabuf->list_node, &db_list.head);
	mutex_unlock(&db_list.lock);
@@ -556,6 +561,7 @@ struct dma_buf *dma_buf_get(int fd)
		fput(file);
		return ERR_PTR(-EINVAL);
	}
	dma_buf_ref_mod(file->private_data, 1);

	return file->private_data;
}
@@ -576,6 +582,7 @@ void dma_buf_put(struct dma_buf *dmabuf)
	if (WARN_ON(!dmabuf || !dmabuf->file))
		return;

	dma_buf_ref_mod(dmabuf, -1);
	fput(dmabuf->file);
}
EXPORT_SYMBOL_GPL(dma_buf_put);
@@ -1266,6 +1273,8 @@ static int dma_buf_debug_show(struct seq_file *s, void *unused)
		seq_printf(s, "Total %d devices attached\n\n",
				attach_count);

		dma_buf_ref_show(s, buf_obj);

		count++;
		size += buf_obj->size;
		mutex_unlock(&buf_obj->lock);
+36 −0
Original line number Diff line number Diff line
/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#ifndef _DMA_BUF_REF_H
#define _DMA_BUF_REF_H

struct dma_buf;
struct seq_file;

#ifdef CONFIG_DEBUG_DMA_BUF_REF
void dma_buf_ref_init(struct dma_buf *b);
void dma_buf_ref_destroy(struct dma_buf *b);
void dma_buf_ref_mod(struct dma_buf *b, int nr);
int dma_buf_ref_show(struct seq_file *s, struct dma_buf *dmabuf);

#else
static inline void dma_buf_ref_init(struct dma_buf *b) {}
static inline void dma_buf_ref_destroy(struct dma_buf *b) {}
static inline void dma_buf_ref_mod(struct dma_buf *b, int nr) {}
static inline int dma_buf_ref_show(struct seq_file *s, struct dma_buf *dmabuf)
{
	return -ENOMEM;
}
#endif


#endif /* _DMA_BUF_REF_H */
Loading