[PATCH 1/4] media: Add a v4l2 memory allocations tracker

Detlev Casanova detlev.casanova at collabora.com
Wed Sep 16 07:25:26 PDT 2026


This is a wrapper around dma allocation functions to keep track of
allocated buffers and expose the list through a per v4l2 device debugfs.

This currently doesn't support all kind of memory allocation, only
dma_alloc_attrs.

The wrapper can therefore be used by drivers, but also by the vb2
allocation functions, so that all buffers are accounted for.

Signed-off-by: Detlev Casanova <detlev.casanova at collabora.com>
---
 drivers/media/common/videobuf2/Makefile            |   1 +
 drivers/media/common/videobuf2/v4l2-allocator.c    | 180 +++++++++++++++++++++
 .../media/common/videobuf2/videobuf2-dma-contig.c  |  24 ++-
 drivers/media/v4l2-core/v4l2-device.c              |   4 +-
 include/media/v4l2-allocator.h                     |  26 +++
 include/media/v4l2-device.h                        |   2 +
 include/media/videobuf2-core.h                     |   2 +
 7 files changed, 231 insertions(+), 8 deletions(-)

diff --git a/drivers/media/common/videobuf2/Makefile b/drivers/media/common/videobuf2/Makefile
index a6fe3f304685..aa2754731535 100644
--- a/drivers/media/common/videobuf2/Makefile
+++ b/drivers/media/common/videobuf2/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 videobuf2-common-objs := videobuf2-core.o
 videobuf2-common-objs += frame_vector.o
+videobuf2-common-objs += v4l2-allocator.o
 
 ifeq ($(CONFIG_TRACEPOINTS),y)
   videobuf2-common-objs += vb2-trace.o
diff --git a/drivers/media/common/videobuf2/v4l2-allocator.c b/drivers/media/common/videobuf2/v4l2-allocator.c
new file mode 100644
index 000000000000..9dde9d4a342b
--- /dev/null
+++ b/drivers/media/common/videobuf2/v4l2-allocator.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <media/v4l2-allocator.h>
+#include <media/v4l2-device.h>
+
+#include <linux/types.h>
+#include <linux/debugfs.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/seq_file.h>
+
+#define ENTRY_NAME_LEN	64
+
+static struct dentry *v4l2_debugfs_dir;
+
+struct v4l2_allocator {
+	struct list_head list;
+	struct mutex lock;
+	struct dentry *debugfs_dir;
+	struct dentry *debugfs_entry;
+};
+
+struct v4l2_allocator_entry {
+	struct list_head list;
+	size_t size;
+	dma_addr_t dma_addr;
+	char name[ENTRY_NAME_LEN];
+	char creator[TASK_COMM_LEN];
+	pid_t tgid;
+	u16 fd;
+};
+
+static int v4l2_allocator_debugfs_show(struct seq_file *m, void *data)
+{
+	struct v4l2_device *v4l2_dev = m->private;
+	struct v4l2_allocator *allocator = v4l2_dev->v4l2_allocator;
+	struct v4l2_allocator_entry *entry;
+	size_t total_size = 0;
+
+	if (!allocator)
+		return 0;
+
+	seq_puts(m, "created-by                      fd              pid             size            label\n");
+	seq_puts(m, "-------------------------------------------------------------------------------------\n");
+	mutex_lock(&allocator->lock);
+	list_for_each_entry(entry, &allocator->list, list) {
+		seq_printf(m, "%-32s%-16u%-16u%-16zu%s\n",
+			   entry->creator,
+			   entry->fd,
+			   entry->tgid,
+			   entry->size,
+			   entry->name);
+		total_size += entry->size;
+	}
+	mutex_unlock(&allocator->lock);
+
+	seq_puts(m, "=====================================================================================\n");
+	seq_printf(m, "Total size: %zu\n", total_size);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(v4l2_allocator_debugfs);
+
+int v4l2_allocator_init(struct v4l2_device *v4l2_dev)
+{
+	struct v4l2_allocator *allocator;
+
+	allocator = kzalloc_obj(*allocator);
+	if (!allocator)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&allocator->list);
+	mutex_init(&allocator->lock);
+	v4l2_dev->v4l2_allocator = allocator;
+
+	if (!v4l2_debugfs_dir)
+		v4l2_debugfs_dir = debugfs_create_dir("v4l2", NULL);
+
+	allocator->debugfs_dir = debugfs_create_dir(dev_name(v4l2_dev->dev),
+						    v4l2_debugfs_dir);
+	allocator->debugfs_entry = debugfs_create_file("mem", 0444,
+						       allocator->debugfs_dir,
+						       v4l2_dev,
+						       &v4l2_allocator_debugfs_fops);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_allocator_init);
+
+void v4l2_allocator_cleanup(struct v4l2_device *v4l2_dev)
+{
+	struct v4l2_allocator *allocator = v4l2_dev->v4l2_allocator;
+	struct v4l2_allocator_entry *entry, *tmp;
+
+	if (allocator) {
+		debugfs_remove(allocator->debugfs_entry);
+		debugfs_remove(allocator->debugfs_dir);
+		mutex_lock(&allocator->lock);
+		list_for_each_entry_safe(entry, tmp, &allocator->list, list) {
+			list_del(&entry->list);
+			kfree(entry);
+		}
+		mutex_unlock(&allocator->lock);
+		mutex_destroy(&allocator->lock);
+		kfree(allocator);
+		v4l2_dev->v4l2_allocator = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(v4l2_allocator_cleanup);
+
+static int v4l2_allocator_add(struct v4l2_device *v4l2_dev, size_t size,
+			      dma_addr_t dma_addr, const char *name)
+{
+	struct v4l2_allocator *allocator = v4l2_dev->v4l2_allocator;
+	struct v4l2_allocator_entry *entry;
+
+	if (!allocator)
+		return -EINVAL;
+
+	entry = kzalloc_obj(*entry);
+	if (!entry)
+		return -ENOMEM;
+
+	entry->size = size;
+	entry->dma_addr = dma_addr;
+	strscpy(entry->name, name, sizeof(entry->name));
+	get_task_comm(entry->creator, current->group_leader);
+	entry->tgid = current->tgid;
+
+	mutex_lock(&allocator->lock);
+	list_add(&entry->list, &allocator->list);
+	mutex_unlock(&allocator->lock);
+
+	return 0;
+}
+
+static void v4l2_allocator_remove(struct v4l2_device *v4l2_dev, size_t size, dma_addr_t dma_addr)
+{
+	struct v4l2_allocator *allocator = v4l2_dev->v4l2_allocator;
+	struct v4l2_allocator_entry *entry, *tmp;
+
+	if (!allocator)
+		return;
+
+	mutex_lock(&allocator->lock);
+	list_for_each_entry_safe(entry, tmp, &allocator->list, list) {
+		if (entry->size == size && entry->dma_addr == dma_addr) {
+			list_del(&entry->list);
+			mutex_unlock(&allocator->lock);
+			kfree(entry);
+			return;
+		}
+	}
+	mutex_unlock(&allocator->lock);
+}
+
+void *v4l2_dma_alloc_attrs(struct v4l2_device *v4l2_dev, struct device *dev,
+			   size_t size, dma_addr_t *dma_handle,
+			   gfp_t flag, unsigned long attrs, const char *name)
+{
+	void *ret = dma_alloc_attrs(dev, size, dma_handle, flag, attrs);
+
+	if (ret && v4l2_dev)
+		v4l2_allocator_add(v4l2_dev, size, *dma_handle, name);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(v4l2_dma_alloc_attrs);
+
+void v4l2_dma_free_attrs(struct v4l2_device *v4l2_dev, struct device *dev,
+			 size_t size, void *cpu_addr,
+			 dma_addr_t dma_handle, unsigned long attrs)
+{
+	if (v4l2_dev)
+		v4l2_allocator_remove(v4l2_dev, size, dma_handle);
+
+	dma_free_attrs(dev, size, cpu_addr, dma_handle, attrs);
+}
+EXPORT_SYMBOL_GPL(v4l2_dma_free_attrs);
+
diff --git a/drivers/media/common/videobuf2/videobuf2-dma-contig.c b/drivers/media/common/videobuf2/videobuf2-dma-contig.c
index 9ce6284cd5f2..b6b7ddc96b9c 100644
--- a/drivers/media/common/videobuf2/videobuf2-dma-contig.c
+++ b/drivers/media/common/videobuf2/videobuf2-dma-contig.c
@@ -22,6 +22,7 @@
 #include <media/videobuf2-v4l2.h>
 #include <media/videobuf2-dma-contig.h>
 #include <media/videobuf2-memops.h>
+#include <media/v4l2-allocator.h>
 
 struct vb2_dc_buf {
 	struct device			*dev;
@@ -43,6 +44,7 @@ struct vb2_dc_buf {
 	struct dma_buf_attachment	*db_attach;
 
 	struct vb2_buffer		*vb;
+	struct v4l2_device		*v4l2_dev;
 	bool				non_coherent_mem;
 };
 
@@ -181,8 +183,10 @@ static void vb2_dc_put(void *buf_priv)
 			sg_free_table(buf->sgt_base);
 			kfree(buf->sgt_base);
 		}
-		dma_free_attrs(buf->dev, buf->size, buf->cookie,
-			       buf->dma_addr, buf->attrs);
+
+		v4l2_dma_free_attrs(buf->v4l2_dev, buf->dev, buf->size, buf->cookie,
+				    buf->dma_addr, buf->attrs);
+
 	}
 	put_device(buf->dev);
 	kfree(buf);
@@ -191,12 +195,17 @@ static void vb2_dc_put(void *buf_priv)
 static int vb2_dc_alloc_coherent(struct vb2_dc_buf *buf)
 {
 	struct vb2_queue *q = buf->vb->vb2_queue;
+	char name[64] = {0};
+
+	sprintf(name, "%s-%d", q->name, buf->vb->index);
 
-	buf->cookie = dma_alloc_attrs(buf->dev,
-				      buf->size,
-				      &buf->dma_addr,
-				      GFP_KERNEL | q->gfp_flags,
-				      buf->attrs);
+	buf->cookie = v4l2_dma_alloc_attrs(buf->v4l2_dev,
+					   buf->dev,
+					   buf->size,
+					   &buf->dma_addr,
+					   GFP_KERNEL | q->gfp_flags,
+					   buf->attrs,
+					   name);
 	if (!buf->cookie)
 		return -ENOMEM;
 
@@ -246,6 +255,7 @@ static void *vb2_dc_alloc(struct vb2_buffer *vb,
 	buf->dma_dir = vb->vb2_queue->dma_dir;
 	buf->vb = vb;
 	buf->non_coherent_mem = vb->vb2_queue->non_coherent_mem;
+	buf->v4l2_dev = vb->vb2_queue->v4l2_dev;
 
 	buf->size = size;
 	/* Prevent the device from being released while the buffer is used */
diff --git a/drivers/media/v4l2-core/v4l2-device.c b/drivers/media/v4l2-core/v4l2-device.c
index 67e3073de132..3cce2b4bc4c7 100644
--- a/drivers/media/v4l2-core/v4l2-device.c
+++ b/drivers/media/v4l2-core/v4l2-device.c
@@ -13,6 +13,7 @@
 #include <linux/videodev2.h>
 #include <media/v4l2-device.h>
 #include <media/v4l2-ctrls.h>
+#include <media/v4l2-allocator.h>
 
 int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
 {
@@ -38,7 +39,7 @@ int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
 			dev->driver->name, dev_name(dev));
 	if (!dev_get_drvdata(dev))
 		dev_set_drvdata(dev, v4l2_dev);
-	return 0;
+	return v4l2_allocator_init(v4l2_dev);
 }
 EXPORT_SYMBOL_GPL(v4l2_device_register);
 
@@ -93,6 +94,7 @@ void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
 	 * unregistered before. */
 	if (v4l2_dev == NULL || !v4l2_dev->name[0])
 		return;
+	v4l2_allocator_cleanup(v4l2_dev);
 	v4l2_device_disconnect(v4l2_dev);
 
 	/* Unregister subdevs */
diff --git a/include/media/v4l2-allocator.h b/include/media/v4l2-allocator.h
new file mode 100644
index 000000000000..f3fe0da10321
--- /dev/null
+++ b/include/media/v4l2-allocator.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * V4L2 Memory Allocator - Track memory allocations and deallocations in the v4l2 device.
+ *
+ * It provides functions to allocate and free memory while keeping
+ * track of the allocations for debugging and analysis purposes.
+ *
+ * Copyright 2026 Collabora, Ltd.
+ *   Detlev Casanova <detlev.casanova at collabora.com>
+ */
+
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+
+#include <media/v4l2-device.h>
+
+int v4l2_allocator_init(struct v4l2_device *v4l2_dev);
+void v4l2_allocator_cleanup(struct v4l2_device *v4l2_dev);
+
+void *v4l2_dma_alloc_attrs(struct v4l2_device *v4l2_dev, struct device *dev,
+			   size_t size, dma_addr_t *dma_handle,
+			   gfp_t flag, unsigned long attrs, const char *name);
+
+void v4l2_dma_free_attrs(struct v4l2_device *v4l2_dev, struct device *dev,
+			 size_t size, void *cpu_addr,
+			 dma_addr_t dma_handle, unsigned long attrs);
diff --git a/include/media/v4l2-device.h b/include/media/v4l2-device.h
index 25f69b1b8db0..c2b882d32012 100644
--- a/include/media/v4l2-device.h
+++ b/include/media/v4l2-device.h
@@ -14,6 +14,7 @@
 #include <media/v4l2-dev.h>
 
 struct v4l2_ctrl_handler;
+struct v4l2_allocator;
 
 /**
  * struct v4l2_device - main struct to for V4L2 device drivers
@@ -46,6 +47,7 @@ struct v4l2_device {
 	struct device *dev;
 	struct media_device *mdev;
 	struct list_head subdevs;
+	struct v4l2_allocator *v4l2_allocator;
 	spinlock_t lock;
 	char name[36];
 	void (*notify)(struct v4l2_subdev *sd,
diff --git a/include/media/videobuf2-core.h b/include/media/videobuf2-core.h
index 4424d481d7f7..83a5aea34f3c 100644
--- a/include/media/videobuf2-core.h
+++ b/include/media/videobuf2-core.h
@@ -19,6 +19,7 @@
 #include <linux/bitops.h>
 #include <media/media-request.h>
 #include <media/frame_vector.h>
+#include <media/v4l2-device.h>
 
 #define VB2_MAX_FRAME	(32)
 #define VB2_MAX_PLANES	(8)
@@ -602,6 +603,7 @@ struct vb2_queue {
 	unsigned int			type;
 	unsigned int			io_modes;
 	struct device			*dev;
+	struct v4l2_device		*v4l2_dev;
 	unsigned long			dma_attrs;
 	unsigned int			bidirectional:1;
 	unsigned int			fileio_read_once:1;

-- 
2.55.0




More information about the linux-arm-kernel mailing list