All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1 0/9] drm/panthor: Add a GEM shrinker
@ 2026-01-09 13:07 Boris Brezillon
  2026-01-09 13:07 ` [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails Boris Brezillon
                   ` (9 more replies)
  0 siblings, 10 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:07 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

Hello,

This is an attempt at adding a GEM shrinker to panthor so the system
can finally reclaim GPU memory.

This implementation is losely based on the MSM shrinker (which is why
I added the MSM maintainers in Cc), and it's relying on the drm_gpuvm
eviction/validation infrastructure.

I've only done very basic IGT-based [1] and chromium-based (opening
a lot of tabs on Aquarium until the system starts reclaiming+swaping
out GPU buffers) testing, but I'm posting this early so I can get
preliminary feedback on the implementation. If someone knows about
better tools/ways to test the shrinker, please let me know.

A few words about some design/implementation choices:
- No MADVISE support because I want to see if we can live with just
  transparent reclaim
- We considered basing this implementation on the generic shrinker work
  started by Dmitry [2], but
  1. with the activeness/idleness tracking happening at the VM
     granularity, having per-BO LRUs would caused a lot of
     list_move()s that are not really needed (the VM as a whole
     become active/idle, we can track individual BOs)
  2. Thomas Zimmermann recently suggested that we should have our
     own GEM implementation instead of trying to add this extra reclaim
     complexity to gem-shmem. There are some plans to create a
     gem-uma (Unified Memory Architecture) lib that would do more
     than gem-shmem but in a way that doesn't force all its users
     to pay the overhead (size overhead of the gem object, mostly)
     for features they don't use. Patch "Part ways with
     drm_gem_shmem_object" is showing what this component-based lib
     API could look like if it were to be extracted
- At the moment we only support swapout, but we could add an
  extra flag to specify when buffer content doesn't need to be
  preserved to avoid the swapout/swapin dance. First candidate for
  this DISCARD_ON_RECLAIM flag would probably be the tiler heap chunks.
- Reclaim uses _try_lock() all the way because of the various lock order
  inversions between the reclaim path and submission paths. That means
  we don't try very hard to reclaim hot GPU buffers, but the locking is
  such a mess that I don't really see a better option to be honest.

Regards,

Boris

[1]https://gitlab.freedesktop.org/bbrezillon/igt-gpu-tools/-/commit/fc76934a5579767d2aabe787d40e38a17c3f4ea4
[2]https://lkml.org/lkml/2024/1/5/665

Akash Goel (1):
  drm/panthor: Add a GEM shrinker

Boris Brezillon (8):
  drm/gem: Consider GEM object reclaimable if shrinking fails
  drm/gpuvm: Validate BOs in the extobj list when VM is resv protected
  drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c
  drm/panthor: Group panthor_kernel_bo_xxx() helpers
  drm/panthor: Part ways with drm_gem_shmem_object
  drm/panthor: Lazily allocate pages on mmap()
  drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for
    reclaim
  drm/panthor: Track the number of mmap on a BO

 drivers/gpu/drm/drm_gem.c                |   10 +
 drivers/gpu/drm/drm_gpuvm.c              |   23 +-
 drivers/gpu/drm/panthor/Kconfig          |    1 -
 drivers/gpu/drm/panthor/panthor_device.c |   11 +-
 drivers/gpu/drm/panthor/panthor_device.h |   73 ++
 drivers/gpu/drm/panthor/panthor_drv.c    |   33 +-
 drivers/gpu/drm/panthor/panthor_fw.c     |   16 +-
 drivers/gpu/drm/panthor/panthor_gem.c    | 1387 ++++++++++++++++++----
 drivers/gpu/drm/panthor/panthor_gem.h    |  135 ++-
 drivers/gpu/drm/panthor/panthor_mmu.c    |  451 +++++--
 drivers/gpu/drm/panthor/panthor_mmu.h    |    8 +
 drivers/gpu/drm/panthor/panthor_sched.c  |    9 +-
 include/drm/drm_gpuvm.h                  |    6 +
 13 files changed, 1829 insertions(+), 334 deletions(-)

-- 
2.52.0


^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
@ 2026-01-09 13:07 ` Boris Brezillon
  2026-01-12  9:25   ` Alice Ryhl
  2026-01-15 13:28   ` Liviu Dudau
  2026-01-09 13:07 ` [PATCH v1 2/9] drm/gpuvm: Validate BOs in the extobj list when VM is resv protected Boris Brezillon
                   ` (8 subsequent siblings)
  9 siblings, 2 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:07 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

If the object wasn't moved to a different LRU after the shrink callback
is called, it means the buffer is still reclaimable. Update the remaining
counter to reflect that.

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
---
 drivers/gpu/drm/drm_gem.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
index f7cbf6e8d1e0..442853511106 100644
--- a/drivers/gpu/drm/drm_gem.c
+++ b/drivers/gpu/drm/drm_gem.c
@@ -1671,6 +1671,16 @@ drm_gem_lru_scan(struct drm_gem_lru *lru,
 			 */
 			WARN_ON(obj->lru == &still_in_lru);
 			WARN_ON(obj->lru == lru);
+		} else if (obj->lru == &still_in_lru) {
+			/*
+			 * If the object wasn't moved and wasn't shrunk either,
+			 * it's still remaining as reclaimable. Note that
+			 * obj->lru is supposed to be checked with the LRU lock
+			 * held for an accurate result, but we don't care about
+			 * accuracy here. Worst thing that could happen is an
+			 * extra scan.
+			 */
+			*remaining += obj->size >> PAGE_SHIFT;
 		}
 
 		dma_resv_unlock(obj->resv);
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v1 2/9] drm/gpuvm: Validate BOs in the extobj list when VM is resv protected
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
  2026-01-09 13:07 ` [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails Boris Brezillon
@ 2026-01-09 13:07 ` Boris Brezillon
  2026-01-09 19:38   ` Danilo Krummrich
  2026-01-09 13:07 ` [PATCH v1 3/9] drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c Boris Brezillon
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:07 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

When the VM is protected with the root object resv, evicted extobjs
are not inserted in the evict list. Make sure we record when the extobj
list contains evicted BOs that are not already in the evict list so we
can validate those when drm_gpuvm_validate() is called.

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 23 ++++++++++++++++++++++-
 include/drm/drm_gpuvm.h     |  6 ++++++
 2 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 0bb115b6b59c..95f0a1c843ad 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -1505,6 +1505,22 @@ drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
 			drm_gpuvm_bo_list_del_init(vm_bo, evict, false);
 	}
 
+	if (READ_ONCE(gpuvm->extobj.check_evicted)) {
+		list_for_each_entry_safe(vm_bo, next, &gpuvm->extobj.list,
+					 list.entry.extobj) {
+			dma_resv_assert_held(vm_bo->obj->resv);
+
+			if (!vm_bo->evicted)
+				continue;
+
+			ret = ops->vm_bo_validate(vm_bo, exec);
+			if (ret)
+				break;
+		}
+
+		WRITE_ONCE(gpuvm->extobj.check_evicted, false);
+	}
+
 	return ret;
 }
 
@@ -1940,9 +1956,14 @@ drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
 	/* Can't add external objects to the evicted list directly if not using
 	 * internal spinlocks, since in this case the evicted list is protected
 	 * with the VM's common dma-resv lock.
+	 * In that case, we flag the extobj list has containing evicted objects,
+	 * which will force a scan on the next drm_gpuvm_validate_locked()
+	 * call.
 	 */
-	if (drm_gpuvm_is_extobj(gpuvm, obj) && !lock)
+	if (drm_gpuvm_is_extobj(gpuvm, obj) && !lock) {
+		WRITE_ONCE(gpuvm->extobj.check_evicted, true);
 		return;
+	}
 
 	if (evict)
 		drm_gpuvm_bo_list_add(vm_bo, evict, lock);
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 655bd9104ffb..6061c361dc90 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -310,6 +310,12 @@ struct drm_gpuvm {
 		 * @extobj.lock: spinlock to protect the extobj list
 		 */
 		spinlock_t lock;
+
+		/**
+		 * @extobj.check_evicted: true if the list contains evicted BOs
+		 * that are not already in the evict list
+		 */
+		bool check_evicted;
 	} extobj;
 
 	/**
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v1 3/9] drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
  2026-01-09 13:07 ` [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails Boris Brezillon
  2026-01-09 13:07 ` [PATCH v1 2/9] drm/gpuvm: Validate BOs in the extobj list when VM is resv protected Boris Brezillon
@ 2026-01-09 13:07 ` Boris Brezillon
  2026-01-12 11:27   ` Steven Price
  2026-01-15 13:39   ` Liviu Dudau
  2026-01-09 13:07 ` [PATCH v1 4/9] drm/panthor: Group panthor_kernel_bo_xxx() helpers Boris Brezillon
                   ` (6 subsequent siblings)
  9 siblings, 2 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:07 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

There's no reason for panthor_drv to know about panthor_gem.c internals,
so let's move the GEM debugfs init logic to panthor_gem.c.

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
---
 drivers/gpu/drm/panthor/panthor_drv.c | 26 +-----------------------
 drivers/gpu/drm/panthor/panthor_gem.c | 29 +++++++++++++++++++++++++--
 drivers/gpu/drm/panthor/panthor_gem.h |  3 +--
 3 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
index 165dddfde6ca..52c27a60c84a 100644
--- a/drivers/gpu/drm/panthor/panthor_drv.c
+++ b/drivers/gpu/drm/panthor/panthor_drv.c
@@ -1635,34 +1635,10 @@ static const struct file_operations panthor_drm_driver_fops = {
 };
 
 #ifdef CONFIG_DEBUG_FS
-static int panthor_gems_show(struct seq_file *m, void *data)
-{
-	struct drm_info_node *node = m->private;
-	struct drm_device *dev = node->minor->dev;
-	struct panthor_device *ptdev = container_of(dev, struct panthor_device, base);
-
-	panthor_gem_debugfs_print_bos(ptdev, m);
-
-	return 0;
-}
-
-static struct drm_info_list panthor_debugfs_list[] = {
-	{"gems", panthor_gems_show, 0, NULL},
-};
-
-static int panthor_gems_debugfs_init(struct drm_minor *minor)
-{
-	drm_debugfs_create_files(panthor_debugfs_list,
-				 ARRAY_SIZE(panthor_debugfs_list),
-				 minor->debugfs_root, minor);
-
-	return 0;
-}
-
 static void panthor_debugfs_init(struct drm_minor *minor)
 {
 	panthor_mmu_debugfs_init(minor);
-	panthor_gems_debugfs_init(minor);
+	panthor_gem_debugfs_init(minor);
 }
 #endif
 
diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
index b61908fd508a..13e9dd3764fa 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.c
+++ b/drivers/gpu/drm/panthor/panthor_gem.c
@@ -9,6 +9,8 @@
 #include <linux/err.h>
 #include <linux/slab.h>
 
+#include <drm/drm_debugfs.h>
+#include <drm/drm_file.h>
 #include <drm/drm_print.h>
 #include <drm/panthor_drm.h>
 
@@ -683,8 +685,8 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
 		totals->reclaimable += resident_size;
 }
 
-void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
-				   struct seq_file *m)
+static void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
+					  struct seq_file *m)
 {
 	struct gem_size_totals totals = {0};
 	struct panthor_gem_object *bo;
@@ -704,4 +706,27 @@ void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
 	seq_printf(m, "Total size: %zd, Total resident: %zd, Total reclaimable: %zd\n",
 		   totals.size, totals.resident, totals.reclaimable);
 }
+
+static int panthor_gem_show_bos(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct panthor_device *ptdev =
+		container_of(dev, struct panthor_device, base);
+
+	panthor_gem_debugfs_print_bos(ptdev, m);
+
+	return 0;
+}
+
+static struct drm_info_list panthor_gem_debugfs_list[] = {
+	{ "gems", panthor_gem_show_bos, 0, NULL },
+};
+
+void panthor_gem_debugfs_init(struct drm_minor *minor)
+{
+	drm_debugfs_create_files(panthor_gem_debugfs_list,
+				 ARRAY_SIZE(panthor_gem_debugfs_list),
+				 minor->debugfs_root, minor);
+}
 #endif
diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
index 22519c570b5a..94b2d17cf032 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.h
+++ b/drivers/gpu/drm/panthor/panthor_gem.h
@@ -203,8 +203,7 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
 void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo);
 
 #ifdef CONFIG_DEBUG_FS
-void panthor_gem_debugfs_print_bos(struct panthor_device *pfdev,
-				   struct seq_file *m);
+void panthor_gem_debugfs_init(struct drm_minor *minor);
 #endif
 
 #endif /* __PANTHOR_GEM_H__ */
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v1 4/9] drm/panthor: Group panthor_kernel_bo_xxx() helpers
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
                   ` (2 preceding siblings ...)
  2026-01-09 13:07 ` [PATCH v1 3/9] drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c Boris Brezillon
@ 2026-01-09 13:07 ` Boris Brezillon
  2026-01-12 11:29   ` Steven Price
  2026-01-15 13:41   ` Liviu Dudau
  2026-01-09 13:07 ` [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object Boris Brezillon
                   ` (5 subsequent siblings)
  9 siblings, 2 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:07 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

Move all panthor_kernel_bo_xxx() helpers at the end of the file, just
before the debugfs init logic. This will make further panthor_gem.c
refactoring more readable.

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
---
 drivers/gpu/drm/panthor/panthor_gem.c | 212 +++++++++++++-------------
 1 file changed, 106 insertions(+), 106 deletions(-)

diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
index 13e9dd3764fa..4b3d82f001d8 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.c
+++ b/drivers/gpu/drm/panthor/panthor_gem.c
@@ -132,112 +132,6 @@ static void panthor_gem_free_object(struct drm_gem_object *obj)
 	drm_gem_object_put(vm_root_gem);
 }
 
-/**
- * panthor_kernel_bo_destroy() - Destroy a kernel buffer object
- * @bo: Kernel buffer object to destroy. If NULL or an ERR_PTR(), the destruction
- * is skipped.
- */
-void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
-{
-	struct panthor_vm *vm;
-
-	if (IS_ERR_OR_NULL(bo))
-		return;
-
-	vm = bo->vm;
-	panthor_kernel_bo_vunmap(bo);
-
-	drm_WARN_ON(bo->obj->dev,
-		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
-	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
-	panthor_vm_free_va(vm, &bo->va_node);
-	drm_gem_object_put(bo->obj);
-	panthor_vm_put(vm);
-	kfree(bo);
-}
-
-/**
- * panthor_kernel_bo_create() - Create and map a GEM object to a VM
- * @ptdev: Device.
- * @vm: VM to map the GEM to. If NULL, the kernel object is not GPU mapped.
- * @size: Size of the buffer object.
- * @bo_flags: Combination of drm_panthor_bo_flags flags.
- * @vm_map_flags: Combination of drm_panthor_vm_bind_op_flags (only those
- * that are related to map operations).
- * @gpu_va: GPU address assigned when mapping to the VM.
- * If gpu_va == PANTHOR_VM_KERNEL_AUTO_VA, the virtual address will be
- * automatically allocated.
- * @name: Descriptive label of the BO's contents
- *
- * Return: A valid pointer in case of success, an ERR_PTR() otherwise.
- */
-struct panthor_kernel_bo *
-panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
-			 size_t size, u32 bo_flags, u32 vm_map_flags,
-			 u64 gpu_va, const char *name)
-{
-	struct drm_gem_shmem_object *obj;
-	struct panthor_kernel_bo *kbo;
-	struct panthor_gem_object *bo;
-	u32 debug_flags = PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL;
-	int ret;
-
-	if (drm_WARN_ON(&ptdev->base, !vm))
-		return ERR_PTR(-EINVAL);
-
-	kbo = kzalloc(sizeof(*kbo), GFP_KERNEL);
-	if (!kbo)
-		return ERR_PTR(-ENOMEM);
-
-	obj = drm_gem_shmem_create(&ptdev->base, size);
-	if (IS_ERR(obj)) {
-		ret = PTR_ERR(obj);
-		goto err_free_bo;
-	}
-
-	bo = to_panthor_bo(&obj->base);
-	kbo->obj = &obj->base;
-	bo->flags = bo_flags;
-	bo->base.map_wc = should_map_wc(bo, vm);
-	bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm);
-	drm_gem_object_get(bo->exclusive_vm_root_gem);
-	bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
-
-	if (vm == panthor_fw_vm(ptdev))
-		debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED;
-
-	panthor_gem_kernel_bo_set_label(kbo, name);
-	panthor_gem_debugfs_set_usage_flags(to_panthor_bo(kbo->obj), debug_flags);
-
-	/* The system and GPU MMU page size might differ, which becomes a
-	 * problem for FW sections that need to be mapped at explicit address
-	 * since our PAGE_SIZE alignment might cover a VA range that's
-	 * expected to be used for another section.
-	 * Make sure we never map more than we need.
-	 */
-	size = ALIGN(size, panthor_vm_page_size(vm));
-	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
-	if (ret)
-		goto err_put_obj;
-
-	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
-	if (ret)
-		goto err_free_va;
-
-	kbo->vm = panthor_vm_get(vm);
-	return kbo;
-
-err_free_va:
-	panthor_vm_free_va(vm, &kbo->va_node);
-
-err_put_obj:
-	drm_gem_object_put(&obj->base);
-
-err_free_bo:
-	kfree(kbo);
-	return ERR_PTR(ret);
-}
-
 static struct sg_table *
 panthor_gem_prime_map_dma_buf(struct dma_buf_attachment *attach,
 			      enum dma_data_direction dir)
@@ -603,6 +497,112 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
 	return 0;
 }
 
+/**
+ * panthor_kernel_bo_destroy() - Destroy a kernel buffer object
+ * @bo: Kernel buffer object to destroy. If NULL or an ERR_PTR(), the destruction
+ * is skipped.
+ */
+void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
+{
+	struct panthor_vm *vm;
+
+	if (IS_ERR_OR_NULL(bo))
+		return;
+
+	vm = bo->vm;
+	panthor_kernel_bo_vunmap(bo);
+
+	drm_WARN_ON(bo->obj->dev,
+		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
+	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
+	panthor_vm_free_va(vm, &bo->va_node);
+	drm_gem_object_put(bo->obj);
+	panthor_vm_put(vm);
+	kfree(bo);
+}
+
+/**
+ * panthor_kernel_bo_create() - Create and map a GEM object to a VM
+ * @ptdev: Device.
+ * @vm: VM to map the GEM to. If NULL, the kernel object is not GPU mapped.
+ * @size: Size of the buffer object.
+ * @bo_flags: Combination of drm_panthor_bo_flags flags.
+ * @vm_map_flags: Combination of drm_panthor_vm_bind_op_flags (only those
+ * that are related to map operations).
+ * @gpu_va: GPU address assigned when mapping to the VM.
+ * If gpu_va == PANTHOR_VM_KERNEL_AUTO_VA, the virtual address will be
+ * automatically allocated.
+ * @name: Descriptive label of the BO's contents
+ *
+ * Return: A valid pointer in case of success, an ERR_PTR() otherwise.
+ */
+struct panthor_kernel_bo *
+panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
+			 size_t size, u32 bo_flags, u32 vm_map_flags,
+			 u64 gpu_va, const char *name)
+{
+	struct drm_gem_shmem_object *obj;
+	struct panthor_kernel_bo *kbo;
+	struct panthor_gem_object *bo;
+	u32 debug_flags = PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL;
+	int ret;
+
+	if (drm_WARN_ON(&ptdev->base, !vm))
+		return ERR_PTR(-EINVAL);
+
+	kbo = kzalloc(sizeof(*kbo), GFP_KERNEL);
+	if (!kbo)
+		return ERR_PTR(-ENOMEM);
+
+	obj = drm_gem_shmem_create(&ptdev->base, size);
+	if (IS_ERR(obj)) {
+		ret = PTR_ERR(obj);
+		goto err_free_bo;
+	}
+
+	bo = to_panthor_bo(&obj->base);
+	kbo->obj = &obj->base;
+	bo->flags = bo_flags;
+	bo->base.map_wc = should_map_wc(bo, vm);
+	bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm);
+	drm_gem_object_get(bo->exclusive_vm_root_gem);
+	bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
+
+	if (vm == panthor_fw_vm(ptdev))
+		debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED;
+
+	panthor_gem_kernel_bo_set_label(kbo, name);
+	panthor_gem_debugfs_set_usage_flags(to_panthor_bo(kbo->obj), debug_flags);
+
+	/* The system and GPU MMU page size might differ, which becomes a
+	 * problem for FW sections that need to be mapped at explicit address
+	 * since our PAGE_SIZE alignment might cover a VA range that's
+	 * expected to be used for another section.
+	 * Make sure we never map more than we need.
+	 */
+	size = ALIGN(size, panthor_vm_page_size(vm));
+	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
+	if (ret)
+		goto err_put_obj;
+
+	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
+	if (ret)
+		goto err_free_va;
+
+	kbo->vm = panthor_vm_get(vm);
+	return kbo;
+
+err_free_va:
+	panthor_vm_free_va(vm, &kbo->va_node);
+
+err_put_obj:
+	drm_gem_object_put(&obj->base);
+
+err_free_bo:
+	kfree(kbo);
+	return ERR_PTR(ret);
+}
+
 #ifdef CONFIG_DEBUG_FS
 struct gem_size_totals {
 	size_t size;
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
                   ` (3 preceding siblings ...)
  2026-01-09 13:07 ` [PATCH v1 4/9] drm/panthor: Group panthor_kernel_bo_xxx() helpers Boris Brezillon
@ 2026-01-09 13:07 ` Boris Brezillon
  2026-01-12 12:06   ` Steven Price
  2026-01-15 16:51   ` Liviu Dudau
  2026-01-09 13:07 ` [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap() Boris Brezillon
                   ` (4 subsequent siblings)
  9 siblings, 2 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:07 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

While drm_gem_shmem_object does most of the job we need it to do, the
way sub-resources (pages, sgt, vmap) are handled and their lifetimes
gets in the way of BO reclaim. There has been attempts to address
that [1], but in the meantime, new gem_shmem users were introduced
(accel drivers), and some of them manually free some of these resources.
This makes things harder to control/sanitize/validate.

Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
and forcing gem_shmem users to go through new gem_shmem helpers when they
need manual control of some sort, and I believe this is a dead end if
we don't force users to follow some stricter rules through carefully
designed helpers, because there will always be one user doing crazy things
with gem_shmem_object internals, which ends up tripping out the common
helpers when they are called.

The consensus we reached was that we would be better off forking
gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
current transition tries to minimize the changes, but there are still
some aspects that are different, the main one being that we no longer
have a pages_use_count, and pages stays around until the GEM object is
destroyed (or when evicted once we've added a shrinker). The sgt also
no longer retains pages. This is losely based on how msm does things by
the way.

If there's any interest in sharing code (probably with msm, since the
panthor shrinker is going to be losely based on the msm implementation),
we can always change gears and do that once we have everything
working/merged.

[1]https://patchwork.kernel.org/project/dri-devel/patch/20240105184624.508603-1-dmitry.osipenko@collabora.com/

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
---
 drivers/gpu/drm/panthor/Kconfig         |   1 -
 drivers/gpu/drm/panthor/panthor_drv.c   |   7 +-
 drivers/gpu/drm/panthor/panthor_fw.c    |  16 +-
 drivers/gpu/drm/panthor/panthor_gem.c   | 696 ++++++++++++++++++++----
 drivers/gpu/drm/panthor/panthor_gem.h   |  62 ++-
 drivers/gpu/drm/panthor/panthor_mmu.c   |  49 +-
 drivers/gpu/drm/panthor/panthor_sched.c |   9 +-
 7 files changed, 666 insertions(+), 174 deletions(-)

diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig
index 55b40ad07f3b..911e7f4810c3 100644
--- a/drivers/gpu/drm/panthor/Kconfig
+++ b/drivers/gpu/drm/panthor/Kconfig
@@ -8,7 +8,6 @@ config DRM_PANTHOR
 	depends on MMU
 	select DEVFREQ_GOV_SIMPLE_ONDEMAND
 	select DRM_EXEC
-	select DRM_GEM_SHMEM_HELPER
 	select DRM_GPUVM
 	select DRM_SCHED
 	select IOMMU_IO_PGTABLE_LPAE
diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
index 52c27a60c84a..90e9abc22d9e 100644
--- a/drivers/gpu/drm/panthor/panthor_drv.c
+++ b/drivers/gpu/drm/panthor/panthor_drv.c
@@ -19,6 +19,7 @@
 #include <drm/drm_debugfs.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_exec.h>
+#include <drm/drm_file.h>
 #include <drm/drm_ioctl.h>
 #include <drm/drm_print.h>
 #include <drm/drm_syncobj.h>
@@ -1457,7 +1458,7 @@ static int panthor_ioctl_bo_query_info(struct drm_device *ddev, void *data,
 	args->create_flags = bo->flags;
 
 	args->extra_flags = 0;
-	if (drm_gem_is_imported(&bo->base.base))
+	if (drm_gem_is_imported(&bo->base))
 		args->extra_flags |= DRM_PANTHOR_BO_IS_IMPORTED;
 
 	drm_gem_object_put(obj);
@@ -1671,8 +1672,7 @@ static const struct drm_driver panthor_drm_driver = {
 	.major = 1,
 	.minor = 7,
 
-	.gem_create_object = panthor_gem_create_object,
-	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
+	.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
 	.gem_prime_import = panthor_gem_prime_import,
 #ifdef CONFIG_DEBUG_FS
 	.debugfs_init = panthor_debugfs_init,
@@ -1822,3 +1822,4 @@ module_exit(panthor_exit);
 MODULE_AUTHOR("Panthor Project Developers");
 MODULE_DESCRIPTION("Panthor DRM Driver");
 MODULE_LICENSE("Dual MIT/GPL");
+MODULE_IMPORT_NS("DMA_BUF");
diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
index a64ec8756bed..f135cf2130b8 100644
--- a/drivers/gpu/drm/panthor/panthor_fw.c
+++ b/drivers/gpu/drm/panthor/panthor_fw.c
@@ -627,7 +627,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
 		u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK;
 		struct panthor_gem_object *bo;
 		u32 vm_map_flags = 0;
-		struct sg_table *sgt;
 		u64 va = hdr.va.start;
 
 		if (!(hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_WR))
@@ -665,11 +664,12 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
 		panthor_fw_init_section_mem(ptdev, section);
 
 		bo = to_panthor_bo(section->mem->obj);
-		sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
-		if (IS_ERR(sgt))
-			return PTR_ERR(sgt);
 
-		dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
+		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
+		if (drm_WARN_ON_ONCE(&ptdev->base, !bo->dmap.sgt))
+			return -EINVAL;
+
+		dma_sync_sgtable_for_device(ptdev->base.dev, bo->dmap.sgt, DMA_TO_DEVICE);
 	}
 
 	if (hdr.va.start == CSF_MCU_SHARED_REGION_START)
@@ -729,8 +729,10 @@ panthor_reload_fw_sections(struct panthor_device *ptdev, bool full_reload)
 			continue;
 
 		panthor_fw_init_section_mem(ptdev, section);
-		sgt = drm_gem_shmem_get_pages_sgt(&to_panthor_bo(section->mem->obj)->base);
-		if (!drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(sgt)))
+
+		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
+		sgt = to_panthor_bo(section->mem->obj)->dmap.sgt;
+		if (!drm_WARN_ON_ONCE(&ptdev->base, !sgt))
 			dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
 	}
 }
diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
index 4b3d82f001d8..0e52c7a07c87 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.c
+++ b/drivers/gpu/drm/panthor/panthor_gem.c
@@ -8,9 +8,11 @@
 #include <linux/dma-mapping.h>
 #include <linux/err.h>
 #include <linux/slab.h>
+#include <linux/vmalloc.h>
 
 #include <drm/drm_debugfs.h>
 #include <drm/drm_file.h>
+#include <drm/drm_prime.h>
 #include <drm/drm_print.h>
 #include <drm/panthor_drm.h>
 
@@ -44,7 +46,7 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo)
 
 static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
 {
-	struct panthor_device *ptdev = container_of(bo->base.base.dev,
+	struct panthor_device *ptdev = container_of(bo->base.dev,
 						    struct panthor_device, base);
 
 	bo->debugfs.creator.tgid = current->group_leader->pid;
@@ -57,7 +59,7 @@ static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
 
 static void panthor_gem_debugfs_bo_rm(struct panthor_gem_object *bo)
 {
-	struct panthor_device *ptdev = container_of(bo->base.base.dev,
+	struct panthor_device *ptdev = container_of(bo->base.dev,
 						    struct panthor_device, base);
 
 	if (list_empty(&bo->debugfs.node))
@@ -80,9 +82,9 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo) {}
 #endif
 
 static bool
-should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
+should_map_wc(struct panthor_gem_object *bo)
 {
-	struct panthor_device *ptdev = container_of(bo->base.base.dev, struct panthor_device, base);
+	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
 
 	/* We can't do uncached mappings if the device is coherent,
 	 * because the zeroing done by the shmem layer at page allocation
@@ -112,6 +114,208 @@ should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
 	return true;
 }
 
+static void
+panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
+{
+	if (!bo->backing.pages)
+		return;
+
+	drm_gem_put_pages(&bo->base, bo->backing.pages, true, false);
+	bo->backing.pages = NULL;
+}
+
+static int
+panthor_gem_backing_get_pages_locked(struct panthor_gem_object *bo)
+{
+	dma_resv_assert_held(bo->base.resv);
+
+	if (bo->backing.pages)
+		return 0;
+
+	bo->backing.pages = drm_gem_get_pages(&bo->base);
+	if (IS_ERR(bo->backing.pages)) {
+		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
+			    bo->backing.pages);
+		return PTR_ERR(bo->backing.pages);
+	}
+
+	return 0;
+}
+
+static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
+{
+	int ret;
+
+	dma_resv_assert_held(bo->base.resv);
+	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
+
+	if (refcount_inc_not_zero(&bo->backing.pin_count))
+		return 0;
+
+	ret = panthor_gem_backing_get_pages_locked(bo);
+	if (!ret)
+		refcount_set(&bo->backing.pin_count, 1);
+
+	return ret;
+}
+
+static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
+{
+	dma_resv_assert_held(bo->base.resv);
+	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
+
+	/* We don't release anything when pin_count drops to zero.
+	 * Pages stay there until an explicit cleanup is requested.
+	 */
+	if (!refcount_dec_not_one(&bo->backing.pin_count))
+		refcount_set(&bo->backing.pin_count, 0);
+}
+
+static void
+panthor_gem_dev_map_cleanup(struct panthor_gem_object *bo)
+{
+	if (!bo->dmap.sgt)
+		return;
+
+	dma_unmap_sgtable(drm_dev_dma_dev(bo->base.dev), bo->dmap.sgt, DMA_BIDIRECTIONAL, 0);
+	sg_free_table(bo->dmap.sgt);
+	kfree(bo->dmap.sgt);
+	bo->dmap.sgt = NULL;
+}
+
+static struct sg_table *
+panthor_gem_dev_map_get_sgt_locked(struct panthor_gem_object *bo)
+{
+	struct sg_table *sgt;
+	int ret;
+
+	dma_resv_assert_held(bo->base.resv);
+
+	if (bo->dmap.sgt)
+		return bo->dmap.sgt;
+
+	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
+		return ERR_PTR(-EINVAL);
+
+	/* Pages stay around after they've been allocated. At least that stands
+	 * until we add a shrinker.
+	 */
+	ret = panthor_gem_backing_get_pages_locked(bo);
+	if (ret)
+		return ERR_PTR(ret);
+
+	sgt = drm_prime_pages_to_sg(bo->base.dev, bo->backing.pages,
+				    bo->base.size >> PAGE_SHIFT);
+	if (IS_ERR(sgt))
+		return sgt;
+
+	/* Map the pages for use by the h/w. */
+	ret = dma_map_sgtable(drm_dev_dma_dev(bo->base.dev), sgt, DMA_BIDIRECTIONAL, 0);
+	if (ret)
+		goto err_free_sgt;
+
+	bo->dmap.sgt = sgt;
+	return sgt;
+
+err_free_sgt:
+	sg_free_table(sgt);
+	kfree(sgt);
+	return ERR_PTR(ret);
+}
+
+struct sg_table *
+panthor_gem_get_dev_sgt(struct panthor_gem_object *bo)
+{
+	struct sg_table *sgt;
+
+	dma_resv_lock(bo->base.resv, NULL);
+	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
+	dma_resv_unlock(bo->base.resv);
+
+	return sgt;
+}
+
+static void
+panthor_gem_vmap_cleanup(struct panthor_gem_object *bo)
+{
+	if (!bo->cmap.vaddr)
+		return;
+
+	vunmap(bo->cmap.vaddr);
+	bo->cmap.vaddr = NULL;
+	panthor_gem_backing_unpin_locked(bo);
+}
+
+static int
+panthor_gem_prep_for_cpu_map_locked(struct panthor_gem_object *bo)
+{
+	if (should_map_wc(bo)) {
+		struct sg_table *sgt;
+
+		sgt = panthor_gem_dev_map_get_sgt_locked(bo);
+		if (IS_ERR(sgt))
+			return PTR_ERR(sgt);
+	}
+
+	return 0;
+}
+
+static void *
+panthor_gem_vmap_get_locked(struct panthor_gem_object *bo)
+{
+	pgprot_t prot = PAGE_KERNEL;
+	void *vaddr;
+	int ret;
+
+	dma_resv_assert_held(bo->base.resv);
+
+	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
+		return ERR_PTR(-EINVAL);
+
+	if (refcount_inc_not_zero(&bo->cmap.vaddr_use_count)) {
+		drm_WARN_ON_ONCE(bo->base.dev, !bo->cmap.vaddr);
+		return bo->cmap.vaddr;
+	}
+
+	ret = panthor_gem_backing_pin_locked(bo);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = panthor_gem_prep_for_cpu_map_locked(bo);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (should_map_wc(bo))
+		prot = pgprot_writecombine(prot);
+
+	vaddr = vmap(bo->backing.pages, bo->base.size >> PAGE_SHIFT, VM_MAP, prot);
+	if (!vaddr) {
+		ret = -ENOMEM;
+		goto err_unpin;
+	}
+
+	bo->cmap.vaddr = vaddr;
+	refcount_set(&bo->cmap.vaddr_use_count, 1);
+	return vaddr;
+
+err_unpin:
+	panthor_gem_backing_unpin_locked(bo);
+	return ERR_PTR(ret);
+}
+
+static void
+panthor_gem_vmap_put_locked(struct panthor_gem_object *bo)
+{
+	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
+		return;
+
+	if (refcount_dec_not_one(&bo->cmap.vaddr_use_count))
+		return;
+
+	refcount_set(&bo->cmap.vaddr_use_count, 0);
+	panthor_gem_vmap_cleanup(bo);
+}
+
 static void panthor_gem_free_object(struct drm_gem_object *obj)
 {
 	struct panthor_gem_object *bo = to_panthor_bo(obj);
@@ -127,8 +331,17 @@ static void panthor_gem_free_object(struct drm_gem_object *obj)
 
 	mutex_destroy(&bo->label.lock);
 
-	drm_gem_free_mmap_offset(&bo->base.base);
-	drm_gem_shmem_free(&bo->base);
+	if (drm_gem_is_imported(obj)) {
+		drm_prime_gem_destroy(obj, bo->dmap.sgt);
+	} else {
+		panthor_gem_vmap_cleanup(bo);
+		panthor_gem_dev_map_cleanup(bo);
+		panthor_gem_backing_cleanup(bo);
+	}
+
+	drm_gem_object_release(obj);
+
+	kfree(bo);
 	drm_gem_object_put(vm_root_gem);
 }
 
@@ -159,15 +372,15 @@ panthor_gem_prime_begin_cpu_access(struct dma_buf *dma_buf,
 {
 	struct drm_gem_object *obj = dma_buf->priv;
 	struct drm_device *dev = obj->dev;
-	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
+	struct panthor_gem_object *bo = to_panthor_bo(obj);
 	struct dma_buf_attachment *attach;
 
 	dma_resv_lock(obj->resv, NULL);
-	if (shmem->sgt)
-		dma_sync_sgtable_for_cpu(dev->dev, shmem->sgt, dir);
+	if (bo->dmap.sgt)
+		dma_sync_sgtable_for_cpu(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
 
-	if (shmem->vaddr)
-		invalidate_kernel_vmap_range(shmem->vaddr, shmem->base.size);
+	if (bo->cmap.vaddr)
+		invalidate_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
 
 	list_for_each_entry(attach, &dma_buf->attachments, node) {
 		struct sg_table *sgt = attach->priv;
@@ -186,7 +399,7 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
 {
 	struct drm_gem_object *obj = dma_buf->priv;
 	struct drm_device *dev = obj->dev;
-	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
+	struct panthor_gem_object *bo = to_panthor_bo(obj);
 	struct dma_buf_attachment *attach;
 
 	dma_resv_lock(obj->resv, NULL);
@@ -197,11 +410,11 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
 			dma_sync_sgtable_for_device(attach->dev, sgt, dir);
 	}
 
-	if (shmem->vaddr)
-		flush_kernel_vmap_range(shmem->vaddr, shmem->base.size);
+	if (bo->cmap.vaddr)
+		flush_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
 
-	if (shmem->sgt)
-		dma_sync_sgtable_for_device(dev->dev, shmem->sgt, dir);
+	if (bo->dmap.sgt)
+		dma_sync_sgtable_for_device(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
 
 	dma_resv_unlock(obj->resv);
 	return 0;
@@ -258,53 +471,339 @@ panthor_gem_prime_import(struct drm_device *dev,
 	return drm_gem_prime_import(dev, dma_buf);
 }
 
+static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
+				   const struct drm_gem_object *obj)
+{
+	const struct panthor_gem_object *bo = to_panthor_bo(obj);
+
+	if (drm_gem_is_imported(&bo->base))
+		return;
+
+	drm_printf_indent(p, indent, "resident=%s\n", str_true_false(bo->backing.pages));
+	drm_printf_indent(p, indent, "pages_pin_count=%u\n", refcount_read(&bo->backing.pin_count));
+	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
+			  refcount_read(&bo->cmap.vaddr_use_count));
+	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
+}
+
+static int panthor_gem_pin_locked(struct drm_gem_object *obj)
+{
+	if (drm_gem_is_imported(obj))
+		return 0;
+
+	return panthor_gem_backing_pin_locked(to_panthor_bo(obj));
+}
+
+static void panthor_gem_unpin_locked(struct drm_gem_object *obj)
+{
+	if (!drm_gem_is_imported(obj))
+		panthor_gem_backing_unpin_locked(to_panthor_bo(obj));
+}
+
+int panthor_gem_pin(struct panthor_gem_object *bo)
+{
+	int ret = 0;
+
+	if (drm_gem_is_imported(&bo->base))
+		return 0;
+
+	if (refcount_inc_not_zero(&bo->backing.pin_count))
+		return 0;
+
+	dma_resv_lock(bo->base.resv, NULL);
+	ret = panthor_gem_pin_locked(&bo->base);
+	dma_resv_unlock(bo->base.resv);
+
+	return ret;
+}
+
+void panthor_gem_unpin(struct panthor_gem_object *bo)
+{
+	if (drm_gem_is_imported(&bo->base))
+		return;
+
+	if (refcount_dec_not_one(&bo->backing.pin_count))
+		return;
+
+	dma_resv_lock(bo->base.resv, NULL);
+	panthor_gem_unpin_locked(&bo->base);
+	dma_resv_unlock(bo->base.resv);
+}
+
+static struct sg_table *panthor_gem_get_sg_table(struct drm_gem_object *obj)
+{
+	struct panthor_gem_object *bo = to_panthor_bo(obj);
+
+	drm_WARN_ON_ONCE(obj->dev, drm_gem_is_imported(obj));
+	drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages);
+	drm_WARN_ON_ONCE(obj->dev, !refcount_read(&bo->backing.pin_count));
+
+	return drm_prime_pages_to_sg(obj->dev, bo->backing.pages, obj->size >> PAGE_SHIFT);
+}
+
+static int panthor_gem_vmap_locked(struct drm_gem_object *obj,
+				   struct iosys_map *map)
+{
+	struct panthor_gem_object *bo = to_panthor_bo(obj);
+	void *vaddr;
+
+	dma_resv_assert_held(obj->resv);
+
+	if (drm_gem_is_imported(obj))
+		return dma_buf_vmap(obj->import_attach->dmabuf, map);
+
+	vaddr = panthor_gem_vmap_get_locked(bo);
+	if (IS_ERR(vaddr))
+		return PTR_ERR(vaddr);
+
+	iosys_map_set_vaddr(map, vaddr);
+	return 0;
+}
+
+static void panthor_gem_vunmap_locked(struct drm_gem_object *obj,
+				      struct iosys_map *map)
+{
+	struct panthor_gem_object *bo = to_panthor_bo(obj);
+
+	dma_resv_assert_held(obj->resv);
+
+	if (drm_gem_is_imported(obj)) {
+		dma_buf_vunmap(obj->import_attach->dmabuf, map);
+	} else {
+		drm_WARN_ON_ONCE(obj->dev, bo->cmap.vaddr != map->vaddr);
+		panthor_gem_vmap_put_locked(bo);
+	}
+}
+
+static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
+{
+	struct panthor_gem_object *bo = to_panthor_bo(obj);
+	int ret;
+
+	if (drm_gem_is_imported(obj)) {
+		/* Reset both vm_ops and vm_private_data, so we don't end up with
+		 * vm_ops pointing to our implementation if the dma-buf backend
+		 * doesn't set those fields.
+		 */
+		vma->vm_private_data = NULL;
+		vma->vm_ops = NULL;
+
+		ret = dma_buf_mmap(obj->dma_buf, vma, 0);
+
+		/* Drop the reference drm_gem_mmap_obj() acquired.*/
+		if (!ret)
+			drm_gem_object_put(obj);
+
+		return ret;
+	}
+
+	if (is_cow_mapping(vma->vm_flags))
+		return -EINVAL;
+
+	dma_resv_lock(obj->resv, NULL);
+	ret = panthor_gem_backing_get_pages_locked(bo);
+	if (!ret)
+		ret = panthor_gem_prep_for_cpu_map_locked(bo);
+	dma_resv_unlock(obj->resv);
+
+	if (ret)
+		return ret;
+
+	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
+	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
+	if (should_map_wc(bo))
+		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+	return 0;
+}
+
 static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
 {
 	struct panthor_gem_object *bo = to_panthor_bo(obj);
 	enum drm_gem_object_status res = 0;
 
-	if (drm_gem_is_imported(&bo->base.base) || bo->base.pages)
+	if (drm_gem_is_imported(&bo->base) || bo->backing.pages)
 		res |= DRM_GEM_OBJECT_RESIDENT;
 
 	return res;
 }
 
-static const struct drm_gem_object_funcs panthor_gem_funcs = {
-	.free = panthor_gem_free_object,
-	.print_info = drm_gem_shmem_object_print_info,
-	.pin = drm_gem_shmem_object_pin,
-	.unpin = drm_gem_shmem_object_unpin,
-	.get_sg_table = drm_gem_shmem_object_get_sg_table,
-	.vmap = drm_gem_shmem_object_vmap,
-	.vunmap = drm_gem_shmem_object_vunmap,
-	.mmap = drm_gem_shmem_object_mmap,
-	.status = panthor_gem_status,
-	.export = panthor_gem_prime_export,
-	.vm_ops = &drm_gem_shmem_vm_ops,
+static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
+{
+#ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
+	unsigned long pfn = page_to_pfn(page);
+	unsigned long paddr = pfn << PAGE_SHIFT;
+	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
+
+	if (aligned &&
+	    pmd_none(*vmf->pmd) &&
+	    folio_test_pmd_mappable(page_folio(page))) {
+		pfn &= PMD_MASK >> PAGE_SHIFT;
+		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
+			return true;
+	}
+#endif
+
+	return false;
+}
+
+static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
+{
+	struct vm_area_struct *vma = vmf->vma;
+	struct drm_gem_object *obj = vma->vm_private_data;
+	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
+	loff_t num_pages = obj->size >> PAGE_SHIFT;
+	vm_fault_t ret;
+	pgoff_t page_offset;
+	unsigned long pfn;
+
+	/* Offset to faulty address in the VMA. */
+	page_offset = vmf->pgoff - vma->vm_pgoff;
+
+	dma_resv_lock(bo->base.resv, NULL);
+
+	if (page_offset >= num_pages ||
+	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
+		ret = VM_FAULT_SIGBUS;
+		goto out;
+	}
+
+	if (try_map_pmd(vmf, vmf->address, bo->backing.pages[page_offset])) {
+		ret = VM_FAULT_NOPAGE;
+		goto out;
+	}
+
+	pfn = page_to_pfn(bo->backing.pages[page_offset]);
+	ret = vmf_insert_pfn(vma, vmf->address, pfn);
+
+ out:
+	dma_resv_unlock(bo->base.resv);
+
+	return ret;
+}
+
+static void panthor_gem_vm_open(struct vm_area_struct *vma)
+{
+	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
+
+	drm_WARN_ON(bo->base.dev, drm_gem_is_imported(&bo->base));
+
+	dma_resv_lock(bo->base.resv, NULL);
+
+	/* We should have already pinned the pages when the buffer was first
+	 * mmap'd, vm_open() just grabs an additional reference for the new
+	 * mm the vma is getting copied into (ie. on fork()).
+	 */
+	drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages);
+
+	dma_resv_unlock(bo->base.resv);
+
+	drm_gem_vm_open(vma);
+}
+
+const struct vm_operations_struct panthor_gem_vm_ops = {
+	.fault = panthor_gem_fault,
+	.open = panthor_gem_vm_open,
+	.close = drm_gem_vm_close,
 };
 
-/**
- * panthor_gem_create_object - Implementation of driver->gem_create_object.
- * @ddev: DRM device
- * @size: Size in bytes of the memory the object will reference
- *
- * This lets the GEM helpers allocate object structs for us, and keep
- * our BO stats correct.
- */
-struct drm_gem_object *panthor_gem_create_object(struct drm_device *ddev, size_t size)
-{
-	struct panthor_gem_object *obj;
+static const struct drm_gem_object_funcs panthor_gem_funcs = {
+	.free = panthor_gem_free_object,
+	.print_info = panthor_gem_print_info,
+	.pin = panthor_gem_pin_locked,
+	.unpin = panthor_gem_unpin_locked,
+	.get_sg_table = panthor_gem_get_sg_table,
+	.vmap = panthor_gem_vmap_locked,
+	.vunmap = panthor_gem_vunmap_locked,
+	.mmap = panthor_gem_mmap,
+	.status = panthor_gem_status,
+	.export = panthor_gem_prime_export,
+	.vm_ops = &panthor_gem_vm_ops,
+};
 
-	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
-	if (!obj)
+static struct panthor_gem_object *
+panthor_gem_alloc_object(uint32_t flags)
+{
+	struct panthor_gem_object *bo;
+
+	bo = kzalloc(sizeof(*bo), GFP_KERNEL);
+	if (!bo)
 		return ERR_PTR(-ENOMEM);
 
-	obj->base.base.funcs = &panthor_gem_funcs;
-	mutex_init(&obj->label.lock);
+	bo->base.funcs = &panthor_gem_funcs;
+	bo->flags = flags;
+	mutex_init(&bo->label.lock);
+	panthor_gem_debugfs_bo_init(bo);
+	return bo;
+}
 
-	panthor_gem_debugfs_bo_init(obj);
+static struct panthor_gem_object *
+panthor_gem_create(struct drm_device *dev, size_t size, uint32_t flags,
+		   struct panthor_vm *exclusive_vm, u32 usage_flags)
+{
+	struct panthor_gem_object *bo;
+	int ret;
 
-	return &obj->base.base;
+	bo = panthor_gem_alloc_object(flags);
+	if (IS_ERR(bo))
+		return bo;
+
+	size = PAGE_ALIGN(size);
+	ret = drm_gem_object_init(dev, &bo->base, size);
+	if (ret)
+		goto err_put;
+
+	/* Our buffers are kept pinned, so allocating them
+	 * from the MOVABLE zone is a really bad idea, and
+	 * conflicts with CMA. See comments above new_inode()
+	 * why this is required _and_ expected if you're
+	 * going to pin these pages.
+	 */
+	mapping_set_gfp_mask(bo->base.filp->f_mapping,
+			     GFP_HIGHUSER | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
+
+	ret = drm_gem_create_mmap_offset(&bo->base);
+	if (ret)
+		goto err_put;
+
+	if (exclusive_vm) {
+		bo->exclusive_vm_root_gem = panthor_vm_root_gem(exclusive_vm);
+		drm_gem_object_get(bo->exclusive_vm_root_gem);
+		bo->base.resv = bo->exclusive_vm_root_gem->resv;
+	}
+
+	panthor_gem_debugfs_set_usage_flags(bo, usage_flags);
+	return bo;
+
+err_put:
+	drm_gem_object_put(&bo->base);
+	return ERR_PTR(ret);
+}
+
+struct drm_gem_object *
+panthor_gem_prime_import_sg_table(struct drm_device *dev,
+				  struct dma_buf_attachment *attach,
+				  struct sg_table *sgt)
+{
+	struct panthor_gem_object *bo;
+	int ret;
+
+	bo = panthor_gem_alloc_object(0);
+	if (IS_ERR(bo))
+		return &bo->base;
+
+	drm_gem_private_object_init(dev, &bo->base, attach->dmabuf->size);
+
+	ret = drm_gem_create_mmap_offset(&bo->base);
+	if (ret)
+		goto err_put;
+
+	bo->dmap.sgt = sgt;
+	return &bo->base;
+
+err_put:
+	drm_gem_object_put(&bo->base);
+	return ERR_PTR(ret);
 }
 
 /**
@@ -325,54 +824,22 @@ panthor_gem_create_with_handle(struct drm_file *file,
 			       u64 *size, u32 flags, u32 *handle)
 {
 	int ret;
-	struct drm_gem_shmem_object *shmem;
 	struct panthor_gem_object *bo;
 
-	shmem = drm_gem_shmem_create(ddev, *size);
-	if (IS_ERR(shmem))
-		return PTR_ERR(shmem);
-
-	bo = to_panthor_bo(&shmem->base);
-	bo->flags = flags;
-	bo->base.map_wc = should_map_wc(bo, exclusive_vm);
-
-	if (exclusive_vm) {
-		bo->exclusive_vm_root_gem = panthor_vm_root_gem(exclusive_vm);
-		drm_gem_object_get(bo->exclusive_vm_root_gem);
-		bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
-	}
-
-	panthor_gem_debugfs_set_usage_flags(bo, 0);
-
-	/* If this is a write-combine mapping, we query the sgt to force a CPU
-	 * cache flush (dma_map_sgtable() is called when the sgt is created).
-	 * This ensures the zero-ing is visible to any uncached mapping created
-	 * by vmap/mmap.
-	 * FIXME: Ideally this should be done when pages are allocated, not at
-	 * BO creation time.
-	 */
-	if (shmem->map_wc) {
-		struct sg_table *sgt;
-
-		sgt = drm_gem_shmem_get_pages_sgt(shmem);
-		if (IS_ERR(sgt)) {
-			ret = PTR_ERR(sgt);
-			goto out_put_gem;
-		}
-	}
+	bo = panthor_gem_create(ddev, *size, flags, exclusive_vm, 0);
+	if (IS_ERR(bo))
+		return PTR_ERR(bo);
 
 	/*
 	 * Allocate an id of idr table where the obj is registered
 	 * and handle has the id what user can see.
 	 */
-	ret = drm_gem_handle_create(file, &shmem->base, handle);
+	ret = drm_gem_handle_create(file, &bo->base, handle);
 	if (!ret)
-		*size = bo->base.base.size;
+		*size = bo->base.size;
 
-out_put_gem:
 	/* drop reference from allocate - handle holds it now. */
-	drm_gem_object_put(&shmem->base);
-
+	drm_gem_object_put(&bo->base);
 	return ret;
 }
 
@@ -417,18 +884,17 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
 		 u64 offset, u64 size)
 {
 	struct panthor_gem_object *bo = to_panthor_bo(obj);
-	struct drm_gem_shmem_object *shmem = &bo->base;
-	const struct drm_device *dev = shmem->base.dev;
+	struct device *dma_dev = drm_dev_dma_dev(bo->base.dev);
 	struct sg_table *sgt;
 	struct scatterlist *sgl;
 	unsigned int count;
 
 	/* Make sure the range is in bounds. */
-	if (offset + size < offset || offset + size > shmem->base.size)
+	if (offset + size < offset || offset + size > bo->base.size)
 		return -EINVAL;
 
 	/* Disallow CPU-cache maintenance on imported buffers. */
-	if (drm_gem_is_imported(&shmem->base))
+	if (drm_gem_is_imported(&bo->base))
 		return -EINVAL;
 
 	switch (type) {
@@ -441,14 +907,14 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
 	}
 
 	/* Don't bother if it's WC-mapped */
-	if (shmem->map_wc)
+	if (should_map_wc(bo))
 		return 0;
 
 	/* Nothing to do if the size is zero. */
 	if (size == 0)
 		return 0;
 
-	sgt = drm_gem_shmem_get_pages_sgt(shmem);
+	sgt = panthor_gem_get_dev_sgt(bo);
 	if (IS_ERR(sgt))
 		return PTR_ERR(sgt);
 
@@ -489,9 +955,9 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
 		 *
 		 * for the flush+invalidate case.
 		 */
-		dma_sync_single_for_device(dev->dev, paddr, len, DMA_TO_DEVICE);
+		dma_sync_single_for_device(dma_dev, paddr, len, DMA_TO_DEVICE);
 		if (type == DRM_PANTHOR_BO_SYNC_CPU_CACHE_FLUSH_AND_INVALIDATE)
-			dma_sync_single_for_cpu(dev->dev, paddr, len, DMA_FROM_DEVICE);
+			dma_sync_single_for_cpu(dma_dev, paddr, len, DMA_FROM_DEVICE);
 	}
 
 	return 0;
@@ -541,7 +1007,6 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
 			 size_t size, u32 bo_flags, u32 vm_map_flags,
 			 u64 gpu_va, const char *name)
 {
-	struct drm_gem_shmem_object *obj;
 	struct panthor_kernel_bo *kbo;
 	struct panthor_gem_object *bo;
 	u32 debug_flags = PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL;
@@ -554,25 +1019,18 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
 	if (!kbo)
 		return ERR_PTR(-ENOMEM);
 
-	obj = drm_gem_shmem_create(&ptdev->base, size);
-	if (IS_ERR(obj)) {
-		ret = PTR_ERR(obj);
-		goto err_free_bo;
-	}
-
-	bo = to_panthor_bo(&obj->base);
-	kbo->obj = &obj->base;
-	bo->flags = bo_flags;
-	bo->base.map_wc = should_map_wc(bo, vm);
-	bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm);
-	drm_gem_object_get(bo->exclusive_vm_root_gem);
-	bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
-
 	if (vm == panthor_fw_vm(ptdev))
 		debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED;
 
+	bo = panthor_gem_create(&ptdev->base, size, bo_flags, vm, debug_flags);
+	if (IS_ERR(bo)) {
+		ret = PTR_ERR(bo);
+		goto err_free_kbo;
+	}
+
+	kbo->obj = &bo->base;
+
 	panthor_gem_kernel_bo_set_label(kbo, name);
-	panthor_gem_debugfs_set_usage_flags(to_panthor_bo(kbo->obj), debug_flags);
 
 	/* The system and GPU MMU page size might differ, which becomes a
 	 * problem for FW sections that need to be mapped at explicit address
@@ -596,9 +1054,9 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
 	panthor_vm_free_va(vm, &kbo->va_node);
 
 err_put_obj:
-	drm_gem_object_put(&obj->base);
+	drm_gem_object_put(&bo->base);
 
-err_free_bo:
+err_free_kbo:
 	kfree(kbo);
 	return ERR_PTR(ret);
 }
@@ -646,7 +1104,7 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
 					 struct seq_file *m,
 					 struct gem_size_totals *totals)
 {
-	unsigned int refcount = kref_read(&bo->base.base.refcount);
+	unsigned int refcount = kref_read(&bo->base.refcount);
 	char creator_info[32] = {};
 	size_t resident_size;
 	u32 gem_usage_flags = bo->debugfs.flags;
@@ -656,21 +1114,21 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
 	if (!refcount)
 		return;
 
-	resident_size = bo->base.pages ? bo->base.base.size : 0;
+	resident_size = bo->backing.pages ? bo->base.size : 0;
 
 	snprintf(creator_info, sizeof(creator_info),
 		 "%s/%d", bo->debugfs.creator.process_name, bo->debugfs.creator.tgid);
 	seq_printf(m, "%-32s%-16d%-16d%-16zd%-16zd0x%-16lx",
 		   creator_info,
-		   bo->base.base.name,
+		   bo->base.name,
 		   refcount,
-		   bo->base.base.size,
+		   bo->base.size,
 		   resident_size,
-		   drm_vma_node_start(&bo->base.base.vma_node));
+		   drm_vma_node_start(&bo->base.vma_node));
 
-	if (bo->base.base.import_attach)
+	if (bo->base.import_attach)
 		gem_state_flags |= PANTHOR_DEBUGFS_GEM_STATE_FLAG_IMPORTED;
-	if (bo->base.base.dma_buf)
+	if (bo->base.dma_buf)
 		gem_state_flags |= PANTHOR_DEBUGFS_GEM_STATE_FLAG_EXPORTED;
 
 	seq_printf(m, "0x%-8x 0x%-10x", gem_state_flags, gem_usage_flags);
@@ -679,10 +1137,8 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
 		seq_printf(m, "%s\n", bo->label.str ? : "");
 	}
 
-	totals->size += bo->base.base.size;
+	totals->size += bo->base.size;
 	totals->resident += resident_size;
-	if (bo->base.madv > 0)
-		totals->reclaimable += resident_size;
 }
 
 static void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
index 94b2d17cf032..b66478c9590c 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.h
+++ b/drivers/gpu/drm/panthor/panthor_gem.h
@@ -5,7 +5,7 @@
 #ifndef __PANTHOR_GEM_H__
 #define __PANTHOR_GEM_H__
 
-#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_gem.h>
 #include <drm/drm_mm.h>
 
 #include <linux/iosys-map.h>
@@ -60,12 +60,51 @@ struct panthor_gem_debugfs {
 	u32 flags;
 };
 
+/**
+ * struct panthor_gem_backing - GEM memory backing related data
+ */
+struct panthor_gem_backing {
+	/** @pages: Pages requested with drm_gem_get_pages() */
+	struct page **pages;
+
+	/** @pin_count: Number of active pin requests on this GEM */
+	refcount_t pin_count;
+};
+
+/**
+ * struct panthor_gem_cpu_map - GEM CPU mapping related data
+ */
+struct panthor_gem_cpu_map {
+	/** @vaddr: Address returned by vmap() */
+	void *vaddr;
+
+	/** @vaddr_use_count: Number of active vmap() requests on this GEM */
+	refcount_t vaddr_use_count;
+};
+
+/**
+ * struct panthor_gem_dev_map - GEM device mapping related data
+ */
+struct panthor_gem_dev_map {
+	/** @sgt: Device mapped sg_table for this GEM */
+	struct sg_table *sgt;
+};
+
 /**
  * struct panthor_gem_object - Driver specific GEM object.
  */
 struct panthor_gem_object {
-	/** @base: Inherit from drm_gem_shmem_object. */
-	struct drm_gem_shmem_object base;
+	/** @base: Inherit from drm_gem_object. */
+	struct drm_gem_object base;
+
+	/** @backing: Memory backing state */
+	struct panthor_gem_backing backing;
+
+	/** @cmap: CPU mapping state */
+	struct panthor_gem_cpu_map cmap;
+
+	/** @dmap: Device mapping state */
+	struct panthor_gem_dev_map dmap;
 
 	/**
 	 * @exclusive_vm_root_gem: Root GEM of the exclusive VM this GEM object
@@ -130,22 +169,25 @@ struct panthor_kernel_bo {
 	void *kmap;
 };
 
-static inline
-struct panthor_gem_object *to_panthor_bo(struct drm_gem_object *obj)
-{
-	return container_of(to_drm_gem_shmem_obj(obj), struct panthor_gem_object, base);
-}
+#define to_panthor_bo(obj) container_of_const(obj, struct panthor_gem_object, base)
 
 void panthor_gem_init(struct panthor_device *ptdev);
 
-struct drm_gem_object *panthor_gem_create_object(struct drm_device *ddev, size_t size);
-
+struct drm_gem_object *
+panthor_gem_prime_import_sg_table(struct drm_device *dev,
+				  struct dma_buf_attachment *attach,
+				  struct sg_table *sgt);
 int
 panthor_gem_create_with_handle(struct drm_file *file,
 			       struct drm_device *ddev,
 			       struct panthor_vm *exclusive_vm,
 			       u64 *size, u32 flags, uint32_t *handle);
 
+struct sg_table *
+panthor_gem_get_dev_sgt(struct panthor_gem_object *bo);
+int panthor_gem_pin(struct panthor_gem_object *bo);
+void panthor_gem_unpin(struct panthor_gem_object *bo);
+
 void panthor_gem_bo_set_label(struct drm_gem_object *obj, const char *label);
 void panthor_gem_kernel_bo_set_label(struct panthor_kernel_bo *bo, const char *label);
 int panthor_gem_sync(struct drm_gem_object *obj,
diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
index b888fff05efe..c323b7123713 100644
--- a/drivers/gpu/drm/panthor/panthor_mmu.c
+++ b/drivers/gpu/drm/panthor/panthor_mmu.c
@@ -5,6 +5,7 @@
 #include <drm/drm_debugfs.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_exec.h>
+#include <drm/drm_file.h>
 #include <drm/drm_gpuvm.h>
 #include <drm/drm_managed.h>
 #include <drm/drm_print.h>
@@ -1073,8 +1074,7 @@ static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo)
 {
 	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
 
-	if (!drm_gem_is_imported(&bo->base.base))
-		drm_gem_shmem_unpin(&bo->base);
+	panthor_gem_unpin(bo);
 	kfree(vm_bo);
 }
 
@@ -1196,7 +1196,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
 		return -EINVAL;
 
 	/* Make sure the VA and size are in-bounds. */
-	if (size > bo->base.base.size || offset > bo->base.base.size - size)
+	if (size > bo->base.size || offset > bo->base.size - size)
 		return -EINVAL;
 
 	/* If the BO has an exclusive VM attached, it can't be mapped to other VMs. */
@@ -1213,33 +1213,25 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
 	if (ret)
 		goto err_cleanup;
 
-	if (!drm_gem_is_imported(&bo->base.base)) {
-		/* Pre-reserve the BO pages, so the map operation doesn't have to
-		 * allocate. This pin is dropped in panthor_vm_bo_free(), so
-		 * once we have successfully called drm_gpuvm_bo_create(),
-		 * GPUVM will take care of dropping the pin for us.
-		 */
-		ret = drm_gem_shmem_pin(&bo->base);
-		if (ret)
-			goto err_cleanup;
-	}
+	/* Pre-reserve the BO pages, so the map operation doesn't have to
+	 * allocate.
+	 */
+	ret = panthor_gem_pin(bo);
+	if (ret)
+		goto err_cleanup;
 
-	sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
+	sgt = panthor_gem_get_dev_sgt(bo);
 	if (IS_ERR(sgt)) {
-		if (!drm_gem_is_imported(&bo->base.base))
-			drm_gem_shmem_unpin(&bo->base);
-
+		panthor_gem_unpin(bo);
 		ret = PTR_ERR(sgt);
 		goto err_cleanup;
 	}
 
 	op_ctx->map.sgt = sgt;
 
-	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base.base);
+	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base);
 	if (!preallocated_vm_bo) {
-		if (!drm_gem_is_imported(&bo->base.base))
-			drm_gem_shmem_unpin(&bo->base);
-
+		panthor_gem_unpin(bo);
 		ret = -ENOMEM;
 		goto err_cleanup;
 	}
@@ -1251,9 +1243,9 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
 	 * calling this function.
 	 */
 	dma_resv_lock(panthor_vm_resv(vm), NULL);
-	mutex_lock(&bo->base.base.gpuva.lock);
+	mutex_lock(&bo->base.gpuva.lock);
 	op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
-	mutex_unlock(&bo->base.base.gpuva.lock);
+	mutex_unlock(&bo->base.gpuva.lock);
 	dma_resv_unlock(panthor_vm_resv(vm));
 
 	op_ctx->map.bo_offset = offset;
@@ -2063,9 +2055,9 @@ static void panthor_vma_link(struct panthor_vm *vm,
 {
 	struct panthor_gem_object *bo = to_panthor_bo(vma->base.gem.obj);
 
-	mutex_lock(&bo->base.base.gpuva.lock);
+	mutex_lock(&bo->base.gpuva.lock);
 	drm_gpuva_link(&vma->base, vm_bo);
-	mutex_unlock(&bo->base.base.gpuva.lock);
+	mutex_unlock(&bo->base.gpuva.lock);
 }
 
 static void panthor_vma_unlink(struct panthor_vma *vma)
@@ -2117,11 +2109,12 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
 static bool
 iova_mapped_as_huge_page(struct drm_gpuva_op_map *op, u64 addr)
 {
+	struct panthor_gem_object *bo = to_panthor_bo(op->gem.obj);
 	const struct page *pg;
 	pgoff_t bo_offset;
 
 	bo_offset = addr - op->va.addr + op->gem.offset;
-	pg = to_panthor_bo(op->gem.obj)->base.pages[bo_offset >> PAGE_SHIFT];
+	pg = bo->backing.pages[bo_offset >> PAGE_SHIFT];
 
 	return folio_size(page_folio(pg)) >= SZ_2M;
 }
@@ -2190,7 +2183,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
 		u64 size = op->remap.prev->va.addr + op->remap.prev->va.range - unmap_start;
 
 		ret = panthor_vm_map_pages(vm, unmap_start, flags_to_prot(unmap_vma->flags),
-					   bo->base.sgt, offset, size);
+					   bo->dmap.sgt, offset, size);
 		if (ret)
 			return ret;
 
@@ -2204,7 +2197,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
 		u64 size = unmap_start + unmap_range - op->remap.next->va.addr;
 
 		ret = panthor_vm_map_pages(vm, addr, flags_to_prot(unmap_vma->flags),
-					   bo->base.sgt, op->remap.next->gem.offset, size);
+					   bo->dmap.sgt, op->remap.next->gem.offset, size);
 		if (ret)
 			return ret;
 
diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c
index ca272dbae14d..2ddf64870d85 100644
--- a/drivers/gpu/drm/panthor/panthor_sched.c
+++ b/drivers/gpu/drm/panthor/panthor_sched.c
@@ -3,7 +3,7 @@
 
 #include <drm/drm_drv.h>
 #include <drm/drm_exec.h>
-#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_file.h>
 #include <drm/drm_managed.h>
 #include <drm/drm_print.h>
 #include <drm/gpu_scheduler.h>
@@ -871,8 +871,7 @@ panthor_queue_get_syncwait_obj(struct panthor_group *group, struct panthor_queue
 	int ret;
 
 	if (queue->syncwait.kmap) {
-		bo = container_of(queue->syncwait.obj,
-				  struct panthor_gem_object, base.base);
+		bo = to_panthor_bo(queue->syncwait.obj);
 		goto out_sync;
 	}
 
@@ -882,7 +881,7 @@ panthor_queue_get_syncwait_obj(struct panthor_group *group, struct panthor_queue
 	if (drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(bo)))
 		goto err_put_syncwait_obj;
 
-	queue->syncwait.obj = &bo->base.base;
+	queue->syncwait.obj = &bo->base;
 	ret = drm_gem_vmap(queue->syncwait.obj, &map);
 	if (drm_WARN_ON(&ptdev->base, ret))
 		goto err_put_syncwait_obj;
@@ -896,7 +895,7 @@ panthor_queue_get_syncwait_obj(struct panthor_group *group, struct panthor_queue
 	 * drm_gem_shmem_sync() is a NOP if map_wc=true, so no need to check
 	 * it here.
 	 */
-	panthor_gem_sync(&bo->base.base, queue->syncwait.offset,
+	panthor_gem_sync(&bo->base, queue->syncwait.offset,
 			 queue->syncwait.sync64 ?
 			 sizeof(struct panthor_syncobj_64b) :
 			 sizeof(struct panthor_syncobj_32b),
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap()
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
                   ` (4 preceding siblings ...)
  2026-01-09 13:07 ` [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object Boris Brezillon
@ 2026-01-09 13:07 ` Boris Brezillon
  2026-01-12 12:15   ` Steven Price
                     ` (2 more replies)
  2026-01-09 13:07 ` [PATCH v1 7/9] drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for reclaim Boris Brezillon
                   ` (3 subsequent siblings)
  9 siblings, 3 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:07 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

Defer pages allocation until their first access.

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
---
 drivers/gpu/drm/panthor/panthor_gem.c | 119 ++++++++++++++++----------
 1 file changed, 75 insertions(+), 44 deletions(-)

diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
index 0e52c7a07c87..44f05bd957e7 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.c
+++ b/drivers/gpu/drm/panthor/panthor_gem.c
@@ -600,15 +600,6 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
 	if (is_cow_mapping(vma->vm_flags))
 		return -EINVAL;
 
-	dma_resv_lock(obj->resv, NULL);
-	ret = panthor_gem_backing_get_pages_locked(bo);
-	if (!ret)
-		ret = panthor_gem_prep_for_cpu_map_locked(bo);
-	dma_resv_unlock(obj->resv);
-
-	if (ret)
-		return ret;
-
 	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
 	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
 	if (should_map_wc(bo))
@@ -628,82 +619,122 @@ static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
 	return res;
 }
 
-static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
+static vm_fault_t insert_page(struct vm_fault *vmf, struct page *page)
 {
+	struct vm_area_struct *vma = vmf->vma;
+	vm_fault_t ret;
+
 #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
 	unsigned long pfn = page_to_pfn(page);
 	unsigned long paddr = pfn << PAGE_SHIFT;
-	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
+	bool aligned = (vmf->address & ~PMD_MASK) == (paddr & ~PMD_MASK);
 
 	if (aligned &&
 	    pmd_none(*vmf->pmd) &&
 	    folio_test_pmd_mappable(page_folio(page))) {
 		pfn &= PMD_MASK >> PAGE_SHIFT;
-		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
-			return true;
+		ret = vmf_insert_pfn_pmd(vmf, pfn, false);
+		if (ret == VM_FAULT_NOPAGE)
+			return VM_FAULT_NOPAGE;
 	}
 #endif
 
-	return false;
+	return vmf_insert_pfn(vma, vmf->address, page_to_pfn(page));
 }
 
-static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
+static vm_fault_t nonblocking_page_setup(struct vm_fault *vmf, pgoff_t page_offset)
 {
 	struct vm_area_struct *vma = vmf->vma;
-	struct drm_gem_object *obj = vma->vm_private_data;
 	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
-	loff_t num_pages = obj->size >> PAGE_SHIFT;
 	vm_fault_t ret;
-	pgoff_t page_offset;
-	unsigned long pfn;
 
-	/* Offset to faulty address in the VMA. */
-	page_offset = vmf->pgoff - vma->vm_pgoff;
+	if (!dma_resv_trylock(bo->base.resv))
+		return VM_FAULT_RETRY;
 
-	dma_resv_lock(bo->base.resv, NULL);
+	if (bo->backing.pages)
+		ret = insert_page(vmf, bo->backing.pages[page_offset]);
+	else
+		ret = VM_FAULT_RETRY;
 
-	if (page_offset >= num_pages ||
-	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
-		ret = VM_FAULT_SIGBUS;
-		goto out;
+	dma_resv_unlock(bo->base.resv);
+	return ret;
+}
+
+static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
+				      struct panthor_gem_object *bo,
+				      pgoff_t page_offset, bool mmap_lock_held)
+{
+	vm_fault_t ret;
+	int err;
+
+	if (vmf->flags & FAULT_FLAG_INTERRUPTIBLE) {
+		err = dma_resv_lock_interruptible(bo->base.resv, NULL);
+		if (err)
+			return mmap_lock_held ? VM_FAULT_NOPAGE : VM_FAULT_RETRY;
+	} else {
+		dma_resv_lock(bo->base.resv, NULL);
 	}
 
-	if (try_map_pmd(vmf, vmf->address, bo->backing.pages[page_offset])) {
-		ret = VM_FAULT_NOPAGE;
-		goto out;
+	err = panthor_gem_backing_get_pages_locked(bo);
+	if (!err)
+		err = panthor_gem_prep_for_cpu_map_locked(bo);
+
+	if (err) {
+		ret = mmap_lock_held ? VM_FAULT_SIGBUS : VM_FAULT_RETRY;
+	} else {
+		struct page *page = bo->backing.pages[page_offset];
+
+		if (mmap_lock_held)
+			ret = insert_page(vmf, page);
+		else
+			ret = VM_FAULT_RETRY;
 	}
 
-	pfn = page_to_pfn(bo->backing.pages[page_offset]);
-	ret = vmf_insert_pfn(vma, vmf->address, pfn);
-
- out:
 	dma_resv_unlock(bo->base.resv);
 
 	return ret;
 }
 
-static void panthor_gem_vm_open(struct vm_area_struct *vma)
+static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
 {
+	struct vm_area_struct *vma = vmf->vma;
 	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
+	loff_t num_pages = bo->base.size >> PAGE_SHIFT;
+	pgoff_t page_offset;
+	vm_fault_t ret;
 
-	drm_WARN_ON(bo->base.dev, drm_gem_is_imported(&bo->base));
+	/* We don't use vmf->pgoff since that has the fake offset */
+	page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
+	if (page_offset >= num_pages)
+		return VM_FAULT_SIGBUS;
 
-	dma_resv_lock(bo->base.resv, NULL);
+	ret = nonblocking_page_setup(vmf, page_offset);
+	if (ret != VM_FAULT_RETRY)
+		return ret;
 
-	/* We should have already pinned the pages when the buffer was first
-	 * mmap'd, vm_open() just grabs an additional reference for the new
-	 * mm the vma is getting copied into (ie. on fork()).
-	 */
-	drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages);
+	/* Check if we're allowed to retry. */
+	if (fault_flag_allow_retry_first(vmf->flags)) {
+		/* If we're allowed to retry but not wait here, return
+		 * immediately, the wait will be done when the fault
+		 * handler is called again, with the mmap_lock held.
+		 */
+		if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT)
+			return VM_FAULT_RETRY;
 
-	dma_resv_unlock(bo->base.resv);
+		/* Wait with the mmap lock released, if we're allowed to. */
+		drm_gem_object_get(&bo->base);
+		mmap_read_unlock(vmf->vma->vm_mm);
+		ret = blocking_page_setup(vmf, bo, page_offset, false);
+		drm_gem_object_put(&bo->base);
+		return ret;
+	}
 
-	drm_gem_vm_open(vma);
+	return blocking_page_setup(vmf, bo, page_offset, true);
 }
 
 const struct vm_operations_struct panthor_gem_vm_ops = {
 	.fault = panthor_gem_fault,
-	.open = panthor_gem_vm_open,
+	.open = drm_gem_vm_open,
 	.close = drm_gem_vm_close,
 };
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v1 7/9] drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for reclaim
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
                   ` (5 preceding siblings ...)
  2026-01-09 13:07 ` [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap() Boris Brezillon
@ 2026-01-09 13:07 ` Boris Brezillon
  2026-01-12 12:21   ` Steven Price
  2026-01-15 17:40   ` Liviu Dudau
  2026-01-09 13:08 ` [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO Boris Brezillon
                   ` (2 subsequent siblings)
  9 siblings, 2 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:07 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

We're gonna need just the page table reservation logic when we restore
evicted BO mappings, so let's prepare for that by extracting the
op_ctx init and page table pre-allocation into separate helpers.

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
---
 drivers/gpu/drm/panthor/panthor_mmu.c | 70 ++++++++++++++++-----------
 1 file changed, 42 insertions(+), 28 deletions(-)

diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
index c323b7123713..3290e0b5facb 100644
--- a/drivers/gpu/drm/panthor/panthor_mmu.c
+++ b/drivers/gpu/drm/panthor/panthor_mmu.c
@@ -1170,6 +1170,45 @@ panthor_vm_op_ctx_prealloc_vmas(struct panthor_vm_op_ctx *op_ctx)
 	return 0;
 }
 
+static void panthor_vm_init_op_ctx(struct panthor_vm_op_ctx *op_ctx,
+				   u64 size, u64 va, u32 flags)
+{
+	memset(op_ctx, 0, sizeof(*op_ctx));
+	op_ctx->flags = flags;
+	op_ctx->va.range = size;
+	op_ctx->va.addr = va;
+}
+
+static int panthor_vm_op_ctx_prealloc_pts(struct panthor_vm_op_ctx *op_ctx)
+{
+	u64 size = op_ctx->va.range;
+	u64 va = op_ctx->va.addr;
+	int ret;
+
+	/* L1, L2 and L3 page tables.
+	 * We could optimize L3 allocation by iterating over the sgt and merging
+	 * 2M contiguous blocks, but it's simpler to over-provision and return
+	 * the pages if they're not used.
+	 */
+	u64 pt_count = ((ALIGN(va + size, 1ull << 39) - ALIGN_DOWN(va, 1ull << 39)) >> 39) +
+		       ((ALIGN(va + size, 1ull << 30) - ALIGN_DOWN(va, 1ull << 30)) >> 30) +
+		       ((ALIGN(va + size, 1ull << 21) - ALIGN_DOWN(va, 1ull << 21)) >> 21);
+
+	op_ctx->rsvd_page_tables.pages = kcalloc(pt_count,
+						 sizeof(*op_ctx->rsvd_page_tables.pages),
+						 GFP_KERNEL);
+	if (!op_ctx->rsvd_page_tables.pages)
+		return -ENOMEM;
+
+	ret = kmem_cache_alloc_bulk(pt_cache, GFP_KERNEL, pt_count,
+				    op_ctx->rsvd_page_tables.pages);
+	op_ctx->rsvd_page_tables.count = ret;
+	if (ret != pt_count)
+		return -ENOMEM;
+
+	return 0;
+}
+
 #define PANTHOR_VM_BIND_OP_MAP_FLAGS \
 	(DRM_PANTHOR_VM_BIND_OP_MAP_READONLY | \
 	 DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC | \
@@ -1185,7 +1224,6 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
 {
 	struct drm_gpuvm_bo *preallocated_vm_bo;
 	struct sg_table *sgt = NULL;
-	u64 pt_count;
 	int ret;
 
 	if (!bo)
@@ -1204,10 +1242,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
 	    bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm))
 		return -EINVAL;
 
-	memset(op_ctx, 0, sizeof(*op_ctx));
-	op_ctx->flags = flags;
-	op_ctx->va.range = size;
-	op_ctx->va.addr = va;
+	panthor_vm_init_op_ctx(op_ctx, size, va, flags);
 
 	ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx);
 	if (ret)
@@ -1250,30 +1285,9 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
 
 	op_ctx->map.bo_offset = offset;
 
-	/* L1, L2 and L3 page tables.
-	 * We could optimize L3 allocation by iterating over the sgt and merging
-	 * 2M contiguous blocks, but it's simpler to over-provision and return
-	 * the pages if they're not used.
-	 */
-	pt_count = ((ALIGN(va + size, 1ull << 39) - ALIGN_DOWN(va, 1ull << 39)) >> 39) +
-		   ((ALIGN(va + size, 1ull << 30) - ALIGN_DOWN(va, 1ull << 30)) >> 30) +
-		   ((ALIGN(va + size, 1ull << 21) - ALIGN_DOWN(va, 1ull << 21)) >> 21);
-
-	op_ctx->rsvd_page_tables.pages = kcalloc(pt_count,
-						 sizeof(*op_ctx->rsvd_page_tables.pages),
-						 GFP_KERNEL);
-	if (!op_ctx->rsvd_page_tables.pages) {
-		ret = -ENOMEM;
+	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
+	if (ret)
 		goto err_cleanup;
-	}
-
-	ret = kmem_cache_alloc_bulk(pt_cache, GFP_KERNEL, pt_count,
-				    op_ctx->rsvd_page_tables.pages);
-	op_ctx->rsvd_page_tables.count = ret;
-	if (ret != pt_count) {
-		ret = -ENOMEM;
-		goto err_cleanup;
-	}
 
 	/* Insert BO into the extobj list last, when we know nothing can fail. */
 	dma_resv_lock(panthor_vm_resv(vm), NULL);
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
                   ` (6 preceding siblings ...)
  2026-01-09 13:07 ` [PATCH v1 7/9] drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for reclaim Boris Brezillon
@ 2026-01-09 13:08 ` Boris Brezillon
  2026-01-12 12:33   ` Steven Price
  2026-01-09 13:08 ` [PATCH v1 9/9] drm/panthor: Add a GEM shrinker Boris Brezillon
  2026-01-12  8:37 ` [PATCH v1 0/9] " Boris Brezillon
  9 siblings, 1 reply; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:08 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

This will be used to order things by reclaimability.

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
---
 drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
 drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
 2 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
index 44f05bd957e7..458d22380e96 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.c
+++ b/drivers/gpu/drm/panthor/panthor_gem.c
@@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
 	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
 			  refcount_read(&bo->cmap.vaddr_use_count));
 	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
+	drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
 }
 
 static int panthor_gem_pin_locked(struct drm_gem_object *obj)
@@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
 	if (is_cow_mapping(vma->vm_flags))
 		return -EINVAL;
 
+	if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
+		dma_resv_lock(obj->resv, NULL);
+		if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
+			refcount_set(&bo->cmap.mmap_count, 1);
+		dma_resv_unlock(obj->resv);
+	}
+
 	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
 	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
 	if (should_map_wc(bo))
@@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
 	return blocking_page_setup(vmf, bo, page_offset, true);
 }
 
+static void panthor_gem_vm_open(struct vm_area_struct *vma)
+{
+	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
+
+	/* mmap_count must have been incremented at mmap time, so it can't be
+	 * zero here.
+	 */
+	if (!drm_gem_is_imported(&bo->base))
+		drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
+
+	drm_gem_vm_open(vma);
+}
+
+static void panthor_gem_vm_close(struct vm_area_struct *vma)
+{
+	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
+
+	if (drm_gem_is_imported(&bo->base))
+		goto out;
+
+	if (refcount_dec_not_one(&bo->cmap.mmap_count))
+		goto out;
+
+	dma_resv_lock(bo->base.resv, NULL);
+	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
+		refcount_set(&bo->cmap.mmap_count, 0);
+	dma_resv_unlock(bo->base.resv);
+
+out:
+	drm_gem_object_put(&bo->base);
+}
+
 const struct vm_operations_struct panthor_gem_vm_ops = {
 	.fault = panthor_gem_fault,
-	.open = drm_gem_vm_open,
-	.close = drm_gem_vm_close,
+	.open = panthor_gem_vm_open,
+	.close = panthor_gem_vm_close,
 };
 
 static const struct drm_gem_object_funcs panthor_gem_funcs = {
diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
index b66478c9590c..c0a18dca732c 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.h
+++ b/drivers/gpu/drm/panthor/panthor_gem.h
@@ -80,6 +80,9 @@ struct panthor_gem_cpu_map {
 
 	/** @vaddr_use_count: Number of active vmap() requests on this GEM */
 	refcount_t vaddr_use_count;
+
+	/** @mmap_count: Number of active mmap() requests on this GEM */
+	refcount_t mmap_count;
 };
 
 /**
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
                   ` (7 preceding siblings ...)
  2026-01-09 13:08 ` [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO Boris Brezillon
@ 2026-01-09 13:08 ` Boris Brezillon
  2026-01-14 15:05   ` Steven Price
                     ` (2 more replies)
  2026-01-12  8:37 ` [PATCH v1 0/9] " Boris Brezillon
  9 siblings, 3 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-09 13:08 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

From: Akash Goel <akash.goel@arm.com>

This implementation is losely based on the MSM shrinker, and it's
relying on the drm_gpuvm eviction/validation infrastructure.

Right now we only support swapout/eviction, but we could add an extra
flag to specify when buffer content doesn't need to be preserved to
avoid the swapout/swapin dance.

Locking is a bit of a nightmare, but using _trylock() all the way in
the reclaim path seems to make lockdep happy. And yes, we might be
missing opportunities to reclaim when the system is under heavy GPU
load/heavy memory pressure/heavy GPU VM activity, but that's better
than no reclaim at all.

Signed-off-by: Akash Goel <akash.goel@arm.com>
Co-developed-by: Boris Brezillon <boris.brezillon@collabora.com>
Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
---
 drivers/gpu/drm/panthor/panthor_device.c |  11 +-
 drivers/gpu/drm/panthor/panthor_device.h |  73 ++++
 drivers/gpu/drm/panthor/panthor_gem.c    | 427 ++++++++++++++++++++++-
 drivers/gpu/drm/panthor/panthor_gem.h    |  67 ++++
 drivers/gpu/drm/panthor/panthor_mmu.c    | 338 +++++++++++++++++-
 drivers/gpu/drm/panthor/panthor_mmu.h    |   8 +
 6 files changed, 901 insertions(+), 23 deletions(-)

diff --git a/drivers/gpu/drm/panthor/panthor_device.c b/drivers/gpu/drm/panthor/panthor_device.c
index 54fbb1aa07c5..bc62a498a8a8 100644
--- a/drivers/gpu/drm/panthor/panthor_device.c
+++ b/drivers/gpu/drm/panthor/panthor_device.c
@@ -2,6 +2,7 @@
 /* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
 /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
 /* Copyright 2023 Collabora ltd. */
+/* Copyright 2025 ARM Limited. All rights reserved. */
 
 #include <linux/clk.h>
 #include <linux/mm.h>
@@ -122,6 +123,7 @@ void panthor_device_unplug(struct panthor_device *ptdev)
 	panthor_sched_unplug(ptdev);
 	panthor_fw_unplug(ptdev);
 	panthor_mmu_unplug(ptdev);
+	panthor_gem_shrinker_unplug(ptdev);
 	panthor_gpu_unplug(ptdev);
 	panthor_pwr_unplug(ptdev);
 
@@ -291,10 +293,14 @@ int panthor_device_init(struct panthor_device *ptdev)
 	if (ret)
 		goto err_unplug_gpu;
 
-	ret = panthor_mmu_init(ptdev);
+	ret = panthor_gem_shrinker_init(ptdev);
 	if (ret)
 		goto err_unplug_gpu;
 
+	ret = panthor_mmu_init(ptdev);
+	if (ret)
+		goto err_unplug_shrinker;
+
 	ret = panthor_fw_init(ptdev);
 	if (ret)
 		goto err_unplug_mmu;
@@ -326,6 +332,9 @@ int panthor_device_init(struct panthor_device *ptdev)
 err_unplug_mmu:
 	panthor_mmu_unplug(ptdev);
 
+err_unplug_shrinker:
+	panthor_gem_shrinker_unplug(ptdev);
+
 err_unplug_gpu:
 	panthor_gpu_unplug(ptdev);
 
diff --git a/drivers/gpu/drm/panthor/panthor_device.h b/drivers/gpu/drm/panthor/panthor_device.h
index f35e52b9546a..bc348aa9634e 100644
--- a/drivers/gpu/drm/panthor/panthor_device.h
+++ b/drivers/gpu/drm/panthor/panthor_device.h
@@ -14,6 +14,7 @@
 #include <linux/spinlock.h>
 
 #include <drm/drm_device.h>
+#include <drm/drm_gem.h>
 #include <drm/drm_mm.h>
 #include <drm/gpu_scheduler.h>
 #include <drm/panthor_drm.h>
@@ -157,6 +158,78 @@ struct panthor_device {
 	/** @devfreq: Device frequency scaling management data. */
 	struct panthor_devfreq *devfreq;
 
+	/** @reclaim: Reclaim related stuff */
+	struct {
+		/** @reclaim.shrinker: Shrinker instance */
+		struct shrinker *shrinker;
+
+		/** @reclaim.lock: Lock protecting all LRUs */
+		struct mutex lock;
+
+		/**
+		 * @reclaim.unused: BOs with unused pages
+		 *
+		 * Basically all buffers that got mmapped, vmapped or GPU mapped and
+		 * then unmapped. There should be no contention on these buffers,
+		 * making them ideal to reclaim.
+		 */
+		struct drm_gem_lru unused;
+
+		/**
+		 * @reclaim.mmapped: mmap()-ed buffers
+		 *
+		 * Those are relatively easy to reclaim since we don't need user
+		 * agreement, we can simply teardown the mapping and let it fault on
+		 * the next access.
+		 */
+		struct drm_gem_lru mmapped;
+
+		/**
+		 * @reclaim.vms: VM LRU list
+		 *
+		 * VMs that have reclaimable BOs only mapped to a single VM are placed
+		 * in this LRU. Reclaiming such BOs implies waiting for VM idleness
+		 * (no in-flight GPU jobs targeting this VM), meaning we can't reclaim
+		 * those if we're in a context where we can't block/sleep.
+		 */
+		struct list_head vms;
+
+		/**
+		 * @reclaim.gpu_mapped_shared: shared BO LRU list
+		 *
+		 * That's the most tricky BO type to reclaim, because it involves
+		 * tearing down all mappings in all VMs where this BO is mapped,
+		 * which increases the risk of contention and thus decreases the
+		 * likeliness of success.
+		 */
+		struct drm_gem_lru gpu_mapped_shared;
+
+		/**
+		 * @reclaim.gpu_mapped_count: Global counter of pages that are GPU mapped
+		 *
+		 * Allows us to get the number of reclaimable pages without walking
+		 * the vms and gpu_mapped_shared LRUs.
+		 */
+		long gpu_mapped_count;
+
+		/**
+		 * @reclaim.retry_count: Number of times we ran the shrinker without being
+		 * able to reclaim stuff
+		 *
+		 * Used to stop scanning GEMs when too many attempts were made
+		 * without progress.
+		 */
+		atomic_t retry_count;
+
+#ifdef CONFIG_DEBUG_FS
+		/**
+		 * @reclaim.nr_pages_reclaimed_on_last_scan: Number of pages reclaimed on the last
+		 * shrinker scan
+		 */
+		unsigned long nr_pages_reclaimed_on_last_scan;
+#endif
+	} reclaim;
+
 	/** @unplug: Device unplug related fields. */
 	struct {
 		/** @lock: Lock used to serialize unplug operations. */
diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
index 458d22380e96..065956a1f0fb 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.c
+++ b/drivers/gpu/drm/panthor/panthor_gem.c
@@ -2,8 +2,10 @@
 /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
 /* Copyright 2023 Collabora ltd. */
 /* Copyright 2025 Amazon.com, Inc. or its affiliates */
+/* Copyright 2025 ARM Limited. All rights reserved. */
 
 #include <linux/cleanup.h>
+#include <linux/debugfs.h>
 #include <linux/dma-buf.h>
 #include <linux/dma-mapping.h>
 #include <linux/err.h>
@@ -12,6 +14,8 @@
 
 #include <drm/drm_debugfs.h>
 #include <drm/drm_file.h>
+#include <drm/drm_gpuvm.h>
+#include <drm/drm_managed.h>
 #include <drm/drm_prime.h>
 #include <drm/drm_print.h>
 #include <drm/panthor_drm.h>
@@ -114,6 +118,98 @@ should_map_wc(struct panthor_gem_object *bo)
 	return true;
 }
 
+static bool is_gpu_mapped(struct panthor_gem_object *bo,
+			  enum panthor_gem_reclaim_state *state)
+{
+	struct drm_gpuvm *vm = NULL;
+	struct drm_gpuvm_bo *vm_bo;
+
+	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
+		if (!vm) {
+			*state = PANTHOR_GEM_GPU_MAPPED_PRIVATE;
+			vm = vm_bo->vm;
+		} else if (vm != vm_bo->vm) {
+			*state = PANTHOR_GEM_GPU_MAPPED_SHARED;
+			break;
+		}
+	}
+
+	return !!vm;
+}
+
+static enum panthor_gem_reclaim_state
+panthor_gem_evaluate_reclaim_state_locked(struct panthor_gem_object *bo)
+{
+	enum panthor_gem_reclaim_state gpu_mapped_state;
+
+	dma_resv_assert_held(bo->base.resv);
+	lockdep_assert_held(&bo->base.gpuva.lock);
+
+	/* If pages have not been allocated, there's nothing to reclaim. */
+	if (!bo->backing.pages)
+		return PANTHOR_GEM_UNRECLAIMABLE;
+
+	/* If memory is pinned, we prevent reclaim. */
+	if (refcount_read(&bo->backing.pin_count))
+		return PANTHOR_GEM_UNRECLAIMABLE;
+
+	if (is_gpu_mapped(bo, &gpu_mapped_state))
+		return gpu_mapped_state;
+
+	if (refcount_read(&bo->cmap.mmap_count) && bo->backing.pages)
+		return PANTHOR_GEM_MMAPPED;
+
+	return PANTHOR_GEM_UNUSED;
+}
+
+void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
+					     enum panthor_gem_reclaim_state *old_statep)
+{
+	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
+	enum panthor_gem_reclaim_state old_state = bo->reclaim_state;
+	enum panthor_gem_reclaim_state new_state;
+	bool was_gpu_mapped, is_gpu_mapped;
+
+	if (old_statep)
+		*old_statep = old_state;
+
+	new_state = panthor_gem_evaluate_reclaim_state_locked(bo);
+	if (new_state == old_state)
+		return;
+
+	was_gpu_mapped = old_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
+			 old_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
+	is_gpu_mapped = new_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
+			new_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
+
+	if (is_gpu_mapped && !was_gpu_mapped)
+		ptdev->reclaim.gpu_mapped_count += bo->base.size >> PAGE_SHIFT;
+	else if (!is_gpu_mapped && was_gpu_mapped)
+		ptdev->reclaim.gpu_mapped_count -= bo->base.size >> PAGE_SHIFT;
+
+	switch (new_state) {
+	case PANTHOR_GEM_UNUSED:
+		drm_gem_lru_move_tail(&ptdev->reclaim.unused, &bo->base);
+		break;
+	case PANTHOR_GEM_MMAPPED:
+		drm_gem_lru_move_tail(&ptdev->reclaim.mmapped, &bo->base);
+		break;
+	case PANTHOR_GEM_GPU_MAPPED_PRIVATE:
+		panthor_vm_update_bo_reclaim_lru_locked(bo);
+		break;
+	case PANTHOR_GEM_GPU_MAPPED_SHARED:
+		drm_gem_lru_move_tail(&ptdev->reclaim.gpu_mapped_shared, &bo->base);
+		break;
+	case PANTHOR_GEM_UNRECLAIMABLE:
+		drm_gem_lru_remove(&bo->base);
+		break;
+	default:
+		break;
+	}
+
+	bo->reclaim_state = new_state;
+}
+
 static void
 panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
 {
@@ -153,8 +249,12 @@ static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
 		return 0;
 
 	ret = panthor_gem_backing_get_pages_locked(bo);
-	if (!ret)
+	if (!ret) {
 		refcount_set(&bo->backing.pin_count, 1);
+		mutex_lock(&bo->base.gpuva.lock);
+		panthor_gem_update_reclaim_state_locked(bo, NULL);
+		mutex_unlock(&bo->base.gpuva.lock);
+	}
 
 	return ret;
 }
@@ -167,8 +267,12 @@ static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
 	/* We don't release anything when pin_count drops to zero.
 	 * Pages stay there until an explicit cleanup is requested.
 	 */
-	if (!refcount_dec_not_one(&bo->backing.pin_count))
+	if (!refcount_dec_not_one(&bo->backing.pin_count)) {
 		refcount_set(&bo->backing.pin_count, 0);
+		mutex_lock(&bo->base.gpuva.lock);
+		panthor_gem_update_reclaim_state_locked(bo, NULL);
+		mutex_unlock(&bo->base.gpuva.lock);
+	}
 }
 
 static void
@@ -531,6 +635,49 @@ void panthor_gem_unpin(struct panthor_gem_object *bo)
 	dma_resv_unlock(bo->base.resv);
 }
 
+int panthor_gem_swapin_locked(struct panthor_gem_object *bo)
+{
+	struct sg_table *sgt;
+	int ret;
+
+	dma_resv_assert_held(bo->base.resv);
+
+	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
+		return -EINVAL;
+
+	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
+		return -EINVAL;
+
+	ret = panthor_gem_backing_get_pages_locked(bo);
+	if (ret)
+		return ret;
+
+	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
+	if (IS_ERR(sgt))
+		return PTR_ERR(sgt);
+
+	return 0;
+}
+
+static void panthor_gem_evict_locked(struct panthor_gem_object *bo)
+{
+	dma_resv_assert_held(bo->base.resv);
+	lockdep_assert_held(&bo->base.gpuva.lock);
+
+	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
+		return;
+
+	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
+		return;
+
+	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
+		return;
+
+	panthor_gem_dev_map_cleanup(bo);
+	panthor_gem_backing_cleanup(bo);
+	panthor_gem_update_reclaim_state_locked(bo, NULL);
+}
+
 static struct sg_table *panthor_gem_get_sg_table(struct drm_gem_object *obj)
 {
 	struct panthor_gem_object *bo = to_panthor_bo(obj);
@@ -692,6 +839,10 @@ static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
 	} else {
 		struct page *page = bo->backing.pages[page_offset];
 
+		mutex_lock(&bo->base.gpuva.lock);
+		panthor_gem_update_reclaim_state_locked(bo, NULL);
+		mutex_unlock(&bo->base.gpuva.lock);
+
 		if (mmap_lock_held)
 			ret = insert_page(vmf, page);
 		else
@@ -764,8 +915,12 @@ static void panthor_gem_vm_close(struct vm_area_struct *vma)
 		goto out;
 
 	dma_resv_lock(bo->base.resv, NULL);
-	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
+	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
 		refcount_set(&bo->cmap.mmap_count, 0);
+		mutex_lock(&bo->base.gpuva.lock);
+		panthor_gem_update_reclaim_state_locked(bo, NULL);
+		mutex_unlock(&bo->base.gpuva.lock);
+	}
 	dma_resv_unlock(bo->base.resv);
 
 out:
@@ -801,6 +956,7 @@ panthor_gem_alloc_object(uint32_t flags)
 	if (!bo)
 		return ERR_PTR(-ENOMEM);
 
+	bo->reclaim_state = PANTHOR_GEM_UNRECLAIMABLE;
 	bo->base.funcs = &panthor_gem_funcs;
 	bo->flags = flags;
 	mutex_init(&bo->label.lock);
@@ -1041,11 +1197,13 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
  */
 void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
 {
+	struct panthor_device *ptdev;
 	struct panthor_vm *vm;
 
 	if (IS_ERR_OR_NULL(bo))
 		return;
 
+	ptdev = container_of(bo->obj->dev, struct panthor_device, base);
 	vm = bo->vm;
 	panthor_kernel_bo_vunmap(bo);
 
@@ -1053,6 +1211,8 @@ void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
 		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
 	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
 	panthor_vm_free_va(vm, &bo->va_node);
+	if (vm == panthor_fw_vm(ptdev))
+		panthor_gem_unpin(to_panthor_bo(bo->obj));
 	drm_gem_object_put(bo->obj);
 	panthor_vm_put(vm);
 	kfree(bo);
@@ -1101,6 +1261,12 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
 
 	kbo->obj = &bo->base;
 
+	if (vm == panthor_fw_vm(ptdev)) {
+		ret = panthor_gem_pin(bo);
+		if (ret)
+			goto err_put_obj;
+	}
+
 	panthor_gem_kernel_bo_set_label(kbo, name);
 
 	/* The system and GPU MMU page size might differ, which becomes a
@@ -1112,7 +1278,7 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
 	size = ALIGN(size, panthor_vm_page_size(vm));
 	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
 	if (ret)
-		goto err_put_obj;
+		goto err_unpin;
 
 	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
 	if (ret)
@@ -1124,6 +1290,10 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
 err_free_va:
 	panthor_vm_free_va(vm, &kbo->va_node);
 
+err_unpin:
+	if (vm == panthor_fw_vm(ptdev))
+		panthor_gem_unpin(bo);
+
 err_put_obj:
 	drm_gem_object_put(&bo->base);
 
@@ -1132,6 +1302,223 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
 	return ERR_PTR(ret);
 }
 
+static bool can_swap(void)
+{
+	return get_nr_swap_pages() > 0;
+}
+
+static bool can_block(struct shrink_control *sc)
+{
+	if (!(sc->gfp_mask & __GFP_DIRECT_RECLAIM))
+		return false;
+	return current_is_kswapd() || (sc->gfp_mask & __GFP_RECLAIM);
+}
+
+static unsigned long
+panthor_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
+{
+	struct panthor_device *ptdev = shrinker->private_data;
+	unsigned long count;
+
+	/* We currently don't have a flag to tell when the content of a
+	 * BO can be discarded.
+	 */
+	if (!can_swap())
+		return 0;
+
+	count = ptdev->reclaim.unused.count;
+	count += ptdev->reclaim.mmapped.count;
+
+	if (can_block(sc))
+		count += ptdev->reclaim.gpu_mapped_count;
+
+	return count;
+}
+
+static bool should_wait(enum panthor_gem_reclaim_state reclaim_state)
+{
+	return reclaim_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE ||
+	       reclaim_state == PANTHOR_GEM_GPU_MAPPED_SHARED;
+}
+
+bool panthor_gem_try_evict(struct drm_gem_object *obj,
+			   struct ww_acquire_ctx *ticket)
+{
+	/*
+	 * Track last locked entry for unwinding locks in error and
+	 * success paths
+	 */
+	struct panthor_gem_object *bo = to_panthor_bo(obj);
+	struct drm_gpuvm_bo *vm_bo, *last_locked = NULL;
+	enum panthor_gem_reclaim_state old_state;
+	int ret = 0;
+
+	/* To avoid potential lock ordering issue between bo_gpuva and
+	 * mapping->i_mmap_rwsem, unmap the pages from CPU side before
+	 * acquring the bo_gpuva lock. As the bo_resv lock is held, CPU
+	 * page fault handler won't be able to map in the pages whilst
+	 * eviction is in progress.
+	 */
+	drm_vma_node_unmap(&bo->base.vma_node, bo->base.dev->anon_inode->i_mapping);
+
+	/* We take this lock when walking the list to prevent
+	 * insertion/deletion.
+	 */
+	/* We can only trylock in that path, because
+	 * - allocation might happen while some of these locks are held
+	 * - lock ordering is different in other paths
+	 *     vm_resv -> bo_resv -> bo_gpuva
+	 *     vs
+	 *     bo_resv -> bo_gpuva -> vm_resv
+	 *
+	 * If we fail to lock that's fine, we back off and will get
+	 * back to it later.
+	 */
+	if (!mutex_trylock(&bo->base.gpuva.lock))
+		return false;
+
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+		struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
+
+		if (resv == obj->resv)
+			continue;
+
+		if (!dma_resv_trylock(resv)) {
+			ret = -EDEADLK;
+			goto out_unlock;
+		}
+
+		last_locked = vm_bo;
+	}
+
+	/* Update the state before trying to evict the buffer, if the state was
+	 * updated to something that's harder to reclaim (higher value in the
+	 * enum), skip it (will be processed when the relevant LRU is).
+	 */
+	panthor_gem_update_reclaim_state_locked(bo, &old_state);
+	if (old_state < bo->reclaim_state) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	/* Wait was too long, skip. */
+	if (should_wait(bo->reclaim_state) &&
+	    dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP, false, 10) <= 0) {
+		ret = -ETIMEDOUT;
+		goto out_unlock;
+	}
+
+	/* Couldn't teardown the GPU mappings? Skip. */
+	ret = panthor_vm_evict_bo_mappings_locked(bo);
+	if (ret)
+		goto out_unlock;
+
+	/* If everything went fine, evict the object. */
+	panthor_gem_evict_locked(bo);
+
+out_unlock:
+	if (last_locked) {
+		drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+			struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
+
+			if (resv == obj->resv)
+				continue;
+
+			dma_resv_unlock(resv);
+
+			if (last_locked == vm_bo)
+				break;
+		}
+	}
+	mutex_unlock(&bo->base.gpuva.lock);
+
+	return ret == 0;
+}
+
+static unsigned long
+panthor_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
+{
+	struct panthor_device *ptdev = shrinker->private_data;
+	unsigned long remaining = 0;
+	unsigned long freed = 0;
+
+	if (!can_swap())
+		goto out;
+
+	freed += drm_gem_lru_scan(&ptdev->reclaim.unused,
+				  sc->nr_to_scan - freed, &remaining,
+				  panthor_gem_try_evict, NULL);
+	if (freed >= sc->nr_to_scan)
+		goto out;
+
+	freed += drm_gem_lru_scan(&ptdev->reclaim.mmapped,
+				  sc->nr_to_scan - freed, &remaining,
+				  panthor_gem_try_evict, NULL);
+	if (freed >= sc->nr_to_scan)
+		goto out;
+
+	freed += panthor_mmu_reclaim_priv_bos(ptdev, sc->nr_to_scan - freed,
+					      &remaining, panthor_gem_try_evict);
+	if (freed >= sc->nr_to_scan)
+		goto out;
+
+	freed += drm_gem_lru_scan(&ptdev->reclaim.gpu_mapped_shared,
+				  sc->nr_to_scan - freed, &remaining,
+				  panthor_gem_try_evict, NULL);
+
+out:
+#ifdef CONFIG_DEBUG_FS
+	/* This is racy, but that's okay, because this is just debugfs
+	 * reporting and doesn't need to be accurate.
+	 */
+	ptdev->reclaim.nr_pages_reclaimed_on_last_scan = freed;
+#endif
+
+	/* If there are things to reclaim, try a couple times before giving up. */
+	if (!freed && remaining > 0 &&
+	    atomic_inc_return(&ptdev->reclaim.retry_count) < 2)
+		return 0;
+
+	/* There's nothing left to reclaim, or the resource are contended. Give up now. */
+	atomic_set(&ptdev->reclaim.retry_count, 0);
+	return SHRINK_STOP;
+}
+
+int panthor_gem_shrinker_init(struct panthor_device *ptdev)
+{
+	struct shrinker *shrinker;
+
+	drmm_mutex_init(&ptdev->base, &ptdev->reclaim.lock);
+	INIT_LIST_HEAD(&ptdev->reclaim.vms);
+	drm_gem_lru_init(&ptdev->reclaim.unused, &ptdev->reclaim.lock);
+	drm_gem_lru_init(&ptdev->reclaim.mmapped, &ptdev->reclaim.lock);
+	drm_gem_lru_init(&ptdev->reclaim.gpu_mapped_shared, &ptdev->reclaim.lock);
+	ptdev->reclaim.gpu_mapped_count = 0;
+
+	/* Teach lockdep about lock ordering wrt. shrinker: */
+	fs_reclaim_acquire(GFP_KERNEL);
+	might_lock(&ptdev->reclaim.lock);
+	fs_reclaim_release(GFP_KERNEL);
+
+	shrinker = shrinker_alloc(0, "drm-panthor-gem");
+	if (!shrinker)
+		return -ENOMEM;
+
+	shrinker->count_objects = panthor_gem_shrinker_count;
+	shrinker->scan_objects = panthor_gem_shrinker_scan;
+	shrinker->private_data = ptdev;
+	ptdev->reclaim.shrinker = shrinker;
+
+	shrinker_register(shrinker);
+	return 0;
+}
+
+void panthor_gem_shrinker_unplug(struct panthor_device *ptdev)
+{
+	if (ptdev->reclaim.shrinker)
+		shrinker_free(ptdev->reclaim.shrinker);
+}
+
 #ifdef CONFIG_DEBUG_FS
 struct gem_size_totals {
 	size_t size;
@@ -1250,10 +1637,42 @@ static struct drm_info_list panthor_gem_debugfs_list[] = {
 	{ "gems", panthor_gem_show_bos, 0, NULL },
 };
 
+static int shrink_get(void *data, u64 *val)
+{
+	struct panthor_device *ptdev =
+		container_of(data, struct panthor_device, base);
+
+	*val = ptdev->reclaim.nr_pages_reclaimed_on_last_scan;
+	return 0;
+}
+
+static int shrink_set(void *data, u64 val)
+{
+	struct panthor_device *ptdev =
+		container_of(data, struct panthor_device, base);
+	struct shrink_control sc = {
+		.gfp_mask = GFP_KERNEL,
+		.nr_to_scan = val,
+	};
+
+	fs_reclaim_acquire(GFP_KERNEL);
+	if (ptdev->reclaim.shrinker)
+		panthor_gem_shrinker_scan(ptdev->reclaim.shrinker, &sc);
+	fs_reclaim_release(GFP_KERNEL);
+
+	return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(panthor_gem_debugfs_shrink_fops,
+			 shrink_get, shrink_set,
+			 "0x%08llx\n");
+
 void panthor_gem_debugfs_init(struct drm_minor *minor)
 {
 	drm_debugfs_create_files(panthor_gem_debugfs_list,
 				 ARRAY_SIZE(panthor_gem_debugfs_list),
 				 minor->debugfs_root, minor);
+	debugfs_create_file("shrink", 0600, minor->debugfs_root,
+			    minor->dev, &panthor_gem_debugfs_shrink_fops);
 }
 #endif
diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
index c0a18dca732c..6cb5b597ff1e 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.h
+++ b/drivers/gpu/drm/panthor/panthor_gem.h
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 or MIT */
 /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
 /* Copyright 2023 Collabora ltd. */
+/* Copyright 2025 ARM Limited. All rights reserved. */
 
 #ifndef __PANTHOR_GEM_H__
 #define __PANTHOR_GEM_H__
@@ -93,6 +94,62 @@ struct panthor_gem_dev_map {
 	struct sg_table *sgt;
 };
 
+/**
+ * enum panthor_gem_reclaim_state - Reclaim state of a GEM object
+ *
+ * This is defined in descending reclaimability order and some part
+ * of the code depends on that.
+ */
+enum panthor_gem_reclaim_state {
+	/**
+	 * @PANTHOR_GEM_UNUSED: GEM is currently unused
+	 *
+	 * This can happen when the GEM was previously vmap-ed, mmap-ed,
+	 * and/or GPU mapped and got unmapped. Because pages are lazily
+	 * returned to the shmem layer, we want to keep a list of such
+	 * BOs, because they should be fairly easy to reclaim (no need
+	 * to wait for GPU to be done, and no need to tear down user
+	 * mappings either).
+	 */
+	PANTHOR_GEM_UNUSED,
+
+	/**
+	 * @PANTHOR_GEM_MMAPPED: GEM is currently mmap-ed
+	 *
+	 * When a GEM has pages allocated and the mmap_count is > 0, the
+	 * GEM is placed in the mmapped list. This comes right after
+	 * unused because we can relatively easily tear down user mappings.
+	 */
+	PANTHOR_GEM_MMAPPED,
+
+	/**
+	 * @PANTHOR_GEM_GPU_MAPPED_PRIVATE: GEM is GPU mapped to only one VM
+	 *
+	 * When a GEM is mapped to a single VM, reclaim requests have more
+	 * chances to succeed, because we only need to synchronize against
+	 * a single GPU context. This is more annoying than reclaiming
+	 * mmap-ed pages still, because we have to wait for in-flight jobs
+	 * to land, and we might not be able to acquire all necessary locks
+	 * at reclaim time either.
+	 */
+	PANTHOR_GEM_GPU_MAPPED_PRIVATE,
+
+	/**
+	 * @PANTHOR_GEM_GPU_MAPPED_SHARED: GEM is GPU mapped to multiple VMs
+	 *
+	 * Like PANTHOR_GEM_GPU_MAPPED_PRIVATE, but the synchronization across
+	 * VMs makes such BOs harder to reclaim.
+	 */
+	PANTHOR_GEM_GPU_MAPPED_SHARED,
+
+	/**
+	 * @PANTHOR_GEM_UNRECLAIMABLE: GEM can't be reclaimed
+	 *
+	 * Happens when the GEM memory is pinned.
+	 */
+	PANTHOR_GEM_UNRECLAIMABLE,
+};
+
 /**
  * struct panthor_gem_object - Driver specific GEM object.
  */
@@ -109,6 +166,9 @@ struct panthor_gem_object {
 	/** @dmap: Device mapping state */
 	struct panthor_gem_dev_map dmap;
 
+	/** @reclaim_state: Cached reclaim state */
+	enum panthor_gem_reclaim_state reclaim_state;
+
 	/**
 	 * @exclusive_vm_root_gem: Root GEM of the exclusive VM this GEM object
 	 * is attached to.
@@ -190,6 +250,13 @@ struct sg_table *
 panthor_gem_get_dev_sgt(struct panthor_gem_object *bo);
 int panthor_gem_pin(struct panthor_gem_object *bo);
 void panthor_gem_unpin(struct panthor_gem_object *bo);
+int panthor_gem_swapin_locked(struct panthor_gem_object *bo);
+void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
+					     enum panthor_gem_reclaim_state *old_state);
+bool panthor_gem_try_evict(struct drm_gem_object *obj,
+			   struct ww_acquire_ctx *ticket);
+int panthor_gem_shrinker_init(struct panthor_device *ptdev);
+void panthor_gem_shrinker_unplug(struct panthor_device *ptdev);
 
 void panthor_gem_bo_set_label(struct drm_gem_object *obj, const char *label);
 void panthor_gem_kernel_bo_set_label(struct panthor_kernel_bo *bo, const char *label);
diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
index 3290e0b5facb..ffd821b3be46 100644
--- a/drivers/gpu/drm/panthor/panthor_mmu.c
+++ b/drivers/gpu/drm/panthor/panthor_mmu.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0 or MIT
 /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
 /* Copyright 2023 Collabora ltd. */
+/* Copyright 2025 ARM Limited. All rights reserved. */
 
 #include <drm/drm_debugfs.h>
 #include <drm/drm_drv.h>
@@ -131,6 +132,9 @@ struct panthor_vma {
 	 * Only map related flags are accepted.
 	 */
 	u32 flags;
+
+	/** @evicted: True if the VMA has been evicted. */
+	bool evicted;
 };
 
 /**
@@ -191,13 +195,8 @@ struct panthor_vm_op_ctx {
 		/** @map.bo_offset: Offset in the buffer object. */
 		u64 bo_offset;
 
-		/**
-		 * @map.sgt: sg-table pointing to pages backing the GEM object.
-		 *
-		 * This is gathered at job creation time, such that we don't have
-		 * to allocate in ::run_job().
-		 */
-		struct sg_table *sgt;
+		/** @map.bo: the BO being mapped. */
+		struct panthor_gem_object *bo;
 
 		/**
 		 * @map.new_vma: The new VMA object that will be inserted to the VA tree.
@@ -385,6 +384,18 @@ struct panthor_vm {
 		/** @locked_region.size: Size of the locked region. */
 		u64 size;
 	} locked_region;
+
+	/** @reclaim: Fields related to BO reclaim. */
+	struct {
+		/** @reclaim.lru: LRU of BOs that are only mapped to this VM. */
+		struct drm_gem_lru lru;
+
+		/**
+		 * @reclaim.lru_node: Node used to insert the VM in
+		 * panthor_device::reclaim::vms.
+		 */
+		struct list_head lru_node;
+	} reclaim;
 };
 
 /**
@@ -678,6 +689,16 @@ int panthor_vm_active(struct panthor_vm *vm)
 	if (refcount_inc_not_zero(&vm->as.active_cnt))
 		goto out_dev_exit;
 
+	/* As soon as active is called, we place the VM as the end of the VM LRU.
+	 * If something fails after that, the only downside is that this VM that
+	 * never became active in the first place will be reclaimed last, but
+	 * that's an acceptable trade-off.
+	 */
+	mutex_lock(&ptdev->reclaim.lock);
+	if (vm->reclaim.lru.count)
+		list_move_tail(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
+	mutex_unlock(&ptdev->reclaim.lock);
+
 	/* Make sure we don't race with lock/unlock_region() calls
 	 * happening around VM bind operations.
 	 */
@@ -1074,7 +1095,15 @@ static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo)
 {
 	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
 
-	panthor_gem_unpin(bo);
+	/* We couldn't call this when we unlinked, because the resv lock can't
+	 * be taken in the dma signalling path, so call it now.
+	 */
+	dma_resv_lock(bo->base.resv, NULL);
+	mutex_lock(&bo->base.gpuva.lock);
+	panthor_gem_update_reclaim_state_locked(bo, NULL);
+	mutex_unlock(&bo->base.gpuva.lock);
+	dma_resv_unlock(bo->base.resv);
+
 	kfree(vm_bo);
 }
 
@@ -1095,6 +1124,11 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx,
 	if (op_ctx->map.vm_bo)
 		drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo);
 
+	if (op_ctx->map.bo) {
+		panthor_gem_unpin(op_ctx->map.bo);
+		drm_gem_object_put(&op_ctx->map.bo->base);
+	}
+
 	for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++)
 		kfree(op_ctx->preallocated_vmas[i]);
 
@@ -1255,18 +1289,17 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
 	if (ret)
 		goto err_cleanup;
 
+	drm_gem_object_get(&bo->base);
+	op_ctx->map.bo = bo;
+
 	sgt = panthor_gem_get_dev_sgt(bo);
 	if (IS_ERR(sgt)) {
-		panthor_gem_unpin(bo);
 		ret = PTR_ERR(sgt);
 		goto err_cleanup;
 	}
 
-	op_ctx->map.sgt = sgt;
-
 	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base);
 	if (!preallocated_vm_bo) {
-		panthor_gem_unpin(bo);
 		ret = -ENOMEM;
 		goto err_cleanup;
 	}
@@ -1280,9 +1313,19 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
 	dma_resv_lock(panthor_vm_resv(vm), NULL);
 	mutex_lock(&bo->base.gpuva.lock);
 	op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
+	if (panthor_vm_resv(vm) == bo->base.resv)
+		panthor_gem_update_reclaim_state_locked(bo, NULL);
 	mutex_unlock(&bo->base.gpuva.lock);
 	dma_resv_unlock(panthor_vm_resv(vm));
 
+	if (panthor_vm_resv(vm) != bo->base.resv) {
+		dma_resv_lock(bo->base.resv, NULL);
+		mutex_lock(&bo->base.gpuva.lock);
+		panthor_gem_update_reclaim_state_locked(bo, NULL);
+		mutex_unlock(&bo->base.gpuva.lock);
+		dma_resv_unlock(bo->base.resv);
+	}
+
 	op_ctx->map.bo_offset = offset;
 
 	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
@@ -1891,6 +1934,10 @@ static void panthor_vm_free(struct drm_gpuvm *gpuvm)
 	struct panthor_vm *vm = container_of(gpuvm, struct panthor_vm, base);
 	struct panthor_device *ptdev = vm->ptdev;
 
+	mutex_lock(&ptdev->reclaim.lock);
+	list_del_init(&vm->reclaim.lru_node);
+	mutex_unlock(&ptdev->reclaim.lock);
+
 	mutex_lock(&vm->heaps.lock);
 	if (drm_WARN_ON(&ptdev->base, vm->heaps.pool))
 		panthor_heap_pool_destroy(vm->heaps.pool);
@@ -2104,7 +2151,7 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
 	panthor_vma_init(vma, op_ctx->flags & PANTHOR_VM_MAP_FLAGS);
 
 	ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags),
-				   op_ctx->map.sgt, op->map.gem.offset,
+				   op_ctx->map.bo->dmap.sgt, op->map.gem.offset,
 				   op->map.va.range);
 	if (ret) {
 		panthor_vm_op_ctx_return_vma(op_ctx, vma);
@@ -2188,8 +2235,10 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
 	 * atomicity. panthor_vm_lock_region() bails out early if the new region
 	 * is already part of the locked region, so no need to do this check here.
 	 */
-	panthor_vm_lock_region(vm, unmap_start, unmap_range);
-	panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
+	if (!unmap_vma->evicted) {
+		panthor_vm_lock_region(vm, unmap_start, unmap_range);
+		panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
+	}
 
 	if (op->remap.prev) {
 		struct panthor_gem_object *bo = to_panthor_bo(op->remap.prev->gem.obj);
@@ -2203,6 +2252,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
 
 		prev_vma = panthor_vm_op_ctx_get_vma(op_ctx);
 		panthor_vma_init(prev_vma, unmap_vma->flags);
+		prev_vma->evicted = unmap_vma->evicted;
 	}
 
 	if (op->remap.next) {
@@ -2217,6 +2267,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
 
 		next_vma = panthor_vm_op_ctx_get_vma(op_ctx);
 		panthor_vma_init(next_vma, unmap_vma->flags);
+		next_vma->evicted = unmap_vma->evicted;
 	}
 
 	drm_gpuva_remap(prev_vma ? &prev_vma->base : NULL,
@@ -2246,19 +2297,197 @@ static int panthor_gpuva_sm_step_unmap(struct drm_gpuva_op *op,
 	struct panthor_vma *unmap_vma = container_of(op->unmap.va, struct panthor_vma, base);
 	struct panthor_vm *vm = priv;
 
-	panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
-			       unmap_vma->base.va.range);
+	if (!unmap_vma->evicted) {
+		panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
+				       unmap_vma->base.va.range);
+	}
+
 	drm_gpuva_unmap(&op->unmap);
 	panthor_vma_unlink(unmap_vma);
 	return 0;
 }
 
+void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
+{
+	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
+	struct panthor_vm *vm = NULL;
+	struct drm_gpuvm_bo *vm_bo;
+
+	dma_resv_assert_held(bo->base.resv);
+	lockdep_assert_held(&bo->base.gpuva.lock);
+
+	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
+		/* We're only supposed to have one vm_bo in the list if we get there. */
+		drm_WARN_ON(&ptdev->base, vm);
+		vm = container_of(vm_bo->vm, struct panthor_vm, base);
+
+		mutex_lock(&ptdev->reclaim.lock);
+		drm_gem_lru_move_tail_locked(&vm->reclaim.lru, &bo->base);
+		if (list_empty(&vm->reclaim.lru_node))
+			list_move(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
+		mutex_unlock(&ptdev->reclaim.lock);
+	}
+}
+
+int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
+		struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
+		struct drm_gpuva *va;
+
+		if (!mutex_trylock(&vm->op_lock))
+			return -EDEADLK;
+
+		drm_gpuvm_bo_evict(vm_bo, true);
+		drm_gpuvm_bo_for_each_va(va, vm_bo) {
+			struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
+
+			panthor_vm_lock_region(vm, va->va.addr, va->va.range);
+			panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);
+			panthor_vm_unlock_region(vm);
+			vma->evicted = true;
+		}
+
+		mutex_unlock(&vm->op_lock);
+	}
+
+	return 0;
+}
+
+static struct panthor_vma *select_evicted_vma(struct drm_gpuvm_bo *vm_bo,
+					      struct panthor_vm_op_ctx *op_ctx)
+{
+	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
+	struct panthor_vma *first_evicted_vma = NULL;
+	struct drm_gpuva *va;
+
+	/* Take op_lock to protect against va insertion/removal. */
+	mutex_lock(&vm->op_lock);
+	drm_gpuvm_bo_for_each_va(va, vm_bo) {
+		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
+
+		if (vma->evicted) {
+			first_evicted_vma = vma;
+			panthor_vm_init_op_ctx(op_ctx, va->va.range, va->va.addr, vma->flags);
+			op_ctx->map.bo_offset = va->gem.offset;
+			break;
+		}
+	}
+	mutex_unlock(&vm->op_lock);
+
+	return first_evicted_vma;
+}
+
+static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
+			     struct panthor_vma *evicted_vma,
+			     struct panthor_vm_op_ctx *op_ctx)
+{
+	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
+	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
+	struct drm_gpuva *va;
+	bool found = false;
+	int ret;
+
+	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
+	if (ret)
+		goto out_cleanup;
+
+	/* Take op_lock to protect against va insertion/removal. */
+	mutex_lock(&vm->op_lock);
+	drm_gpuvm_bo_for_each_va(va, vm_bo) {
+		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
+
+		if (vma != evicted_vma)
+			continue;
+
+		/* We can't rely solely on pointer equality, because the VMA might have been
+		 * freed and a new one allocated at the same address. If the evicted bit
+		 * is still set, we're sure it's our VMA, because population/eviction is
+		 * serialized with the BO resv lock.
+		 */
+		if (vma->evicted)
+			found = true;
+
+		break;
+	}
+
+	if (found) {
+		vm->op_ctx = op_ctx;
+		ret = panthor_vm_lock_region(vm, evicted_vma->base.va.addr,
+					     evicted_vma->base.va.range);
+		if (!ret) {
+			ret = panthor_vm_map_pages(vm, evicted_vma->base.va.addr,
+						   flags_to_prot(evicted_vma->flags),
+						   bo->dmap.sgt,
+						   evicted_vma->base.gem.offset,
+						   evicted_vma->base.va.range);
+		}
+
+		if (!ret)
+			evicted_vma->evicted = false;
+
+		panthor_vm_unlock_region(vm);
+		vm->op_ctx = NULL;
+	}
+
+	mutex_unlock(&vm->op_lock);
+
+out_cleanup:
+	panthor_vm_cleanup_op_ctx(op_ctx, vm);
+	return ret;
+}
+
+static int panthor_vm_restore_vmas(struct drm_gpuvm_bo *vm_bo)
+{
+	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
+	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
+	struct panthor_vm_op_ctx op_ctx;
+
+	if (drm_WARN_ON_ONCE(&vm->ptdev->base, !bo->dmap.sgt))
+		return -EINVAL;
+
+	for (struct panthor_vma *vma = select_evicted_vma(vm_bo, &op_ctx);
+	     vma; vma = select_evicted_vma(vm_bo, &op_ctx)) {
+		int ret;
+
+		ret = remap_evicted_vma(vm_bo, vma, &op_ctx);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int panthor_vm_bo_validate(struct drm_gpuvm_bo *vm_bo,
+				  struct drm_exec *exec)
+{
+	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
+	int ret;
+
+	ret = panthor_gem_swapin_locked(bo);
+	if (ret)
+		return ret;
+
+	ret = panthor_vm_restore_vmas(vm_bo);
+	if (ret)
+		return ret;
+
+	drm_gpuvm_bo_evict(vm_bo, false);
+	mutex_lock(&bo->base.gpuva.lock);
+	panthor_gem_update_reclaim_state_locked(bo, NULL);
+	mutex_unlock(&bo->base.gpuva.lock);
+	return 0;
+}
+
 static const struct drm_gpuvm_ops panthor_gpuvm_ops = {
 	.vm_free = panthor_vm_free,
 	.vm_bo_free = panthor_vm_bo_free,
 	.sm_step_map = panthor_gpuva_sm_step_map,
 	.sm_step_remap = panthor_gpuva_sm_step_remap,
 	.sm_step_unmap = panthor_gpuva_sm_step_unmap,
+	.vm_bo_validate = panthor_vm_bo_validate,
 };
 
 /**
@@ -2473,6 +2702,8 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
 	vm->kernel_auto_va.start = auto_kernel_va_start;
 	vm->kernel_auto_va.end = vm->kernel_auto_va.start + auto_kernel_va_size - 1;
 
+	drm_gem_lru_init(&vm->reclaim.lru, &ptdev->reclaim.lock);
+	INIT_LIST_HEAD(&vm->reclaim.lru_node);
 	INIT_LIST_HEAD(&vm->node);
 	INIT_LIST_HEAD(&vm->as.lru_node);
 	vm->as.id = -1;
@@ -2820,7 +3051,78 @@ int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec, struct panthor_vm
 	if (ret)
 		return ret;
 
-	return drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
+	ret = drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
+	if (ret)
+		return ret;
+
+	return drm_gpuvm_validate(&vm->base, exec);
+}
+
+unsigned long
+panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
+			     unsigned int nr_to_scan, unsigned long *remaining,
+			     bool (*shrink)(struct drm_gem_object *,
+					    struct ww_acquire_ctx *))
+{
+	unsigned long freed = 0;
+	LIST_HEAD(remaining_vms);
+	LIST_HEAD(vms);
+
+	mutex_lock(&ptdev->reclaim.lock);
+	list_splice_init(&ptdev->reclaim.vms, &vms);
+
+	while (freed < nr_to_scan) {
+		struct panthor_vm *vm;
+
+		vm = list_first_entry_or_null(&vms, typeof(*vm),
+					      reclaim.lru_node);
+		if (!vm)
+			break;
+
+		if (!kref_get_unless_zero(&vm->base.kref)) {
+			list_del_init(&vm->reclaim.lru_node);
+			continue;
+		}
+
+		mutex_unlock(&ptdev->reclaim.lock);
+
+		freed += drm_gem_lru_scan(&vm->reclaim.lru, nr_to_scan - freed,
+					  remaining, shrink, NULL);
+
+		mutex_lock(&ptdev->reclaim.lock);
+
+		/* If the VM is still in the temporary list, remove it so we
+		 * can proceed with the next VM.
+		 */
+		if (vm->reclaim.lru_node.prev == &vms) {
+			list_del_init(&vm->reclaim.lru_node);
+
+			/* Keep the VM around if there are still things to
+			 * reclaim, so we can preserve the LRU order when
+			 * re-inserting in ptdev->reclaim.vms at the end.
+			 */
+			if (vm->reclaim.lru.count > 0)
+				list_add_tail(&vm->reclaim.lru_node, &remaining_vms);
+		}
+
+		mutex_unlock(&ptdev->reclaim.lock);
+
+		panthor_vm_put(vm);
+
+		mutex_lock(&ptdev->reclaim.lock);
+	}
+
+	/* Re-insert VMs with remaining data to reclaim at the beginning of
+	 * the LRU. Note that any activeness change on the VM that happened
+	 * while we were reclaiming would have moved the VM out of our
+	 * temporary [remaining_]vms list, meaning anything we re-insert here
+	 * preserves the LRU order.
+	 */
+	list_splice_tail(&vms, &remaining_vms);
+	list_splice(&remaining_vms, &ptdev->reclaim.vms);
+	mutex_unlock(&ptdev->reclaim.lock);
+
+	return freed;
 }
 
 /**
diff --git a/drivers/gpu/drm/panthor/panthor_mmu.h b/drivers/gpu/drm/panthor/panthor_mmu.h
index 0e268fdfdb2f..3522fbbce369 100644
--- a/drivers/gpu/drm/panthor/panthor_mmu.h
+++ b/drivers/gpu/drm/panthor/panthor_mmu.h
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 or MIT */
 /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
 /* Copyright 2023 Collabora ltd. */
+/* Copyright 2025 ARM Limited. All rights reserved. */
 
 #ifndef __PANTHOR_MMU_H__
 #define __PANTHOR_MMU_H__
@@ -46,6 +47,13 @@ struct panthor_vm *panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
 				     u64 kernel_auto_va_start,
 				     u64 kernel_auto_va_size);
 
+void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo);
+int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo);
+unsigned long
+panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
+			     unsigned int nr_to_scan, unsigned long *remaining,
+			     bool (*shrink)(struct drm_gem_object *,
+					    struct ww_acquire_ctx *));
 int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec,
 					struct panthor_vm *vm,
 					u32 slot_count);
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 2/9] drm/gpuvm: Validate BOs in the extobj list when VM is resv protected
  2026-01-09 13:07 ` [PATCH v1 2/9] drm/gpuvm: Validate BOs in the extobj list when VM is resv protected Boris Brezillon
@ 2026-01-09 19:38   ` Danilo Krummrich
  2026-01-12  7:30     ` Boris Brezillon
  0 siblings, 1 reply; 62+ messages in thread
From: Danilo Krummrich @ 2026-01-09 19:38 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Akash Goel, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand, Matthew Brost,
	Thomas Hellström, Alice Ryhl, kernel

On Fri Jan 9, 2026 at 2:07 PM CET, Boris Brezillon wrote:
> When the VM is protected with the root object resv, evicted extobjs
> are not inserted in the evict list.

I'd rather say: "When the evicted object list is protected with the root object
reservation lock, ...".

> Make sure we record when the extobj
> list contains evicted BOs that are not already in the evict list so we
> can validate those when drm_gpuvm_validate() is called.

Why is this necessary in the first place? drm_gpuvm_prepare_objects() picks up
the affected external objects already.

> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>  drivers/gpu/drm/drm_gpuvm.c | 23 ++++++++++++++++++++++-
>  include/drm/drm_gpuvm.h     |  6 ++++++
>  2 files changed, 28 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
> index 0bb115b6b59c..95f0a1c843ad 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -1505,6 +1505,22 @@ drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>  			drm_gpuvm_bo_list_del_init(vm_bo, evict, false);
>  	}
>  
> +	if (READ_ONCE(gpuvm->extobj.check_evicted)) {
> +		list_for_each_entry_safe(vm_bo, next, &gpuvm->extobj.list,
> +					 list.entry.extobj) {

Running the external object list (again) seems wasteful given that this case can
never happen when drm_gpuvm_prepare_objects() is called before (which has to be
the case, given that somehow the objects have to be locked).

And even if for some reason you open code this, it would be much better to add a
helper doing the check when the object is locked.

> +			dma_resv_assert_held(vm_bo->obj->resv);
> +
> +			if (!vm_bo->evicted)
> +				continue;
> +
> +			ret = ops->vm_bo_validate(vm_bo, exec);
> +			if (ret)
> +				break;
> +		}
> +
> +		WRITE_ONCE(gpuvm->extobj.check_evicted, false);

Why do we need READ_ONCE() and WRITE_ONCE() here? Don't we hold the object's
reservation lock whenever we touch check_evicted anyways?


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 2/9] drm/gpuvm: Validate BOs in the extobj list when VM is resv protected
  2026-01-09 19:38   ` Danilo Krummrich
@ 2026-01-12  7:30     ` Boris Brezillon
  0 siblings, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12  7:30 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Akash Goel, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand, Matthew Brost,
	Thomas Hellström, Alice Ryhl, kernel

On Fri, 09 Jan 2026 20:38:37 +0100
"Danilo Krummrich" <dakr@kernel.org> wrote:

> On Fri Jan 9, 2026 at 2:07 PM CET, Boris Brezillon wrote:
> > When the VM is protected with the root object resv, evicted extobjs
> > are not inserted in the evict list.  
> 
> I'd rather say: "When the evicted object list is protected with the root object
> reservation lock, ...".
> 
> > Make sure we record when the extobj
> > list contains evicted BOs that are not already in the evict list so we
> > can validate those when drm_gpuvm_validate() is called.  
> 
> Why is this necessary in the first place? drm_gpuvm_prepare_objects() picks up
> the affected external objects already.

Oops, that's correct. Was looking for something calling
->vm_bo_validate() on evicted objects, but those are added to the
evict list instead. I'll drop this patch.

Thanks,

Boris

> 
> > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > ---
> >  drivers/gpu/drm/drm_gpuvm.c | 23 ++++++++++++++++++++++-
> >  include/drm/drm_gpuvm.h     |  6 ++++++
> >  2 files changed, 28 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
> > index 0bb115b6b59c..95f0a1c843ad 100644
> > --- a/drivers/gpu/drm/drm_gpuvm.c
> > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > @@ -1505,6 +1505,22 @@ drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
> >  			drm_gpuvm_bo_list_del_init(vm_bo, evict, false);
> >  	}
> >  
> > +	if (READ_ONCE(gpuvm->extobj.check_evicted)) {
> > +		list_for_each_entry_safe(vm_bo, next, &gpuvm->extobj.list,
> > +					 list.entry.extobj) {  
> 
> Running the external object list (again) seems wasteful given that this case can
> never happen when drm_gpuvm_prepare_objects() is called before (which has to be
> the case, given that somehow the objects have to be locked).
> 
> And even if for some reason you open code this, it would be much better to add a
> helper doing the check when the object is locked.
> 
> > +			dma_resv_assert_held(vm_bo->obj->resv);
> > +
> > +			if (!vm_bo->evicted)
> > +				continue;
> > +
> > +			ret = ops->vm_bo_validate(vm_bo, exec);
> > +			if (ret)
> > +				break;
> > +		}
> > +
> > +		WRITE_ONCE(gpuvm->extobj.check_evicted, false);  
> 
> Why do we need READ_ONCE() and WRITE_ONCE() here? Don't we hold the object's
> reservation lock whenever we touch check_evicted anyways?
> 


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 0/9] drm/panthor: Add a GEM shrinker
  2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
                   ` (8 preceding siblings ...)
  2026-01-09 13:08 ` [PATCH v1 9/9] drm/panthor: Add a GEM shrinker Boris Brezillon
@ 2026-01-12  8:37 ` Boris Brezillon
  9 siblings, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12  8:37 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, Chia-I Wu

+Chia-I

On Fri,  9 Jan 2026 14:07:52 +0100
Boris Brezillon <boris.brezillon@collabora.com> wrote:

> Hello,
> 
> This is an attempt at adding a GEM shrinker to panthor so the system
> can finally reclaim GPU memory.
> 
> This implementation is losely based on the MSM shrinker (which is why
> I added the MSM maintainers in Cc), and it's relying on the drm_gpuvm
> eviction/validation infrastructure.
> 
> I've only done very basic IGT-based [1] and chromium-based (opening
> a lot of tabs on Aquarium until the system starts reclaiming+swaping
> out GPU buffers) testing, but I'm posting this early so I can get
> preliminary feedback on the implementation. If someone knows about
> better tools/ways to test the shrinker, please let me know.
> 
> A few words about some design/implementation choices:
> - No MADVISE support because I want to see if we can live with just
>   transparent reclaim
> - We considered basing this implementation on the generic shrinker work
>   started by Dmitry [2], but
>   1. with the activeness/idleness tracking happening at the VM
>      granularity, having per-BO LRUs would caused a lot of
>      list_move()s that are not really needed (the VM as a whole
>      become active/idle, we can track individual BOs)
>   2. Thomas Zimmermann recently suggested that we should have our
>      own GEM implementation instead of trying to add this extra reclaim
>      complexity to gem-shmem. There are some plans to create a
>      gem-uma (Unified Memory Architecture) lib that would do more
>      than gem-shmem but in a way that doesn't force all its users
>      to pay the overhead (size overhead of the gem object, mostly)
>      for features they don't use. Patch "Part ways with
>      drm_gem_shmem_object" is showing what this component-based lib
>      API could look like if it were to be extracted
> - At the moment we only support swapout, but we could add an
>   extra flag to specify when buffer content doesn't need to be
>   preserved to avoid the swapout/swapin dance. First candidate for
>   this DISCARD_ON_RECLAIM flag would probably be the tiler heap chunks.
> - Reclaim uses _try_lock() all the way because of the various lock order
>   inversions between the reclaim path and submission paths. That means
>   we don't try very hard to reclaim hot GPU buffers, but the locking is
>   such a mess that I don't really see a better option to be honest.
> 
> Regards,
> 
> Boris
> 
> [1]https://gitlab.freedesktop.org/bbrezillon/igt-gpu-tools/-/commit/fc76934a5579767d2aabe787d40e38a17c3f4ea4
> [2]https://lkml.org/lkml/2024/1/5/665
> 
> Akash Goel (1):
>   drm/panthor: Add a GEM shrinker
> 
> Boris Brezillon (8):
>   drm/gem: Consider GEM object reclaimable if shrinking fails
>   drm/gpuvm: Validate BOs in the extobj list when VM is resv protected
>   drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c
>   drm/panthor: Group panthor_kernel_bo_xxx() helpers
>   drm/panthor: Part ways with drm_gem_shmem_object
>   drm/panthor: Lazily allocate pages on mmap()
>   drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for
>     reclaim
>   drm/panthor: Track the number of mmap on a BO
> 
>  drivers/gpu/drm/drm_gem.c                |   10 +
>  drivers/gpu/drm/drm_gpuvm.c              |   23 +-
>  drivers/gpu/drm/panthor/Kconfig          |    1 -
>  drivers/gpu/drm/panthor/panthor_device.c |   11 +-
>  drivers/gpu/drm/panthor/panthor_device.h |   73 ++
>  drivers/gpu/drm/panthor/panthor_drv.c    |   33 +-
>  drivers/gpu/drm/panthor/panthor_fw.c     |   16 +-
>  drivers/gpu/drm/panthor/panthor_gem.c    | 1387 ++++++++++++++++++----
>  drivers/gpu/drm/panthor/panthor_gem.h    |  135 ++-
>  drivers/gpu/drm/panthor/panthor_mmu.c    |  451 +++++--
>  drivers/gpu/drm/panthor/panthor_mmu.h    |    8 +
>  drivers/gpu/drm/panthor/panthor_sched.c  |    9 +-
>  include/drm/drm_gpuvm.h                  |    6 +
>  13 files changed, 1829 insertions(+), 334 deletions(-)
> 


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails
  2026-01-09 13:07 ` [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails Boris Brezillon
@ 2026-01-12  9:25   ` Alice Ryhl
  2026-01-12 10:02     ` Boris Brezillon
  2026-01-15 13:28   ` Liviu Dudau
  1 sibling, 1 reply; 62+ messages in thread
From: Alice Ryhl @ 2026-01-12  9:25 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Akash Goel, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström, kernel

On Fri, Jan 09, 2026 at 02:07:53PM +0100, Boris Brezillon wrote:
> If the object wasn't moved to a different LRU after the shrink callback
> is called, it means the buffer is still reclaimable. Update the remaining
> counter to reflect that.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>

Should this have a Fixes:?

Alice

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails
  2026-01-12  9:25   ` Alice Ryhl
@ 2026-01-12 10:02     ` Boris Brezillon
  0 siblings, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 10:02 UTC (permalink / raw)
  To: Alice Ryhl
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Akash Goel, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström, kernel

On Mon, 12 Jan 2026 09:25:04 +0000
Alice Ryhl <aliceryhl@google.com> wrote:

> On Fri, Jan 09, 2026 at 02:07:53PM +0100, Boris Brezillon wrote:
> > If the object wasn't moved to a different LRU after the shrink callback
> > is called, it means the buffer is still reclaimable. Update the remaining
> > counter to reflect that.
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>  
> 
> Should this have a Fixes:?

If we wanted one, that would be e7c2af13f811 ("drm/gem: Add LRU/shrinker
helper"), but I'm still not sure what the original intent was, so let's
wait for Rob to confirm that a transient reclaim failures (most likely
an inner lock that couldn't be acquired with a _try_lock()) should count
as reclaimable.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 3/9] drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c
  2026-01-09 13:07 ` [PATCH v1 3/9] drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c Boris Brezillon
@ 2026-01-12 11:27   ` Steven Price
  2026-01-15 13:39   ` Liviu Dudau
  1 sibling, 0 replies; 62+ messages in thread
From: Steven Price @ 2026-01-12 11:27 UTC (permalink / raw)
  To: Boris Brezillon, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 09/01/2026 13:07, Boris Brezillon wrote:
> There's no reason for panthor_drv to know about panthor_gem.c internals,
> so let's move the GEM debugfs init logic to panthor_gem.c.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>

Reviewed-by: Steven Price <steven.price@arm.com>

> ---
>  drivers/gpu/drm/panthor/panthor_drv.c | 26 +-----------------------
>  drivers/gpu/drm/panthor/panthor_gem.c | 29 +++++++++++++++++++++++++--
>  drivers/gpu/drm/panthor/panthor_gem.h |  3 +--
>  3 files changed, 29 insertions(+), 29 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
> index 165dddfde6ca..52c27a60c84a 100644
> --- a/drivers/gpu/drm/panthor/panthor_drv.c
> +++ b/drivers/gpu/drm/panthor/panthor_drv.c
> @@ -1635,34 +1635,10 @@ static const struct file_operations panthor_drm_driver_fops = {
>  };
>  
>  #ifdef CONFIG_DEBUG_FS
> -static int panthor_gems_show(struct seq_file *m, void *data)
> -{
> -	struct drm_info_node *node = m->private;
> -	struct drm_device *dev = node->minor->dev;
> -	struct panthor_device *ptdev = container_of(dev, struct panthor_device, base);
> -
> -	panthor_gem_debugfs_print_bos(ptdev, m);
> -
> -	return 0;
> -}
> -
> -static struct drm_info_list panthor_debugfs_list[] = {
> -	{"gems", panthor_gems_show, 0, NULL},
> -};
> -
> -static int panthor_gems_debugfs_init(struct drm_minor *minor)
> -{
> -	drm_debugfs_create_files(panthor_debugfs_list,
> -				 ARRAY_SIZE(panthor_debugfs_list),
> -				 minor->debugfs_root, minor);
> -
> -	return 0;
> -}
> -
>  static void panthor_debugfs_init(struct drm_minor *minor)
>  {
>  	panthor_mmu_debugfs_init(minor);
> -	panthor_gems_debugfs_init(minor);
> +	panthor_gem_debugfs_init(minor);
>  }
>  #endif
>  
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index b61908fd508a..13e9dd3764fa 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -9,6 +9,8 @@
>  #include <linux/err.h>
>  #include <linux/slab.h>
>  
> +#include <drm/drm_debugfs.h>
> +#include <drm/drm_file.h>
>  #include <drm/drm_print.h>
>  #include <drm/panthor_drm.h>
>  
> @@ -683,8 +685,8 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
>  		totals->reclaimable += resident_size;
>  }
>  
> -void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
> -				   struct seq_file *m)
> +static void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
> +					  struct seq_file *m)
>  {
>  	struct gem_size_totals totals = {0};
>  	struct panthor_gem_object *bo;
> @@ -704,4 +706,27 @@ void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
>  	seq_printf(m, "Total size: %zd, Total resident: %zd, Total reclaimable: %zd\n",
>  		   totals.size, totals.resident, totals.reclaimable);
>  }
> +
> +static int panthor_gem_show_bos(struct seq_file *m, void *data)
> +{
> +	struct drm_info_node *node = m->private;
> +	struct drm_device *dev = node->minor->dev;
> +	struct panthor_device *ptdev =
> +		container_of(dev, struct panthor_device, base);
> +
> +	panthor_gem_debugfs_print_bos(ptdev, m);
> +
> +	return 0;
> +}
> +
> +static struct drm_info_list panthor_gem_debugfs_list[] = {
> +	{ "gems", panthor_gem_show_bos, 0, NULL },
> +};
> +
> +void panthor_gem_debugfs_init(struct drm_minor *minor)
> +{
> +	drm_debugfs_create_files(panthor_gem_debugfs_list,
> +				 ARRAY_SIZE(panthor_gem_debugfs_list),
> +				 minor->debugfs_root, minor);
> +}
>  #endif
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
> index 22519c570b5a..94b2d17cf032 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.h
> +++ b/drivers/gpu/drm/panthor/panthor_gem.h
> @@ -203,8 +203,7 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo);
>  
>  #ifdef CONFIG_DEBUG_FS
> -void panthor_gem_debugfs_print_bos(struct panthor_device *pfdev,
> -				   struct seq_file *m);
> +void panthor_gem_debugfs_init(struct drm_minor *minor);
>  #endif
>  
>  #endif /* __PANTHOR_GEM_H__ */


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 4/9] drm/panthor: Group panthor_kernel_bo_xxx() helpers
  2026-01-09 13:07 ` [PATCH v1 4/9] drm/panthor: Group panthor_kernel_bo_xxx() helpers Boris Brezillon
@ 2026-01-12 11:29   ` Steven Price
  2026-01-15 13:41   ` Liviu Dudau
  1 sibling, 0 replies; 62+ messages in thread
From: Steven Price @ 2026-01-12 11:29 UTC (permalink / raw)
  To: Boris Brezillon, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 09/01/2026 13:07, Boris Brezillon wrote:
> Move all panthor_kernel_bo_xxx() helpers at the end of the file, just
> before the debugfs init logic. This will make further panthor_gem.c
> refactoring more readable.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>

Reviewed-by: Steven Price <steven.price@arm.com>

> ---
>  drivers/gpu/drm/panthor/panthor_gem.c | 212 +++++++++++++-------------
>  1 file changed, 106 insertions(+), 106 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index 13e9dd3764fa..4b3d82f001d8 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -132,112 +132,6 @@ static void panthor_gem_free_object(struct drm_gem_object *obj)
>  	drm_gem_object_put(vm_root_gem);
>  }
>  
> -/**
> - * panthor_kernel_bo_destroy() - Destroy a kernel buffer object
> - * @bo: Kernel buffer object to destroy. If NULL or an ERR_PTR(), the destruction
> - * is skipped.
> - */
> -void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
> -{
> -	struct panthor_vm *vm;
> -
> -	if (IS_ERR_OR_NULL(bo))
> -		return;
> -
> -	vm = bo->vm;
> -	panthor_kernel_bo_vunmap(bo);
> -
> -	drm_WARN_ON(bo->obj->dev,
> -		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
> -	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
> -	panthor_vm_free_va(vm, &bo->va_node);
> -	drm_gem_object_put(bo->obj);
> -	panthor_vm_put(vm);
> -	kfree(bo);
> -}
> -
> -/**
> - * panthor_kernel_bo_create() - Create and map a GEM object to a VM
> - * @ptdev: Device.
> - * @vm: VM to map the GEM to. If NULL, the kernel object is not GPU mapped.
> - * @size: Size of the buffer object.
> - * @bo_flags: Combination of drm_panthor_bo_flags flags.
> - * @vm_map_flags: Combination of drm_panthor_vm_bind_op_flags (only those
> - * that are related to map operations).
> - * @gpu_va: GPU address assigned when mapping to the VM.
> - * If gpu_va == PANTHOR_VM_KERNEL_AUTO_VA, the virtual address will be
> - * automatically allocated.
> - * @name: Descriptive label of the BO's contents
> - *
> - * Return: A valid pointer in case of success, an ERR_PTR() otherwise.
> - */
> -struct panthor_kernel_bo *
> -panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
> -			 size_t size, u32 bo_flags, u32 vm_map_flags,
> -			 u64 gpu_va, const char *name)
> -{
> -	struct drm_gem_shmem_object *obj;
> -	struct panthor_kernel_bo *kbo;
> -	struct panthor_gem_object *bo;
> -	u32 debug_flags = PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL;
> -	int ret;
> -
> -	if (drm_WARN_ON(&ptdev->base, !vm))
> -		return ERR_PTR(-EINVAL);
> -
> -	kbo = kzalloc(sizeof(*kbo), GFP_KERNEL);
> -	if (!kbo)
> -		return ERR_PTR(-ENOMEM);
> -
> -	obj = drm_gem_shmem_create(&ptdev->base, size);
> -	if (IS_ERR(obj)) {
> -		ret = PTR_ERR(obj);
> -		goto err_free_bo;
> -	}
> -
> -	bo = to_panthor_bo(&obj->base);
> -	kbo->obj = &obj->base;
> -	bo->flags = bo_flags;
> -	bo->base.map_wc = should_map_wc(bo, vm);
> -	bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm);
> -	drm_gem_object_get(bo->exclusive_vm_root_gem);
> -	bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
> -
> -	if (vm == panthor_fw_vm(ptdev))
> -		debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED;
> -
> -	panthor_gem_kernel_bo_set_label(kbo, name);
> -	panthor_gem_debugfs_set_usage_flags(to_panthor_bo(kbo->obj), debug_flags);
> -
> -	/* The system and GPU MMU page size might differ, which becomes a
> -	 * problem for FW sections that need to be mapped at explicit address
> -	 * since our PAGE_SIZE alignment might cover a VA range that's
> -	 * expected to be used for another section.
> -	 * Make sure we never map more than we need.
> -	 */
> -	size = ALIGN(size, panthor_vm_page_size(vm));
> -	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
> -	if (ret)
> -		goto err_put_obj;
> -
> -	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
> -	if (ret)
> -		goto err_free_va;
> -
> -	kbo->vm = panthor_vm_get(vm);
> -	return kbo;
> -
> -err_free_va:
> -	panthor_vm_free_va(vm, &kbo->va_node);
> -
> -err_put_obj:
> -	drm_gem_object_put(&obj->base);
> -
> -err_free_bo:
> -	kfree(kbo);
> -	return ERR_PTR(ret);
> -}
> -
>  static struct sg_table *
>  panthor_gem_prime_map_dma_buf(struct dma_buf_attachment *attach,
>  			      enum dma_data_direction dir)
> @@ -603,6 +497,112 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>  	return 0;
>  }
>  
> +/**
> + * panthor_kernel_bo_destroy() - Destroy a kernel buffer object
> + * @bo: Kernel buffer object to destroy. If NULL or an ERR_PTR(), the destruction
> + * is skipped.
> + */
> +void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
> +{
> +	struct panthor_vm *vm;
> +
> +	if (IS_ERR_OR_NULL(bo))
> +		return;
> +
> +	vm = bo->vm;
> +	panthor_kernel_bo_vunmap(bo);
> +
> +	drm_WARN_ON(bo->obj->dev,
> +		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
> +	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
> +	panthor_vm_free_va(vm, &bo->va_node);
> +	drm_gem_object_put(bo->obj);
> +	panthor_vm_put(vm);
> +	kfree(bo);
> +}
> +
> +/**
> + * panthor_kernel_bo_create() - Create and map a GEM object to a VM
> + * @ptdev: Device.
> + * @vm: VM to map the GEM to. If NULL, the kernel object is not GPU mapped.
> + * @size: Size of the buffer object.
> + * @bo_flags: Combination of drm_panthor_bo_flags flags.
> + * @vm_map_flags: Combination of drm_panthor_vm_bind_op_flags (only those
> + * that are related to map operations).
> + * @gpu_va: GPU address assigned when mapping to the VM.
> + * If gpu_va == PANTHOR_VM_KERNEL_AUTO_VA, the virtual address will be
> + * automatically allocated.
> + * @name: Descriptive label of the BO's contents
> + *
> + * Return: A valid pointer in case of success, an ERR_PTR() otherwise.
> + */
> +struct panthor_kernel_bo *
> +panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
> +			 size_t size, u32 bo_flags, u32 vm_map_flags,
> +			 u64 gpu_va, const char *name)
> +{
> +	struct drm_gem_shmem_object *obj;
> +	struct panthor_kernel_bo *kbo;
> +	struct panthor_gem_object *bo;
> +	u32 debug_flags = PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL;
> +	int ret;
> +
> +	if (drm_WARN_ON(&ptdev->base, !vm))
> +		return ERR_PTR(-EINVAL);
> +
> +	kbo = kzalloc(sizeof(*kbo), GFP_KERNEL);
> +	if (!kbo)
> +		return ERR_PTR(-ENOMEM);
> +
> +	obj = drm_gem_shmem_create(&ptdev->base, size);
> +	if (IS_ERR(obj)) {
> +		ret = PTR_ERR(obj);
> +		goto err_free_bo;
> +	}
> +
> +	bo = to_panthor_bo(&obj->base);
> +	kbo->obj = &obj->base;
> +	bo->flags = bo_flags;
> +	bo->base.map_wc = should_map_wc(bo, vm);
> +	bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm);
> +	drm_gem_object_get(bo->exclusive_vm_root_gem);
> +	bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
> +
> +	if (vm == panthor_fw_vm(ptdev))
> +		debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED;
> +
> +	panthor_gem_kernel_bo_set_label(kbo, name);
> +	panthor_gem_debugfs_set_usage_flags(to_panthor_bo(kbo->obj), debug_flags);
> +
> +	/* The system and GPU MMU page size might differ, which becomes a
> +	 * problem for FW sections that need to be mapped at explicit address
> +	 * since our PAGE_SIZE alignment might cover a VA range that's
> +	 * expected to be used for another section.
> +	 * Make sure we never map more than we need.
> +	 */
> +	size = ALIGN(size, panthor_vm_page_size(vm));
> +	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
> +	if (ret)
> +		goto err_put_obj;
> +
> +	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
> +	if (ret)
> +		goto err_free_va;
> +
> +	kbo->vm = panthor_vm_get(vm);
> +	return kbo;
> +
> +err_free_va:
> +	panthor_vm_free_va(vm, &kbo->va_node);
> +
> +err_put_obj:
> +	drm_gem_object_put(&obj->base);
> +
> +err_free_bo:
> +	kfree(kbo);
> +	return ERR_PTR(ret);
> +}
> +
>  #ifdef CONFIG_DEBUG_FS
>  struct gem_size_totals {
>  	size_t size;


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-09 13:07 ` [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object Boris Brezillon
@ 2026-01-12 12:06   ` Steven Price
  2026-01-12 14:17     ` Boris Brezillon
  2026-01-15 16:51   ` Liviu Dudau
  1 sibling, 1 reply; 62+ messages in thread
From: Steven Price @ 2026-01-12 12:06 UTC (permalink / raw)
  To: Boris Brezillon, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 09/01/2026 13:07, Boris Brezillon wrote:
> While drm_gem_shmem_object does most of the job we need it to do, the
> way sub-resources (pages, sgt, vmap) are handled and their lifetimes
> gets in the way of BO reclaim. There has been attempts to address
> that [1], but in the meantime, new gem_shmem users were introduced
> (accel drivers), and some of them manually free some of these resources.
> This makes things harder to control/sanitize/validate.
> 
> Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
> and forcing gem_shmem users to go through new gem_shmem helpers when they
> need manual control of some sort, and I believe this is a dead end if
> we don't force users to follow some stricter rules through carefully
> designed helpers, because there will always be one user doing crazy things
> with gem_shmem_object internals, which ends up tripping out the common
> helpers when they are called.
> 
> The consensus we reached was that we would be better off forking
> gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
> current transition tries to minimize the changes, but there are still
> some aspects that are different, the main one being that we no longer
> have a pages_use_count, and pages stays around until the GEM object is
> destroyed (or when evicted once we've added a shrinker). The sgt also
> no longer retains pages. This is losely based on how msm does things by
> the way.

From a reviewing perspective it's a little tricky trying to match up the
implementation to shmem because of these changes. I don't know how
difficult it would be to split the changes to a patch which literally
copies (with renames) from shmem, followed by simplifying out the parts
we don't want.

Of course the main issue is going to be getting some proper testing of
this (especially with the shrinker added).

> 
> If there's any interest in sharing code (probably with msm, since the
> panthor shrinker is going to be losely based on the msm implementation),
> we can always change gears and do that once we have everything
> working/merged.
> 
> [1]https://patchwork.kernel.org/project/dri-devel/patch/20240105184624.508603-1-dmitry.osipenko@collabora.com/
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>  drivers/gpu/drm/panthor/Kconfig         |   1 -
>  drivers/gpu/drm/panthor/panthor_drv.c   |   7 +-
>  drivers/gpu/drm/panthor/panthor_fw.c    |  16 +-
>  drivers/gpu/drm/panthor/panthor_gem.c   | 696 ++++++++++++++++++++----
>  drivers/gpu/drm/panthor/panthor_gem.h   |  62 ++-
>  drivers/gpu/drm/panthor/panthor_mmu.c   |  49 +-
>  drivers/gpu/drm/panthor/panthor_sched.c |   9 +-
>  7 files changed, 666 insertions(+), 174 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig
> index 55b40ad07f3b..911e7f4810c3 100644
> --- a/drivers/gpu/drm/panthor/Kconfig
> +++ b/drivers/gpu/drm/panthor/Kconfig
> @@ -8,7 +8,6 @@ config DRM_PANTHOR
>  	depends on MMU
>  	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>  	select DRM_EXEC
> -	select DRM_GEM_SHMEM_HELPER
>  	select DRM_GPUVM
>  	select DRM_SCHED
>  	select IOMMU_IO_PGTABLE_LPAE
> diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
> index 52c27a60c84a..90e9abc22d9e 100644
> --- a/drivers/gpu/drm/panthor/panthor_drv.c
> +++ b/drivers/gpu/drm/panthor/panthor_drv.c
> @@ -19,6 +19,7 @@
>  #include <drm/drm_debugfs.h>
>  #include <drm/drm_drv.h>
>  #include <drm/drm_exec.h>
> +#include <drm/drm_file.h>
>  #include <drm/drm_ioctl.h>
>  #include <drm/drm_print.h>
>  #include <drm/drm_syncobj.h>
> @@ -1457,7 +1458,7 @@ static int panthor_ioctl_bo_query_info(struct drm_device *ddev, void *data,
>  	args->create_flags = bo->flags;
>  
>  	args->extra_flags = 0;
> -	if (drm_gem_is_imported(&bo->base.base))
> +	if (drm_gem_is_imported(&bo->base))
>  		args->extra_flags |= DRM_PANTHOR_BO_IS_IMPORTED;
>  
>  	drm_gem_object_put(obj);
> @@ -1671,8 +1672,7 @@ static const struct drm_driver panthor_drm_driver = {
>  	.major = 1,
>  	.minor = 7,
>  
> -	.gem_create_object = panthor_gem_create_object,
> -	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
> +	.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
>  	.gem_prime_import = panthor_gem_prime_import,
>  #ifdef CONFIG_DEBUG_FS
>  	.debugfs_init = panthor_debugfs_init,
> @@ -1822,3 +1822,4 @@ module_exit(panthor_exit);
>  MODULE_AUTHOR("Panthor Project Developers");
>  MODULE_DESCRIPTION("Panthor DRM Driver");
>  MODULE_LICENSE("Dual MIT/GPL");
> +MODULE_IMPORT_NS("DMA_BUF");
> diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
> index a64ec8756bed..f135cf2130b8 100644
> --- a/drivers/gpu/drm/panthor/panthor_fw.c
> +++ b/drivers/gpu/drm/panthor/panthor_fw.c
> @@ -627,7 +627,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
>  		u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK;
>  		struct panthor_gem_object *bo;
>  		u32 vm_map_flags = 0;
> -		struct sg_table *sgt;
>  		u64 va = hdr.va.start;
>  
>  		if (!(hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_WR))
> @@ -665,11 +664,12 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
>  		panthor_fw_init_section_mem(ptdev, section);
>  
>  		bo = to_panthor_bo(section->mem->obj);
> -		sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
> -		if (IS_ERR(sgt))
> -			return PTR_ERR(sgt);
>  
> -		dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
> +		if (drm_WARN_ON_ONCE(&ptdev->base, !bo->dmap.sgt))
> +			return -EINVAL;
> +
> +		dma_sync_sgtable_for_device(ptdev->base.dev, bo->dmap.sgt, DMA_TO_DEVICE);
>  	}
>  
>  	if (hdr.va.start == CSF_MCU_SHARED_REGION_START)
> @@ -729,8 +729,10 @@ panthor_reload_fw_sections(struct panthor_device *ptdev, bool full_reload)
>  			continue;
>  
>  		panthor_fw_init_section_mem(ptdev, section);
> -		sgt = drm_gem_shmem_get_pages_sgt(&to_panthor_bo(section->mem->obj)->base);
> -		if (!drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(sgt)))
> +
> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
> +		sgt = to_panthor_bo(section->mem->obj)->dmap.sgt;
> +		if (!drm_WARN_ON_ONCE(&ptdev->base, !sgt))
>  			dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
>  	}
>  }
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index 4b3d82f001d8..0e52c7a07c87 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -8,9 +8,11 @@
>  #include <linux/dma-mapping.h>
>  #include <linux/err.h>
>  #include <linux/slab.h>
> +#include <linux/vmalloc.h>
>  
>  #include <drm/drm_debugfs.h>
>  #include <drm/drm_file.h>
> +#include <drm/drm_prime.h>
>  #include <drm/drm_print.h>
>  #include <drm/panthor_drm.h>
>  
> @@ -44,7 +46,7 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo)
>  
>  static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
>  {
> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> +	struct panthor_device *ptdev = container_of(bo->base.dev,
>  						    struct panthor_device, base);
>  
>  	bo->debugfs.creator.tgid = current->group_leader->pid;
> @@ -57,7 +59,7 @@ static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
>  
>  static void panthor_gem_debugfs_bo_rm(struct panthor_gem_object *bo)
>  {
> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> +	struct panthor_device *ptdev = container_of(bo->base.dev,
>  						    struct panthor_device, base);
>  
>  	if (list_empty(&bo->debugfs.node))
> @@ -80,9 +82,9 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo) {}
>  #endif
>  
>  static bool
> -should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> +should_map_wc(struct panthor_gem_object *bo)
>  {
> -	struct panthor_device *ptdev = container_of(bo->base.base.dev, struct panthor_device, base);
> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
>  
>  	/* We can't do uncached mappings if the device is coherent,
>  	 * because the zeroing done by the shmem layer at page allocation
> @@ -112,6 +114,208 @@ should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
>  	return true;
>  }
>  
> +static void
> +panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
> +{
> +	if (!bo->backing.pages)
> +		return;
> +
> +	drm_gem_put_pages(&bo->base, bo->backing.pages, true, false);
> +	bo->backing.pages = NULL;
> +}
> +
> +static int
> +panthor_gem_backing_get_pages_locked(struct panthor_gem_object *bo)
> +{
> +	dma_resv_assert_held(bo->base.resv);
> +
> +	if (bo->backing.pages)
> +		return 0;
> +
> +	bo->backing.pages = drm_gem_get_pages(&bo->base);
> +	if (IS_ERR(bo->backing.pages)) {
> +		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
> +			    bo->backing.pages);
> +		return PTR_ERR(bo->backing.pages);

This leaves bo->backing.pages set to the error value, which means a
future call to panthor_gem_backing_get_pages_locked() for the same
object will return success. Unless there's some 'poisoning' that I
haven't spotted this looks like a bug.

> +	}
> +
> +	return 0;
> +}
> +
> +static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
> +{
> +	int ret;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> +
> +	if (refcount_inc_not_zero(&bo->backing.pin_count))
> +		return 0;
> +
> +	ret = panthor_gem_backing_get_pages_locked(bo);
> +	if (!ret)
> +		refcount_set(&bo->backing.pin_count, 1);
> +
> +	return ret;
> +}
> +
> +static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
> +{
> +	dma_resv_assert_held(bo->base.resv);
> +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> +
> +	/* We don't release anything when pin_count drops to zero.
> +	 * Pages stay there until an explicit cleanup is requested.
> +	 */
> +	if (!refcount_dec_not_one(&bo->backing.pin_count))
> +		refcount_set(&bo->backing.pin_count, 0);

Why not just refcount_dec()?

> +}
> +
> +static void
> +panthor_gem_dev_map_cleanup(struct panthor_gem_object *bo)
> +{
> +	if (!bo->dmap.sgt)
> +		return;
> +
> +	dma_unmap_sgtable(drm_dev_dma_dev(bo->base.dev), bo->dmap.sgt, DMA_BIDIRECTIONAL, 0);
> +	sg_free_table(bo->dmap.sgt);
> +	kfree(bo->dmap.sgt);
> +	bo->dmap.sgt = NULL;
> +}
> +
> +static struct sg_table *
> +panthor_gem_dev_map_get_sgt_locked(struct panthor_gem_object *bo)
> +{
> +	struct sg_table *sgt;
> +	int ret;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +
> +	if (bo->dmap.sgt)
> +		return bo->dmap.sgt;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> +		return ERR_PTR(-EINVAL);
> +
> +	/* Pages stay around after they've been allocated. At least that stands
> +	 * until we add a shrinker.
> +	 */
> +	ret = panthor_gem_backing_get_pages_locked(bo);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	sgt = drm_prime_pages_to_sg(bo->base.dev, bo->backing.pages,
> +				    bo->base.size >> PAGE_SHIFT);
> +	if (IS_ERR(sgt))
> +		return sgt;
> +
> +	/* Map the pages for use by the h/w. */
> +	ret = dma_map_sgtable(drm_dev_dma_dev(bo->base.dev), sgt, DMA_BIDIRECTIONAL, 0);
> +	if (ret)
> +		goto err_free_sgt;
> +
> +	bo->dmap.sgt = sgt;
> +	return sgt;
> +
> +err_free_sgt:
> +	sg_free_table(sgt);
> +	kfree(sgt);
> +	return ERR_PTR(ret);
> +}
> +
> +struct sg_table *
> +panthor_gem_get_dev_sgt(struct panthor_gem_object *bo)
> +{
> +	struct sg_table *sgt;
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> +	dma_resv_unlock(bo->base.resv);
> +
> +	return sgt;
> +}
> +
> +static void
> +panthor_gem_vmap_cleanup(struct panthor_gem_object *bo)
> +{
> +	if (!bo->cmap.vaddr)
> +		return;
> +
> +	vunmap(bo->cmap.vaddr);
> +	bo->cmap.vaddr = NULL;
> +	panthor_gem_backing_unpin_locked(bo);
> +}
> +
> +static int
> +panthor_gem_prep_for_cpu_map_locked(struct panthor_gem_object *bo)
> +{
> +	if (should_map_wc(bo)) {
> +		struct sg_table *sgt;
> +
> +		sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> +		if (IS_ERR(sgt))
> +			return PTR_ERR(sgt);
> +	}
> +
> +	return 0;
> +}
> +
> +static void *
> +panthor_gem_vmap_get_locked(struct panthor_gem_object *bo)
> +{
> +	pgprot_t prot = PAGE_KERNEL;
> +	void *vaddr;
> +	int ret;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> +		return ERR_PTR(-EINVAL);
> +
> +	if (refcount_inc_not_zero(&bo->cmap.vaddr_use_count)) {
> +		drm_WARN_ON_ONCE(bo->base.dev, !bo->cmap.vaddr);
> +		return bo->cmap.vaddr;
> +	}
> +
> +	ret = panthor_gem_backing_pin_locked(bo);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	ret = panthor_gem_prep_for_cpu_map_locked(bo);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	if (should_map_wc(bo))
> +		prot = pgprot_writecombine(prot);
> +
> +	vaddr = vmap(bo->backing.pages, bo->base.size >> PAGE_SHIFT, VM_MAP, prot);
> +	if (!vaddr) {
> +		ret = -ENOMEM;
> +		goto err_unpin;
> +	}
> +
> +	bo->cmap.vaddr = vaddr;
> +	refcount_set(&bo->cmap.vaddr_use_count, 1);
> +	return vaddr;
> +
> +err_unpin:
> +	panthor_gem_backing_unpin_locked(bo);
> +	return ERR_PTR(ret);
> +}
> +
> +static void
> +panthor_gem_vmap_put_locked(struct panthor_gem_object *bo)
> +{
> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> +		return;
> +
> +	if (refcount_dec_not_one(&bo->cmap.vaddr_use_count))
> +		return;
> +
> +	refcount_set(&bo->cmap.vaddr_use_count, 0);
> +	panthor_gem_vmap_cleanup(bo);
> +}
> +
>  static void panthor_gem_free_object(struct drm_gem_object *obj)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(obj);
> @@ -127,8 +331,17 @@ static void panthor_gem_free_object(struct drm_gem_object *obj)
>  
>  	mutex_destroy(&bo->label.lock);
>  
> -	drm_gem_free_mmap_offset(&bo->base.base);
> -	drm_gem_shmem_free(&bo->base);
> +	if (drm_gem_is_imported(obj)) {
> +		drm_prime_gem_destroy(obj, bo->dmap.sgt);
> +	} else {
> +		panthor_gem_vmap_cleanup(bo);

panthor_gem_vmap_cleanup() calls panthor_gem_backing_unpin_locked()
which expects the reservation lock to be held.

> +		panthor_gem_dev_map_cleanup(bo);
> +		panthor_gem_backing_cleanup(bo);
> +	}
> +
> +	drm_gem_object_release(obj);
> +
> +	kfree(bo);
>  	drm_gem_object_put(vm_root_gem);
>  }
>  
> @@ -159,15 +372,15 @@ panthor_gem_prime_begin_cpu_access(struct dma_buf *dma_buf,
>  {
>  	struct drm_gem_object *obj = dma_buf->priv;
>  	struct drm_device *dev = obj->dev;
> -	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>  	struct dma_buf_attachment *attach;
>  
>  	dma_resv_lock(obj->resv, NULL);
> -	if (shmem->sgt)
> -		dma_sync_sgtable_for_cpu(dev->dev, shmem->sgt, dir);
> +	if (bo->dmap.sgt)
> +		dma_sync_sgtable_for_cpu(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
>  
> -	if (shmem->vaddr)
> -		invalidate_kernel_vmap_range(shmem->vaddr, shmem->base.size);
> +	if (bo->cmap.vaddr)
> +		invalidate_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
>  
>  	list_for_each_entry(attach, &dma_buf->attachments, node) {
>  		struct sg_table *sgt = attach->priv;
> @@ -186,7 +399,7 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
>  {
>  	struct drm_gem_object *obj = dma_buf->priv;
>  	struct drm_device *dev = obj->dev;
> -	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>  	struct dma_buf_attachment *attach;
>  
>  	dma_resv_lock(obj->resv, NULL);
> @@ -197,11 +410,11 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
>  			dma_sync_sgtable_for_device(attach->dev, sgt, dir);
>  	}
>  
> -	if (shmem->vaddr)
> -		flush_kernel_vmap_range(shmem->vaddr, shmem->base.size);
> +	if (bo->cmap.vaddr)
> +		flush_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
>  
> -	if (shmem->sgt)
> -		dma_sync_sgtable_for_device(dev->dev, shmem->sgt, dir);
> +	if (bo->dmap.sgt)
> +		dma_sync_sgtable_for_device(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
>  
>  	dma_resv_unlock(obj->resv);
>  	return 0;
> @@ -258,53 +471,339 @@ panthor_gem_prime_import(struct drm_device *dev,
>  	return drm_gem_prime_import(dev, dma_buf);
>  }
>  
> +static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
> +				   const struct drm_gem_object *obj)
> +{
> +	const struct panthor_gem_object *bo = to_panthor_bo(obj);
> +
> +	if (drm_gem_is_imported(&bo->base))
> +		return;
> +
> +	drm_printf_indent(p, indent, "resident=%s\n", str_true_false(bo->backing.pages));
> +	drm_printf_indent(p, indent, "pages_pin_count=%u\n", refcount_read(&bo->backing.pin_count));
> +	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
> +			  refcount_read(&bo->cmap.vaddr_use_count));
> +	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> +}
> +
> +static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> +{
> +	if (drm_gem_is_imported(obj))
> +		return 0;
> +
> +	return panthor_gem_backing_pin_locked(to_panthor_bo(obj));
> +}
> +
> +static void panthor_gem_unpin_locked(struct drm_gem_object *obj)
> +{
> +	if (!drm_gem_is_imported(obj))
> +		panthor_gem_backing_unpin_locked(to_panthor_bo(obj));
> +}
> +
> +int panthor_gem_pin(struct panthor_gem_object *bo)
> +{
> +	int ret = 0;
> +
> +	if (drm_gem_is_imported(&bo->base))
> +		return 0;
> +
> +	if (refcount_inc_not_zero(&bo->backing.pin_count))
> +		return 0;
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +	ret = panthor_gem_pin_locked(&bo->base);

We might as well call panthor_gem_backing_pin_locked() since we know
it's not imported.

> +	dma_resv_unlock(bo->base.resv);
> +
> +	return ret;
> +}
> +
> +void panthor_gem_unpin(struct panthor_gem_object *bo)
> +{
> +	if (drm_gem_is_imported(&bo->base))
> +		return;
> +
> +	if (refcount_dec_not_one(&bo->backing.pin_count))
> +		return;
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +	panthor_gem_unpin_locked(&bo->base);

Same here.

> +	dma_resv_unlock(bo->base.resv);
> +}
> +
> +static struct sg_table *panthor_gem_get_sg_table(struct drm_gem_object *obj)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +
> +	drm_WARN_ON_ONCE(obj->dev, drm_gem_is_imported(obj));
> +	drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages);
> +	drm_WARN_ON_ONCE(obj->dev, !refcount_read(&bo->backing.pin_count));
> +
> +	return drm_prime_pages_to_sg(obj->dev, bo->backing.pages, obj->size >> PAGE_SHIFT);
> +}
> +
> +static int panthor_gem_vmap_locked(struct drm_gem_object *obj,
> +				   struct iosys_map *map)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +	void *vaddr;
> +
> +	dma_resv_assert_held(obj->resv);
> +
> +	if (drm_gem_is_imported(obj))
> +		return dma_buf_vmap(obj->import_attach->dmabuf, map);
> +
> +	vaddr = panthor_gem_vmap_get_locked(bo);
> +	if (IS_ERR(vaddr))
> +		return PTR_ERR(vaddr);
> +
> +	iosys_map_set_vaddr(map, vaddr);
> +	return 0;
> +}
> +
> +static void panthor_gem_vunmap_locked(struct drm_gem_object *obj,
> +				      struct iosys_map *map)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +
> +	dma_resv_assert_held(obj->resv);
> +
> +	if (drm_gem_is_imported(obj)) {
> +		dma_buf_vunmap(obj->import_attach->dmabuf, map);
> +	} else {
> +		drm_WARN_ON_ONCE(obj->dev, bo->cmap.vaddr != map->vaddr);
> +		panthor_gem_vmap_put_locked(bo);
> +	}
> +}
> +
> +static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +	int ret;
> +
> +	if (drm_gem_is_imported(obj)) {
> +		/* Reset both vm_ops and vm_private_data, so we don't end up with
> +		 * vm_ops pointing to our implementation if the dma-buf backend
> +		 * doesn't set those fields.
> +		 */
> +		vma->vm_private_data = NULL;
> +		vma->vm_ops = NULL;
> +
> +		ret = dma_buf_mmap(obj->dma_buf, vma, 0);
> +
> +		/* Drop the reference drm_gem_mmap_obj() acquired.*/
> +		if (!ret)
> +			drm_gem_object_put(obj);
> +
> +		return ret;
> +	}
> +
> +	if (is_cow_mapping(vma->vm_flags))
> +		return -EINVAL;
> +
> +	dma_resv_lock(obj->resv, NULL);
> +	ret = panthor_gem_backing_get_pages_locked(bo);
> +	if (!ret)
> +		ret = panthor_gem_prep_for_cpu_map_locked(bo);
> +	dma_resv_unlock(obj->resv);
> +
> +	if (ret)
> +		return ret;
> +
> +	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> +	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> +	if (should_map_wc(bo))
> +		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
> +
> +	return 0;
> +}
> +
>  static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(obj);
>  	enum drm_gem_object_status res = 0;
>  
> -	if (drm_gem_is_imported(&bo->base.base) || bo->base.pages)
> +	if (drm_gem_is_imported(&bo->base) || bo->backing.pages)
>  		res |= DRM_GEM_OBJECT_RESIDENT;
>  
>  	return res;
>  }
>  
> -static const struct drm_gem_object_funcs panthor_gem_funcs = {
> -	.free = panthor_gem_free_object,
> -	.print_info = drm_gem_shmem_object_print_info,
> -	.pin = drm_gem_shmem_object_pin,
> -	.unpin = drm_gem_shmem_object_unpin,
> -	.get_sg_table = drm_gem_shmem_object_get_sg_table,
> -	.vmap = drm_gem_shmem_object_vmap,
> -	.vunmap = drm_gem_shmem_object_vunmap,
> -	.mmap = drm_gem_shmem_object_mmap,
> -	.status = panthor_gem_status,
> -	.export = panthor_gem_prime_export,
> -	.vm_ops = &drm_gem_shmem_vm_ops,
> +static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
> +{
> +#ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
> +	unsigned long pfn = page_to_pfn(page);
> +	unsigned long paddr = pfn << PAGE_SHIFT;
> +	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
> +
> +	if (aligned &&
> +	    pmd_none(*vmf->pmd) &&
> +	    folio_test_pmd_mappable(page_folio(page))) {
> +		pfn &= PMD_MASK >> PAGE_SHIFT;
> +		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
> +			return true;
> +	}
> +#endif
> +
> +	return false;
> +}
> +
> +static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> +{
> +	struct vm_area_struct *vma = vmf->vma;
> +	struct drm_gem_object *obj = vma->vm_private_data;
> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> +	loff_t num_pages = obj->size >> PAGE_SHIFT;
> +	vm_fault_t ret;
> +	pgoff_t page_offset;
> +	unsigned long pfn;
> +
> +	/* Offset to faulty address in the VMA. */
> +	page_offset = vmf->pgoff - vma->vm_pgoff;
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +
> +	if (page_offset >= num_pages ||
> +	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
> +		ret = VM_FAULT_SIGBUS;
> +		goto out;
> +	}
> +
> +	if (try_map_pmd(vmf, vmf->address, bo->backing.pages[page_offset])) {
> +		ret = VM_FAULT_NOPAGE;
> +		goto out;
> +	}
> +
> +	pfn = page_to_pfn(bo->backing.pages[page_offset]);
> +	ret = vmf_insert_pfn(vma, vmf->address, pfn);
> +
> + out:
> +	dma_resv_unlock(bo->base.resv);
> +
> +	return ret;
> +}
> +
> +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> +
> +	drm_WARN_ON(bo->base.dev, drm_gem_is_imported(&bo->base));
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +
> +	/* We should have already pinned the pages when the buffer was first
> +	 * mmap'd, vm_open() just grabs an additional reference for the new
> +	 * mm the vma is getting copied into (ie. on fork()).
> +	 */
> +	drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages);
> +
> +	dma_resv_unlock(bo->base.resv);
> +
> +	drm_gem_vm_open(vma);
> +}
> +
> +const struct vm_operations_struct panthor_gem_vm_ops = {
> +	.fault = panthor_gem_fault,
> +	.open = panthor_gem_vm_open,
> +	.close = drm_gem_vm_close,
>  };
>  
> -/**
> - * panthor_gem_create_object - Implementation of driver->gem_create_object.
> - * @ddev: DRM device
> - * @size: Size in bytes of the memory the object will reference
> - *
> - * This lets the GEM helpers allocate object structs for us, and keep
> - * our BO stats correct.
> - */
> -struct drm_gem_object *panthor_gem_create_object(struct drm_device *ddev, size_t size)
> -{
> -	struct panthor_gem_object *obj;
> +static const struct drm_gem_object_funcs panthor_gem_funcs = {
> +	.free = panthor_gem_free_object,
> +	.print_info = panthor_gem_print_info,
> +	.pin = panthor_gem_pin_locked,
> +	.unpin = panthor_gem_unpin_locked,
> +	.get_sg_table = panthor_gem_get_sg_table,
> +	.vmap = panthor_gem_vmap_locked,
> +	.vunmap = panthor_gem_vunmap_locked,
> +	.mmap = panthor_gem_mmap,
> +	.status = panthor_gem_status,
> +	.export = panthor_gem_prime_export,
> +	.vm_ops = &panthor_gem_vm_ops,
> +};
>  
> -	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> -	if (!obj)
> +static struct panthor_gem_object *
> +panthor_gem_alloc_object(uint32_t flags)
> +{
> +	struct panthor_gem_object *bo;
> +
> +	bo = kzalloc(sizeof(*bo), GFP_KERNEL);
> +	if (!bo)
>  		return ERR_PTR(-ENOMEM);
>  
> -	obj->base.base.funcs = &panthor_gem_funcs;
> -	mutex_init(&obj->label.lock);
> +	bo->base.funcs = &panthor_gem_funcs;
> +	bo->flags = flags;
> +	mutex_init(&bo->label.lock);
> +	panthor_gem_debugfs_bo_init(bo);
> +	return bo;
> +}
>  
> -	panthor_gem_debugfs_bo_init(obj);
> +static struct panthor_gem_object *
> +panthor_gem_create(struct drm_device *dev, size_t size, uint32_t flags,
> +		   struct panthor_vm *exclusive_vm, u32 usage_flags)
> +{
> +	struct panthor_gem_object *bo;
> +	int ret;
>  
> -	return &obj->base.base;
> +	bo = panthor_gem_alloc_object(flags);
> +	if (IS_ERR(bo))
> +		return bo;
> +
> +	size = PAGE_ALIGN(size);
> +	ret = drm_gem_object_init(dev, &bo->base, size);
> +	if (ret)
> +		goto err_put;
> +
> +	/* Our buffers are kept pinned, so allocating them
> +	 * from the MOVABLE zone is a really bad idea, and
> +	 * conflicts with CMA. See comments above new_inode()
> +	 * why this is required _and_ expected if you're
> +	 * going to pin these pages.
> +	 */
> +	mapping_set_gfp_mask(bo->base.filp->f_mapping,
> +			     GFP_HIGHUSER | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
> +
> +	ret = drm_gem_create_mmap_offset(&bo->base);
> +	if (ret)
> +		goto err_put;
> +
> +	if (exclusive_vm) {
> +		bo->exclusive_vm_root_gem = panthor_vm_root_gem(exclusive_vm);
> +		drm_gem_object_get(bo->exclusive_vm_root_gem);
> +		bo->base.resv = bo->exclusive_vm_root_gem->resv;
> +	}
> +
> +	panthor_gem_debugfs_set_usage_flags(bo, usage_flags);
> +	return bo;
> +
> +err_put:
> +	drm_gem_object_put(&bo->base);

Is this call to _put() correct? I see the __drm_gem_shmem_init()
function uses drm_gem_object_release() instead.

> +	return ERR_PTR(ret);
> +}
> +
> +struct drm_gem_object *
> +panthor_gem_prime_import_sg_table(struct drm_device *dev,
> +				  struct dma_buf_attachment *attach,
> +				  struct sg_table *sgt)
> +{
> +	struct panthor_gem_object *bo;
> +	int ret;
> +
> +	bo = panthor_gem_alloc_object(0);
> +	if (IS_ERR(bo))
> +		return &bo->base;

bo->base is invalid here. I think you want ERR_CAST(bo).

> +
> +	drm_gem_private_object_init(dev, &bo->base, attach->dmabuf->size);
> +
> +	ret = drm_gem_create_mmap_offset(&bo->base);
> +	if (ret)
> +		goto err_put;
> +
> +	bo->dmap.sgt = sgt;
> +	return &bo->base;
> +
> +err_put:
> +	drm_gem_object_put(&bo->base);

Again I'm not convinced _put does the right thing here.

Thanks,
Steve

> +	return ERR_PTR(ret);
>  }
>  
>  /**
> @@ -325,54 +824,22 @@ panthor_gem_create_with_handle(struct drm_file *file,
>  			       u64 *size, u32 flags, u32 *handle)
>  {
>  	int ret;
> -	struct drm_gem_shmem_object *shmem;
>  	struct panthor_gem_object *bo;
>  
> -	shmem = drm_gem_shmem_create(ddev, *size);
> -	if (IS_ERR(shmem))
> -		return PTR_ERR(shmem);
> -
> -	bo = to_panthor_bo(&shmem->base);
> -	bo->flags = flags;
> -	bo->base.map_wc = should_map_wc(bo, exclusive_vm);
> -
> -	if (exclusive_vm) {
> -		bo->exclusive_vm_root_gem = panthor_vm_root_gem(exclusive_vm);
> -		drm_gem_object_get(bo->exclusive_vm_root_gem);
> -		bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
> -	}
> -
> -	panthor_gem_debugfs_set_usage_flags(bo, 0);
> -
> -	/* If this is a write-combine mapping, we query the sgt to force a CPU
> -	 * cache flush (dma_map_sgtable() is called when the sgt is created).
> -	 * This ensures the zero-ing is visible to any uncached mapping created
> -	 * by vmap/mmap.
> -	 * FIXME: Ideally this should be done when pages are allocated, not at
> -	 * BO creation time.
> -	 */
> -	if (shmem->map_wc) {
> -		struct sg_table *sgt;
> -
> -		sgt = drm_gem_shmem_get_pages_sgt(shmem);
> -		if (IS_ERR(sgt)) {
> -			ret = PTR_ERR(sgt);
> -			goto out_put_gem;
> -		}
> -	}
> +	bo = panthor_gem_create(ddev, *size, flags, exclusive_vm, 0);
> +	if (IS_ERR(bo))
> +		return PTR_ERR(bo);
>  
>  	/*
>  	 * Allocate an id of idr table where the obj is registered
>  	 * and handle has the id what user can see.
>  	 */
> -	ret = drm_gem_handle_create(file, &shmem->base, handle);
> +	ret = drm_gem_handle_create(file, &bo->base, handle);
>  	if (!ret)
> -		*size = bo->base.base.size;
> +		*size = bo->base.size;
>  
> -out_put_gem:
>  	/* drop reference from allocate - handle holds it now. */
> -	drm_gem_object_put(&shmem->base);
> -
> +	drm_gem_object_put(&bo->base);
>  	return ret;
>  }
>  
> @@ -417,18 +884,17 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>  		 u64 offset, u64 size)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(obj);
> -	struct drm_gem_shmem_object *shmem = &bo->base;
> -	const struct drm_device *dev = shmem->base.dev;
> +	struct device *dma_dev = drm_dev_dma_dev(bo->base.dev);
>  	struct sg_table *sgt;
>  	struct scatterlist *sgl;
>  	unsigned int count;
>  
>  	/* Make sure the range is in bounds. */
> -	if (offset + size < offset || offset + size > shmem->base.size)
> +	if (offset + size < offset || offset + size > bo->base.size)
>  		return -EINVAL;
>  
>  	/* Disallow CPU-cache maintenance on imported buffers. */
> -	if (drm_gem_is_imported(&shmem->base))
> +	if (drm_gem_is_imported(&bo->base))
>  		return -EINVAL;
>  
>  	switch (type) {
> @@ -441,14 +907,14 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>  	}
>  
>  	/* Don't bother if it's WC-mapped */
> -	if (shmem->map_wc)
> +	if (should_map_wc(bo))
>  		return 0;
>  
>  	/* Nothing to do if the size is zero. */
>  	if (size == 0)
>  		return 0;
>  
> -	sgt = drm_gem_shmem_get_pages_sgt(shmem);
> +	sgt = panthor_gem_get_dev_sgt(bo);
>  	if (IS_ERR(sgt))
>  		return PTR_ERR(sgt);
>  
> @@ -489,9 +955,9 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>  		 *
>  		 * for the flush+invalidate case.
>  		 */
> -		dma_sync_single_for_device(dev->dev, paddr, len, DMA_TO_DEVICE);
> +		dma_sync_single_for_device(dma_dev, paddr, len, DMA_TO_DEVICE);
>  		if (type == DRM_PANTHOR_BO_SYNC_CPU_CACHE_FLUSH_AND_INVALIDATE)
> -			dma_sync_single_for_cpu(dev->dev, paddr, len, DMA_FROM_DEVICE);
> +			dma_sync_single_for_cpu(dma_dev, paddr, len, DMA_FROM_DEVICE);
>  	}
>  
>  	return 0;
> @@ -541,7 +1007,6 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  			 size_t size, u32 bo_flags, u32 vm_map_flags,
>  			 u64 gpu_va, const char *name)
>  {
> -	struct drm_gem_shmem_object *obj;
>  	struct panthor_kernel_bo *kbo;
>  	struct panthor_gem_object *bo;
>  	u32 debug_flags = PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL;
> @@ -554,25 +1019,18 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  	if (!kbo)
>  		return ERR_PTR(-ENOMEM);
>  
> -	obj = drm_gem_shmem_create(&ptdev->base, size);
> -	if (IS_ERR(obj)) {
> -		ret = PTR_ERR(obj);
> -		goto err_free_bo;
> -	}
> -
> -	bo = to_panthor_bo(&obj->base);
> -	kbo->obj = &obj->base;
> -	bo->flags = bo_flags;
> -	bo->base.map_wc = should_map_wc(bo, vm);
> -	bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm);
> -	drm_gem_object_get(bo->exclusive_vm_root_gem);
> -	bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
> -
>  	if (vm == panthor_fw_vm(ptdev))
>  		debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED;
>  
> +	bo = panthor_gem_create(&ptdev->base, size, bo_flags, vm, debug_flags);
> +	if (IS_ERR(bo)) {
> +		ret = PTR_ERR(bo);
> +		goto err_free_kbo;
> +	}
> +
> +	kbo->obj = &bo->base;
> +
>  	panthor_gem_kernel_bo_set_label(kbo, name);
> -	panthor_gem_debugfs_set_usage_flags(to_panthor_bo(kbo->obj), debug_flags);
>  
>  	/* The system and GPU MMU page size might differ, which becomes a
>  	 * problem for FW sections that need to be mapped at explicit address
> @@ -596,9 +1054,9 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  	panthor_vm_free_va(vm, &kbo->va_node);
>  
>  err_put_obj:
> -	drm_gem_object_put(&obj->base);
> +	drm_gem_object_put(&bo->base);
>  
> -err_free_bo:
> +err_free_kbo:
>  	kfree(kbo);
>  	return ERR_PTR(ret);
>  }
> @@ -646,7 +1104,7 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
>  					 struct seq_file *m,
>  					 struct gem_size_totals *totals)
>  {
> -	unsigned int refcount = kref_read(&bo->base.base.refcount);
> +	unsigned int refcount = kref_read(&bo->base.refcount);
>  	char creator_info[32] = {};
>  	size_t resident_size;
>  	u32 gem_usage_flags = bo->debugfs.flags;
> @@ -656,21 +1114,21 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
>  	if (!refcount)
>  		return;
>  
> -	resident_size = bo->base.pages ? bo->base.base.size : 0;
> +	resident_size = bo->backing.pages ? bo->base.size : 0;
>  
>  	snprintf(creator_info, sizeof(creator_info),
>  		 "%s/%d", bo->debugfs.creator.process_name, bo->debugfs.creator.tgid);
>  	seq_printf(m, "%-32s%-16d%-16d%-16zd%-16zd0x%-16lx",
>  		   creator_info,
> -		   bo->base.base.name,
> +		   bo->base.name,
>  		   refcount,
> -		   bo->base.base.size,
> +		   bo->base.size,
>  		   resident_size,
> -		   drm_vma_node_start(&bo->base.base.vma_node));
> +		   drm_vma_node_start(&bo->base.vma_node));
>  
> -	if (bo->base.base.import_attach)
> +	if (bo->base.import_attach)
>  		gem_state_flags |= PANTHOR_DEBUGFS_GEM_STATE_FLAG_IMPORTED;
> -	if (bo->base.base.dma_buf)
> +	if (bo->base.dma_buf)
>  		gem_state_flags |= PANTHOR_DEBUGFS_GEM_STATE_FLAG_EXPORTED;
>  
>  	seq_printf(m, "0x%-8x 0x%-10x", gem_state_flags, gem_usage_flags);
> @@ -679,10 +1137,8 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
>  		seq_printf(m, "%s\n", bo->label.str ? : "");
>  	}
>  
> -	totals->size += bo->base.base.size;
> +	totals->size += bo->base.size;
>  	totals->resident += resident_size;
> -	if (bo->base.madv > 0)
> -		totals->reclaimable += resident_size;
>  }
>  
>  static void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
> index 94b2d17cf032..b66478c9590c 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.h
> +++ b/drivers/gpu/drm/panthor/panthor_gem.h
> @@ -5,7 +5,7 @@
>  #ifndef __PANTHOR_GEM_H__
>  #define __PANTHOR_GEM_H__
>  
> -#include <drm/drm_gem_shmem_helper.h>
> +#include <drm/drm_gem.h>
>  #include <drm/drm_mm.h>
>  
>  #include <linux/iosys-map.h>
> @@ -60,12 +60,51 @@ struct panthor_gem_debugfs {
>  	u32 flags;
>  };
>  
> +/**
> + * struct panthor_gem_backing - GEM memory backing related data
> + */
> +struct panthor_gem_backing {
> +	/** @pages: Pages requested with drm_gem_get_pages() */
> +	struct page **pages;
> +
> +	/** @pin_count: Number of active pin requests on this GEM */
> +	refcount_t pin_count;
> +};
> +
> +/**
> + * struct panthor_gem_cpu_map - GEM CPU mapping related data
> + */
> +struct panthor_gem_cpu_map {
> +	/** @vaddr: Address returned by vmap() */
> +	void *vaddr;
> +
> +	/** @vaddr_use_count: Number of active vmap() requests on this GEM */
> +	refcount_t vaddr_use_count;
> +};
> +
> +/**
> + * struct panthor_gem_dev_map - GEM device mapping related data
> + */
> +struct panthor_gem_dev_map {
> +	/** @sgt: Device mapped sg_table for this GEM */
> +	struct sg_table *sgt;
> +};
> +
>  /**
>   * struct panthor_gem_object - Driver specific GEM object.
>   */
>  struct panthor_gem_object {
> -	/** @base: Inherit from drm_gem_shmem_object. */
> -	struct drm_gem_shmem_object base;
> +	/** @base: Inherit from drm_gem_object. */
> +	struct drm_gem_object base;
> +
> +	/** @backing: Memory backing state */
> +	struct panthor_gem_backing backing;
> +
> +	/** @cmap: CPU mapping state */
> +	struct panthor_gem_cpu_map cmap;
> +
> +	/** @dmap: Device mapping state */
> +	struct panthor_gem_dev_map dmap;
>  
>  	/**
>  	 * @exclusive_vm_root_gem: Root GEM of the exclusive VM this GEM object
> @@ -130,22 +169,25 @@ struct panthor_kernel_bo {
>  	void *kmap;
>  };
>  
> -static inline
> -struct panthor_gem_object *to_panthor_bo(struct drm_gem_object *obj)
> -{
> -	return container_of(to_drm_gem_shmem_obj(obj), struct panthor_gem_object, base);
> -}
> +#define to_panthor_bo(obj) container_of_const(obj, struct panthor_gem_object, base)
>  
>  void panthor_gem_init(struct panthor_device *ptdev);
>  
> -struct drm_gem_object *panthor_gem_create_object(struct drm_device *ddev, size_t size);
> -
> +struct drm_gem_object *
> +panthor_gem_prime_import_sg_table(struct drm_device *dev,
> +				  struct dma_buf_attachment *attach,
> +				  struct sg_table *sgt);
>  int
>  panthor_gem_create_with_handle(struct drm_file *file,
>  			       struct drm_device *ddev,
>  			       struct panthor_vm *exclusive_vm,
>  			       u64 *size, u32 flags, uint32_t *handle);
>  
> +struct sg_table *
> +panthor_gem_get_dev_sgt(struct panthor_gem_object *bo);
> +int panthor_gem_pin(struct panthor_gem_object *bo);
> +void panthor_gem_unpin(struct panthor_gem_object *bo);
> +
>  void panthor_gem_bo_set_label(struct drm_gem_object *obj, const char *label);
>  void panthor_gem_kernel_bo_set_label(struct panthor_kernel_bo *bo, const char *label);
>  int panthor_gem_sync(struct drm_gem_object *obj,
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index b888fff05efe..c323b7123713 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -5,6 +5,7 @@
>  #include <drm/drm_debugfs.h>
>  #include <drm/drm_drv.h>
>  #include <drm/drm_exec.h>
> +#include <drm/drm_file.h>
>  #include <drm/drm_gpuvm.h>
>  #include <drm/drm_managed.h>
>  #include <drm/drm_print.h>
> @@ -1073,8 +1074,7 @@ static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
>  
> -	if (!drm_gem_is_imported(&bo->base.base))
> -		drm_gem_shmem_unpin(&bo->base);
> +	panthor_gem_unpin(bo);
>  	kfree(vm_bo);
>  }
>  
> @@ -1196,7 +1196,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  		return -EINVAL;
>  
>  	/* Make sure the VA and size are in-bounds. */
> -	if (size > bo->base.base.size || offset > bo->base.base.size - size)
> +	if (size > bo->base.size || offset > bo->base.size - size)
>  		return -EINVAL;
>  
>  	/* If the BO has an exclusive VM attached, it can't be mapped to other VMs. */
> @@ -1213,33 +1213,25 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	if (ret)
>  		goto err_cleanup;
>  
> -	if (!drm_gem_is_imported(&bo->base.base)) {
> -		/* Pre-reserve the BO pages, so the map operation doesn't have to
> -		 * allocate. This pin is dropped in panthor_vm_bo_free(), so
> -		 * once we have successfully called drm_gpuvm_bo_create(),
> -		 * GPUVM will take care of dropping the pin for us.
> -		 */
> -		ret = drm_gem_shmem_pin(&bo->base);
> -		if (ret)
> -			goto err_cleanup;
> -	}
> +	/* Pre-reserve the BO pages, so the map operation doesn't have to
> +	 * allocate.
> +	 */
> +	ret = panthor_gem_pin(bo);
> +	if (ret)
> +		goto err_cleanup;
>  
> -	sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
> +	sgt = panthor_gem_get_dev_sgt(bo);
>  	if (IS_ERR(sgt)) {
> -		if (!drm_gem_is_imported(&bo->base.base))
> -			drm_gem_shmem_unpin(&bo->base);
> -
> +		panthor_gem_unpin(bo);
>  		ret = PTR_ERR(sgt);
>  		goto err_cleanup;
>  	}
>  
>  	op_ctx->map.sgt = sgt;
>  
> -	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base.base);
> +	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base);
>  	if (!preallocated_vm_bo) {
> -		if (!drm_gem_is_imported(&bo->base.base))
> -			drm_gem_shmem_unpin(&bo->base);
> -
> +		panthor_gem_unpin(bo);
>  		ret = -ENOMEM;
>  		goto err_cleanup;
>  	}
> @@ -1251,9 +1243,9 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	 * calling this function.
>  	 */
>  	dma_resv_lock(panthor_vm_resv(vm), NULL);
> -	mutex_lock(&bo->base.base.gpuva.lock);
> +	mutex_lock(&bo->base.gpuva.lock);
>  	op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
> -	mutex_unlock(&bo->base.base.gpuva.lock);
> +	mutex_unlock(&bo->base.gpuva.lock);
>  	dma_resv_unlock(panthor_vm_resv(vm));
>  
>  	op_ctx->map.bo_offset = offset;
> @@ -2063,9 +2055,9 @@ static void panthor_vma_link(struct panthor_vm *vm,
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(vma->base.gem.obj);
>  
> -	mutex_lock(&bo->base.base.gpuva.lock);
> +	mutex_lock(&bo->base.gpuva.lock);
>  	drm_gpuva_link(&vma->base, vm_bo);
> -	mutex_unlock(&bo->base.base.gpuva.lock);
> +	mutex_unlock(&bo->base.gpuva.lock);
>  }
>  
>  static void panthor_vma_unlink(struct panthor_vma *vma)
> @@ -2117,11 +2109,12 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
>  static bool
>  iova_mapped_as_huge_page(struct drm_gpuva_op_map *op, u64 addr)
>  {
> +	struct panthor_gem_object *bo = to_panthor_bo(op->gem.obj);
>  	const struct page *pg;
>  	pgoff_t bo_offset;
>  
>  	bo_offset = addr - op->va.addr + op->gem.offset;
> -	pg = to_panthor_bo(op->gem.obj)->base.pages[bo_offset >> PAGE_SHIFT];
> +	pg = bo->backing.pages[bo_offset >> PAGE_SHIFT];
>  
>  	return folio_size(page_folio(pg)) >= SZ_2M;
>  }
> @@ -2190,7 +2183,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  		u64 size = op->remap.prev->va.addr + op->remap.prev->va.range - unmap_start;
>  
>  		ret = panthor_vm_map_pages(vm, unmap_start, flags_to_prot(unmap_vma->flags),
> -					   bo->base.sgt, offset, size);
> +					   bo->dmap.sgt, offset, size);
>  		if (ret)
>  			return ret;
>  
> @@ -2204,7 +2197,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  		u64 size = unmap_start + unmap_range - op->remap.next->va.addr;
>  
>  		ret = panthor_vm_map_pages(vm, addr, flags_to_prot(unmap_vma->flags),
> -					   bo->base.sgt, op->remap.next->gem.offset, size);
> +					   bo->dmap.sgt, op->remap.next->gem.offset, size);
>  		if (ret)
>  			return ret;
>  
> diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c
> index ca272dbae14d..2ddf64870d85 100644
> --- a/drivers/gpu/drm/panthor/panthor_sched.c
> +++ b/drivers/gpu/drm/panthor/panthor_sched.c
> @@ -3,7 +3,7 @@
>  
>  #include <drm/drm_drv.h>
>  #include <drm/drm_exec.h>
> -#include <drm/drm_gem_shmem_helper.h>
> +#include <drm/drm_file.h>
>  #include <drm/drm_managed.h>
>  #include <drm/drm_print.h>
>  #include <drm/gpu_scheduler.h>
> @@ -871,8 +871,7 @@ panthor_queue_get_syncwait_obj(struct panthor_group *group, struct panthor_queue
>  	int ret;
>  
>  	if (queue->syncwait.kmap) {
> -		bo = container_of(queue->syncwait.obj,
> -				  struct panthor_gem_object, base.base);
> +		bo = to_panthor_bo(queue->syncwait.obj);
>  		goto out_sync;
>  	}
>  
> @@ -882,7 +881,7 @@ panthor_queue_get_syncwait_obj(struct panthor_group *group, struct panthor_queue
>  	if (drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(bo)))
>  		goto err_put_syncwait_obj;
>  
> -	queue->syncwait.obj = &bo->base.base;
> +	queue->syncwait.obj = &bo->base;
>  	ret = drm_gem_vmap(queue->syncwait.obj, &map);
>  	if (drm_WARN_ON(&ptdev->base, ret))
>  		goto err_put_syncwait_obj;
> @@ -896,7 +895,7 @@ panthor_queue_get_syncwait_obj(struct panthor_group *group, struct panthor_queue
>  	 * drm_gem_shmem_sync() is a NOP if map_wc=true, so no need to check
>  	 * it here.
>  	 */
> -	panthor_gem_sync(&bo->base.base, queue->syncwait.offset,
> +	panthor_gem_sync(&bo->base, queue->syncwait.offset,
>  			 queue->syncwait.sync64 ?
>  			 sizeof(struct panthor_syncobj_64b) :
>  			 sizeof(struct panthor_syncobj_32b),


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap()
  2026-01-09 13:07 ` [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap() Boris Brezillon
@ 2026-01-12 12:15   ` Steven Price
  2026-01-12 14:32     ` Boris Brezillon
  2026-01-15 17:34   ` Liviu Dudau
  2026-01-16  8:19   ` kernel test robot
  2 siblings, 1 reply; 62+ messages in thread
From: Steven Price @ 2026-01-12 12:15 UTC (permalink / raw)
  To: Boris Brezillon, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 09/01/2026 13:07, Boris Brezillon wrote:
> Defer pages allocation until their first access.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>  drivers/gpu/drm/panthor/panthor_gem.c | 119 ++++++++++++++++----------
>  1 file changed, 75 insertions(+), 44 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index 0e52c7a07c87..44f05bd957e7 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -600,15 +600,6 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
>  	if (is_cow_mapping(vma->vm_flags))
>  		return -EINVAL;
>  
> -	dma_resv_lock(obj->resv, NULL);
> -	ret = panthor_gem_backing_get_pages_locked(bo);
> -	if (!ret)
> -		ret = panthor_gem_prep_for_cpu_map_locked(bo);
> -	dma_resv_unlock(obj->resv);
> -
> -	if (ret)
> -		return ret;
> -
>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
>  	if (should_map_wc(bo))
> @@ -628,82 +619,122 @@ static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
>  	return res;
>  }
>  
> -static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
> +static vm_fault_t insert_page(struct vm_fault *vmf, struct page *page)
>  {
> +	struct vm_area_struct *vma = vmf->vma;
> +	vm_fault_t ret;
> +
>  #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
>  	unsigned long pfn = page_to_pfn(page);
>  	unsigned long paddr = pfn << PAGE_SHIFT;
> -	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
> +	bool aligned = (vmf->address & ~PMD_MASK) == (paddr & ~PMD_MASK);
>  
>  	if (aligned &&
>  	    pmd_none(*vmf->pmd) &&
>  	    folio_test_pmd_mappable(page_folio(page))) {
>  		pfn &= PMD_MASK >> PAGE_SHIFT;
> -		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
> -			return true;
> +		ret = vmf_insert_pfn_pmd(vmf, pfn, false);
> +		if (ret == VM_FAULT_NOPAGE)
> +			return VM_FAULT_NOPAGE;
>  	}
>  #endif
>  
> -	return false;
> +	return vmf_insert_pfn(vma, vmf->address, page_to_pfn(page));
>  }
>  
> -static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> +static vm_fault_t nonblocking_page_setup(struct vm_fault *vmf, pgoff_t page_offset)
>  {
>  	struct vm_area_struct *vma = vmf->vma;
> -	struct drm_gem_object *obj = vma->vm_private_data;
>  	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> -	loff_t num_pages = obj->size >> PAGE_SHIFT;
>  	vm_fault_t ret;
> -	pgoff_t page_offset;
> -	unsigned long pfn;
>  
> -	/* Offset to faulty address in the VMA. */
> -	page_offset = vmf->pgoff - vma->vm_pgoff;
> +	if (!dma_resv_trylock(bo->base.resv))
> +		return VM_FAULT_RETRY;
>  
> -	dma_resv_lock(bo->base.resv, NULL);
> +	if (bo->backing.pages)
> +		ret = insert_page(vmf, bo->backing.pages[page_offset]);
> +	else
> +		ret = VM_FAULT_RETRY;
>  
> -	if (page_offset >= num_pages ||
> -	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
> -		ret = VM_FAULT_SIGBUS;
> -		goto out;
> +	dma_resv_unlock(bo->base.resv);
> +	return ret;
> +}
> +
> +static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
> +				      struct panthor_gem_object *bo,
> +				      pgoff_t page_offset, bool mmap_lock_held)
> +{
> +	vm_fault_t ret;
> +	int err;
> +
> +	if (vmf->flags & FAULT_FLAG_INTERRUPTIBLE) {
> +		err = dma_resv_lock_interruptible(bo->base.resv, NULL);
> +		if (err)
> +			return mmap_lock_held ? VM_FAULT_NOPAGE : VM_FAULT_RETRY;

I'm not sure about this. First FAULT_FLAG_INTERRUPTIBLE is currently
only used by userfaultfd AFAICT. Second returning VM_FAULT_NOPAGE seems
wrong - that's for the case were we've inserted a pte but in this case
we haven't.

Otherwise I couldn't spot any issues staring at the code, but I might
have missed something. mm code is always hard to follow!

Thanks,
Steve

> +	} else {
> +		dma_resv_lock(bo->base.resv, NULL);
>  	}
>  
> -	if (try_map_pmd(vmf, vmf->address, bo->backing.pages[page_offset])) {
> -		ret = VM_FAULT_NOPAGE;
> -		goto out;
> +	err = panthor_gem_backing_get_pages_locked(bo);
> +	if (!err)
> +		err = panthor_gem_prep_for_cpu_map_locked(bo);
> +
> +	if (err) {
> +		ret = mmap_lock_held ? VM_FAULT_SIGBUS : VM_FAULT_RETRY;
> +	} else {
> +		struct page *page = bo->backing.pages[page_offset];
> +
> +		if (mmap_lock_held)
> +			ret = insert_page(vmf, page);
> +		else
> +			ret = VM_FAULT_RETRY;
>  	}
>  
> -	pfn = page_to_pfn(bo->backing.pages[page_offset]);
> -	ret = vmf_insert_pfn(vma, vmf->address, pfn);
> -
> - out:
>  	dma_resv_unlock(bo->base.resv);
>  
>  	return ret;
>  }
>  
> -static void panthor_gem_vm_open(struct vm_area_struct *vma)
> +static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
>  {
> +	struct vm_area_struct *vma = vmf->vma;
>  	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> +	loff_t num_pages = bo->base.size >> PAGE_SHIFT;
> +	pgoff_t page_offset;
> +	vm_fault_t ret;
>  
> -	drm_WARN_ON(bo->base.dev, drm_gem_is_imported(&bo->base));
> +	/* We don't use vmf->pgoff since that has the fake offset */
> +	page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
> +	if (page_offset >= num_pages)
> +		return VM_FAULT_SIGBUS;
>  
> -	dma_resv_lock(bo->base.resv, NULL);
> +	ret = nonblocking_page_setup(vmf, page_offset);
> +	if (ret != VM_FAULT_RETRY)
> +		return ret;
>  
> -	/* We should have already pinned the pages when the buffer was first
> -	 * mmap'd, vm_open() just grabs an additional reference for the new
> -	 * mm the vma is getting copied into (ie. on fork()).
> -	 */
> -	drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages);
> +	/* Check if we're allowed to retry. */
> +	if (fault_flag_allow_retry_first(vmf->flags)) {
> +		/* If we're allowed to retry but not wait here, return
> +		 * immediately, the wait will be done when the fault
> +		 * handler is called again, with the mmap_lock held.
> +		 */
> +		if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT)
> +			return VM_FAULT_RETRY;
>  
> -	dma_resv_unlock(bo->base.resv);
> +		/* Wait with the mmap lock released, if we're allowed to. */
> +		drm_gem_object_get(&bo->base);
> +		mmap_read_unlock(vmf->vma->vm_mm);
> +		ret = blocking_page_setup(vmf, bo, page_offset, false);
> +		drm_gem_object_put(&bo->base);
> +		return ret;
> +	}
>  
> -	drm_gem_vm_open(vma);
> +	return blocking_page_setup(vmf, bo, page_offset, true);
>  }
>  
>  const struct vm_operations_struct panthor_gem_vm_ops = {
>  	.fault = panthor_gem_fault,
> -	.open = panthor_gem_vm_open,
> +	.open = drm_gem_vm_open,
>  	.close = drm_gem_vm_close,
>  };
>  


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 7/9] drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for reclaim
  2026-01-09 13:07 ` [PATCH v1 7/9] drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for reclaim Boris Brezillon
@ 2026-01-12 12:21   ` Steven Price
  2026-01-15 17:40   ` Liviu Dudau
  1 sibling, 0 replies; 62+ messages in thread
From: Steven Price @ 2026-01-12 12:21 UTC (permalink / raw)
  To: Boris Brezillon, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 09/01/2026 13:07, Boris Brezillon wrote:
> We're gonna need just the page table reservation logic when we restore
> evicted BO mappings, so let's prepare for that by extracting the
> op_ctx init and page table pre-allocation into separate helpers.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>

Reviewed-by: Steven Price <steven.price@arm.com>

(A nice easy refactor after trying to get my head round mm-code - just
what I needed ;) )

Thanks,
Steve

> ---
>  drivers/gpu/drm/panthor/panthor_mmu.c | 70 ++++++++++++++++-----------
>  1 file changed, 42 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index c323b7123713..3290e0b5facb 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -1170,6 +1170,45 @@ panthor_vm_op_ctx_prealloc_vmas(struct panthor_vm_op_ctx *op_ctx)
>  	return 0;
>  }
>  
> +static void panthor_vm_init_op_ctx(struct panthor_vm_op_ctx *op_ctx,
> +				   u64 size, u64 va, u32 flags)
> +{
> +	memset(op_ctx, 0, sizeof(*op_ctx));
> +	op_ctx->flags = flags;
> +	op_ctx->va.range = size;
> +	op_ctx->va.addr = va;
> +}
> +
> +static int panthor_vm_op_ctx_prealloc_pts(struct panthor_vm_op_ctx *op_ctx)
> +{
> +	u64 size = op_ctx->va.range;
> +	u64 va = op_ctx->va.addr;
> +	int ret;
> +
> +	/* L1, L2 and L3 page tables.
> +	 * We could optimize L3 allocation by iterating over the sgt and merging
> +	 * 2M contiguous blocks, but it's simpler to over-provision and return
> +	 * the pages if they're not used.
> +	 */
> +	u64 pt_count = ((ALIGN(va + size, 1ull << 39) - ALIGN_DOWN(va, 1ull << 39)) >> 39) +
> +		       ((ALIGN(va + size, 1ull << 30) - ALIGN_DOWN(va, 1ull << 30)) >> 30) +
> +		       ((ALIGN(va + size, 1ull << 21) - ALIGN_DOWN(va, 1ull << 21)) >> 21);
> +
> +	op_ctx->rsvd_page_tables.pages = kcalloc(pt_count,
> +						 sizeof(*op_ctx->rsvd_page_tables.pages),
> +						 GFP_KERNEL);
> +	if (!op_ctx->rsvd_page_tables.pages)
> +		return -ENOMEM;
> +
> +	ret = kmem_cache_alloc_bulk(pt_cache, GFP_KERNEL, pt_count,
> +				    op_ctx->rsvd_page_tables.pages);
> +	op_ctx->rsvd_page_tables.count = ret;
> +	if (ret != pt_count)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> +
>  #define PANTHOR_VM_BIND_OP_MAP_FLAGS \
>  	(DRM_PANTHOR_VM_BIND_OP_MAP_READONLY | \
>  	 DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC | \
> @@ -1185,7 +1224,6 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  {
>  	struct drm_gpuvm_bo *preallocated_vm_bo;
>  	struct sg_table *sgt = NULL;
> -	u64 pt_count;
>  	int ret;
>  
>  	if (!bo)
> @@ -1204,10 +1242,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	    bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm))
>  		return -EINVAL;
>  
> -	memset(op_ctx, 0, sizeof(*op_ctx));
> -	op_ctx->flags = flags;
> -	op_ctx->va.range = size;
> -	op_ctx->va.addr = va;
> +	panthor_vm_init_op_ctx(op_ctx, size, va, flags);
>  
>  	ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx);
>  	if (ret)
> @@ -1250,30 +1285,9 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  
>  	op_ctx->map.bo_offset = offset;
>  
> -	/* L1, L2 and L3 page tables.
> -	 * We could optimize L3 allocation by iterating over the sgt and merging
> -	 * 2M contiguous blocks, but it's simpler to over-provision and return
> -	 * the pages if they're not used.
> -	 */
> -	pt_count = ((ALIGN(va + size, 1ull << 39) - ALIGN_DOWN(va, 1ull << 39)) >> 39) +
> -		   ((ALIGN(va + size, 1ull << 30) - ALIGN_DOWN(va, 1ull << 30)) >> 30) +
> -		   ((ALIGN(va + size, 1ull << 21) - ALIGN_DOWN(va, 1ull << 21)) >> 21);
> -
> -	op_ctx->rsvd_page_tables.pages = kcalloc(pt_count,
> -						 sizeof(*op_ctx->rsvd_page_tables.pages),
> -						 GFP_KERNEL);
> -	if (!op_ctx->rsvd_page_tables.pages) {
> -		ret = -ENOMEM;
> +	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
> +	if (ret)
>  		goto err_cleanup;
> -	}
> -
> -	ret = kmem_cache_alloc_bulk(pt_cache, GFP_KERNEL, pt_count,
> -				    op_ctx->rsvd_page_tables.pages);
> -	op_ctx->rsvd_page_tables.count = ret;
> -	if (ret != pt_count) {
> -		ret = -ENOMEM;
> -		goto err_cleanup;
> -	}
>  
>  	/* Insert BO into the extobj list last, when we know nothing can fail. */
>  	dma_resv_lock(panthor_vm_resv(vm), NULL);


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-09 13:08 ` [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO Boris Brezillon
@ 2026-01-12 12:33   ` Steven Price
  2026-01-12 14:39     ` Boris Brezillon
  0 siblings, 1 reply; 62+ messages in thread
From: Steven Price @ 2026-01-12 12:33 UTC (permalink / raw)
  To: Boris Brezillon, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 09/01/2026 13:08, Boris Brezillon wrote:
> This will be used to order things by reclaimability.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>  drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
>  drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
>  2 files changed, 45 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index 44f05bd957e7..458d22380e96 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
>  	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
>  			  refcount_read(&bo->cmap.vaddr_use_count));
>  	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> +	drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
>  }
>  
>  static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> @@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
>  	if (is_cow_mapping(vma->vm_flags))
>  		return -EINVAL;
>  
> +	if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
> +		dma_resv_lock(obj->resv, NULL);
> +		if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
> +			refcount_set(&bo->cmap.mmap_count, 1);
> +		dma_resv_unlock(obj->resv);
> +	}
> +
>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
>  	if (should_map_wc(bo))
> @@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
>  	return blocking_page_setup(vmf, bo, page_offset, true);
>  }
>  
> +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> +
> +	/* mmap_count must have been incremented at mmap time, so it can't be
> +	 * zero here.
> +	 */
> +	if (!drm_gem_is_imported(&bo->base))
> +		drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
> +
> +	drm_gem_vm_open(vma);
> +}
> +
> +static void panthor_gem_vm_close(struct vm_area_struct *vma)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> +
> +	if (drm_gem_is_imported(&bo->base))
> +		goto out;
> +
> +	if (refcount_dec_not_one(&bo->cmap.mmap_count))
> +		goto out;
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> +		refcount_set(&bo->cmap.mmap_count, 0);
> +	dma_resv_unlock(bo->base.resv);

I don't think this logic is safe. Holding the resv_lock doesn't protect
against another thread doing a refcount_inc_not_zero() without holding
the lock.

I think you can just replace the if() part with a refcount_dec() call,
the lock AFAICT is needed because the following patch wants to be sure
that !!mmap_count is stable when resv_lock is held.

I also feel you should invert the conditino for refcount_dec_not_one,
leading to the following which I feel is easier to read:

static void panthor_gem_vm_close(struct vm_area_struct *vma)
{
	[...]

	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
		dma_resv_lock(bo->base.resv, NULL);
		refcount_dec(&bo->cmap.mmap_count);
		dma_resv_unlock(bo->base.resv);
	}

	drm_gem_object_put(&bo->base);
}

Thanks,
Steve

> +
> +out:
> +	drm_gem_object_put(&bo->base);
> +}
> +
>  const struct vm_operations_struct panthor_gem_vm_ops = {
>  	.fault = panthor_gem_fault,
> -	.open = drm_gem_vm_open,
> -	.close = drm_gem_vm_close,
> +	.open = panthor_gem_vm_open,
> +	.close = panthor_gem_vm_close,
>  };
>  
>  static const struct drm_gem_object_funcs panthor_gem_funcs = {
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
> index b66478c9590c..c0a18dca732c 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.h
> +++ b/drivers/gpu/drm/panthor/panthor_gem.h
> @@ -80,6 +80,9 @@ struct panthor_gem_cpu_map {
>  
>  	/** @vaddr_use_count: Number of active vmap() requests on this GEM */
>  	refcount_t vaddr_use_count;
> +
> +	/** @mmap_count: Number of active mmap() requests on this GEM */
> +	refcount_t mmap_count;
>  };
>  
>  /**


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-12 12:06   ` Steven Price
@ 2026-01-12 14:17     ` Boris Brezillon
  2026-01-12 16:03       ` Steven Price
  2026-01-21 11:11       ` Akash Goel
  0 siblings, 2 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 14:17 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Mon, 12 Jan 2026 12:06:17 +0000
Steven Price <steven.price@arm.com> wrote:

> On 09/01/2026 13:07, Boris Brezillon wrote:
> > While drm_gem_shmem_object does most of the job we need it to do, the
> > way sub-resources (pages, sgt, vmap) are handled and their lifetimes
> > gets in the way of BO reclaim. There has been attempts to address
> > that [1], but in the meantime, new gem_shmem users were introduced
> > (accel drivers), and some of them manually free some of these resources.
> > This makes things harder to control/sanitize/validate.
> > 
> > Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
> > and forcing gem_shmem users to go through new gem_shmem helpers when they
> > need manual control of some sort, and I believe this is a dead end if
> > we don't force users to follow some stricter rules through carefully
> > designed helpers, because there will always be one user doing crazy things
> > with gem_shmem_object internals, which ends up tripping out the common
> > helpers when they are called.
> > 
> > The consensus we reached was that we would be better off forking
> > gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
> > current transition tries to minimize the changes, but there are still
> > some aspects that are different, the main one being that we no longer
> > have a pages_use_count, and pages stays around until the GEM object is
> > destroyed (or when evicted once we've added a shrinker). The sgt also
> > no longer retains pages. This is losely based on how msm does things by
> > the way.  
> 
> From a reviewing perspective it's a little tricky trying to match up the
> implementation to shmem because of these changes. I don't know how
> difficult it would be to split the changes to a patch which literally
> copies (with renames) from shmem, followed by simplifying out the parts
> we don't want.

It's a bit annoying as the new implementation is not based on shmem at
all, but if you think it helps the review, I can try what you're
suggesting. I mean, I'm not convinced it will be significantly easier
to review with this extra step, since the new logic is different enough
(especially when it comes to resource refcounting) that it needs a
careful review anyway (which you started doing here).

> 
> Of course the main issue is going to be getting some proper testing of
> this (especially with the shrinker added).

For the shrinker, the best I can propose for now is extending the
IGT tests I've added. For close-to-real-usecases testing of the shmem ->
custom transition (this commit), making sure the g610 jobs we have in
mesa CI still passes is a start. If you have other ideas, I'd be happy
to give them a try.

> 
> > 
> > If there's any interest in sharing code (probably with msm, since the
> > panthor shrinker is going to be losely based on the msm implementation),
> > we can always change gears and do that once we have everything
> > working/merged.
> > 
> > [1]https://patchwork.kernel.org/project/dri-devel/patch/20240105184624.508603-1-dmitry.osipenko@collabora.com/
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > ---
> >  drivers/gpu/drm/panthor/Kconfig         |   1 -
> >  drivers/gpu/drm/panthor/panthor_drv.c   |   7 +-
> >  drivers/gpu/drm/panthor/panthor_fw.c    |  16 +-
> >  drivers/gpu/drm/panthor/panthor_gem.c   | 696 ++++++++++++++++++++----
> >  drivers/gpu/drm/panthor/panthor_gem.h   |  62 ++-
> >  drivers/gpu/drm/panthor/panthor_mmu.c   |  49 +-
> >  drivers/gpu/drm/panthor/panthor_sched.c |   9 +-
> >  7 files changed, 666 insertions(+), 174 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig
> > index 55b40ad07f3b..911e7f4810c3 100644
> > --- a/drivers/gpu/drm/panthor/Kconfig
> > +++ b/drivers/gpu/drm/panthor/Kconfig
> > @@ -8,7 +8,6 @@ config DRM_PANTHOR
> >  	depends on MMU
> >  	select DEVFREQ_GOV_SIMPLE_ONDEMAND
> >  	select DRM_EXEC
> > -	select DRM_GEM_SHMEM_HELPER
> >  	select DRM_GPUVM
> >  	select DRM_SCHED
> >  	select IOMMU_IO_PGTABLE_LPAE
> > diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
> > index 52c27a60c84a..90e9abc22d9e 100644
> > --- a/drivers/gpu/drm/panthor/panthor_drv.c
> > +++ b/drivers/gpu/drm/panthor/panthor_drv.c
> > @@ -19,6 +19,7 @@
> >  #include <drm/drm_debugfs.h>
> >  #include <drm/drm_drv.h>
> >  #include <drm/drm_exec.h>
> > +#include <drm/drm_file.h>
> >  #include <drm/drm_ioctl.h>
> >  #include <drm/drm_print.h>
> >  #include <drm/drm_syncobj.h>
> > @@ -1457,7 +1458,7 @@ static int panthor_ioctl_bo_query_info(struct drm_device *ddev, void *data,
> >  	args->create_flags = bo->flags;
> >  
> >  	args->extra_flags = 0;
> > -	if (drm_gem_is_imported(&bo->base.base))
> > +	if (drm_gem_is_imported(&bo->base))
> >  		args->extra_flags |= DRM_PANTHOR_BO_IS_IMPORTED;
> >  
> >  	drm_gem_object_put(obj);
> > @@ -1671,8 +1672,7 @@ static const struct drm_driver panthor_drm_driver = {
> >  	.major = 1,
> >  	.minor = 7,
> >  
> > -	.gem_create_object = panthor_gem_create_object,
> > -	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
> > +	.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
> >  	.gem_prime_import = panthor_gem_prime_import,
> >  #ifdef CONFIG_DEBUG_FS
> >  	.debugfs_init = panthor_debugfs_init,
> > @@ -1822,3 +1822,4 @@ module_exit(panthor_exit);
> >  MODULE_AUTHOR("Panthor Project Developers");
> >  MODULE_DESCRIPTION("Panthor DRM Driver");
> >  MODULE_LICENSE("Dual MIT/GPL");
> > +MODULE_IMPORT_NS("DMA_BUF");
> > diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
> > index a64ec8756bed..f135cf2130b8 100644
> > --- a/drivers/gpu/drm/panthor/panthor_fw.c
> > +++ b/drivers/gpu/drm/panthor/panthor_fw.c
> > @@ -627,7 +627,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
> >  		u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK;
> >  		struct panthor_gem_object *bo;
> >  		u32 vm_map_flags = 0;
> > -		struct sg_table *sgt;
> >  		u64 va = hdr.va.start;
> >  
> >  		if (!(hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_WR))
> > @@ -665,11 +664,12 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
> >  		panthor_fw_init_section_mem(ptdev, section);
> >  
> >  		bo = to_panthor_bo(section->mem->obj);
> > -		sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
> > -		if (IS_ERR(sgt))
> > -			return PTR_ERR(sgt);
> >  
> > -		dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> > +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
> > +		if (drm_WARN_ON_ONCE(&ptdev->base, !bo->dmap.sgt))
> > +			return -EINVAL;
> > +
> > +		dma_sync_sgtable_for_device(ptdev->base.dev, bo->dmap.sgt, DMA_TO_DEVICE);
> >  	}
> >  
> >  	if (hdr.va.start == CSF_MCU_SHARED_REGION_START)
> > @@ -729,8 +729,10 @@ panthor_reload_fw_sections(struct panthor_device *ptdev, bool full_reload)
> >  			continue;
> >  
> >  		panthor_fw_init_section_mem(ptdev, section);
> > -		sgt = drm_gem_shmem_get_pages_sgt(&to_panthor_bo(section->mem->obj)->base);
> > -		if (!drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(sgt)))
> > +
> > +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
> > +		sgt = to_panthor_bo(section->mem->obj)->dmap.sgt;
> > +		if (!drm_WARN_ON_ONCE(&ptdev->base, !sgt))
> >  			dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> >  	}
> >  }
> > diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> > index 4b3d82f001d8..0e52c7a07c87 100644
> > --- a/drivers/gpu/drm/panthor/panthor_gem.c
> > +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> > @@ -8,9 +8,11 @@
> >  #include <linux/dma-mapping.h>
> >  #include <linux/err.h>
> >  #include <linux/slab.h>
> > +#include <linux/vmalloc.h>
> >  
> >  #include <drm/drm_debugfs.h>
> >  #include <drm/drm_file.h>
> > +#include <drm/drm_prime.h>
> >  #include <drm/drm_print.h>
> >  #include <drm/panthor_drm.h>
> >  
> > @@ -44,7 +46,7 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo)
> >  
> >  static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
> >  {
> > -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> > +	struct panthor_device *ptdev = container_of(bo->base.dev,
> >  						    struct panthor_device, base);
> >  
> >  	bo->debugfs.creator.tgid = current->group_leader->pid;
> > @@ -57,7 +59,7 @@ static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
> >  
> >  static void panthor_gem_debugfs_bo_rm(struct panthor_gem_object *bo)
> >  {
> > -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> > +	struct panthor_device *ptdev = container_of(bo->base.dev,
> >  						    struct panthor_device, base);
> >  
> >  	if (list_empty(&bo->debugfs.node))
> > @@ -80,9 +82,9 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo) {}
> >  #endif
> >  
> >  static bool
> > -should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> > +should_map_wc(struct panthor_gem_object *bo)
> >  {
> > -	struct panthor_device *ptdev = container_of(bo->base.base.dev, struct panthor_device, base);
> > +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> >  
> >  	/* We can't do uncached mappings if the device is coherent,
> >  	 * because the zeroing done by the shmem layer at page allocation
> > @@ -112,6 +114,208 @@ should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> >  	return true;
> >  }
> >  
> > +static void
> > +panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
> > +{
> > +	if (!bo->backing.pages)
> > +		return;
> > +
> > +	drm_gem_put_pages(&bo->base, bo->backing.pages, true, false);
> > +	bo->backing.pages = NULL;
> > +}
> > +
> > +static int
> > +panthor_gem_backing_get_pages_locked(struct panthor_gem_object *bo)
> > +{
> > +	dma_resv_assert_held(bo->base.resv);
> > +
> > +	if (bo->backing.pages)
> > +		return 0;
> > +
> > +	bo->backing.pages = drm_gem_get_pages(&bo->base);
> > +	if (IS_ERR(bo->backing.pages)) {
> > +		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
> > +			    bo->backing.pages);
> > +		return PTR_ERR(bo->backing.pages);  
> 
> This leaves bo->backing.pages set to the error value, which means a
> future call to panthor_gem_backing_get_pages_locked() for the same
> object will return success. Unless there's some 'poisoning' that I
> haven't spotted this looks like a bug.

That's a bug, I'll fix it.

> 
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
> > +{
> > +	int ret;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> > +
> > +	if (refcount_inc_not_zero(&bo->backing.pin_count))
> > +		return 0;
> > +
> > +	ret = panthor_gem_backing_get_pages_locked(bo);
> > +	if (!ret)
> > +		refcount_set(&bo->backing.pin_count, 1);
> > +
> > +	return ret;
> > +}
> > +
> > +static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
> > +{
> > +	dma_resv_assert_held(bo->base.resv);
> > +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> > +
> > +	/* We don't release anything when pin_count drops to zero.
> > +	 * Pages stay there until an explicit cleanup is requested.
> > +	 */
> > +	if (!refcount_dec_not_one(&bo->backing.pin_count))
> > +		refcount_set(&bo->backing.pin_count, 0);  
> 
> Why not just refcount_dec()?

Because refcount_dec() complains when it's passed a value that's less
than 2. The rational being that you need to do something special
(release resources) when you reach zero. In our case we don't, because
pages are lazily reclaimed, so we just set the counter back to zero.

> 
> > +}
> > +
> > +static void
> > +panthor_gem_dev_map_cleanup(struct panthor_gem_object *bo)
> > +{
> > +	if (!bo->dmap.sgt)
> > +		return;
> > +
> > +	dma_unmap_sgtable(drm_dev_dma_dev(bo->base.dev), bo->dmap.sgt, DMA_BIDIRECTIONAL, 0);
> > +	sg_free_table(bo->dmap.sgt);
> > +	kfree(bo->dmap.sgt);
> > +	bo->dmap.sgt = NULL;
> > +}
> > +
> > +static struct sg_table *
> > +panthor_gem_dev_map_get_sgt_locked(struct panthor_gem_object *bo)
> > +{
> > +	struct sg_table *sgt;
> > +	int ret;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +
> > +	if (bo->dmap.sgt)
> > +		return bo->dmap.sgt;
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> > +		return ERR_PTR(-EINVAL);
> > +
> > +	/* Pages stay around after they've been allocated. At least that stands
> > +	 * until we add a shrinker.
> > +	 */
> > +	ret = panthor_gem_backing_get_pages_locked(bo);
> > +	if (ret)
> > +		return ERR_PTR(ret);
> > +
> > +	sgt = drm_prime_pages_to_sg(bo->base.dev, bo->backing.pages,
> > +				    bo->base.size >> PAGE_SHIFT);
> > +	if (IS_ERR(sgt))
> > +		return sgt;
> > +
> > +	/* Map the pages for use by the h/w. */
> > +	ret = dma_map_sgtable(drm_dev_dma_dev(bo->base.dev), sgt, DMA_BIDIRECTIONAL, 0);
> > +	if (ret)
> > +		goto err_free_sgt;
> > +
> > +	bo->dmap.sgt = sgt;
> > +	return sgt;
> > +
> > +err_free_sgt:
> > +	sg_free_table(sgt);
> > +	kfree(sgt);
> > +	return ERR_PTR(ret);
> > +}
> > +
> > +struct sg_table *
> > +panthor_gem_get_dev_sgt(struct panthor_gem_object *bo)
> > +{
> > +	struct sg_table *sgt;
> > +
> > +	dma_resv_lock(bo->base.resv, NULL);
> > +	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> > +	dma_resv_unlock(bo->base.resv);
> > +
> > +	return sgt;
> > +}
> > +
> > +static void
> > +panthor_gem_vmap_cleanup(struct panthor_gem_object *bo)
> > +{
> > +	if (!bo->cmap.vaddr)
> > +		return;
> > +
> > +	vunmap(bo->cmap.vaddr);
> > +	bo->cmap.vaddr = NULL;
> > +	panthor_gem_backing_unpin_locked(bo);
> > +}
> > +
> > +static int
> > +panthor_gem_prep_for_cpu_map_locked(struct panthor_gem_object *bo)
> > +{
> > +	if (should_map_wc(bo)) {
> > +		struct sg_table *sgt;
> > +
> > +		sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> > +		if (IS_ERR(sgt))
> > +			return PTR_ERR(sgt);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void *
> > +panthor_gem_vmap_get_locked(struct panthor_gem_object *bo)
> > +{
> > +	pgprot_t prot = PAGE_KERNEL;
> > +	void *vaddr;
> > +	int ret;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> > +		return ERR_PTR(-EINVAL);
> > +
> > +	if (refcount_inc_not_zero(&bo->cmap.vaddr_use_count)) {
> > +		drm_WARN_ON_ONCE(bo->base.dev, !bo->cmap.vaddr);
> > +		return bo->cmap.vaddr;
> > +	}
> > +
> > +	ret = panthor_gem_backing_pin_locked(bo);
> > +	if (ret)
> > +		return ERR_PTR(ret);
> > +
> > +	ret = panthor_gem_prep_for_cpu_map_locked(bo);
> > +	if (ret)
> > +		return ERR_PTR(ret);
> > +
> > +	if (should_map_wc(bo))
> > +		prot = pgprot_writecombine(prot);
> > +
> > +	vaddr = vmap(bo->backing.pages, bo->base.size >> PAGE_SHIFT, VM_MAP, prot);
> > +	if (!vaddr) {
> > +		ret = -ENOMEM;
> > +		goto err_unpin;
> > +	}
> > +
> > +	bo->cmap.vaddr = vaddr;
> > +	refcount_set(&bo->cmap.vaddr_use_count, 1);
> > +	return vaddr;
> > +
> > +err_unpin:
> > +	panthor_gem_backing_unpin_locked(bo);
> > +	return ERR_PTR(ret);
> > +}
> > +
> > +static void
> > +panthor_gem_vmap_put_locked(struct panthor_gem_object *bo)
> > +{
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> > +		return;
> > +
> > +	if (refcount_dec_not_one(&bo->cmap.vaddr_use_count))
> > +		return;
> > +
> > +	refcount_set(&bo->cmap.vaddr_use_count, 0);
> > +	panthor_gem_vmap_cleanup(bo);
> > +}
> > +
> >  static void panthor_gem_free_object(struct drm_gem_object *obj)
> >  {
> >  	struct panthor_gem_object *bo = to_panthor_bo(obj);
> > @@ -127,8 +331,17 @@ static void panthor_gem_free_object(struct drm_gem_object *obj)
> >  
> >  	mutex_destroy(&bo->label.lock);
> >  
> > -	drm_gem_free_mmap_offset(&bo->base.base);
> > -	drm_gem_shmem_free(&bo->base);
> > +	if (drm_gem_is_imported(obj)) {
> > +		drm_prime_gem_destroy(obj, bo->dmap.sgt);
> > +	} else {
> > +		panthor_gem_vmap_cleanup(bo);  
> 
> panthor_gem_vmap_cleanup() calls panthor_gem_backing_unpin_locked()
> which expects the reservation lock to be held.

Good catch! I think we need to rename panthor_gem_vmap_cleanup() into
panthor_gem_vmap_cleanup_locked(), take the resv lock before calling
panthor_gem_vmap_cleanup_locked() and release it after calling
panthor_gem_backing_cleanup_locked().

> 
> > +		panthor_gem_dev_map_cleanup(bo);

We should probably suffix that one with _locked() too, with the extra
resv_held() annotations in the code.

> > +		panthor_gem_backing_cleanup(bo);
> > +	}
> > +
> > +	drm_gem_object_release(obj);
> > +
> > +	kfree(bo);
> >  	drm_gem_object_put(vm_root_gem);
> >  }
> >  
> > @@ -159,15 +372,15 @@ panthor_gem_prime_begin_cpu_access(struct dma_buf *dma_buf,
> >  {
> >  	struct drm_gem_object *obj = dma_buf->priv;
> >  	struct drm_device *dev = obj->dev;
> > -	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
> > +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> >  	struct dma_buf_attachment *attach;
> >  
> >  	dma_resv_lock(obj->resv, NULL);
> > -	if (shmem->sgt)
> > -		dma_sync_sgtable_for_cpu(dev->dev, shmem->sgt, dir);
> > +	if (bo->dmap.sgt)
> > +		dma_sync_sgtable_for_cpu(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
> >  
> > -	if (shmem->vaddr)
> > -		invalidate_kernel_vmap_range(shmem->vaddr, shmem->base.size);
> > +	if (bo->cmap.vaddr)
> > +		invalidate_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
> >  
> >  	list_for_each_entry(attach, &dma_buf->attachments, node) {
> >  		struct sg_table *sgt = attach->priv;
> > @@ -186,7 +399,7 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
> >  {
> >  	struct drm_gem_object *obj = dma_buf->priv;
> >  	struct drm_device *dev = obj->dev;
> > -	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
> > +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> >  	struct dma_buf_attachment *attach;
> >  
> >  	dma_resv_lock(obj->resv, NULL);
> > @@ -197,11 +410,11 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
> >  			dma_sync_sgtable_for_device(attach->dev, sgt, dir);
> >  	}
> >  
> > -	if (shmem->vaddr)
> > -		flush_kernel_vmap_range(shmem->vaddr, shmem->base.size);
> > +	if (bo->cmap.vaddr)
> > +		flush_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
> >  
> > -	if (shmem->sgt)
> > -		dma_sync_sgtable_for_device(dev->dev, shmem->sgt, dir);
> > +	if (bo->dmap.sgt)
> > +		dma_sync_sgtable_for_device(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
> >  
> >  	dma_resv_unlock(obj->resv);
> >  	return 0;
> > @@ -258,53 +471,339 @@ panthor_gem_prime_import(struct drm_device *dev,
> >  	return drm_gem_prime_import(dev, dma_buf);
> >  }
> >  
> > +static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
> > +				   const struct drm_gem_object *obj)
> > +{
> > +	const struct panthor_gem_object *bo = to_panthor_bo(obj);
> > +
> > +	if (drm_gem_is_imported(&bo->base))
> > +		return;
> > +
> > +	drm_printf_indent(p, indent, "resident=%s\n", str_true_false(bo->backing.pages));
> > +	drm_printf_indent(p, indent, "pages_pin_count=%u\n", refcount_read(&bo->backing.pin_count));
> > +	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
> > +			  refcount_read(&bo->cmap.vaddr_use_count));
> > +	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> > +}
> > +
> > +static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> > +{
> > +	if (drm_gem_is_imported(obj))
> > +		return 0;
> > +
> > +	return panthor_gem_backing_pin_locked(to_panthor_bo(obj));
> > +}
> > +
> > +static void panthor_gem_unpin_locked(struct drm_gem_object *obj)
> > +{
> > +	if (!drm_gem_is_imported(obj))
> > +		panthor_gem_backing_unpin_locked(to_panthor_bo(obj));
> > +}
> > +
> > +int panthor_gem_pin(struct panthor_gem_object *bo)
> > +{
> > +	int ret = 0;
> > +
> > +	if (drm_gem_is_imported(&bo->base))
> > +		return 0;
> > +
> > +	if (refcount_inc_not_zero(&bo->backing.pin_count))
> > +		return 0;
> > +
> > +	dma_resv_lock(bo->base.resv, NULL);
> > +	ret = panthor_gem_pin_locked(&bo->base);  
> 
> We might as well call panthor_gem_backing_pin_locked() since we know
> it's not imported.

Fair enough.

> 
> > +	dma_resv_unlock(bo->base.resv);
> > +
> > +	return ret;
> > +}
> > +
> > +void panthor_gem_unpin(struct panthor_gem_object *bo)
> > +{
> > +	if (drm_gem_is_imported(&bo->base))
> > +		return;
> > +
> > +	if (refcount_dec_not_one(&bo->backing.pin_count))
> > +		return;
> > +
> > +	dma_resv_lock(bo->base.resv, NULL);
> > +	panthor_gem_unpin_locked(&bo->base);  
> 
> Same here.

Will do.

> 
> > +	dma_resv_unlock(bo->base.resv);
> > +}
> > +
> > +static struct sg_table *panthor_gem_get_sg_table(struct drm_gem_object *obj)
> > +{
> > +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> > +
> > +	drm_WARN_ON_ONCE(obj->dev, drm_gem_is_imported(obj));
> > +	drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages);
> > +	drm_WARN_ON_ONCE(obj->dev, !refcount_read(&bo->backing.pin_count));
> > +
> > +	return drm_prime_pages_to_sg(obj->dev, bo->backing.pages, obj->size >> PAGE_SHIFT);
> > +}
> > +
> > +static int panthor_gem_vmap_locked(struct drm_gem_object *obj,
> > +				   struct iosys_map *map)
> > +{
> > +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> > +	void *vaddr;
> > +
> > +	dma_resv_assert_held(obj->resv);
> > +
> > +	if (drm_gem_is_imported(obj))
> > +		return dma_buf_vmap(obj->import_attach->dmabuf, map);
> > +
> > +	vaddr = panthor_gem_vmap_get_locked(bo);
> > +	if (IS_ERR(vaddr))
> > +		return PTR_ERR(vaddr);
> > +
> > +	iosys_map_set_vaddr(map, vaddr);
> > +	return 0;
> > +}
> > +
> > +static void panthor_gem_vunmap_locked(struct drm_gem_object *obj,
> > +				      struct iosys_map *map)
> > +{
> > +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> > +
> > +	dma_resv_assert_held(obj->resv);
> > +
> > +	if (drm_gem_is_imported(obj)) {
> > +		dma_buf_vunmap(obj->import_attach->dmabuf, map);
> > +	} else {
> > +		drm_WARN_ON_ONCE(obj->dev, bo->cmap.vaddr != map->vaddr);
> > +		panthor_gem_vmap_put_locked(bo);
> > +	}
> > +}
> > +
> > +static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
> > +{
> > +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> > +	int ret;
> > +
> > +	if (drm_gem_is_imported(obj)) {
> > +		/* Reset both vm_ops and vm_private_data, so we don't end up with
> > +		 * vm_ops pointing to our implementation if the dma-buf backend
> > +		 * doesn't set those fields.
> > +		 */
> > +		vma->vm_private_data = NULL;
> > +		vma->vm_ops = NULL;
> > +
> > +		ret = dma_buf_mmap(obj->dma_buf, vma, 0);
> > +
> > +		/* Drop the reference drm_gem_mmap_obj() acquired.*/
> > +		if (!ret)
> > +			drm_gem_object_put(obj);
> > +
> > +		return ret;
> > +	}
> > +
> > +	if (is_cow_mapping(vma->vm_flags))
> > +		return -EINVAL;
> > +
> > +	dma_resv_lock(obj->resv, NULL);
> > +	ret = panthor_gem_backing_get_pages_locked(bo);
> > +	if (!ret)
> > +		ret = panthor_gem_prep_for_cpu_map_locked(bo);
> > +	dma_resv_unlock(obj->resv);
> > +
> > +	if (ret)
> > +		return ret;
> > +
> > +	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> > +	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> > +	if (should_map_wc(bo))
> > +		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
> > +
> > +	return 0;
> > +}
> > +
> >  static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
> >  {
> >  	struct panthor_gem_object *bo = to_panthor_bo(obj);
> >  	enum drm_gem_object_status res = 0;
> >  
> > -	if (drm_gem_is_imported(&bo->base.base) || bo->base.pages)
> > +	if (drm_gem_is_imported(&bo->base) || bo->backing.pages)
> >  		res |= DRM_GEM_OBJECT_RESIDENT;
> >  
> >  	return res;
> >  }
> >  
> > -static const struct drm_gem_object_funcs panthor_gem_funcs = {
> > -	.free = panthor_gem_free_object,
> > -	.print_info = drm_gem_shmem_object_print_info,
> > -	.pin = drm_gem_shmem_object_pin,
> > -	.unpin = drm_gem_shmem_object_unpin,
> > -	.get_sg_table = drm_gem_shmem_object_get_sg_table,
> > -	.vmap = drm_gem_shmem_object_vmap,
> > -	.vunmap = drm_gem_shmem_object_vunmap,
> > -	.mmap = drm_gem_shmem_object_mmap,
> > -	.status = panthor_gem_status,
> > -	.export = panthor_gem_prime_export,
> > -	.vm_ops = &drm_gem_shmem_vm_ops,
> > +static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
> > +{
> > +#ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
> > +	unsigned long pfn = page_to_pfn(page);
> > +	unsigned long paddr = pfn << PAGE_SHIFT;
> > +	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
> > +
> > +	if (aligned &&
> > +	    pmd_none(*vmf->pmd) &&
> > +	    folio_test_pmd_mappable(page_folio(page))) {
> > +		pfn &= PMD_MASK >> PAGE_SHIFT;
> > +		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
> > +			return true;
> > +	}
> > +#endif
> > +
> > +	return false;
> > +}
> > +
> > +static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> > +{
> > +	struct vm_area_struct *vma = vmf->vma;
> > +	struct drm_gem_object *obj = vma->vm_private_data;
> > +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > +	loff_t num_pages = obj->size >> PAGE_SHIFT;
> > +	vm_fault_t ret;
> > +	pgoff_t page_offset;
> > +	unsigned long pfn;
> > +
> > +	/* Offset to faulty address in the VMA. */
> > +	page_offset = vmf->pgoff - vma->vm_pgoff;
> > +
> > +	dma_resv_lock(bo->base.resv, NULL);
> > +
> > +	if (page_offset >= num_pages ||
> > +	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
> > +		ret = VM_FAULT_SIGBUS;
> > +		goto out;
> > +	}
> > +
> > +	if (try_map_pmd(vmf, vmf->address, bo->backing.pages[page_offset])) {
> > +		ret = VM_FAULT_NOPAGE;
> > +		goto out;
> > +	}
> > +
> > +	pfn = page_to_pfn(bo->backing.pages[page_offset]);
> > +	ret = vmf_insert_pfn(vma, vmf->address, pfn);
> > +
> > + out:
> > +	dma_resv_unlock(bo->base.resv);
> > +
> > +	return ret;
> > +}
> > +
> > +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> > +{
> > +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > +
> > +	drm_WARN_ON(bo->base.dev, drm_gem_is_imported(&bo->base));
> > +
> > +	dma_resv_lock(bo->base.resv, NULL);
> > +
> > +	/* We should have already pinned the pages when the buffer was first
> > +	 * mmap'd, vm_open() just grabs an additional reference for the new
> > +	 * mm the vma is getting copied into (ie. on fork()).
> > +	 */
> > +	drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages);
> > +
> > +	dma_resv_unlock(bo->base.resv);
> > +
> > +	drm_gem_vm_open(vma);
> > +}
> > +
> > +const struct vm_operations_struct panthor_gem_vm_ops = {
> > +	.fault = panthor_gem_fault,
> > +	.open = panthor_gem_vm_open,
> > +	.close = drm_gem_vm_close,
> >  };
> >  
> > -/**
> > - * panthor_gem_create_object - Implementation of driver->gem_create_object.
> > - * @ddev: DRM device
> > - * @size: Size in bytes of the memory the object will reference
> > - *
> > - * This lets the GEM helpers allocate object structs for us, and keep
> > - * our BO stats correct.
> > - */
> > -struct drm_gem_object *panthor_gem_create_object(struct drm_device *ddev, size_t size)
> > -{
> > -	struct panthor_gem_object *obj;
> > +static const struct drm_gem_object_funcs panthor_gem_funcs = {
> > +	.free = panthor_gem_free_object,
> > +	.print_info = panthor_gem_print_info,
> > +	.pin = panthor_gem_pin_locked,
> > +	.unpin = panthor_gem_unpin_locked,
> > +	.get_sg_table = panthor_gem_get_sg_table,
> > +	.vmap = panthor_gem_vmap_locked,
> > +	.vunmap = panthor_gem_vunmap_locked,
> > +	.mmap = panthor_gem_mmap,
> > +	.status = panthor_gem_status,
> > +	.export = panthor_gem_prime_export,
> > +	.vm_ops = &panthor_gem_vm_ops,
> > +};
> >  
> > -	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> > -	if (!obj)
> > +static struct panthor_gem_object *
> > +panthor_gem_alloc_object(uint32_t flags)
> > +{
> > +	struct panthor_gem_object *bo;
> > +
> > +	bo = kzalloc(sizeof(*bo), GFP_KERNEL);
> > +	if (!bo)
> >  		return ERR_PTR(-ENOMEM);
> >  
> > -	obj->base.base.funcs = &panthor_gem_funcs;
> > -	mutex_init(&obj->label.lock);
> > +	bo->base.funcs = &panthor_gem_funcs;
> > +	bo->flags = flags;
> > +	mutex_init(&bo->label.lock);
> > +	panthor_gem_debugfs_bo_init(bo);
> > +	return bo;
> > +}
> >  
> > -	panthor_gem_debugfs_bo_init(obj);
> > +static struct panthor_gem_object *
> > +panthor_gem_create(struct drm_device *dev, size_t size, uint32_t flags,
> > +		   struct panthor_vm *exclusive_vm, u32 usage_flags)
> > +{
> > +	struct panthor_gem_object *bo;
> > +	int ret;
> >  
> > -	return &obj->base.base;
> > +	bo = panthor_gem_alloc_object(flags);
> > +	if (IS_ERR(bo))
> > +		return bo;
> > +
> > +	size = PAGE_ALIGN(size);
> > +	ret = drm_gem_object_init(dev, &bo->base, size);
> > +	if (ret)
> > +		goto err_put;
> > +
> > +	/* Our buffers are kept pinned, so allocating them
> > +	 * from the MOVABLE zone is a really bad idea, and
> > +	 * conflicts with CMA. See comments above new_inode()
> > +	 * why this is required _and_ expected if you're
> > +	 * going to pin these pages.
> > +	 */
> > +	mapping_set_gfp_mask(bo->base.filp->f_mapping,
> > +			     GFP_HIGHUSER | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
> > +
> > +	ret = drm_gem_create_mmap_offset(&bo->base);
> > +	if (ret)
> > +		goto err_put;
> > +
> > +	if (exclusive_vm) {
> > +		bo->exclusive_vm_root_gem = panthor_vm_root_gem(exclusive_vm);
> > +		drm_gem_object_get(bo->exclusive_vm_root_gem);
> > +		bo->base.resv = bo->exclusive_vm_root_gem->resv;
> > +	}
> > +
> > +	panthor_gem_debugfs_set_usage_flags(bo, usage_flags);
> > +	return bo;
> > +
> > +err_put:
> > +	drm_gem_object_put(&bo->base);  
> 
> Is this call to _put() correct? I see the __drm_gem_shmem_init()
> function uses drm_gem_object_release() instead.

I think it is okay, as long as:

- the GEM object is zeroed at alloc time (all pointers set to NULL,
  vma_node unallocated, ...)
- the GEM funcs are set early (done in panthor_gem_alloc_object())
- the drm_gem_object is initialized (done as part
  of drm_gem_object_init(), early enough to guarantee that nothing
  fails before this is done)
- the gem->funcs.free() function treats any NULL pointer as a "partially
  initialized object" case instead of "invalid object", and that we do
  in panthor_gem_free_object(), I think, just like
  drm_gem_object_release() does.

I'd really prefer to keep this _put() instead of adding new
err_<undo_x> labels for each of the steps that might have taken place
between drm_gem_object_init() and the failing call, unless there's a
proof this is unsafe (might have missed something you spotted).

> 
> > +	return ERR_PTR(ret);
> > +}
> > +
> > +struct drm_gem_object *
> > +panthor_gem_prime_import_sg_table(struct drm_device *dev,
> > +				  struct dma_buf_attachment *attach,
> > +				  struct sg_table *sgt)
> > +{
> > +	struct panthor_gem_object *bo;
> > +	int ret;
> > +
> > +	bo = panthor_gem_alloc_object(0);
> > +	if (IS_ERR(bo))
> > +		return &bo->base;  
> 
> bo->base is invalid here. I think you want ERR_CAST(bo).

Absolutely. Will fix that.

> 
> > +
> > +	drm_gem_private_object_init(dev, &bo->base, attach->dmabuf->size);
> > +
> > +	ret = drm_gem_create_mmap_offset(&bo->base);
> > +	if (ret)
> > +		goto err_put;
> > +
> > +	bo->dmap.sgt = sgt;
> > +	return &bo->base;
> > +
> > +err_put:
> > +	drm_gem_object_put(&bo->base);  
> 
> Again I'm not convinced _put does the right thing here.

Hm, we probably have to re-order the bo->dmap.sgt assignment so it
happens just after drm_gem_private_object_init() and there's no NULL
deref in the panthor_gem_free_object() path, but otherwise I think it's
safe to call panthor_gem_free_object() after the non-fallible
initialization took place. Am I missing something?

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap()
  2026-01-12 12:15   ` Steven Price
@ 2026-01-12 14:32     ` Boris Brezillon
  2026-01-12 16:41       ` Steven Price
  0 siblings, 1 reply; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 14:32 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Mon, 12 Jan 2026 12:15:13 +0000
Steven Price <steven.price@arm.com> wrote:

> On 09/01/2026 13:07, Boris Brezillon wrote:
> > Defer pages allocation until their first access.
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > ---
> >  drivers/gpu/drm/panthor/panthor_gem.c | 119 ++++++++++++++++----------
> >  1 file changed, 75 insertions(+), 44 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> > index 0e52c7a07c87..44f05bd957e7 100644
> > --- a/drivers/gpu/drm/panthor/panthor_gem.c
> > +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> > @@ -600,15 +600,6 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
> >  	if (is_cow_mapping(vma->vm_flags))
> >  		return -EINVAL;
> >  
> > -	dma_resv_lock(obj->resv, NULL);
> > -	ret = panthor_gem_backing_get_pages_locked(bo);
> > -	if (!ret)
> > -		ret = panthor_gem_prep_for_cpu_map_locked(bo);
> > -	dma_resv_unlock(obj->resv);
> > -
> > -	if (ret)
> > -		return ret;
> > -
> >  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> >  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> >  	if (should_map_wc(bo))
> > @@ -628,82 +619,122 @@ static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
> >  	return res;
> >  }
> >  
> > -static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
> > +static vm_fault_t insert_page(struct vm_fault *vmf, struct page *page)
> >  {
> > +	struct vm_area_struct *vma = vmf->vma;
> > +	vm_fault_t ret;
> > +
> >  #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
> >  	unsigned long pfn = page_to_pfn(page);
> >  	unsigned long paddr = pfn << PAGE_SHIFT;
> > -	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
> > +	bool aligned = (vmf->address & ~PMD_MASK) == (paddr & ~PMD_MASK);
> >  
> >  	if (aligned &&
> >  	    pmd_none(*vmf->pmd) &&
> >  	    folio_test_pmd_mappable(page_folio(page))) {
> >  		pfn &= PMD_MASK >> PAGE_SHIFT;
> > -		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
> > -			return true;
> > +		ret = vmf_insert_pfn_pmd(vmf, pfn, false);
> > +		if (ret == VM_FAULT_NOPAGE)
> > +			return VM_FAULT_NOPAGE;
> >  	}
> >  #endif
> >  
> > -	return false;
> > +	return vmf_insert_pfn(vma, vmf->address, page_to_pfn(page));
> >  }
> >  
> > -static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> > +static vm_fault_t nonblocking_page_setup(struct vm_fault *vmf, pgoff_t page_offset)
> >  {
> >  	struct vm_area_struct *vma = vmf->vma;
> > -	struct drm_gem_object *obj = vma->vm_private_data;
> >  	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > -	loff_t num_pages = obj->size >> PAGE_SHIFT;
> >  	vm_fault_t ret;
> > -	pgoff_t page_offset;
> > -	unsigned long pfn;
> >  
> > -	/* Offset to faulty address in the VMA. */
> > -	page_offset = vmf->pgoff - vma->vm_pgoff;
> > +	if (!dma_resv_trylock(bo->base.resv))
> > +		return VM_FAULT_RETRY;
> >  
> > -	dma_resv_lock(bo->base.resv, NULL);
> > +	if (bo->backing.pages)
> > +		ret = insert_page(vmf, bo->backing.pages[page_offset]);
> > +	else
> > +		ret = VM_FAULT_RETRY;
> >  
> > -	if (page_offset >= num_pages ||
> > -	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
> > -		ret = VM_FAULT_SIGBUS;
> > -		goto out;
> > +	dma_resv_unlock(bo->base.resv);
> > +	return ret;
> > +}
> > +
> > +static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
> > +				      struct panthor_gem_object *bo,
> > +				      pgoff_t page_offset, bool mmap_lock_held)
> > +{
> > +	vm_fault_t ret;
> > +	int err;
> > +
> > +	if (vmf->flags & FAULT_FLAG_INTERRUPTIBLE) {
> > +		err = dma_resv_lock_interruptible(bo->base.resv, NULL);
> > +		if (err)
> > +			return mmap_lock_held ? VM_FAULT_NOPAGE : VM_FAULT_RETRY;  
> 
> I'm not sure about this. First FAULT_FLAG_INTERRUPTIBLE is currently
> only used by userfaultfd AFAICT.

And GUP, which admittedly, only seems possible if one tries to map a
userpage in kernel space, and we don't support that (yet?).

> Second returning VM_FAULT_NOPAGE seems
> wrong - that's for the case were we've inserted a pte but in this case
> we haven't.

Got this from [1], and remember going through the fault handler API
with Akash, and finding something describing this case.

> 
> Otherwise I couldn't spot any issues staring at the code, but I might
> have missed something. mm code is always hard to follow!

It is, indeed, which is why I'm glad to have a new pair of eyes looking
at this ;-).

Thanks,

Boris

[1]https://elixir.bootlin.com/linux/v6.18.4/source/drivers/gpu/drm/ttm/ttm_bo_vm.c#L116


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 12:33   ` Steven Price
@ 2026-01-12 14:39     ` Boris Brezillon
  2026-01-12 15:19       ` Alice Ryhl
  2026-01-12 16:49       ` Steven Price
  0 siblings, 2 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 14:39 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Mon, 12 Jan 2026 12:33:33 +0000
Steven Price <steven.price@arm.com> wrote:

> On 09/01/2026 13:08, Boris Brezillon wrote:
> > This will be used to order things by reclaimability.
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > ---
> >  drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
> >  drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
> >  2 files changed, 45 insertions(+), 2 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> > index 44f05bd957e7..458d22380e96 100644
> > --- a/drivers/gpu/drm/panthor/panthor_gem.c
> > +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> > @@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
> >  	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
> >  			  refcount_read(&bo->cmap.vaddr_use_count));
> >  	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> > +	drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
> >  }
> >  
> >  static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> > @@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
> >  	if (is_cow_mapping(vma->vm_flags))
> >  		return -EINVAL;
> >  
> > +	if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
> > +		dma_resv_lock(obj->resv, NULL);
> > +		if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
> > +			refcount_set(&bo->cmap.mmap_count, 1);
> > +		dma_resv_unlock(obj->resv);
> > +	}
> > +
> >  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> >  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> >  	if (should_map_wc(bo))
> > @@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> >  	return blocking_page_setup(vmf, bo, page_offset, true);
> >  }
> >  
> > +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> > +{
> > +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > +
> > +	/* mmap_count must have been incremented at mmap time, so it can't be
> > +	 * zero here.
> > +	 */
> > +	if (!drm_gem_is_imported(&bo->base))
> > +		drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
> > +
> > +	drm_gem_vm_open(vma);
> > +}
> > +
> > +static void panthor_gem_vm_close(struct vm_area_struct *vma)
> > +{
> > +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > +
> > +	if (drm_gem_is_imported(&bo->base))
> > +		goto out;
> > +
> > +	if (refcount_dec_not_one(&bo->cmap.mmap_count))
> > +		goto out;
> > +
> > +	dma_resv_lock(bo->base.resv, NULL);
> > +	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> > +		refcount_set(&bo->cmap.mmap_count, 0);
> > +	dma_resv_unlock(bo->base.resv);  
> 
> I don't think this logic is safe. Holding the resv_lock doesn't protect
> against another thread doing a refcount_inc_not_zero() without holding
> the lock.
> 
> I think you can just replace the if() part with a refcount_dec() call,
> the lock AFAICT is needed because the following patch wants to be sure
> that !!mmap_count is stable when resv_lock is held.

I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
complaining :P.

> 
> I also feel you should invert the conditino for refcount_dec_not_one,
> leading to the following which I feel is easier to read:
> 
> static void panthor_gem_vm_close(struct vm_area_struct *vma)
> {
> 	[...]
> 
> 	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> 		dma_resv_lock(bo->base.resv, NULL);
> 		refcount_dec(&bo->cmap.mmap_count);
> 		dma_resv_unlock(bo->base.resv);
> 	}

The best I can do is:

 	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
 		dma_resv_lock(bo->base.resv, NULL);
 		if (!refcount_dec_not_one(&bo->cmap.mmap_count))
			refcount_set(&bo->cmap.mmap_count, 0);
 		dma_resv_unlock(bo->base.resv);
 	}

so we only take the lock when absolutely needed, but the 1 -> 0
transition still has to be done with "if (dec_not_one) set(0)".

> 
> 	drm_gem_object_put(&bo->base);
> }

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 14:39     ` Boris Brezillon
@ 2026-01-12 15:19       ` Alice Ryhl
  2026-01-12 15:49         ` Boris Brezillon
  2026-01-12 16:49       ` Steven Price
  1 sibling, 1 reply; 62+ messages in thread
From: Alice Ryhl @ 2026-01-12 15:19 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Akash Goel, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström, kernel

On Mon, Jan 12, 2026 at 3:40 PM Boris Brezillon
<boris.brezillon@collabora.com> wrote:
>
> On Mon, 12 Jan 2026 12:33:33 +0000
> Steven Price <steven.price@arm.com> wrote:
>
> > On 09/01/2026 13:08, Boris Brezillon wrote:
> > > This will be used to order things by reclaimability.
> > >
> > > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > > ---
> > >  drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
> > >  drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
> > >  2 files changed, 45 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> > > index 44f05bd957e7..458d22380e96 100644
> > > --- a/drivers/gpu/drm/panthor/panthor_gem.c
> > > +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> > > @@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
> > >     drm_printf_indent(p, indent, "vmap_use_count=%u\n",
> > >                       refcount_read(&bo->cmap.vaddr_use_count));
> > >     drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> > > +   drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
> > >  }
> > >
> > >  static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> > > @@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
> > >     if (is_cow_mapping(vma->vm_flags))
> > >             return -EINVAL;
> > >
> > > +   if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
> > > +           dma_resv_lock(obj->resv, NULL);
> > > +           if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
> > > +                   refcount_set(&bo->cmap.mmap_count, 1);
> > > +           dma_resv_unlock(obj->resv);
> > > +   }
> > > +
> > >     vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> > >     vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> > >     if (should_map_wc(bo))
> > > @@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> > >     return blocking_page_setup(vmf, bo, page_offset, true);
> > >  }
> > >
> > > +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> > > +{
> > > +   struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > > +
> > > +   /* mmap_count must have been incremented at mmap time, so it can't be
> > > +    * zero here.
> > > +    */
> > > +   if (!drm_gem_is_imported(&bo->base))
> > > +           drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
> > > +
> > > +   drm_gem_vm_open(vma);
> > > +}
> > > +
> > > +static void panthor_gem_vm_close(struct vm_area_struct *vma)
> > > +{
> > > +   struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > > +
> > > +   if (drm_gem_is_imported(&bo->base))
> > > +           goto out;
> > > +
> > > +   if (refcount_dec_not_one(&bo->cmap.mmap_count))
> > > +           goto out;
> > > +
> > > +   dma_resv_lock(bo->base.resv, NULL);
> > > +   if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> > > +           refcount_set(&bo->cmap.mmap_count, 0);
> > > +   dma_resv_unlock(bo->base.resv);
> >
> > I don't think this logic is safe. Holding the resv_lock doesn't protect
> > against another thread doing a refcount_inc_not_zero() without holding
> > the lock.
> >
> > I think you can just replace the if() part with a refcount_dec() call,
> > the lock AFAICT is needed because the following patch wants to be sure
> > that !!mmap_count is stable when resv_lock is held.
>
> I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
> complaining :P.

I'm pretty sure that refcount_dec() is fine with 1->0. Did you mean
refcount_inc() and 0->1?

> > I also feel you should invert the conditino for refcount_dec_not_one,
> > leading to the following which I feel is easier to read:
> >
> > static void panthor_gem_vm_close(struct vm_area_struct *vma)
> > {
> >       [...]
> >
> >       if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> >               dma_resv_lock(bo->base.resv, NULL);
> >               refcount_dec(&bo->cmap.mmap_count);
> >               dma_resv_unlock(bo->base.resv);
> >       }
>
> The best I can do is:
>
>         if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
>                 dma_resv_lock(bo->base.resv, NULL);
>                 if (!refcount_dec_not_one(&bo->cmap.mmap_count))
>                         refcount_set(&bo->cmap.mmap_count, 0);
>                 dma_resv_unlock(bo->base.resv);
>         }
>
> so we only take the lock when absolutely needed, but the 1 -> 0
> transition still has to be done with "if (dec_not_one) set(0)".

Why not just use atomic_t and use the atomic inc/dec operations? They
don't have saturation, but also do not require treating zero
specially.

Alice

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 15:19       ` Alice Ryhl
@ 2026-01-12 15:49         ` Boris Brezillon
  2026-01-12 15:51           ` Alice Ryhl
  0 siblings, 1 reply; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 15:49 UTC (permalink / raw)
  To: Alice Ryhl
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Akash Goel, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström, kernel

On Mon, 12 Jan 2026 16:19:52 +0100
Alice Ryhl <aliceryhl@google.com> wrote:

> On Mon, Jan 12, 2026 at 3:40 PM Boris Brezillon
> <boris.brezillon@collabora.com> wrote:
> >
> > On Mon, 12 Jan 2026 12:33:33 +0000
> > Steven Price <steven.price@arm.com> wrote:
> >  
> > > On 09/01/2026 13:08, Boris Brezillon wrote:  
> > > > This will be used to order things by reclaimability.
> > > >
> > > > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > > > ---
> > > >  drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
> > > >  drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
> > > >  2 files changed, 45 insertions(+), 2 deletions(-)
> > > >
> > > > diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> > > > index 44f05bd957e7..458d22380e96 100644
> > > > --- a/drivers/gpu/drm/panthor/panthor_gem.c
> > > > +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> > > > @@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
> > > >     drm_printf_indent(p, indent, "vmap_use_count=%u\n",
> > > >                       refcount_read(&bo->cmap.vaddr_use_count));
> > > >     drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> > > > +   drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
> > > >  }
> > > >
> > > >  static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> > > > @@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
> > > >     if (is_cow_mapping(vma->vm_flags))
> > > >             return -EINVAL;
> > > >
> > > > +   if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
> > > > +           dma_resv_lock(obj->resv, NULL);
> > > > +           if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
> > > > +                   refcount_set(&bo->cmap.mmap_count, 1);
> > > > +           dma_resv_unlock(obj->resv);
> > > > +   }
> > > > +
> > > >     vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> > > >     vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> > > >     if (should_map_wc(bo))
> > > > @@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> > > >     return blocking_page_setup(vmf, bo, page_offset, true);
> > > >  }
> > > >
> > > > +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> > > > +{
> > > > +   struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > > > +
> > > > +   /* mmap_count must have been incremented at mmap time, so it can't be
> > > > +    * zero here.
> > > > +    */
> > > > +   if (!drm_gem_is_imported(&bo->base))
> > > > +           drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
> > > > +
> > > > +   drm_gem_vm_open(vma);
> > > > +}
> > > > +
> > > > +static void panthor_gem_vm_close(struct vm_area_struct *vma)
> > > > +{
> > > > +   struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > > > +
> > > > +   if (drm_gem_is_imported(&bo->base))
> > > > +           goto out;
> > > > +
> > > > +   if (refcount_dec_not_one(&bo->cmap.mmap_count))
> > > > +           goto out;
> > > > +
> > > > +   dma_resv_lock(bo->base.resv, NULL);
> > > > +   if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> > > > +           refcount_set(&bo->cmap.mmap_count, 0);
> > > > +   dma_resv_unlock(bo->base.resv);  
> > >
> > > I don't think this logic is safe. Holding the resv_lock doesn't protect
> > > against another thread doing a refcount_inc_not_zero() without holding
> > > the lock.
> > >
> > > I think you can just replace the if() part with a refcount_dec() call,
> > > the lock AFAICT is needed because the following patch wants to be sure
> > > that !!mmap_count is stable when resv_lock is held.  
> >
> > I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
> > complaining :P.  
> 
> I'm pretty sure that refcount_dec() is fine with 1->0.

That's not what [1] says. refcount_dec_and_test() is okay though, but
it'd force us to do a

	(void)refcount_dec_and_test()

and detail why it's okay to ignore the returned value. Not too sure
which one is better.

> Did you mean
> refcount_inc() and 0->1?
> 
> > > I also feel you should invert the conditino for refcount_dec_not_one,
> > > leading to the following which I feel is easier to read:
> > >
> > > static void panthor_gem_vm_close(struct vm_area_struct *vma)
> > > {
> > >       [...]
> > >
> > >       if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> > >               dma_resv_lock(bo->base.resv, NULL);
> > >               refcount_dec(&bo->cmap.mmap_count);
> > >               dma_resv_unlock(bo->base.resv);
> > >       }  
> >
> > The best I can do is:
> >
> >         if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> >                 dma_resv_lock(bo->base.resv, NULL);
> >                 if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> >                         refcount_set(&bo->cmap.mmap_count, 0);
> >                 dma_resv_unlock(bo->base.resv);
> >         }
> >
> > so we only take the lock when absolutely needed, but the 1 -> 0
> > transition still has to be done with "if (dec_not_one) set(0)".  
> 
> Why not just use atomic_t and use the atomic inc/dec operations? They
> don't have saturation, but also do not require treating zero
> specially.

I had suggested using atomics back when I was reviewing the
shmem-shrinker stuff to avoid this exact same issue. I can't find the
thread anymore and I can't remember the rationale either (probably that
saturation detection was useful, still), but the decision was to use a
refcount_t. I don't mind using atomics here, but I'd rather not be
blocked on that when/if I try to move that code into a common lib.

[1]https://elixir.bootlin.com/linux/v6.19-rc4/source/include/linux/refcount.h#L460

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 15:49         ` Boris Brezillon
@ 2026-01-12 15:51           ` Alice Ryhl
  2026-01-12 16:06             ` Boris Brezillon
  0 siblings, 1 reply; 62+ messages in thread
From: Alice Ryhl @ 2026-01-12 15:51 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Akash Goel, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström, kernel

On Mon, Jan 12, 2026 at 4:49 PM Boris Brezillon
<boris.brezillon@collabora.com> wrote:
>
> On Mon, 12 Jan 2026 16:19:52 +0100
> Alice Ryhl <aliceryhl@google.com> wrote:
>
> > On Mon, Jan 12, 2026 at 3:40 PM Boris Brezillon
> > <boris.brezillon@collabora.com> wrote:
> > >
> > > On Mon, 12 Jan 2026 12:33:33 +0000
> > > Steven Price <steven.price@arm.com> wrote:
> > >
> > > > On 09/01/2026 13:08, Boris Brezillon wrote:
> > > > > +static void panthor_gem_vm_close(struct vm_area_struct *vma)
> > > > > +{
> > > > > +   struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > > > > +
> > > > > +   if (drm_gem_is_imported(&bo->base))
> > > > > +           goto out;
> > > > > +
> > > > > +   if (refcount_dec_not_one(&bo->cmap.mmap_count))
> > > > > +           goto out;
> > > > > +
> > > > > +   dma_resv_lock(bo->base.resv, NULL);
> > > > > +   if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> > > > > +           refcount_set(&bo->cmap.mmap_count, 0);
> > > > > +   dma_resv_unlock(bo->base.resv);
> > > >
> > > > I don't think this logic is safe. Holding the resv_lock doesn't protect
> > > > against another thread doing a refcount_inc_not_zero() without holding
> > > > the lock.
> > > >
> > > > I think you can just replace the if() part with a refcount_dec() call,
> > > > the lock AFAICT is needed because the following patch wants to be sure
> > > > that !!mmap_count is stable when resv_lock is held.
> > >
> > > I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
> > > complaining :P.
> >
> > I'm pretty sure that refcount_dec() is fine with 1->0.
>
> That's not what [1] says. refcount_dec_and_test() is okay though, but
> it'd force us to do a
>
>         (void)refcount_dec_and_test()
>
> and detail why it's okay to ignore the returned value. Not too sure
> which one is better.

You're right, I mixed it up with refcount_dec_and_test().

> > > > I also feel you should invert the conditino for refcount_dec_not_one,
> > > > leading to the following which I feel is easier to read:
> > > >
> > > > static void panthor_gem_vm_close(struct vm_area_struct *vma)
> > > > {
> > > >       [...]
> > > >
> > > >       if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> > > >               dma_resv_lock(bo->base.resv, NULL);
> > > >               refcount_dec(&bo->cmap.mmap_count);
> > > >               dma_resv_unlock(bo->base.resv);
> > > >       }
> > >
> > > The best I can do is:
> > >
> > >         if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> > >                 dma_resv_lock(bo->base.resv, NULL);
> > >                 if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> > >                         refcount_set(&bo->cmap.mmap_count, 0);
> > >                 dma_resv_unlock(bo->base.resv);
> > >         }
> > >
> > > so we only take the lock when absolutely needed, but the 1 -> 0
> > > transition still has to be done with "if (dec_not_one) set(0)".
> >
> > Why not just use atomic_t and use the atomic inc/dec operations? They
> > don't have saturation, but also do not require treating zero
> > specially.
>
> I had suggested using atomics back when I was reviewing the
> shmem-shrinker stuff to avoid this exact same issue. I can't find the
> thread anymore and I can't remember the rationale either (probably that
> saturation detection was useful, still), but the decision was to use a
> refcount_t. I don't mind using atomics here, but I'd rather not be
> blocked on that when/if I try to move that code into a common lib.
>
> [1]https://elixir.bootlin.com/linux/v6.19-rc4/source/include/linux/refcount.h#L460

It's just a suggestion - no need to block on it.

It sounds like refcount_t should have an refcount_inc_maybe_first()
where 0->1 is ok.

Alice

Alice

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-12 14:17     ` Boris Brezillon
@ 2026-01-12 16:03       ` Steven Price
  2026-01-12 16:45         ` Boris Brezillon
  2026-01-21 11:11       ` Akash Goel
  1 sibling, 1 reply; 62+ messages in thread
From: Steven Price @ 2026-01-12 16:03 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 12/01/2026 14:17, Boris Brezillon wrote:
> On Mon, 12 Jan 2026 12:06:17 +0000
> Steven Price <steven.price@arm.com> wrote:
> 
>> On 09/01/2026 13:07, Boris Brezillon wrote:
>>> While drm_gem_shmem_object does most of the job we need it to do, the
>>> way sub-resources (pages, sgt, vmap) are handled and their lifetimes
>>> gets in the way of BO reclaim. There has been attempts to address
>>> that [1], but in the meantime, new gem_shmem users were introduced
>>> (accel drivers), and some of them manually free some of these resources.
>>> This makes things harder to control/sanitize/validate.
>>>
>>> Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
>>> and forcing gem_shmem users to go through new gem_shmem helpers when they
>>> need manual control of some sort, and I believe this is a dead end if
>>> we don't force users to follow some stricter rules through carefully
>>> designed helpers, because there will always be one user doing crazy things
>>> with gem_shmem_object internals, which ends up tripping out the common
>>> helpers when they are called.
>>>
>>> The consensus we reached was that we would be better off forking
>>> gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
>>> current transition tries to minimize the changes, but there are still
>>> some aspects that are different, the main one being that we no longer
>>> have a pages_use_count, and pages stays around until the GEM object is
>>> destroyed (or when evicted once we've added a shrinker). The sgt also
>>> no longer retains pages. This is losely based on how msm does things by
>>> the way.  
>>
>> From a reviewing perspective it's a little tricky trying to match up the
>> implementation to shmem because of these changes. I don't know how
>> difficult it would be to split the changes to a patch which literally
>> copies (with renames) from shmem, followed by simplifying out the parts
>> we don't want.
> 
> It's a bit annoying as the new implementation is not based on shmem at
> all, but if you think it helps the review, I can try what you're
> suggesting. I mean, I'm not convinced it will be significantly easier
> to review with this extra step, since the new logic is different enough
> (especially when it comes to resource refcounting) that it needs a
> careful review anyway (which you started doing here).

I wasn't sure how much you had originally based it on shmem. I noticed
some comments were copied over and in some places it was easy to match
up. But in others it's much less clear.

If you haven't actually started from a direct copy of shmem then it's
probably not going to be much clearer doing that as an extra step. It's
just in places it looked like you had.

>>
>> Of course the main issue is going to be getting some proper testing of
>> this (especially with the shrinker added).
> 
> For the shrinker, the best I can propose for now is extending the
> IGT tests I've added. For close-to-real-usecases testing of the shmem ->
> custom transition (this commit), making sure the g610 jobs we have in
> mesa CI still passes is a start. If you have other ideas, I'd be happy
> to give them a try.

Sadly I don't have any good suggestions. I haven't found the time to
work on improving my own test setup.

>>
>>>
>>> If there's any interest in sharing code (probably with msm, since the
>>> panthor shrinker is going to be losely based on the msm implementation),
>>> we can always change gears and do that once we have everything
>>> working/merged.
>>>
>>> [1]https://patchwork.kernel.org/project/dri-devel/patch/20240105184624.508603-1-dmitry.osipenko@collabora.com/
>>>
>>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
>>> ---
>>>  drivers/gpu/drm/panthor/Kconfig         |   1 -
>>>  drivers/gpu/drm/panthor/panthor_drv.c   |   7 +-
>>>  drivers/gpu/drm/panthor/panthor_fw.c    |  16 +-
>>>  drivers/gpu/drm/panthor/panthor_gem.c   | 696 ++++++++++++++++++++----
>>>  drivers/gpu/drm/panthor/panthor_gem.h   |  62 ++-
>>>  drivers/gpu/drm/panthor/panthor_mmu.c   |  49 +-
>>>  drivers/gpu/drm/panthor/panthor_sched.c |   9 +-
>>>  7 files changed, 666 insertions(+), 174 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig
>>> index 55b40ad07f3b..911e7f4810c3 100644
>>> --- a/drivers/gpu/drm/panthor/Kconfig
>>> +++ b/drivers/gpu/drm/panthor/Kconfig
>>> @@ -8,7 +8,6 @@ config DRM_PANTHOR
>>>  	depends on MMU
>>>  	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>>  	select DRM_EXEC
>>> -	select DRM_GEM_SHMEM_HELPER
>>>  	select DRM_GPUVM
>>>  	select DRM_SCHED
>>>  	select IOMMU_IO_PGTABLE_LPAE
>>> diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
>>> index 52c27a60c84a..90e9abc22d9e 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_drv.c
>>> +++ b/drivers/gpu/drm/panthor/panthor_drv.c
>>> @@ -19,6 +19,7 @@
>>>  #include <drm/drm_debugfs.h>
>>>  #include <drm/drm_drv.h>
>>>  #include <drm/drm_exec.h>
>>> +#include <drm/drm_file.h>
>>>  #include <drm/drm_ioctl.h>
>>>  #include <drm/drm_print.h>
>>>  #include <drm/drm_syncobj.h>
>>> @@ -1457,7 +1458,7 @@ static int panthor_ioctl_bo_query_info(struct drm_device *ddev, void *data,
>>>  	args->create_flags = bo->flags;
>>>  
>>>  	args->extra_flags = 0;
>>> -	if (drm_gem_is_imported(&bo->base.base))
>>> +	if (drm_gem_is_imported(&bo->base))
>>>  		args->extra_flags |= DRM_PANTHOR_BO_IS_IMPORTED;
>>>  
>>>  	drm_gem_object_put(obj);
>>> @@ -1671,8 +1672,7 @@ static const struct drm_driver panthor_drm_driver = {
>>>  	.major = 1,
>>>  	.minor = 7,
>>>  
>>> -	.gem_create_object = panthor_gem_create_object,
>>> -	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
>>> +	.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
>>>  	.gem_prime_import = panthor_gem_prime_import,
>>>  #ifdef CONFIG_DEBUG_FS
>>>  	.debugfs_init = panthor_debugfs_init,
>>> @@ -1822,3 +1822,4 @@ module_exit(panthor_exit);
>>>  MODULE_AUTHOR("Panthor Project Developers");
>>>  MODULE_DESCRIPTION("Panthor DRM Driver");
>>>  MODULE_LICENSE("Dual MIT/GPL");
>>> +MODULE_IMPORT_NS("DMA_BUF");
>>> diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
>>> index a64ec8756bed..f135cf2130b8 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_fw.c
>>> +++ b/drivers/gpu/drm/panthor/panthor_fw.c
>>> @@ -627,7 +627,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
>>>  		u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK;
>>>  		struct panthor_gem_object *bo;
>>>  		u32 vm_map_flags = 0;
>>> -		struct sg_table *sgt;
>>>  		u64 va = hdr.va.start;
>>>  
>>>  		if (!(hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_WR))
>>> @@ -665,11 +664,12 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
>>>  		panthor_fw_init_section_mem(ptdev, section);
>>>  
>>>  		bo = to_panthor_bo(section->mem->obj);
>>> -		sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
>>> -		if (IS_ERR(sgt))
>>> -			return PTR_ERR(sgt);
>>>  
>>> -		dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
>>> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
>>> +		if (drm_WARN_ON_ONCE(&ptdev->base, !bo->dmap.sgt))
>>> +			return -EINVAL;
>>> +
>>> +		dma_sync_sgtable_for_device(ptdev->base.dev, bo->dmap.sgt, DMA_TO_DEVICE);
>>>  	}
>>>  
>>>  	if (hdr.va.start == CSF_MCU_SHARED_REGION_START)
>>> @@ -729,8 +729,10 @@ panthor_reload_fw_sections(struct panthor_device *ptdev, bool full_reload)
>>>  			continue;
>>>  
>>>  		panthor_fw_init_section_mem(ptdev, section);
>>> -		sgt = drm_gem_shmem_get_pages_sgt(&to_panthor_bo(section->mem->obj)->base);
>>> -		if (!drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(sgt)))
>>> +
>>> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
>>> +		sgt = to_panthor_bo(section->mem->obj)->dmap.sgt;
>>> +		if (!drm_WARN_ON_ONCE(&ptdev->base, !sgt))
>>>  			dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
>>>  	}
>>>  }
>>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
>>> index 4b3d82f001d8..0e52c7a07c87 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
>>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
>>> @@ -8,9 +8,11 @@
>>>  #include <linux/dma-mapping.h>
>>>  #include <linux/err.h>
>>>  #include <linux/slab.h>
>>> +#include <linux/vmalloc.h>
>>>  
>>>  #include <drm/drm_debugfs.h>
>>>  #include <drm/drm_file.h>
>>> +#include <drm/drm_prime.h>
>>>  #include <drm/drm_print.h>
>>>  #include <drm/panthor_drm.h>
>>>  
>>> @@ -44,7 +46,7 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo)
>>>  
>>>  static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
>>>  {
>>> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
>>> +	struct panthor_device *ptdev = container_of(bo->base.dev,
>>>  						    struct panthor_device, base);
>>>  
>>>  	bo->debugfs.creator.tgid = current->group_leader->pid;
>>> @@ -57,7 +59,7 @@ static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
>>>  
>>>  static void panthor_gem_debugfs_bo_rm(struct panthor_gem_object *bo)
>>>  {
>>> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
>>> +	struct panthor_device *ptdev = container_of(bo->base.dev,
>>>  						    struct panthor_device, base);
>>>  
>>>  	if (list_empty(&bo->debugfs.node))
>>> @@ -80,9 +82,9 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo) {}
>>>  #endif
>>>  
>>>  static bool
>>> -should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
>>> +should_map_wc(struct panthor_gem_object *bo)
>>>  {
>>> -	struct panthor_device *ptdev = container_of(bo->base.base.dev, struct panthor_device, base);
>>> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
>>>  
>>>  	/* We can't do uncached mappings if the device is coherent,
>>>  	 * because the zeroing done by the shmem layer at page allocation
>>> @@ -112,6 +114,208 @@ should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
>>>  	return true;
>>>  }
>>>  
>>> +static void
>>> +panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
>>> +{
>>> +	if (!bo->backing.pages)
>>> +		return;
>>> +
>>> +	drm_gem_put_pages(&bo->base, bo->backing.pages, true, false);
>>> +	bo->backing.pages = NULL;
>>> +}
>>> +
>>> +static int
>>> +panthor_gem_backing_get_pages_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	dma_resv_assert_held(bo->base.resv);
>>> +
>>> +	if (bo->backing.pages)
>>> +		return 0;
>>> +
>>> +	bo->backing.pages = drm_gem_get_pages(&bo->base);
>>> +	if (IS_ERR(bo->backing.pages)) {
>>> +		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
>>> +			    bo->backing.pages);
>>> +		return PTR_ERR(bo->backing.pages);  
>>
>> This leaves bo->backing.pages set to the error value, which means a
>> future call to panthor_gem_backing_get_pages_locked() for the same
>> object will return success. Unless there's some 'poisoning' that I
>> haven't spotted this looks like a bug.
> 
> That's a bug, I'll fix it.
> 
>>
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	int ret;
>>> +
>>> +	dma_resv_assert_held(bo->base.resv);
>>> +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
>>> +
>>> +	if (refcount_inc_not_zero(&bo->backing.pin_count))
>>> +		return 0;
>>> +
>>> +	ret = panthor_gem_backing_get_pages_locked(bo);
>>> +	if (!ret)
>>> +		refcount_set(&bo->backing.pin_count, 1);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	dma_resv_assert_held(bo->base.resv);
>>> +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
>>> +
>>> +	/* We don't release anything when pin_count drops to zero.
>>> +	 * Pages stay there until an explicit cleanup is requested.
>>> +	 */
>>> +	if (!refcount_dec_not_one(&bo->backing.pin_count))
>>> +		refcount_set(&bo->backing.pin_count, 0);  
>>
>> Why not just refcount_dec()?
> 
> Because refcount_dec() complains when it's passed a value that's less
> than 2. The rational being that you need to do something special
> (release resources) when you reach zero. In our case we don't, because
> pages are lazily reclaimed, so we just set the counter back to zero.

Ah, yes I'd misread the "old <= 1" check as "old < 1". Hmm, I dislike it
because it's breaking the atomicity - if another thread does a increment
between the two operations then we lose a reference count.

It does make me think that perhaps the refcount APIs are not designed
for this case and perhaps we should just use atomics directly.

>>
>>> +}
>>> +
>>> +static void
>>> +panthor_gem_dev_map_cleanup(struct panthor_gem_object *bo)
>>> +{
>>> +	if (!bo->dmap.sgt)
>>> +		return;
>>> +
>>> +	dma_unmap_sgtable(drm_dev_dma_dev(bo->base.dev), bo->dmap.sgt, DMA_BIDIRECTIONAL, 0);
>>> +	sg_free_table(bo->dmap.sgt);
>>> +	kfree(bo->dmap.sgt);
>>> +	bo->dmap.sgt = NULL;
>>> +}
>>> +
>>> +static struct sg_table *
>>> +panthor_gem_dev_map_get_sgt_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	struct sg_table *sgt;
>>> +	int ret;
>>> +
>>> +	dma_resv_assert_held(bo->base.resv);
>>> +
>>> +	if (bo->dmap.sgt)
>>> +		return bo->dmap.sgt;
>>> +
>>> +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
>>> +		return ERR_PTR(-EINVAL);
>>> +
>>> +	/* Pages stay around after they've been allocated. At least that stands
>>> +	 * until we add a shrinker.
>>> +	 */
>>> +	ret = panthor_gem_backing_get_pages_locked(bo);
>>> +	if (ret)
>>> +		return ERR_PTR(ret);
>>> +
>>> +	sgt = drm_prime_pages_to_sg(bo->base.dev, bo->backing.pages,
>>> +				    bo->base.size >> PAGE_SHIFT);
>>> +	if (IS_ERR(sgt))
>>> +		return sgt;
>>> +
>>> +	/* Map the pages for use by the h/w. */
>>> +	ret = dma_map_sgtable(drm_dev_dma_dev(bo->base.dev), sgt, DMA_BIDIRECTIONAL, 0);
>>> +	if (ret)
>>> +		goto err_free_sgt;
>>> +
>>> +	bo->dmap.sgt = sgt;
>>> +	return sgt;
>>> +
>>> +err_free_sgt:
>>> +	sg_free_table(sgt);
>>> +	kfree(sgt);
>>> +	return ERR_PTR(ret);
>>> +}
>>> +
>>> +struct sg_table *
>>> +panthor_gem_get_dev_sgt(struct panthor_gem_object *bo)
>>> +{
>>> +	struct sg_table *sgt;
>>> +
>>> +	dma_resv_lock(bo->base.resv, NULL);
>>> +	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
>>> +	dma_resv_unlock(bo->base.resv);
>>> +
>>> +	return sgt;
>>> +}
>>> +
>>> +static void
>>> +panthor_gem_vmap_cleanup(struct panthor_gem_object *bo)
>>> +{
>>> +	if (!bo->cmap.vaddr)
>>> +		return;
>>> +
>>> +	vunmap(bo->cmap.vaddr);
>>> +	bo->cmap.vaddr = NULL;
>>> +	panthor_gem_backing_unpin_locked(bo);
>>> +}
>>> +
>>> +static int
>>> +panthor_gem_prep_for_cpu_map_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	if (should_map_wc(bo)) {
>>> +		struct sg_table *sgt;
>>> +
>>> +		sgt = panthor_gem_dev_map_get_sgt_locked(bo);
>>> +		if (IS_ERR(sgt))
>>> +			return PTR_ERR(sgt);
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static void *
>>> +panthor_gem_vmap_get_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	pgprot_t prot = PAGE_KERNEL;
>>> +	void *vaddr;
>>> +	int ret;
>>> +
>>> +	dma_resv_assert_held(bo->base.resv);
>>> +
>>> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
>>> +		return ERR_PTR(-EINVAL);
>>> +
>>> +	if (refcount_inc_not_zero(&bo->cmap.vaddr_use_count)) {
>>> +		drm_WARN_ON_ONCE(bo->base.dev, !bo->cmap.vaddr);
>>> +		return bo->cmap.vaddr;
>>> +	}
>>> +
>>> +	ret = panthor_gem_backing_pin_locked(bo);
>>> +	if (ret)
>>> +		return ERR_PTR(ret);
>>> +
>>> +	ret = panthor_gem_prep_for_cpu_map_locked(bo);
>>> +	if (ret)
>>> +		return ERR_PTR(ret);
>>> +
>>> +	if (should_map_wc(bo))
>>> +		prot = pgprot_writecombine(prot);
>>> +
>>> +	vaddr = vmap(bo->backing.pages, bo->base.size >> PAGE_SHIFT, VM_MAP, prot);
>>> +	if (!vaddr) {
>>> +		ret = -ENOMEM;
>>> +		goto err_unpin;
>>> +	}
>>> +
>>> +	bo->cmap.vaddr = vaddr;
>>> +	refcount_set(&bo->cmap.vaddr_use_count, 1);
>>> +	return vaddr;
>>> +
>>> +err_unpin:
>>> +	panthor_gem_backing_unpin_locked(bo);
>>> +	return ERR_PTR(ret);
>>> +}
>>> +
>>> +static void
>>> +panthor_gem_vmap_put_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
>>> +		return;
>>> +
>>> +	if (refcount_dec_not_one(&bo->cmap.vaddr_use_count))
>>> +		return;
>>> +
>>> +	refcount_set(&bo->cmap.vaddr_use_count, 0);
>>> +	panthor_gem_vmap_cleanup(bo);
>>> +}
>>> +
>>>  static void panthor_gem_free_object(struct drm_gem_object *obj)
>>>  {
>>>  	struct panthor_gem_object *bo = to_panthor_bo(obj);
>>> @@ -127,8 +331,17 @@ static void panthor_gem_free_object(struct drm_gem_object *obj)
>>>  
>>>  	mutex_destroy(&bo->label.lock);
>>>  
>>> -	drm_gem_free_mmap_offset(&bo->base.base);
>>> -	drm_gem_shmem_free(&bo->base);
>>> +	if (drm_gem_is_imported(obj)) {
>>> +		drm_prime_gem_destroy(obj, bo->dmap.sgt);
>>> +	} else {
>>> +		panthor_gem_vmap_cleanup(bo);  
>>
>> panthor_gem_vmap_cleanup() calls panthor_gem_backing_unpin_locked()
>> which expects the reservation lock to be held.
> 
> Good catch! I think we need to rename panthor_gem_vmap_cleanup() into
> panthor_gem_vmap_cleanup_locked(), take the resv lock before calling
> panthor_gem_vmap_cleanup_locked() and release it after calling
> panthor_gem_backing_cleanup_locked().
> 
>>
>>> +		panthor_gem_dev_map_cleanup(bo);
> 
> We should probably suffix that one with _locked() too, with the extra
> resv_held() annotations in the code.
> 
>>> +		panthor_gem_backing_cleanup(bo);
>>> +	}
>>> +
>>> +	drm_gem_object_release(obj);
>>> +
>>> +	kfree(bo);
>>>  	drm_gem_object_put(vm_root_gem);
>>>  }
>>>  
>>> @@ -159,15 +372,15 @@ panthor_gem_prime_begin_cpu_access(struct dma_buf *dma_buf,
>>>  {
>>>  	struct drm_gem_object *obj = dma_buf->priv;
>>>  	struct drm_device *dev = obj->dev;
>>> -	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
>>> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>>>  	struct dma_buf_attachment *attach;
>>>  
>>>  	dma_resv_lock(obj->resv, NULL);
>>> -	if (shmem->sgt)
>>> -		dma_sync_sgtable_for_cpu(dev->dev, shmem->sgt, dir);
>>> +	if (bo->dmap.sgt)
>>> +		dma_sync_sgtable_for_cpu(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
>>>  
>>> -	if (shmem->vaddr)
>>> -		invalidate_kernel_vmap_range(shmem->vaddr, shmem->base.size);
>>> +	if (bo->cmap.vaddr)
>>> +		invalidate_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
>>>  
>>>  	list_for_each_entry(attach, &dma_buf->attachments, node) {
>>>  		struct sg_table *sgt = attach->priv;
>>> @@ -186,7 +399,7 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
>>>  {
>>>  	struct drm_gem_object *obj = dma_buf->priv;
>>>  	struct drm_device *dev = obj->dev;
>>> -	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
>>> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>>>  	struct dma_buf_attachment *attach;
>>>  
>>>  	dma_resv_lock(obj->resv, NULL);
>>> @@ -197,11 +410,11 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
>>>  			dma_sync_sgtable_for_device(attach->dev, sgt, dir);
>>>  	}
>>>  
>>> -	if (shmem->vaddr)
>>> -		flush_kernel_vmap_range(shmem->vaddr, shmem->base.size);
>>> +	if (bo->cmap.vaddr)
>>> +		flush_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
>>>  
>>> -	if (shmem->sgt)
>>> -		dma_sync_sgtable_for_device(dev->dev, shmem->sgt, dir);
>>> +	if (bo->dmap.sgt)
>>> +		dma_sync_sgtable_for_device(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
>>>  
>>>  	dma_resv_unlock(obj->resv);
>>>  	return 0;
>>> @@ -258,53 +471,339 @@ panthor_gem_prime_import(struct drm_device *dev,
>>>  	return drm_gem_prime_import(dev, dma_buf);
>>>  }
>>>  
>>> +static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
>>> +				   const struct drm_gem_object *obj)
>>> +{
>>> +	const struct panthor_gem_object *bo = to_panthor_bo(obj);
>>> +
>>> +	if (drm_gem_is_imported(&bo->base))
>>> +		return;
>>> +
>>> +	drm_printf_indent(p, indent, "resident=%s\n", str_true_false(bo->backing.pages));
>>> +	drm_printf_indent(p, indent, "pages_pin_count=%u\n", refcount_read(&bo->backing.pin_count));
>>> +	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
>>> +			  refcount_read(&bo->cmap.vaddr_use_count));
>>> +	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
>>> +}
>>> +
>>> +static int panthor_gem_pin_locked(struct drm_gem_object *obj)
>>> +{
>>> +	if (drm_gem_is_imported(obj))
>>> +		return 0;
>>> +
>>> +	return panthor_gem_backing_pin_locked(to_panthor_bo(obj));
>>> +}
>>> +
>>> +static void panthor_gem_unpin_locked(struct drm_gem_object *obj)
>>> +{
>>> +	if (!drm_gem_is_imported(obj))
>>> +		panthor_gem_backing_unpin_locked(to_panthor_bo(obj));
>>> +}
>>> +
>>> +int panthor_gem_pin(struct panthor_gem_object *bo)
>>> +{
>>> +	int ret = 0;
>>> +
>>> +	if (drm_gem_is_imported(&bo->base))
>>> +		return 0;
>>> +
>>> +	if (refcount_inc_not_zero(&bo->backing.pin_count))
>>> +		return 0;
>>> +
>>> +	dma_resv_lock(bo->base.resv, NULL);
>>> +	ret = panthor_gem_pin_locked(&bo->base);  
>>
>> We might as well call panthor_gem_backing_pin_locked() since we know
>> it's not imported.
> 
> Fair enough.
> 
>>
>>> +	dma_resv_unlock(bo->base.resv);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +void panthor_gem_unpin(struct panthor_gem_object *bo)
>>> +{
>>> +	if (drm_gem_is_imported(&bo->base))
>>> +		return;
>>> +
>>> +	if (refcount_dec_not_one(&bo->backing.pin_count))
>>> +		return;
>>> +
>>> +	dma_resv_lock(bo->base.resv, NULL);
>>> +	panthor_gem_unpin_locked(&bo->base);  
>>
>> Same here.
> 
> Will do.
> 
>>
>>> +	dma_resv_unlock(bo->base.resv);
>>> +}
>>> +
>>> +static struct sg_table *panthor_gem_get_sg_table(struct drm_gem_object *obj)
>>> +{
>>> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>>> +
>>> +	drm_WARN_ON_ONCE(obj->dev, drm_gem_is_imported(obj));
>>> +	drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages);
>>> +	drm_WARN_ON_ONCE(obj->dev, !refcount_read(&bo->backing.pin_count));
>>> +
>>> +	return drm_prime_pages_to_sg(obj->dev, bo->backing.pages, obj->size >> PAGE_SHIFT);
>>> +}
>>> +
>>> +static int panthor_gem_vmap_locked(struct drm_gem_object *obj,
>>> +				   struct iosys_map *map)
>>> +{
>>> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>>> +	void *vaddr;
>>> +
>>> +	dma_resv_assert_held(obj->resv);
>>> +
>>> +	if (drm_gem_is_imported(obj))
>>> +		return dma_buf_vmap(obj->import_attach->dmabuf, map);
>>> +
>>> +	vaddr = panthor_gem_vmap_get_locked(bo);
>>> +	if (IS_ERR(vaddr))
>>> +		return PTR_ERR(vaddr);
>>> +
>>> +	iosys_map_set_vaddr(map, vaddr);
>>> +	return 0;
>>> +}
>>> +
>>> +static void panthor_gem_vunmap_locked(struct drm_gem_object *obj,
>>> +				      struct iosys_map *map)
>>> +{
>>> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>>> +
>>> +	dma_resv_assert_held(obj->resv);
>>> +
>>> +	if (drm_gem_is_imported(obj)) {
>>> +		dma_buf_vunmap(obj->import_attach->dmabuf, map);
>>> +	} else {
>>> +		drm_WARN_ON_ONCE(obj->dev, bo->cmap.vaddr != map->vaddr);
>>> +		panthor_gem_vmap_put_locked(bo);
>>> +	}
>>> +}
>>> +
>>> +static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
>>> +{
>>> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>>> +	int ret;
>>> +
>>> +	if (drm_gem_is_imported(obj)) {
>>> +		/* Reset both vm_ops and vm_private_data, so we don't end up with
>>> +		 * vm_ops pointing to our implementation if the dma-buf backend
>>> +		 * doesn't set those fields.
>>> +		 */
>>> +		vma->vm_private_data = NULL;
>>> +		vma->vm_ops = NULL;
>>> +
>>> +		ret = dma_buf_mmap(obj->dma_buf, vma, 0);
>>> +
>>> +		/* Drop the reference drm_gem_mmap_obj() acquired.*/
>>> +		if (!ret)
>>> +			drm_gem_object_put(obj);
>>> +
>>> +		return ret;
>>> +	}
>>> +
>>> +	if (is_cow_mapping(vma->vm_flags))
>>> +		return -EINVAL;
>>> +
>>> +	dma_resv_lock(obj->resv, NULL);
>>> +	ret = panthor_gem_backing_get_pages_locked(bo);
>>> +	if (!ret)
>>> +		ret = panthor_gem_prep_for_cpu_map_locked(bo);
>>> +	dma_resv_unlock(obj->resv);
>>> +
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
>>> +	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
>>> +	if (should_map_wc(bo))
>>> +		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>>  static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
>>>  {
>>>  	struct panthor_gem_object *bo = to_panthor_bo(obj);
>>>  	enum drm_gem_object_status res = 0;
>>>  
>>> -	if (drm_gem_is_imported(&bo->base.base) || bo->base.pages)
>>> +	if (drm_gem_is_imported(&bo->base) || bo->backing.pages)
>>>  		res |= DRM_GEM_OBJECT_RESIDENT;
>>>  
>>>  	return res;
>>>  }
>>>  
>>> -static const struct drm_gem_object_funcs panthor_gem_funcs = {
>>> -	.free = panthor_gem_free_object,
>>> -	.print_info = drm_gem_shmem_object_print_info,
>>> -	.pin = drm_gem_shmem_object_pin,
>>> -	.unpin = drm_gem_shmem_object_unpin,
>>> -	.get_sg_table = drm_gem_shmem_object_get_sg_table,
>>> -	.vmap = drm_gem_shmem_object_vmap,
>>> -	.vunmap = drm_gem_shmem_object_vunmap,
>>> -	.mmap = drm_gem_shmem_object_mmap,
>>> -	.status = panthor_gem_status,
>>> -	.export = panthor_gem_prime_export,
>>> -	.vm_ops = &drm_gem_shmem_vm_ops,
>>> +static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
>>> +{
>>> +#ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
>>> +	unsigned long pfn = page_to_pfn(page);
>>> +	unsigned long paddr = pfn << PAGE_SHIFT;
>>> +	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
>>> +
>>> +	if (aligned &&
>>> +	    pmd_none(*vmf->pmd) &&
>>> +	    folio_test_pmd_mappable(page_folio(page))) {
>>> +		pfn &= PMD_MASK >> PAGE_SHIFT;
>>> +		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
>>> +			return true;
>>> +	}
>>> +#endif
>>> +
>>> +	return false;
>>> +}
>>> +
>>> +static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
>>> +{
>>> +	struct vm_area_struct *vma = vmf->vma;
>>> +	struct drm_gem_object *obj = vma->vm_private_data;
>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
>>> +	loff_t num_pages = obj->size >> PAGE_SHIFT;
>>> +	vm_fault_t ret;
>>> +	pgoff_t page_offset;
>>> +	unsigned long pfn;
>>> +
>>> +	/* Offset to faulty address in the VMA. */
>>> +	page_offset = vmf->pgoff - vma->vm_pgoff;
>>> +
>>> +	dma_resv_lock(bo->base.resv, NULL);
>>> +
>>> +	if (page_offset >= num_pages ||
>>> +	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
>>> +		ret = VM_FAULT_SIGBUS;
>>> +		goto out;
>>> +	}
>>> +
>>> +	if (try_map_pmd(vmf, vmf->address, bo->backing.pages[page_offset])) {
>>> +		ret = VM_FAULT_NOPAGE;
>>> +		goto out;
>>> +	}
>>> +
>>> +	pfn = page_to_pfn(bo->backing.pages[page_offset]);
>>> +	ret = vmf_insert_pfn(vma, vmf->address, pfn);
>>> +
>>> + out:
>>> +	dma_resv_unlock(bo->base.resv);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static void panthor_gem_vm_open(struct vm_area_struct *vma)
>>> +{
>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
>>> +
>>> +	drm_WARN_ON(bo->base.dev, drm_gem_is_imported(&bo->base));
>>> +
>>> +	dma_resv_lock(bo->base.resv, NULL);
>>> +
>>> +	/* We should have already pinned the pages when the buffer was first
>>> +	 * mmap'd, vm_open() just grabs an additional reference for the new
>>> +	 * mm the vma is getting copied into (ie. on fork()).
>>> +	 */
>>> +	drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages);
>>> +
>>> +	dma_resv_unlock(bo->base.resv);
>>> +
>>> +	drm_gem_vm_open(vma);
>>> +}
>>> +
>>> +const struct vm_operations_struct panthor_gem_vm_ops = {
>>> +	.fault = panthor_gem_fault,
>>> +	.open = panthor_gem_vm_open,
>>> +	.close = drm_gem_vm_close,
>>>  };
>>>  
>>> -/**
>>> - * panthor_gem_create_object - Implementation of driver->gem_create_object.
>>> - * @ddev: DRM device
>>> - * @size: Size in bytes of the memory the object will reference
>>> - *
>>> - * This lets the GEM helpers allocate object structs for us, and keep
>>> - * our BO stats correct.
>>> - */
>>> -struct drm_gem_object *panthor_gem_create_object(struct drm_device *ddev, size_t size)
>>> -{
>>> -	struct panthor_gem_object *obj;
>>> +static const struct drm_gem_object_funcs panthor_gem_funcs = {
>>> +	.free = panthor_gem_free_object,
>>> +	.print_info = panthor_gem_print_info,
>>> +	.pin = panthor_gem_pin_locked,
>>> +	.unpin = panthor_gem_unpin_locked,
>>> +	.get_sg_table = panthor_gem_get_sg_table,
>>> +	.vmap = panthor_gem_vmap_locked,
>>> +	.vunmap = panthor_gem_vunmap_locked,
>>> +	.mmap = panthor_gem_mmap,
>>> +	.status = panthor_gem_status,
>>> +	.export = panthor_gem_prime_export,
>>> +	.vm_ops = &panthor_gem_vm_ops,
>>> +};
>>>  
>>> -	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
>>> -	if (!obj)
>>> +static struct panthor_gem_object *
>>> +panthor_gem_alloc_object(uint32_t flags)
>>> +{
>>> +	struct panthor_gem_object *bo;
>>> +
>>> +	bo = kzalloc(sizeof(*bo), GFP_KERNEL);
>>> +	if (!bo)
>>>  		return ERR_PTR(-ENOMEM);
>>>  
>>> -	obj->base.base.funcs = &panthor_gem_funcs;
>>> -	mutex_init(&obj->label.lock);
>>> +	bo->base.funcs = &panthor_gem_funcs;
>>> +	bo->flags = flags;
>>> +	mutex_init(&bo->label.lock);
>>> +	panthor_gem_debugfs_bo_init(bo);
>>> +	return bo;
>>> +}
>>>  
>>> -	panthor_gem_debugfs_bo_init(obj);
>>> +static struct panthor_gem_object *
>>> +panthor_gem_create(struct drm_device *dev, size_t size, uint32_t flags,
>>> +		   struct panthor_vm *exclusive_vm, u32 usage_flags)
>>> +{
>>> +	struct panthor_gem_object *bo;
>>> +	int ret;
>>>  
>>> -	return &obj->base.base;
>>> +	bo = panthor_gem_alloc_object(flags);
>>> +	if (IS_ERR(bo))
>>> +		return bo;
>>> +
>>> +	size = PAGE_ALIGN(size);
>>> +	ret = drm_gem_object_init(dev, &bo->base, size);
>>> +	if (ret)
>>> +		goto err_put;
>>> +
>>> +	/* Our buffers are kept pinned, so allocating them
>>> +	 * from the MOVABLE zone is a really bad idea, and
>>> +	 * conflicts with CMA. See comments above new_inode()
>>> +	 * why this is required _and_ expected if you're
>>> +	 * going to pin these pages.
>>> +	 */
>>> +	mapping_set_gfp_mask(bo->base.filp->f_mapping,
>>> +			     GFP_HIGHUSER | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
>>> +
>>> +	ret = drm_gem_create_mmap_offset(&bo->base);
>>> +	if (ret)
>>> +		goto err_put;
>>> +
>>> +	if (exclusive_vm) {
>>> +		bo->exclusive_vm_root_gem = panthor_vm_root_gem(exclusive_vm);
>>> +		drm_gem_object_get(bo->exclusive_vm_root_gem);
>>> +		bo->base.resv = bo->exclusive_vm_root_gem->resv;
>>> +	}
>>> +
>>> +	panthor_gem_debugfs_set_usage_flags(bo, usage_flags);
>>> +	return bo;
>>> +
>>> +err_put:
>>> +	drm_gem_object_put(&bo->base);  
>>
>> Is this call to _put() correct? I see the __drm_gem_shmem_init()
>> function uses drm_gem_object_release() instead.
> 
> I think it is okay, as long as:
> 
> - the GEM object is zeroed at alloc time (all pointers set to NULL,
>   vma_node unallocated, ...)
> - the GEM funcs are set early (done in panthor_gem_alloc_object())
> - the drm_gem_object is initialized (done as part
>   of drm_gem_object_init(), early enough to guarantee that nothing
>   fails before this is done)
> - the gem->funcs.free() function treats any NULL pointer as a "partially
>   initialized object" case instead of "invalid object", and that we do
>   in panthor_gem_free_object(), I think, just like
>   drm_gem_object_release() does.
> 
> I'd really prefer to keep this _put() instead of adding new
> err_<undo_x> labels for each of the steps that might have taken place
> between drm_gem_object_init() and the failing call, unless there's a
> proof this is unsafe (might have missed something you spotted).

So the difference that I saw is that drm_gem_object_put() decrements the
reference count, whereas drm_gem_object_release() doesn't. I wasn't
really sure whether that actually made any real difference. The refcount
should be known to be 1 at this point.

I'm happy for this to stay as _put().

>>
>>> +	return ERR_PTR(ret);
>>> +}
>>> +
>>> +struct drm_gem_object *
>>> +panthor_gem_prime_import_sg_table(struct drm_device *dev,
>>> +				  struct dma_buf_attachment *attach,
>>> +				  struct sg_table *sgt)
>>> +{
>>> +	struct panthor_gem_object *bo;
>>> +	int ret;
>>> +
>>> +	bo = panthor_gem_alloc_object(0);
>>> +	if (IS_ERR(bo))
>>> +		return &bo->base;  
>>
>> bo->base is invalid here. I think you want ERR_CAST(bo).
> 
> Absolutely. Will fix that.
> 
>>
>>> +
>>> +	drm_gem_private_object_init(dev, &bo->base, attach->dmabuf->size);
>>> +
>>> +	ret = drm_gem_create_mmap_offset(&bo->base);
>>> +	if (ret)
>>> +		goto err_put;
>>> +
>>> +	bo->dmap.sgt = sgt;
>>> +	return &bo->base;
>>> +
>>> +err_put:
>>> +	drm_gem_object_put(&bo->base);  
>>
>> Again I'm not convinced _put does the right thing here.
> 
> Hm, we probably have to re-order the bo->dmap.sgt assignment so it
> happens just after drm_gem_private_object_init() and there's no NULL
> deref in the panthor_gem_free_object() path, but otherwise I think it's
> safe to call panthor_gem_free_object() after the non-fallible
> initialization took place. Am I missing something?

I think with the reordering it should be fine.

Thanks,
Steve


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 15:51           ` Alice Ryhl
@ 2026-01-12 16:06             ` Boris Brezillon
  0 siblings, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 16:06 UTC (permalink / raw)
  To: Alice Ryhl
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Akash Goel, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström, kernel

On Mon, 12 Jan 2026 16:51:26 +0100
Alice Ryhl <aliceryhl@google.com> wrote:

> On Mon, Jan 12, 2026 at 4:49 PM Boris Brezillon
> <boris.brezillon@collabora.com> wrote:
> >
> > On Mon, 12 Jan 2026 16:19:52 +0100
> > Alice Ryhl <aliceryhl@google.com> wrote:
> >  
> > > On Mon, Jan 12, 2026 at 3:40 PM Boris Brezillon
> > > <boris.brezillon@collabora.com> wrote:  
> > > >
> > > > On Mon, 12 Jan 2026 12:33:33 +0000
> > > > Steven Price <steven.price@arm.com> wrote:
> > > >  
> > > > > On 09/01/2026 13:08, Boris Brezillon wrote:  
> > > > > > +static void panthor_gem_vm_close(struct vm_area_struct *vma)
> > > > > > +{
> > > > > > +   struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > > > > > +
> > > > > > +   if (drm_gem_is_imported(&bo->base))
> > > > > > +           goto out;
> > > > > > +
> > > > > > +   if (refcount_dec_not_one(&bo->cmap.mmap_count))
> > > > > > +           goto out;
> > > > > > +
> > > > > > +   dma_resv_lock(bo->base.resv, NULL);
> > > > > > +   if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> > > > > > +           refcount_set(&bo->cmap.mmap_count, 0);
> > > > > > +   dma_resv_unlock(bo->base.resv);  
> > > > >
> > > > > I don't think this logic is safe. Holding the resv_lock doesn't protect
> > > > > against another thread doing a refcount_inc_not_zero() without holding
> > > > > the lock.
> > > > >
> > > > > I think you can just replace the if() part with a refcount_dec() call,
> > > > > the lock AFAICT is needed because the following patch wants to be sure
> > > > > that !!mmap_count is stable when resv_lock is held.  
> > > >
> > > > I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
> > > > complaining :P.  
> > >
> > > I'm pretty sure that refcount_dec() is fine with 1->0.  
> >
> > That's not what [1] says. refcount_dec_and_test() is okay though, but
> > it'd force us to do a
> >
> >         (void)refcount_dec_and_test()
> >
> > and detail why it's okay to ignore the returned value. Not too sure
> > which one is better.  
> 
> You're right, I mixed it up with refcount_dec_and_test().
> 
> > > > > I also feel you should invert the conditino for refcount_dec_not_one,
> > > > > leading to the following which I feel is easier to read:
> > > > >
> > > > > static void panthor_gem_vm_close(struct vm_area_struct *vma)
> > > > > {
> > > > >       [...]
> > > > >
> > > > >       if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> > > > >               dma_resv_lock(bo->base.resv, NULL);
> > > > >               refcount_dec(&bo->cmap.mmap_count);
> > > > >               dma_resv_unlock(bo->base.resv);
> > > > >       }  
> > > >
> > > > The best I can do is:
> > > >
> > > >         if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> > > >                 dma_resv_lock(bo->base.resv, NULL);
> > > >                 if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> > > >                         refcount_set(&bo->cmap.mmap_count, 0);
> > > >                 dma_resv_unlock(bo->base.resv);
> > > >         }
> > > >
> > > > so we only take the lock when absolutely needed, but the 1 -> 0
> > > > transition still has to be done with "if (dec_not_one) set(0)".  
> > >
> > > Why not just use atomic_t and use the atomic inc/dec operations? They
> > > don't have saturation, but also do not require treating zero
> > > specially.  
> >
> > I had suggested using atomics back when I was reviewing the
> > shmem-shrinker stuff to avoid this exact same issue. I can't find the
> > thread anymore and I can't remember the rationale either (probably that
> > saturation detection was useful, still), but the decision was to use a
> > refcount_t. I don't mind using atomics here, but I'd rather not be
> > blocked on that when/if I try to move that code into a common lib.
> >
> > [1]https://elixir.bootlin.com/linux/v6.19-rc4/source/include/linux/refcount.h#L460  
> 
> It's just a suggestion - no need to block on it.

Sure, np.

> 
> It sounds like refcount_t should have an refcount_inc_maybe_first()
> where 0->1 is ok.

We actually need refcount_dec_maybe_last() in this context, but I get
the idea. Don't know if the "ATOMIC INFRASTRUCTURE" maintainers would
be okay with this idea though, since it's silencing the "do something
special when your counter reaches zero" behavior enforced by the
refcount API, and it's not that often that resources are released
lazily like that anyway, so maybe the extra line is not that big of a
deal.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap()
  2026-01-12 14:32     ` Boris Brezillon
@ 2026-01-12 16:41       ` Steven Price
  2026-01-12 16:50         ` Boris Brezillon
  0 siblings, 1 reply; 62+ messages in thread
From: Steven Price @ 2026-01-12 16:41 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 12/01/2026 14:32, Boris Brezillon wrote:
> On Mon, 12 Jan 2026 12:15:13 +0000
> Steven Price <steven.price@arm.com> wrote:
> 
>> On 09/01/2026 13:07, Boris Brezillon wrote:
>>> Defer pages allocation until their first access.
>>>
>>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
>>> ---
>>>  drivers/gpu/drm/panthor/panthor_gem.c | 119 ++++++++++++++++----------
>>>  1 file changed, 75 insertions(+), 44 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
>>> index 0e52c7a07c87..44f05bd957e7 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
>>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
>>> @@ -600,15 +600,6 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
>>>  	if (is_cow_mapping(vma->vm_flags))
>>>  		return -EINVAL;
>>>  
>>> -	dma_resv_lock(obj->resv, NULL);
>>> -	ret = panthor_gem_backing_get_pages_locked(bo);
>>> -	if (!ret)
>>> -		ret = panthor_gem_prep_for_cpu_map_locked(bo);
>>> -	dma_resv_unlock(obj->resv);
>>> -
>>> -	if (ret)
>>> -		return ret;
>>> -
>>>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
>>>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
>>>  	if (should_map_wc(bo))
>>> @@ -628,82 +619,122 @@ static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
>>>  	return res;
>>>  }
>>>  
>>> -static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
>>> +static vm_fault_t insert_page(struct vm_fault *vmf, struct page *page)
>>>  {
>>> +	struct vm_area_struct *vma = vmf->vma;
>>> +	vm_fault_t ret;
>>> +
>>>  #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
>>>  	unsigned long pfn = page_to_pfn(page);
>>>  	unsigned long paddr = pfn << PAGE_SHIFT;
>>> -	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
>>> +	bool aligned = (vmf->address & ~PMD_MASK) == (paddr & ~PMD_MASK);
>>>  
>>>  	if (aligned &&
>>>  	    pmd_none(*vmf->pmd) &&
>>>  	    folio_test_pmd_mappable(page_folio(page))) {
>>>  		pfn &= PMD_MASK >> PAGE_SHIFT;
>>> -		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
>>> -			return true;
>>> +		ret = vmf_insert_pfn_pmd(vmf, pfn, false);
>>> +		if (ret == VM_FAULT_NOPAGE)
>>> +			return VM_FAULT_NOPAGE;
>>>  	}
>>>  #endif
>>>  
>>> -	return false;
>>> +	return vmf_insert_pfn(vma, vmf->address, page_to_pfn(page));
>>>  }
>>>  
>>> -static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
>>> +static vm_fault_t nonblocking_page_setup(struct vm_fault *vmf, pgoff_t page_offset)
>>>  {
>>>  	struct vm_area_struct *vma = vmf->vma;
>>> -	struct drm_gem_object *obj = vma->vm_private_data;
>>>  	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
>>> -	loff_t num_pages = obj->size >> PAGE_SHIFT;
>>>  	vm_fault_t ret;
>>> -	pgoff_t page_offset;
>>> -	unsigned long pfn;
>>>  
>>> -	/* Offset to faulty address in the VMA. */
>>> -	page_offset = vmf->pgoff - vma->vm_pgoff;
>>> +	if (!dma_resv_trylock(bo->base.resv))
>>> +		return VM_FAULT_RETRY;
>>>  
>>> -	dma_resv_lock(bo->base.resv, NULL);
>>> +	if (bo->backing.pages)
>>> +		ret = insert_page(vmf, bo->backing.pages[page_offset]);
>>> +	else
>>> +		ret = VM_FAULT_RETRY;
>>>  
>>> -	if (page_offset >= num_pages ||
>>> -	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
>>> -		ret = VM_FAULT_SIGBUS;
>>> -		goto out;
>>> +	dma_resv_unlock(bo->base.resv);
>>> +	return ret;
>>> +}
>>> +
>>> +static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
>>> +				      struct panthor_gem_object *bo,
>>> +				      pgoff_t page_offset, bool mmap_lock_held)
>>> +{
>>> +	vm_fault_t ret;
>>> +	int err;
>>> +
>>> +	if (vmf->flags & FAULT_FLAG_INTERRUPTIBLE) {
>>> +		err = dma_resv_lock_interruptible(bo->base.resv, NULL);
>>> +		if (err)
>>> +			return mmap_lock_held ? VM_FAULT_NOPAGE : VM_FAULT_RETRY;  
>>
>> I'm not sure about this. First FAULT_FLAG_INTERRUPTIBLE is currently
>> only used by userfaultfd AFAICT.
> 
> And GUP, which admittedly, only seems possible if one tries to map a
> userpage in kernel space, and we don't support that (yet?).

Well unless my grepping is wrong, GUP is the one place that sets it and
userfaultfd is the one place that consumes it.

As special as Mali is, I'm not sure why we're doing something that no
other driver is?

>> Second returning VM_FAULT_NOPAGE seems
>> wrong - that's for the case were we've inserted a pte but in this case
>> we haven't.
> 
> Got this from [1], and remember going through the fault handler API
> with Akash, and finding something describing this case.

Yes - it does appear that pattern appears elsewhere as a retry
mechanism. Fair enough - the mm documentation is confusing!

Thanks,
Steve

>>
>> Otherwise I couldn't spot any issues staring at the code, but I might
>> have missed something. mm code is always hard to follow!
> 
> It is, indeed, which is why I'm glad to have a new pair of eyes looking
> at this ;-).
> 
> Thanks,
> 
> Boris
> 
> [1]https://elixir.bootlin.com/linux/v6.18.4/source/drivers/gpu/drm/ttm/ttm_bo_vm.c#L116
> 


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-12 16:03       ` Steven Price
@ 2026-01-12 16:45         ` Boris Brezillon
  0 siblings, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 16:45 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Mon, 12 Jan 2026 16:03:18 +0000
Steven Price <steven.price@arm.com> wrote:

> On 12/01/2026 14:17, Boris Brezillon wrote:
> > On Mon, 12 Jan 2026 12:06:17 +0000
> > Steven Price <steven.price@arm.com> wrote:
> >   
> >> On 09/01/2026 13:07, Boris Brezillon wrote:  
> >>> While drm_gem_shmem_object does most of the job we need it to do, the
> >>> way sub-resources (pages, sgt, vmap) are handled and their lifetimes
> >>> gets in the way of BO reclaim. There has been attempts to address
> >>> that [1], but in the meantime, new gem_shmem users were introduced
> >>> (accel drivers), and some of them manually free some of these resources.
> >>> This makes things harder to control/sanitize/validate.
> >>>
> >>> Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
> >>> and forcing gem_shmem users to go through new gem_shmem helpers when they
> >>> need manual control of some sort, and I believe this is a dead end if
> >>> we don't force users to follow some stricter rules through carefully
> >>> designed helpers, because there will always be one user doing crazy things
> >>> with gem_shmem_object internals, which ends up tripping out the common
> >>> helpers when they are called.
> >>>
> >>> The consensus we reached was that we would be better off forking
> >>> gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
> >>> current transition tries to minimize the changes, but there are still
> >>> some aspects that are different, the main one being that we no longer
> >>> have a pages_use_count, and pages stays around until the GEM object is
> >>> destroyed (or when evicted once we've added a shrinker). The sgt also
> >>> no longer retains pages. This is losely based on how msm does things by
> >>> the way.    
> >>
> >> From a reviewing perspective it's a little tricky trying to match up the
> >> implementation to shmem because of these changes. I don't know how
> >> difficult it would be to split the changes to a patch which literally
> >> copies (with renames) from shmem, followed by simplifying out the parts
> >> we don't want.  
> > 
> > It's a bit annoying as the new implementation is not based on shmem at
> > all, but if you think it helps the review, I can try what you're
> > suggesting. I mean, I'm not convinced it will be significantly easier
> > to review with this extra step, since the new logic is different enough
> > (especially when it comes to resource refcounting) that it needs a
> > careful review anyway (which you started doing here).  
> 
> I wasn't sure how much you had originally based it on shmem. I noticed
> some comments were copied over and in some places it was easy to match
> up. But in others it's much less clear.
> 
> If you haven't actually started from a direct copy of shmem then it's
> probably not going to be much clearer doing that as an extra step. It's
> just in places it looked like you had.

The reason both look similar has more to do with the fact they both
use shmem for their memory allocation than one being a copy of the
other. That's not to say I didn't pick bits and pieces here and there
(including comments), but it didn't start as a full copy followed by
incremental modifications.

> >>  
> >>> +	}
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
> >>> +{
> >>> +	int ret;
> >>> +
> >>> +	dma_resv_assert_held(bo->base.resv);
> >>> +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> >>> +
> >>> +	if (refcount_inc_not_zero(&bo->backing.pin_count))
> >>> +		return 0;
> >>> +
> >>> +	ret = panthor_gem_backing_get_pages_locked(bo);
> >>> +	if (!ret)
> >>> +		refcount_set(&bo->backing.pin_count, 1);
> >>> +
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
> >>> +{
> >>> +	dma_resv_assert_held(bo->base.resv);
> >>> +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> >>> +
> >>> +	/* We don't release anything when pin_count drops to zero.
> >>> +	 * Pages stay there until an explicit cleanup is requested.
> >>> +	 */
> >>> +	if (!refcount_dec_not_one(&bo->backing.pin_count))
> >>> +		refcount_set(&bo->backing.pin_count, 0);    
> >>
> >> Why not just refcount_dec()?  
> > 
> > Because refcount_dec() complains when it's passed a value that's less
> > than 2. The rational being that you need to do something special
> > (release resources) when you reach zero. In our case we don't, because
> > pages are lazily reclaimed, so we just set the counter back to zero.  
> 
> Ah, yes I'd misread the "old <= 1" check as "old < 1". Hmm, I dislike it
> because it's breaking the atomicity - if another thread does a increment
> between the two operations then we lose a reference count.

I don't think we do, because any 0 <-> 1 transition needs to happen
with the resv lock held (see the dma_resv_assert_held() in both
panthor_gem_backing_unpin_locked() and
panthor_gem_backing_pin_locked()).

> 
> It does make me think that perhaps the refcount APIs are not designed
> for this case and perhaps we should just use atomics directly.

It's the lazy/deferred put_pages() that makes it look weird I think,
but for the rest, refcount_t looks like the right tool (!locked variants
and even _pin_locked() look sane).

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 14:39     ` Boris Brezillon
  2026-01-12 15:19       ` Alice Ryhl
@ 2026-01-12 16:49       ` Steven Price
  2026-01-12 16:59         ` Boris Brezillon
  1 sibling, 1 reply; 62+ messages in thread
From: Steven Price @ 2026-01-12 16:49 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 12/01/2026 14:39, Boris Brezillon wrote:
> On Mon, 12 Jan 2026 12:33:33 +0000
> Steven Price <steven.price@arm.com> wrote:
> 
>> On 09/01/2026 13:08, Boris Brezillon wrote:
>>> This will be used to order things by reclaimability.
>>>
>>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
>>> ---
>>>  drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
>>>  drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
>>>  2 files changed, 45 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
>>> index 44f05bd957e7..458d22380e96 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
>>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
>>> @@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
>>>  	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
>>>  			  refcount_read(&bo->cmap.vaddr_use_count));
>>>  	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
>>> +	drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
>>>  }
>>>  
>>>  static int panthor_gem_pin_locked(struct drm_gem_object *obj)
>>> @@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
>>>  	if (is_cow_mapping(vma->vm_flags))
>>>  		return -EINVAL;
>>>  
>>> +	if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
>>> +		dma_resv_lock(obj->resv, NULL);
>>> +		if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
>>> +			refcount_set(&bo->cmap.mmap_count, 1);
>>> +		dma_resv_unlock(obj->resv);
>>> +	}
>>> +
>>>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
>>>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
>>>  	if (should_map_wc(bo))
>>> @@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
>>>  	return blocking_page_setup(vmf, bo, page_offset, true);
>>>  }
>>>  
>>> +static void panthor_gem_vm_open(struct vm_area_struct *vma)
>>> +{
>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
>>> +
>>> +	/* mmap_count must have been incremented at mmap time, so it can't be
>>> +	 * zero here.
>>> +	 */
>>> +	if (!drm_gem_is_imported(&bo->base))
>>> +		drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
>>> +
>>> +	drm_gem_vm_open(vma);
>>> +}
>>> +
>>> +static void panthor_gem_vm_close(struct vm_area_struct *vma)
>>> +{
>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
>>> +
>>> +	if (drm_gem_is_imported(&bo->base))
>>> +		goto out;
>>> +
>>> +	if (refcount_dec_not_one(&bo->cmap.mmap_count))
>>> +		goto out;
>>> +
>>> +	dma_resv_lock(bo->base.resv, NULL);
>>> +	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
>>> +		refcount_set(&bo->cmap.mmap_count, 0);
>>> +	dma_resv_unlock(bo->base.resv);  
>>
>> I don't think this logic is safe. Holding the resv_lock doesn't protect
>> against another thread doing a refcount_inc_not_zero() without holding
>> the lock.
>>
>> I think you can just replace the if() part with a refcount_dec() call,
>> the lock AFAICT is needed because the following patch wants to be sure
>> that !!mmap_count is stable when resv_lock is held.
> 
> I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
> complaining :P.

Yeah, I misread the refcount_dec() code the first time I looked at it.

>>
>> I also feel you should invert the conditino for refcount_dec_not_one,
>> leading to the following which I feel is easier to read:
>>
>> static void panthor_gem_vm_close(struct vm_area_struct *vma)
>> {
>> 	[...]
>>
>> 	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
>> 		dma_resv_lock(bo->base.resv, NULL);
>> 		refcount_dec(&bo->cmap.mmap_count);
>> 		dma_resv_unlock(bo->base.resv);
>> 	}
> 
> The best I can do is:
> 
>  	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
>  		dma_resv_lock(bo->base.resv, NULL);
>  		if (!refcount_dec_not_one(&bo->cmap.mmap_count))

The problem is here - if another thread does an increment from 1 to 2 at
this point then we lose a reference count. And since you don't have to
hold the lock for that we have a problem.

> 			refcount_set(&bo->cmap.mmap_count, 0);
>  		dma_resv_unlock(bo->base.resv);
>  	}
> 
> so we only take the lock when absolutely needed, but the 1 -> 0
> transition still has to be done with "if (dec_not_one) set(0)".

It's not the holding of the lock that's concerning me - it's the fact
that the increment part might not be holding the lock.

Your suggestion of "(void)refcount_dec_and_test()" would work, but it's
rather abusing the API. I can't help feeling this points to the refcount
API being the wrong thing to be using...

Thanks,
Steve

>>
>> 	drm_gem_object_put(&bo->base);
>> }


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap()
  2026-01-12 16:41       ` Steven Price
@ 2026-01-12 16:50         ` Boris Brezillon
  0 siblings, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 16:50 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Mon, 12 Jan 2026 16:41:41 +0000
Steven Price <steven.price@arm.com> wrote:

> On 12/01/2026 14:32, Boris Brezillon wrote:
> > On Mon, 12 Jan 2026 12:15:13 +0000
> > Steven Price <steven.price@arm.com> wrote:
> >   
> >> On 09/01/2026 13:07, Boris Brezillon wrote:  
> >>> Defer pages allocation until their first access.
> >>>
> >>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> >>> ---
> >>>  drivers/gpu/drm/panthor/panthor_gem.c | 119 ++++++++++++++++----------
> >>>  1 file changed, 75 insertions(+), 44 deletions(-)
> >>>
> >>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> >>> index 0e52c7a07c87..44f05bd957e7 100644
> >>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> >>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> >>> @@ -600,15 +600,6 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
> >>>  	if (is_cow_mapping(vma->vm_flags))
> >>>  		return -EINVAL;
> >>>  
> >>> -	dma_resv_lock(obj->resv, NULL);
> >>> -	ret = panthor_gem_backing_get_pages_locked(bo);
> >>> -	if (!ret)
> >>> -		ret = panthor_gem_prep_for_cpu_map_locked(bo);
> >>> -	dma_resv_unlock(obj->resv);
> >>> -
> >>> -	if (ret)
> >>> -		return ret;
> >>> -
> >>>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> >>>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> >>>  	if (should_map_wc(bo))
> >>> @@ -628,82 +619,122 @@ static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
> >>>  	return res;
> >>>  }
> >>>  
> >>> -static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
> >>> +static vm_fault_t insert_page(struct vm_fault *vmf, struct page *page)
> >>>  {
> >>> +	struct vm_area_struct *vma = vmf->vma;
> >>> +	vm_fault_t ret;
> >>> +
> >>>  #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
> >>>  	unsigned long pfn = page_to_pfn(page);
> >>>  	unsigned long paddr = pfn << PAGE_SHIFT;
> >>> -	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
> >>> +	bool aligned = (vmf->address & ~PMD_MASK) == (paddr & ~PMD_MASK);
> >>>  
> >>>  	if (aligned &&
> >>>  	    pmd_none(*vmf->pmd) &&
> >>>  	    folio_test_pmd_mappable(page_folio(page))) {
> >>>  		pfn &= PMD_MASK >> PAGE_SHIFT;
> >>> -		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
> >>> -			return true;
> >>> +		ret = vmf_insert_pfn_pmd(vmf, pfn, false);
> >>> +		if (ret == VM_FAULT_NOPAGE)
> >>> +			return VM_FAULT_NOPAGE;
> >>>  	}
> >>>  #endif
> >>>  
> >>> -	return false;
> >>> +	return vmf_insert_pfn(vma, vmf->address, page_to_pfn(page));
> >>>  }
> >>>  
> >>> -static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> >>> +static vm_fault_t nonblocking_page_setup(struct vm_fault *vmf, pgoff_t page_offset)
> >>>  {
> >>>  	struct vm_area_struct *vma = vmf->vma;
> >>> -	struct drm_gem_object *obj = vma->vm_private_data;
> >>>  	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> >>> -	loff_t num_pages = obj->size >> PAGE_SHIFT;
> >>>  	vm_fault_t ret;
> >>> -	pgoff_t page_offset;
> >>> -	unsigned long pfn;
> >>>  
> >>> -	/* Offset to faulty address in the VMA. */
> >>> -	page_offset = vmf->pgoff - vma->vm_pgoff;
> >>> +	if (!dma_resv_trylock(bo->base.resv))
> >>> +		return VM_FAULT_RETRY;
> >>>  
> >>> -	dma_resv_lock(bo->base.resv, NULL);
> >>> +	if (bo->backing.pages)
> >>> +		ret = insert_page(vmf, bo->backing.pages[page_offset]);
> >>> +	else
> >>> +		ret = VM_FAULT_RETRY;
> >>>  
> >>> -	if (page_offset >= num_pages ||
> >>> -	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
> >>> -		ret = VM_FAULT_SIGBUS;
> >>> -		goto out;
> >>> +	dma_resv_unlock(bo->base.resv);
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
> >>> +				      struct panthor_gem_object *bo,
> >>> +				      pgoff_t page_offset, bool mmap_lock_held)
> >>> +{
> >>> +	vm_fault_t ret;
> >>> +	int err;
> >>> +
> >>> +	if (vmf->flags & FAULT_FLAG_INTERRUPTIBLE) {
> >>> +		err = dma_resv_lock_interruptible(bo->base.resv, NULL);
> >>> +		if (err)
> >>> +			return mmap_lock_held ? VM_FAULT_NOPAGE : VM_FAULT_RETRY;    
> >>
> >> I'm not sure about this. First FAULT_FLAG_INTERRUPTIBLE is currently
> >> only used by userfaultfd AFAICT.  
> > 
> > And GUP, which admittedly, only seems possible if one tries to map a
> > userpage in kernel space, and we don't support that (yet?).  
> 
> Well unless my grepping is wrong, GUP is the one place that sets it and
> userfaultfd is the one place that consumes it.

Indeed.

> 
> As special as Mali is, I'm not sure why we're doing something that no
> other driver is?

Fair enough, I'll drop this "if (interruptible)" block.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 16:49       ` Steven Price
@ 2026-01-12 16:59         ` Boris Brezillon
  2026-01-12 17:10           ` Steven Price
  0 siblings, 1 reply; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 16:59 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Mon, 12 Jan 2026 16:49:33 +0000
Steven Price <steven.price@arm.com> wrote:

> On 12/01/2026 14:39, Boris Brezillon wrote:
> > On Mon, 12 Jan 2026 12:33:33 +0000
> > Steven Price <steven.price@arm.com> wrote:
> >   
> >> On 09/01/2026 13:08, Boris Brezillon wrote:  
> >>> This will be used to order things by reclaimability.
> >>>
> >>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> >>> ---
> >>>  drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
> >>>  drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
> >>>  2 files changed, 45 insertions(+), 2 deletions(-)
> >>>
> >>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> >>> index 44f05bd957e7..458d22380e96 100644
> >>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> >>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> >>> @@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
> >>>  	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
> >>>  			  refcount_read(&bo->cmap.vaddr_use_count));
> >>>  	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> >>> +	drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
> >>>  }
> >>>  
> >>>  static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> >>> @@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
> >>>  	if (is_cow_mapping(vma->vm_flags))
> >>>  		return -EINVAL;
> >>>  
> >>> +	if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
> >>> +		dma_resv_lock(obj->resv, NULL);
> >>> +		if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
> >>> +			refcount_set(&bo->cmap.mmap_count, 1);
> >>> +		dma_resv_unlock(obj->resv);
> >>> +	}
> >>> +
> >>>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> >>>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> >>>  	if (should_map_wc(bo))
> >>> @@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> >>>  	return blocking_page_setup(vmf, bo, page_offset, true);
> >>>  }
> >>>  
> >>> +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> >>> +{
> >>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> >>> +
> >>> +	/* mmap_count must have been incremented at mmap time, so it can't be
> >>> +	 * zero here.
> >>> +	 */
> >>> +	if (!drm_gem_is_imported(&bo->base))
> >>> +		drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
> >>> +
> >>> +	drm_gem_vm_open(vma);
> >>> +}
> >>> +
> >>> +static void panthor_gem_vm_close(struct vm_area_struct *vma)
> >>> +{
> >>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> >>> +
> >>> +	if (drm_gem_is_imported(&bo->base))
> >>> +		goto out;
> >>> +
> >>> +	if (refcount_dec_not_one(&bo->cmap.mmap_count))
> >>> +		goto out;
> >>> +
> >>> +	dma_resv_lock(bo->base.resv, NULL);
> >>> +	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> >>> +		refcount_set(&bo->cmap.mmap_count, 0);
> >>> +	dma_resv_unlock(bo->base.resv);    
> >>
> >> I don't think this logic is safe. Holding the resv_lock doesn't protect
> >> against another thread doing a refcount_inc_not_zero() without holding
> >> the lock.
> >>
> >> I think you can just replace the if() part with a refcount_dec() call,
> >> the lock AFAICT is needed because the following patch wants to be sure
> >> that !!mmap_count is stable when resv_lock is held.  
> > 
> > I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
> > complaining :P.  
> 
> Yeah, I misread the refcount_dec() code the first time I looked at it.
> 
> >>
> >> I also feel you should invert the conditino for refcount_dec_not_one,
> >> leading to the following which I feel is easier to read:
> >>
> >> static void panthor_gem_vm_close(struct vm_area_struct *vma)
> >> {
> >> 	[...]
> >>
> >> 	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> >> 		dma_resv_lock(bo->base.resv, NULL);
> >> 		refcount_dec(&bo->cmap.mmap_count);
> >> 		dma_resv_unlock(bo->base.resv);
> >> 	}  
> > 
> > The best I can do is:
> > 
> >  	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> >  		dma_resv_lock(bo->base.resv, NULL);
> >  		if (!refcount_dec_not_one(&bo->cmap.mmap_count))  
> 
> The problem is here - if another thread does an increment from 1 to 2 at
> this point then we lose a reference count. And since you don't have to
> hold the lock for that we have a problem.

Okay, I see what you mean now. I was considering 0 -> 1, not 1 -> 2. So
we really need a

		if (refcount_dec_and_test(&bo->cmap.mmap_count))
			/* Do nothing */;

> 
> > 			refcount_set(&bo->cmap.mmap_count, 0);
> >  		dma_resv_unlock(bo->base.resv);
> >  	}
> > 
> > so we only take the lock when absolutely needed, but the 1 -> 0
> > transition still has to be done with "if (dec_not_one) set(0)".  
> 
> It's not the holding of the lock that's concerning me - it's the fact
> that the increment part might not be holding the lock.
> 
> Your suggestion of "(void)refcount_dec_and_test()" would work, but it's
> rather abusing the API. I can't help feeling this points to the refcount
> API being the wrong thing to be using...
> 
> Thanks,
> Steve
> 
> >>
> >> 	drm_gem_object_put(&bo->base);
> >> }  
> 


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 16:59         ` Boris Brezillon
@ 2026-01-12 17:10           ` Steven Price
  2026-01-12 17:18             ` Boris Brezillon
  2026-01-13 12:26             ` Boris Brezillon
  0 siblings, 2 replies; 62+ messages in thread
From: Steven Price @ 2026-01-12 17:10 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 12/01/2026 16:59, Boris Brezillon wrote:
> On Mon, 12 Jan 2026 16:49:33 +0000
> Steven Price <steven.price@arm.com> wrote:
> 
>> On 12/01/2026 14:39, Boris Brezillon wrote:
>>> On Mon, 12 Jan 2026 12:33:33 +0000
>>> Steven Price <steven.price@arm.com> wrote:
>>>   
>>>> On 09/01/2026 13:08, Boris Brezillon wrote:  
>>>>> This will be used to order things by reclaimability.
>>>>>
>>>>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
>>>>> ---
>>>>>  drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
>>>>>  drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
>>>>>  2 files changed, 45 insertions(+), 2 deletions(-)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
>>>>> index 44f05bd957e7..458d22380e96 100644
>>>>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
>>>>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
>>>>> @@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
>>>>>  	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
>>>>>  			  refcount_read(&bo->cmap.vaddr_use_count));
>>>>>  	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
>>>>> +	drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
>>>>>  }
>>>>>  
>>>>>  static int panthor_gem_pin_locked(struct drm_gem_object *obj)
>>>>> @@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
>>>>>  	if (is_cow_mapping(vma->vm_flags))
>>>>>  		return -EINVAL;
>>>>>  
>>>>> +	if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
>>>>> +		dma_resv_lock(obj->resv, NULL);
>>>>> +		if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
>>>>> +			refcount_set(&bo->cmap.mmap_count, 1);
>>>>> +		dma_resv_unlock(obj->resv);
>>>>> +	}
>>>>> +
>>>>>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
>>>>>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
>>>>>  	if (should_map_wc(bo))
>>>>> @@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
>>>>>  	return blocking_page_setup(vmf, bo, page_offset, true);
>>>>>  }
>>>>>  
>>>>> +static void panthor_gem_vm_open(struct vm_area_struct *vma)
>>>>> +{
>>>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
>>>>> +
>>>>> +	/* mmap_count must have been incremented at mmap time, so it can't be
>>>>> +	 * zero here.
>>>>> +	 */
>>>>> +	if (!drm_gem_is_imported(&bo->base))
>>>>> +		drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
>>>>> +
>>>>> +	drm_gem_vm_open(vma);
>>>>> +}
>>>>> +
>>>>> +static void panthor_gem_vm_close(struct vm_area_struct *vma)
>>>>> +{
>>>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
>>>>> +
>>>>> +	if (drm_gem_is_imported(&bo->base))
>>>>> +		goto out;
>>>>> +
>>>>> +	if (refcount_dec_not_one(&bo->cmap.mmap_count))
>>>>> +		goto out;
>>>>> +
>>>>> +	dma_resv_lock(bo->base.resv, NULL);
>>>>> +	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
>>>>> +		refcount_set(&bo->cmap.mmap_count, 0);
>>>>> +	dma_resv_unlock(bo->base.resv);    
>>>>
>>>> I don't think this logic is safe. Holding the resv_lock doesn't protect
>>>> against another thread doing a refcount_inc_not_zero() without holding
>>>> the lock.
>>>>
>>>> I think you can just replace the if() part with a refcount_dec() call,
>>>> the lock AFAICT is needed because the following patch wants to be sure
>>>> that !!mmap_count is stable when resv_lock is held.  
>>>
>>> I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
>>> complaining :P.  
>>
>> Yeah, I misread the refcount_dec() code the first time I looked at it.
>>
>>>>
>>>> I also feel you should invert the conditino for refcount_dec_not_one,
>>>> leading to the following which I feel is easier to read:
>>>>
>>>> static void panthor_gem_vm_close(struct vm_area_struct *vma)
>>>> {
>>>> 	[...]
>>>>
>>>> 	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
>>>> 		dma_resv_lock(bo->base.resv, NULL);
>>>> 		refcount_dec(&bo->cmap.mmap_count);
>>>> 		dma_resv_unlock(bo->base.resv);
>>>> 	}  
>>>
>>> The best I can do is:
>>>
>>>  	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
>>>  		dma_resv_lock(bo->base.resv, NULL);
>>>  		if (!refcount_dec_not_one(&bo->cmap.mmap_count))  
>>
>> The problem is here - if another thread does an increment from 1 to 2 at
>> this point then we lose a reference count. And since you don't have to
>> hold the lock for that we have a problem.
> 
> Okay, I see what you mean now. I was considering 0 -> 1, not 1 -> 2. So
> we really need a
> 
> 		if (refcount_dec_and_test(&bo->cmap.mmap_count))
> 			/* Do nothing */;

Yes - I was just writing a longer email to say that ;)

I'm not sure if there's a better way of writing that - it does seem like
a missing function in the refcount API. I just worry it points to
refcounts being the wrong thing to use. But other than the missing
function the refcount API does work fine here.

Thanks,
Steve

>>
>>> 			refcount_set(&bo->cmap.mmap_count, 0);
>>>  		dma_resv_unlock(bo->base.resv);
>>>  	}
>>>
>>> so we only take the lock when absolutely needed, but the 1 -> 0
>>> transition still has to be done with "if (dec_not_one) set(0)".  
>>
>> It's not the holding of the lock that's concerning me - it's the fact
>> that the increment part might not be holding the lock.
>>
>> Your suggestion of "(void)refcount_dec_and_test()" would work, but it's
>> rather abusing the API. I can't help feeling this points to the refcount
>> API being the wrong thing to be using...
>>
>> Thanks,
>> Steve
>>
>>>>
>>>> 	drm_gem_object_put(&bo->base);
>>>> }  
>>
> 


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 17:10           ` Steven Price
@ 2026-01-12 17:18             ` Boris Brezillon
  2026-01-13 12:26             ` Boris Brezillon
  1 sibling, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-12 17:18 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Mon, 12 Jan 2026 17:10:12 +0000
Steven Price <steven.price@arm.com> wrote:

> On 12/01/2026 16:59, Boris Brezillon wrote:
> > On Mon, 12 Jan 2026 16:49:33 +0000
> > Steven Price <steven.price@arm.com> wrote:
> >   
> >> On 12/01/2026 14:39, Boris Brezillon wrote:  
> >>> On Mon, 12 Jan 2026 12:33:33 +0000
> >>> Steven Price <steven.price@arm.com> wrote:
> >>>     
> >>>> On 09/01/2026 13:08, Boris Brezillon wrote:    
> >>>>> This will be used to order things by reclaimability.
> >>>>>
> >>>>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> >>>>> ---
> >>>>>  drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
> >>>>>  drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
> >>>>>  2 files changed, 45 insertions(+), 2 deletions(-)
> >>>>>
> >>>>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> >>>>> index 44f05bd957e7..458d22380e96 100644
> >>>>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> >>>>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> >>>>> @@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
> >>>>>  	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
> >>>>>  			  refcount_read(&bo->cmap.vaddr_use_count));
> >>>>>  	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> >>>>> +	drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
> >>>>>  }
> >>>>>  
> >>>>>  static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> >>>>> @@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
> >>>>>  	if (is_cow_mapping(vma->vm_flags))
> >>>>>  		return -EINVAL;
> >>>>>  
> >>>>> +	if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
> >>>>> +		dma_resv_lock(obj->resv, NULL);
> >>>>> +		if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
> >>>>> +			refcount_set(&bo->cmap.mmap_count, 1);
> >>>>> +		dma_resv_unlock(obj->resv);
> >>>>> +	}
> >>>>> +
> >>>>>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> >>>>>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> >>>>>  	if (should_map_wc(bo))
> >>>>> @@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> >>>>>  	return blocking_page_setup(vmf, bo, page_offset, true);
> >>>>>  }
> >>>>>  
> >>>>> +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> >>>>> +{
> >>>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> >>>>> +
> >>>>> +	/* mmap_count must have been incremented at mmap time, so it can't be
> >>>>> +	 * zero here.
> >>>>> +	 */
> >>>>> +	if (!drm_gem_is_imported(&bo->base))
> >>>>> +		drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
> >>>>> +
> >>>>> +	drm_gem_vm_open(vma);
> >>>>> +}
> >>>>> +
> >>>>> +static void panthor_gem_vm_close(struct vm_area_struct *vma)
> >>>>> +{
> >>>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> >>>>> +
> >>>>> +	if (drm_gem_is_imported(&bo->base))
> >>>>> +		goto out;
> >>>>> +
> >>>>> +	if (refcount_dec_not_one(&bo->cmap.mmap_count))
> >>>>> +		goto out;
> >>>>> +
> >>>>> +	dma_resv_lock(bo->base.resv, NULL);
> >>>>> +	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> >>>>> +		refcount_set(&bo->cmap.mmap_count, 0);
> >>>>> +	dma_resv_unlock(bo->base.resv);      
> >>>>
> >>>> I don't think this logic is safe. Holding the resv_lock doesn't protect
> >>>> against another thread doing a refcount_inc_not_zero() without holding
> >>>> the lock.
> >>>>
> >>>> I think you can just replace the if() part with a refcount_dec() call,
> >>>> the lock AFAICT is needed because the following patch wants to be sure
> >>>> that !!mmap_count is stable when resv_lock is held.    
> >>>
> >>> I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
> >>> complaining :P.    
> >>
> >> Yeah, I misread the refcount_dec() code the first time I looked at it.
> >>  
> >>>>
> >>>> I also feel you should invert the conditino for refcount_dec_not_one,
> >>>> leading to the following which I feel is easier to read:
> >>>>
> >>>> static void panthor_gem_vm_close(struct vm_area_struct *vma)
> >>>> {
> >>>> 	[...]
> >>>>
> >>>> 	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> >>>> 		dma_resv_lock(bo->base.resv, NULL);
> >>>> 		refcount_dec(&bo->cmap.mmap_count);
> >>>> 		dma_resv_unlock(bo->base.resv);
> >>>> 	}    
> >>>
> >>> The best I can do is:
> >>>
> >>>  	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> >>>  		dma_resv_lock(bo->base.resv, NULL);
> >>>  		if (!refcount_dec_not_one(&bo->cmap.mmap_count))    
> >>
> >> The problem is here - if another thread does an increment from 1 to 2 at
> >> this point then we lose a reference count. And since you don't have to
> >> hold the lock for that we have a problem.  
> > 
> > Okay, I see what you mean now. I was considering 0 -> 1, not 1 -> 2. So
> > we really need a
> > 
> > 		if (refcount_dec_and_test(&bo->cmap.mmap_count))
> > 			/* Do nothing */;  
> 
> Yes - I was just writing a longer email to say that ;)

Note that as soon as the shrinker is introduced, there's stuff to put
in those if (refcount_dec_and_test(&bo->cmap.mmap_count)) blocks, so
it's not that terrible in the end.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO
  2026-01-12 17:10           ` Steven Price
  2026-01-12 17:18             ` Boris Brezillon
@ 2026-01-13 12:26             ` Boris Brezillon
  1 sibling, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-13 12:26 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Mon, 12 Jan 2026 17:10:12 +0000
Steven Price <steven.price@arm.com> wrote:

> On 12/01/2026 16:59, Boris Brezillon wrote:
> > On Mon, 12 Jan 2026 16:49:33 +0000
> > Steven Price <steven.price@arm.com> wrote:
> >   
> >> On 12/01/2026 14:39, Boris Brezillon wrote:  
> >>> On Mon, 12 Jan 2026 12:33:33 +0000
> >>> Steven Price <steven.price@arm.com> wrote:
> >>>     
> >>>> On 09/01/2026 13:08, Boris Brezillon wrote:    
> >>>>> This will be used to order things by reclaimability.
> >>>>>
> >>>>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> >>>>> ---
> >>>>>  drivers/gpu/drm/panthor/panthor_gem.c | 44 +++++++++++++++++++++++++--
> >>>>>  drivers/gpu/drm/panthor/panthor_gem.h |  3 ++
> >>>>>  2 files changed, 45 insertions(+), 2 deletions(-)
> >>>>>
> >>>>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> >>>>> index 44f05bd957e7..458d22380e96 100644
> >>>>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> >>>>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> >>>>> @@ -484,6 +484,7 @@ static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
> >>>>>  	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
> >>>>>  			  refcount_read(&bo->cmap.vaddr_use_count));
> >>>>>  	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> >>>>> +	drm_printf_indent(p, indent, "mmap_count=%u\n", refcount_read(&bo->cmap.mmap_count));
> >>>>>  }
> >>>>>  
> >>>>>  static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> >>>>> @@ -600,6 +601,13 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
> >>>>>  	if (is_cow_mapping(vma->vm_flags))
> >>>>>  		return -EINVAL;
> >>>>>  
> >>>>> +	if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
> >>>>> +		dma_resv_lock(obj->resv, NULL);
> >>>>> +		if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
> >>>>> +			refcount_set(&bo->cmap.mmap_count, 1);
> >>>>> +		dma_resv_unlock(obj->resv);
> >>>>> +	}
> >>>>> +
> >>>>>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> >>>>>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> >>>>>  	if (should_map_wc(bo))
> >>>>> @@ -732,10 +740,42 @@ static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> >>>>>  	return blocking_page_setup(vmf, bo, page_offset, true);
> >>>>>  }
> >>>>>  
> >>>>> +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> >>>>> +{
> >>>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> >>>>> +
> >>>>> +	/* mmap_count must have been incremented at mmap time, so it can't be
> >>>>> +	 * zero here.
> >>>>> +	 */
> >>>>> +	if (!drm_gem_is_imported(&bo->base))
> >>>>> +		drm_WARN_ON(bo->base.dev, !refcount_inc_not_zero(&bo->cmap.mmap_count));
> >>>>> +
> >>>>> +	drm_gem_vm_open(vma);
> >>>>> +}
> >>>>> +
> >>>>> +static void panthor_gem_vm_close(struct vm_area_struct *vma)
> >>>>> +{
> >>>>> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> >>>>> +
> >>>>> +	if (drm_gem_is_imported(&bo->base))
> >>>>> +		goto out;
> >>>>> +
> >>>>> +	if (refcount_dec_not_one(&bo->cmap.mmap_count))
> >>>>> +		goto out;
> >>>>> +
> >>>>> +	dma_resv_lock(bo->base.resv, NULL);
> >>>>> +	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> >>>>> +		refcount_set(&bo->cmap.mmap_count, 0);
> >>>>> +	dma_resv_unlock(bo->base.resv);      
> >>>>
> >>>> I don't think this logic is safe. Holding the resv_lock doesn't protect
> >>>> against another thread doing a refcount_inc_not_zero() without holding
> >>>> the lock.
> >>>>
> >>>> I think you can just replace the if() part with a refcount_dec() call,
> >>>> the lock AFAICT is needed because the following patch wants to be sure
> >>>> that !!mmap_count is stable when resv_lock is held.    
> >>>
> >>> I wish I could, but refcount_dec() doesn't let me do the 1 -> 0 without
> >>> complaining :P.    
> >>
> >> Yeah, I misread the refcount_dec() code the first time I looked at it.
> >>  
> >>>>
> >>>> I also feel you should invert the conditino for refcount_dec_not_one,
> >>>> leading to the following which I feel is easier to read:
> >>>>
> >>>> static void panthor_gem_vm_close(struct vm_area_struct *vma)
> >>>> {
> >>>> 	[...]
> >>>>
> >>>> 	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> >>>> 		dma_resv_lock(bo->base.resv, NULL);
> >>>> 		refcount_dec(&bo->cmap.mmap_count);
> >>>> 		dma_resv_unlock(bo->base.resv);
> >>>> 	}    
> >>>
> >>> The best I can do is:
> >>>
> >>>  	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> >>>  		dma_resv_lock(bo->base.resv, NULL);
> >>>  		if (!refcount_dec_not_one(&bo->cmap.mmap_count))    
> >>
> >> The problem is here - if another thread does an increment from 1 to 2 at
> >> this point then we lose a reference count. And since you don't have to
> >> hold the lock for that we have a problem.  
> > 
> > Okay, I see what you mean now. I was considering 0 -> 1, not 1 -> 2. So
> > we really need a
> > 
> > 		if (refcount_dec_and_test(&bo->cmap.mmap_count))
> > 			/* Do nothing */;  
> 
> Yes - I was just writing a longer email to say that ;)
> 
> I'm not sure if there's a better way of writing that - it does seem like
> a missing function in the refcount API. I just worry it points to
> refcounts being the wrong thing to use. But other than the missing
> function the refcount API does work fine here.

New version (with changes as fixup commits) is here [1] in case you want
to check it. I'll wait for more feedback on the shrinker implementation
before posting a v2.

[1]https://gitlab.freedesktop.org/bbrezillon/linux/-/commits/panthor-shrinker

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-09 13:08 ` [PATCH v1 9/9] drm/panthor: Add a GEM shrinker Boris Brezillon
@ 2026-01-14 15:05   ` Steven Price
  2026-01-15 10:50     ` Boris Brezillon
  2026-01-15 13:56   ` Akash Goel
  2026-01-21 11:49   ` Akash Goel
  2 siblings, 1 reply; 62+ messages in thread
From: Steven Price @ 2026-01-14 15:05 UTC (permalink / raw)
  To: Boris Brezillon, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Akash Goel, Rob Clark,
	Sean Paul, Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 09/01/2026 13:08, Boris Brezillon wrote:
> From: Akash Goel <akash.goel@arm.com>
> 
> This implementation is losely based on the MSM shrinker, and it's
> relying on the drm_gpuvm eviction/validation infrastructure.
> 
> Right now we only support swapout/eviction, but we could add an extra
> flag to specify when buffer content doesn't need to be preserved to
> avoid the swapout/swapin dance.
> 
> Locking is a bit of a nightmare, but using _trylock() all the way in
> the reclaim path seems to make lockdep happy. And yes, we might be
> missing opportunities to reclaim when the system is under heavy GPU
> load/heavy memory pressure/heavy GPU VM activity, but that's better
> than no reclaim at all.
> 
> Signed-off-by: Akash Goel <akash.goel@arm.com>
> Co-developed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>  drivers/gpu/drm/panthor/panthor_device.c |  11 +-
>  drivers/gpu/drm/panthor/panthor_device.h |  73 ++++
>  drivers/gpu/drm/panthor/panthor_gem.c    | 427 ++++++++++++++++++++++-
>  drivers/gpu/drm/panthor/panthor_gem.h    |  67 ++++
>  drivers/gpu/drm/panthor/panthor_mmu.c    | 338 +++++++++++++++++-
>  drivers/gpu/drm/panthor/panthor_mmu.h    |   8 +
>  6 files changed, 901 insertions(+), 23 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_device.c b/drivers/gpu/drm/panthor/panthor_device.c
> index 54fbb1aa07c5..bc62a498a8a8 100644
> --- a/drivers/gpu/drm/panthor/panthor_device.c
> +++ b/drivers/gpu/drm/panthor/panthor_device.c
> @@ -2,6 +2,7 @@
>  /* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
>  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>  /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>  
>  #include <linux/clk.h>
>  #include <linux/mm.h>
> @@ -122,6 +123,7 @@ void panthor_device_unplug(struct panthor_device *ptdev)
>  	panthor_sched_unplug(ptdev);
>  	panthor_fw_unplug(ptdev);
>  	panthor_mmu_unplug(ptdev);
> +	panthor_gem_shrinker_unplug(ptdev);
>  	panthor_gpu_unplug(ptdev);
>  	panthor_pwr_unplug(ptdev);
>  
> @@ -291,10 +293,14 @@ int panthor_device_init(struct panthor_device *ptdev)
>  	if (ret)
>  		goto err_unplug_gpu;
>  
> -	ret = panthor_mmu_init(ptdev);
> +	ret = panthor_gem_shrinker_init(ptdev);
>  	if (ret)
>  		goto err_unplug_gpu;
>  
> +	ret = panthor_mmu_init(ptdev);
> +	if (ret)
> +		goto err_unplug_shrinker;
> +
>  	ret = panthor_fw_init(ptdev);
>  	if (ret)
>  		goto err_unplug_mmu;
> @@ -326,6 +332,9 @@ int panthor_device_init(struct panthor_device *ptdev)
>  err_unplug_mmu:
>  	panthor_mmu_unplug(ptdev);
>  
> +err_unplug_shrinker:
> +	panthor_gem_shrinker_unplug(ptdev);
> +
>  err_unplug_gpu:
>  	panthor_gpu_unplug(ptdev);
>  
> diff --git a/drivers/gpu/drm/panthor/panthor_device.h b/drivers/gpu/drm/panthor/panthor_device.h
> index f35e52b9546a..bc348aa9634e 100644
> --- a/drivers/gpu/drm/panthor/panthor_device.h
> +++ b/drivers/gpu/drm/panthor/panthor_device.h
> @@ -14,6 +14,7 @@
>  #include <linux/spinlock.h>
>  
>  #include <drm/drm_device.h>
> +#include <drm/drm_gem.h>
>  #include <drm/drm_mm.h>
>  #include <drm/gpu_scheduler.h>
>  #include <drm/panthor_drm.h>
> @@ -157,6 +158,78 @@ struct panthor_device {
>  	/** @devfreq: Device frequency scaling management data. */
>  	struct panthor_devfreq *devfreq;
>  
> +	/** @reclaim: Reclaim related stuff */
> +	struct {
> +		/** @reclaim.shrinker: Shrinker instance */
> +		struct shrinker *shrinker;
> +
> +		/** @reclaim.lock: Lock protecting all LRUs */
> +		struct mutex lock;
> +
> +		/**
> +		 * @reclaim.unused: BOs with unused pages
> +		 *
> +		 * Basically all buffers that got mmapped, vmapped or GPU mapped and
> +		 * then unmapped. There should be no contention on these buffers,
> +		 * making them ideal to reclaim.
> +		 */
> +		struct drm_gem_lru unused;
> +
> +		/**
> +		 * @reclaim.mmapped: mmap()-ed buffers
> +		 *
> +		 * Those are relatively easy to reclaim since we don't need user
> +		 * agreement, we can simply teardown the mapping and let it fault on
> +		 * the next access.
> +		 */
> +		struct drm_gem_lru mmapped;
> +
> +		/**
> +		 * @reclaim.vms: VM LRU list
> +		 *
> +		 * VMs that have reclaimable BOs only mapped to a single VM are placed
> +		 * in this LRU. Reclaiming such BOs implies waiting for VM idleness
> +		 * (no in-flight GPU jobs targeting this VM), meaning we can't reclaim
> +		 * those if we're in a context where we can't block/sleep.
> +		 */
> +		struct list_head vms;

NIT: I would move this down below gpu_mapped_shared so that the three
(per-device) LRU lists for BOs are together.

> +
> +		/**
> +		 * @reclaim.gpu_mapped_shared: shared BO LRU list
> +		 *
> +		 * That's the most tricky BO type to reclaim, because it involves
> +		 * tearing down all mappings in all VMs where this BO is mapped,
> +		 * which increases the risk of contention and thus decreases the
> +		 * likeliness of success.
> +		 */
> +		struct drm_gem_lru gpu_mapped_shared;
> +
> +		/**
> +		 * @reclaim.gpu_mapped_count: Global counter of pages that are GPU mapped
> +		 *
> +		 * Allows us to get the number of reclaimable pages without walking
> +		 * the vms and gpu_mapped_shared LRUs.
> +		 */
> +		long gpu_mapped_count;
> +
> +		/**
> +		 * @reclaim.retry_count: Number of times we ran the shrinker without being
> +		 * able to reclaim stuff
> +		 *
> +		 * Used to stop scanning GEMs when too many attempts were made
> +		 * without progress.
> +		 */
> +		atomic_t retry_count;
> +
> +#ifdef CONFIG_DEBUG_FS
> +		/**
> +		 * @reclaim.nr_pages_reclaimed_on_last_scan: Number of pages reclaimed on the last
> +		 * shrinker scan
> +		 */
> +		unsigned long nr_pages_reclaimed_on_last_scan;
> +#endif
> +	} reclaim;
> +
>  	/** @unplug: Device unplug related fields. */
>  	struct {
>  		/** @lock: Lock used to serialize unplug operations. */
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index 458d22380e96..065956a1f0fb 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -2,8 +2,10 @@
>  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>  /* Copyright 2023 Collabora ltd. */
>  /* Copyright 2025 Amazon.com, Inc. or its affiliates */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>  
>  #include <linux/cleanup.h>
> +#include <linux/debugfs.h>
>  #include <linux/dma-buf.h>
>  #include <linux/dma-mapping.h>
>  #include <linux/err.h>
> @@ -12,6 +14,8 @@
>  
>  #include <drm/drm_debugfs.h>
>  #include <drm/drm_file.h>
> +#include <drm/drm_gpuvm.h>
> +#include <drm/drm_managed.h>
>  #include <drm/drm_prime.h>
>  #include <drm/drm_print.h>
>  #include <drm/panthor_drm.h>
> @@ -114,6 +118,98 @@ should_map_wc(struct panthor_gem_object *bo)
>  	return true;
>  }
>  
> +static bool is_gpu_mapped(struct panthor_gem_object *bo,
> +			  enum panthor_gem_reclaim_state *state)
> +{
> +	struct drm_gpuvm *vm = NULL;
> +	struct drm_gpuvm_bo *vm_bo;
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +		if (!vm) {
> +			*state = PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> +			vm = vm_bo->vm;
> +		} else if (vm != vm_bo->vm) {
> +			*state = PANTHOR_GEM_GPU_MAPPED_SHARED;
> +			break;
> +		}
> +	}
> +
> +	return !!vm;
> +}
> +
> +static enum panthor_gem_reclaim_state
> +panthor_gem_evaluate_reclaim_state_locked(struct panthor_gem_object *bo)
> +{
> +	enum panthor_gem_reclaim_state gpu_mapped_state;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +	lockdep_assert_held(&bo->base.gpuva.lock);
> +
> +	/* If pages have not been allocated, there's nothing to reclaim. */
> +	if (!bo->backing.pages)
> +		return PANTHOR_GEM_UNRECLAIMABLE;
> +
> +	/* If memory is pinned, we prevent reclaim. */
> +	if (refcount_read(&bo->backing.pin_count))
> +		return PANTHOR_GEM_UNRECLAIMABLE;
> +
> +	if (is_gpu_mapped(bo, &gpu_mapped_state))
> +		return gpu_mapped_state;
> +
> +	if (refcount_read(&bo->cmap.mmap_count) && bo->backing.pages)

Checking bo->backing.pages is redundant as !bo->backing.pages is checked
above.

> +		return PANTHOR_GEM_MMAPPED;
> +
> +	return PANTHOR_GEM_UNUSED;
> +}
> +
> +void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
> +					     enum panthor_gem_reclaim_state *old_statep)
> +{
> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> +	enum panthor_gem_reclaim_state old_state = bo->reclaim_state;
> +	enum panthor_gem_reclaim_state new_state;
> +	bool was_gpu_mapped, is_gpu_mapped;
> +
> +	if (old_statep)
> +		*old_statep = old_state;
> +
> +	new_state = panthor_gem_evaluate_reclaim_state_locked(bo);
> +	if (new_state == old_state)
> +		return;
> +
> +	was_gpu_mapped = old_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> +			 old_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> +	is_gpu_mapped = new_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> +			new_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> +
> +	if (is_gpu_mapped && !was_gpu_mapped)
> +		ptdev->reclaim.gpu_mapped_count += bo->base.size >> PAGE_SHIFT;
> +	else if (!is_gpu_mapped && was_gpu_mapped)
> +		ptdev->reclaim.gpu_mapped_count -= bo->base.size >> PAGE_SHIFT;
> +
> +	switch (new_state) {
> +	case PANTHOR_GEM_UNUSED:
> +		drm_gem_lru_move_tail(&ptdev->reclaim.unused, &bo->base);
> +		break;
> +	case PANTHOR_GEM_MMAPPED:
> +		drm_gem_lru_move_tail(&ptdev->reclaim.mmapped, &bo->base);
> +		break;
> +	case PANTHOR_GEM_GPU_MAPPED_PRIVATE:
> +		panthor_vm_update_bo_reclaim_lru_locked(bo);
> +		break;
> +	case PANTHOR_GEM_GPU_MAPPED_SHARED:
> +		drm_gem_lru_move_tail(&ptdev->reclaim.gpu_mapped_shared, &bo->base);
> +		break;
> +	case PANTHOR_GEM_UNRECLAIMABLE:
> +		drm_gem_lru_remove(&bo->base);
> +		break;
> +	default:

WARN()? We don't expect this to happen.

> +		break;
> +	}
> +
> +	bo->reclaim_state = new_state;
> +}
> +
>  static void
>  panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
>  {
> @@ -153,8 +249,12 @@ static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
>  		return 0;
>  
>  	ret = panthor_gem_backing_get_pages_locked(bo);
> -	if (!ret)
> +	if (!ret) {
>  		refcount_set(&bo->backing.pin_count, 1);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +	}
>  
>  	return ret;
>  }
> @@ -167,8 +267,12 @@ static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
>  	/* We don't release anything when pin_count drops to zero.
>  	 * Pages stay there until an explicit cleanup is requested.
>  	 */
> -	if (!refcount_dec_not_one(&bo->backing.pin_count))
> +	if (!refcount_dec_not_one(&bo->backing.pin_count)) {
>  		refcount_set(&bo->backing.pin_count, 0);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +	}
>  }
>  
>  static void
> @@ -531,6 +635,49 @@ void panthor_gem_unpin(struct panthor_gem_object *bo)
>  	dma_resv_unlock(bo->base.resv);
>  }
>  
> +int panthor_gem_swapin_locked(struct panthor_gem_object *bo)
> +{
> +	struct sg_table *sgt;
> +	int ret;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> +		return -EINVAL;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
> +		return -EINVAL;
> +
> +	ret = panthor_gem_backing_get_pages_locked(bo);
> +	if (ret)
> +		return ret;
> +
> +	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> +	if (IS_ERR(sgt))
> +		return PTR_ERR(sgt);
> +
> +	return 0;
> +}
> +
> +static void panthor_gem_evict_locked(struct panthor_gem_object *bo)
> +{
> +	dma_resv_assert_held(bo->base.resv);
> +	lockdep_assert_held(&bo->base.gpuva.lock);
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> +		return;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
> +		return;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> +		return;
> +
> +	panthor_gem_dev_map_cleanup(bo);
> +	panthor_gem_backing_cleanup(bo);
> +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> +}
> +
>  static struct sg_table *panthor_gem_get_sg_table(struct drm_gem_object *obj)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(obj);

Not in this diff, but in panthor_gem_dev_map_get_sgt_locked() we have a
comment which needs updating:

	/* Pages stay around after they've been allocated. At least that stands
	 * until we add a shrinker.
	 */

> @@ -692,6 +839,10 @@ static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
>  	} else {
>  		struct page *page = bo->backing.pages[page_offset];
>  
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +
>  		if (mmap_lock_held)
>  			ret = insert_page(vmf, page);
>  		else
> @@ -764,8 +915,12 @@ static void panthor_gem_vm_close(struct vm_area_struct *vma)
>  		goto out;
>  
>  	dma_resv_lock(bo->base.resv, NULL);
> -	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> +	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
>  		refcount_set(&bo->cmap.mmap_count, 0);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +	}
>  	dma_resv_unlock(bo->base.resv);
>  
>  out:
> @@ -801,6 +956,7 @@ panthor_gem_alloc_object(uint32_t flags)
>  	if (!bo)
>  		return ERR_PTR(-ENOMEM);
>  
> +	bo->reclaim_state = PANTHOR_GEM_UNRECLAIMABLE;
>  	bo->base.funcs = &panthor_gem_funcs;
>  	bo->flags = flags;
>  	mutex_init(&bo->label.lock);
> @@ -1041,11 +1197,13 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>   */
>  void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
>  {
> +	struct panthor_device *ptdev;
>  	struct panthor_vm *vm;
>  
>  	if (IS_ERR_OR_NULL(bo))
>  		return;
>  
> +	ptdev = container_of(bo->obj->dev, struct panthor_device, base);
>  	vm = bo->vm;
>  	panthor_kernel_bo_vunmap(bo);
>  
> @@ -1053,6 +1211,8 @@ void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
>  		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
>  	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
>  	panthor_vm_free_va(vm, &bo->va_node);
> +	if (vm == panthor_fw_vm(ptdev))
> +		panthor_gem_unpin(to_panthor_bo(bo->obj));
>  	drm_gem_object_put(bo->obj);
>  	panthor_vm_put(vm);
>  	kfree(bo);
> @@ -1101,6 +1261,12 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  
>  	kbo->obj = &bo->base;
>  
> +	if (vm == panthor_fw_vm(ptdev)) {
> +		ret = panthor_gem_pin(bo);
> +		if (ret)
> +			goto err_put_obj;
> +	}
> +
>  	panthor_gem_kernel_bo_set_label(kbo, name);
>  
>  	/* The system and GPU MMU page size might differ, which becomes a
> @@ -1112,7 +1278,7 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  	size = ALIGN(size, panthor_vm_page_size(vm));
>  	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
>  	if (ret)
> -		goto err_put_obj;
> +		goto err_unpin;
>  
>  	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
>  	if (ret)
> @@ -1124,6 +1290,10 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  err_free_va:
>  	panthor_vm_free_va(vm, &kbo->va_node);
>  
> +err_unpin:
> +	if (vm == panthor_fw_vm(ptdev))
> +		panthor_gem_unpin(bo);
> +
>  err_put_obj:
>  	drm_gem_object_put(&bo->base);
>  
> @@ -1132,6 +1302,223 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  	return ERR_PTR(ret);
>  }
>  
> +static bool can_swap(void)
> +{
> +	return get_nr_swap_pages() > 0;
> +}
> +
> +static bool can_block(struct shrink_control *sc)
> +{
> +	if (!(sc->gfp_mask & __GFP_DIRECT_RECLAIM))
> +		return false;
> +	return current_is_kswapd() || (sc->gfp_mask & __GFP_RECLAIM);
> +}
> +
> +static unsigned long
> +panthor_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
> +{
> +	struct panthor_device *ptdev = shrinker->private_data;
> +	unsigned long count;
> +
> +	/* We currently don't have a flag to tell when the content of a
> +	 * BO can be discarded.
> +	 */
> +	if (!can_swap())
> +		return 0;
> +
> +	count = ptdev->reclaim.unused.count;
> +	count += ptdev->reclaim.mmapped.count;
> +
> +	if (can_block(sc))
> +		count += ptdev->reclaim.gpu_mapped_count;
> +
> +	return count;
> +}
> +
> +static bool should_wait(enum panthor_gem_reclaim_state reclaim_state)
> +{
> +	return reclaim_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE ||
> +	       reclaim_state == PANTHOR_GEM_GPU_MAPPED_SHARED;
> +}
> +
> +bool panthor_gem_try_evict(struct drm_gem_object *obj,
> +			   struct ww_acquire_ctx *ticket)
> +{
> +	/*
> +	 * Track last locked entry for unwinding locks in error and
> +	 * success paths
> +	 */
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +	struct drm_gpuvm_bo *vm_bo, *last_locked = NULL;
> +	enum panthor_gem_reclaim_state old_state;
> +	int ret = 0;
> +
> +	/* To avoid potential lock ordering issue between bo_gpuva and
> +	 * mapping->i_mmap_rwsem, unmap the pages from CPU side before
> +	 * acquring the bo_gpuva lock. As the bo_resv lock is held, CPU
> +	 * page fault handler won't be able to map in the pages whilst
> +	 * eviction is in progress.
> +	 */
> +	drm_vma_node_unmap(&bo->base.vma_node, bo->base.dev->anon_inode->i_mapping);
> +
> +	/* We take this lock when walking the list to prevent
> +	 * insertion/deletion.
> +	 */
> +	/* We can only trylock in that path, because
> +	 * - allocation might happen while some of these locks are held
> +	 * - lock ordering is different in other paths
> +	 *     vm_resv -> bo_resv -> bo_gpuva
> +	 *     vs
> +	 *     bo_resv -> bo_gpuva -> vm_resv
> +	 *
> +	 * If we fail to lock that's fine, we back off and will get
> +	 * back to it later.
> +	 */
> +	if (!mutex_trylock(&bo->base.gpuva.lock))
> +		return false;
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +		struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> +
> +		if (resv == obj->resv)
> +			continue;
> +
> +		if (!dma_resv_trylock(resv)) {
> +			ret = -EDEADLK;
> +			goto out_unlock;
> +		}
> +
> +		last_locked = vm_bo;
> +	}
> +
> +	/* Update the state before trying to evict the buffer, if the state was
> +	 * updated to something that's harder to reclaim (higher value in the
> +	 * enum), skip it (will be processed when the relevant LRU is).
> +	 */
> +	panthor_gem_update_reclaim_state_locked(bo, &old_state);
> +	if (old_state < bo->reclaim_state) {
> +		ret = -EAGAIN;
> +		goto out_unlock;
> +	}
> +
> +	/* Wait was too long, skip. */
> +	if (should_wait(bo->reclaim_state) &&
> +	    dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP, false, 10) <= 0) {
> +		ret = -ETIMEDOUT;
> +		goto out_unlock;
> +	}
> +
> +	/* Couldn't teardown the GPU mappings? Skip. */
> +	ret = panthor_vm_evict_bo_mappings_locked(bo);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* If everything went fine, evict the object. */
> +	panthor_gem_evict_locked(bo);
> +
> +out_unlock:
> +	if (last_locked) {
> +		drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +			struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> +
> +			if (resv == obj->resv)
> +				continue;
> +
> +			dma_resv_unlock(resv);
> +
> +			if (last_locked == vm_bo)
> +				break;
> +		}
> +	}
> +	mutex_unlock(&bo->base.gpuva.lock);
> +
> +	return ret == 0;
> +}
> +
> +static unsigned long
> +panthor_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
> +{
> +	struct panthor_device *ptdev = shrinker->private_data;
> +	unsigned long remaining = 0;
> +	unsigned long freed = 0;
> +
> +	if (!can_swap())
> +		goto out;
> +
> +	freed += drm_gem_lru_scan(&ptdev->reclaim.unused,
> +				  sc->nr_to_scan - freed, &remaining,
> +				  panthor_gem_try_evict, NULL);
> +	if (freed >= sc->nr_to_scan)
> +		goto out;
> +
> +	freed += drm_gem_lru_scan(&ptdev->reclaim.mmapped,
> +				  sc->nr_to_scan - freed, &remaining,
> +				  panthor_gem_try_evict, NULL);
> +	if (freed >= sc->nr_to_scan)
> +		goto out;
> +
> +	freed += panthor_mmu_reclaim_priv_bos(ptdev, sc->nr_to_scan - freed,
> +					      &remaining, panthor_gem_try_evict);
> +	if (freed >= sc->nr_to_scan)
> +		goto out;
> +
> +	freed += drm_gem_lru_scan(&ptdev->reclaim.gpu_mapped_shared,
> +				  sc->nr_to_scan - freed, &remaining,
> +				  panthor_gem_try_evict, NULL);
> +
> +out:
> +#ifdef CONFIG_DEBUG_FS
> +	/* This is racy, but that's okay, because this is just debugfs
> +	 * reporting and doesn't need to be accurate.
> +	 */
> +	ptdev->reclaim.nr_pages_reclaimed_on_last_scan = freed;
> +#endif
> +
> +	/* If there are things to reclaim, try a couple times before giving up. */
> +	if (!freed && remaining > 0 &&
> +	    atomic_inc_return(&ptdev->reclaim.retry_count) < 2)
> +		return 0;
> +
> +	/* There's nothing left to reclaim, or the resource are contended. Give up now. */
> +	atomic_set(&ptdev->reclaim.retry_count, 0);

I think you're missing:

	if (freed)
		return freed;

(or similar)

> +	return SHRINK_STOP;
> +}
> +
> +int panthor_gem_shrinker_init(struct panthor_device *ptdev)
> +{
> +	struct shrinker *shrinker;
> +
> +	drmm_mutex_init(&ptdev->base, &ptdev->reclaim.lock);
> +	INIT_LIST_HEAD(&ptdev->reclaim.vms);
> +	drm_gem_lru_init(&ptdev->reclaim.unused, &ptdev->reclaim.lock);
> +	drm_gem_lru_init(&ptdev->reclaim.mmapped, &ptdev->reclaim.lock);
> +	drm_gem_lru_init(&ptdev->reclaim.gpu_mapped_shared, &ptdev->reclaim.lock);
> +	ptdev->reclaim.gpu_mapped_count = 0;
> +
> +	/* Teach lockdep about lock ordering wrt. shrinker: */
> +	fs_reclaim_acquire(GFP_KERNEL);
> +	might_lock(&ptdev->reclaim.lock);
> +	fs_reclaim_release(GFP_KERNEL);
> +
> +	shrinker = shrinker_alloc(0, "drm-panthor-gem");
> +	if (!shrinker)
> +		return -ENOMEM;
> +
> +	shrinker->count_objects = panthor_gem_shrinker_count;
> +	shrinker->scan_objects = panthor_gem_shrinker_scan;
> +	shrinker->private_data = ptdev;
> +	ptdev->reclaim.shrinker = shrinker;
> +
> +	shrinker_register(shrinker);
> +	return 0;
> +}
> +
> +void panthor_gem_shrinker_unplug(struct panthor_device *ptdev)
> +{
> +	if (ptdev->reclaim.shrinker)
> +		shrinker_free(ptdev->reclaim.shrinker);
> +}
> +
>  #ifdef CONFIG_DEBUG_FS
>  struct gem_size_totals {
>  	size_t size;
> @@ -1250,10 +1637,42 @@ static struct drm_info_list panthor_gem_debugfs_list[] = {
>  	{ "gems", panthor_gem_show_bos, 0, NULL },
>  };
>  
> +static int shrink_get(void *data, u64 *val)
> +{
> +	struct panthor_device *ptdev =
> +		container_of(data, struct panthor_device, base);
> +
> +	*val = ptdev->reclaim.nr_pages_reclaimed_on_last_scan;
> +	return 0;
> +}
> +
> +static int shrink_set(void *data, u64 val)
> +{
> +	struct panthor_device *ptdev =
> +		container_of(data, struct panthor_device, base);
> +	struct shrink_control sc = {
> +		.gfp_mask = GFP_KERNEL,
> +		.nr_to_scan = val,
> +	};
> +
> +	fs_reclaim_acquire(GFP_KERNEL);
> +	if (ptdev->reclaim.shrinker)
> +		panthor_gem_shrinker_scan(ptdev->reclaim.shrinker, &sc);
> +	fs_reclaim_release(GFP_KERNEL);
> +
> +	return 0;
> +}

Do you have some test to drive this? My immediate thought was that it
would be nice (for manual testing at least) to printk the return value
from panthor_gem_shrinker_scan(). But we probably wouldn't actually need
nr_pages_reclaimed_on_last_scan if you could just read that from dmesg.
But I can see integrating that into a test might not be ideal.

> +
> +DEFINE_DEBUGFS_ATTRIBUTE(panthor_gem_debugfs_shrink_fops,
> +			 shrink_get, shrink_set,
> +			 "0x%08llx\n");
> +
>  void panthor_gem_debugfs_init(struct drm_minor *minor)
>  {
>  	drm_debugfs_create_files(panthor_gem_debugfs_list,
>  				 ARRAY_SIZE(panthor_gem_debugfs_list),
>  				 minor->debugfs_root, minor);
> +	debugfs_create_file("shrink", 0600, minor->debugfs_root,
> +			    minor->dev, &panthor_gem_debugfs_shrink_fops);
>  }
>  #endif
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
> index c0a18dca732c..6cb5b597ff1e 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.h
> +++ b/drivers/gpu/drm/panthor/panthor_gem.h
> @@ -1,6 +1,7 @@
>  /* SPDX-License-Identifier: GPL-2.0 or MIT */
>  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>  /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>  
>  #ifndef __PANTHOR_GEM_H__
>  #define __PANTHOR_GEM_H__
> @@ -93,6 +94,62 @@ struct panthor_gem_dev_map {
>  	struct sg_table *sgt;
>  };
>  
> +/**
> + * enum panthor_gem_reclaim_state - Reclaim state of a GEM object
> + *
> + * This is defined in descending reclaimability order and some part
> + * of the code depends on that.
> + */
> +enum panthor_gem_reclaim_state {
> +	/**
> +	 * @PANTHOR_GEM_UNUSED: GEM is currently unused
> +	 *
> +	 * This can happen when the GEM was previously vmap-ed, mmap-ed,
> +	 * and/or GPU mapped and got unmapped. Because pages are lazily
> +	 * returned to the shmem layer, we want to keep a list of such
> +	 * BOs, because they should be fairly easy to reclaim (no need
> +	 * to wait for GPU to be done, and no need to tear down user
> +	 * mappings either).
> +	 */
> +	PANTHOR_GEM_UNUSED,
> +
> +	/**
> +	 * @PANTHOR_GEM_MMAPPED: GEM is currently mmap-ed
> +	 *
> +	 * When a GEM has pages allocated and the mmap_count is > 0, the
> +	 * GEM is placed in the mmapped list. This comes right after
> +	 * unused because we can relatively easily tear down user mappings.
> +	 */
> +	PANTHOR_GEM_MMAPPED,
> +
> +	/**
> +	 * @PANTHOR_GEM_GPU_MAPPED_PRIVATE: GEM is GPU mapped to only one VM
> +	 *
> +	 * When a GEM is mapped to a single VM, reclaim requests have more
> +	 * chances to succeed, because we only need to synchronize against
> +	 * a single GPU context. This is more annoying than reclaiming
> +	 * mmap-ed pages still, because we have to wait for in-flight jobs
> +	 * to land, and we might not be able to acquire all necessary locks
> +	 * at reclaim time either.
> +	 */
> +	PANTHOR_GEM_GPU_MAPPED_PRIVATE,
> +
> +	/**
> +	 * @PANTHOR_GEM_GPU_MAPPED_SHARED: GEM is GPU mapped to multiple VMs
> +	 *
> +	 * Like PANTHOR_GEM_GPU_MAPPED_PRIVATE, but the synchronization across
> +	 * VMs makes such BOs harder to reclaim.
> +	 */
> +	PANTHOR_GEM_GPU_MAPPED_SHARED,
> +
> +	/**
> +	 * @PANTHOR_GEM_UNRECLAIMABLE: GEM can't be reclaimed
> +	 *
> +	 * Happens when the GEM memory is pinned.

Also the initial state when creating a GEM object (which I found
non-obvious at least).

> +	 */
> +	PANTHOR_GEM_UNRECLAIMABLE,
> +};
> +
>  /**
>   * struct panthor_gem_object - Driver specific GEM object.
>   */
> @@ -109,6 +166,9 @@ struct panthor_gem_object {
>  	/** @dmap: Device mapping state */
>  	struct panthor_gem_dev_map dmap;
>  
> +	/** @reclaim_state: Cached reclaim state */
> +	enum panthor_gem_reclaim_state reclaim_state;
> +
>  	/**
>  	 * @exclusive_vm_root_gem: Root GEM of the exclusive VM this GEM object
>  	 * is attached to.
> @@ -190,6 +250,13 @@ struct sg_table *
>  panthor_gem_get_dev_sgt(struct panthor_gem_object *bo);
>  int panthor_gem_pin(struct panthor_gem_object *bo);
>  void panthor_gem_unpin(struct panthor_gem_object *bo);
> +int panthor_gem_swapin_locked(struct panthor_gem_object *bo);
> +void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
> +					     enum panthor_gem_reclaim_state *old_state);
> +bool panthor_gem_try_evict(struct drm_gem_object *obj,
> +			   struct ww_acquire_ctx *ticket);
> +int panthor_gem_shrinker_init(struct panthor_device *ptdev);
> +void panthor_gem_shrinker_unplug(struct panthor_device *ptdev);
>  
>  void panthor_gem_bo_set_label(struct drm_gem_object *obj, const char *label);
>  void panthor_gem_kernel_bo_set_label(struct panthor_kernel_bo *bo, const char *label);
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index 3290e0b5facb..ffd821b3be46 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0 or MIT
>  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>  /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>  
>  #include <drm/drm_debugfs.h>
>  #include <drm/drm_drv.h>
> @@ -131,6 +132,9 @@ struct panthor_vma {
>  	 * Only map related flags are accepted.
>  	 */
>  	u32 flags;
> +
> +	/** @evicted: True if the VMA has been evicted. */
> +	bool evicted;
>  };
>  
>  /**
> @@ -191,13 +195,8 @@ struct panthor_vm_op_ctx {
>  		/** @map.bo_offset: Offset in the buffer object. */
>  		u64 bo_offset;
>  
> -		/**
> -		 * @map.sgt: sg-table pointing to pages backing the GEM object.
> -		 *
> -		 * This is gathered at job creation time, such that we don't have
> -		 * to allocate in ::run_job().
> -		 */
> -		struct sg_table *sgt;
> +		/** @map.bo: the BO being mapped. */
> +		struct panthor_gem_object *bo;
>  
>  		/**
>  		 * @map.new_vma: The new VMA object that will be inserted to the VA tree.
> @@ -385,6 +384,18 @@ struct panthor_vm {
>  		/** @locked_region.size: Size of the locked region. */
>  		u64 size;
>  	} locked_region;
> +
> +	/** @reclaim: Fields related to BO reclaim. */
> +	struct {
> +		/** @reclaim.lru: LRU of BOs that are only mapped to this VM. */
> +		struct drm_gem_lru lru;
> +
> +		/**
> +		 * @reclaim.lru_node: Node used to insert the VM in
> +		 * panthor_device::reclaim::vms.
> +		 */
> +		struct list_head lru_node;
> +	} reclaim;
>  };
>  
>  /**
> @@ -678,6 +689,16 @@ int panthor_vm_active(struct panthor_vm *vm)
>  	if (refcount_inc_not_zero(&vm->as.active_cnt))
>  		goto out_dev_exit;
>  
> +	/* As soon as active is called, we place the VM as the end of the VM LRU.
> +	 * If something fails after that, the only downside is that this VM that
> +	 * never became active in the first place will be reclaimed last, but
> +	 * that's an acceptable trade-off.
> +	 */
> +	mutex_lock(&ptdev->reclaim.lock);
> +	if (vm->reclaim.lru.count)
> +		list_move_tail(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> +	mutex_unlock(&ptdev->reclaim.lock);
> +
>  	/* Make sure we don't race with lock/unlock_region() calls
>  	 * happening around VM bind operations.
>  	 */
> @@ -1074,7 +1095,15 @@ static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
>  
> -	panthor_gem_unpin(bo);
> +	/* We couldn't call this when we unlinked, because the resv lock can't
> +	 * be taken in the dma signalling path, so call it now.
> +	 */
> +	dma_resv_lock(bo->base.resv, NULL);
> +	mutex_lock(&bo->base.gpuva.lock);
> +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> +	mutex_unlock(&bo->base.gpuva.lock);
> +	dma_resv_unlock(bo->base.resv);
> +
>  	kfree(vm_bo);
>  }
>  
> @@ -1095,6 +1124,11 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	if (op_ctx->map.vm_bo)
>  		drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo);
>  
> +	if (op_ctx->map.bo) {
> +		panthor_gem_unpin(op_ctx->map.bo);
> +		drm_gem_object_put(&op_ctx->map.bo->base);
> +	}
> +
>  	for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++)
>  		kfree(op_ctx->preallocated_vmas[i]);
>  
> @@ -1255,18 +1289,17 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	if (ret)
>  		goto err_cleanup;
>  
> +	drm_gem_object_get(&bo->base);
> +	op_ctx->map.bo = bo;
> +
>  	sgt = panthor_gem_get_dev_sgt(bo);
>  	if (IS_ERR(sgt)) {
> -		panthor_gem_unpin(bo);
>  		ret = PTR_ERR(sgt);
>  		goto err_cleanup;
>  	}
>  
> -	op_ctx->map.sgt = sgt;
> -
>  	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base);
>  	if (!preallocated_vm_bo) {
> -		panthor_gem_unpin(bo);
>  		ret = -ENOMEM;
>  		goto err_cleanup;
>  	}
> @@ -1280,9 +1313,19 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	dma_resv_lock(panthor_vm_resv(vm), NULL);
>  	mutex_lock(&bo->base.gpuva.lock);
>  	op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
> +	if (panthor_vm_resv(vm) == bo->base.resv)
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
>  	mutex_unlock(&bo->base.gpuva.lock);
>  	dma_resv_unlock(panthor_vm_resv(vm));
>  
> +	if (panthor_vm_resv(vm) != bo->base.resv) {
> +		dma_resv_lock(bo->base.resv, NULL);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +		dma_resv_unlock(bo->base.resv);
> +	}
> +
>  	op_ctx->map.bo_offset = offset;
>  
>  	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
> @@ -1891,6 +1934,10 @@ static void panthor_vm_free(struct drm_gpuvm *gpuvm)
>  	struct panthor_vm *vm = container_of(gpuvm, struct panthor_vm, base);
>  	struct panthor_device *ptdev = vm->ptdev;
>  
> +	mutex_lock(&ptdev->reclaim.lock);
> +	list_del_init(&vm->reclaim.lru_node);
> +	mutex_unlock(&ptdev->reclaim.lock);
> +
>  	mutex_lock(&vm->heaps.lock);
>  	if (drm_WARN_ON(&ptdev->base, vm->heaps.pool))
>  		panthor_heap_pool_destroy(vm->heaps.pool);
> @@ -2104,7 +2151,7 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
>  	panthor_vma_init(vma, op_ctx->flags & PANTHOR_VM_MAP_FLAGS);
>  
>  	ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags),
> -				   op_ctx->map.sgt, op->map.gem.offset,
> +				   op_ctx->map.bo->dmap.sgt, op->map.gem.offset,
>  				   op->map.va.range);
>  	if (ret) {
>  		panthor_vm_op_ctx_return_vma(op_ctx, vma);
> @@ -2188,8 +2235,10 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  	 * atomicity. panthor_vm_lock_region() bails out early if the new region
>  	 * is already part of the locked region, so no need to do this check here.
>  	 */
> -	panthor_vm_lock_region(vm, unmap_start, unmap_range);
> -	panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
> +	if (!unmap_vma->evicted) {
> +		panthor_vm_lock_region(vm, unmap_start, unmap_range);
> +		panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
> +	}
>  
>  	if (op->remap.prev) {
>  		struct panthor_gem_object *bo = to_panthor_bo(op->remap.prev->gem.obj);
> @@ -2203,6 +2252,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  
>  		prev_vma = panthor_vm_op_ctx_get_vma(op_ctx);
>  		panthor_vma_init(prev_vma, unmap_vma->flags);
> +		prev_vma->evicted = unmap_vma->evicted;
>  	}
>  
>  	if (op->remap.next) {
> @@ -2217,6 +2267,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  
>  		next_vma = panthor_vm_op_ctx_get_vma(op_ctx);
>  		panthor_vma_init(next_vma, unmap_vma->flags);
> +		next_vma->evicted = unmap_vma->evicted;
>  	}
>  
>  	drm_gpuva_remap(prev_vma ? &prev_vma->base : NULL,
> @@ -2246,19 +2297,197 @@ static int panthor_gpuva_sm_step_unmap(struct drm_gpuva_op *op,
>  	struct panthor_vma *unmap_vma = container_of(op->unmap.va, struct panthor_vma, base);
>  	struct panthor_vm *vm = priv;
>  
> -	panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
> -			       unmap_vma->base.va.range);
> +	if (!unmap_vma->evicted) {
> +		panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
> +				       unmap_vma->base.va.range);
> +	}
> +
>  	drm_gpuva_unmap(&op->unmap);
>  	panthor_vma_unlink(unmap_vma);
>  	return 0;
>  }
>  
> +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
> +{
> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> +	struct panthor_vm *vm = NULL;
> +	struct drm_gpuvm_bo *vm_bo;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +	lockdep_assert_held(&bo->base.gpuva.lock);
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +		/* We're only supposed to have one vm_bo in the list if we get there. */
> +		drm_WARN_ON(&ptdev->base, vm);
> +		vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +
> +		mutex_lock(&ptdev->reclaim.lock);
> +		drm_gem_lru_move_tail_locked(&vm->reclaim.lru, &bo->base);
> +		if (list_empty(&vm->reclaim.lru_node))
> +			list_move(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> +		mutex_unlock(&ptdev->reclaim.lock);
> +	}
> +}
> +
> +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
> +{
> +	struct drm_gpuvm_bo *vm_bo;
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +		struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +		struct drm_gpuva *va;
> +
> +		if (!mutex_trylock(&vm->op_lock))
> +			return -EDEADLK;
> +
> +		drm_gpuvm_bo_evict(vm_bo, true);
> +		drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +			struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> +
> +			panthor_vm_lock_region(vm, va->va.addr, va->va.range);
> +			panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);
> +			panthor_vm_unlock_region(vm);
> +			vma->evicted = true;
> +		}
> +
> +		mutex_unlock(&vm->op_lock);
> +	}
> +
> +	return 0;
> +}
> +
> +static struct panthor_vma *select_evicted_vma(struct drm_gpuvm_bo *vm_bo,
> +					      struct panthor_vm_op_ctx *op_ctx)
> +{
> +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +	struct panthor_vma *first_evicted_vma = NULL;
> +	struct drm_gpuva *va;
> +
> +	/* Take op_lock to protect against va insertion/removal. */
> +	mutex_lock(&vm->op_lock);
> +	drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> +
> +		if (vma->evicted) {
> +			first_evicted_vma = vma;
> +			panthor_vm_init_op_ctx(op_ctx, va->va.range, va->va.addr, vma->flags);
> +			op_ctx->map.bo_offset = va->gem.offset;
> +			break;
> +		}
> +	}
> +	mutex_unlock(&vm->op_lock);
> +
> +	return first_evicted_vma;
> +}
> +
> +static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
> +			     struct panthor_vma *evicted_vma,
> +			     struct panthor_vm_op_ctx *op_ctx)
> +{
> +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> +	struct drm_gpuva *va;
> +	bool found = false;
> +	int ret;
> +
> +	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
> +	if (ret)
> +		goto out_cleanup;
> +
> +	/* Take op_lock to protect against va insertion/removal. */
> +	mutex_lock(&vm->op_lock);
> +	drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> +
> +		if (vma != evicted_vma)
> +			continue;
> +
> +		/* We can't rely solely on pointer equality, because the VMA might have been
> +		 * freed and a new one allocated at the same address. If the evicted bit
> +		 * is still set, we're sure it's our VMA, because population/eviction is
> +		 * serialized with the BO resv lock.
> +		 */
> +		if (vma->evicted)
> +			found = true;
> +
> +		break;
> +	}
> +
> +	if (found) {
> +		vm->op_ctx = op_ctx;
> +		ret = panthor_vm_lock_region(vm, evicted_vma->base.va.addr,
> +					     evicted_vma->base.va.range);
> +		if (!ret) {
> +			ret = panthor_vm_map_pages(vm, evicted_vma->base.va.addr,
> +						   flags_to_prot(evicted_vma->flags),
> +						   bo->dmap.sgt,
> +						   evicted_vma->base.gem.offset,
> +						   evicted_vma->base.va.range);
> +		}
> +
> +		if (!ret)
> +			evicted_vma->evicted = false;
> +
> +		panthor_vm_unlock_region(vm);
> +		vm->op_ctx = NULL;
> +	}
> +
> +	mutex_unlock(&vm->op_lock);
> +
> +out_cleanup:
> +	panthor_vm_cleanup_op_ctx(op_ctx, vm);
> +	return ret;
> +}
> +
> +static int panthor_vm_restore_vmas(struct drm_gpuvm_bo *vm_bo)
> +{
> +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> +	struct panthor_vm_op_ctx op_ctx;
> +
> +	if (drm_WARN_ON_ONCE(&vm->ptdev->base, !bo->dmap.sgt))
> +		return -EINVAL;
> +
> +	for (struct panthor_vma *vma = select_evicted_vma(vm_bo, &op_ctx);
> +	     vma; vma = select_evicted_vma(vm_bo, &op_ctx)) {
> +		int ret;
> +
> +		ret = remap_evicted_vma(vm_bo, vma, &op_ctx);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int panthor_vm_bo_validate(struct drm_gpuvm_bo *vm_bo,
> +				  struct drm_exec *exec)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> +	int ret;
> +
> +	ret = panthor_gem_swapin_locked(bo);
> +	if (ret)
> +		return ret;
> +
> +	ret = panthor_vm_restore_vmas(vm_bo);
> +	if (ret)
> +		return ret;
> +
> +	drm_gpuvm_bo_evict(vm_bo, false);
> +	mutex_lock(&bo->base.gpuva.lock);
> +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> +	mutex_unlock(&bo->base.gpuva.lock);
> +	return 0;
> +}
> +
>  static const struct drm_gpuvm_ops panthor_gpuvm_ops = {
>  	.vm_free = panthor_vm_free,
>  	.vm_bo_free = panthor_vm_bo_free,
>  	.sm_step_map = panthor_gpuva_sm_step_map,
>  	.sm_step_remap = panthor_gpuva_sm_step_remap,
>  	.sm_step_unmap = panthor_gpuva_sm_step_unmap,
> +	.vm_bo_validate = panthor_vm_bo_validate,
>  };
>  
>  /**
> @@ -2473,6 +2702,8 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>  	vm->kernel_auto_va.start = auto_kernel_va_start;
>  	vm->kernel_auto_va.end = vm->kernel_auto_va.start + auto_kernel_va_size - 1;
>  
> +	drm_gem_lru_init(&vm->reclaim.lru, &ptdev->reclaim.lock);
> +	INIT_LIST_HEAD(&vm->reclaim.lru_node);
>  	INIT_LIST_HEAD(&vm->node);
>  	INIT_LIST_HEAD(&vm->as.lru_node);
>  	vm->as.id = -1;
> @@ -2820,7 +3051,78 @@ int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec, struct panthor_vm
>  	if (ret)
>  		return ret;
>  
> -	return drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
> +	ret = drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
> +	if (ret)
> +		return ret;
> +
> +	return drm_gpuvm_validate(&vm->base, exec);
> +}
> +
> +unsigned long
> +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
> +			     unsigned int nr_to_scan, unsigned long *remaining,
> +			     bool (*shrink)(struct drm_gem_object *,
> +					    struct ww_acquire_ctx *))
> +{
> +	unsigned long freed = 0;
> +	LIST_HEAD(remaining_vms);
> +	LIST_HEAD(vms);
> +
> +	mutex_lock(&ptdev->reclaim.lock);
> +	list_splice_init(&ptdev->reclaim.vms, &vms);
> +
> +	while (freed < nr_to_scan) {
> +		struct panthor_vm *vm;
> +
> +		vm = list_first_entry_or_null(&vms, typeof(*vm),
> +					      reclaim.lru_node);
> +		if (!vm)
> +			break;
> +
> +		if (!kref_get_unless_zero(&vm->base.kref)) {
> +			list_del_init(&vm->reclaim.lru_node);
> +			continue;
> +		}
> +
> +		mutex_unlock(&ptdev->reclaim.lock);
> +
> +		freed += drm_gem_lru_scan(&vm->reclaim.lru, nr_to_scan - freed,
> +					  remaining, shrink, NULL);
> +
> +		mutex_lock(&ptdev->reclaim.lock);
> +
> +		/* If the VM is still in the temporary list, remove it so we
> +		 * can proceed with the next VM.
> +		 */
> +		if (vm->reclaim.lru_node.prev == &vms) {
> +			list_del_init(&vm->reclaim.lru_node);
> +
> +			/* Keep the VM around if there are still things to
> +			 * reclaim, so we can preserve the LRU order when
> +			 * re-inserting in ptdev->reclaim.vms at the end.
> +			 */
> +			if (vm->reclaim.lru.count > 0)
> +				list_add_tail(&vm->reclaim.lru_node, &remaining_vms);
> +		}
> +
> +		mutex_unlock(&ptdev->reclaim.lock);
> +
> +		panthor_vm_put(vm);
> +
> +		mutex_lock(&ptdev->reclaim.lock);
> +	}
> +
> +	/* Re-insert VMs with remaining data to reclaim at the beginning of
> +	 * the LRU. Note that any activeness change on the VM that happened
> +	 * while we were reclaiming would have moved the VM out of our
> +	 * temporary [remaining_]vms list, meaning anything we re-insert here
> +	 * preserves the LRU order.
> +	 */
> +	list_splice_tail(&vms, &remaining_vms);
> +	list_splice(&remaining_vms, &ptdev->reclaim.vms);
> +	mutex_unlock(&ptdev->reclaim.lock);
> +
> +	return freed;
>  }
>  
>  /**
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.h b/drivers/gpu/drm/panthor/panthor_mmu.h
> index 0e268fdfdb2f..3522fbbce369 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.h
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.h
> @@ -1,6 +1,7 @@
>  /* SPDX-License-Identifier: GPL-2.0 or MIT */
>  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>  /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>  
>  #ifndef __PANTHOR_MMU_H__
>  #define __PANTHOR_MMU_H__
> @@ -46,6 +47,13 @@ struct panthor_vm *panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>  				     u64 kernel_auto_va_start,
>  				     u64 kernel_auto_va_size);
>  
> +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo);
> +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo);
> +unsigned long
> +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
> +			     unsigned int nr_to_scan, unsigned long *remaining,
> +			     bool (*shrink)(struct drm_gem_object *,
> +					    struct ww_acquire_ctx *));
>  int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec,
>  					struct panthor_vm *vm,
>  					u32 slot_count);

I *think* there's an issue with objects being evicted then accessed by
mmap() or vmap. There's a call to drm_gpuvm_bo_evict(..., true) in
panthor_vm_evict_bo_mappings_locked() to evict, but the only
"de-eviction" (i.e. drm_gpuvm_bo_evict(..., false)) is
panthor_vm_bo_validate() which is called on the submission path but not
from other paths.

If this is right then on the next submission panthor_gem_swapin_locked()
will it the WARN_ON for the pin_count being non-zero.

I have to admit to being very unsure about all of this - I even resorted
to asking AI, which I think has made me more confused ;)

Thanks,
Steve

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-14 15:05   ` Steven Price
@ 2026-01-15 10:50     ` Boris Brezillon
  2026-01-15 11:24       ` Steven Price
  0 siblings, 1 reply; 62+ messages in thread
From: Boris Brezillon @ 2026-01-15 10:50 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

Hi Steve,

On Wed, 14 Jan 2026 15:05:36 +0000
Steven Price <steven.price@arm.com> wrote:

> On 09/01/2026 13:08, Boris Brezillon wrote:
> > From: Akash Goel <akash.goel@arm.com>
> > 
> > This implementation is losely based on the MSM shrinker, and it's
> > relying on the drm_gpuvm eviction/validation infrastructure.
> > 
> > Right now we only support swapout/eviction, but we could add an extra
> > flag to specify when buffer content doesn't need to be preserved to
> > avoid the swapout/swapin dance.
> > 
> > Locking is a bit of a nightmare, but using _trylock() all the way in
> > the reclaim path seems to make lockdep happy. And yes, we might be
> > missing opportunities to reclaim when the system is under heavy GPU
> > load/heavy memory pressure/heavy GPU VM activity, but that's better
> > than no reclaim at all.
> > 
> > Signed-off-by: Akash Goel <akash.goel@arm.com>
> > Co-developed-by: Boris Brezillon <boris.brezillon@collabora.com>
> > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > ---
> >  drivers/gpu/drm/panthor/panthor_device.c |  11 +-
> >  drivers/gpu/drm/panthor/panthor_device.h |  73 ++++
> >  drivers/gpu/drm/panthor/panthor_gem.c    | 427 ++++++++++++++++++++++-
> >  drivers/gpu/drm/panthor/panthor_gem.h    |  67 ++++
> >  drivers/gpu/drm/panthor/panthor_mmu.c    | 338 +++++++++++++++++-
> >  drivers/gpu/drm/panthor/panthor_mmu.h    |   8 +
> >  6 files changed, 901 insertions(+), 23 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/panthor/panthor_device.c b/drivers/gpu/drm/panthor/panthor_device.c
> > index 54fbb1aa07c5..bc62a498a8a8 100644
> > --- a/drivers/gpu/drm/panthor/panthor_device.c
> > +++ b/drivers/gpu/drm/panthor/panthor_device.c
> > @@ -2,6 +2,7 @@
> >  /* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
> >  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
> >  /* Copyright 2023 Collabora ltd. */
> > +/* Copyright 2025 ARM Limited. All rights reserved. */
> >  
> >  #include <linux/clk.h>
> >  #include <linux/mm.h>
> > @@ -122,6 +123,7 @@ void panthor_device_unplug(struct panthor_device *ptdev)
> >  	panthor_sched_unplug(ptdev);
> >  	panthor_fw_unplug(ptdev);
> >  	panthor_mmu_unplug(ptdev);
> > +	panthor_gem_shrinker_unplug(ptdev);
> >  	panthor_gpu_unplug(ptdev);
> >  	panthor_pwr_unplug(ptdev);
> >  
> > @@ -291,10 +293,14 @@ int panthor_device_init(struct panthor_device *ptdev)
> >  	if (ret)
> >  		goto err_unplug_gpu;
> >  
> > -	ret = panthor_mmu_init(ptdev);
> > +	ret = panthor_gem_shrinker_init(ptdev);
> >  	if (ret)
> >  		goto err_unplug_gpu;
> >  
> > +	ret = panthor_mmu_init(ptdev);
> > +	if (ret)
> > +		goto err_unplug_shrinker;
> > +
> >  	ret = panthor_fw_init(ptdev);
> >  	if (ret)
> >  		goto err_unplug_mmu;
> > @@ -326,6 +332,9 @@ int panthor_device_init(struct panthor_device *ptdev)
> >  err_unplug_mmu:
> >  	panthor_mmu_unplug(ptdev);
> >  
> > +err_unplug_shrinker:
> > +	panthor_gem_shrinker_unplug(ptdev);
> > +
> >  err_unplug_gpu:
> >  	panthor_gpu_unplug(ptdev);
> >  
> > diff --git a/drivers/gpu/drm/panthor/panthor_device.h b/drivers/gpu/drm/panthor/panthor_device.h
> > index f35e52b9546a..bc348aa9634e 100644
> > --- a/drivers/gpu/drm/panthor/panthor_device.h
> > +++ b/drivers/gpu/drm/panthor/panthor_device.h
> > @@ -14,6 +14,7 @@
> >  #include <linux/spinlock.h>
> >  
> >  #include <drm/drm_device.h>
> > +#include <drm/drm_gem.h>
> >  #include <drm/drm_mm.h>
> >  #include <drm/gpu_scheduler.h>
> >  #include <drm/panthor_drm.h>
> > @@ -157,6 +158,78 @@ struct panthor_device {
> >  	/** @devfreq: Device frequency scaling management data. */
> >  	struct panthor_devfreq *devfreq;
> >  
> > +	/** @reclaim: Reclaim related stuff */
> > +	struct {
> > +		/** @reclaim.shrinker: Shrinker instance */
> > +		struct shrinker *shrinker;
> > +
> > +		/** @reclaim.lock: Lock protecting all LRUs */
> > +		struct mutex lock;
> > +
> > +		/**
> > +		 * @reclaim.unused: BOs with unused pages
> > +		 *
> > +		 * Basically all buffers that got mmapped, vmapped or GPU mapped and
> > +		 * then unmapped. There should be no contention on these buffers,
> > +		 * making them ideal to reclaim.
> > +		 */
> > +		struct drm_gem_lru unused;
> > +
> > +		/**
> > +		 * @reclaim.mmapped: mmap()-ed buffers
> > +		 *
> > +		 * Those are relatively easy to reclaim since we don't need user
> > +		 * agreement, we can simply teardown the mapping and let it fault on
> > +		 * the next access.
> > +		 */
> > +		struct drm_gem_lru mmapped;
> > +
> > +		/**
> > +		 * @reclaim.vms: VM LRU list
> > +		 *
> > +		 * VMs that have reclaimable BOs only mapped to a single VM are placed
> > +		 * in this LRU. Reclaiming such BOs implies waiting for VM idleness
> > +		 * (no in-flight GPU jobs targeting this VM), meaning we can't reclaim
> > +		 * those if we're in a context where we can't block/sleep.
> > +		 */
> > +		struct list_head vms;  
> 
> NIT: I would move this down below gpu_mapped_shared so that the three
> (per-device) LRU lists for BOs are together.

Sure, I can do that.

> 
> > +
> > +		/**
> > +		 * @reclaim.gpu_mapped_shared: shared BO LRU list
> > +		 *
> > +		 * That's the most tricky BO type to reclaim, because it involves
> > +		 * tearing down all mappings in all VMs where this BO is mapped,
> > +		 * which increases the risk of contention and thus decreases the
> > +		 * likeliness of success.
> > +		 */
> > +		struct drm_gem_lru gpu_mapped_shared;
> > +
> > +		/**
> > +		 * @reclaim.gpu_mapped_count: Global counter of pages that are GPU mapped
> > +		 *
> > +		 * Allows us to get the number of reclaimable pages without walking
> > +		 * the vms and gpu_mapped_shared LRUs.
> > +		 */
> > +		long gpu_mapped_count;
> > +
> > +		/**
> > +		 * @reclaim.retry_count: Number of times we ran the shrinker without being
> > +		 * able to reclaim stuff
> > +		 *
> > +		 * Used to stop scanning GEMs when too many attempts were made
> > +		 * without progress.
> > +		 */
> > +		atomic_t retry_count;
> > +
> > +#ifdef CONFIG_DEBUG_FS
> > +		/**
> > +		 * @reclaim.nr_pages_reclaimed_on_last_scan: Number of pages reclaimed on the last
> > +		 * shrinker scan
> > +		 */
> > +		unsigned long nr_pages_reclaimed_on_last_scan;
> > +#endif
> > +	} reclaim;
> > +
> >  	/** @unplug: Device unplug related fields. */
> >  	struct {
> >  		/** @lock: Lock used to serialize unplug operations. */
> > diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> > index 458d22380e96..065956a1f0fb 100644
> > --- a/drivers/gpu/drm/panthor/panthor_gem.c
> > +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> > @@ -2,8 +2,10 @@
> >  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
> >  /* Copyright 2023 Collabora ltd. */
> >  /* Copyright 2025 Amazon.com, Inc. or its affiliates */
> > +/* Copyright 2025 ARM Limited. All rights reserved. */
> >  
> >  #include <linux/cleanup.h>
> > +#include <linux/debugfs.h>
> >  #include <linux/dma-buf.h>
> >  #include <linux/dma-mapping.h>
> >  #include <linux/err.h>
> > @@ -12,6 +14,8 @@
> >  
> >  #include <drm/drm_debugfs.h>
> >  #include <drm/drm_file.h>
> > +#include <drm/drm_gpuvm.h>
> > +#include <drm/drm_managed.h>
> >  #include <drm/drm_prime.h>
> >  #include <drm/drm_print.h>
> >  #include <drm/panthor_drm.h>
> > @@ -114,6 +118,98 @@ should_map_wc(struct panthor_gem_object *bo)
> >  	return true;
> >  }
> >  
> > +static bool is_gpu_mapped(struct panthor_gem_object *bo,
> > +			  enum panthor_gem_reclaim_state *state)
> > +{
> > +	struct drm_gpuvm *vm = NULL;
> > +	struct drm_gpuvm_bo *vm_bo;
> > +
> > +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> > +		if (!vm) {
> > +			*state = PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> > +			vm = vm_bo->vm;
> > +		} else if (vm != vm_bo->vm) {
> > +			*state = PANTHOR_GEM_GPU_MAPPED_SHARED;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return !!vm;
> > +}
> > +
> > +static enum panthor_gem_reclaim_state
> > +panthor_gem_evaluate_reclaim_state_locked(struct panthor_gem_object *bo)
> > +{
> > +	enum panthor_gem_reclaim_state gpu_mapped_state;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +	lockdep_assert_held(&bo->base.gpuva.lock);
> > +
> > +	/* If pages have not been allocated, there's nothing to reclaim. */
> > +	if (!bo->backing.pages)
> > +		return PANTHOR_GEM_UNRECLAIMABLE;
> > +
> > +	/* If memory is pinned, we prevent reclaim. */
> > +	if (refcount_read(&bo->backing.pin_count))
> > +		return PANTHOR_GEM_UNRECLAIMABLE;
> > +
> > +	if (is_gpu_mapped(bo, &gpu_mapped_state))
> > +		return gpu_mapped_state;
> > +
> > +	if (refcount_read(&bo->cmap.mmap_count) && bo->backing.pages)  
> 
> Checking bo->backing.pages is redundant as !bo->backing.pages is checked
> above.

Will drop this check.

> 
> > +		return PANTHOR_GEM_MMAPPED;
> > +
> > +	return PANTHOR_GEM_UNUSED;
> > +}
> > +
> > +void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
> > +					     enum panthor_gem_reclaim_state *old_statep)
> > +{
> > +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> > +	enum panthor_gem_reclaim_state old_state = bo->reclaim_state;
> > +	enum panthor_gem_reclaim_state new_state;
> > +	bool was_gpu_mapped, is_gpu_mapped;
> > +
> > +	if (old_statep)
> > +		*old_statep = old_state;
> > +
> > +	new_state = panthor_gem_evaluate_reclaim_state_locked(bo);
> > +	if (new_state == old_state)
> > +		return;
> > +
> > +	was_gpu_mapped = old_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> > +			 old_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> > +	is_gpu_mapped = new_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> > +			new_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> > +
> > +	if (is_gpu_mapped && !was_gpu_mapped)
> > +		ptdev->reclaim.gpu_mapped_count += bo->base.size >> PAGE_SHIFT;
> > +	else if (!is_gpu_mapped && was_gpu_mapped)
> > +		ptdev->reclaim.gpu_mapped_count -= bo->base.size >> PAGE_SHIFT;
> > +
> > +	switch (new_state) {
> > +	case PANTHOR_GEM_UNUSED:
> > +		drm_gem_lru_move_tail(&ptdev->reclaim.unused, &bo->base);
> > +		break;
> > +	case PANTHOR_GEM_MMAPPED:
> > +		drm_gem_lru_move_tail(&ptdev->reclaim.mmapped, &bo->base);
> > +		break;
> > +	case PANTHOR_GEM_GPU_MAPPED_PRIVATE:
> > +		panthor_vm_update_bo_reclaim_lru_locked(bo);
> > +		break;
> > +	case PANTHOR_GEM_GPU_MAPPED_SHARED:
> > +		drm_gem_lru_move_tail(&ptdev->reclaim.gpu_mapped_shared, &bo->base);
> > +		break;
> > +	case PANTHOR_GEM_UNRECLAIMABLE:
> > +		drm_gem_lru_remove(&bo->base);
> > +		break;
> > +	default:  
> 
> WARN()? We don't expect this to happen.

Yep, I'll add one.

> 
> > +		break;
> > +	}
> > +
> > +	bo->reclaim_state = new_state;
> > +}
> > +
> >  static void
> >  panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
> >  {
> > @@ -153,8 +249,12 @@ static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
> >  		return 0;
> >  
> >  	ret = panthor_gem_backing_get_pages_locked(bo);
> > -	if (!ret)
> > +	if (!ret) {
> >  		refcount_set(&bo->backing.pin_count, 1);
> > +		mutex_lock(&bo->base.gpuva.lock);
> > +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +		mutex_unlock(&bo->base.gpuva.lock);
> > +	}
> >  
> >  	return ret;
> >  }
> > @@ -167,8 +267,12 @@ static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
> >  	/* We don't release anything when pin_count drops to zero.
> >  	 * Pages stay there until an explicit cleanup is requested.
> >  	 */
> > -	if (!refcount_dec_not_one(&bo->backing.pin_count))
> > +	if (!refcount_dec_not_one(&bo->backing.pin_count)) {
> >  		refcount_set(&bo->backing.pin_count, 0);
> > +		mutex_lock(&bo->base.gpuva.lock);
> > +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +		mutex_unlock(&bo->base.gpuva.lock);
> > +	}
> >  }
> >  
> >  static void
> > @@ -531,6 +635,49 @@ void panthor_gem_unpin(struct panthor_gem_object *bo)
> >  	dma_resv_unlock(bo->base.resv);
> >  }
> >  
> > +int panthor_gem_swapin_locked(struct panthor_gem_object *bo)
> > +{
> > +	struct sg_table *sgt;
> > +	int ret;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> > +		return -EINVAL;
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
> > +		return -EINVAL;
> > +
> > +	ret = panthor_gem_backing_get_pages_locked(bo);
> > +	if (ret)
> > +		return ret;
> > +
> > +	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> > +	if (IS_ERR(sgt))
> > +		return PTR_ERR(sgt);
> > +
> > +	return 0;
> > +}
> > +
> > +static void panthor_gem_evict_locked(struct panthor_gem_object *bo)
> > +{
> > +	dma_resv_assert_held(bo->base.resv);
> > +	lockdep_assert_held(&bo->base.gpuva.lock);
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> > +		return;
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
> > +		return;
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> > +		return;
> > +
> > +	panthor_gem_dev_map_cleanup(bo);
> > +	panthor_gem_backing_cleanup(bo);
> > +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +}
> > +
> >  static struct sg_table *panthor_gem_get_sg_table(struct drm_gem_object *obj)
> >  {
> >  	struct panthor_gem_object *bo = to_panthor_bo(obj);  
> 
> Not in this diff, but in panthor_gem_dev_map_get_sgt_locked() we have a
> comment which needs updating:
> 
> 	/* Pages stay around after they've been allocated. At least that stands
> 	 * until we add a shrinker.
> 	 */

Yep, I'll fix that.

> 
> > @@ -692,6 +839,10 @@ static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
> >  	} else {
> >  		struct page *page = bo->backing.pages[page_offset];
> >  
> > +		mutex_lock(&bo->base.gpuva.lock);
> > +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +		mutex_unlock(&bo->base.gpuva.lock);
> > +
> >  		if (mmap_lock_held)
> >  			ret = insert_page(vmf, page);
> >  		else
> > @@ -764,8 +915,12 @@ static void panthor_gem_vm_close(struct vm_area_struct *vma)
> >  		goto out;
> >  
> >  	dma_resv_lock(bo->base.resv, NULL);
> > -	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> > +	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
> >  		refcount_set(&bo->cmap.mmap_count, 0);
> > +		mutex_lock(&bo->base.gpuva.lock);
> > +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +		mutex_unlock(&bo->base.gpuva.lock);
> > +	}
> >  	dma_resv_unlock(bo->base.resv);
> >  
> >  out:
> > @@ -801,6 +956,7 @@ panthor_gem_alloc_object(uint32_t flags)
> >  	if (!bo)
> >  		return ERR_PTR(-ENOMEM);
> >  
> > +	bo->reclaim_state = PANTHOR_GEM_UNRECLAIMABLE;
> >  	bo->base.funcs = &panthor_gem_funcs;
> >  	bo->flags = flags;
> >  	mutex_init(&bo->label.lock);
> > @@ -1041,11 +1197,13 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
> >   */
> >  void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
> >  {
> > +	struct panthor_device *ptdev;
> >  	struct panthor_vm *vm;
> >  
> >  	if (IS_ERR_OR_NULL(bo))
> >  		return;
> >  
> > +	ptdev = container_of(bo->obj->dev, struct panthor_device, base);
> >  	vm = bo->vm;
> >  	panthor_kernel_bo_vunmap(bo);
> >  
> > @@ -1053,6 +1211,8 @@ void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
> >  		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
> >  	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
> >  	panthor_vm_free_va(vm, &bo->va_node);
> > +	if (vm == panthor_fw_vm(ptdev))
> > +		panthor_gem_unpin(to_panthor_bo(bo->obj));
> >  	drm_gem_object_put(bo->obj);
> >  	panthor_vm_put(vm);
> >  	kfree(bo);
> > @@ -1101,6 +1261,12 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
> >  
> >  	kbo->obj = &bo->base;
> >  
> > +	if (vm == panthor_fw_vm(ptdev)) {
> > +		ret = panthor_gem_pin(bo);
> > +		if (ret)
> > +			goto err_put_obj;
> > +	}
> > +
> >  	panthor_gem_kernel_bo_set_label(kbo, name);
> >  
> >  	/* The system and GPU MMU page size might differ, which becomes a
> > @@ -1112,7 +1278,7 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
> >  	size = ALIGN(size, panthor_vm_page_size(vm));
> >  	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
> >  	if (ret)
> > -		goto err_put_obj;
> > +		goto err_unpin;
> >  
> >  	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
> >  	if (ret)
> > @@ -1124,6 +1290,10 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
> >  err_free_va:
> >  	panthor_vm_free_va(vm, &kbo->va_node);
> >  
> > +err_unpin:
> > +	if (vm == panthor_fw_vm(ptdev))
> > +		panthor_gem_unpin(bo);
> > +
> >  err_put_obj:
> >  	drm_gem_object_put(&bo->base);
> >  
> > @@ -1132,6 +1302,223 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
> >  	return ERR_PTR(ret);
> >  }
> >  
> > +static bool can_swap(void)
> > +{
> > +	return get_nr_swap_pages() > 0;
> > +}
> > +
> > +static bool can_block(struct shrink_control *sc)
> > +{
> > +	if (!(sc->gfp_mask & __GFP_DIRECT_RECLAIM))
> > +		return false;
> > +	return current_is_kswapd() || (sc->gfp_mask & __GFP_RECLAIM);
> > +}
> > +
> > +static unsigned long
> > +panthor_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
> > +{
> > +	struct panthor_device *ptdev = shrinker->private_data;
> > +	unsigned long count;
> > +
> > +	/* We currently don't have a flag to tell when the content of a
> > +	 * BO can be discarded.
> > +	 */
> > +	if (!can_swap())
> > +		return 0;
> > +
> > +	count = ptdev->reclaim.unused.count;
> > +	count += ptdev->reclaim.mmapped.count;
> > +
> > +	if (can_block(sc))
> > +		count += ptdev->reclaim.gpu_mapped_count;
> > +
> > +	return count;
> > +}
> > +
> > +static bool should_wait(enum panthor_gem_reclaim_state reclaim_state)
> > +{
> > +	return reclaim_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE ||
> > +	       reclaim_state == PANTHOR_GEM_GPU_MAPPED_SHARED;
> > +}
> > +
> > +bool panthor_gem_try_evict(struct drm_gem_object *obj,
> > +			   struct ww_acquire_ctx *ticket)
> > +{
> > +	/*
> > +	 * Track last locked entry for unwinding locks in error and
> > +	 * success paths
> > +	 */
> > +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> > +	struct drm_gpuvm_bo *vm_bo, *last_locked = NULL;
> > +	enum panthor_gem_reclaim_state old_state;
> > +	int ret = 0;
> > +
> > +	/* To avoid potential lock ordering issue between bo_gpuva and
> > +	 * mapping->i_mmap_rwsem, unmap the pages from CPU side before
> > +	 * acquring the bo_gpuva lock. As the bo_resv lock is held, CPU
> > +	 * page fault handler won't be able to map in the pages whilst
> > +	 * eviction is in progress.
> > +	 */
> > +	drm_vma_node_unmap(&bo->base.vma_node, bo->base.dev->anon_inode->i_mapping);
> > +
> > +	/* We take this lock when walking the list to prevent
> > +	 * insertion/deletion.
> > +	 */
> > +	/* We can only trylock in that path, because
> > +	 * - allocation might happen while some of these locks are held
> > +	 * - lock ordering is different in other paths
> > +	 *     vm_resv -> bo_resv -> bo_gpuva
> > +	 *     vs
> > +	 *     bo_resv -> bo_gpuva -> vm_resv
> > +	 *
> > +	 * If we fail to lock that's fine, we back off and will get
> > +	 * back to it later.
> > +	 */
> > +	if (!mutex_trylock(&bo->base.gpuva.lock))
> > +		return false;
> > +
> > +	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> > +		struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> > +
> > +		if (resv == obj->resv)
> > +			continue;
> > +
> > +		if (!dma_resv_trylock(resv)) {
> > +			ret = -EDEADLK;
> > +			goto out_unlock;
> > +		}
> > +
> > +		last_locked = vm_bo;
> > +	}
> > +
> > +	/* Update the state before trying to evict the buffer, if the state was
> > +	 * updated to something that's harder to reclaim (higher value in the
> > +	 * enum), skip it (will be processed when the relevant LRU is).
> > +	 */
> > +	panthor_gem_update_reclaim_state_locked(bo, &old_state);
> > +	if (old_state < bo->reclaim_state) {
> > +		ret = -EAGAIN;
> > +		goto out_unlock;
> > +	}
> > +
> > +	/* Wait was too long, skip. */
> > +	if (should_wait(bo->reclaim_state) &&
> > +	    dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP, false, 10) <= 0) {
> > +		ret = -ETIMEDOUT;
> > +		goto out_unlock;
> > +	}
> > +
> > +	/* Couldn't teardown the GPU mappings? Skip. */
> > +	ret = panthor_vm_evict_bo_mappings_locked(bo);
> > +	if (ret)
> > +		goto out_unlock;
> > +
> > +	/* If everything went fine, evict the object. */
> > +	panthor_gem_evict_locked(bo);
> > +
> > +out_unlock:
> > +	if (last_locked) {
> > +		drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> > +			struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> > +
> > +			if (resv == obj->resv)
> > +				continue;
> > +
> > +			dma_resv_unlock(resv);
> > +
> > +			if (last_locked == vm_bo)
> > +				break;
> > +		}
> > +	}
> > +	mutex_unlock(&bo->base.gpuva.lock);
> > +
> > +	return ret == 0;
> > +}
> > +
> > +static unsigned long
> > +panthor_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
> > +{
> > +	struct panthor_device *ptdev = shrinker->private_data;
> > +	unsigned long remaining = 0;
> > +	unsigned long freed = 0;
> > +
> > +	if (!can_swap())
> > +		goto out;
> > +
> > +	freed += drm_gem_lru_scan(&ptdev->reclaim.unused,
> > +				  sc->nr_to_scan - freed, &remaining,
> > +				  panthor_gem_try_evict, NULL);
> > +	if (freed >= sc->nr_to_scan)
> > +		goto out;
> > +
> > +	freed += drm_gem_lru_scan(&ptdev->reclaim.mmapped,
> > +				  sc->nr_to_scan - freed, &remaining,
> > +				  panthor_gem_try_evict, NULL);
> > +	if (freed >= sc->nr_to_scan)
> > +		goto out;
> > +
> > +	freed += panthor_mmu_reclaim_priv_bos(ptdev, sc->nr_to_scan - freed,
> > +					      &remaining, panthor_gem_try_evict);
> > +	if (freed >= sc->nr_to_scan)
> > +		goto out;
> > +
> > +	freed += drm_gem_lru_scan(&ptdev->reclaim.gpu_mapped_shared,
> > +				  sc->nr_to_scan - freed, &remaining,
> > +				  panthor_gem_try_evict, NULL);
> > +
> > +out:
> > +#ifdef CONFIG_DEBUG_FS
> > +	/* This is racy, but that's okay, because this is just debugfs
> > +	 * reporting and doesn't need to be accurate.
> > +	 */
> > +	ptdev->reclaim.nr_pages_reclaimed_on_last_scan = freed;
> > +#endif
> > +
> > +	/* If there are things to reclaim, try a couple times before giving up. */
> > +	if (!freed && remaining > 0 &&
> > +	    atomic_inc_return(&ptdev->reclaim.retry_count) < 2)
> > +		return 0;
> > +
> > +	/* There's nothing left to reclaim, or the resource are contended. Give up now. */
> > +	atomic_set(&ptdev->reclaim.retry_count, 0);  
> 
> I think you're missing:
> 
> 	if (freed)
> 		return freed;
> 
> (or similar)

Oops, indeed.

> 
> > +	return SHRINK_STOP;
> > +}
> > +
> > +int panthor_gem_shrinker_init(struct panthor_device *ptdev)
> > +{
> > +	struct shrinker *shrinker;
> > +
> > +	drmm_mutex_init(&ptdev->base, &ptdev->reclaim.lock);
> > +	INIT_LIST_HEAD(&ptdev->reclaim.vms);
> > +	drm_gem_lru_init(&ptdev->reclaim.unused, &ptdev->reclaim.lock);
> > +	drm_gem_lru_init(&ptdev->reclaim.mmapped, &ptdev->reclaim.lock);
> > +	drm_gem_lru_init(&ptdev->reclaim.gpu_mapped_shared, &ptdev->reclaim.lock);
> > +	ptdev->reclaim.gpu_mapped_count = 0;
> > +
> > +	/* Teach lockdep about lock ordering wrt. shrinker: */
> > +	fs_reclaim_acquire(GFP_KERNEL);
> > +	might_lock(&ptdev->reclaim.lock);
> > +	fs_reclaim_release(GFP_KERNEL);
> > +
> > +	shrinker = shrinker_alloc(0, "drm-panthor-gem");
> > +	if (!shrinker)
> > +		return -ENOMEM;
> > +
> > +	shrinker->count_objects = panthor_gem_shrinker_count;
> > +	shrinker->scan_objects = panthor_gem_shrinker_scan;
> > +	shrinker->private_data = ptdev;
> > +	ptdev->reclaim.shrinker = shrinker;
> > +
> > +	shrinker_register(shrinker);
> > +	return 0;
> > +}
> > +
> > +void panthor_gem_shrinker_unplug(struct panthor_device *ptdev)
> > +{
> > +	if (ptdev->reclaim.shrinker)
> > +		shrinker_free(ptdev->reclaim.shrinker);
> > +}
> > +
> >  #ifdef CONFIG_DEBUG_FS
> >  struct gem_size_totals {
> >  	size_t size;
> > @@ -1250,10 +1637,42 @@ static struct drm_info_list panthor_gem_debugfs_list[] = {
> >  	{ "gems", panthor_gem_show_bos, 0, NULL },
> >  };
> >  
> > +static int shrink_get(void *data, u64 *val)
> > +{
> > +	struct panthor_device *ptdev =
> > +		container_of(data, struct panthor_device, base);
> > +
> > +	*val = ptdev->reclaim.nr_pages_reclaimed_on_last_scan;
> > +	return 0;
> > +}
> > +
> > +static int shrink_set(void *data, u64 val)
> > +{
> > +	struct panthor_device *ptdev =
> > +		container_of(data, struct panthor_device, base);
> > +	struct shrink_control sc = {
> > +		.gfp_mask = GFP_KERNEL,
> > +		.nr_to_scan = val,
> > +	};
> > +
> > +	fs_reclaim_acquire(GFP_KERNEL);
> > +	if (ptdev->reclaim.shrinker)
> > +		panthor_gem_shrinker_scan(ptdev->reclaim.shrinker, &sc);
> > +	fs_reclaim_release(GFP_KERNEL);
> > +
> > +	return 0;
> > +}  
> 
> Do you have some test to drive this?

Yes, I do [1].

> My immediate thought was that it
> would be nice (for manual testing at least) to printk the return value
> from panthor_gem_shrinker_scan(). But we probably wouldn't actually need
> nr_pages_reclaimed_on_last_scan if you could just read that from dmesg.
> But I can see integrating that into a test might not be ideal.

I basically based the interface on the existing MSM one. Might not be
the best, but it was good enough for this initial testing.

> 
> > +
> > +DEFINE_DEBUGFS_ATTRIBUTE(panthor_gem_debugfs_shrink_fops,
> > +			 shrink_get, shrink_set,
> > +			 "0x%08llx\n");
> > +
> >  void panthor_gem_debugfs_init(struct drm_minor *minor)
> >  {
> >  	drm_debugfs_create_files(panthor_gem_debugfs_list,
> >  				 ARRAY_SIZE(panthor_gem_debugfs_list),
> >  				 minor->debugfs_root, minor);
> > +	debugfs_create_file("shrink", 0600, minor->debugfs_root,
> > +			    minor->dev, &panthor_gem_debugfs_shrink_fops);
> >  }
> >  #endif
> > diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
> > index c0a18dca732c..6cb5b597ff1e 100644
> > --- a/drivers/gpu/drm/panthor/panthor_gem.h
> > +++ b/drivers/gpu/drm/panthor/panthor_gem.h
> > @@ -1,6 +1,7 @@
> >  /* SPDX-License-Identifier: GPL-2.0 or MIT */
> >  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
> >  /* Copyright 2023 Collabora ltd. */
> > +/* Copyright 2025 ARM Limited. All rights reserved. */
> >  
> >  #ifndef __PANTHOR_GEM_H__
> >  #define __PANTHOR_GEM_H__
> > @@ -93,6 +94,62 @@ struct panthor_gem_dev_map {
> >  	struct sg_table *sgt;
> >  };
> >  
> > +/**
> > + * enum panthor_gem_reclaim_state - Reclaim state of a GEM object
> > + *
> > + * This is defined in descending reclaimability order and some part
> > + * of the code depends on that.
> > + */
> > +enum panthor_gem_reclaim_state {
> > +	/**
> > +	 * @PANTHOR_GEM_UNUSED: GEM is currently unused
> > +	 *
> > +	 * This can happen when the GEM was previously vmap-ed, mmap-ed,
> > +	 * and/or GPU mapped and got unmapped. Because pages are lazily
> > +	 * returned to the shmem layer, we want to keep a list of such
> > +	 * BOs, because they should be fairly easy to reclaim (no need
> > +	 * to wait for GPU to be done, and no need to tear down user
> > +	 * mappings either).
> > +	 */
> > +	PANTHOR_GEM_UNUSED,
> > +
> > +	/**
> > +	 * @PANTHOR_GEM_MMAPPED: GEM is currently mmap-ed
> > +	 *
> > +	 * When a GEM has pages allocated and the mmap_count is > 0, the
> > +	 * GEM is placed in the mmapped list. This comes right after
> > +	 * unused because we can relatively easily tear down user mappings.
> > +	 */
> > +	PANTHOR_GEM_MMAPPED,
> > +
> > +	/**
> > +	 * @PANTHOR_GEM_GPU_MAPPED_PRIVATE: GEM is GPU mapped to only one VM
> > +	 *
> > +	 * When a GEM is mapped to a single VM, reclaim requests have more
> > +	 * chances to succeed, because we only need to synchronize against
> > +	 * a single GPU context. This is more annoying than reclaiming
> > +	 * mmap-ed pages still, because we have to wait for in-flight jobs
> > +	 * to land, and we might not be able to acquire all necessary locks
> > +	 * at reclaim time either.
> > +	 */
> > +	PANTHOR_GEM_GPU_MAPPED_PRIVATE,
> > +
> > +	/**
> > +	 * @PANTHOR_GEM_GPU_MAPPED_SHARED: GEM is GPU mapped to multiple VMs
> > +	 *
> > +	 * Like PANTHOR_GEM_GPU_MAPPED_PRIVATE, but the synchronization across
> > +	 * VMs makes such BOs harder to reclaim.
> > +	 */
> > +	PANTHOR_GEM_GPU_MAPPED_SHARED,
> > +
> > +	/**
> > +	 * @PANTHOR_GEM_UNRECLAIMABLE: GEM can't be reclaimed
> > +	 *
> > +	 * Happens when the GEM memory is pinned.  
> 
> Also the initial state when creating a GEM object (which I found
> non-obvious at least).

It's mostly because GEMs start being unpopulated, so there's nothing to
reclaim (hence the unreclaimable) until pages are requested. I'll add a
sentence to make that clear.

> 
> > +	 */
> > +	PANTHOR_GEM_UNRECLAIMABLE,
> > +};
> > +
> >  /**
> >   * struct panthor_gem_object - Driver specific GEM object.
> >   */
> > @@ -109,6 +166,9 @@ struct panthor_gem_object {
> >  	/** @dmap: Device mapping state */
> >  	struct panthor_gem_dev_map dmap;
> >  
> > +	/** @reclaim_state: Cached reclaim state */
> > +	enum panthor_gem_reclaim_state reclaim_state;
> > +
> >  	/**
> >  	 * @exclusive_vm_root_gem: Root GEM of the exclusive VM this GEM object
> >  	 * is attached to.
> > @@ -190,6 +250,13 @@ struct sg_table *
> >  panthor_gem_get_dev_sgt(struct panthor_gem_object *bo);
> >  int panthor_gem_pin(struct panthor_gem_object *bo);
> >  void panthor_gem_unpin(struct panthor_gem_object *bo);
> > +int panthor_gem_swapin_locked(struct panthor_gem_object *bo);
> > +void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
> > +					     enum panthor_gem_reclaim_state *old_state);
> > +bool panthor_gem_try_evict(struct drm_gem_object *obj,
> > +			   struct ww_acquire_ctx *ticket);
> > +int panthor_gem_shrinker_init(struct panthor_device *ptdev);
> > +void panthor_gem_shrinker_unplug(struct panthor_device *ptdev);
> >  
> >  void panthor_gem_bo_set_label(struct drm_gem_object *obj, const char *label);
> >  void panthor_gem_kernel_bo_set_label(struct panthor_kernel_bo *bo, const char *label);
> > diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> > index 3290e0b5facb..ffd821b3be46 100644
> > --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> > +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> > @@ -1,6 +1,7 @@
> >  // SPDX-License-Identifier: GPL-2.0 or MIT
> >  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
> >  /* Copyright 2023 Collabora ltd. */
> > +/* Copyright 2025 ARM Limited. All rights reserved. */
> >  
> >  #include <drm/drm_debugfs.h>
> >  #include <drm/drm_drv.h>
> > @@ -131,6 +132,9 @@ struct panthor_vma {
> >  	 * Only map related flags are accepted.
> >  	 */
> >  	u32 flags;
> > +
> > +	/** @evicted: True if the VMA has been evicted. */
> > +	bool evicted;
> >  };
> >  
> >  /**
> > @@ -191,13 +195,8 @@ struct panthor_vm_op_ctx {
> >  		/** @map.bo_offset: Offset in the buffer object. */
> >  		u64 bo_offset;
> >  
> > -		/**
> > -		 * @map.sgt: sg-table pointing to pages backing the GEM object.
> > -		 *
> > -		 * This is gathered at job creation time, such that we don't have
> > -		 * to allocate in ::run_job().
> > -		 */
> > -		struct sg_table *sgt;
> > +		/** @map.bo: the BO being mapped. */
> > +		struct panthor_gem_object *bo;
> >  
> >  		/**
> >  		 * @map.new_vma: The new VMA object that will be inserted to the VA tree.
> > @@ -385,6 +384,18 @@ struct panthor_vm {
> >  		/** @locked_region.size: Size of the locked region. */
> >  		u64 size;
> >  	} locked_region;
> > +
> > +	/** @reclaim: Fields related to BO reclaim. */
> > +	struct {
> > +		/** @reclaim.lru: LRU of BOs that are only mapped to this VM. */
> > +		struct drm_gem_lru lru;
> > +
> > +		/**
> > +		 * @reclaim.lru_node: Node used to insert the VM in
> > +		 * panthor_device::reclaim::vms.
> > +		 */
> > +		struct list_head lru_node;
> > +	} reclaim;
> >  };
> >  
> >  /**
> > @@ -678,6 +689,16 @@ int panthor_vm_active(struct panthor_vm *vm)
> >  	if (refcount_inc_not_zero(&vm->as.active_cnt))
> >  		goto out_dev_exit;
> >  
> > +	/* As soon as active is called, we place the VM as the end of the VM LRU.
> > +	 * If something fails after that, the only downside is that this VM that
> > +	 * never became active in the first place will be reclaimed last, but
> > +	 * that's an acceptable trade-off.
> > +	 */
> > +	mutex_lock(&ptdev->reclaim.lock);
> > +	if (vm->reclaim.lru.count)
> > +		list_move_tail(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> > +	mutex_unlock(&ptdev->reclaim.lock);
> > +
> >  	/* Make sure we don't race with lock/unlock_region() calls
> >  	 * happening around VM bind operations.
> >  	 */
> > @@ -1074,7 +1095,15 @@ static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo)
> >  {
> >  	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> >  
> > -	panthor_gem_unpin(bo);
> > +	/* We couldn't call this when we unlinked, because the resv lock can't
> > +	 * be taken in the dma signalling path, so call it now.
> > +	 */
> > +	dma_resv_lock(bo->base.resv, NULL);
> > +	mutex_lock(&bo->base.gpuva.lock);
> > +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +	mutex_unlock(&bo->base.gpuva.lock);
> > +	dma_resv_unlock(bo->base.resv);
> > +
> >  	kfree(vm_bo);
> >  }
> >  
> > @@ -1095,6 +1124,11 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx,
> >  	if (op_ctx->map.vm_bo)
> >  		drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo);
> >  
> > +	if (op_ctx->map.bo) {
> > +		panthor_gem_unpin(op_ctx->map.bo);
> > +		drm_gem_object_put(&op_ctx->map.bo->base);
> > +	}
> > +
> >  	for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++)
> >  		kfree(op_ctx->preallocated_vmas[i]);
> >  
> > @@ -1255,18 +1289,17 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
> >  	if (ret)
> >  		goto err_cleanup;
> >  
> > +	drm_gem_object_get(&bo->base);
> > +	op_ctx->map.bo = bo;
> > +
> >  	sgt = panthor_gem_get_dev_sgt(bo);
> >  	if (IS_ERR(sgt)) {
> > -		panthor_gem_unpin(bo);
> >  		ret = PTR_ERR(sgt);
> >  		goto err_cleanup;
> >  	}
> >  
> > -	op_ctx->map.sgt = sgt;
> > -
> >  	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base);
> >  	if (!preallocated_vm_bo) {
> > -		panthor_gem_unpin(bo);
> >  		ret = -ENOMEM;
> >  		goto err_cleanup;
> >  	}
> > @@ -1280,9 +1313,19 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
> >  	dma_resv_lock(panthor_vm_resv(vm), NULL);
> >  	mutex_lock(&bo->base.gpuva.lock);
> >  	op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
> > +	if (panthor_vm_resv(vm) == bo->base.resv)
> > +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> >  	mutex_unlock(&bo->base.gpuva.lock);
> >  	dma_resv_unlock(panthor_vm_resv(vm));
> >  
> > +	if (panthor_vm_resv(vm) != bo->base.resv) {
> > +		dma_resv_lock(bo->base.resv, NULL);
> > +		mutex_lock(&bo->base.gpuva.lock);
> > +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +		mutex_unlock(&bo->base.gpuva.lock);
> > +		dma_resv_unlock(bo->base.resv);
> > +	}
> > +
> >  	op_ctx->map.bo_offset = offset;
> >  
> >  	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
> > @@ -1891,6 +1934,10 @@ static void panthor_vm_free(struct drm_gpuvm *gpuvm)
> >  	struct panthor_vm *vm = container_of(gpuvm, struct panthor_vm, base);
> >  	struct panthor_device *ptdev = vm->ptdev;
> >  
> > +	mutex_lock(&ptdev->reclaim.lock);
> > +	list_del_init(&vm->reclaim.lru_node);
> > +	mutex_unlock(&ptdev->reclaim.lock);
> > +
> >  	mutex_lock(&vm->heaps.lock);
> >  	if (drm_WARN_ON(&ptdev->base, vm->heaps.pool))
> >  		panthor_heap_pool_destroy(vm->heaps.pool);
> > @@ -2104,7 +2151,7 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
> >  	panthor_vma_init(vma, op_ctx->flags & PANTHOR_VM_MAP_FLAGS);
> >  
> >  	ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags),
> > -				   op_ctx->map.sgt, op->map.gem.offset,
> > +				   op_ctx->map.bo->dmap.sgt, op->map.gem.offset,
> >  				   op->map.va.range);
> >  	if (ret) {
> >  		panthor_vm_op_ctx_return_vma(op_ctx, vma);
> > @@ -2188,8 +2235,10 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
> >  	 * atomicity. panthor_vm_lock_region() bails out early if the new region
> >  	 * is already part of the locked region, so no need to do this check here.
> >  	 */
> > -	panthor_vm_lock_region(vm, unmap_start, unmap_range);
> > -	panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
> > +	if (!unmap_vma->evicted) {
> > +		panthor_vm_lock_region(vm, unmap_start, unmap_range);
> > +		panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
> > +	}
> >  
> >  	if (op->remap.prev) {
> >  		struct panthor_gem_object *bo = to_panthor_bo(op->remap.prev->gem.obj);
> > @@ -2203,6 +2252,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
> >  
> >  		prev_vma = panthor_vm_op_ctx_get_vma(op_ctx);
> >  		panthor_vma_init(prev_vma, unmap_vma->flags);
> > +		prev_vma->evicted = unmap_vma->evicted;
> >  	}
> >  
> >  	if (op->remap.next) {
> > @@ -2217,6 +2267,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
> >  
> >  		next_vma = panthor_vm_op_ctx_get_vma(op_ctx);
> >  		panthor_vma_init(next_vma, unmap_vma->flags);
> > +		next_vma->evicted = unmap_vma->evicted;
> >  	}
> >  
> >  	drm_gpuva_remap(prev_vma ? &prev_vma->base : NULL,
> > @@ -2246,19 +2297,197 @@ static int panthor_gpuva_sm_step_unmap(struct drm_gpuva_op *op,
> >  	struct panthor_vma *unmap_vma = container_of(op->unmap.va, struct panthor_vma, base);
> >  	struct panthor_vm *vm = priv;
> >  
> > -	panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
> > -			       unmap_vma->base.va.range);
> > +	if (!unmap_vma->evicted) {
> > +		panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
> > +				       unmap_vma->base.va.range);
> > +	}
> > +
> >  	drm_gpuva_unmap(&op->unmap);
> >  	panthor_vma_unlink(unmap_vma);
> >  	return 0;
> >  }
> >  
> > +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
> > +{
> > +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> > +	struct panthor_vm *vm = NULL;
> > +	struct drm_gpuvm_bo *vm_bo;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +	lockdep_assert_held(&bo->base.gpuva.lock);
> > +
> > +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> > +		/* We're only supposed to have one vm_bo in the list if we get there. */
> > +		drm_WARN_ON(&ptdev->base, vm);
> > +		vm = container_of(vm_bo->vm, struct panthor_vm, base);
> > +
> > +		mutex_lock(&ptdev->reclaim.lock);
> > +		drm_gem_lru_move_tail_locked(&vm->reclaim.lru, &bo->base);
> > +		if (list_empty(&vm->reclaim.lru_node))
> > +			list_move(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> > +		mutex_unlock(&ptdev->reclaim.lock);
> > +	}
> > +}
> > +
> > +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
> > +{
> > +	struct drm_gpuvm_bo *vm_bo;
> > +
> > +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> > +		struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> > +		struct drm_gpuva *va;
> > +
> > +		if (!mutex_trylock(&vm->op_lock))
> > +			return -EDEADLK;
> > +
> > +		drm_gpuvm_bo_evict(vm_bo, true);
> > +		drm_gpuvm_bo_for_each_va(va, vm_bo) {
> > +			struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> > +
> > +			panthor_vm_lock_region(vm, va->va.addr, va->va.range);
> > +			panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);
> > +			panthor_vm_unlock_region(vm);
> > +			vma->evicted = true;
> > +		}
> > +
> > +		mutex_unlock(&vm->op_lock);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static struct panthor_vma *select_evicted_vma(struct drm_gpuvm_bo *vm_bo,
> > +					      struct panthor_vm_op_ctx *op_ctx)
> > +{
> > +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> > +	struct panthor_vma *first_evicted_vma = NULL;
> > +	struct drm_gpuva *va;
> > +
> > +	/* Take op_lock to protect against va insertion/removal. */
> > +	mutex_lock(&vm->op_lock);
> > +	drm_gpuvm_bo_for_each_va(va, vm_bo) {
> > +		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> > +
> > +		if (vma->evicted) {
> > +			first_evicted_vma = vma;
> > +			panthor_vm_init_op_ctx(op_ctx, va->va.range, va->va.addr, vma->flags);
> > +			op_ctx->map.bo_offset = va->gem.offset;
> > +			break;
> > +		}
> > +	}
> > +	mutex_unlock(&vm->op_lock);
> > +
> > +	return first_evicted_vma;
> > +}
> > +
> > +static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
> > +			     struct panthor_vma *evicted_vma,
> > +			     struct panthor_vm_op_ctx *op_ctx)
> > +{
> > +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> > +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> > +	struct drm_gpuva *va;
> > +	bool found = false;
> > +	int ret;
> > +
> > +	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
> > +	if (ret)
> > +		goto out_cleanup;
> > +
> > +	/* Take op_lock to protect against va insertion/removal. */
> > +	mutex_lock(&vm->op_lock);
> > +	drm_gpuvm_bo_for_each_va(va, vm_bo) {
> > +		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> > +
> > +		if (vma != evicted_vma)
> > +			continue;
> > +
> > +		/* We can't rely solely on pointer equality, because the VMA might have been
> > +		 * freed and a new one allocated at the same address. If the evicted bit
> > +		 * is still set, we're sure it's our VMA, because population/eviction is
> > +		 * serialized with the BO resv lock.
> > +		 */
> > +		if (vma->evicted)
> > +			found = true;
> > +
> > +		break;
> > +	}
> > +
> > +	if (found) {
> > +		vm->op_ctx = op_ctx;
> > +		ret = panthor_vm_lock_region(vm, evicted_vma->base.va.addr,
> > +					     evicted_vma->base.va.range);
> > +		if (!ret) {
> > +			ret = panthor_vm_map_pages(vm, evicted_vma->base.va.addr,
> > +						   flags_to_prot(evicted_vma->flags),
> > +						   bo->dmap.sgt,
> > +						   evicted_vma->base.gem.offset,
> > +						   evicted_vma->base.va.range);
> > +		}
> > +
> > +		if (!ret)
> > +			evicted_vma->evicted = false;
> > +
> > +		panthor_vm_unlock_region(vm);
> > +		vm->op_ctx = NULL;
> > +	}
> > +
> > +	mutex_unlock(&vm->op_lock);
> > +
> > +out_cleanup:
> > +	panthor_vm_cleanup_op_ctx(op_ctx, vm);
> > +	return ret;
> > +}
> > +
> > +static int panthor_vm_restore_vmas(struct drm_gpuvm_bo *vm_bo)
> > +{
> > +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> > +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> > +	struct panthor_vm_op_ctx op_ctx;
> > +
> > +	if (drm_WARN_ON_ONCE(&vm->ptdev->base, !bo->dmap.sgt))
> > +		return -EINVAL;
> > +
> > +	for (struct panthor_vma *vma = select_evicted_vma(vm_bo, &op_ctx);
> > +	     vma; vma = select_evicted_vma(vm_bo, &op_ctx)) {
> > +		int ret;
> > +
> > +		ret = remap_evicted_vma(vm_bo, vma, &op_ctx);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int panthor_vm_bo_validate(struct drm_gpuvm_bo *vm_bo,
> > +				  struct drm_exec *exec)
> > +{
> > +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> > +	int ret;
> > +
> > +	ret = panthor_gem_swapin_locked(bo);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = panthor_vm_restore_vmas(vm_bo);
> > +	if (ret)
> > +		return ret;
> > +
> > +	drm_gpuvm_bo_evict(vm_bo, false);
> > +	mutex_lock(&bo->base.gpuva.lock);
> > +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +	mutex_unlock(&bo->base.gpuva.lock);
> > +	return 0;
> > +}
> > +
> >  static const struct drm_gpuvm_ops panthor_gpuvm_ops = {
> >  	.vm_free = panthor_vm_free,
> >  	.vm_bo_free = panthor_vm_bo_free,
> >  	.sm_step_map = panthor_gpuva_sm_step_map,
> >  	.sm_step_remap = panthor_gpuva_sm_step_remap,
> >  	.sm_step_unmap = panthor_gpuva_sm_step_unmap,
> > +	.vm_bo_validate = panthor_vm_bo_validate,
> >  };
> >  
> >  /**
> > @@ -2473,6 +2702,8 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
> >  	vm->kernel_auto_va.start = auto_kernel_va_start;
> >  	vm->kernel_auto_va.end = vm->kernel_auto_va.start + auto_kernel_va_size - 1;
> >  
> > +	drm_gem_lru_init(&vm->reclaim.lru, &ptdev->reclaim.lock);
> > +	INIT_LIST_HEAD(&vm->reclaim.lru_node);
> >  	INIT_LIST_HEAD(&vm->node);
> >  	INIT_LIST_HEAD(&vm->as.lru_node);
> >  	vm->as.id = -1;
> > @@ -2820,7 +3051,78 @@ int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec, struct panthor_vm
> >  	if (ret)
> >  		return ret;
> >  
> > -	return drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
> > +	ret = drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return drm_gpuvm_validate(&vm->base, exec);
> > +}
> > +
> > +unsigned long
> > +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
> > +			     unsigned int nr_to_scan, unsigned long *remaining,
> > +			     bool (*shrink)(struct drm_gem_object *,
> > +					    struct ww_acquire_ctx *))
> > +{
> > +	unsigned long freed = 0;
> > +	LIST_HEAD(remaining_vms);
> > +	LIST_HEAD(vms);
> > +
> > +	mutex_lock(&ptdev->reclaim.lock);
> > +	list_splice_init(&ptdev->reclaim.vms, &vms);
> > +
> > +	while (freed < nr_to_scan) {
> > +		struct panthor_vm *vm;
> > +
> > +		vm = list_first_entry_or_null(&vms, typeof(*vm),
> > +					      reclaim.lru_node);
> > +		if (!vm)
> > +			break;
> > +
> > +		if (!kref_get_unless_zero(&vm->base.kref)) {
> > +			list_del_init(&vm->reclaim.lru_node);
> > +			continue;
> > +		}
> > +
> > +		mutex_unlock(&ptdev->reclaim.lock);
> > +
> > +		freed += drm_gem_lru_scan(&vm->reclaim.lru, nr_to_scan - freed,
> > +					  remaining, shrink, NULL);
> > +
> > +		mutex_lock(&ptdev->reclaim.lock);
> > +
> > +		/* If the VM is still in the temporary list, remove it so we
> > +		 * can proceed with the next VM.
> > +		 */
> > +		if (vm->reclaim.lru_node.prev == &vms) {
> > +			list_del_init(&vm->reclaim.lru_node);
> > +
> > +			/* Keep the VM around if there are still things to
> > +			 * reclaim, so we can preserve the LRU order when
> > +			 * re-inserting in ptdev->reclaim.vms at the end.
> > +			 */
> > +			if (vm->reclaim.lru.count > 0)
> > +				list_add_tail(&vm->reclaim.lru_node, &remaining_vms);
> > +		}
> > +
> > +		mutex_unlock(&ptdev->reclaim.lock);
> > +
> > +		panthor_vm_put(vm);
> > +
> > +		mutex_lock(&ptdev->reclaim.lock);
> > +	}
> > +
> > +	/* Re-insert VMs with remaining data to reclaim at the beginning of
> > +	 * the LRU. Note that any activeness change on the VM that happened
> > +	 * while we were reclaiming would have moved the VM out of our
> > +	 * temporary [remaining_]vms list, meaning anything we re-insert here
> > +	 * preserves the LRU order.
> > +	 */
> > +	list_splice_tail(&vms, &remaining_vms);
> > +	list_splice(&remaining_vms, &ptdev->reclaim.vms);
> > +	mutex_unlock(&ptdev->reclaim.lock);
> > +
> > +	return freed;
> >  }
> >  
> >  /**
> > diff --git a/drivers/gpu/drm/panthor/panthor_mmu.h b/drivers/gpu/drm/panthor/panthor_mmu.h
> > index 0e268fdfdb2f..3522fbbce369 100644
> > --- a/drivers/gpu/drm/panthor/panthor_mmu.h
> > +++ b/drivers/gpu/drm/panthor/panthor_mmu.h
> > @@ -1,6 +1,7 @@
> >  /* SPDX-License-Identifier: GPL-2.0 or MIT */
> >  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
> >  /* Copyright 2023 Collabora ltd. */
> > +/* Copyright 2025 ARM Limited. All rights reserved. */
> >  
> >  #ifndef __PANTHOR_MMU_H__
> >  #define __PANTHOR_MMU_H__
> > @@ -46,6 +47,13 @@ struct panthor_vm *panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
> >  				     u64 kernel_auto_va_start,
> >  				     u64 kernel_auto_va_size);
> >  
> > +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo);
> > +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo);
> > +unsigned long
> > +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
> > +			     unsigned int nr_to_scan, unsigned long *remaining,
> > +			     bool (*shrink)(struct drm_gem_object *,
> > +					    struct ww_acquire_ctx *));
> >  int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec,
> >  					struct panthor_vm *vm,
> >  					u32 slot_count);  
> 
> I *think* there's an issue with objects being evicted then accessed by
> mmap() or vmap. There's a call to drm_gpuvm_bo_evict(..., true) in
> panthor_vm_evict_bo_mappings_locked() to evict, but the only
> "de-eviction" (i.e. drm_gpuvm_bo_evict(..., false)) is
> panthor_vm_bo_validate() which is called on the submission path but not
> from other paths.

So, we don't allow vmap() reclaims yet (we pin on vmap()). That's
something to add as a follow-up if we really care (MSM has something
for that), but there's not many BOs that are vmap()-ed for a long period
of time other than FW ones, and most of those can't be reclaimed anyway,
expect maybe for the CSG buffers, but those should account for a very
minimal of memory compared to the rest.

For mmap(), I'd expect the eviction to kill the user mappings, causing
a fault on the next access, at which point we do repopulate. Now, we
don't set the evicted bit back to zero just yet, because the GPU
mapping is still gone, but this means the de-eviction on the next
submit will only have half of the work to do (creating the sgt, and
updating the page table).

> 
> If this is right then on the next submission panthor_gem_swapin_locked()
> will it the WARN_ON for the pin_count being non-zero.

I see. So if the BO is vmap()ed before the next submit we will
indeed hit this path. Maybe we should get rid of this WARN_ON() and keep
going instead of returning EINVAL in that case.

> 
> I have to admit to being very unsure about all of this - I even resorted
> to asking AI, which I think has made me more confused ;)

I think you're right that we shouldn't complain+fail if pin_count > 0
in the de-eviction path. As I said above, de-eviction for the CPU is not
the same as de-eviction for the GPU, so pages being present and pinned
doesn't mean we have nothing to do for the GPU mapping to be restored.
Maybe we should come with a better name for this function.

Thanks for the review!

Boris

[1]https://gitlab.freedesktop.org/bbrezillon/igt-gpu-tools/-/commit/fc76934a5579767d2aabe787d40e38a17c3f4ea4

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-15 10:50     ` Boris Brezillon
@ 2026-01-15 11:24       ` Steven Price
  2026-01-15 12:01         ` Boris Brezillon
  0 siblings, 1 reply; 62+ messages in thread
From: Steven Price @ 2026-01-15 11:24 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On 15/01/2026 10:50, Boris Brezillon wrote:
> Hi Steve,
> 
> On Wed, 14 Jan 2026 15:05:36 +0000
> Steven Price <steven.price@arm.com> wrote:
> 
>> On 09/01/2026 13:08, Boris Brezillon wrote:
[...]
>>> @@ -1250,10 +1637,42 @@ static struct drm_info_list panthor_gem_debugfs_list[] = {
>>>  	{ "gems", panthor_gem_show_bos, 0, NULL },
>>>  };
>>>  
>>> +static int shrink_get(void *data, u64 *val)
>>> +{
>>> +	struct panthor_device *ptdev =
>>> +		container_of(data, struct panthor_device, base);
>>> +
>>> +	*val = ptdev->reclaim.nr_pages_reclaimed_on_last_scan;
>>> +	return 0;
>>> +}
>>> +
>>> +static int shrink_set(void *data, u64 val)
>>> +{
>>> +	struct panthor_device *ptdev =
>>> +		container_of(data, struct panthor_device, base);
>>> +	struct shrink_control sc = {
>>> +		.gfp_mask = GFP_KERNEL,
>>> +		.nr_to_scan = val,
>>> +	};
>>> +
>>> +	fs_reclaim_acquire(GFP_KERNEL);
>>> +	if (ptdev->reclaim.shrinker)
>>> +		panthor_gem_shrinker_scan(ptdev->reclaim.shrinker, &sc);
>>> +	fs_reclaim_release(GFP_KERNEL);
>>> +
>>> +	return 0;
>>> +}  
>>
>> Do you have some test to drive this?
> 
> Yes, I do [1].
> 
>> My immediate thought was that it
>> would be nice (for manual testing at least) to printk the return value
>> from panthor_gem_shrinker_scan(). But we probably wouldn't actually need
>> nr_pages_reclaimed_on_last_scan if you could just read that from dmesg.
>> But I can see integrating that into a test might not be ideal.
> 
> I basically based the interface on the existing MSM one. Might not be
> the best, but it was good enough for this initial testing.

Ah well if we're matching MSM then that's probably a good justification.
It just seemed a little odd throwing away the return value and then
having to have a separate mechanism to get the number of pages reclaimed
out. And given I'd just spotted a bug in the return value I thought I'd
ask ;)

>>
>>> +
>>> +DEFINE_DEBUGFS_ATTRIBUTE(panthor_gem_debugfs_shrink_fops,
>>> +			 shrink_get, shrink_set,
>>> +			 "0x%08llx\n");
>>> +
>>>  void panthor_gem_debugfs_init(struct drm_minor *minor)
>>>  {
>>>  	drm_debugfs_create_files(panthor_gem_debugfs_list,
>>>  				 ARRAY_SIZE(panthor_gem_debugfs_list),
>>>  				 minor->debugfs_root, minor);
>>> +	debugfs_create_file("shrink", 0600, minor->debugfs_root,
>>> +			    minor->dev, &panthor_gem_debugfs_shrink_fops);
>>>  }
>>>  #endif
>>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
>>> index c0a18dca732c..6cb5b597ff1e 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_gem.h
>>> +++ b/drivers/gpu/drm/panthor/panthor_gem.h
>>> @@ -1,6 +1,7 @@
>>>  /* SPDX-License-Identifier: GPL-2.0 or MIT */
>>>  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>>>  /* Copyright 2023 Collabora ltd. */
>>> +/* Copyright 2025 ARM Limited. All rights reserved. */
>>>  
>>>  #ifndef __PANTHOR_GEM_H__
>>>  #define __PANTHOR_GEM_H__
>>> @@ -93,6 +94,62 @@ struct panthor_gem_dev_map {
>>>  	struct sg_table *sgt;
>>>  };
>>>  
>>> +/**
>>> + * enum panthor_gem_reclaim_state - Reclaim state of a GEM object
>>> + *
>>> + * This is defined in descending reclaimability order and some part
>>> + * of the code depends on that.
>>> + */
>>> +enum panthor_gem_reclaim_state {
>>> +	/**
>>> +	 * @PANTHOR_GEM_UNUSED: GEM is currently unused
>>> +	 *
>>> +	 * This can happen when the GEM was previously vmap-ed, mmap-ed,
>>> +	 * and/or GPU mapped and got unmapped. Because pages are lazily
>>> +	 * returned to the shmem layer, we want to keep a list of such
>>> +	 * BOs, because they should be fairly easy to reclaim (no need
>>> +	 * to wait for GPU to be done, and no need to tear down user
>>> +	 * mappings either).
>>> +	 */
>>> +	PANTHOR_GEM_UNUSED,
>>> +
>>> +	/**
>>> +	 * @PANTHOR_GEM_MMAPPED: GEM is currently mmap-ed
>>> +	 *
>>> +	 * When a GEM has pages allocated and the mmap_count is > 0, the
>>> +	 * GEM is placed in the mmapped list. This comes right after
>>> +	 * unused because we can relatively easily tear down user mappings.
>>> +	 */
>>> +	PANTHOR_GEM_MMAPPED,
>>> +
>>> +	/**
>>> +	 * @PANTHOR_GEM_GPU_MAPPED_PRIVATE: GEM is GPU mapped to only one VM
>>> +	 *
>>> +	 * When a GEM is mapped to a single VM, reclaim requests have more
>>> +	 * chances to succeed, because we only need to synchronize against
>>> +	 * a single GPU context. This is more annoying than reclaiming
>>> +	 * mmap-ed pages still, because we have to wait for in-flight jobs
>>> +	 * to land, and we might not be able to acquire all necessary locks
>>> +	 * at reclaim time either.
>>> +	 */
>>> +	PANTHOR_GEM_GPU_MAPPED_PRIVATE,
>>> +
>>> +	/**
>>> +	 * @PANTHOR_GEM_GPU_MAPPED_SHARED: GEM is GPU mapped to multiple VMs
>>> +	 *
>>> +	 * Like PANTHOR_GEM_GPU_MAPPED_PRIVATE, but the synchronization across
>>> +	 * VMs makes such BOs harder to reclaim.
>>> +	 */
>>> +	PANTHOR_GEM_GPU_MAPPED_SHARED,
>>> +
>>> +	/**
>>> +	 * @PANTHOR_GEM_UNRECLAIMABLE: GEM can't be reclaimed
>>> +	 *
>>> +	 * Happens when the GEM memory is pinned.  
>>
>> Also the initial state when creating a GEM object (which I found
>> non-obvious at least).
> 
> It's mostly because GEMs start being unpopulated, so there's nothing to
> reclaim (hence the unreclaimable) until pages are requested. I'll add a
> sentence to make that clear.

Yeah it makes perfect sense - I'd just read the descriptions and none
had mentioned being the start state so I'd assumed '0' and "unused"
makes some sense.

However (and you've already got a comment above that I didn't think
through when I first read the code) these are actually sorted in
reclaimability and fairly obvious a just created GEM object isn't
reclaimable because there's nothing to reclaim. So a comment here would
be appreciated for the future when I've forgotten everything ;)

>>
>>> +	 */
>>> +	PANTHOR_GEM_UNRECLAIMABLE,
>>> +};
>>> +
>>>  /**
>>>   * struct panthor_gem_object - Driver specific GEM object.
>>>   */
>>> @@ -109,6 +166,9 @@ struct panthor_gem_object {
>>>  	/** @dmap: Device mapping state */
>>>  	struct panthor_gem_dev_map dmap;
>>>  
>>> +	/** @reclaim_state: Cached reclaim state */
>>> +	enum panthor_gem_reclaim_state reclaim_state;
>>> +
>>>  	/**
>>>  	 * @exclusive_vm_root_gem: Root GEM of the exclusive VM this GEM object
>>>  	 * is attached to.
>>> @@ -190,6 +250,13 @@ struct sg_table *
>>>  panthor_gem_get_dev_sgt(struct panthor_gem_object *bo);
>>>  int panthor_gem_pin(struct panthor_gem_object *bo);
>>>  void panthor_gem_unpin(struct panthor_gem_object *bo);
>>> +int panthor_gem_swapin_locked(struct panthor_gem_object *bo);
>>> +void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
>>> +					     enum panthor_gem_reclaim_state *old_state);
>>> +bool panthor_gem_try_evict(struct drm_gem_object *obj,
>>> +			   struct ww_acquire_ctx *ticket);
>>> +int panthor_gem_shrinker_init(struct panthor_device *ptdev);
>>> +void panthor_gem_shrinker_unplug(struct panthor_device *ptdev);
>>>  
>>>  void panthor_gem_bo_set_label(struct drm_gem_object *obj, const char *label);
>>>  void panthor_gem_kernel_bo_set_label(struct panthor_kernel_bo *bo, const char *label);
>>> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
>>> index 3290e0b5facb..ffd821b3be46 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
>>> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
>>> @@ -1,6 +1,7 @@
>>>  // SPDX-License-Identifier: GPL-2.0 or MIT
>>>  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>>>  /* Copyright 2023 Collabora ltd. */
>>> +/* Copyright 2025 ARM Limited. All rights reserved. */
>>>  
>>>  #include <drm/drm_debugfs.h>
>>>  #include <drm/drm_drv.h>
>>> @@ -131,6 +132,9 @@ struct panthor_vma {
>>>  	 * Only map related flags are accepted.
>>>  	 */
>>>  	u32 flags;
>>> +
>>> +	/** @evicted: True if the VMA has been evicted. */
>>> +	bool evicted;
>>>  };
>>>  
>>>  /**
>>> @@ -191,13 +195,8 @@ struct panthor_vm_op_ctx {
>>>  		/** @map.bo_offset: Offset in the buffer object. */
>>>  		u64 bo_offset;
>>>  
>>> -		/**
>>> -		 * @map.sgt: sg-table pointing to pages backing the GEM object.
>>> -		 *
>>> -		 * This is gathered at job creation time, such that we don't have
>>> -		 * to allocate in ::run_job().
>>> -		 */
>>> -		struct sg_table *sgt;
>>> +		/** @map.bo: the BO being mapped. */
>>> +		struct panthor_gem_object *bo;
>>>  
>>>  		/**
>>>  		 * @map.new_vma: The new VMA object that will be inserted to the VA tree.
>>> @@ -385,6 +384,18 @@ struct panthor_vm {
>>>  		/** @locked_region.size: Size of the locked region. */
>>>  		u64 size;
>>>  	} locked_region;
>>> +
>>> +	/** @reclaim: Fields related to BO reclaim. */
>>> +	struct {
>>> +		/** @reclaim.lru: LRU of BOs that are only mapped to this VM. */
>>> +		struct drm_gem_lru lru;
>>> +
>>> +		/**
>>> +		 * @reclaim.lru_node: Node used to insert the VM in
>>> +		 * panthor_device::reclaim::vms.
>>> +		 */
>>> +		struct list_head lru_node;
>>> +	} reclaim;
>>>  };
>>>  
>>>  /**
>>> @@ -678,6 +689,16 @@ int panthor_vm_active(struct panthor_vm *vm)
>>>  	if (refcount_inc_not_zero(&vm->as.active_cnt))
>>>  		goto out_dev_exit;
>>>  
>>> +	/* As soon as active is called, we place the VM as the end of the VM LRU.
>>> +	 * If something fails after that, the only downside is that this VM that
>>> +	 * never became active in the first place will be reclaimed last, but
>>> +	 * that's an acceptable trade-off.
>>> +	 */
>>> +	mutex_lock(&ptdev->reclaim.lock);
>>> +	if (vm->reclaim.lru.count)
>>> +		list_move_tail(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
>>> +	mutex_unlock(&ptdev->reclaim.lock);
>>> +
>>>  	/* Make sure we don't race with lock/unlock_region() calls
>>>  	 * happening around VM bind operations.
>>>  	 */
>>> @@ -1074,7 +1095,15 @@ static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo)
>>>  {
>>>  	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
>>>  
>>> -	panthor_gem_unpin(bo);
>>> +	/* We couldn't call this when we unlinked, because the resv lock can't
>>> +	 * be taken in the dma signalling path, so call it now.
>>> +	 */
>>> +	dma_resv_lock(bo->base.resv, NULL);
>>> +	mutex_lock(&bo->base.gpuva.lock);
>>> +	panthor_gem_update_reclaim_state_locked(bo, NULL);
>>> +	mutex_unlock(&bo->base.gpuva.lock);
>>> +	dma_resv_unlock(bo->base.resv);
>>> +
>>>  	kfree(vm_bo);
>>>  }
>>>  
>>> @@ -1095,6 +1124,11 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>>>  	if (op_ctx->map.vm_bo)
>>>  		drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo);
>>>  
>>> +	if (op_ctx->map.bo) {
>>> +		panthor_gem_unpin(op_ctx->map.bo);
>>> +		drm_gem_object_put(&op_ctx->map.bo->base);
>>> +	}
>>> +
>>>  	for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++)
>>>  		kfree(op_ctx->preallocated_vmas[i]);
>>>  
>>> @@ -1255,18 +1289,17 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>>>  	if (ret)
>>>  		goto err_cleanup;
>>>  
>>> +	drm_gem_object_get(&bo->base);
>>> +	op_ctx->map.bo = bo;
>>> +
>>>  	sgt = panthor_gem_get_dev_sgt(bo);
>>>  	if (IS_ERR(sgt)) {
>>> -		panthor_gem_unpin(bo);
>>>  		ret = PTR_ERR(sgt);
>>>  		goto err_cleanup;
>>>  	}
>>>  
>>> -	op_ctx->map.sgt = sgt;
>>> -
>>>  	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base);
>>>  	if (!preallocated_vm_bo) {
>>> -		panthor_gem_unpin(bo);
>>>  		ret = -ENOMEM;
>>>  		goto err_cleanup;
>>>  	}
>>> @@ -1280,9 +1313,19 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>>>  	dma_resv_lock(panthor_vm_resv(vm), NULL);
>>>  	mutex_lock(&bo->base.gpuva.lock);
>>>  	op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
>>> +	if (panthor_vm_resv(vm) == bo->base.resv)
>>> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
>>>  	mutex_unlock(&bo->base.gpuva.lock);
>>>  	dma_resv_unlock(panthor_vm_resv(vm));
>>>  
>>> +	if (panthor_vm_resv(vm) != bo->base.resv) {
>>> +		dma_resv_lock(bo->base.resv, NULL);
>>> +		mutex_lock(&bo->base.gpuva.lock);
>>> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
>>> +		mutex_unlock(&bo->base.gpuva.lock);
>>> +		dma_resv_unlock(bo->base.resv);
>>> +	}
>>> +
>>>  	op_ctx->map.bo_offset = offset;
>>>  
>>>  	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
>>> @@ -1891,6 +1934,10 @@ static void panthor_vm_free(struct drm_gpuvm *gpuvm)
>>>  	struct panthor_vm *vm = container_of(gpuvm, struct panthor_vm, base);
>>>  	struct panthor_device *ptdev = vm->ptdev;
>>>  
>>> +	mutex_lock(&ptdev->reclaim.lock);
>>> +	list_del_init(&vm->reclaim.lru_node);
>>> +	mutex_unlock(&ptdev->reclaim.lock);
>>> +
>>>  	mutex_lock(&vm->heaps.lock);
>>>  	if (drm_WARN_ON(&ptdev->base, vm->heaps.pool))
>>>  		panthor_heap_pool_destroy(vm->heaps.pool);
>>> @@ -2104,7 +2151,7 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
>>>  	panthor_vma_init(vma, op_ctx->flags & PANTHOR_VM_MAP_FLAGS);
>>>  
>>>  	ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags),
>>> -				   op_ctx->map.sgt, op->map.gem.offset,
>>> +				   op_ctx->map.bo->dmap.sgt, op->map.gem.offset,
>>>  				   op->map.va.range);
>>>  	if (ret) {
>>>  		panthor_vm_op_ctx_return_vma(op_ctx, vma);
>>> @@ -2188,8 +2235,10 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>>>  	 * atomicity. panthor_vm_lock_region() bails out early if the new region
>>>  	 * is already part of the locked region, so no need to do this check here.
>>>  	 */
>>> -	panthor_vm_lock_region(vm, unmap_start, unmap_range);
>>> -	panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
>>> +	if (!unmap_vma->evicted) {
>>> +		panthor_vm_lock_region(vm, unmap_start, unmap_range);
>>> +		panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
>>> +	}
>>>  
>>>  	if (op->remap.prev) {
>>>  		struct panthor_gem_object *bo = to_panthor_bo(op->remap.prev->gem.obj);
>>> @@ -2203,6 +2252,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>>>  
>>>  		prev_vma = panthor_vm_op_ctx_get_vma(op_ctx);
>>>  		panthor_vma_init(prev_vma, unmap_vma->flags);
>>> +		prev_vma->evicted = unmap_vma->evicted;
>>>  	}
>>>  
>>>  	if (op->remap.next) {
>>> @@ -2217,6 +2267,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>>>  
>>>  		next_vma = panthor_vm_op_ctx_get_vma(op_ctx);
>>>  		panthor_vma_init(next_vma, unmap_vma->flags);
>>> +		next_vma->evicted = unmap_vma->evicted;
>>>  	}
>>>  
>>>  	drm_gpuva_remap(prev_vma ? &prev_vma->base : NULL,
>>> @@ -2246,19 +2297,197 @@ static int panthor_gpuva_sm_step_unmap(struct drm_gpuva_op *op,
>>>  	struct panthor_vma *unmap_vma = container_of(op->unmap.va, struct panthor_vma, base);
>>>  	struct panthor_vm *vm = priv;
>>>  
>>> -	panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
>>> -			       unmap_vma->base.va.range);
>>> +	if (!unmap_vma->evicted) {
>>> +		panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
>>> +				       unmap_vma->base.va.range);
>>> +	}
>>> +
>>>  	drm_gpuva_unmap(&op->unmap);
>>>  	panthor_vma_unlink(unmap_vma);
>>>  	return 0;
>>>  }
>>>  
>>> +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
>>> +	struct panthor_vm *vm = NULL;
>>> +	struct drm_gpuvm_bo *vm_bo;
>>> +
>>> +	dma_resv_assert_held(bo->base.resv);
>>> +	lockdep_assert_held(&bo->base.gpuva.lock);
>>> +
>>> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
>>> +		/* We're only supposed to have one vm_bo in the list if we get there. */
>>> +		drm_WARN_ON(&ptdev->base, vm);
>>> +		vm = container_of(vm_bo->vm, struct panthor_vm, base);
>>> +
>>> +		mutex_lock(&ptdev->reclaim.lock);
>>> +		drm_gem_lru_move_tail_locked(&vm->reclaim.lru, &bo->base);
>>> +		if (list_empty(&vm->reclaim.lru_node))
>>> +			list_move(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
>>> +		mutex_unlock(&ptdev->reclaim.lock);
>>> +	}
>>> +}
>>> +
>>> +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	struct drm_gpuvm_bo *vm_bo;
>>> +
>>> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
>>> +		struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
>>> +		struct drm_gpuva *va;
>>> +
>>> +		if (!mutex_trylock(&vm->op_lock))
>>> +			return -EDEADLK;
>>> +
>>> +		drm_gpuvm_bo_evict(vm_bo, true);
>>> +		drm_gpuvm_bo_for_each_va(va, vm_bo) {
>>> +			struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
>>> +
>>> +			panthor_vm_lock_region(vm, va->va.addr, va->va.range);
>>> +			panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);
>>> +			panthor_vm_unlock_region(vm);
>>> +			vma->evicted = true;
>>> +		}
>>> +
>>> +		mutex_unlock(&vm->op_lock);
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static struct panthor_vma *select_evicted_vma(struct drm_gpuvm_bo *vm_bo,
>>> +					      struct panthor_vm_op_ctx *op_ctx)
>>> +{
>>> +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
>>> +	struct panthor_vma *first_evicted_vma = NULL;
>>> +	struct drm_gpuva *va;
>>> +
>>> +	/* Take op_lock to protect against va insertion/removal. */
>>> +	mutex_lock(&vm->op_lock);
>>> +	drm_gpuvm_bo_for_each_va(va, vm_bo) {
>>> +		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
>>> +
>>> +		if (vma->evicted) {
>>> +			first_evicted_vma = vma;
>>> +			panthor_vm_init_op_ctx(op_ctx, va->va.range, va->va.addr, vma->flags);
>>> +			op_ctx->map.bo_offset = va->gem.offset;
>>> +			break;
>>> +		}
>>> +	}
>>> +	mutex_unlock(&vm->op_lock);
>>> +
>>> +	return first_evicted_vma;
>>> +}
>>> +
>>> +static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
>>> +			     struct panthor_vma *evicted_vma,
>>> +			     struct panthor_vm_op_ctx *op_ctx)
>>> +{
>>> +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
>>> +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
>>> +	struct drm_gpuva *va;
>>> +	bool found = false;
>>> +	int ret;
>>> +
>>> +	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
>>> +	if (ret)
>>> +		goto out_cleanup;
>>> +
>>> +	/* Take op_lock to protect against va insertion/removal. */
>>> +	mutex_lock(&vm->op_lock);
>>> +	drm_gpuvm_bo_for_each_va(va, vm_bo) {
>>> +		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
>>> +
>>> +		if (vma != evicted_vma)
>>> +			continue;
>>> +
>>> +		/* We can't rely solely on pointer equality, because the VMA might have been
>>> +		 * freed and a new one allocated at the same address. If the evicted bit
>>> +		 * is still set, we're sure it's our VMA, because population/eviction is
>>> +		 * serialized with the BO resv lock.
>>> +		 */
>>> +		if (vma->evicted)
>>> +			found = true;
>>> +
>>> +		break;
>>> +	}
>>> +
>>> +	if (found) {
>>> +		vm->op_ctx = op_ctx;
>>> +		ret = panthor_vm_lock_region(vm, evicted_vma->base.va.addr,
>>> +					     evicted_vma->base.va.range);
>>> +		if (!ret) {
>>> +			ret = panthor_vm_map_pages(vm, evicted_vma->base.va.addr,
>>> +						   flags_to_prot(evicted_vma->flags),
>>> +						   bo->dmap.sgt,
>>> +						   evicted_vma->base.gem.offset,
>>> +						   evicted_vma->base.va.range);
>>> +		}
>>> +
>>> +		if (!ret)
>>> +			evicted_vma->evicted = false;
>>> +
>>> +		panthor_vm_unlock_region(vm);
>>> +		vm->op_ctx = NULL;
>>> +	}
>>> +
>>> +	mutex_unlock(&vm->op_lock);
>>> +
>>> +out_cleanup:
>>> +	panthor_vm_cleanup_op_ctx(op_ctx, vm);
>>> +	return ret;
>>> +}
>>> +
>>> +static int panthor_vm_restore_vmas(struct drm_gpuvm_bo *vm_bo)
>>> +{
>>> +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
>>> +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
>>> +	struct panthor_vm_op_ctx op_ctx;
>>> +
>>> +	if (drm_WARN_ON_ONCE(&vm->ptdev->base, !bo->dmap.sgt))
>>> +		return -EINVAL;
>>> +
>>> +	for (struct panthor_vma *vma = select_evicted_vma(vm_bo, &op_ctx);
>>> +	     vma; vma = select_evicted_vma(vm_bo, &op_ctx)) {
>>> +		int ret;
>>> +
>>> +		ret = remap_evicted_vma(vm_bo, vma, &op_ctx);
>>> +		if (ret)
>>> +			return ret;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int panthor_vm_bo_validate(struct drm_gpuvm_bo *vm_bo,
>>> +				  struct drm_exec *exec)
>>> +{
>>> +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
>>> +	int ret;
>>> +
>>> +	ret = panthor_gem_swapin_locked(bo);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	ret = panthor_vm_restore_vmas(vm_bo);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	drm_gpuvm_bo_evict(vm_bo, false);
>>> +	mutex_lock(&bo->base.gpuva.lock);
>>> +	panthor_gem_update_reclaim_state_locked(bo, NULL);
>>> +	mutex_unlock(&bo->base.gpuva.lock);
>>> +	return 0;
>>> +}
>>> +
>>>  static const struct drm_gpuvm_ops panthor_gpuvm_ops = {
>>>  	.vm_free = panthor_vm_free,
>>>  	.vm_bo_free = panthor_vm_bo_free,
>>>  	.sm_step_map = panthor_gpuva_sm_step_map,
>>>  	.sm_step_remap = panthor_gpuva_sm_step_remap,
>>>  	.sm_step_unmap = panthor_gpuva_sm_step_unmap,
>>> +	.vm_bo_validate = panthor_vm_bo_validate,
>>>  };
>>>  
>>>  /**
>>> @@ -2473,6 +2702,8 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>>>  	vm->kernel_auto_va.start = auto_kernel_va_start;
>>>  	vm->kernel_auto_va.end = vm->kernel_auto_va.start + auto_kernel_va_size - 1;
>>>  
>>> +	drm_gem_lru_init(&vm->reclaim.lru, &ptdev->reclaim.lock);
>>> +	INIT_LIST_HEAD(&vm->reclaim.lru_node);
>>>  	INIT_LIST_HEAD(&vm->node);
>>>  	INIT_LIST_HEAD(&vm->as.lru_node);
>>>  	vm->as.id = -1;
>>> @@ -2820,7 +3051,78 @@ int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec, struct panthor_vm
>>>  	if (ret)
>>>  		return ret;
>>>  
>>> -	return drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
>>> +	ret = drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	return drm_gpuvm_validate(&vm->base, exec);
>>> +}
>>> +
>>> +unsigned long
>>> +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
>>> +			     unsigned int nr_to_scan, unsigned long *remaining,
>>> +			     bool (*shrink)(struct drm_gem_object *,
>>> +					    struct ww_acquire_ctx *))
>>> +{
>>> +	unsigned long freed = 0;
>>> +	LIST_HEAD(remaining_vms);
>>> +	LIST_HEAD(vms);
>>> +
>>> +	mutex_lock(&ptdev->reclaim.lock);
>>> +	list_splice_init(&ptdev->reclaim.vms, &vms);
>>> +
>>> +	while (freed < nr_to_scan) {
>>> +		struct panthor_vm *vm;
>>> +
>>> +		vm = list_first_entry_or_null(&vms, typeof(*vm),
>>> +					      reclaim.lru_node);
>>> +		if (!vm)
>>> +			break;
>>> +
>>> +		if (!kref_get_unless_zero(&vm->base.kref)) {
>>> +			list_del_init(&vm->reclaim.lru_node);
>>> +			continue;
>>> +		}
>>> +
>>> +		mutex_unlock(&ptdev->reclaim.lock);
>>> +
>>> +		freed += drm_gem_lru_scan(&vm->reclaim.lru, nr_to_scan - freed,
>>> +					  remaining, shrink, NULL);
>>> +
>>> +		mutex_lock(&ptdev->reclaim.lock);
>>> +
>>> +		/* If the VM is still in the temporary list, remove it so we
>>> +		 * can proceed with the next VM.
>>> +		 */
>>> +		if (vm->reclaim.lru_node.prev == &vms) {
>>> +			list_del_init(&vm->reclaim.lru_node);
>>> +
>>> +			/* Keep the VM around if there are still things to
>>> +			 * reclaim, so we can preserve the LRU order when
>>> +			 * re-inserting in ptdev->reclaim.vms at the end.
>>> +			 */
>>> +			if (vm->reclaim.lru.count > 0)
>>> +				list_add_tail(&vm->reclaim.lru_node, &remaining_vms);
>>> +		}
>>> +
>>> +		mutex_unlock(&ptdev->reclaim.lock);
>>> +
>>> +		panthor_vm_put(vm);
>>> +
>>> +		mutex_lock(&ptdev->reclaim.lock);
>>> +	}
>>> +
>>> +	/* Re-insert VMs with remaining data to reclaim at the beginning of
>>> +	 * the LRU. Note that any activeness change on the VM that happened
>>> +	 * while we were reclaiming would have moved the VM out of our
>>> +	 * temporary [remaining_]vms list, meaning anything we re-insert here
>>> +	 * preserves the LRU order.
>>> +	 */
>>> +	list_splice_tail(&vms, &remaining_vms);
>>> +	list_splice(&remaining_vms, &ptdev->reclaim.vms);
>>> +	mutex_unlock(&ptdev->reclaim.lock);
>>> +
>>> +	return freed;
>>>  }
>>>  
>>>  /**
>>> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.h b/drivers/gpu/drm/panthor/panthor_mmu.h
>>> index 0e268fdfdb2f..3522fbbce369 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_mmu.h
>>> +++ b/drivers/gpu/drm/panthor/panthor_mmu.h
>>> @@ -1,6 +1,7 @@
>>>  /* SPDX-License-Identifier: GPL-2.0 or MIT */
>>>  /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>>>  /* Copyright 2023 Collabora ltd. */
>>> +/* Copyright 2025 ARM Limited. All rights reserved. */
>>>  
>>>  #ifndef __PANTHOR_MMU_H__
>>>  #define __PANTHOR_MMU_H__
>>> @@ -46,6 +47,13 @@ struct panthor_vm *panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>>>  				     u64 kernel_auto_va_start,
>>>  				     u64 kernel_auto_va_size);
>>>  
>>> +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo);
>>> +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo);
>>> +unsigned long
>>> +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
>>> +			     unsigned int nr_to_scan, unsigned long *remaining,
>>> +			     bool (*shrink)(struct drm_gem_object *,
>>> +					    struct ww_acquire_ctx *));
>>>  int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec,
>>>  					struct panthor_vm *vm,
>>>  					u32 slot_count);  
>>
>> I *think* there's an issue with objects being evicted then accessed by
>> mmap() or vmap. There's a call to drm_gpuvm_bo_evict(..., true) in
>> panthor_vm_evict_bo_mappings_locked() to evict, but the only
>> "de-eviction" (i.e. drm_gpuvm_bo_evict(..., false)) is
>> panthor_vm_bo_validate() which is called on the submission path but not
>> from other paths.
> 
> So, we don't allow vmap() reclaims yet (we pin on vmap()). That's
> something to add as a follow-up if we really care (MSM has something
> for that), but there's not many BOs that are vmap()-ed for a long period
> of time other than FW ones, and most of those can't be reclaimed anyway,
> expect maybe for the CSG buffers, but those should account for a very
> minimal of memory compared to the rest.
> 
> For mmap(), I'd expect the eviction to kill the user mappings, causing
> a fault on the next access, at which point we do repopulate. Now, we
> don't set the evicted bit back to zero just yet, because the GPU
> mapping is still gone, but this means the de-eviction on the next
> submit will only have half of the work to do (creating the sgt, and
> updating the page table).
> 
>>
>> If this is right then on the next submission panthor_gem_swapin_locked()
>> will it the WARN_ON for the pin_count being non-zero.
> 
> I see. So if the BO is vmap()ed before the next submit we will
> indeed hit this path. Maybe we should get rid of this WARN_ON() and keep
> going instead of returning EINVAL in that case.
> 
>>
>> I have to admit to being very unsure about all of this - I even resorted
>> to asking AI, which I think has made me more confused ;)
> 
> I think you're right that we shouldn't complain+fail if pin_count > 0
> in the de-eviction path. As I said above, de-eviction for the CPU is not
> the same as de-eviction for the GPU, so pages being present and pinned
> doesn't mean we have nothing to do for the GPU mapping to be restored.
> Maybe we should come with a better name for this function.

Yes the AI was insisting that the problem was the GPU submission would
fail. Sadly it was incredibly bad at actually explaining the issue.

So I agree it looks like it would be safe to remove the WARN_ON and keep
going in the case of pin_count > 0. The (also confusingly named)
"vm_bo_validate" will be called because the evicted flag is set which
will get the BO mapped on the GPU again.

I'm all for better names, but I'm afraid I don't have any suggestions.

Thanks,
Steve

> Thanks for the review!
> 
> Boris
> 
> [1]https://gitlab.freedesktop.org/bbrezillon/igt-gpu-tools/-/commit/fc76934a5579767d2aabe787d40e38a17c3f4ea4


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-15 11:24       ` Steven Price
@ 2026-01-15 12:01         ` Boris Brezillon
  0 siblings, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-15 12:01 UTC (permalink / raw)
  To: Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Thu, 15 Jan 2026 11:24:49 +0000
Steven Price <steven.price@arm.com> wrote:

> On 15/01/2026 10:50, Boris Brezillon wrote:
> > Hi Steve,
> > 
> > On Wed, 14 Jan 2026 15:05:36 +0000
> > Steven Price <steven.price@arm.com> wrote:
> >   
> >> On 09/01/2026 13:08, Boris Brezillon wrote:  
> [...]
> >>> @@ -1250,10 +1637,42 @@ static struct drm_info_list panthor_gem_debugfs_list[] = {
> >>>  	{ "gems", panthor_gem_show_bos, 0, NULL },
> >>>  };
> >>>  
> >>> +static int shrink_get(void *data, u64 *val)
> >>> +{
> >>> +	struct panthor_device *ptdev =
> >>> +		container_of(data, struct panthor_device, base);
> >>> +
> >>> +	*val = ptdev->reclaim.nr_pages_reclaimed_on_last_scan;
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int shrink_set(void *data, u64 val)
> >>> +{
> >>> +	struct panthor_device *ptdev =
> >>> +		container_of(data, struct panthor_device, base);
> >>> +	struct shrink_control sc = {
> >>> +		.gfp_mask = GFP_KERNEL,
> >>> +		.nr_to_scan = val,
> >>> +	};
> >>> +
> >>> +	fs_reclaim_acquire(GFP_KERNEL);
> >>> +	if (ptdev->reclaim.shrinker)
> >>> +		panthor_gem_shrinker_scan(ptdev->reclaim.shrinker, &sc);
> >>> +	fs_reclaim_release(GFP_KERNEL);
> >>> +
> >>> +	return 0;
> >>> +}    
> >>
> >> Do you have some test to drive this?  
> > 
> > Yes, I do [1].
> >   
> >> My immediate thought was that it
> >> would be nice (for manual testing at least) to printk the return value
> >> from panthor_gem_shrinker_scan(). But we probably wouldn't actually need
> >> nr_pages_reclaimed_on_last_scan if you could just read that from dmesg.
> >> But I can see integrating that into a test might not be ideal.  
> > 
> > I basically based the interface on the existing MSM one. Might not be
> > the best, but it was good enough for this initial testing.  
> 
> Ah well if we're matching MSM then that's probably a good justification.
> It just seemed a little odd throwing away the return value and then
> having to have a separate mechanism to get the number of pages reclaimed
> out. And given I'd just spotted a bug in the return value I thought I'd
> ask ;).

For that particular thing, I diverged from what MSM was doing, because
in some cases, SHRINK_STOP is returned even though pages were
reclaimed. I need to double-check the ->scan() semantics, I got that
wrong a few times already, and paged out everything about these tricky
corner cases already.

> >> I have to admit to being very unsure about all of this - I even resorted
> >> to asking AI, which I think has made me more confused ;)  
> > 
> > I think you're right that we shouldn't complain+fail if pin_count > 0
> > in the de-eviction path. As I said above, de-eviction for the CPU is not
> > the same as de-eviction for the GPU, so pages being present and pinned
> > doesn't mean we have nothing to do for the GPU mapping to be restored.
> > Maybe we should come with a better name for this function.  
> 
> Yes the AI was insisting that the problem was the GPU submission would
> fail. Sadly it was incredibly bad at actually explaining the issue.
> 
> So I agree it looks like it would be safe to remove the WARN_ON and keep
> going in the case of pin_count > 0. The (also confusingly named)
> "vm_bo_validate" will be called because the evicted flag is set which
> will get the BO mapped on the GPU again.
> 
> I'm all for better names, but I'm afraid I don't have any suggestions.

Alright, I'll stick to gem_swapin() then.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails
  2026-01-09 13:07 ` [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails Boris Brezillon
  2026-01-12  9:25   ` Alice Ryhl
@ 2026-01-15 13:28   ` Liviu Dudau
  1 sibling, 0 replies; 62+ messages in thread
From: Liviu Dudau @ 2026-01-15 13:28 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Fri, Jan 09, 2026 at 02:07:53PM +0100, Boris Brezillon wrote:
> If the object wasn't moved to a different LRU after the shrink callback
> is called, it means the buffer is still reclaimable. Update the remaining
> counter to reflect that.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>

Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>

Best regards,
Liviu

> ---
>  drivers/gpu/drm/drm_gem.c | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
> index f7cbf6e8d1e0..442853511106 100644
> --- a/drivers/gpu/drm/drm_gem.c
> +++ b/drivers/gpu/drm/drm_gem.c
> @@ -1671,6 +1671,16 @@ drm_gem_lru_scan(struct drm_gem_lru *lru,
>  			 */
>  			WARN_ON(obj->lru == &still_in_lru);
>  			WARN_ON(obj->lru == lru);
> +		} else if (obj->lru == &still_in_lru) {
> +			/*
> +			 * If the object wasn't moved and wasn't shrunk either,
> +			 * it's still remaining as reclaimable. Note that
> +			 * obj->lru is supposed to be checked with the LRU lock
> +			 * held for an accurate result, but we don't care about
> +			 * accuracy here. Worst thing that could happen is an
> +			 * extra scan.
> +			 */
> +			*remaining += obj->size >> PAGE_SHIFT;
>  		}
>  
>  		dma_resv_unlock(obj->resv);
> -- 
> 2.52.0
> 

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 3/9] drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c
  2026-01-09 13:07 ` [PATCH v1 3/9] drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c Boris Brezillon
  2026-01-12 11:27   ` Steven Price
@ 2026-01-15 13:39   ` Liviu Dudau
  1 sibling, 0 replies; 62+ messages in thread
From: Liviu Dudau @ 2026-01-15 13:39 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Fri, Jan 09, 2026 at 02:07:55PM +0100, Boris Brezillon wrote:
> There's no reason for panthor_drv to know about panthor_gem.c internals,
> so let's move the GEM debugfs init logic to panthor_gem.c.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>

Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>

Best regards,
Liviu

> ---
>  drivers/gpu/drm/panthor/panthor_drv.c | 26 +-----------------------
>  drivers/gpu/drm/panthor/panthor_gem.c | 29 +++++++++++++++++++++++++--
>  drivers/gpu/drm/panthor/panthor_gem.h |  3 +--
>  3 files changed, 29 insertions(+), 29 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
> index 165dddfde6ca..52c27a60c84a 100644
> --- a/drivers/gpu/drm/panthor/panthor_drv.c
> +++ b/drivers/gpu/drm/panthor/panthor_drv.c
> @@ -1635,34 +1635,10 @@ static const struct file_operations panthor_drm_driver_fops = {
>  };
>  
>  #ifdef CONFIG_DEBUG_FS
> -static int panthor_gems_show(struct seq_file *m, void *data)
> -{
> -	struct drm_info_node *node = m->private;
> -	struct drm_device *dev = node->minor->dev;
> -	struct panthor_device *ptdev = container_of(dev, struct panthor_device, base);
> -
> -	panthor_gem_debugfs_print_bos(ptdev, m);
> -
> -	return 0;
> -}
> -
> -static struct drm_info_list panthor_debugfs_list[] = {
> -	{"gems", panthor_gems_show, 0, NULL},
> -};
> -
> -static int panthor_gems_debugfs_init(struct drm_minor *minor)
> -{
> -	drm_debugfs_create_files(panthor_debugfs_list,
> -				 ARRAY_SIZE(panthor_debugfs_list),
> -				 minor->debugfs_root, minor);
> -
> -	return 0;
> -}
> -
>  static void panthor_debugfs_init(struct drm_minor *minor)
>  {
>  	panthor_mmu_debugfs_init(minor);
> -	panthor_gems_debugfs_init(minor);
> +	panthor_gem_debugfs_init(minor);
>  }
>  #endif
>  
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index b61908fd508a..13e9dd3764fa 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -9,6 +9,8 @@
>  #include <linux/err.h>
>  #include <linux/slab.h>
>  
> +#include <drm/drm_debugfs.h>
> +#include <drm/drm_file.h>
>  #include <drm/drm_print.h>
>  #include <drm/panthor_drm.h>
>  
> @@ -683,8 +685,8 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
>  		totals->reclaimable += resident_size;
>  }
>  
> -void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
> -				   struct seq_file *m)
> +static void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
> +					  struct seq_file *m)
>  {
>  	struct gem_size_totals totals = {0};
>  	struct panthor_gem_object *bo;
> @@ -704,4 +706,27 @@ void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
>  	seq_printf(m, "Total size: %zd, Total resident: %zd, Total reclaimable: %zd\n",
>  		   totals.size, totals.resident, totals.reclaimable);
>  }
> +
> +static int panthor_gem_show_bos(struct seq_file *m, void *data)
> +{
> +	struct drm_info_node *node = m->private;
> +	struct drm_device *dev = node->minor->dev;
> +	struct panthor_device *ptdev =
> +		container_of(dev, struct panthor_device, base);
> +
> +	panthor_gem_debugfs_print_bos(ptdev, m);
> +
> +	return 0;
> +}
> +
> +static struct drm_info_list panthor_gem_debugfs_list[] = {
> +	{ "gems", panthor_gem_show_bos, 0, NULL },
> +};
> +
> +void panthor_gem_debugfs_init(struct drm_minor *minor)
> +{
> +	drm_debugfs_create_files(panthor_gem_debugfs_list,
> +				 ARRAY_SIZE(panthor_gem_debugfs_list),
> +				 minor->debugfs_root, minor);
> +}
>  #endif
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
> index 22519c570b5a..94b2d17cf032 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.h
> +++ b/drivers/gpu/drm/panthor/panthor_gem.h
> @@ -203,8 +203,7 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo);
>  
>  #ifdef CONFIG_DEBUG_FS
> -void panthor_gem_debugfs_print_bos(struct panthor_device *pfdev,
> -				   struct seq_file *m);
> +void panthor_gem_debugfs_init(struct drm_minor *minor);
>  #endif
>  
>  #endif /* __PANTHOR_GEM_H__ */
> -- 
> 2.52.0
> 

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 4/9] drm/panthor: Group panthor_kernel_bo_xxx() helpers
  2026-01-09 13:07 ` [PATCH v1 4/9] drm/panthor: Group panthor_kernel_bo_xxx() helpers Boris Brezillon
  2026-01-12 11:29   ` Steven Price
@ 2026-01-15 13:41   ` Liviu Dudau
  1 sibling, 0 replies; 62+ messages in thread
From: Liviu Dudau @ 2026-01-15 13:41 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Fri, Jan 09, 2026 at 02:07:56PM +0100, Boris Brezillon wrote:
> Move all panthor_kernel_bo_xxx() helpers at the end of the file, just
> before the debugfs init logic. This will make further panthor_gem.c
> refactoring more readable.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>

Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>

> ---
>  drivers/gpu/drm/panthor/panthor_gem.c | 212 +++++++++++++-------------
>  1 file changed, 106 insertions(+), 106 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index 13e9dd3764fa..4b3d82f001d8 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -132,112 +132,6 @@ static void panthor_gem_free_object(struct drm_gem_object *obj)
>  	drm_gem_object_put(vm_root_gem);
>  }
>  
> -/**
> - * panthor_kernel_bo_destroy() - Destroy a kernel buffer object
> - * @bo: Kernel buffer object to destroy. If NULL or an ERR_PTR(), the destruction
> - * is skipped.
> - */
> -void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
> -{
> -	struct panthor_vm *vm;
> -
> -	if (IS_ERR_OR_NULL(bo))
> -		return;
> -
> -	vm = bo->vm;
> -	panthor_kernel_bo_vunmap(bo);
> -
> -	drm_WARN_ON(bo->obj->dev,
> -		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
> -	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
> -	panthor_vm_free_va(vm, &bo->va_node);
> -	drm_gem_object_put(bo->obj);
> -	panthor_vm_put(vm);
> -	kfree(bo);
> -}
> -
> -/**
> - * panthor_kernel_bo_create() - Create and map a GEM object to a VM
> - * @ptdev: Device.
> - * @vm: VM to map the GEM to. If NULL, the kernel object is not GPU mapped.
> - * @size: Size of the buffer object.
> - * @bo_flags: Combination of drm_panthor_bo_flags flags.
> - * @vm_map_flags: Combination of drm_panthor_vm_bind_op_flags (only those
> - * that are related to map operations).
> - * @gpu_va: GPU address assigned when mapping to the VM.
> - * If gpu_va == PANTHOR_VM_KERNEL_AUTO_VA, the virtual address will be
> - * automatically allocated.
> - * @name: Descriptive label of the BO's contents
> - *
> - * Return: A valid pointer in case of success, an ERR_PTR() otherwise.
> - */
> -struct panthor_kernel_bo *
> -panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
> -			 size_t size, u32 bo_flags, u32 vm_map_flags,
> -			 u64 gpu_va, const char *name)
> -{
> -	struct drm_gem_shmem_object *obj;
> -	struct panthor_kernel_bo *kbo;
> -	struct panthor_gem_object *bo;
> -	u32 debug_flags = PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL;
> -	int ret;
> -
> -	if (drm_WARN_ON(&ptdev->base, !vm))
> -		return ERR_PTR(-EINVAL);
> -
> -	kbo = kzalloc(sizeof(*kbo), GFP_KERNEL);
> -	if (!kbo)
> -		return ERR_PTR(-ENOMEM);
> -
> -	obj = drm_gem_shmem_create(&ptdev->base, size);
> -	if (IS_ERR(obj)) {
> -		ret = PTR_ERR(obj);
> -		goto err_free_bo;
> -	}
> -
> -	bo = to_panthor_bo(&obj->base);
> -	kbo->obj = &obj->base;
> -	bo->flags = bo_flags;
> -	bo->base.map_wc = should_map_wc(bo, vm);
> -	bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm);
> -	drm_gem_object_get(bo->exclusive_vm_root_gem);
> -	bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
> -
> -	if (vm == panthor_fw_vm(ptdev))
> -		debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED;
> -
> -	panthor_gem_kernel_bo_set_label(kbo, name);
> -	panthor_gem_debugfs_set_usage_flags(to_panthor_bo(kbo->obj), debug_flags);
> -
> -	/* The system and GPU MMU page size might differ, which becomes a
> -	 * problem for FW sections that need to be mapped at explicit address
> -	 * since our PAGE_SIZE alignment might cover a VA range that's
> -	 * expected to be used for another section.
> -	 * Make sure we never map more than we need.
> -	 */
> -	size = ALIGN(size, panthor_vm_page_size(vm));
> -	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
> -	if (ret)
> -		goto err_put_obj;
> -
> -	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
> -	if (ret)
> -		goto err_free_va;
> -
> -	kbo->vm = panthor_vm_get(vm);
> -	return kbo;
> -
> -err_free_va:
> -	panthor_vm_free_va(vm, &kbo->va_node);
> -
> -err_put_obj:
> -	drm_gem_object_put(&obj->base);
> -
> -err_free_bo:
> -	kfree(kbo);
> -	return ERR_PTR(ret);
> -}
> -
>  static struct sg_table *
>  panthor_gem_prime_map_dma_buf(struct dma_buf_attachment *attach,
>  			      enum dma_data_direction dir)
> @@ -603,6 +497,112 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>  	return 0;
>  }
>  
> +/**
> + * panthor_kernel_bo_destroy() - Destroy a kernel buffer object
> + * @bo: Kernel buffer object to destroy. If NULL or an ERR_PTR(), the destruction
> + * is skipped.
> + */
> +void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
> +{
> +	struct panthor_vm *vm;
> +
> +	if (IS_ERR_OR_NULL(bo))
> +		return;
> +
> +	vm = bo->vm;
> +	panthor_kernel_bo_vunmap(bo);
> +
> +	drm_WARN_ON(bo->obj->dev,
> +		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
> +	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
> +	panthor_vm_free_va(vm, &bo->va_node);
> +	drm_gem_object_put(bo->obj);
> +	panthor_vm_put(vm);
> +	kfree(bo);
> +}
> +
> +/**
> + * panthor_kernel_bo_create() - Create and map a GEM object to a VM
> + * @ptdev: Device.
> + * @vm: VM to map the GEM to. If NULL, the kernel object is not GPU mapped.
> + * @size: Size of the buffer object.
> + * @bo_flags: Combination of drm_panthor_bo_flags flags.
> + * @vm_map_flags: Combination of drm_panthor_vm_bind_op_flags (only those
> + * that are related to map operations).
> + * @gpu_va: GPU address assigned when mapping to the VM.
> + * If gpu_va == PANTHOR_VM_KERNEL_AUTO_VA, the virtual address will be
> + * automatically allocated.
> + * @name: Descriptive label of the BO's contents
> + *
> + * Return: A valid pointer in case of success, an ERR_PTR() otherwise.
> + */
> +struct panthor_kernel_bo *
> +panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
> +			 size_t size, u32 bo_flags, u32 vm_map_flags,
> +			 u64 gpu_va, const char *name)
> +{
> +	struct drm_gem_shmem_object *obj;
> +	struct panthor_kernel_bo *kbo;
> +	struct panthor_gem_object *bo;
> +	u32 debug_flags = PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL;
> +	int ret;
> +
> +	if (drm_WARN_ON(&ptdev->base, !vm))
> +		return ERR_PTR(-EINVAL);
> +
> +	kbo = kzalloc(sizeof(*kbo), GFP_KERNEL);
> +	if (!kbo)
> +		return ERR_PTR(-ENOMEM);
> +
> +	obj = drm_gem_shmem_create(&ptdev->base, size);
> +	if (IS_ERR(obj)) {
> +		ret = PTR_ERR(obj);
> +		goto err_free_bo;
> +	}
> +
> +	bo = to_panthor_bo(&obj->base);
> +	kbo->obj = &obj->base;
> +	bo->flags = bo_flags;
> +	bo->base.map_wc = should_map_wc(bo, vm);
> +	bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm);
> +	drm_gem_object_get(bo->exclusive_vm_root_gem);
> +	bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
> +
> +	if (vm == panthor_fw_vm(ptdev))
> +		debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED;
> +
> +	panthor_gem_kernel_bo_set_label(kbo, name);
> +	panthor_gem_debugfs_set_usage_flags(to_panthor_bo(kbo->obj), debug_flags);
> +
> +	/* The system and GPU MMU page size might differ, which becomes a
> +	 * problem for FW sections that need to be mapped at explicit address
> +	 * since our PAGE_SIZE alignment might cover a VA range that's
> +	 * expected to be used for another section.
> +	 * Make sure we never map more than we need.
> +	 */
> +	size = ALIGN(size, panthor_vm_page_size(vm));
> +	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
> +	if (ret)
> +		goto err_put_obj;
> +
> +	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
> +	if (ret)
> +		goto err_free_va;
> +
> +	kbo->vm = panthor_vm_get(vm);
> +	return kbo;
> +
> +err_free_va:
> +	panthor_vm_free_va(vm, &kbo->va_node);
> +
> +err_put_obj:
> +	drm_gem_object_put(&obj->base);
> +
> +err_free_bo:
> +	kfree(kbo);
> +	return ERR_PTR(ret);
> +}
> +
>  #ifdef CONFIG_DEBUG_FS
>  struct gem_size_totals {
>  	size_t size;
> -- 
> 2.52.0
> 

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-09 13:08 ` [PATCH v1 9/9] drm/panthor: Add a GEM shrinker Boris Brezillon
  2026-01-14 15:05   ` Steven Price
@ 2026-01-15 13:56   ` Akash Goel
  2026-01-15 14:36     ` Boris Brezillon
  2026-01-15 14:37     ` Boris Brezillon
  2026-01-21 11:49   ` Akash Goel
  2 siblings, 2 replies; 62+ messages in thread
From: Akash Goel @ 2026-01-15 13:56 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

Hi Boris,

Many thanks for posting the patch on the mailing list.

On 1/9/26 13:08, Boris Brezillon wrote:
> From: Akash Goel <akash.goel@arm.com>
> 
> This implementation is losely based on the MSM shrinker, and it's
> relying on the drm_gpuvm eviction/validation infrastructure.
> 
> Right now we only support swapout/eviction, but we could add an extra
> flag to specify when buffer content doesn't need to be preserved to
> avoid the swapout/swapin dance.
> 
> Locking is a bit of a nightmare, but using _trylock() all the way in
> the reclaim path seems to make lockdep happy. And yes, we might be
> missing opportunities to reclaim when the system is under heavy GPU
> load/heavy memory pressure/heavy GPU VM activity, but that's better
> than no reclaim at all.
> 
> Signed-off-by: Akash Goel <akash.goel@arm.com>
> Co-developed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>   drivers/gpu/drm/panthor/panthor_device.c |  11 +-
>   drivers/gpu/drm/panthor/panthor_device.h |  73 ++++
>   drivers/gpu/drm/panthor/panthor_gem.c    | 427 ++++++++++++++++++++++-
>   drivers/gpu/drm/panthor/panthor_gem.h    |  67 ++++
>   drivers/gpu/drm/panthor/panthor_mmu.c    | 338 +++++++++++++++++-
>   drivers/gpu/drm/panthor/panthor_mmu.h    |   8 +
>   6 files changed, 901 insertions(+), 23 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_device.c b/drivers/gpu/drm/panthor/panthor_device.c
> index 54fbb1aa07c5..bc62a498a8a8 100644
> --- a/drivers/gpu/drm/panthor/panthor_device.c
> +++ b/drivers/gpu/drm/panthor/panthor_device.c
> @@ -2,6 +2,7 @@
>   /* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
>   /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>   /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>   
>   #include <linux/clk.h>
>   #include <linux/mm.h>
> @@ -122,6 +123,7 @@ void panthor_device_unplug(struct panthor_device *ptdev)
>   	panthor_sched_unplug(ptdev);
>   	panthor_fw_unplug(ptdev);
>   	panthor_mmu_unplug(ptdev);
> +	panthor_gem_shrinker_unplug(ptdev);
>   	panthor_gpu_unplug(ptdev);
>   	panthor_pwr_unplug(ptdev);
>   
> @@ -291,10 +293,14 @@ int panthor_device_init(struct panthor_device *ptdev)
>   	if (ret)
>   		goto err_unplug_gpu;
>   
> -	ret = panthor_mmu_init(ptdev);
> +	ret = panthor_gem_shrinker_init(ptdev);
>   	if (ret)
>   		goto err_unplug_gpu;
>   
> +	ret = panthor_mmu_init(ptdev);
> +	if (ret)
> +		goto err_unplug_shrinker;
> +
>   	ret = panthor_fw_init(ptdev);
>   	if (ret)
>   		goto err_unplug_mmu;
> @@ -326,6 +332,9 @@ int panthor_device_init(struct panthor_device *ptdev)
>   err_unplug_mmu:
>   	panthor_mmu_unplug(ptdev);
>   
> +err_unplug_shrinker:
> +	panthor_gem_shrinker_unplug(ptdev);
> +
>   err_unplug_gpu:
>   	panthor_gpu_unplug(ptdev);
>   
> diff --git a/drivers/gpu/drm/panthor/panthor_device.h b/drivers/gpu/drm/panthor/panthor_device.h
> index f35e52b9546a..bc348aa9634e 100644
> --- a/drivers/gpu/drm/panthor/panthor_device.h
> +++ b/drivers/gpu/drm/panthor/panthor_device.h
> @@ -14,6 +14,7 @@
>   #include <linux/spinlock.h>
>   
>   #include <drm/drm_device.h>
> +#include <drm/drm_gem.h>
>   #include <drm/drm_mm.h>
>   #include <drm/gpu_scheduler.h>
>   #include <drm/panthor_drm.h>
> @@ -157,6 +158,78 @@ struct panthor_device {
>   	/** @devfreq: Device frequency scaling management data. */
>   	struct panthor_devfreq *devfreq;
>   
> +	/** @reclaim: Reclaim related stuff */
> +	struct {
> +		/** @reclaim.shrinker: Shrinker instance */
> +		struct shrinker *shrinker;
> +
> +		/** @reclaim.lock: Lock protecting all LRUs */
> +		struct mutex lock;
> +
> +		/**
> +		 * @reclaim.unused: BOs with unused pages
> +		 *
> +		 * Basically all buffers that got mmapped, vmapped or GPU mapped and
> +		 * then unmapped. There should be no contention on these buffers,
> +		 * making them ideal to reclaim.
> +		 */
> +		struct drm_gem_lru unused;
> +
> +		/**
> +		 * @reclaim.mmapped: mmap()-ed buffers
> +		 *
> +		 * Those are relatively easy to reclaim since we don't need user
> +		 * agreement, we can simply teardown the mapping and let it fault on
> +		 * the next access.
> +		 */
> +		struct drm_gem_lru mmapped;
> +
> +		/**
> +		 * @reclaim.vms: VM LRU list
> +		 *
> +		 * VMs that have reclaimable BOs only mapped to a single VM are placed
> +		 * in this LRU. Reclaiming such BOs implies waiting for VM idleness
> +		 * (no in-flight GPU jobs targeting this VM), meaning we can't reclaim
> +		 * those if we're in a context where we can't block/sleep.
> +		 */
> +		struct list_head vms;
> +
> +		/**
> +		 * @reclaim.gpu_mapped_shared: shared BO LRU list
> +		 *
> +		 * That's the most tricky BO type to reclaim, because it involves
> +		 * tearing down all mappings in all VMs where this BO is mapped,
> +		 * which increases the risk of contention and thus decreases the
> +		 * likeliness of success.
> +		 */
> +		struct drm_gem_lru gpu_mapped_shared;
> +
> +		/**
> +		 * @reclaim.gpu_mapped_count: Global counter of pages that are GPU mapped
> +		 *
> +		 * Allows us to get the number of reclaimable pages without walking
> +		 * the vms and gpu_mapped_shared LRUs.
> +		 */
> +		long gpu_mapped_count;
> +
> +		/**
> +		 * @reclaim.retry_count: Number of times we ran the shrinker without being
> +		 * able to reclaim stuff
> +		 *
> +		 * Used to stop scanning GEMs when too many attempts were made
> +		 * without progress.
> +		 */
> +		atomic_t retry_count;
> +
> +#ifdef CONFIG_DEBUG_FS
> +		/**
> +		 * @reclaim.nr_pages_reclaimed_on_last_scan: Number of pages reclaimed on the last
> +		 * shrinker scan
> +		 */
> +		unsigned long nr_pages_reclaimed_on_last_scan;
> +#endif
> +	} reclaim;
> +
>   	/** @unplug: Device unplug related fields. */
>   	struct {
>   		/** @lock: Lock used to serialize unplug operations. */
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index 458d22380e96..065956a1f0fb 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -2,8 +2,10 @@
>   /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>   /* Copyright 2023 Collabora ltd. */
>   /* Copyright 2025 Amazon.com, Inc. or its affiliates */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>   
>   #include <linux/cleanup.h>
> +#include <linux/debugfs.h>
>   #include <linux/dma-buf.h>
>   #include <linux/dma-mapping.h>
>   #include <linux/err.h>
> @@ -12,6 +14,8 @@
>   
>   #include <drm/drm_debugfs.h>
>   #include <drm/drm_file.h>
> +#include <drm/drm_gpuvm.h>
> +#include <drm/drm_managed.h>
>   #include <drm/drm_prime.h>
>   #include <drm/drm_print.h>
>   #include <drm/panthor_drm.h>
> @@ -114,6 +118,98 @@ should_map_wc(struct panthor_gem_object *bo)
>   	return true;
>   }
>   
> +static bool is_gpu_mapped(struct panthor_gem_object *bo,
> +			  enum panthor_gem_reclaim_state *state)
> +{
> +	struct drm_gpuvm *vm = NULL;
> +	struct drm_gpuvm_bo *vm_bo;
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +		if (!vm) {
> +			*state = PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> +			vm = vm_bo->vm;
> +		} else if (vm != vm_bo->vm) {
> +			*state = PANTHOR_GEM_GPU_MAPPED_SHARED;
> +			break;
> +		}
> +	}
> +
> +	return !!vm;
> +}
> +
> +static enum panthor_gem_reclaim_state
> +panthor_gem_evaluate_reclaim_state_locked(struct panthor_gem_object *bo)
> +{
> +	enum panthor_gem_reclaim_state gpu_mapped_state;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +	lockdep_assert_held(&bo->base.gpuva.lock);
> +
> +	/* If pages have not been allocated, there's nothing to reclaim. */
> +	if (!bo->backing.pages)
> +		return PANTHOR_GEM_UNRECLAIMABLE;
> +
> +	/* If memory is pinned, we prevent reclaim. */
> +	if (refcount_read(&bo->backing.pin_count))
> +		return PANTHOR_GEM_UNRECLAIMABLE;
> +
> +	if (is_gpu_mapped(bo, &gpu_mapped_state))
> +		return gpu_mapped_state;
> +
> +	if (refcount_read(&bo->cmap.mmap_count) && bo->backing.pages)
> +		return PANTHOR_GEM_MMAPPED;
> +
> +	return PANTHOR_GEM_UNUSED;
> +}
> +
> +void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
> +					     enum panthor_gem_reclaim_state *old_statep)
> +{
> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> +	enum panthor_gem_reclaim_state old_state = bo->reclaim_state;
> +	enum panthor_gem_reclaim_state new_state;
> +	bool was_gpu_mapped, is_gpu_mapped;
> +
> +	if (old_statep)
> +		*old_statep = old_state;
> +
> +	new_state = panthor_gem_evaluate_reclaim_state_locked(bo);
> +	if (new_state == old_state)
> +		return;
> +
> +	was_gpu_mapped = old_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> +			 old_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> +	is_gpu_mapped = new_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> +			new_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> +
> +	if (is_gpu_mapped && !was_gpu_mapped)
> +		ptdev->reclaim.gpu_mapped_count += bo->base.size >> PAGE_SHIFT;
> +	else if (!is_gpu_mapped && was_gpu_mapped)
> +		ptdev->reclaim.gpu_mapped_count -= bo->base.size >> PAGE_SHIFT;
> +
> +	switch (new_state) {
> +	case PANTHOR_GEM_UNUSED:
> +		drm_gem_lru_move_tail(&ptdev->reclaim.unused, &bo->base);
> +		break;
> +	case PANTHOR_GEM_MMAPPED:
> +		drm_gem_lru_move_tail(&ptdev->reclaim.mmapped, &bo->base);
> +		break;
> +	case PANTHOR_GEM_GPU_MAPPED_PRIVATE:
> +		panthor_vm_update_bo_reclaim_lru_locked(bo);
> +		break;
> +	case PANTHOR_GEM_GPU_MAPPED_SHARED:
> +		drm_gem_lru_move_tail(&ptdev->reclaim.gpu_mapped_shared, &bo->base);
> +		break;
> +	case PANTHOR_GEM_UNRECLAIMABLE:
> +		drm_gem_lru_remove(&bo->base);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	bo->reclaim_state = new_state;
> +}
> +
>   static void
>   panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
>   {
> @@ -153,8 +249,12 @@ static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
>   		return 0;
>   
>   	ret = panthor_gem_backing_get_pages_locked(bo);
> -	if (!ret)
> +	if (!ret) {
>   		refcount_set(&bo->backing.pin_count, 1);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +	}
>   
>   	return ret;
>   }
> @@ -167,8 +267,12 @@ static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
>   	/* We don't release anything when pin_count drops to zero.
>   	 * Pages stay there until an explicit cleanup is requested.
>   	 */
> -	if (!refcount_dec_not_one(&bo->backing.pin_count))
> +	if (!refcount_dec_not_one(&bo->backing.pin_count)) {
>   		refcount_set(&bo->backing.pin_count, 0);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +	}
>   }
>   
>   static void
> @@ -531,6 +635,49 @@ void panthor_gem_unpin(struct panthor_gem_object *bo)
>   	dma_resv_unlock(bo->base.resv);
>   }
>   
> +int panthor_gem_swapin_locked(struct panthor_gem_object *bo)
> +{
> +	struct sg_table *sgt;
> +	int ret;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> +		return -EINVAL;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
> +		return -EINVAL;
> +
> +	ret = panthor_gem_backing_get_pages_locked(bo);
> +	if (ret)
> +		return ret;
> +
> +	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> +	if (IS_ERR(sgt))
> +		return PTR_ERR(sgt);
> +
> +	return 0;
> +}
> +
> +static void panthor_gem_evict_locked(struct panthor_gem_object *bo)
> +{
> +	dma_resv_assert_held(bo->base.resv);
> +	lockdep_assert_held(&bo->base.gpuva.lock);
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> +		return;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
> +		return;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> +		return;
> +
> +	panthor_gem_dev_map_cleanup(bo);
> +	panthor_gem_backing_cleanup(bo);
> +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> +}
> +
>   static struct sg_table *panthor_gem_get_sg_table(struct drm_gem_object *obj)
>   {
>   	struct panthor_gem_object *bo = to_panthor_bo(obj);
> @@ -692,6 +839,10 @@ static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
>   	} else {
>   		struct page *page = bo->backing.pages[page_offset];
>   
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +
>   		if (mmap_lock_held)
>   			ret = insert_page(vmf, page);
>   		else
> @@ -764,8 +915,12 @@ static void panthor_gem_vm_close(struct vm_area_struct *vma)
>   		goto out;
>   
>   	dma_resv_lock(bo->base.resv, NULL);
> -	if (!refcount_dec_not_one(&bo->cmap.mmap_count))
> +	if (!refcount_dec_not_one(&bo->cmap.mmap_count)) {
>   		refcount_set(&bo->cmap.mmap_count, 0);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +	}
>   	dma_resv_unlock(bo->base.resv);
>   
>   out:
> @@ -801,6 +956,7 @@ panthor_gem_alloc_object(uint32_t flags)
>   	if (!bo)
>   		return ERR_PTR(-ENOMEM);
>   
> +	bo->reclaim_state = PANTHOR_GEM_UNRECLAIMABLE;
>   	bo->base.funcs = &panthor_gem_funcs;
>   	bo->flags = flags;
>   	mutex_init(&bo->label.lock);
> @@ -1041,11 +1197,13 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>    */
>   void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
>   {
> +	struct panthor_device *ptdev;
>   	struct panthor_vm *vm;
>   
>   	if (IS_ERR_OR_NULL(bo))
>   		return;
>   
> +	ptdev = container_of(bo->obj->dev, struct panthor_device, base);
>   	vm = bo->vm;
>   	panthor_kernel_bo_vunmap(bo);
>   
> @@ -1053,6 +1211,8 @@ void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo)
>   		    to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm));
>   	panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size);
>   	panthor_vm_free_va(vm, &bo->va_node);
> +	if (vm == panthor_fw_vm(ptdev))
> +		panthor_gem_unpin(to_panthor_bo(bo->obj));
>   	drm_gem_object_put(bo->obj);
>   	panthor_vm_put(vm);
>   	kfree(bo);
> @@ -1101,6 +1261,12 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>   
>   	kbo->obj = &bo->base;
>   
> +	if (vm == panthor_fw_vm(ptdev)) {
> +		ret = panthor_gem_pin(bo);
> +		if (ret)
> +			goto err_put_obj;
> +	}
> +
>   	panthor_gem_kernel_bo_set_label(kbo, name);
>   
>   	/* The system and GPU MMU page size might differ, which becomes a
> @@ -1112,7 +1278,7 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>   	size = ALIGN(size, panthor_vm_page_size(vm));
>   	ret = panthor_vm_alloc_va(vm, gpu_va, size, &kbo->va_node);
>   	if (ret)
> -		goto err_put_obj;
> +		goto err_unpin;
>   
>   	ret = panthor_vm_map_bo_range(vm, bo, 0, size, kbo->va_node.start, vm_map_flags);
>   	if (ret)
> @@ -1124,6 +1290,10 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>   err_free_va:
>   	panthor_vm_free_va(vm, &kbo->va_node);
>   
> +err_unpin:
> +	if (vm == panthor_fw_vm(ptdev))
> +		panthor_gem_unpin(bo);
> +
>   err_put_obj:
>   	drm_gem_object_put(&bo->base);
>   
> @@ -1132,6 +1302,223 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>   	return ERR_PTR(ret);
>   }
>   
> +static bool can_swap(void)
> +{
> +	return get_nr_swap_pages() > 0;
> +}
> +
> +static bool can_block(struct shrink_control *sc)
> +{
> +	if (!(sc->gfp_mask & __GFP_DIRECT_RECLAIM))
> +		return false;
> +	return current_is_kswapd() || (sc->gfp_mask & __GFP_RECLAIM);
> +}
> +
> +static unsigned long
> +panthor_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
> +{
> +	struct panthor_device *ptdev = shrinker->private_data;
> +	unsigned long count;
> +
> +	/* We currently don't have a flag to tell when the content of a
> +	 * BO can be discarded.
> +	 */
> +	if (!can_swap())
> +		return 0;
> +
> +	count = ptdev->reclaim.unused.count;
> +	count += ptdev->reclaim.mmapped.count;
> +
> +	if (can_block(sc))
> +		count += ptdev->reclaim.gpu_mapped_count;
> +

Should we do like this

	return count ? count : SHRINK_EMPTY;


Other drivers are also doing the same

https://elixir.bootlin.com/linux/v6.18.4/source/drivers/gpu/drm/xe/xe_shrinker.c#L156 


https://elixir.bootlin.com/linux/v6.18.4/source/drivers/gpu/drm/ttm/ttm_pool.c#L1160

https://elixir.bootlin.com/linux/v6.18.4/source/mm/vmalloc.c#L5245

It might matter if CONFIG_MEMCG is set.

> +	return count;
> +}
> +
> +static bool should_wait(enum panthor_gem_reclaim_state reclaim_state)
> +{
> +	return reclaim_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE ||
> +	       reclaim_state == PANTHOR_GEM_GPU_MAPPED_SHARED;
> +}
> +
> +bool panthor_gem_try_evict(struct drm_gem_object *obj,
> +			   struct ww_acquire_ctx *ticket)
> +{
> +	/*
> +	 * Track last locked entry for unwinding locks in error and
> +	 * success paths
> +	 */
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +	struct drm_gpuvm_bo *vm_bo, *last_locked = NULL;
> +	enum panthor_gem_reclaim_state old_state;
> +	int ret = 0;
> +
> +	/* To avoid potential lock ordering issue between bo_gpuva and
> +	 * mapping->i_mmap_rwsem, unmap the pages from CPU side before
> +	 * acquring the bo_gpuva lock. As the bo_resv lock is held, CPU
> +	 * page fault handler won't be able to map in the pages whilst
> +	 * eviction is in progress.
> +	 */
> +	drm_vma_node_unmap(&bo->base.vma_node, bo->base.dev->anon_inode->i_mapping);
> +
> +	/* We take this lock when walking the list to prevent
> +	 * insertion/deletion.
> +	 */
> +	/* We can only trylock in that path, because
> +	 * - allocation might happen while some of these locks are held
> +	 * - lock ordering is different in other paths
> +	 *     vm_resv -> bo_resv -> bo_gpuva
> +	 *     vs
> +	 *     bo_resv -> bo_gpuva -> vm_resv
> +	 *
> +	 * If we fail to lock that's fine, we back off and will get
> +	 * back to it later.
> +	 */
> +	if (!mutex_trylock(&bo->base.gpuva.lock))
> +		return false;
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +		struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> +
> +		if (resv == obj->resv)
> +			continue;
> +
> +		if (!dma_resv_trylock(resv)) {
> +			ret = -EDEADLK;
> +			goto out_unlock;
> +		}
> +
> +		last_locked = vm_bo;
> +	}
> +
> +	/* Update the state before trying to evict the buffer, if the state was
> +	 * updated to something that's harder to reclaim (higher value in the
> +	 * enum), skip it (will be processed when the relevant LRU is).
> +	 */
> +	panthor_gem_update_reclaim_state_locked(bo, &old_state);
> +	if (old_state < bo->reclaim_state) {
> +		ret = -EAGAIN;
> +		goto out_unlock;
> +	}
> +
> +	/* Wait was too long, skip. */
> +	if (should_wait(bo->reclaim_state) &&
> +	    dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP, false, 10) <= 0) {
> +		ret = -ETIMEDOUT;
> +		goto out_unlock;
> +	}
> +
> +	/* Couldn't teardown the GPU mappings? Skip. */
> +	ret = panthor_vm_evict_bo_mappings_locked(bo);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* If everything went fine, evict the object. */
> +	panthor_gem_evict_locked(bo);
> +
> +out_unlock:
> +	if (last_locked) {
> +		drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +			struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> +
> +			if (resv == obj->resv)
> +				continue;
> +
> +			dma_resv_unlock(resv);
> +
> +			if (last_locked == vm_bo)
> +				break;
> +		}
> +	}
> +	mutex_unlock(&bo->base.gpuva.lock);
> +
> +	return ret == 0;
> +}
> +
> +static unsigned long
> +panthor_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
> +{
> +	struct panthor_device *ptdev = shrinker->private_data;
> +	unsigned long remaining = 0;
> +	unsigned long freed = 0;
> +
> +	if (!can_swap())
> +		goto out;
> +
> +	freed += drm_gem_lru_scan(&ptdev->reclaim.unused,
> +				  sc->nr_to_scan - freed, &remaining,
> +				  panthor_gem_try_evict, NULL);
> +	if (freed >= sc->nr_to_scan)
> +		goto out;
> +
> +	freed += drm_gem_lru_scan(&ptdev->reclaim.mmapped,
> +				  sc->nr_to_scan - freed, &remaining,
> +				  panthor_gem_try_evict, NULL);
> +	if (freed >= sc->nr_to_scan)
> +		goto out;
> +
> +	freed += panthor_mmu_reclaim_priv_bos(ptdev, sc->nr_to_scan - freed,
> +					      &remaining, panthor_gem_try_evict);
> +	if (freed >= sc->nr_to_scan)
> +		goto out;
> +
> +	freed += drm_gem_lru_scan(&ptdev->reclaim.gpu_mapped_shared,
> +				  sc->nr_to_scan - freed, &remaining,
> +				  panthor_gem_try_evict, NULL);
> +
> +out:
> +#ifdef CONFIG_DEBUG_FS
> +	/* This is racy, but that's okay, because this is just debugfs
> +	 * reporting and doesn't need to be accurate.
> +	 */
> +	ptdev->reclaim.nr_pages_reclaimed_on_last_scan = freed;
> +#endif
> +
> +	/* If there are things to reclaim, try a couple times before giving up. */
> +	if (!freed && remaining > 0 &&
> +	    atomic_inc_return(&ptdev->reclaim.retry_count) < 2)
> +		return 0;
> +
> +	/* There's nothing left to reclaim, or the resource are contended. Give up now. */
> +	atomic_set(&ptdev->reclaim.retry_count, 0);
> +	return SHRINK_STOP;
> +}
> +
> +int panthor_gem_shrinker_init(struct panthor_device *ptdev)
> +{
> +	struct shrinker *shrinker;
> +
> +	drmm_mutex_init(&ptdev->base, &ptdev->reclaim.lock);

Please consider checking the return value of drmm_mutex_init().

> +	INIT_LIST_HEAD(&ptdev->reclaim.vms);
> +	drm_gem_lru_init(&ptdev->reclaim.unused, &ptdev->reclaim.lock);
> +	drm_gem_lru_init(&ptdev->reclaim.mmapped, &ptdev->reclaim.lock);
> +	drm_gem_lru_init(&ptdev->reclaim.gpu_mapped_shared, &ptdev->reclaim.lock);
> +	ptdev->reclaim.gpu_mapped_count = 0;
> +
> +	/* Teach lockdep about lock ordering wrt. shrinker: */
> +	fs_reclaim_acquire(GFP_KERNEL);
> +	might_lock(&ptdev->reclaim.lock);
> +	fs_reclaim_release(GFP_KERNEL);
> +
> +	shrinker = shrinker_alloc(0, "drm-panthor-gem");
> +	if (!shrinker)
> +		return -ENOMEM;
> +
> +	shrinker->count_objects = panthor_gem_shrinker_count;
> +	shrinker->scan_objects = panthor_gem_shrinker_scan;
> +	shrinker->private_data = ptdev;
> +	ptdev->reclaim.shrinker = shrinker;
> +
> +	shrinker_register(shrinker);
> +	return 0;
> +}
> +
> +void panthor_gem_shrinker_unplug(struct panthor_device *ptdev)
> +{
> +	if (ptdev->reclaim.shrinker)
> +		shrinker_free(ptdev->reclaim.shrinker);
> +}
> +
>   #ifdef CONFIG_DEBUG_FS
>   struct gem_size_totals {
>   	size_t size;
> @@ -1250,10 +1637,42 @@ static struct drm_info_list panthor_gem_debugfs_list[] = {
>   	{ "gems", panthor_gem_show_bos, 0, NULL },
>   };
>   
> +static int shrink_get(void *data, u64 *val)
> +{
> +	struct panthor_device *ptdev =
> +		container_of(data, struct panthor_device, base);
> +
> +	*val = ptdev->reclaim.nr_pages_reclaimed_on_last_scan;
> +	return 0;
> +}
> +
> +static int shrink_set(void *data, u64 val)
> +{
> +	struct panthor_device *ptdev =
> +		container_of(data, struct panthor_device, base);
> +	struct shrink_control sc = {
> +		.gfp_mask = GFP_KERNEL,
> +		.nr_to_scan = val,
> +	};
> +
> +	fs_reclaim_acquire(GFP_KERNEL);
> +	if (ptdev->reclaim.shrinker)
> +		panthor_gem_shrinker_scan(ptdev->reclaim.shrinker, &sc);
> +	fs_reclaim_release(GFP_KERNEL);
> +
> +	return 0;
> +}
> +
> +DEFINE_DEBUGFS_ATTRIBUTE(panthor_gem_debugfs_shrink_fops,
> +			 shrink_get, shrink_set,
> +			 "0x%08llx\n");
> +
>   void panthor_gem_debugfs_init(struct drm_minor *minor)
>   {
>   	drm_debugfs_create_files(panthor_gem_debugfs_list,
>   				 ARRAY_SIZE(panthor_gem_debugfs_list),
>   				 minor->debugfs_root, minor);
> +	debugfs_create_file("shrink", 0600, minor->debugfs_root,
> +			    minor->dev, &panthor_gem_debugfs_shrink_fops);
>   }
>   #endif
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
> index c0a18dca732c..6cb5b597ff1e 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.h
> +++ b/drivers/gpu/drm/panthor/panthor_gem.h
> @@ -1,6 +1,7 @@
>   /* SPDX-License-Identifier: GPL-2.0 or MIT */
>   /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>   /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>   
>   #ifndef __PANTHOR_GEM_H__
>   #define __PANTHOR_GEM_H__
> @@ -93,6 +94,62 @@ struct panthor_gem_dev_map {
>   	struct sg_table *sgt;
>   };
>   
> +/**
> + * enum panthor_gem_reclaim_state - Reclaim state of a GEM object
> + *
> + * This is defined in descending reclaimability order and some part
> + * of the code depends on that.
> + */
> +enum panthor_gem_reclaim_state {
> +	/**
> +	 * @PANTHOR_GEM_UNUSED: GEM is currently unused
> +	 *
> +	 * This can happen when the GEM was previously vmap-ed, mmap-ed,
> +	 * and/or GPU mapped and got unmapped. Because pages are lazily
> +	 * returned to the shmem layer, we want to keep a list of such
> +	 * BOs, because they should be fairly easy to reclaim (no need
> +	 * to wait for GPU to be done, and no need to tear down user
> +	 * mappings either).
> +	 */
> +	PANTHOR_GEM_UNUSED,
> +
> +	/**
> +	 * @PANTHOR_GEM_MMAPPED: GEM is currently mmap-ed
> +	 *
> +	 * When a GEM has pages allocated and the mmap_count is > 0, the
> +	 * GEM is placed in the mmapped list. This comes right after
> +	 * unused because we can relatively easily tear down user mappings.
> +	 */
> +	PANTHOR_GEM_MMAPPED,
> +
> +	/**
> +	 * @PANTHOR_GEM_GPU_MAPPED_PRIVATE: GEM is GPU mapped to only one VM
> +	 *
> +	 * When a GEM is mapped to a single VM, reclaim requests have more
> +	 * chances to succeed, because we only need to synchronize against
> +	 * a single GPU context. This is more annoying than reclaiming
> +	 * mmap-ed pages still, because we have to wait for in-flight jobs
> +	 * to land, and we might not be able to acquire all necessary locks
> +	 * at reclaim time either.
> +	 */
> +	PANTHOR_GEM_GPU_MAPPED_PRIVATE,
> +
> +	/**
> +	 * @PANTHOR_GEM_GPU_MAPPED_SHARED: GEM is GPU mapped to multiple VMs
> +	 *
> +	 * Like PANTHOR_GEM_GPU_MAPPED_PRIVATE, but the synchronization across
> +	 * VMs makes such BOs harder to reclaim.
> +	 */
> +	PANTHOR_GEM_GPU_MAPPED_SHARED,
> +
> +	/**
> +	 * @PANTHOR_GEM_UNRECLAIMABLE: GEM can't be reclaimed
> +	 *
> +	 * Happens when the GEM memory is pinned.
> +	 */
> +	PANTHOR_GEM_UNRECLAIMABLE,
> +};
> +
>   /**
>    * struct panthor_gem_object - Driver specific GEM object.
>    */
> @@ -109,6 +166,9 @@ struct panthor_gem_object {
>   	/** @dmap: Device mapping state */
>   	struct panthor_gem_dev_map dmap;
>   
> +	/** @reclaim_state: Cached reclaim state */
> +	enum panthor_gem_reclaim_state reclaim_state;
> +
>   	/**
>   	 * @exclusive_vm_root_gem: Root GEM of the exclusive VM this GEM object
>   	 * is attached to.
> @@ -190,6 +250,13 @@ struct sg_table *
>   panthor_gem_get_dev_sgt(struct panthor_gem_object *bo);
>   int panthor_gem_pin(struct panthor_gem_object *bo);
>   void panthor_gem_unpin(struct panthor_gem_object *bo);
> +int panthor_gem_swapin_locked(struct panthor_gem_object *bo);
> +void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
> +					     enum panthor_gem_reclaim_state *old_state);
> +bool panthor_gem_try_evict(struct drm_gem_object *obj,
> +			   struct ww_acquire_ctx *ticket);
> +int panthor_gem_shrinker_init(struct panthor_device *ptdev);
> +void panthor_gem_shrinker_unplug(struct panthor_device *ptdev);
>   
>   void panthor_gem_bo_set_label(struct drm_gem_object *obj, const char *label);
>   void panthor_gem_kernel_bo_set_label(struct panthor_kernel_bo *bo, const char *label);
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index 3290e0b5facb..ffd821b3be46 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -1,6 +1,7 @@
>   // SPDX-License-Identifier: GPL-2.0 or MIT
>   /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>   /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>   
>   #include <drm/drm_debugfs.h>
>   #include <drm/drm_drv.h>
> @@ -131,6 +132,9 @@ struct panthor_vma {
>   	 * Only map related flags are accepted.
>   	 */
>   	u32 flags;
> +
> +	/** @evicted: True if the VMA has been evicted. */
> +	bool evicted;
>   };
>   
>   /**
> @@ -191,13 +195,8 @@ struct panthor_vm_op_ctx {
>   		/** @map.bo_offset: Offset in the buffer object. */
>   		u64 bo_offset;
>   
> -		/**
> -		 * @map.sgt: sg-table pointing to pages backing the GEM object.
> -		 *
> -		 * This is gathered at job creation time, such that we don't have
> -		 * to allocate in ::run_job().
> -		 */
> -		struct sg_table *sgt;
> +		/** @map.bo: the BO being mapped. */
> +		struct panthor_gem_object *bo;
>   
>   		/**
>   		 * @map.new_vma: The new VMA object that will be inserted to the VA tree.
> @@ -385,6 +384,18 @@ struct panthor_vm {
>   		/** @locked_region.size: Size of the locked region. */
>   		u64 size;
>   	} locked_region;
> +
> +	/** @reclaim: Fields related to BO reclaim. */
> +	struct {
> +		/** @reclaim.lru: LRU of BOs that are only mapped to this VM. */
> +		struct drm_gem_lru lru;
> +
> +		/**
> +		 * @reclaim.lru_node: Node used to insert the VM in
> +		 * panthor_device::reclaim::vms.
> +		 */
> +		struct list_head lru_node;
> +	} reclaim;
>   };
>   
>   /**
> @@ -678,6 +689,16 @@ int panthor_vm_active(struct panthor_vm *vm)
>   	if (refcount_inc_not_zero(&vm->as.active_cnt))
>   		goto out_dev_exit;
>   
> +	/* As soon as active is called, we place the VM as the end of the VM LRU.


Please replace 'as the end' with 'at the end'.


> +	 * If something fails after that, the only downside is that this VM that
> +	 * never became active in the first place will be reclaimed last, but
> +	 * that's an acceptable trade-off.
> +	 */
> +	mutex_lock(&ptdev->reclaim.lock);
> +	if (vm->reclaim.lru.count)
> +		list_move_tail(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> +	mutex_unlock(&ptdev->reclaim.lock);
> +
>   	/* Make sure we don't race with lock/unlock_region() calls
>   	 * happening around VM bind operations.
>   	 */
> @@ -1074,7 +1095,15 @@ static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo)
>   {
>   	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
>   
> -	panthor_gem_unpin(bo);
> +	/* We couldn't call this when we unlinked, because the resv lock can't
> +	 * be taken in the dma signalling path, so call it now.
> +	 */
> +	dma_resv_lock(bo->base.resv, NULL);
> +	mutex_lock(&bo->base.gpuva.lock);
> +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> +	mutex_unlock(&bo->base.gpuva.lock);
> +	dma_resv_unlock(bo->base.resv);
> +
>   	kfree(vm_bo);
>   }
>   
> @@ -1095,6 +1124,11 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>   	if (op_ctx->map.vm_bo)
>   		drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo);
>   
> +	if (op_ctx->map.bo) {
> +		panthor_gem_unpin(op_ctx->map.bo);
> +		drm_gem_object_put(&op_ctx->map.bo->base);
> +	}
> +
>   	for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++)
>   		kfree(op_ctx->preallocated_vmas[i]);
>   
> @@ -1255,18 +1289,17 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>   	if (ret)
>   		goto err_cleanup;
>   
> +	drm_gem_object_get(&bo->base);
> +	op_ctx->map.bo = bo;
> +
>   	sgt = panthor_gem_get_dev_sgt(bo);
>   	if (IS_ERR(sgt)) {
> -		panthor_gem_unpin(bo);
>   		ret = PTR_ERR(sgt);
>   		goto err_cleanup;
>   	}
>   
> -	op_ctx->map.sgt = sgt;
> -
>   	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base);
>   	if (!preallocated_vm_bo) {
> -		panthor_gem_unpin(bo);
>   		ret = -ENOMEM;
>   		goto err_cleanup;
>   	}
> @@ -1280,9 +1313,19 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>   	dma_resv_lock(panthor_vm_resv(vm), NULL);
>   	mutex_lock(&bo->base.gpuva.lock);
>   	op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
> +	if (panthor_vm_resv(vm) == bo->base.resv)
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
>   	mutex_unlock(&bo->base.gpuva.lock);
>   	dma_resv_unlock(panthor_vm_resv(vm));
>   
> +	if (panthor_vm_resv(vm) != bo->base.resv) {
> +		dma_resv_lock(bo->base.resv, NULL);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +		dma_resv_unlock(bo->base.resv);
> +	}
> +
>   	op_ctx->map.bo_offset = offset;
>   
>   	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
> @@ -1891,6 +1934,10 @@ static void panthor_vm_free(struct drm_gpuvm *gpuvm)
>   	struct panthor_vm *vm = container_of(gpuvm, struct panthor_vm, base);
>   	struct panthor_device *ptdev = vm->ptdev;
>   
> +	mutex_lock(&ptdev->reclaim.lock);
> +	list_del_init(&vm->reclaim.lru_node);
> +	mutex_unlock(&ptdev->reclaim.lock);
> +
>   	mutex_lock(&vm->heaps.lock);
>   	if (drm_WARN_ON(&ptdev->base, vm->heaps.pool))
>   		panthor_heap_pool_destroy(vm->heaps.pool);
> @@ -2104,7 +2151,7 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
>   	panthor_vma_init(vma, op_ctx->flags & PANTHOR_VM_MAP_FLAGS);
>   
>   	ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags),
> -				   op_ctx->map.sgt, op->map.gem.offset,
> +				   op_ctx->map.bo->dmap.sgt, op->map.gem.offset,
>   				   op->map.va.range);
>   	if (ret) {
>   		panthor_vm_op_ctx_return_vma(op_ctx, vma);
> @@ -2188,8 +2235,10 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>   	 * atomicity. panthor_vm_lock_region() bails out early if the new region
>   	 * is already part of the locked region, so no need to do this check here.
>   	 */
> -	panthor_vm_lock_region(vm, unmap_start, unmap_range);
> -	panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
> +	if (!unmap_vma->evicted) {
> +		panthor_vm_lock_region(vm, unmap_start, unmap_range);
> +		panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
> +	}
>   
>   	if (op->remap.prev) {
>   		struct panthor_gem_object *bo = to_panthor_bo(op->remap.prev->gem.obj);
> @@ -2203,6 +2252,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>   
>   		prev_vma = panthor_vm_op_ctx_get_vma(op_ctx);
>   		panthor_vma_init(prev_vma, unmap_vma->flags);
> +		prev_vma->evicted = unmap_vma->evicted;
>   	}
>   
>   	if (op->remap.next) {
> @@ -2217,6 +2267,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>   
>   		next_vma = panthor_vm_op_ctx_get_vma(op_ctx);
>   		panthor_vma_init(next_vma, unmap_vma->flags);
> +		next_vma->evicted = unmap_vma->evicted;
>   	}
>   
>   	drm_gpuva_remap(prev_vma ? &prev_vma->base : NULL,
> @@ -2246,19 +2297,197 @@ static int panthor_gpuva_sm_step_unmap(struct drm_gpuva_op *op,
>   	struct panthor_vma *unmap_vma = container_of(op->unmap.va, struct panthor_vma, base);
>   	struct panthor_vm *vm = priv;
>   
> -	panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
> -			       unmap_vma->base.va.range);
> +	if (!unmap_vma->evicted) {
> +		panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
> +				       unmap_vma->base.va.range);
> +	}
> +
>   	drm_gpuva_unmap(&op->unmap);
>   	panthor_vma_unlink(unmap_vma);
>   	return 0;
>   }
>   
> +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
> +{
> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> +	struct panthor_vm *vm = NULL;
> +	struct drm_gpuvm_bo *vm_bo;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +	lockdep_assert_held(&bo->base.gpuva.lock);
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +		/* We're only supposed to have one vm_bo in the list if we get there. */
> +		drm_WARN_ON(&ptdev->base, vm);
> +		vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +
> +		mutex_lock(&ptdev->reclaim.lock);
> +		drm_gem_lru_move_tail_locked(&vm->reclaim.lru, &bo->base);
> +		if (list_empty(&vm->reclaim.lru_node))
> +			list_move(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> +		mutex_unlock(&ptdev->reclaim.lock);
> +	}
> +}
> +
> +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
> +{
> +	struct drm_gpuvm_bo *vm_bo;
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +		struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +		struct drm_gpuva *va;
> +
> +		if (!mutex_trylock(&vm->op_lock))
> +			return -EDEADLK;
> +
> +		drm_gpuvm_bo_evict(vm_bo, true);
> +		drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +			struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> +
> +			panthor_vm_lock_region(vm, va->va.addr, va->va.range);
> +			panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);
> +			panthor_vm_unlock_region(vm);
> +			vma->evicted = true;
> +		}
> +
> +		mutex_unlock(&vm->op_lock);
> +	}
> +
> +	return 0;
> +}
> +
> +static struct panthor_vma *select_evicted_vma(struct drm_gpuvm_bo *vm_bo,
> +					      struct panthor_vm_op_ctx *op_ctx)
> +{
> +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +	struct panthor_vma *first_evicted_vma = NULL;
> +	struct drm_gpuva *va;
> +
> +	/* Take op_lock to protect against va insertion/removal. */
> +	mutex_lock(&vm->op_lock);
> +	drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> +
> +		if (vma->evicted) {
> +			first_evicted_vma = vma;
> +			panthor_vm_init_op_ctx(op_ctx, va->va.range, va->va.addr, vma->flags);
> +			op_ctx->map.bo_offset = va->gem.offset;
> +			break;
> +		}
> +	}
> +	mutex_unlock(&vm->op_lock);
> +
> +	return first_evicted_vma;
> +}
> +
> +static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
> +			     struct panthor_vma *evicted_vma,
> +			     struct panthor_vm_op_ctx *op_ctx)
> +{
> +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> +	struct drm_gpuva *va;
> +	bool found = false;
> +	int ret;
> +
> +	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
> +	if (ret)
> +		goto out_cleanup;
> +
> +	/* Take op_lock to protect against va insertion/removal. */
> +	mutex_lock(&vm->op_lock);
> +	drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> +
> +		if (vma != evicted_vma)
> +			continue;
> +
> +		/* We can't rely solely on pointer equality, because the VMA might have been
> +		 * freed and a new one allocated at the same address. If the evicted bit
> +		 * is still set, we're sure it's our VMA, because population/eviction is
> +		 * serialized with the BO resv lock.
> +		 */
> +		if (vma->evicted)
> +			found = true;
> +
> +		break;
> +	}
> +
> +	if (found) {
> +		vm->op_ctx = op_ctx;
> +		ret = panthor_vm_lock_region(vm, evicted_vma->base.va.addr,
> +					     evicted_vma->base.va.range);
> +		if (!ret) {
> +			ret = panthor_vm_map_pages(vm, evicted_vma->base.va.addr,
> +						   flags_to_prot(evicted_vma->flags),
> +						   bo->dmap.sgt,
> +						   evicted_vma->base.gem.offset,
> +						   evicted_vma->base.va.range);
> +		}
> +
> +		if (!ret)
> +			evicted_vma->evicted = false;
> +
> +		panthor_vm_unlock_region(vm);
> +		vm->op_ctx = NULL;
> +	}
> +
> +	mutex_unlock(&vm->op_lock);
> +
> +out_cleanup:
> +	panthor_vm_cleanup_op_ctx(op_ctx, vm);
> +	return ret;
> +}
> +
> +static int panthor_vm_restore_vmas(struct drm_gpuvm_bo *vm_bo)
> +{
> +	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> +	struct panthor_vm_op_ctx op_ctx;
> +
> +	if (drm_WARN_ON_ONCE(&vm->ptdev->base, !bo->dmap.sgt))
> +		return -EINVAL;
> +
> +	for (struct panthor_vma *vma = select_evicted_vma(vm_bo, &op_ctx);
> +	     vma; vma = select_evicted_vma(vm_bo, &op_ctx)) {
> +		int ret;
> +
> +		ret = remap_evicted_vma(vm_bo, vma, &op_ctx);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int panthor_vm_bo_validate(struct drm_gpuvm_bo *vm_bo,
> +				  struct drm_exec *exec)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> +	int ret;
> +
> +	ret = panthor_gem_swapin_locked(bo);
> +	if (ret)
> +		return ret;
> +
> +	ret = panthor_vm_restore_vmas(vm_bo);
> +	if (ret)
> +		return ret;
> +
> +	drm_gpuvm_bo_evict(vm_bo, false);
> +	mutex_lock(&bo->base.gpuva.lock);
> +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> +	mutex_unlock(&bo->base.gpuva.lock);
> +	return 0;
> +}
> +
>   static const struct drm_gpuvm_ops panthor_gpuvm_ops = {
>   	.vm_free = panthor_vm_free,
>   	.vm_bo_free = panthor_vm_bo_free,
>   	.sm_step_map = panthor_gpuva_sm_step_map,
>   	.sm_step_remap = panthor_gpuva_sm_step_remap,
>   	.sm_step_unmap = panthor_gpuva_sm_step_unmap,
> +	.vm_bo_validate = panthor_vm_bo_validate,
>   };
>   
>   /**
> @@ -2473,6 +2702,8 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>   	vm->kernel_auto_va.start = auto_kernel_va_start;
>   	vm->kernel_auto_va.end = vm->kernel_auto_va.start + auto_kernel_va_size - 1;
>   
> +	drm_gem_lru_init(&vm->reclaim.lru, &ptdev->reclaim.lock);
> +	INIT_LIST_HEAD(&vm->reclaim.lru_node);
>   	INIT_LIST_HEAD(&vm->node);
>   	INIT_LIST_HEAD(&vm->as.lru_node);
>   	vm->as.id = -1;
> @@ -2820,7 +3051,78 @@ int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec, struct panthor_vm
>   	if (ret)
>   		return ret;
>   
> -	return drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
> +	ret = drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
> +	if (ret)
> +		return ret;
> +
> +	return drm_gpuvm_validate(&vm->base, exec);
> +}
> +
> +unsigned long
> +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
> +			     unsigned int nr_to_scan, unsigned long *remaining,
> +			     bool (*shrink)(struct drm_gem_object *,
> +					    struct ww_acquire_ctx *))
> +{
> +	unsigned long freed = 0;
> +	LIST_HEAD(remaining_vms);
> +	LIST_HEAD(vms);
> +
> +	mutex_lock(&ptdev->reclaim.lock);
> +	list_splice_init(&ptdev->reclaim.vms, &vms);
> +
> +	while (freed < nr_to_scan) {
> +		struct panthor_vm *vm;
> +
> +		vm = list_first_entry_or_null(&vms, typeof(*vm),
> +					      reclaim.lru_node);
> +		if (!vm)
> +			break;
> +
> +		if (!kref_get_unless_zero(&vm->base.kref)) {
> +			list_del_init(&vm->reclaim.lru_node);
> +			continue;
> +		}
> +
> +		mutex_unlock(&ptdev->reclaim.lock);
> +
> +		freed += drm_gem_lru_scan(&vm->reclaim.lru, nr_to_scan - freed,
> +					  remaining, shrink, NULL);
> +
> +		mutex_lock(&ptdev->reclaim.lock);
> +
> +		/* If the VM is still in the temporary list, remove it so we
> +		 * can proceed with the next VM.
> +		 */
> +		if (vm->reclaim.lru_node.prev == &vms) {
> +			list_del_init(&vm->reclaim.lru_node);
> +
> +			/* Keep the VM around if there are still things to
> +			 * reclaim, so we can preserve the LRU order when
> +			 * re-inserting in ptdev->reclaim.vms at the end.
> +			 */
> +			if (vm->reclaim.lru.count > 0)
> +				list_add_tail(&vm->reclaim.lru_node, &remaining_vms);
> +		}
> +
> +		mutex_unlock(&ptdev->reclaim.lock);
> +
> +		panthor_vm_put(vm);
> +
> +		mutex_lock(&ptdev->reclaim.lock);
> +	}
> +
> +	/* Re-insert VMs with remaining data to reclaim at the beginning of
> +	 * the LRU. Note that any activeness change on the VM that happened
> +	 * while we were reclaiming would have moved the VM out of our
> +	 * temporary [remaining_]vms list, meaning anything we re-insert here
> +	 * preserves the LRU order.
> +	 */
> +	list_splice_tail(&vms, &remaining_vms);
> +	list_splice(&remaining_vms, &ptdev->reclaim.vms);
> +	mutex_unlock(&ptdev->reclaim.lock);
> +
> +	return freed;
>   }
>   
>   /**
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.h b/drivers/gpu/drm/panthor/panthor_mmu.h
> index 0e268fdfdb2f..3522fbbce369 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.h
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.h
> @@ -1,6 +1,7 @@
>   /* SPDX-License-Identifier: GPL-2.0 or MIT */
>   /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>   /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>   
>   #ifndef __PANTHOR_MMU_H__
>   #define __PANTHOR_MMU_H__
> @@ -46,6 +47,13 @@ struct panthor_vm *panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>   				     u64 kernel_auto_va_start,
>   				     u64 kernel_auto_va_size);
>   
> +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo);
> +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo);
> +unsigned long
> +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
> +			     unsigned int nr_to_scan, unsigned long *remaining,
> +			     bool (*shrink)(struct drm_gem_object *,
> +					    struct ww_acquire_ctx *));
>   int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec,
>   					struct panthor_vm *vm,
>   					u32 slot_count);

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-15 13:56   ` Akash Goel
@ 2026-01-15 14:36     ` Boris Brezillon
  2026-01-15 14:37     ` Boris Brezillon
  1 sibling, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-15 14:36 UTC (permalink / raw)
  To: Akash Goel
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

On Thu, 15 Jan 2026 13:56:42 +0000
Akash Goel <akash.goel@arm.com> wrote:


> > +static unsigned long
> > +panthor_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
> > +{
> > +	struct panthor_device *ptdev = shrinker->private_data;
> > +	unsigned long count;
> > +
> > +	/* We currently don't have a flag to tell when the content of a
> > +	 * BO can be discarded.
> > +	 */
> > +	if (!can_swap())
> > +		return 0;
> > +
> > +	count = ptdev->reclaim.unused.count;
> > +	count += ptdev->reclaim.mmapped.count;
> > +
> > +	if (can_block(sc))
> > +		count += ptdev->reclaim.gpu_mapped_count;
> > +  
> 
> Should we do like this
> 
> 	return count ? count : SHRINK_EMPTY;
> 
> 
> Other drivers are also doing the same
> 
> https://elixir.bootlin.com/linux/v6.18.4/source/drivers/gpu/drm/xe/xe_shrinker.c#L156 
> 
> 
> https://elixir.bootlin.com/linux/v6.18.4/source/drivers/gpu/drm/ttm/ttm_pool.c#L1160
> 
> https://elixir.bootlin.com/linux/v6.18.4/source/mm/vmalloc.c#L5245
> 
> It might matter if CONFIG_MEMCG is set.

Absolutely. I'll fix that in v2.

> 
> > +	return count;
> > +}

> >   /**
> > @@ -678,6 +689,16 @@ int panthor_vm_active(struct panthor_vm *vm)
> >   	if (refcount_inc_not_zero(&vm->as.active_cnt))
> >   		goto out_dev_exit;
> >   
> > +	/* As soon as active is called, we place the VM as the end of the VM LRU.  
> 
> 
> Please replace 'as the end' with 'at the end'.

Will do.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-15 13:56   ` Akash Goel
  2026-01-15 14:36     ` Boris Brezillon
@ 2026-01-15 14:37     ` Boris Brezillon
  1 sibling, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-15 14:37 UTC (permalink / raw)
  To: Akash Goel
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

On Thu, 15 Jan 2026 13:56:42 +0000
Akash Goel <akash.goel@arm.com> wrote:

> > +int panthor_gem_shrinker_init(struct panthor_device *ptdev)
> > +{
> > +	struct shrinker *shrinker;
> > +
> > +	drmm_mutex_init(&ptdev->base, &ptdev->reclaim.lock);  
> 
> Please consider checking the return value of drmm_mutex_init().

I will.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-09 13:07 ` [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object Boris Brezillon
  2026-01-12 12:06   ` Steven Price
@ 2026-01-15 16:51   ` Liviu Dudau
  2026-01-15 17:27     ` Boris Brezillon
  1 sibling, 1 reply; 62+ messages in thread
From: Liviu Dudau @ 2026-01-15 16:51 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Fri, Jan 09, 2026 at 02:07:57PM +0100, Boris Brezillon wrote:
> While drm_gem_shmem_object does most of the job we need it to do, the
> way sub-resources (pages, sgt, vmap) are handled and their lifetimes
> gets in the way of BO reclaim. There has been attempts to address
> that [1], but in the meantime, new gem_shmem users were introduced
> (accel drivers), and some of them manually free some of these resources.
> This makes things harder to control/sanitize/validate.
> 
> Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
> and forcing gem_shmem users to go through new gem_shmem helpers when they
> need manual control of some sort, and I believe this is a dead end if
> we don't force users to follow some stricter rules through carefully
> designed helpers, because there will always be one user doing crazy things
> with gem_shmem_object internals, which ends up tripping out the common
> helpers when they are called.
> 
> The consensus we reached was that we would be better off forking
> gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
> current transition tries to minimize the changes, but there are still
> some aspects that are different, the main one being that we no longer
> have a pages_use_count, and pages stays around until the GEM object is
> destroyed (or when evicted once we've added a shrinker). The sgt also
> no longer retains pages. This is losely based on how msm does things by
> the way.
> 
> If there's any interest in sharing code (probably with msm, since the
> panthor shrinker is going to be losely based on the msm implementation),
> we can always change gears and do that once we have everything
> working/merged.
> 
> [1]https://patchwork.kernel.org/project/dri-devel/patch/20240105184624.508603-1-dmitry.osipenko@collabora.com/
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>  drivers/gpu/drm/panthor/Kconfig         |   1 -
>  drivers/gpu/drm/panthor/panthor_drv.c   |   7 +-
>  drivers/gpu/drm/panthor/panthor_fw.c    |  16 +-
>  drivers/gpu/drm/panthor/panthor_gem.c   | 696 ++++++++++++++++++++----
>  drivers/gpu/drm/panthor/panthor_gem.h   |  62 ++-
>  drivers/gpu/drm/panthor/panthor_mmu.c   |  49 +-
>  drivers/gpu/drm/panthor/panthor_sched.c |   9 +-
>  7 files changed, 666 insertions(+), 174 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig
> index 55b40ad07f3b..911e7f4810c3 100644
> --- a/drivers/gpu/drm/panthor/Kconfig
> +++ b/drivers/gpu/drm/panthor/Kconfig
> @@ -8,7 +8,6 @@ config DRM_PANTHOR
>  	depends on MMU
>  	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>  	select DRM_EXEC
> -	select DRM_GEM_SHMEM_HELPER
>  	select DRM_GPUVM
>  	select DRM_SCHED
>  	select IOMMU_IO_PGTABLE_LPAE
> diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
> index 52c27a60c84a..90e9abc22d9e 100644
> --- a/drivers/gpu/drm/panthor/panthor_drv.c
> +++ b/drivers/gpu/drm/panthor/panthor_drv.c
> @@ -19,6 +19,7 @@
>  #include <drm/drm_debugfs.h>
>  #include <drm/drm_drv.h>
>  #include <drm/drm_exec.h>
> +#include <drm/drm_file.h>
>  #include <drm/drm_ioctl.h>
>  #include <drm/drm_print.h>
>  #include <drm/drm_syncobj.h>
> @@ -1457,7 +1458,7 @@ static int panthor_ioctl_bo_query_info(struct drm_device *ddev, void *data,
>  	args->create_flags = bo->flags;
>  
>  	args->extra_flags = 0;
> -	if (drm_gem_is_imported(&bo->base.base))
> +	if (drm_gem_is_imported(&bo->base))
>  		args->extra_flags |= DRM_PANTHOR_BO_IS_IMPORTED;
>  
>  	drm_gem_object_put(obj);
> @@ -1671,8 +1672,7 @@ static const struct drm_driver panthor_drm_driver = {
>  	.major = 1,
>  	.minor = 7,
>  
> -	.gem_create_object = panthor_gem_create_object,
> -	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
> +	.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
>  	.gem_prime_import = panthor_gem_prime_import,
>  #ifdef CONFIG_DEBUG_FS
>  	.debugfs_init = panthor_debugfs_init,
> @@ -1822,3 +1822,4 @@ module_exit(panthor_exit);
>  MODULE_AUTHOR("Panthor Project Developers");
>  MODULE_DESCRIPTION("Panthor DRM Driver");
>  MODULE_LICENSE("Dual MIT/GPL");
> +MODULE_IMPORT_NS("DMA_BUF");
> diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
> index a64ec8756bed..f135cf2130b8 100644
> --- a/drivers/gpu/drm/panthor/panthor_fw.c
> +++ b/drivers/gpu/drm/panthor/panthor_fw.c
> @@ -627,7 +627,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
>  		u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK;
>  		struct panthor_gem_object *bo;
>  		u32 vm_map_flags = 0;
> -		struct sg_table *sgt;
>  		u64 va = hdr.va.start;
>  
>  		if (!(hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_WR))
> @@ -665,11 +664,12 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
>  		panthor_fw_init_section_mem(ptdev, section);
>  
>  		bo = to_panthor_bo(section->mem->obj);
> -		sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
> -		if (IS_ERR(sgt))
> -			return PTR_ERR(sgt);
>  
> -		dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
> +		if (drm_WARN_ON_ONCE(&ptdev->base, !bo->dmap.sgt))
> +			return -EINVAL;
> +
> +		dma_sync_sgtable_for_device(ptdev->base.dev, bo->dmap.sgt, DMA_TO_DEVICE);
>  	}
>  
>  	if (hdr.va.start == CSF_MCU_SHARED_REGION_START)
> @@ -729,8 +729,10 @@ panthor_reload_fw_sections(struct panthor_device *ptdev, bool full_reload)
>  			continue;
>  
>  		panthor_fw_init_section_mem(ptdev, section);
> -		sgt = drm_gem_shmem_get_pages_sgt(&to_panthor_bo(section->mem->obj)->base);
> -		if (!drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(sgt)))
> +
> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */

I know these comments are helping us now when reviewing, but I feel that they are not adding
actual information, specially as we do a WARN_ON anyway on it.

> +		sgt = to_panthor_bo(section->mem->obj)->dmap.sgt;
> +		if (!drm_WARN_ON_ONCE(&ptdev->base, !sgt))
>  			dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
>  	}
>  }
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index 4b3d82f001d8..0e52c7a07c87 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -8,9 +8,11 @@
>  #include <linux/dma-mapping.h>
>  #include <linux/err.h>
>  #include <linux/slab.h>
> +#include <linux/vmalloc.h>
>  
>  #include <drm/drm_debugfs.h>
>  #include <drm/drm_file.h>
> +#include <drm/drm_prime.h>
>  #include <drm/drm_print.h>
>  #include <drm/panthor_drm.h>
>  
> @@ -44,7 +46,7 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo)
>  
>  static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
>  {
> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> +	struct panthor_device *ptdev = container_of(bo->base.dev,
>  						    struct panthor_device, base);
>  
>  	bo->debugfs.creator.tgid = current->group_leader->pid;
> @@ -57,7 +59,7 @@ static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
>  
>  static void panthor_gem_debugfs_bo_rm(struct panthor_gem_object *bo)
>  {
> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> +	struct panthor_device *ptdev = container_of(bo->base.dev,
>  						    struct panthor_device, base);
>  
>  	if (list_empty(&bo->debugfs.node))
> @@ -80,9 +82,9 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo) {}
>  #endif
>  
>  static bool
> -should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> +should_map_wc(struct panthor_gem_object *bo)
>  {
> -	struct panthor_device *ptdev = container_of(bo->base.base.dev, struct panthor_device, base);
> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
>  
>  	/* We can't do uncached mappings if the device is coherent,
>  	 * because the zeroing done by the shmem layer at page allocation
> @@ -112,6 +114,208 @@ should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
>  	return true;
>  }
>  
> +static void
> +panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
> +{
> +	if (!bo->backing.pages)
> +		return;
> +
> +	drm_gem_put_pages(&bo->base, bo->backing.pages, true, false);
> +	bo->backing.pages = NULL;
> +}
> +
> +static int
> +panthor_gem_backing_get_pages_locked(struct panthor_gem_object *bo)
> +{
> +	dma_resv_assert_held(bo->base.resv);
> +
> +	if (bo->backing.pages)
> +		return 0;
> +
> +	bo->backing.pages = drm_gem_get_pages(&bo->base);
> +	if (IS_ERR(bo->backing.pages)) {
> +		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
> +			    bo->backing.pages);
> +		return PTR_ERR(bo->backing.pages);
> +	}
> +
> +	return 0;
> +}
> +
> +static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
> +{
> +	int ret;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> +
> +	if (refcount_inc_not_zero(&bo->backing.pin_count))
> +		return 0;
> +
> +	ret = panthor_gem_backing_get_pages_locked(bo);
> +	if (!ret)
> +		refcount_set(&bo->backing.pin_count, 1);
> +
> +	return ret;
> +}
> +
> +static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
> +{
> +	dma_resv_assert_held(bo->base.resv);
> +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> +
> +	/* We don't release anything when pin_count drops to zero.
> +	 * Pages stay there until an explicit cleanup is requested.
> +	 */
> +	if (!refcount_dec_not_one(&bo->backing.pin_count))
> +		refcount_set(&bo->backing.pin_count, 0);
> +}
> +
> +static void
> +panthor_gem_dev_map_cleanup(struct panthor_gem_object *bo)
> +{
> +	if (!bo->dmap.sgt)
> +		return;
> +
> +	dma_unmap_sgtable(drm_dev_dma_dev(bo->base.dev), bo->dmap.sgt, DMA_BIDIRECTIONAL, 0);
> +	sg_free_table(bo->dmap.sgt);
> +	kfree(bo->dmap.sgt);
> +	bo->dmap.sgt = NULL;
> +}
> +
> +static struct sg_table *
> +panthor_gem_dev_map_get_sgt_locked(struct panthor_gem_object *bo)
> +{
> +	struct sg_table *sgt;
> +	int ret;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +
> +	if (bo->dmap.sgt)
> +		return bo->dmap.sgt;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> +		return ERR_PTR(-EINVAL);
> +
> +	/* Pages stay around after they've been allocated. At least that stands
> +	 * until we add a shrinker.
> +	 */
> +	ret = panthor_gem_backing_get_pages_locked(bo);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	sgt = drm_prime_pages_to_sg(bo->base.dev, bo->backing.pages,
> +				    bo->base.size >> PAGE_SHIFT);
> +	if (IS_ERR(sgt))
> +		return sgt;
> +
> +	/* Map the pages for use by the h/w. */
> +	ret = dma_map_sgtable(drm_dev_dma_dev(bo->base.dev), sgt, DMA_BIDIRECTIONAL, 0);
> +	if (ret)
> +		goto err_free_sgt;
> +
> +	bo->dmap.sgt = sgt;
> +	return sgt;
> +
> +err_free_sgt:
> +	sg_free_table(sgt);
> +	kfree(sgt);
> +	return ERR_PTR(ret);
> +}
> +
> +struct sg_table *
> +panthor_gem_get_dev_sgt(struct panthor_gem_object *bo)
> +{
> +	struct sg_table *sgt;
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> +	dma_resv_unlock(bo->base.resv);
> +
> +	return sgt;
> +}
> +
> +static void
> +panthor_gem_vmap_cleanup(struct panthor_gem_object *bo)

As it was already discussed, either this function name needs _locked or
we need to acquire the reservation lock inside it.

> +{
> +	if (!bo->cmap.vaddr)
> +		return;
> +
> +	vunmap(bo->cmap.vaddr);
> +	bo->cmap.vaddr = NULL;
> +	panthor_gem_backing_unpin_locked(bo);
> +}
> +
> +static int
> +panthor_gem_prep_for_cpu_map_locked(struct panthor_gem_object *bo)
> +{
> +	if (should_map_wc(bo)) {
> +		struct sg_table *sgt;
> +
> +		sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> +		if (IS_ERR(sgt))
> +			return PTR_ERR(sgt);
> +	}
> +
> +	return 0;
> +}
> +
> +static void *
> +panthor_gem_vmap_get_locked(struct panthor_gem_object *bo)
> +{
> +	pgprot_t prot = PAGE_KERNEL;
> +	void *vaddr;
> +	int ret;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> +		return ERR_PTR(-EINVAL);
> +
> +	if (refcount_inc_not_zero(&bo->cmap.vaddr_use_count)) {
> +		drm_WARN_ON_ONCE(bo->base.dev, !bo->cmap.vaddr);

Is this drm_WARN_ON_ONCE() necessary? I can't see how we can ever trigger it.

> +		return bo->cmap.vaddr;
> +	}
> +
> +	ret = panthor_gem_backing_pin_locked(bo);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	ret = panthor_gem_prep_for_cpu_map_locked(bo);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	if (should_map_wc(bo))
> +		prot = pgprot_writecombine(prot);
> +
> +	vaddr = vmap(bo->backing.pages, bo->base.size >> PAGE_SHIFT, VM_MAP, prot);
> +	if (!vaddr) {
> +		ret = -ENOMEM;
> +		goto err_unpin;
> +	}
> +
> +	bo->cmap.vaddr = vaddr;
> +	refcount_set(&bo->cmap.vaddr_use_count, 1);
> +	return vaddr;
> +
> +err_unpin:
> +	panthor_gem_backing_unpin_locked(bo);
> +	return ERR_PTR(ret);
> +}
> +
> +static void
> +panthor_gem_vmap_put_locked(struct panthor_gem_object *bo)
> +{
> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> +		return;
> +
> +	if (refcount_dec_not_one(&bo->cmap.vaddr_use_count))
> +		return;
> +
> +	refcount_set(&bo->cmap.vaddr_use_count, 0);
> +	panthor_gem_vmap_cleanup(bo);
> +}
> +
>  static void panthor_gem_free_object(struct drm_gem_object *obj)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(obj);
> @@ -127,8 +331,17 @@ static void panthor_gem_free_object(struct drm_gem_object *obj)
>  
>  	mutex_destroy(&bo->label.lock);
>  
> -	drm_gem_free_mmap_offset(&bo->base.base);
> -	drm_gem_shmem_free(&bo->base);
> +	if (drm_gem_is_imported(obj)) {
> +		drm_prime_gem_destroy(obj, bo->dmap.sgt);
> +	} else {
> +		panthor_gem_vmap_cleanup(bo);
> +		panthor_gem_dev_map_cleanup(bo);
> +		panthor_gem_backing_cleanup(bo);
> +	}
> +
> +	drm_gem_object_release(obj);
> +
> +	kfree(bo);
>  	drm_gem_object_put(vm_root_gem);
>  }
>  
> @@ -159,15 +372,15 @@ panthor_gem_prime_begin_cpu_access(struct dma_buf *dma_buf,
>  {
>  	struct drm_gem_object *obj = dma_buf->priv;
>  	struct drm_device *dev = obj->dev;
> -	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>  	struct dma_buf_attachment *attach;
>  
>  	dma_resv_lock(obj->resv, NULL);
> -	if (shmem->sgt)
> -		dma_sync_sgtable_for_cpu(dev->dev, shmem->sgt, dir);
> +	if (bo->dmap.sgt)
> +		dma_sync_sgtable_for_cpu(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
>  
> -	if (shmem->vaddr)
> -		invalidate_kernel_vmap_range(shmem->vaddr, shmem->base.size);
> +	if (bo->cmap.vaddr)
> +		invalidate_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
>  
>  	list_for_each_entry(attach, &dma_buf->attachments, node) {
>  		struct sg_table *sgt = attach->priv;
> @@ -186,7 +399,7 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
>  {
>  	struct drm_gem_object *obj = dma_buf->priv;
>  	struct drm_device *dev = obj->dev;
> -	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
>  	struct dma_buf_attachment *attach;
>  
>  	dma_resv_lock(obj->resv, NULL);
> @@ -197,11 +410,11 @@ panthor_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
>  			dma_sync_sgtable_for_device(attach->dev, sgt, dir);
>  	}
>  
> -	if (shmem->vaddr)
> -		flush_kernel_vmap_range(shmem->vaddr, shmem->base.size);
> +	if (bo->cmap.vaddr)
> +		flush_kernel_vmap_range(bo->cmap.vaddr, bo->base.size);
>  
> -	if (shmem->sgt)
> -		dma_sync_sgtable_for_device(dev->dev, shmem->sgt, dir);
> +	if (bo->dmap.sgt)
> +		dma_sync_sgtable_for_device(drm_dev_dma_dev(dev), bo->dmap.sgt, dir);
>  
>  	dma_resv_unlock(obj->resv);
>  	return 0;
> @@ -258,53 +471,339 @@ panthor_gem_prime_import(struct drm_device *dev,
>  	return drm_gem_prime_import(dev, dma_buf);
>  }
>  
> +static void panthor_gem_print_info(struct drm_printer *p, unsigned int indent,
> +				   const struct drm_gem_object *obj)
> +{
> +	const struct panthor_gem_object *bo = to_panthor_bo(obj);
> +
> +	if (drm_gem_is_imported(&bo->base))
> +		return;
> +
> +	drm_printf_indent(p, indent, "resident=%s\n", str_true_false(bo->backing.pages));
> +	drm_printf_indent(p, indent, "pages_pin_count=%u\n", refcount_read(&bo->backing.pin_count));
> +	drm_printf_indent(p, indent, "vmap_use_count=%u\n",
> +			  refcount_read(&bo->cmap.vaddr_use_count));
> +	drm_printf_indent(p, indent, "vaddr=%p\n", bo->cmap.vaddr);
> +}
> +
> +static int panthor_gem_pin_locked(struct drm_gem_object *obj)
> +{
> +	if (drm_gem_is_imported(obj))
> +		return 0;
> +
> +	return panthor_gem_backing_pin_locked(to_panthor_bo(obj));
> +}
> +
> +static void panthor_gem_unpin_locked(struct drm_gem_object *obj)
> +{
> +	if (!drm_gem_is_imported(obj))
> +		panthor_gem_backing_unpin_locked(to_panthor_bo(obj));
> +}
> +
> +int panthor_gem_pin(struct panthor_gem_object *bo)
> +{
> +	int ret = 0;
> +
> +	if (drm_gem_is_imported(&bo->base))
> +		return 0;
> +
> +	if (refcount_inc_not_zero(&bo->backing.pin_count))
> +		return 0;
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +	ret = panthor_gem_pin_locked(&bo->base);
> +	dma_resv_unlock(bo->base.resv);
> +
> +	return ret;
> +}
> +
> +void panthor_gem_unpin(struct panthor_gem_object *bo)
> +{
> +	if (drm_gem_is_imported(&bo->base))
> +		return;
> +
> +	if (refcount_dec_not_one(&bo->backing.pin_count))
> +		return;
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +	panthor_gem_unpin_locked(&bo->base);
> +	dma_resv_unlock(bo->base.resv);
> +}
> +
> +static struct sg_table *panthor_gem_get_sg_table(struct drm_gem_object *obj)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +
> +	drm_WARN_ON_ONCE(obj->dev, drm_gem_is_imported(obj));
> +	drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages);
> +	drm_WARN_ON_ONCE(obj->dev, !refcount_read(&bo->backing.pin_count));
> +
> +	return drm_prime_pages_to_sg(obj->dev, bo->backing.pages, obj->size >> PAGE_SHIFT);
> +}
> +
> +static int panthor_gem_vmap_locked(struct drm_gem_object *obj,
> +				   struct iosys_map *map)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +	void *vaddr;
> +
> +	dma_resv_assert_held(obj->resv);
> +
> +	if (drm_gem_is_imported(obj))
> +		return dma_buf_vmap(obj->import_attach->dmabuf, map);
> +
> +	vaddr = panthor_gem_vmap_get_locked(bo);
> +	if (IS_ERR(vaddr))
> +		return PTR_ERR(vaddr);
> +
> +	iosys_map_set_vaddr(map, vaddr);
> +	return 0;
> +}
> +
> +static void panthor_gem_vunmap_locked(struct drm_gem_object *obj,
> +				      struct iosys_map *map)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +
> +	dma_resv_assert_held(obj->resv);
> +
> +	if (drm_gem_is_imported(obj)) {
> +		dma_buf_vunmap(obj->import_attach->dmabuf, map);
> +	} else {
> +		drm_WARN_ON_ONCE(obj->dev, bo->cmap.vaddr != map->vaddr);
> +		panthor_gem_vmap_put_locked(bo);
> +	}
> +}
> +
> +static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +	int ret;
> +
> +	if (drm_gem_is_imported(obj)) {
> +		/* Reset both vm_ops and vm_private_data, so we don't end up with
> +		 * vm_ops pointing to our implementation if the dma-buf backend
> +		 * doesn't set those fields.
> +		 */
> +		vma->vm_private_data = NULL;
> +		vma->vm_ops = NULL;
> +
> +		ret = dma_buf_mmap(obj->dma_buf, vma, 0);
> +
> +		/* Drop the reference drm_gem_mmap_obj() acquired.*/
> +		if (!ret)
> +			drm_gem_object_put(obj);
> +
> +		return ret;
> +	}
> +
> +	if (is_cow_mapping(vma->vm_flags))
> +		return -EINVAL;
> +
> +	dma_resv_lock(obj->resv, NULL);
> +	ret = panthor_gem_backing_get_pages_locked(bo);
> +	if (!ret)
> +		ret = panthor_gem_prep_for_cpu_map_locked(bo);
> +	dma_resv_unlock(obj->resv);
> +
> +	if (ret)
> +		return ret;
> +
> +	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> +	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> +	if (should_map_wc(bo))
> +		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
> +
> +	return 0;
> +}
> +
>  static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(obj);
>  	enum drm_gem_object_status res = 0;
>  
> -	if (drm_gem_is_imported(&bo->base.base) || bo->base.pages)
> +	if (drm_gem_is_imported(&bo->base) || bo->backing.pages)
>  		res |= DRM_GEM_OBJECT_RESIDENT;
>  
>  	return res;
>  }
>  
> -static const struct drm_gem_object_funcs panthor_gem_funcs = {
> -	.free = panthor_gem_free_object,
> -	.print_info = drm_gem_shmem_object_print_info,
> -	.pin = drm_gem_shmem_object_pin,
> -	.unpin = drm_gem_shmem_object_unpin,
> -	.get_sg_table = drm_gem_shmem_object_get_sg_table,
> -	.vmap = drm_gem_shmem_object_vmap,
> -	.vunmap = drm_gem_shmem_object_vunmap,
> -	.mmap = drm_gem_shmem_object_mmap,
> -	.status = panthor_gem_status,
> -	.export = panthor_gem_prime_export,
> -	.vm_ops = &drm_gem_shmem_vm_ops,
> +static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
> +{
> +#ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
> +	unsigned long pfn = page_to_pfn(page);
> +	unsigned long paddr = pfn << PAGE_SHIFT;
> +	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
> +
> +	if (aligned &&
> +	    pmd_none(*vmf->pmd) &&
> +	    folio_test_pmd_mappable(page_folio(page))) {
> +		pfn &= PMD_MASK >> PAGE_SHIFT;
> +		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
> +			return true;
> +	}
> +#endif
> +
> +	return false;
> +}
> +
> +static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> +{
> +	struct vm_area_struct *vma = vmf->vma;
> +	struct drm_gem_object *obj = vma->vm_private_data;
> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> +	loff_t num_pages = obj->size >> PAGE_SHIFT;
> +	vm_fault_t ret;
> +	pgoff_t page_offset;
> +	unsigned long pfn;
> +
> +	/* Offset to faulty address in the VMA. */
> +	page_offset = vmf->pgoff - vma->vm_pgoff;

You're missing a shift right by PAGE_SHIFT here for the rest of the code
to make sense.

With that fixed,

Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>

Best regards,
Liviu

> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +
> +	if (page_offset >= num_pages ||
> +	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
> +		ret = VM_FAULT_SIGBUS;
> +		goto out;
> +	}
> +
> +	if (try_map_pmd(vmf, vmf->address, bo->backing.pages[page_offset])) {
> +		ret = VM_FAULT_NOPAGE;
> +		goto out;
> +	}
> +
> +	pfn = page_to_pfn(bo->backing.pages[page_offset]);
> +	ret = vmf_insert_pfn(vma, vmf->address, pfn);
> +
> + out:
> +	dma_resv_unlock(bo->base.resv);
> +
> +	return ret;
> +}
> +
> +static void panthor_gem_vm_open(struct vm_area_struct *vma)
> +{
> +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> +
> +	drm_WARN_ON(bo->base.dev, drm_gem_is_imported(&bo->base));
> +
> +	dma_resv_lock(bo->base.resv, NULL);
> +
> +	/* We should have already pinned the pages when the buffer was first
> +	 * mmap'd, vm_open() just grabs an additional reference for the new
> +	 * mm the vma is getting copied into (ie. on fork()).
> +	 */
> +	drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages);
> +
> +	dma_resv_unlock(bo->base.resv);
> +
> +	drm_gem_vm_open(vma);
> +}
> +
> +const struct vm_operations_struct panthor_gem_vm_ops = {
> +	.fault = panthor_gem_fault,
> +	.open = panthor_gem_vm_open,
> +	.close = drm_gem_vm_close,
>  };
>  
> -/**
> - * panthor_gem_create_object - Implementation of driver->gem_create_object.
> - * @ddev: DRM device
> - * @size: Size in bytes of the memory the object will reference
> - *
> - * This lets the GEM helpers allocate object structs for us, and keep
> - * our BO stats correct.
> - */
> -struct drm_gem_object *panthor_gem_create_object(struct drm_device *ddev, size_t size)
> -{
> -	struct panthor_gem_object *obj;
> +static const struct drm_gem_object_funcs panthor_gem_funcs = {
> +	.free = panthor_gem_free_object,
> +	.print_info = panthor_gem_print_info,
> +	.pin = panthor_gem_pin_locked,
> +	.unpin = panthor_gem_unpin_locked,
> +	.get_sg_table = panthor_gem_get_sg_table,
> +	.vmap = panthor_gem_vmap_locked,
> +	.vunmap = panthor_gem_vunmap_locked,
> +	.mmap = panthor_gem_mmap,
> +	.status = panthor_gem_status,
> +	.export = panthor_gem_prime_export,
> +	.vm_ops = &panthor_gem_vm_ops,
> +};
>  
> -	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> -	if (!obj)
> +static struct panthor_gem_object *
> +panthor_gem_alloc_object(uint32_t flags)
> +{
> +	struct panthor_gem_object *bo;
> +
> +	bo = kzalloc(sizeof(*bo), GFP_KERNEL);
> +	if (!bo)
>  		return ERR_PTR(-ENOMEM);
>  
> -	obj->base.base.funcs = &panthor_gem_funcs;
> -	mutex_init(&obj->label.lock);
> +	bo->base.funcs = &panthor_gem_funcs;
> +	bo->flags = flags;
> +	mutex_init(&bo->label.lock);
> +	panthor_gem_debugfs_bo_init(bo);
> +	return bo;
> +}
>  
> -	panthor_gem_debugfs_bo_init(obj);
> +static struct panthor_gem_object *
> +panthor_gem_create(struct drm_device *dev, size_t size, uint32_t flags,
> +		   struct panthor_vm *exclusive_vm, u32 usage_flags)
> +{
> +	struct panthor_gem_object *bo;
> +	int ret;
>  
> -	return &obj->base.base;
> +	bo = panthor_gem_alloc_object(flags);
> +	if (IS_ERR(bo))
> +		return bo;
> +
> +	size = PAGE_ALIGN(size);
> +	ret = drm_gem_object_init(dev, &bo->base, size);
> +	if (ret)
> +		goto err_put;
> +
> +	/* Our buffers are kept pinned, so allocating them
> +	 * from the MOVABLE zone is a really bad idea, and
> +	 * conflicts with CMA. See comments above new_inode()
> +	 * why this is required _and_ expected if you're
> +	 * going to pin these pages.
> +	 */
> +	mapping_set_gfp_mask(bo->base.filp->f_mapping,
> +			     GFP_HIGHUSER | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
> +
> +	ret = drm_gem_create_mmap_offset(&bo->base);
> +	if (ret)
> +		goto err_put;
> +
> +	if (exclusive_vm) {
> +		bo->exclusive_vm_root_gem = panthor_vm_root_gem(exclusive_vm);
> +		drm_gem_object_get(bo->exclusive_vm_root_gem);
> +		bo->base.resv = bo->exclusive_vm_root_gem->resv;
> +	}
> +
> +	panthor_gem_debugfs_set_usage_flags(bo, usage_flags);
> +	return bo;
> +
> +err_put:
> +	drm_gem_object_put(&bo->base);
> +	return ERR_PTR(ret);
> +}
> +
> +struct drm_gem_object *
> +panthor_gem_prime_import_sg_table(struct drm_device *dev,
> +				  struct dma_buf_attachment *attach,
> +				  struct sg_table *sgt)
> +{
> +	struct panthor_gem_object *bo;
> +	int ret;
> +
> +	bo = panthor_gem_alloc_object(0);
> +	if (IS_ERR(bo))
> +		return &bo->base;
> +
> +	drm_gem_private_object_init(dev, &bo->base, attach->dmabuf->size);
> +
> +	ret = drm_gem_create_mmap_offset(&bo->base);
> +	if (ret)
> +		goto err_put;
> +
> +	bo->dmap.sgt = sgt;
> +	return &bo->base;
> +
> +err_put:
> +	drm_gem_object_put(&bo->base);
> +	return ERR_PTR(ret);
>  }
>  
>  /**
> @@ -325,54 +824,22 @@ panthor_gem_create_with_handle(struct drm_file *file,
>  			       u64 *size, u32 flags, u32 *handle)
>  {
>  	int ret;
> -	struct drm_gem_shmem_object *shmem;
>  	struct panthor_gem_object *bo;
>  
> -	shmem = drm_gem_shmem_create(ddev, *size);
> -	if (IS_ERR(shmem))
> -		return PTR_ERR(shmem);
> -
> -	bo = to_panthor_bo(&shmem->base);
> -	bo->flags = flags;
> -	bo->base.map_wc = should_map_wc(bo, exclusive_vm);
> -
> -	if (exclusive_vm) {
> -		bo->exclusive_vm_root_gem = panthor_vm_root_gem(exclusive_vm);
> -		drm_gem_object_get(bo->exclusive_vm_root_gem);
> -		bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
> -	}
> -
> -	panthor_gem_debugfs_set_usage_flags(bo, 0);
> -
> -	/* If this is a write-combine mapping, we query the sgt to force a CPU
> -	 * cache flush (dma_map_sgtable() is called when the sgt is created).
> -	 * This ensures the zero-ing is visible to any uncached mapping created
> -	 * by vmap/mmap.
> -	 * FIXME: Ideally this should be done when pages are allocated, not at
> -	 * BO creation time.
> -	 */
> -	if (shmem->map_wc) {
> -		struct sg_table *sgt;
> -
> -		sgt = drm_gem_shmem_get_pages_sgt(shmem);
> -		if (IS_ERR(sgt)) {
> -			ret = PTR_ERR(sgt);
> -			goto out_put_gem;
> -		}
> -	}
> +	bo = panthor_gem_create(ddev, *size, flags, exclusive_vm, 0);
> +	if (IS_ERR(bo))
> +		return PTR_ERR(bo);
>  
>  	/*
>  	 * Allocate an id of idr table where the obj is registered
>  	 * and handle has the id what user can see.
>  	 */
> -	ret = drm_gem_handle_create(file, &shmem->base, handle);
> +	ret = drm_gem_handle_create(file, &bo->base, handle);
>  	if (!ret)
> -		*size = bo->base.base.size;
> +		*size = bo->base.size;
>  
> -out_put_gem:
>  	/* drop reference from allocate - handle holds it now. */
> -	drm_gem_object_put(&shmem->base);
> -
> +	drm_gem_object_put(&bo->base);
>  	return ret;
>  }
>  
> @@ -417,18 +884,17 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>  		 u64 offset, u64 size)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(obj);
> -	struct drm_gem_shmem_object *shmem = &bo->base;
> -	const struct drm_device *dev = shmem->base.dev;
> +	struct device *dma_dev = drm_dev_dma_dev(bo->base.dev);
>  	struct sg_table *sgt;
>  	struct scatterlist *sgl;
>  	unsigned int count;
>  
>  	/* Make sure the range is in bounds. */
> -	if (offset + size < offset || offset + size > shmem->base.size)
> +	if (offset + size < offset || offset + size > bo->base.size)
>  		return -EINVAL;
>  
>  	/* Disallow CPU-cache maintenance on imported buffers. */
> -	if (drm_gem_is_imported(&shmem->base))
> +	if (drm_gem_is_imported(&bo->base))
>  		return -EINVAL;
>  
>  	switch (type) {
> @@ -441,14 +907,14 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>  	}
>  
>  	/* Don't bother if it's WC-mapped */
> -	if (shmem->map_wc)
> +	if (should_map_wc(bo))
>  		return 0;
>  
>  	/* Nothing to do if the size is zero. */
>  	if (size == 0)
>  		return 0;
>  
> -	sgt = drm_gem_shmem_get_pages_sgt(shmem);
> +	sgt = panthor_gem_get_dev_sgt(bo);
>  	if (IS_ERR(sgt))
>  		return PTR_ERR(sgt);
>  
> @@ -489,9 +955,9 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>  		 *
>  		 * for the flush+invalidate case.
>  		 */
> -		dma_sync_single_for_device(dev->dev, paddr, len, DMA_TO_DEVICE);
> +		dma_sync_single_for_device(dma_dev, paddr, len, DMA_TO_DEVICE);
>  		if (type == DRM_PANTHOR_BO_SYNC_CPU_CACHE_FLUSH_AND_INVALIDATE)
> -			dma_sync_single_for_cpu(dev->dev, paddr, len, DMA_FROM_DEVICE);
> +			dma_sync_single_for_cpu(dma_dev, paddr, len, DMA_FROM_DEVICE);
>  	}
>  
>  	return 0;
> @@ -541,7 +1007,6 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  			 size_t size, u32 bo_flags, u32 vm_map_flags,
>  			 u64 gpu_va, const char *name)
>  {
> -	struct drm_gem_shmem_object *obj;
>  	struct panthor_kernel_bo *kbo;
>  	struct panthor_gem_object *bo;
>  	u32 debug_flags = PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL;
> @@ -554,25 +1019,18 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  	if (!kbo)
>  		return ERR_PTR(-ENOMEM);
>  
> -	obj = drm_gem_shmem_create(&ptdev->base, size);
> -	if (IS_ERR(obj)) {
> -		ret = PTR_ERR(obj);
> -		goto err_free_bo;
> -	}
> -
> -	bo = to_panthor_bo(&obj->base);
> -	kbo->obj = &obj->base;
> -	bo->flags = bo_flags;
> -	bo->base.map_wc = should_map_wc(bo, vm);
> -	bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm);
> -	drm_gem_object_get(bo->exclusive_vm_root_gem);
> -	bo->base.base.resv = bo->exclusive_vm_root_gem->resv;
> -
>  	if (vm == panthor_fw_vm(ptdev))
>  		debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED;
>  
> +	bo = panthor_gem_create(&ptdev->base, size, bo_flags, vm, debug_flags);
> +	if (IS_ERR(bo)) {
> +		ret = PTR_ERR(bo);
> +		goto err_free_kbo;
> +	}
> +
> +	kbo->obj = &bo->base;
> +
>  	panthor_gem_kernel_bo_set_label(kbo, name);
> -	panthor_gem_debugfs_set_usage_flags(to_panthor_bo(kbo->obj), debug_flags);
>  
>  	/* The system and GPU MMU page size might differ, which becomes a
>  	 * problem for FW sections that need to be mapped at explicit address
> @@ -596,9 +1054,9 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
>  	panthor_vm_free_va(vm, &kbo->va_node);
>  
>  err_put_obj:
> -	drm_gem_object_put(&obj->base);
> +	drm_gem_object_put(&bo->base);
>  
> -err_free_bo:
> +err_free_kbo:
>  	kfree(kbo);
>  	return ERR_PTR(ret);
>  }
> @@ -646,7 +1104,7 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
>  					 struct seq_file *m,
>  					 struct gem_size_totals *totals)
>  {
> -	unsigned int refcount = kref_read(&bo->base.base.refcount);
> +	unsigned int refcount = kref_read(&bo->base.refcount);
>  	char creator_info[32] = {};
>  	size_t resident_size;
>  	u32 gem_usage_flags = bo->debugfs.flags;
> @@ -656,21 +1114,21 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
>  	if (!refcount)
>  		return;
>  
> -	resident_size = bo->base.pages ? bo->base.base.size : 0;
> +	resident_size = bo->backing.pages ? bo->base.size : 0;
>  
>  	snprintf(creator_info, sizeof(creator_info),
>  		 "%s/%d", bo->debugfs.creator.process_name, bo->debugfs.creator.tgid);
>  	seq_printf(m, "%-32s%-16d%-16d%-16zd%-16zd0x%-16lx",
>  		   creator_info,
> -		   bo->base.base.name,
> +		   bo->base.name,
>  		   refcount,
> -		   bo->base.base.size,
> +		   bo->base.size,
>  		   resident_size,
> -		   drm_vma_node_start(&bo->base.base.vma_node));
> +		   drm_vma_node_start(&bo->base.vma_node));
>  
> -	if (bo->base.base.import_attach)
> +	if (bo->base.import_attach)
>  		gem_state_flags |= PANTHOR_DEBUGFS_GEM_STATE_FLAG_IMPORTED;
> -	if (bo->base.base.dma_buf)
> +	if (bo->base.dma_buf)
>  		gem_state_flags |= PANTHOR_DEBUGFS_GEM_STATE_FLAG_EXPORTED;
>  
>  	seq_printf(m, "0x%-8x 0x%-10x", gem_state_flags, gem_usage_flags);
> @@ -679,10 +1137,8 @@ static void panthor_gem_debugfs_bo_print(struct panthor_gem_object *bo,
>  		seq_printf(m, "%s\n", bo->label.str ? : "");
>  	}
>  
> -	totals->size += bo->base.base.size;
> +	totals->size += bo->base.size;
>  	totals->resident += resident_size;
> -	if (bo->base.madv > 0)
> -		totals->reclaimable += resident_size;
>  }
>  
>  static void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.h b/drivers/gpu/drm/panthor/panthor_gem.h
> index 94b2d17cf032..b66478c9590c 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.h
> +++ b/drivers/gpu/drm/panthor/panthor_gem.h
> @@ -5,7 +5,7 @@
>  #ifndef __PANTHOR_GEM_H__
>  #define __PANTHOR_GEM_H__
>  
> -#include <drm/drm_gem_shmem_helper.h>
> +#include <drm/drm_gem.h>
>  #include <drm/drm_mm.h>
>  
>  #include <linux/iosys-map.h>
> @@ -60,12 +60,51 @@ struct panthor_gem_debugfs {
>  	u32 flags;
>  };
>  
> +/**
> + * struct panthor_gem_backing - GEM memory backing related data
> + */
> +struct panthor_gem_backing {
> +	/** @pages: Pages requested with drm_gem_get_pages() */
> +	struct page **pages;
> +
> +	/** @pin_count: Number of active pin requests on this GEM */
> +	refcount_t pin_count;
> +};
> +
> +/**
> + * struct panthor_gem_cpu_map - GEM CPU mapping related data
> + */
> +struct panthor_gem_cpu_map {
> +	/** @vaddr: Address returned by vmap() */
> +	void *vaddr;
> +
> +	/** @vaddr_use_count: Number of active vmap() requests on this GEM */
> +	refcount_t vaddr_use_count;
> +};
> +
> +/**
> + * struct panthor_gem_dev_map - GEM device mapping related data
> + */
> +struct panthor_gem_dev_map {
> +	/** @sgt: Device mapped sg_table for this GEM */
> +	struct sg_table *sgt;
> +};
> +
>  /**
>   * struct panthor_gem_object - Driver specific GEM object.
>   */
>  struct panthor_gem_object {
> -	/** @base: Inherit from drm_gem_shmem_object. */
> -	struct drm_gem_shmem_object base;
> +	/** @base: Inherit from drm_gem_object. */
> +	struct drm_gem_object base;
> +
> +	/** @backing: Memory backing state */
> +	struct panthor_gem_backing backing;
> +
> +	/** @cmap: CPU mapping state */
> +	struct panthor_gem_cpu_map cmap;
> +
> +	/** @dmap: Device mapping state */
> +	struct panthor_gem_dev_map dmap;
>  
>  	/**
>  	 * @exclusive_vm_root_gem: Root GEM of the exclusive VM this GEM object
> @@ -130,22 +169,25 @@ struct panthor_kernel_bo {
>  	void *kmap;
>  };
>  
> -static inline
> -struct panthor_gem_object *to_panthor_bo(struct drm_gem_object *obj)
> -{
> -	return container_of(to_drm_gem_shmem_obj(obj), struct panthor_gem_object, base);
> -}
> +#define to_panthor_bo(obj) container_of_const(obj, struct panthor_gem_object, base)
>  
>  void panthor_gem_init(struct panthor_device *ptdev);
>  
> -struct drm_gem_object *panthor_gem_create_object(struct drm_device *ddev, size_t size);
> -
> +struct drm_gem_object *
> +panthor_gem_prime_import_sg_table(struct drm_device *dev,
> +				  struct dma_buf_attachment *attach,
> +				  struct sg_table *sgt);
>  int
>  panthor_gem_create_with_handle(struct drm_file *file,
>  			       struct drm_device *ddev,
>  			       struct panthor_vm *exclusive_vm,
>  			       u64 *size, u32 flags, uint32_t *handle);
>  
> +struct sg_table *
> +panthor_gem_get_dev_sgt(struct panthor_gem_object *bo);
> +int panthor_gem_pin(struct panthor_gem_object *bo);
> +void panthor_gem_unpin(struct panthor_gem_object *bo);
> +
>  void panthor_gem_bo_set_label(struct drm_gem_object *obj, const char *label);
>  void panthor_gem_kernel_bo_set_label(struct panthor_kernel_bo *bo, const char *label);
>  int panthor_gem_sync(struct drm_gem_object *obj,
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index b888fff05efe..c323b7123713 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -5,6 +5,7 @@
>  #include <drm/drm_debugfs.h>
>  #include <drm/drm_drv.h>
>  #include <drm/drm_exec.h>
> +#include <drm/drm_file.h>
>  #include <drm/drm_gpuvm.h>
>  #include <drm/drm_managed.h>
>  #include <drm/drm_print.h>
> @@ -1073,8 +1074,7 @@ static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
>  
> -	if (!drm_gem_is_imported(&bo->base.base))
> -		drm_gem_shmem_unpin(&bo->base);
> +	panthor_gem_unpin(bo);
>  	kfree(vm_bo);
>  }
>  
> @@ -1196,7 +1196,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  		return -EINVAL;
>  
>  	/* Make sure the VA and size are in-bounds. */
> -	if (size > bo->base.base.size || offset > bo->base.base.size - size)
> +	if (size > bo->base.size || offset > bo->base.size - size)
>  		return -EINVAL;
>  
>  	/* If the BO has an exclusive VM attached, it can't be mapped to other VMs. */
> @@ -1213,33 +1213,25 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	if (ret)
>  		goto err_cleanup;
>  
> -	if (!drm_gem_is_imported(&bo->base.base)) {
> -		/* Pre-reserve the BO pages, so the map operation doesn't have to
> -		 * allocate. This pin is dropped in panthor_vm_bo_free(), so
> -		 * once we have successfully called drm_gpuvm_bo_create(),
> -		 * GPUVM will take care of dropping the pin for us.
> -		 */
> -		ret = drm_gem_shmem_pin(&bo->base);
> -		if (ret)
> -			goto err_cleanup;
> -	}
> +	/* Pre-reserve the BO pages, so the map operation doesn't have to
> +	 * allocate.
> +	 */
> +	ret = panthor_gem_pin(bo);
> +	if (ret)
> +		goto err_cleanup;
>  
> -	sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
> +	sgt = panthor_gem_get_dev_sgt(bo);
>  	if (IS_ERR(sgt)) {
> -		if (!drm_gem_is_imported(&bo->base.base))
> -			drm_gem_shmem_unpin(&bo->base);
> -
> +		panthor_gem_unpin(bo);
>  		ret = PTR_ERR(sgt);
>  		goto err_cleanup;
>  	}
>  
>  	op_ctx->map.sgt = sgt;
>  
> -	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base.base);
> +	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base);
>  	if (!preallocated_vm_bo) {
> -		if (!drm_gem_is_imported(&bo->base.base))
> -			drm_gem_shmem_unpin(&bo->base);
> -
> +		panthor_gem_unpin(bo);
>  		ret = -ENOMEM;
>  		goto err_cleanup;
>  	}
> @@ -1251,9 +1243,9 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	 * calling this function.
>  	 */
>  	dma_resv_lock(panthor_vm_resv(vm), NULL);
> -	mutex_lock(&bo->base.base.gpuva.lock);
> +	mutex_lock(&bo->base.gpuva.lock);
>  	op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
> -	mutex_unlock(&bo->base.base.gpuva.lock);
> +	mutex_unlock(&bo->base.gpuva.lock);
>  	dma_resv_unlock(panthor_vm_resv(vm));
>  
>  	op_ctx->map.bo_offset = offset;
> @@ -2063,9 +2055,9 @@ static void panthor_vma_link(struct panthor_vm *vm,
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(vma->base.gem.obj);
>  
> -	mutex_lock(&bo->base.base.gpuva.lock);
> +	mutex_lock(&bo->base.gpuva.lock);
>  	drm_gpuva_link(&vma->base, vm_bo);
> -	mutex_unlock(&bo->base.base.gpuva.lock);
> +	mutex_unlock(&bo->base.gpuva.lock);
>  }
>  
>  static void panthor_vma_unlink(struct panthor_vma *vma)
> @@ -2117,11 +2109,12 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
>  static bool
>  iova_mapped_as_huge_page(struct drm_gpuva_op_map *op, u64 addr)
>  {
> +	struct panthor_gem_object *bo = to_panthor_bo(op->gem.obj);
>  	const struct page *pg;
>  	pgoff_t bo_offset;
>  
>  	bo_offset = addr - op->va.addr + op->gem.offset;
> -	pg = to_panthor_bo(op->gem.obj)->base.pages[bo_offset >> PAGE_SHIFT];
> +	pg = bo->backing.pages[bo_offset >> PAGE_SHIFT];
>  
>  	return folio_size(page_folio(pg)) >= SZ_2M;
>  }
> @@ -2190,7 +2183,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  		u64 size = op->remap.prev->va.addr + op->remap.prev->va.range - unmap_start;
>  
>  		ret = panthor_vm_map_pages(vm, unmap_start, flags_to_prot(unmap_vma->flags),
> -					   bo->base.sgt, offset, size);
> +					   bo->dmap.sgt, offset, size);
>  		if (ret)
>  			return ret;
>  
> @@ -2204,7 +2197,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  		u64 size = unmap_start + unmap_range - op->remap.next->va.addr;
>  
>  		ret = panthor_vm_map_pages(vm, addr, flags_to_prot(unmap_vma->flags),
> -					   bo->base.sgt, op->remap.next->gem.offset, size);
> +					   bo->dmap.sgt, op->remap.next->gem.offset, size);
>  		if (ret)
>  			return ret;
>  
> diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c
> index ca272dbae14d..2ddf64870d85 100644
> --- a/drivers/gpu/drm/panthor/panthor_sched.c
> +++ b/drivers/gpu/drm/panthor/panthor_sched.c
> @@ -3,7 +3,7 @@
>  
>  #include <drm/drm_drv.h>
>  #include <drm/drm_exec.h>
> -#include <drm/drm_gem_shmem_helper.h>
> +#include <drm/drm_file.h>
>  #include <drm/drm_managed.h>
>  #include <drm/drm_print.h>
>  #include <drm/gpu_scheduler.h>
> @@ -871,8 +871,7 @@ panthor_queue_get_syncwait_obj(struct panthor_group *group, struct panthor_queue
>  	int ret;
>  
>  	if (queue->syncwait.kmap) {
> -		bo = container_of(queue->syncwait.obj,
> -				  struct panthor_gem_object, base.base);
> +		bo = to_panthor_bo(queue->syncwait.obj);
>  		goto out_sync;
>  	}
>  
> @@ -882,7 +881,7 @@ panthor_queue_get_syncwait_obj(struct panthor_group *group, struct panthor_queue
>  	if (drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(bo)))
>  		goto err_put_syncwait_obj;
>  
> -	queue->syncwait.obj = &bo->base.base;
> +	queue->syncwait.obj = &bo->base;
>  	ret = drm_gem_vmap(queue->syncwait.obj, &map);
>  	if (drm_WARN_ON(&ptdev->base, ret))
>  		goto err_put_syncwait_obj;
> @@ -896,7 +895,7 @@ panthor_queue_get_syncwait_obj(struct panthor_group *group, struct panthor_queue
>  	 * drm_gem_shmem_sync() is a NOP if map_wc=true, so no need to check
>  	 * it here.
>  	 */
> -	panthor_gem_sync(&bo->base.base, queue->syncwait.offset,
> +	panthor_gem_sync(&bo->base, queue->syncwait.offset,
>  			 queue->syncwait.sync64 ?
>  			 sizeof(struct panthor_syncobj_64b) :
>  			 sizeof(struct panthor_syncobj_32b),
> -- 
> 2.52.0
> 

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-15 16:51   ` Liviu Dudau
@ 2026-01-15 17:27     ` Boris Brezillon
  2026-01-15 17:45       ` Liviu Dudau
  0 siblings, 1 reply; 62+ messages in thread
From: Boris Brezillon @ 2026-01-15 17:27 UTC (permalink / raw)
  To: Liviu Dudau
  Cc: Steven Price, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Thu, 15 Jan 2026 16:51:31 +0000
Liviu Dudau <liviu.dudau@arm.com> wrote:

> On Fri, Jan 09, 2026 at 02:07:57PM +0100, Boris Brezillon wrote:
> > While drm_gem_shmem_object does most of the job we need it to do, the
> > way sub-resources (pages, sgt, vmap) are handled and their lifetimes
> > gets in the way of BO reclaim. There has been attempts to address
> > that [1], but in the meantime, new gem_shmem users were introduced
> > (accel drivers), and some of them manually free some of these resources.
> > This makes things harder to control/sanitize/validate.
> > 
> > Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
> > and forcing gem_shmem users to go through new gem_shmem helpers when they
> > need manual control of some sort, and I believe this is a dead end if
> > we don't force users to follow some stricter rules through carefully
> > designed helpers, because there will always be one user doing crazy things
> > with gem_shmem_object internals, which ends up tripping out the common
> > helpers when they are called.
> > 
> > The consensus we reached was that we would be better off forking
> > gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
> > current transition tries to minimize the changes, but there are still
> > some aspects that are different, the main one being that we no longer
> > have a pages_use_count, and pages stays around until the GEM object is
> > destroyed (or when evicted once we've added a shrinker). The sgt also
> > no longer retains pages. This is losely based on how msm does things by
> > the way.
> > 
> > If there's any interest in sharing code (probably with msm, since the
> > panthor shrinker is going to be losely based on the msm implementation),
> > we can always change gears and do that once we have everything
> > working/merged.
> > 
> > [1]https://patchwork.kernel.org/project/dri-devel/patch/20240105184624.508603-1-dmitry.osipenko@collabora.com/
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > ---
> >  drivers/gpu/drm/panthor/Kconfig         |   1 -
> >  drivers/gpu/drm/panthor/panthor_drv.c   |   7 +-
> >  drivers/gpu/drm/panthor/panthor_fw.c    |  16 +-
> >  drivers/gpu/drm/panthor/panthor_gem.c   | 696 ++++++++++++++++++++----
> >  drivers/gpu/drm/panthor/panthor_gem.h   |  62 ++-
> >  drivers/gpu/drm/panthor/panthor_mmu.c   |  49 +-
> >  drivers/gpu/drm/panthor/panthor_sched.c |   9 +-
> >  7 files changed, 666 insertions(+), 174 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig
> > index 55b40ad07f3b..911e7f4810c3 100644
> > --- a/drivers/gpu/drm/panthor/Kconfig
> > +++ b/drivers/gpu/drm/panthor/Kconfig
> > @@ -8,7 +8,6 @@ config DRM_PANTHOR
> >  	depends on MMU
> >  	select DEVFREQ_GOV_SIMPLE_ONDEMAND
> >  	select DRM_EXEC
> > -	select DRM_GEM_SHMEM_HELPER
> >  	select DRM_GPUVM
> >  	select DRM_SCHED
> >  	select IOMMU_IO_PGTABLE_LPAE
> > diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
> > index 52c27a60c84a..90e9abc22d9e 100644
> > --- a/drivers/gpu/drm/panthor/panthor_drv.c
> > +++ b/drivers/gpu/drm/panthor/panthor_drv.c
> > @@ -19,6 +19,7 @@
> >  #include <drm/drm_debugfs.h>
> >  #include <drm/drm_drv.h>
> >  #include <drm/drm_exec.h>
> > +#include <drm/drm_file.h>
> >  #include <drm/drm_ioctl.h>
> >  #include <drm/drm_print.h>
> >  #include <drm/drm_syncobj.h>
> > @@ -1457,7 +1458,7 @@ static int panthor_ioctl_bo_query_info(struct drm_device *ddev, void *data,
> >  	args->create_flags = bo->flags;
> >  
> >  	args->extra_flags = 0;
> > -	if (drm_gem_is_imported(&bo->base.base))
> > +	if (drm_gem_is_imported(&bo->base))
> >  		args->extra_flags |= DRM_PANTHOR_BO_IS_IMPORTED;
> >  
> >  	drm_gem_object_put(obj);
> > @@ -1671,8 +1672,7 @@ static const struct drm_driver panthor_drm_driver = {
> >  	.major = 1,
> >  	.minor = 7,
> >  
> > -	.gem_create_object = panthor_gem_create_object,
> > -	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
> > +	.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
> >  	.gem_prime_import = panthor_gem_prime_import,
> >  #ifdef CONFIG_DEBUG_FS
> >  	.debugfs_init = panthor_debugfs_init,
> > @@ -1822,3 +1822,4 @@ module_exit(panthor_exit);
> >  MODULE_AUTHOR("Panthor Project Developers");
> >  MODULE_DESCRIPTION("Panthor DRM Driver");
> >  MODULE_LICENSE("Dual MIT/GPL");
> > +MODULE_IMPORT_NS("DMA_BUF");
> > diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
> > index a64ec8756bed..f135cf2130b8 100644
> > --- a/drivers/gpu/drm/panthor/panthor_fw.c
> > +++ b/drivers/gpu/drm/panthor/panthor_fw.c
> > @@ -627,7 +627,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
> >  		u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK;
> >  		struct panthor_gem_object *bo;
> >  		u32 vm_map_flags = 0;
> > -		struct sg_table *sgt;
> >  		u64 va = hdr.va.start;
> >  
> >  		if (!(hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_WR))
> > @@ -665,11 +664,12 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
> >  		panthor_fw_init_section_mem(ptdev, section);
> >  
> >  		bo = to_panthor_bo(section->mem->obj);
> > -		sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
> > -		if (IS_ERR(sgt))
> > -			return PTR_ERR(sgt);
> >  
> > -		dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> > +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
> > +		if (drm_WARN_ON_ONCE(&ptdev->base, !bo->dmap.sgt))
> > +			return -EINVAL;
> > +
> > +		dma_sync_sgtable_for_device(ptdev->base.dev, bo->dmap.sgt, DMA_TO_DEVICE);
> >  	}
> >  
> >  	if (hdr.va.start == CSF_MCU_SHARED_REGION_START)
> > @@ -729,8 +729,10 @@ panthor_reload_fw_sections(struct panthor_device *ptdev, bool full_reload)
> >  			continue;
> >  
> >  		panthor_fw_init_section_mem(ptdev, section);
> > -		sgt = drm_gem_shmem_get_pages_sgt(&to_panthor_bo(section->mem->obj)->base);
> > -		if (!drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(sgt)))
> > +
> > +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */  
> 
> I know these comments are helping us now when reviewing, but I feel that they are not adding
> actual information, specially as we do a WARN_ON anyway on it.

Well, it helps the person hitting the WARN_ON() and checking the code
understand what the expectations are, which I think the WARN_ON() alone
doesn't really help with.

> 
> > +		sgt = to_panthor_bo(section->mem->obj)->dmap.sgt;
> > +		if (!drm_WARN_ON_ONCE(&ptdev->base, !sgt))
> >  			dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> >  	}
> >  }
> > diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> > index 4b3d82f001d8..0e52c7a07c87 100644
> > --- a/drivers/gpu/drm/panthor/panthor_gem.c
> > +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> > @@ -8,9 +8,11 @@
> >  #include <linux/dma-mapping.h>
> >  #include <linux/err.h>
> >  #include <linux/slab.h>
> > +#include <linux/vmalloc.h>
> >  
> >  #include <drm/drm_debugfs.h>
> >  #include <drm/drm_file.h>
> > +#include <drm/drm_prime.h>
> >  #include <drm/drm_print.h>
> >  #include <drm/panthor_drm.h>
> >  
> > @@ -44,7 +46,7 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo)
> >  
> >  static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
> >  {
> > -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> > +	struct panthor_device *ptdev = container_of(bo->base.dev,
> >  						    struct panthor_device, base);
> >  
> >  	bo->debugfs.creator.tgid = current->group_leader->pid;
> > @@ -57,7 +59,7 @@ static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
> >  
> >  static void panthor_gem_debugfs_bo_rm(struct panthor_gem_object *bo)
> >  {
> > -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> > +	struct panthor_device *ptdev = container_of(bo->base.dev,
> >  						    struct panthor_device, base);
> >  
> >  	if (list_empty(&bo->debugfs.node))
> > @@ -80,9 +82,9 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo) {}
> >  #endif
> >  
> >  static bool
> > -should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> > +should_map_wc(struct panthor_gem_object *bo)
> >  {
> > -	struct panthor_device *ptdev = container_of(bo->base.base.dev, struct panthor_device, base);
> > +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> >  
> >  	/* We can't do uncached mappings if the device is coherent,
> >  	 * because the zeroing done by the shmem layer at page allocation
> > @@ -112,6 +114,208 @@ should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> >  	return true;
> >  }
> >  
> > +static void
> > +panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
> > +{
> > +	if (!bo->backing.pages)
> > +		return;
> > +
> > +	drm_gem_put_pages(&bo->base, bo->backing.pages, true, false);
> > +	bo->backing.pages = NULL;
> > +}
> > +
> > +static int
> > +panthor_gem_backing_get_pages_locked(struct panthor_gem_object *bo)
> > +{
> > +	dma_resv_assert_held(bo->base.resv);
> > +
> > +	if (bo->backing.pages)
> > +		return 0;
> > +
> > +	bo->backing.pages = drm_gem_get_pages(&bo->base);
> > +	if (IS_ERR(bo->backing.pages)) {
> > +		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
> > +			    bo->backing.pages);
> > +		return PTR_ERR(bo->backing.pages);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
> > +{
> > +	int ret;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> > +
> > +	if (refcount_inc_not_zero(&bo->backing.pin_count))
> > +		return 0;
> > +
> > +	ret = panthor_gem_backing_get_pages_locked(bo);
> > +	if (!ret)
> > +		refcount_set(&bo->backing.pin_count, 1);
> > +
> > +	return ret;
> > +}
> > +
> > +static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
> > +{
> > +	dma_resv_assert_held(bo->base.resv);
> > +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> > +
> > +	/* We don't release anything when pin_count drops to zero.
> > +	 * Pages stay there until an explicit cleanup is requested.
> > +	 */
> > +	if (!refcount_dec_not_one(&bo->backing.pin_count))
> > +		refcount_set(&bo->backing.pin_count, 0);
> > +}
> > +
> > +static void
> > +panthor_gem_dev_map_cleanup(struct panthor_gem_object *bo)
> > +{
> > +	if (!bo->dmap.sgt)
> > +		return;
> > +
> > +	dma_unmap_sgtable(drm_dev_dma_dev(bo->base.dev), bo->dmap.sgt, DMA_BIDIRECTIONAL, 0);
> > +	sg_free_table(bo->dmap.sgt);
> > +	kfree(bo->dmap.sgt);
> > +	bo->dmap.sgt = NULL;
> > +}
> > +
> > +static struct sg_table *
> > +panthor_gem_dev_map_get_sgt_locked(struct panthor_gem_object *bo)
> > +{
> > +	struct sg_table *sgt;
> > +	int ret;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +
> > +	if (bo->dmap.sgt)
> > +		return bo->dmap.sgt;
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> > +		return ERR_PTR(-EINVAL);
> > +
> > +	/* Pages stay around after they've been allocated. At least that stands
> > +	 * until we add a shrinker.
> > +	 */
> > +	ret = panthor_gem_backing_get_pages_locked(bo);
> > +	if (ret)
> > +		return ERR_PTR(ret);
> > +
> > +	sgt = drm_prime_pages_to_sg(bo->base.dev, bo->backing.pages,
> > +				    bo->base.size >> PAGE_SHIFT);
> > +	if (IS_ERR(sgt))
> > +		return sgt;
> > +
> > +	/* Map the pages for use by the h/w. */
> > +	ret = dma_map_sgtable(drm_dev_dma_dev(bo->base.dev), sgt, DMA_BIDIRECTIONAL, 0);
> > +	if (ret)
> > +		goto err_free_sgt;
> > +
> > +	bo->dmap.sgt = sgt;
> > +	return sgt;
> > +
> > +err_free_sgt:
> > +	sg_free_table(sgt);
> > +	kfree(sgt);
> > +	return ERR_PTR(ret);
> > +}
> > +
> > +struct sg_table *
> > +panthor_gem_get_dev_sgt(struct panthor_gem_object *bo)
> > +{
> > +	struct sg_table *sgt;
> > +
> > +	dma_resv_lock(bo->base.resv, NULL);
> > +	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> > +	dma_resv_unlock(bo->base.resv);
> > +
> > +	return sgt;
> > +}
> > +
> > +static void
> > +panthor_gem_vmap_cleanup(struct panthor_gem_object *bo)  
> 
> As it was already discussed, either this function name needs _locked or
> we need to acquire the reservation lock inside it.

Yep, already done in the v2 I'm cooking.

> 
> > +{
> > +	if (!bo->cmap.vaddr)
> > +		return;
> > +
> > +	vunmap(bo->cmap.vaddr);
> > +	bo->cmap.vaddr = NULL;
> > +	panthor_gem_backing_unpin_locked(bo);
> > +}
> > +

[...]

> > +static void *
> > +panthor_gem_vmap_get_locked(struct panthor_gem_object *bo)
> > +{
> > +	pgprot_t prot = PAGE_KERNEL;
> > +	void *vaddr;
> > +	int ret;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> > +		return ERR_PTR(-EINVAL);
> > +
> > +	if (refcount_inc_not_zero(&bo->cmap.vaddr_use_count)) {
> > +		drm_WARN_ON_ONCE(bo->base.dev, !bo->cmap.vaddr);  
> 
> Is this drm_WARN_ON_ONCE() necessary? I can't see how we can ever trigger it.

I know it's not supposed to happen, but isn't WARN_ON() exactly about
catching unexpected situations? :p

> 
> > +		return bo->cmap.vaddr;
> > +	}

[...]

> > +
> > +static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> > +{
> > +	struct vm_area_struct *vma = vmf->vma;
> > +	struct drm_gem_object *obj = vma->vm_private_data;
> > +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > +	loff_t num_pages = obj->size >> PAGE_SHIFT;
> > +	vm_fault_t ret;
> > +	pgoff_t page_offset;
> > +	unsigned long pfn;
> > +
> > +	/* Offset to faulty address in the VMA. */
> > +	page_offset = vmf->pgoff - vma->vm_pgoff;  
> 
> You're missing a shift right by PAGE_SHIFT here for the rest of the code
> to make sense.

Pretty sure I picked that from drm_gem_shmem_helper.c, so if it's buggy
here, it's buggy there too. I believe the pgoff values are in pages not
bytes, so I'd say we're good.

> 
> With that fixed,
> 
> Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap()
  2026-01-09 13:07 ` [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap() Boris Brezillon
  2026-01-12 12:15   ` Steven Price
@ 2026-01-15 17:34   ` Liviu Dudau
  2026-01-15 19:27     ` Boris Brezillon
  2026-01-16  8:19   ` kernel test robot
  2 siblings, 1 reply; 62+ messages in thread
From: Liviu Dudau @ 2026-01-15 17:34 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Fri, Jan 09, 2026 at 02:07:58PM +0100, Boris Brezillon wrote:
> Defer pages allocation until their first access.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>  drivers/gpu/drm/panthor/panthor_gem.c | 119 ++++++++++++++++----------
>  1 file changed, 75 insertions(+), 44 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> index 0e52c7a07c87..44f05bd957e7 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -600,15 +600,6 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
>  	if (is_cow_mapping(vma->vm_flags))
>  		return -EINVAL;
>  
> -	dma_resv_lock(obj->resv, NULL);
> -	ret = panthor_gem_backing_get_pages_locked(bo);
> -	if (!ret)
> -		ret = panthor_gem_prep_for_cpu_map_locked(bo);
> -	dma_resv_unlock(obj->resv);
> -
> -	if (ret)
> -		return ret;
> -
>  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
>  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
>  	if (should_map_wc(bo))
> @@ -628,82 +619,122 @@ static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
>  	return res;
>  }
>  
> -static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
> +static vm_fault_t insert_page(struct vm_fault *vmf, struct page *page)
>  {
> +	struct vm_area_struct *vma = vmf->vma;
> +	vm_fault_t ret;
> +
>  #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
>  	unsigned long pfn = page_to_pfn(page);
>  	unsigned long paddr = pfn << PAGE_SHIFT;
> -	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
> +	bool aligned = (vmf->address & ~PMD_MASK) == (paddr & ~PMD_MASK);
>  
>  	if (aligned &&
>  	    pmd_none(*vmf->pmd) &&
>  	    folio_test_pmd_mappable(page_folio(page))) {
>  		pfn &= PMD_MASK >> PAGE_SHIFT;
> -		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
> -			return true;
> +		ret = vmf_insert_pfn_pmd(vmf, pfn, false);
> +		if (ret == VM_FAULT_NOPAGE)
> +			return VM_FAULT_NOPAGE;
>  	}
>  #endif
>  
> -	return false;
> +	return vmf_insert_pfn(vma, vmf->address, page_to_pfn(page));
>  }
>  
> -static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> +static vm_fault_t nonblocking_page_setup(struct vm_fault *vmf, pgoff_t page_offset)
>  {
>  	struct vm_area_struct *vma = vmf->vma;
> -	struct drm_gem_object *obj = vma->vm_private_data;
>  	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> -	loff_t num_pages = obj->size >> PAGE_SHIFT;
>  	vm_fault_t ret;
> -	pgoff_t page_offset;
> -	unsigned long pfn;
>  
> -	/* Offset to faulty address in the VMA. */
> -	page_offset = vmf->pgoff - vma->vm_pgoff;

Looks like you've removed the line with the missing right shift

> +	if (!dma_resv_trylock(bo->base.resv))
> +		return VM_FAULT_RETRY;
>  
> -	dma_resv_lock(bo->base.resv, NULL);
> +	if (bo->backing.pages)
> +		ret = insert_page(vmf, bo->backing.pages[page_offset]);
> +	else
> +		ret = VM_FAULT_RETRY;
>  
> -	if (page_offset >= num_pages ||
> -	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
> -		ret = VM_FAULT_SIGBUS;
> -		goto out;
> +	dma_resv_unlock(bo->base.resv);
> +	return ret;
> +}
> +
> +static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
> +				      struct panthor_gem_object *bo,
> +				      pgoff_t page_offset, bool mmap_lock_held)
> +{
> +	vm_fault_t ret;
> +	int err;
> +
> +	if (vmf->flags & FAULT_FLAG_INTERRUPTIBLE) {
> +		err = dma_resv_lock_interruptible(bo->base.resv, NULL);
> +		if (err)
> +			return mmap_lock_held ? VM_FAULT_NOPAGE : VM_FAULT_RETRY;
> +	} else {
> +		dma_resv_lock(bo->base.resv, NULL);
>  	}
>  
> -	if (try_map_pmd(vmf, vmf->address, bo->backing.pages[page_offset])) {
> -		ret = VM_FAULT_NOPAGE;
> -		goto out;
> +	err = panthor_gem_backing_get_pages_locked(bo);
> +	if (!err)
> +		err = panthor_gem_prep_for_cpu_map_locked(bo);
> +
> +	if (err) {
> +		ret = mmap_lock_held ? VM_FAULT_SIGBUS : VM_FAULT_RETRY;
> +	} else {
> +		struct page *page = bo->backing.pages[page_offset];
> +
> +		if (mmap_lock_held)
> +			ret = insert_page(vmf, page);
> +		else
> +			ret = VM_FAULT_RETRY;
>  	}
>  
> -	pfn = page_to_pfn(bo->backing.pages[page_offset]);
> -	ret = vmf_insert_pfn(vma, vmf->address, pfn);
> -
> - out:
>  	dma_resv_unlock(bo->base.resv);
>  
>  	return ret;
>  }
>  
> -static void panthor_gem_vm_open(struct vm_area_struct *vma)
> +static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
>  {
> +	struct vm_area_struct *vma = vmf->vma;
>  	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> +	loff_t num_pages = bo->base.size >> PAGE_SHIFT;
> +	pgoff_t page_offset;
> +	vm_fault_t ret;
>  
> -	drm_WARN_ON(bo->base.dev, drm_gem_is_imported(&bo->base));
> +	/* We don't use vmf->pgoff since that has the fake offset */
> +	page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;

... and added it corrected here, so ignore my comment on the previous patch.

Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>

Best regards,
Liviu

> +	if (page_offset >= num_pages)
> +		return VM_FAULT_SIGBUS;
>  
> -	dma_resv_lock(bo->base.resv, NULL);
> +	ret = nonblocking_page_setup(vmf, page_offset);
> +	if (ret != VM_FAULT_RETRY)
> +		return ret;
>  
> -	/* We should have already pinned the pages when the buffer was first
> -	 * mmap'd, vm_open() just grabs an additional reference for the new
> -	 * mm the vma is getting copied into (ie. on fork()).
> -	 */
> -	drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages);
> +	/* Check if we're allowed to retry. */
> +	if (fault_flag_allow_retry_first(vmf->flags)) {
> +		/* If we're allowed to retry but not wait here, return
> +		 * immediately, the wait will be done when the fault
> +		 * handler is called again, with the mmap_lock held.
> +		 */
> +		if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT)
> +			return VM_FAULT_RETRY;
>  
> -	dma_resv_unlock(bo->base.resv);
> +		/* Wait with the mmap lock released, if we're allowed to. */
> +		drm_gem_object_get(&bo->base);
> +		mmap_read_unlock(vmf->vma->vm_mm);
> +		ret = blocking_page_setup(vmf, bo, page_offset, false);
> +		drm_gem_object_put(&bo->base);
> +		return ret;
> +	}
>  
> -	drm_gem_vm_open(vma);
> +	return blocking_page_setup(vmf, bo, page_offset, true);
>  }
>  
>  const struct vm_operations_struct panthor_gem_vm_ops = {
>  	.fault = panthor_gem_fault,
> -	.open = panthor_gem_vm_open,
> +	.open = drm_gem_vm_open,
>  	.close = drm_gem_vm_close,
>  };
>  
> -- 
> 2.52.0
> 

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 7/9] drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for reclaim
  2026-01-09 13:07 ` [PATCH v1 7/9] drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for reclaim Boris Brezillon
  2026-01-12 12:21   ` Steven Price
@ 2026-01-15 17:40   ` Liviu Dudau
  1 sibling, 0 replies; 62+ messages in thread
From: Liviu Dudau @ 2026-01-15 17:40 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Fri, Jan 09, 2026 at 02:07:59PM +0100, Boris Brezillon wrote:
> We're gonna need just the page table reservation logic when we restore
> evicted BO mappings, so let's prepare for that by extracting the
> op_ctx init and page table pre-allocation into separate helpers.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>

Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>

Best regards,
Liviu

> ---
>  drivers/gpu/drm/panthor/panthor_mmu.c | 70 ++++++++++++++++-----------
>  1 file changed, 42 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index c323b7123713..3290e0b5facb 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -1170,6 +1170,45 @@ panthor_vm_op_ctx_prealloc_vmas(struct panthor_vm_op_ctx *op_ctx)
>  	return 0;
>  }
>  
> +static void panthor_vm_init_op_ctx(struct panthor_vm_op_ctx *op_ctx,
> +				   u64 size, u64 va, u32 flags)
> +{
> +	memset(op_ctx, 0, sizeof(*op_ctx));
> +	op_ctx->flags = flags;
> +	op_ctx->va.range = size;
> +	op_ctx->va.addr = va;
> +}
> +
> +static int panthor_vm_op_ctx_prealloc_pts(struct panthor_vm_op_ctx *op_ctx)
> +{
> +	u64 size = op_ctx->va.range;
> +	u64 va = op_ctx->va.addr;
> +	int ret;
> +
> +	/* L1, L2 and L3 page tables.
> +	 * We could optimize L3 allocation by iterating over the sgt and merging
> +	 * 2M contiguous blocks, but it's simpler to over-provision and return
> +	 * the pages if they're not used.
> +	 */
> +	u64 pt_count = ((ALIGN(va + size, 1ull << 39) - ALIGN_DOWN(va, 1ull << 39)) >> 39) +
> +		       ((ALIGN(va + size, 1ull << 30) - ALIGN_DOWN(va, 1ull << 30)) >> 30) +
> +		       ((ALIGN(va + size, 1ull << 21) - ALIGN_DOWN(va, 1ull << 21)) >> 21);
> +
> +	op_ctx->rsvd_page_tables.pages = kcalloc(pt_count,
> +						 sizeof(*op_ctx->rsvd_page_tables.pages),
> +						 GFP_KERNEL);
> +	if (!op_ctx->rsvd_page_tables.pages)
> +		return -ENOMEM;
> +
> +	ret = kmem_cache_alloc_bulk(pt_cache, GFP_KERNEL, pt_count,
> +				    op_ctx->rsvd_page_tables.pages);
> +	op_ctx->rsvd_page_tables.count = ret;
> +	if (ret != pt_count)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> +
>  #define PANTHOR_VM_BIND_OP_MAP_FLAGS \
>  	(DRM_PANTHOR_VM_BIND_OP_MAP_READONLY | \
>  	 DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC | \
> @@ -1185,7 +1224,6 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  {
>  	struct drm_gpuvm_bo *preallocated_vm_bo;
>  	struct sg_table *sgt = NULL;
> -	u64 pt_count;
>  	int ret;
>  
>  	if (!bo)
> @@ -1204,10 +1242,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	    bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm))
>  		return -EINVAL;
>  
> -	memset(op_ctx, 0, sizeof(*op_ctx));
> -	op_ctx->flags = flags;
> -	op_ctx->va.range = size;
> -	op_ctx->va.addr = va;
> +	panthor_vm_init_op_ctx(op_ctx, size, va, flags);
>  
>  	ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx);
>  	if (ret)
> @@ -1250,30 +1285,9 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  
>  	op_ctx->map.bo_offset = offset;
>  
> -	/* L1, L2 and L3 page tables.
> -	 * We could optimize L3 allocation by iterating over the sgt and merging
> -	 * 2M contiguous blocks, but it's simpler to over-provision and return
> -	 * the pages if they're not used.
> -	 */
> -	pt_count = ((ALIGN(va + size, 1ull << 39) - ALIGN_DOWN(va, 1ull << 39)) >> 39) +
> -		   ((ALIGN(va + size, 1ull << 30) - ALIGN_DOWN(va, 1ull << 30)) >> 30) +
> -		   ((ALIGN(va + size, 1ull << 21) - ALIGN_DOWN(va, 1ull << 21)) >> 21);
> -
> -	op_ctx->rsvd_page_tables.pages = kcalloc(pt_count,
> -						 sizeof(*op_ctx->rsvd_page_tables.pages),
> -						 GFP_KERNEL);
> -	if (!op_ctx->rsvd_page_tables.pages) {
> -		ret = -ENOMEM;
> +	ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
> +	if (ret)
>  		goto err_cleanup;
> -	}
> -
> -	ret = kmem_cache_alloc_bulk(pt_cache, GFP_KERNEL, pt_count,
> -				    op_ctx->rsvd_page_tables.pages);
> -	op_ctx->rsvd_page_tables.count = ret;
> -	if (ret != pt_count) {
> -		ret = -ENOMEM;
> -		goto err_cleanup;
> -	}
>  
>  	/* Insert BO into the extobj list last, when we know nothing can fail. */
>  	dma_resv_lock(panthor_vm_resv(vm), NULL);
> -- 
> 2.52.0
> 

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-15 17:27     ` Boris Brezillon
@ 2026-01-15 17:45       ` Liviu Dudau
  2026-01-16 12:09         ` Steven Price
  0 siblings, 1 reply; 62+ messages in thread
From: Liviu Dudau @ 2026-01-15 17:45 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Thu, Jan 15, 2026 at 06:27:00PM +0100, Boris Brezillon wrote:
> On Thu, 15 Jan 2026 16:51:31 +0000
> Liviu Dudau <liviu.dudau@arm.com> wrote:
> 
> > On Fri, Jan 09, 2026 at 02:07:57PM +0100, Boris Brezillon wrote:
> > > While drm_gem_shmem_object does most of the job we need it to do, the
> > > way sub-resources (pages, sgt, vmap) are handled and their lifetimes
> > > gets in the way of BO reclaim. There has been attempts to address
> > > that [1], but in the meantime, new gem_shmem users were introduced
> > > (accel drivers), and some of them manually free some of these resources.
> > > This makes things harder to control/sanitize/validate.
> > > 
> > > Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
> > > and forcing gem_shmem users to go through new gem_shmem helpers when they
> > > need manual control of some sort, and I believe this is a dead end if
> > > we don't force users to follow some stricter rules through carefully
> > > designed helpers, because there will always be one user doing crazy things
> > > with gem_shmem_object internals, which ends up tripping out the common
> > > helpers when they are called.
> > > 
> > > The consensus we reached was that we would be better off forking
> > > gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
> > > current transition tries to minimize the changes, but there are still
> > > some aspects that are different, the main one being that we no longer
> > > have a pages_use_count, and pages stays around until the GEM object is
> > > destroyed (or when evicted once we've added a shrinker). The sgt also
> > > no longer retains pages. This is losely based on how msm does things by
> > > the way.
> > > 
> > > If there's any interest in sharing code (probably with msm, since the
> > > panthor shrinker is going to be losely based on the msm implementation),
> > > we can always change gears and do that once we have everything
> > > working/merged.
> > > 
> > > [1]https://patchwork.kernel.org/project/dri-devel/patch/20240105184624.508603-1-dmitry.osipenko@collabora.com/
> > > 
> > > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > > ---
> > >  drivers/gpu/drm/panthor/Kconfig         |   1 -
> > >  drivers/gpu/drm/panthor/panthor_drv.c   |   7 +-
> > >  drivers/gpu/drm/panthor/panthor_fw.c    |  16 +-
> > >  drivers/gpu/drm/panthor/panthor_gem.c   | 696 ++++++++++++++++++++----
> > >  drivers/gpu/drm/panthor/panthor_gem.h   |  62 ++-
> > >  drivers/gpu/drm/panthor/panthor_mmu.c   |  49 +-
> > >  drivers/gpu/drm/panthor/panthor_sched.c |   9 +-
> > >  7 files changed, 666 insertions(+), 174 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig
> > > index 55b40ad07f3b..911e7f4810c3 100644
> > > --- a/drivers/gpu/drm/panthor/Kconfig
> > > +++ b/drivers/gpu/drm/panthor/Kconfig
> > > @@ -8,7 +8,6 @@ config DRM_PANTHOR
> > >  	depends on MMU
> > >  	select DEVFREQ_GOV_SIMPLE_ONDEMAND
> > >  	select DRM_EXEC
> > > -	select DRM_GEM_SHMEM_HELPER
> > >  	select DRM_GPUVM
> > >  	select DRM_SCHED
> > >  	select IOMMU_IO_PGTABLE_LPAE
> > > diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
> > > index 52c27a60c84a..90e9abc22d9e 100644
> > > --- a/drivers/gpu/drm/panthor/panthor_drv.c
> > > +++ b/drivers/gpu/drm/panthor/panthor_drv.c
> > > @@ -19,6 +19,7 @@
> > >  #include <drm/drm_debugfs.h>
> > >  #include <drm/drm_drv.h>
> > >  #include <drm/drm_exec.h>
> > > +#include <drm/drm_file.h>
> > >  #include <drm/drm_ioctl.h>
> > >  #include <drm/drm_print.h>
> > >  #include <drm/drm_syncobj.h>
> > > @@ -1457,7 +1458,7 @@ static int panthor_ioctl_bo_query_info(struct drm_device *ddev, void *data,
> > >  	args->create_flags = bo->flags;
> > >  
> > >  	args->extra_flags = 0;
> > > -	if (drm_gem_is_imported(&bo->base.base))
> > > +	if (drm_gem_is_imported(&bo->base))
> > >  		args->extra_flags |= DRM_PANTHOR_BO_IS_IMPORTED;
> > >  
> > >  	drm_gem_object_put(obj);
> > > @@ -1671,8 +1672,7 @@ static const struct drm_driver panthor_drm_driver = {
> > >  	.major = 1,
> > >  	.minor = 7,
> > >  
> > > -	.gem_create_object = panthor_gem_create_object,
> > > -	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
> > > +	.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
> > >  	.gem_prime_import = panthor_gem_prime_import,
> > >  #ifdef CONFIG_DEBUG_FS
> > >  	.debugfs_init = panthor_debugfs_init,
> > > @@ -1822,3 +1822,4 @@ module_exit(panthor_exit);
> > >  MODULE_AUTHOR("Panthor Project Developers");
> > >  MODULE_DESCRIPTION("Panthor DRM Driver");
> > >  MODULE_LICENSE("Dual MIT/GPL");
> > > +MODULE_IMPORT_NS("DMA_BUF");
> > > diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
> > > index a64ec8756bed..f135cf2130b8 100644
> > > --- a/drivers/gpu/drm/panthor/panthor_fw.c
> > > +++ b/drivers/gpu/drm/panthor/panthor_fw.c
> > > @@ -627,7 +627,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
> > >  		u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK;
> > >  		struct panthor_gem_object *bo;
> > >  		u32 vm_map_flags = 0;
> > > -		struct sg_table *sgt;
> > >  		u64 va = hdr.va.start;
> > >  
> > >  		if (!(hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_WR))
> > > @@ -665,11 +664,12 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
> > >  		panthor_fw_init_section_mem(ptdev, section);
> > >  
> > >  		bo = to_panthor_bo(section->mem->obj);
> > > -		sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
> > > -		if (IS_ERR(sgt))
> > > -			return PTR_ERR(sgt);
> > >  
> > > -		dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> > > +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
> > > +		if (drm_WARN_ON_ONCE(&ptdev->base, !bo->dmap.sgt))
> > > +			return -EINVAL;
> > > +
> > > +		dma_sync_sgtable_for_device(ptdev->base.dev, bo->dmap.sgt, DMA_TO_DEVICE);
> > >  	}
> > >  
> > >  	if (hdr.va.start == CSF_MCU_SHARED_REGION_START)
> > > @@ -729,8 +729,10 @@ panthor_reload_fw_sections(struct panthor_device *ptdev, bool full_reload)
> > >  			continue;
> > >  
> > >  		panthor_fw_init_section_mem(ptdev, section);
> > > -		sgt = drm_gem_shmem_get_pages_sgt(&to_panthor_bo(section->mem->obj)->base);
> > > -		if (!drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(sgt)))
> > > +
> > > +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */  
> > 
> > I know these comments are helping us now when reviewing, but I feel that they are not adding
> > actual information, specially as we do a WARN_ON anyway on it.
> 
> Well, it helps the person hitting the WARN_ON() and checking the code
> understand what the expectations are, which I think the WARN_ON() alone
> doesn't really help with.
> 
> > 
> > > +		sgt = to_panthor_bo(section->mem->obj)->dmap.sgt;
> > > +		if (!drm_WARN_ON_ONCE(&ptdev->base, !sgt))
> > >  			dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> > >  	}
> > >  }
> > > diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> > > index 4b3d82f001d8..0e52c7a07c87 100644
> > > --- a/drivers/gpu/drm/panthor/panthor_gem.c
> > > +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> > > @@ -8,9 +8,11 @@
> > >  #include <linux/dma-mapping.h>
> > >  #include <linux/err.h>
> > >  #include <linux/slab.h>
> > > +#include <linux/vmalloc.h>
> > >  
> > >  #include <drm/drm_debugfs.h>
> > >  #include <drm/drm_file.h>
> > > +#include <drm/drm_prime.h>
> > >  #include <drm/drm_print.h>
> > >  #include <drm/panthor_drm.h>
> > >  
> > > @@ -44,7 +46,7 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo)
> > >  
> > >  static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
> > >  {
> > > -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> > > +	struct panthor_device *ptdev = container_of(bo->base.dev,
> > >  						    struct panthor_device, base);
> > >  
> > >  	bo->debugfs.creator.tgid = current->group_leader->pid;
> > > @@ -57,7 +59,7 @@ static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
> > >  
> > >  static void panthor_gem_debugfs_bo_rm(struct panthor_gem_object *bo)
> > >  {
> > > -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> > > +	struct panthor_device *ptdev = container_of(bo->base.dev,
> > >  						    struct panthor_device, base);
> > >  
> > >  	if (list_empty(&bo->debugfs.node))
> > > @@ -80,9 +82,9 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo) {}
> > >  #endif
> > >  
> > >  static bool
> > > -should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> > > +should_map_wc(struct panthor_gem_object *bo)
> > >  {
> > > -	struct panthor_device *ptdev = container_of(bo->base.base.dev, struct panthor_device, base);
> > > +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> > >  
> > >  	/* We can't do uncached mappings if the device is coherent,
> > >  	 * because the zeroing done by the shmem layer at page allocation
> > > @@ -112,6 +114,208 @@ should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> > >  	return true;
> > >  }
> > >  
> > > +static void
> > > +panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
> > > +{
> > > +	if (!bo->backing.pages)
> > > +		return;
> > > +
> > > +	drm_gem_put_pages(&bo->base, bo->backing.pages, true, false);
> > > +	bo->backing.pages = NULL;
> > > +}
> > > +
> > > +static int
> > > +panthor_gem_backing_get_pages_locked(struct panthor_gem_object *bo)
> > > +{
> > > +	dma_resv_assert_held(bo->base.resv);
> > > +
> > > +	if (bo->backing.pages)
> > > +		return 0;
> > > +
> > > +	bo->backing.pages = drm_gem_get_pages(&bo->base);
> > > +	if (IS_ERR(bo->backing.pages)) {
> > > +		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
> > > +			    bo->backing.pages);
> > > +		return PTR_ERR(bo->backing.pages);
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
> > > +{
> > > +	int ret;
> > > +
> > > +	dma_resv_assert_held(bo->base.resv);
> > > +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> > > +
> > > +	if (refcount_inc_not_zero(&bo->backing.pin_count))
> > > +		return 0;
> > > +
> > > +	ret = panthor_gem_backing_get_pages_locked(bo);
> > > +	if (!ret)
> > > +		refcount_set(&bo->backing.pin_count, 1);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
> > > +{
> > > +	dma_resv_assert_held(bo->base.resv);
> > > +	drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base));
> > > +
> > > +	/* We don't release anything when pin_count drops to zero.
> > > +	 * Pages stay there until an explicit cleanup is requested.
> > > +	 */
> > > +	if (!refcount_dec_not_one(&bo->backing.pin_count))
> > > +		refcount_set(&bo->backing.pin_count, 0);
> > > +}
> > > +
> > > +static void
> > > +panthor_gem_dev_map_cleanup(struct panthor_gem_object *bo)
> > > +{
> > > +	if (!bo->dmap.sgt)
> > > +		return;
> > > +
> > > +	dma_unmap_sgtable(drm_dev_dma_dev(bo->base.dev), bo->dmap.sgt, DMA_BIDIRECTIONAL, 0);
> > > +	sg_free_table(bo->dmap.sgt);
> > > +	kfree(bo->dmap.sgt);
> > > +	bo->dmap.sgt = NULL;
> > > +}
> > > +
> > > +static struct sg_table *
> > > +panthor_gem_dev_map_get_sgt_locked(struct panthor_gem_object *bo)
> > > +{
> > > +	struct sg_table *sgt;
> > > +	int ret;
> > > +
> > > +	dma_resv_assert_held(bo->base.resv);
> > > +
> > > +	if (bo->dmap.sgt)
> > > +		return bo->dmap.sgt;
> > > +
> > > +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> > > +		return ERR_PTR(-EINVAL);
> > > +
> > > +	/* Pages stay around after they've been allocated. At least that stands
> > > +	 * until we add a shrinker.
> > > +	 */
> > > +	ret = panthor_gem_backing_get_pages_locked(bo);
> > > +	if (ret)
> > > +		return ERR_PTR(ret);
> > > +
> > > +	sgt = drm_prime_pages_to_sg(bo->base.dev, bo->backing.pages,
> > > +				    bo->base.size >> PAGE_SHIFT);
> > > +	if (IS_ERR(sgt))
> > > +		return sgt;
> > > +
> > > +	/* Map the pages for use by the h/w. */
> > > +	ret = dma_map_sgtable(drm_dev_dma_dev(bo->base.dev), sgt, DMA_BIDIRECTIONAL, 0);
> > > +	if (ret)
> > > +		goto err_free_sgt;
> > > +
> > > +	bo->dmap.sgt = sgt;
> > > +	return sgt;
> > > +
> > > +err_free_sgt:
> > > +	sg_free_table(sgt);
> > > +	kfree(sgt);
> > > +	return ERR_PTR(ret);
> > > +}
> > > +
> > > +struct sg_table *
> > > +panthor_gem_get_dev_sgt(struct panthor_gem_object *bo)
> > > +{
> > > +	struct sg_table *sgt;
> > > +
> > > +	dma_resv_lock(bo->base.resv, NULL);
> > > +	sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> > > +	dma_resv_unlock(bo->base.resv);
> > > +
> > > +	return sgt;
> > > +}
> > > +
> > > +static void
> > > +panthor_gem_vmap_cleanup(struct panthor_gem_object *bo)  
> > 
> > As it was already discussed, either this function name needs _locked or
> > we need to acquire the reservation lock inside it.
> 
> Yep, already done in the v2 I'm cooking.
> 
> > 
> > > +{
> > > +	if (!bo->cmap.vaddr)
> > > +		return;
> > > +
> > > +	vunmap(bo->cmap.vaddr);
> > > +	bo->cmap.vaddr = NULL;
> > > +	panthor_gem_backing_unpin_locked(bo);
> > > +}
> > > +
> 
> [...]
> 
> > > +static void *
> > > +panthor_gem_vmap_get_locked(struct panthor_gem_object *bo)
> > > +{
> > > +	pgprot_t prot = PAGE_KERNEL;
> > > +	void *vaddr;
> > > +	int ret;
> > > +
> > > +	dma_resv_assert_held(bo->base.resv);
> > > +
> > > +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> > > +		return ERR_PTR(-EINVAL);
> > > +
> > > +	if (refcount_inc_not_zero(&bo->cmap.vaddr_use_count)) {
> > > +		drm_WARN_ON_ONCE(bo->base.dev, !bo->cmap.vaddr);  
> > 
> > Is this drm_WARN_ON_ONCE() necessary? I can't see how we can ever trigger it.
> 
> I know it's not supposed to happen, but isn't WARN_ON() exactly about
> catching unexpected situations? :p

Agree, but I'm not convinced the WARN_ON() can be triggered at all as cmap.vaddr
should not be zero if cmap.vaddr_use_count is non-zero.

> 
> > 
> > > +		return bo->cmap.vaddr;
> > > +	}
> 
> [...]
> 
> > > +
> > > +static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> > > +{
> > > +	struct vm_area_struct *vma = vmf->vma;
> > > +	struct drm_gem_object *obj = vma->vm_private_data;
> > > +	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > > +	loff_t num_pages = obj->size >> PAGE_SHIFT;
> > > +	vm_fault_t ret;
> > > +	pgoff_t page_offset;
> > > +	unsigned long pfn;
> > > +
> > > +	/* Offset to faulty address in the VMA. */
> > > +	page_offset = vmf->pgoff - vma->vm_pgoff;  
> > 
> > You're missing a shift right by PAGE_SHIFT here for the rest of the code
> > to make sense.
> 
> Pretty sure I picked that from drm_gem_shmem_helper.c, so if it's buggy
> here, it's buggy there too. I believe the pgoff values are in pages not
> bytes, so I'd say we're good.

I've managed to confuse myself by looking at the drm_gem_shmem_fault() implementation
and failing to notice that the similar looking code is using pgoff and not address
like the drm_gem_shmem_helper.c. Like I've said in the following email, line got
removed anyway so ignore me on this one.

Best regards,
Liviu

> 
> > 
> > With that fixed,
> > 
> > Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap()
  2026-01-15 17:34   ` Liviu Dudau
@ 2026-01-15 19:27     ` Boris Brezillon
  0 siblings, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-15 19:27 UTC (permalink / raw)
  To: Liviu Dudau
  Cc: Steven Price, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel

On Thu, 15 Jan 2026 17:34:46 +0000
Liviu Dudau <liviu.dudau@arm.com> wrote:

> On Fri, Jan 09, 2026 at 02:07:58PM +0100, Boris Brezillon wrote:
> > Defer pages allocation until their first access.
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > ---
> >  drivers/gpu/drm/panthor/panthor_gem.c | 119 ++++++++++++++++----------
> >  1 file changed, 75 insertions(+), 44 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> > index 0e52c7a07c87..44f05bd957e7 100644
> > --- a/drivers/gpu/drm/panthor/panthor_gem.c
> > +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> > @@ -600,15 +600,6 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
> >  	if (is_cow_mapping(vma->vm_flags))
> >  		return -EINVAL;
> >  
> > -	dma_resv_lock(obj->resv, NULL);
> > -	ret = panthor_gem_backing_get_pages_locked(bo);
> > -	if (!ret)
> > -		ret = panthor_gem_prep_for_cpu_map_locked(bo);
> > -	dma_resv_unlock(obj->resv);
> > -
> > -	if (ret)
> > -		return ret;
> > -
> >  	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> >  	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> >  	if (should_map_wc(bo))
> > @@ -628,82 +619,122 @@ static enum drm_gem_object_status panthor_gem_status(struct drm_gem_object *obj)
> >  	return res;
> >  }
> >  
> > -static bool try_map_pmd(struct vm_fault *vmf, unsigned long addr, struct page *page)
> > +static vm_fault_t insert_page(struct vm_fault *vmf, struct page *page)
> >  {
> > +	struct vm_area_struct *vma = vmf->vma;
> > +	vm_fault_t ret;
> > +
> >  #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
> >  	unsigned long pfn = page_to_pfn(page);
> >  	unsigned long paddr = pfn << PAGE_SHIFT;
> > -	bool aligned = (addr & ~PMD_MASK) == (paddr & ~PMD_MASK);
> > +	bool aligned = (vmf->address & ~PMD_MASK) == (paddr & ~PMD_MASK);
> >  
> >  	if (aligned &&
> >  	    pmd_none(*vmf->pmd) &&
> >  	    folio_test_pmd_mappable(page_folio(page))) {
> >  		pfn &= PMD_MASK >> PAGE_SHIFT;
> > -		if (vmf_insert_pfn_pmd(vmf, pfn, false) == VM_FAULT_NOPAGE)
> > -			return true;
> > +		ret = vmf_insert_pfn_pmd(vmf, pfn, false);
> > +		if (ret == VM_FAULT_NOPAGE)
> > +			return VM_FAULT_NOPAGE;
> >  	}
> >  #endif
> >  
> > -	return false;
> > +	return vmf_insert_pfn(vma, vmf->address, page_to_pfn(page));
> >  }
> >  
> > -static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> > +static vm_fault_t nonblocking_page_setup(struct vm_fault *vmf, pgoff_t page_offset)
> >  {
> >  	struct vm_area_struct *vma = vmf->vma;
> > -	struct drm_gem_object *obj = vma->vm_private_data;
> >  	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > -	loff_t num_pages = obj->size >> PAGE_SHIFT;
> >  	vm_fault_t ret;
> > -	pgoff_t page_offset;
> > -	unsigned long pfn;
> >  
> > -	/* Offset to faulty address in the VMA. */
> > -	page_offset = vmf->pgoff - vma->vm_pgoff;  
> 
> Looks like you've removed the line with the missing right shift
> 
> > +	if (!dma_resv_trylock(bo->base.resv))
> > +		return VM_FAULT_RETRY;
> >  
> > -	dma_resv_lock(bo->base.resv, NULL);
> > +	if (bo->backing.pages)
> > +		ret = insert_page(vmf, bo->backing.pages[page_offset]);
> > +	else
> > +		ret = VM_FAULT_RETRY;
> >  
> > -	if (page_offset >= num_pages ||
> > -	    drm_WARN_ON_ONCE(obj->dev, !bo->backing.pages)) {
> > -		ret = VM_FAULT_SIGBUS;
> > -		goto out;
> > +	dma_resv_unlock(bo->base.resv);
> > +	return ret;
> > +}
> > +
> > +static vm_fault_t blocking_page_setup(struct vm_fault *vmf,
> > +				      struct panthor_gem_object *bo,
> > +				      pgoff_t page_offset, bool mmap_lock_held)
> > +{
> > +	vm_fault_t ret;
> > +	int err;
> > +
> > +	if (vmf->flags & FAULT_FLAG_INTERRUPTIBLE) {
> > +		err = dma_resv_lock_interruptible(bo->base.resv, NULL);
> > +		if (err)
> > +			return mmap_lock_held ? VM_FAULT_NOPAGE : VM_FAULT_RETRY;
> > +	} else {
> > +		dma_resv_lock(bo->base.resv, NULL);
> >  	}
> >  
> > -	if (try_map_pmd(vmf, vmf->address, bo->backing.pages[page_offset])) {
> > -		ret = VM_FAULT_NOPAGE;
> > -		goto out;
> > +	err = panthor_gem_backing_get_pages_locked(bo);
> > +	if (!err)
> > +		err = panthor_gem_prep_for_cpu_map_locked(bo);
> > +
> > +	if (err) {
> > +		ret = mmap_lock_held ? VM_FAULT_SIGBUS : VM_FAULT_RETRY;
> > +	} else {
> > +		struct page *page = bo->backing.pages[page_offset];
> > +
> > +		if (mmap_lock_held)
> > +			ret = insert_page(vmf, page);
> > +		else
> > +			ret = VM_FAULT_RETRY;
> >  	}
> >  
> > -	pfn = page_to_pfn(bo->backing.pages[page_offset]);
> > -	ret = vmf_insert_pfn(vma, vmf->address, pfn);
> > -
> > - out:
> >  	dma_resv_unlock(bo->base.resv);
> >  
> >  	return ret;
> >  }
> >  
> > -static void panthor_gem_vm_open(struct vm_area_struct *vma)
> > +static vm_fault_t panthor_gem_fault(struct vm_fault *vmf)
> >  {
> > +	struct vm_area_struct *vma = vmf->vma;
> >  	struct panthor_gem_object *bo = to_panthor_bo(vma->vm_private_data);
> > +	loff_t num_pages = bo->base.size >> PAGE_SHIFT;
> > +	pgoff_t page_offset;
> > +	vm_fault_t ret;
> >  
> > -	drm_WARN_ON(bo->base.dev, drm_gem_is_imported(&bo->base));
> > +	/* We don't use vmf->pgoff since that has the fake offset */
> > +	page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;  
> 
> ... and added it corrected here, so ignore my comment on the previous patch.

Hm, actually I think we should have

	page_offset = vmf->pgoff - vma->vm_pgoff;

here so we can get rid of the right shift (which is not needed because
these offsets are in pages, not bytes). I've mixed the old and new
versions of the fault handler. I'll fix that in v2.

> 
> Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>
> 
> Best regards,
> Liviu
> 
> > +	if (page_offset >= num_pages)
> > +		return VM_FAULT_SIGBUS;
> >  
> > -	dma_resv_lock(bo->base.resv, NULL);
> > +	ret = nonblocking_page_setup(vmf, page_offset);
> > +	if (ret != VM_FAULT_RETRY)
> > +		return ret;
> >  
> > -	/* We should have already pinned the pages when the buffer was first
> > -	 * mmap'd, vm_open() just grabs an additional reference for the new
> > -	 * mm the vma is getting copied into (ie. on fork()).
> > -	 */
> > -	drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages);
> > +	/* Check if we're allowed to retry. */
> > +	if (fault_flag_allow_retry_first(vmf->flags)) {
> > +		/* If we're allowed to retry but not wait here, return
> > +		 * immediately, the wait will be done when the fault
> > +		 * handler is called again, with the mmap_lock held.
> > +		 */
> > +		if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT)
> > +			return VM_FAULT_RETRY;
> >  
> > -	dma_resv_unlock(bo->base.resv);
> > +		/* Wait with the mmap lock released, if we're allowed to. */
> > +		drm_gem_object_get(&bo->base);
> > +		mmap_read_unlock(vmf->vma->vm_mm);
> > +		ret = blocking_page_setup(vmf, bo, page_offset, false);
> > +		drm_gem_object_put(&bo->base);
> > +		return ret;
> > +	}
> >  
> > -	drm_gem_vm_open(vma);
> > +	return blocking_page_setup(vmf, bo, page_offset, true);
> >  }
> >  
> >  const struct vm_operations_struct panthor_gem_vm_ops = {
> >  	.fault = panthor_gem_fault,
> > -	.open = panthor_gem_vm_open,
> > +	.open = drm_gem_vm_open,
> >  	.close = drm_gem_vm_close,
> >  };
> >  
> > -- 
> > 2.52.0
> >   


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap()
  2026-01-09 13:07 ` [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap() Boris Brezillon
  2026-01-12 12:15   ` Steven Price
  2026-01-15 17:34   ` Liviu Dudau
@ 2026-01-16  8:19   ` kernel test robot
  2 siblings, 0 replies; 62+ messages in thread
From: kernel test robot @ 2026-01-16  8:19 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: oe-kbuild-all, dri-devel, David Airlie, Simona Vetter, Akash Goel,
	Rob Clark, Sean Paul, Konrad Dybcio, Akhil P Oommen,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
	Dmitry Osipenko, Chris Diamand, Danilo Krummrich, Matthew Brost,
	Thomas Hellström, Alice Ryhl, kernel

Hi Boris,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[cannot apply to linus/master v6.19-rc5 next-20260115]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Boris-Brezillon/drm-gem-Consider-GEM-object-reclaimable-if-shrinking-fails/20260109-211034
base:   https://gitlab.freedesktop.org/drm/misc/kernel.git drm-misc-next
patch link:    https://lore.kernel.org/r/20260109130801.1239558-7-boris.brezillon%40collabora.com
patch subject: [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap()
config: um-allyesconfig (https://download.01.org/0day-ci/archive/20260116/202601161646.7Uuv5fVY-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260116/202601161646.7Uuv5fVY-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601161646.7Uuv5fVY-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/gpu/drm/panthor/panthor_gem.c: In function 'insert_page':
>> drivers/gpu/drm/panthor/panthor_gem.c:625:20: warning: unused variable 'ret' [-Wunused-variable]
     625 |         vm_fault_t ret;
         |                    ^~~


vim +/ret +625 drivers/gpu/drm/panthor/panthor_gem.c

   621	
   622	static vm_fault_t insert_page(struct vm_fault *vmf, struct page *page)
   623	{
   624		struct vm_area_struct *vma = vmf->vma;
 > 625		vm_fault_t ret;
   626	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-15 17:45       ` Liviu Dudau
@ 2026-01-16 12:09         ` Steven Price
  0 siblings, 0 replies; 62+ messages in thread
From: Steven Price @ 2026-01-16 12:09 UTC (permalink / raw)
  To: Liviu Dudau, Boris Brezillon
  Cc: Adrián Larumbe, dri-devel, David Airlie, Simona Vetter,
	Akash Goel, Rob Clark, Sean Paul, Konrad Dybcio, Akhil P Oommen,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
	Dmitry Osipenko, Chris Diamand, Danilo Krummrich, Matthew Brost,
	Thomas Hellström, Alice Ryhl, kernel

On 15/01/2026 17:45, Liviu Dudau wrote:
> On Thu, Jan 15, 2026 at 06:27:00PM +0100, Boris Brezillon wrote:
>> On Thu, 15 Jan 2026 16:51:31 +0000
>> Liviu Dudau <liviu.dudau@arm.com> wrote:
>>
>>> On Fri, Jan 09, 2026 at 02:07:57PM +0100, Boris Brezillon wrote:
[...]
>>>> +static void *
>>>> +panthor_gem_vmap_get_locked(struct panthor_gem_object *bo)
>>>> +{
>>>> +	pgprot_t prot = PAGE_KERNEL;
>>>> +	void *vaddr;
>>>> +	int ret;
>>>> +
>>>> +	dma_resv_assert_held(bo->base.resv);
>>>> +
>>>> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
>>>> +		return ERR_PTR(-EINVAL);
>>>> +
>>>> +	if (refcount_inc_not_zero(&bo->cmap.vaddr_use_count)) {
>>>> +		drm_WARN_ON_ONCE(bo->base.dev, !bo->cmap.vaddr);  
>>>
>>> Is this drm_WARN_ON_ONCE() necessary? I can't see how we can ever trigger it.
>>
>> I know it's not supposed to happen, but isn't WARN_ON() exactly about
>> catching unexpected situations? :p
> 
> Agree, but I'm not convinced the WARN_ON() can be triggered at all as cmap.vaddr
> should not be zero if cmap.vaddr_use_count is non-zero.

If you can ever see how a WARN_ON can be triggered, then there's a bug
to fix. If we went around removing WARN_ON()s that we think cannot be
triggered then we'd have no WARN_ON()s left.

A better argument here is that we could handle the error. At the moment
we'd end up returning NULL, but the caller (panthor_gem_vmap_locked())
checks IS_ERR. So either we could return a ERR_PTR in this case, or
handle the NULL in the caller. [Although given this is a "can never
happen" case I don't tend to worry too much about the error handling...]

Thanks,
Steve


^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-12 14:17     ` Boris Brezillon
  2026-01-12 16:03       ` Steven Price
@ 2026-01-21 11:11       ` Akash Goel
  2026-01-21 15:17         ` Boris Brezillon
  1 sibling, 1 reply; 62+ messages in thread
From: Akash Goel @ 2026-01-21 11:11 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price
  Cc: Liviu Dudau, Adrián Larumbe, dri-devel, David Airlie,
	Simona Vetter, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

Hi Boris, Steve


On 1/12/26 14:17, Boris Brezillon wrote:
> On Mon, 12 Jan 2026 12:06:17 +0000
> Steven Price <steven.price@arm.com> wrote:
> 
>> On 09/01/2026 13:07, Boris Brezillon wrote:
>>> While drm_gem_shmem_object does most of the job we need it to do, the
>>> way sub-resources (pages, sgt, vmap) are handled and their lifetimes
>>> gets in the way of BO reclaim. There has been attempts to address
>>> that [1], but in the meantime, new gem_shmem users were introduced
>>> (accel drivers), and some of them manually free some of these resources.
>>> This makes things harder to control/sanitize/validate.
>>>
>>> Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
>>> and forcing gem_shmem users to go through new gem_shmem helpers when they
>>> need manual control of some sort, and I believe this is a dead end if
>>> we don't force users to follow some stricter rules through carefully
>>> designed helpers, because there will always be one user doing crazy things
>>> with gem_shmem_object internals, which ends up tripping out the common
>>> helpers when they are called.
>>>
>>> The consensus we reached was that we would be better off forking
>>> gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
>>> current transition tries to minimize the changes, but there are still
>>> some aspects that are different, the main one being that we no longer
>>> have a pages_use_count, and pages stays around until the GEM object is
>>> destroyed (or when evicted once we've added a shrinker). The sgt also
>>> no longer retains pages. This is losely based on how msm does things by
>>> the way.
>>
>>  From a reviewing perspective it's a little tricky trying to match up the
>> implementation to shmem because of these changes. I don't know how
>> difficult it would be to split the changes to a patch which literally
>> copies (with renames) from shmem, followed by simplifying out the parts
>> we don't want.
> 
> It's a bit annoying as the new implementation is not based on shmem at
> all, but if you think it helps the review, I can try what you're
> suggesting. I mean, I'm not convinced it will be significantly easier
> to review with this extra step, since the new logic is different enough
> (especially when it comes to resource refcounting) that it needs a
> careful review anyway (which you started doing here).
> 
>>
>> Of course the main issue is going to be getting some proper testing of
>> this (especially with the shrinker added).
> 
> For the shrinker, the best I can propose for now is extending the
> IGT tests I've added. For close-to-real-usecases testing of the shmem ->
> custom transition (this commit), making sure the g610 jobs we have in
> mesa CI still passes is a start. If you have other ideas, I'd be happy
> to give them a try.
> 
>>
>>>
>>> If there's any interest in sharing code (probably with msm, since the
>>> panthor shrinker is going to be losely based on the msm implementation),
>>> we can always change gears and do that once we have everything
>>> working/merged.
>>>
>>> [1]https://patchwork.kernel.org/project/dri-devel/patch/20240105184624.508603-1-dmitry.osipenko@collabora.com/
>>>
>>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
>>> ---
>>>   drivers/gpu/drm/panthor/Kconfig         |   1 -
>>>   drivers/gpu/drm/panthor/panthor_drv.c   |   7 +-
>>>   drivers/gpu/drm/panthor/panthor_fw.c    |  16 +-
>>>   drivers/gpu/drm/panthor/panthor_gem.c   | 696 ++++++++++++++++++++----
>>>   drivers/gpu/drm/panthor/panthor_gem.h   |  62 ++-
>>>   drivers/gpu/drm/panthor/panthor_mmu.c   |  49 +-
>>>   drivers/gpu/drm/panthor/panthor_sched.c |   9 +-
>>>   7 files changed, 666 insertions(+), 174 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig
>>> index 55b40ad07f3b..911e7f4810c3 100644
>>> --- a/drivers/gpu/drm/panthor/Kconfig
>>> +++ b/drivers/gpu/drm/panthor/Kconfig
>>> @@ -8,7 +8,6 @@ config DRM_PANTHOR
>>>   	depends on MMU
>>>   	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>>   	select DRM_EXEC
>>> -	select DRM_GEM_SHMEM_HELPER
>>>   	select DRM_GPUVM
>>>   	select DRM_SCHED
>>>   	select IOMMU_IO_PGTABLE_LPAE
>>> diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
>>> index 52c27a60c84a..90e9abc22d9e 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_drv.c
>>> +++ b/drivers/gpu/drm/panthor/panthor_drv.c
>>> @@ -19,6 +19,7 @@
>>>   #include <drm/drm_debugfs.h>
>>>   #include <drm/drm_drv.h>
>>>   #include <drm/drm_exec.h>
>>> +#include <drm/drm_file.h>
>>>   #include <drm/drm_ioctl.h>
>>>   #include <drm/drm_print.h>
>>>   #include <drm/drm_syncobj.h>
>>> @@ -1457,7 +1458,7 @@ static int panthor_ioctl_bo_query_info(struct drm_device *ddev, void *data,
>>>   	args->create_flags = bo->flags;
>>>   
>>>   	args->extra_flags = 0;
>>> -	if (drm_gem_is_imported(&bo->base.base))
>>> +	if (drm_gem_is_imported(&bo->base))
>>>   		args->extra_flags |= DRM_PANTHOR_BO_IS_IMPORTED;
>>>   
>>>   	drm_gem_object_put(obj);
>>> @@ -1671,8 +1672,7 @@ static const struct drm_driver panthor_drm_driver = {
>>>   	.major = 1,
>>>   	.minor = 7,
>>>   
>>> -	.gem_create_object = panthor_gem_create_object,
>>> -	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
>>> +	.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
>>>   	.gem_prime_import = panthor_gem_prime_import,
>>>   #ifdef CONFIG_DEBUG_FS
>>>   	.debugfs_init = panthor_debugfs_init,
>>> @@ -1822,3 +1822,4 @@ module_exit(panthor_exit);
>>>   MODULE_AUTHOR("Panthor Project Developers");
>>>   MODULE_DESCRIPTION("Panthor DRM Driver");
>>>   MODULE_LICENSE("Dual MIT/GPL");
>>> +MODULE_IMPORT_NS("DMA_BUF");
>>> diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
>>> index a64ec8756bed..f135cf2130b8 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_fw.c
>>> +++ b/drivers/gpu/drm/panthor/panthor_fw.c
>>> @@ -627,7 +627,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
>>>   		u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK;
>>>   		struct panthor_gem_object *bo;
>>>   		u32 vm_map_flags = 0;
>>> -		struct sg_table *sgt;
>>>   		u64 va = hdr.va.start;
>>>   
>>>   		if (!(hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_WR))
>>> @@ -665,11 +664,12 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
>>>   		panthor_fw_init_section_mem(ptdev, section);
>>>   
>>>   		bo = to_panthor_bo(section->mem->obj);
>>> -		sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
>>> -		if (IS_ERR(sgt))
>>> -			return PTR_ERR(sgt);
>>>   
>>> -		dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
>>> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
>>> +		if (drm_WARN_ON_ONCE(&ptdev->base, !bo->dmap.sgt))
>>> +			return -EINVAL;
>>> +
>>> +		dma_sync_sgtable_for_device(ptdev->base.dev, bo->dmap.sgt, DMA_TO_DEVICE);
>>>   	}
>>>   
>>>   	if (hdr.va.start == CSF_MCU_SHARED_REGION_START)
>>> @@ -729,8 +729,10 @@ panthor_reload_fw_sections(struct panthor_device *ptdev, bool full_reload)
>>>   			continue;
>>>   
>>>   		panthor_fw_init_section_mem(ptdev, section);
>>> -		sgt = drm_gem_shmem_get_pages_sgt(&to_panthor_bo(section->mem->obj)->base);
>>> -		if (!drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(sgt)))
>>> +
>>> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
>>> +		sgt = to_panthor_bo(section->mem->obj)->dmap.sgt;
>>> +		if (!drm_WARN_ON_ONCE(&ptdev->base, !sgt))
>>>   			dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
>>>   	}
>>>   }
>>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
>>> index 4b3d82f001d8..0e52c7a07c87 100644
>>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
>>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
>>> @@ -8,9 +8,11 @@
>>>   #include <linux/dma-mapping.h>
>>>   #include <linux/err.h>
>>>   #include <linux/slab.h>
>>> +#include <linux/vmalloc.h>
>>>   
>>>   #include <drm/drm_debugfs.h>
>>>   #include <drm/drm_file.h>
>>> +#include <drm/drm_prime.h>
>>>   #include <drm/drm_print.h>
>>>   #include <drm/panthor_drm.h>
>>>   
>>> @@ -44,7 +46,7 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo)
>>>   
>>>   static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
>>>   {
>>> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
>>> +	struct panthor_device *ptdev = container_of(bo->base.dev,
>>>   						    struct panthor_device, base);
>>>   
>>>   	bo->debugfs.creator.tgid = current->group_leader->pid;
>>> @@ -57,7 +59,7 @@ static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
>>>   
>>>   static void panthor_gem_debugfs_bo_rm(struct panthor_gem_object *bo)
>>>   {
>>> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
>>> +	struct panthor_device *ptdev = container_of(bo->base.dev,
>>>   						    struct panthor_device, base);
>>>   
>>>   	if (list_empty(&bo->debugfs.node))
>>> @@ -80,9 +82,9 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo) {}
>>>   #endif
>>>   
>>>   static bool
>>> -should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
>>> +should_map_wc(struct panthor_gem_object *bo)
>>>   {
>>> -	struct panthor_device *ptdev = container_of(bo->base.base.dev, struct panthor_device, base);
>>> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
>>>   
>>>   	/* We can't do uncached mappings if the device is coherent,
>>>   	 * because the zeroing done by the shmem layer at page allocation
>>> @@ -112,6 +114,208 @@ should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
>>>   	return true;
>>>   }
>>>   
>>> +static void
>>> +panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
>>> +{
>>> +	if (!bo->backing.pages)
>>> +		return;
>>> +
>>> +	drm_gem_put_pages(&bo->base, bo->backing.pages, true, false);
>>> +	bo->backing.pages = NULL;
>>> +}
>>> +
>>> +static int
>>> +panthor_gem_backing_get_pages_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	dma_resv_assert_held(bo->base.resv);
>>> +
>>> +	if (bo->backing.pages)
>>> +		return 0;
>>> +
>>> +	bo->backing.pages = drm_gem_get_pages(&bo->base);
>>> +	if (IS_ERR(bo->backing.pages)) {
>>> +		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
>>> +			    bo->backing.pages);
>>> +		return PTR_ERR(bo->backing.pages);
>>
>> This leaves bo->backing.pages set to the error value, which means a
>> future call to panthor_gem_backing_get_pages_locked() for the same
>> object will return success. Unless there's some 'poisoning' that I
>> haven't spotted this looks like a bug.
> 
> That's a bug, I'll fix it.
> 

I ran into this bug, during the stress testing, which resulted in a crash.


Unable to handle kernel paging request at virtual address fffffffffffffff4
KASAN: maybe wild-memory-access in range 
[0x0003ffffffffffa0-0x0003ffffffffffa7]
Mem abort info:
   ESR = 0x0000000096000006
   EC = 0x25: DABT (current EL), IL = 32 bits
   SET = 0, FnV = 0
   EA = 0, S1PTW = 0
   FSC = 0x06: level 2 translation fault
Data abort info:
   ISV = 0, ISS = 0x00000006, ISS2 = 0x00000000
   CM = 0, WnR = 0, TnD = 0, TagAccess = 0
   GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
swapper pgtable: 4k pages, 48-bit VAs, pgdp=0000000084026000
[fffffffffffffff4] pgd=0000000000000000, p4d=0000000088a6d403, 
pud=0000000088a6e403, pmd=0000000000000000
pc : nonblocking_page_setup+0x90/0x108 [panthor]
lr : nonblocking_page_setup+0x8c/0x108 [panthor]
sp : ffff80008bdd7a80
Call trace:
  nonblocking_page_setup+0x90/0x108 [panthor] (P)
  panthor_gem_fault+0x84/0x218 [panthor]
  __do_fault+0x78/0x3d0
  __handle_mm_fault+0xe20/0x23a0
  handle_mm_fault+0x10c/0x438
  do_page_fault+0x234/0x958
  do_translation_fault+0xa0/0xd8
  do_mem_abort+0x68/0x100
  el0_da+0x54/0x1d8
  el0t_64_sync_handler+0xd0/0xe8
  el0t_64_sync+0x198/0x1a0


First the call to panthor_gem_backing_get_pages_locked(), from 
blocking_page_setup(), failed due to low memory.

panthor_gem_fault() returned VM_FAULT_RETRY as mmap_lock_held was 0.

Then the crash happened inside nonblocking_page_setup() when
panthor_gem_fault() was called for the 2nd time.


Made the following change locally to avoid the issue.

diff --git a/drivers/gpu/drm/panthor/panthor_gem.c 
b/drivers/gpu/drm/panthor/panthor_gem.c
index 6e91c5022d0d..b31a4606a8c6 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.c
+++ b/drivers/gpu/drm/panthor/panthor_gem.c
@@ -234,7 +234,7 @@ panthor_gem_backing_get_pages_locked(struct 
panthor_gem_object *bo)
         if (IS_ERR(bo->backing.pages)) {
                 drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
                             bo->backing.pages);
-               return PTR_ERR(bo->backing.pages);
+               return PTR_ERR(xchg(&bo->backing.pages, NULL));
         }



Best regards
Akash


^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-09 13:08 ` [PATCH v1 9/9] drm/panthor: Add a GEM shrinker Boris Brezillon
  2026-01-14 15:05   ` Steven Price
  2026-01-15 13:56   ` Akash Goel
@ 2026-01-21 11:49   ` Akash Goel
  2026-01-21 14:52     ` Boris Brezillon
  2 siblings, 1 reply; 62+ messages in thread
From: Akash Goel @ 2026-01-21 11:49 UTC (permalink / raw)
  To: Boris Brezillon, Steven Price, Liviu Dudau, Adrián Larumbe
  Cc: dri-devel, David Airlie, Simona Vetter, Rob Clark, Sean Paul,
	Konrad Dybcio, Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

Hi Boris,

On 1/9/26 13:08, Boris Brezillon wrote:
> From: Akash Goel <akash.goel@arm.com>
> 
> This implementation is losely based on the MSM shrinker, and it's
> relying on the drm_gpuvm eviction/validation infrastructure.
> 
> Right now we only support swapout/eviction, but we could add an extra
> flag to specify when buffer content doesn't need to be preserved to
> avoid the swapout/swapin dance.
> 
> Locking is a bit of a nightmare, but using _trylock() all the way in
> the reclaim path seems to make lockdep happy. And yes, we might be
> missing opportunities to reclaim when the system is under heavy GPU
> load/heavy memory pressure/heavy GPU VM activity, but that's better
> than no reclaim at all.
> 
> Signed-off-by: Akash Goel <akash.goel@arm.com>
> Co-developed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>   drivers/gpu/drm/panthor/panthor_device.c |  11 +-
>   drivers/gpu/drm/panthor/panthor_device.h |  73 ++++
>   drivers/gpu/drm/panthor/panthor_gem.c    | 427 ++++++++++++++++++++++-
>   drivers/gpu/drm/panthor/panthor_gem.h    |  67 ++++
>   drivers/gpu/drm/panthor/panthor_mmu.c    | 338 +++++++++++++++++-
>   drivers/gpu/drm/panthor/panthor_mmu.h    |   8 +
>   6 files changed, 901 insertions(+), 23 deletions(-)
> 

> diff --git a/drivers/gpu/drm/panthor/panthor_device.h b/drivers/gpu/drm/panthor/panthor_device.h
> index f35e52b9546a..bc348aa9634e 100644
> --- a/drivers/gpu/drm/panthor/panthor_device.h
> +++ b/drivers/gpu/drm/panthor/panthor_device.h
> @@ -14,6 +14,7 @@
>   #include <linux/spinlock.h>
>   
>   #include <drm/drm_device.h>
> +#include <drm/drm_gem.h>
>   #include <drm/drm_mm.h>
>   #include <drm/gpu_scheduler.h>
>   #include <drm/panthor_drm.h>
> @@ -157,6 +158,78 @@ struct panthor_device {
>   	/** @devfreq: Device frequency scaling management data. */
>   	struct panthor_devfreq *devfreq;
>   
>   
> +static bool is_gpu_mapped(struct panthor_gem_object *bo,
> +			  enum panthor_gem_reclaim_state *state)
> +{
> +	struct drm_gpuvm *vm = NULL;
> +	struct drm_gpuvm_bo *vm_bo;
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +		if (!vm) {
> +			*state = PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> +			vm = vm_bo->vm;
> +		} else if (vm != vm_bo->vm) {
> +			*state = PANTHOR_GEM_GPU_MAPPED_SHARED;
> +			break;
> +		}
> +	}
> +
> +	return !!vm;
> +}
> +
> +static enum panthor_gem_reclaim_state
> +panthor_gem_evaluate_reclaim_state_locked(struct panthor_gem_object *bo)
> +{
> +	enum panthor_gem_reclaim_state gpu_mapped_state;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +	lockdep_assert_held(&bo->base.gpuva.lock);
> +
> +	/* If pages have not been allocated, there's nothing to reclaim. */
> +	if (!bo->backing.pages)
> +		return PANTHOR_GEM_UNRECLAIMABLE;
> +
> +	/* If memory is pinned, we prevent reclaim. */
> +	if (refcount_read(&bo->backing.pin_count))
> +		return PANTHOR_GEM_UNRECLAIMABLE;
> +
> +	if (is_gpu_mapped(bo, &gpu_mapped_state))
> +		return gpu_mapped_state;
> +
> +	if (refcount_read(&bo->cmap.mmap_count) && bo->backing.pages)
> +		return PANTHOR_GEM_MMAPPED;
> +
> +	return PANTHOR_GEM_UNUSED;
> +}
> +
> +void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
> +					     enum panthor_gem_reclaim_state *old_statep)
> +{
> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> +	enum panthor_gem_reclaim_state old_state = bo->reclaim_state;
> +	enum panthor_gem_reclaim_state new_state;
> +	bool was_gpu_mapped, is_gpu_mapped;
> +
> +	if (old_statep)
> +		*old_statep = old_state;
> +
> +	new_state = panthor_gem_evaluate_reclaim_state_locked(bo);
> +	if (new_state == old_state)
> +		return;
> +
> +	was_gpu_mapped = old_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> +			 old_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> +	is_gpu_mapped = new_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> +			new_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> +
> +	if (is_gpu_mapped && !was_gpu_mapped)
> +		ptdev->reclaim.gpu_mapped_count += bo->base.size >> PAGE_SHIFT;
> +	else if (!is_gpu_mapped && was_gpu_mapped)
> +		ptdev->reclaim.gpu_mapped_count -= bo->base.size >> PAGE_SHIFT;
> +
> +	switch (new_state) {
> +	case PANTHOR_GEM_UNUSED:
> +		drm_gem_lru_move_tail(&ptdev->reclaim.unused, &bo->base);
> +		break;
> +	case PANTHOR_GEM_MMAPPED:
> +		drm_gem_lru_move_tail(&ptdev->reclaim.mmapped, &bo->base);
> +		break;
> +	case PANTHOR_GEM_GPU_MAPPED_PRIVATE:
> +		panthor_vm_update_bo_reclaim_lru_locked(bo);
> +		break;
> +	case PANTHOR_GEM_GPU_MAPPED_SHARED:
> +		drm_gem_lru_move_tail(&ptdev->reclaim.gpu_mapped_shared, &bo->base);
> +		break;
> +	case PANTHOR_GEM_UNRECLAIMABLE:
> +		drm_gem_lru_remove(&bo->base);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	bo->reclaim_state = new_state;
> +}
> +
>   static void
>   panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
>   {
> @@ -153,8 +249,12 @@ static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
>   		return 0;
>   
>   	ret = panthor_gem_backing_get_pages_locked(bo);
> -	if (!ret)
> +	if (!ret) {
>   		refcount_set(&bo->backing.pin_count, 1);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +	}
>   
>   	return ret;
>   }
> @@ -167,8 +267,12 @@ static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
>   	/* We don't release anything when pin_count drops to zero.
>   	 * Pages stay there until an explicit cleanup is requested.
>   	 */
> -	if (!refcount_dec_not_one(&bo->backing.pin_count))
> +	if (!refcount_dec_not_one(&bo->backing.pin_count)) {
>   		refcount_set(&bo->backing.pin_count, 0);
> +		mutex_lock(&bo->base.gpuva.lock);
> +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> +		mutex_unlock(&bo->base.gpuva.lock);
> +	}
>   }
>   
>   static void
> @@ -531,6 +635,49 @@ void panthor_gem_unpin(struct panthor_gem_object *bo)
>   	dma_resv_unlock(bo->base.resv);
>   }
>   
> +
> +static void panthor_gem_evict_locked(struct panthor_gem_object *bo)
> +{
> +	dma_resv_assert_held(bo->base.resv);
> +	lockdep_assert_held(&bo->base.gpuva.lock);
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> +		return;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
> +		return;
> +
> +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> +		return;
> +
> +	panthor_gem_dev_map_cleanup(bo);
> +	panthor_gem_backing_cleanup(bo);
> +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> +}
> +

> +
> +static bool should_wait(enum panthor_gem_reclaim_state reclaim_state)
> +{
> +	return reclaim_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE ||
> +	       reclaim_state == PANTHOR_GEM_GPU_MAPPED_SHARED;
> +}
> +
> +bool panthor_gem_try_evict(struct drm_gem_object *obj,
> +			   struct ww_acquire_ctx *ticket)
> +{
> +	/*
> +	 * Track last locked entry for unwinding locks in error and
> +	 * success paths
> +	 */
> +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> +	struct drm_gpuvm_bo *vm_bo, *last_locked = NULL;
> +	enum panthor_gem_reclaim_state old_state;
> +	int ret = 0;
> +
> +	/* To avoid potential lock ordering issue between bo_gpuva and
> +	 * mapping->i_mmap_rwsem, unmap the pages from CPU side before
> +	 * acquring the bo_gpuva lock. As the bo_resv lock is held, CPU
> +	 * page fault handler won't be able to map in the pages whilst
> +	 * eviction is in progress.
> +	 */
> +	drm_vma_node_unmap(&bo->base.vma_node, bo->base.dev->anon_inode->i_mapping);
> +
> +	/* We take this lock when walking the list to prevent
> +	 * insertion/deletion.
> +	 */
> +	/* We can only trylock in that path, because
> +	 * - allocation might happen while some of these locks are held
> +	 * - lock ordering is different in other paths
> +	 *     vm_resv -> bo_resv -> bo_gpuva
> +	 *     vs
> +	 *     bo_resv -> bo_gpuva -> vm_resv
> +	 *
> +	 * If we fail to lock that's fine, we back off and will get
> +	 * back to it later.
> +	 */
> +	if (!mutex_trylock(&bo->base.gpuva.lock))
> +		return false;
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +		struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> +
> +		if (resv == obj->resv)
> +			continue;
> +
> +		if (!dma_resv_trylock(resv)) {
> +			ret = -EDEADLK;
> +			goto out_unlock;
> +		}
> +
> +		last_locked = vm_bo;
> +	}
> +
> +	/* Update the state before trying to evict the buffer, if the state was
> +	 * updated to something that's harder to reclaim (higher value in the
> +	 * enum), skip it (will be processed when the relevant LRU is).
> +	 */
> +	panthor_gem_update_reclaim_state_locked(bo, &old_state);
> +	if (old_state < bo->reclaim_state) {
> +		ret = -EAGAIN;
> +		goto out_unlock;
> +	}
> +
> +	/* Wait was too long, skip. */
> +	if (should_wait(bo->reclaim_state) &&
> +	    dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP, false, 10) <= 0) {
> +		ret = -ETIMEDOUT;
> +		goto out_unlock;
> +	}
> +
> +	/* Couldn't teardown the GPU mappings? Skip. */
> +	ret = panthor_vm_evict_bo_mappings_locked(bo);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* If everything went fine, evict the object. */
> +	panthor_gem_evict_locked(bo);
> +
> +out_unlock:
> +	if (last_locked) {
> +		drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +			struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> +
> +			if (resv == obj->resv)
> +				continue;
> +
> +			dma_resv_unlock(resv);
> +
> +			if (last_locked == vm_bo)
> +				break;
> +		}
> +	}
> +	mutex_unlock(&bo->base.gpuva.lock);
> +
> +	return ret == 0;
> +}


> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index 3290e0b5facb..ffd821b3be46 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -1,6 +1,7 @@
>   // SPDX-License-Identifier: GPL-2.0 or MIT
>   /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>   /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>   
>   #include <drm/drm_debugfs.h>
>   #include <drm/drm_drv.h>
> @@ -131,6 +132,9 @@ struct panthor_vma {
>   	 * Only map related flags are accepted.
>   	 */
>   	u32 flags;
> +
> +	/** @evicted: True if the VMA has been evicted. */
> +	bool evicted;
>   };
>   
>   /**
>   
> +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
> +{
> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> +	struct panthor_vm *vm = NULL;
> +	struct drm_gpuvm_bo *vm_bo;
> +
> +	dma_resv_assert_held(bo->base.resv);
> +	lockdep_assert_held(&bo->base.gpuva.lock);
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +		/* We're only supposed to have one vm_bo in the list if we get there. */
> +		drm_WARN_ON(&ptdev->base, vm);
> +		vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +
> +		mutex_lock(&ptdev->reclaim.lock);
> +		drm_gem_lru_move_tail_locked(&vm->reclaim.lru, &bo->base);
> +		if (list_empty(&vm->reclaim.lru_node))
> +			list_move(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> +		mutex_unlock(&ptdev->reclaim.lock);
> +	}
> +}
> +
> +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
> +{
> +	struct drm_gpuvm_bo *vm_bo;
> +
> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +		struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +		struct drm_gpuva *va;
> +
> +		if (!mutex_trylock(&vm->op_lock))
> +			return -EDEADLK;
> +
> +		drm_gpuvm_bo_evict(vm_bo, true);
> +		drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +			struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> +
> +			panthor_vm_lock_region(vm, va->va.addr, va->va.range);
> +			panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);

On further testing, I ran into a kernel warning issue.

https://elixir.bootlin.com/linux/v6.18-rc5/source/drivers/iommu/io-pgtable-arm.c#L641

https://elixir.bootlin.com/linux/v6.18-rc5/source/drivers/gpu/drm/panthor/panthor_mmu.c#L930

Call trace:
  __arm_lpae_unmap+0x570/0x5c8 (P)
  __arm_lpae_unmap+0x144/0x5c8
  __arm_lpae_unmap+0x144/0x5c8
  arm_lpae_unmap_pages+0xe8/0x120
  panthor_vm_unmap_pages+0x1f0/0x3f8 [panthor]
  panthor_vm_evict_bo_mappings_locked+0xf4/0x1d8 [panthor]
  panthor_gem_try_evict+0x190/0x7c8 [panthor]
  drm_gem_lru_scan+0x388/0x698

Following sequence leads to the kernel warnings.

- BO is mapped to GPU and is in GPU_MAPPED_PRIVATE state.

- Shrinker callback gets invoked and that BO is evicted. As a result BO 
is moved to UNRECLAIMABLE state.

- Userspace accesses the evicted BO and panthor_gem_fault() gets the 
backing pages again. BO is moved back to GPU_MAPPED_PRIVATE state (even 
though technically the BO is still in evicted state, both vm_bo->evicted 
and vma->evicted are TRUE.

- Shrinker callback is invoked again and tries to evict the same BO.

- panthor_vm_evict_bo_mappings_locked() calls panthor_vm_unmap_pages() 
and the kernel warnings are emiited as PTEs are already invalid.


Made the following change locally to avoid the warning.

diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c 
b/drivers/gpu/drm/panthor/panthor_mmu.c
index ffd821b3be46..e0a1dda1675f 100644
--- a/drivers/gpu/drm/panthor/panthor_mmu.c
+++ b/drivers/gpu/drm/panthor/panthor_mmu.c
@@ -2344,6 +2344,8 @@ int panthor_vm_evict_bo_mappings_locked(struct 
panthor_gem_object *bo)
                 drm_gpuvm_bo_for_each_va(va, vm_bo) {
                         struct panthor_vma *vma = container_of(va, 
struct panthor_vma, base);

+                       if (vma->evicted)
+                               continue;
                         panthor_vm_lock_region(vm, va->va.addr, 
va->va.range);
                         panthor_vm_unmap_pages(vm, va->va.addr, 
va->va.range);
                         panthor_vm_unlock_region(vm);



Do you think we can also update is_gpu_mapped() so that an evicted BO 
moves to MMAPPED state, instead of GPU_MAPPED_PRIVATE state, on CPU 
access ?.

Not sure if the following change makes sense.

diff --git a/drivers/gpu/drm/panthor/panthor_gem.c 
b/drivers/gpu/drm/panthor/panthor_gem.c
index 6e91c5022d0d..8a8411fed195 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.c
+++ b/drivers/gpu/drm/panthor/panthor_gem.c
@@ -125,6 +125,8 @@ static bool is_gpu_mapped(struct panthor_gem_object *bo,
         struct drm_gpuvm_bo *vm_bo;

         drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
+               if (vm_bo->evicted)
+                       continue;
                 if (!vm) {
                         *state = PANTHOR_GEM_GPU_MAPPED_PRIVATE;
                         vm = vm_bo->vm;


Please let me know.


Best regards
Akash




> +			panthor_vm_unlock_region(vm);
> +			vma->evicted = true;
> +		}
> +
> +		mutex_unlock(&vm->op_lock);
> +	}
> +
> +	return 0;
> +}
> +

>   static const struct drm_gpuvm_ops panthor_gpuvm_ops = {
>   	.vm_free = panthor_vm_free,
>   	.vm_bo_free = panthor_vm_bo_free,
>   	.sm_step_map = panthor_gpuva_sm_step_map,
>   	.sm_step_remap = panthor_gpuva_sm_step_remap,
>   	.sm_step_unmap = panthor_gpuva_sm_step_unmap,
> +	.vm_bo_validate = panthor_vm_bo_validate,
>   };
>   
>   /**
> @@ -2473,6 +2702,8 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>   	vm->kernel_auto_va.start = auto_kernel_va_start;
>   	vm->kernel_auto_va.end = vm->kernel_auto_va.start + auto_kernel_va_size - 1;
>   
> +	drm_gem_lru_init(&vm->reclaim.lru, &ptdev->reclaim.lock);
> +	INIT_LIST_HEAD(&vm->reclaim.lru_node);
>   	INIT_LIST_HEAD(&vm->node);
>   	INIT_LIST_HEAD(&vm->as.lru_node);
>   	vm->as.id = -1;
> @@ -2820,7 +3051,78 @@ int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec, struct panthor_vm
>   	if (ret)
>   		return ret;
>   
> -	return drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
> +	ret = drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
> +	if (ret)
> +		return ret;
> +
> +	return drm_gpuvm_validate(&vm->base, exec);
> +}
> +
> +unsigned long
> +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
> +			     unsigned int nr_to_scan, unsigned long *remaining,
> +			     bool (*shrink)(struct drm_gem_object *,
> +					    struct ww_acquire_ctx *))
> +{
> +	unsigned long freed = 0;
> +	LIST_HEAD(remaining_vms);
> +	LIST_HEAD(vms);
> +
> +	mutex_lock(&ptdev->reclaim.lock);
> +	list_splice_init(&ptdev->reclaim.vms, &vms);
> +
> +	while (freed < nr_to_scan) {
> +		struct panthor_vm *vm;
> +
> +		vm = list_first_entry_or_null(&vms, typeof(*vm),
> +					      reclaim.lru_node);
> +		if (!vm)
> +			break;
> +
> +		if (!kref_get_unless_zero(&vm->base.kref)) {
> +			list_del_init(&vm->reclaim.lru_node);
> +			continue;
> +		}
> +
> +		mutex_unlock(&ptdev->reclaim.lock);
> +
> +		freed += drm_gem_lru_scan(&vm->reclaim.lru, nr_to_scan - freed,
> +					  remaining, shrink, NULL);
> +
> +		mutex_lock(&ptdev->reclaim.lock);
> +
> +		/* If the VM is still in the temporary list, remove it so we
> +		 * can proceed with the next VM.
> +		 */
> +		if (vm->reclaim.lru_node.prev == &vms) {
> +			list_del_init(&vm->reclaim.lru_node);
> +
> +			/* Keep the VM around if there are still things to
> +			 * reclaim, so we can preserve the LRU order when
> +			 * re-inserting in ptdev->reclaim.vms at the end.
> +			 */
> +			if (vm->reclaim.lru.count > 0)
> +				list_add_tail(&vm->reclaim.lru_node, &remaining_vms);
> +		}
> +
> +		mutex_unlock(&ptdev->reclaim.lock);
> +
> +		panthor_vm_put(vm);
> +
> +		mutex_lock(&ptdev->reclaim.lock);
> +	}
> +
> +	/* Re-insert VMs with remaining data to reclaim at the beginning of
> +	 * the LRU. Note that any activeness change on the VM that happened
> +	 * while we were reclaiming would have moved the VM out of our
> +	 * temporary [remaining_]vms list, meaning anything we re-insert here
> +	 * preserves the LRU order.
> +	 */
> +	list_splice_tail(&vms, &remaining_vms);
> +	list_splice(&remaining_vms, &ptdev->reclaim.vms);
> +	mutex_unlock(&ptdev->reclaim.lock);
> +
> +	return freed;
>   }
>   
>   /**
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.h b/drivers/gpu/drm/panthor/panthor_mmu.h
> index 0e268fdfdb2f..3522fbbce369 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.h
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.h
> @@ -1,6 +1,7 @@
>   /* SPDX-License-Identifier: GPL-2.0 or MIT */
>   /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
>   /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>   
>   #ifndef __PANTHOR_MMU_H__
>   #define __PANTHOR_MMU_H__
> @@ -46,6 +47,13 @@ struct panthor_vm *panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>   				     u64 kernel_auto_va_start,
>   				     u64 kernel_auto_va_size);
>   
> +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo);
> +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo);
> +unsigned long
> +panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
> +			     unsigned int nr_to_scan, unsigned long *remaining,
> +			     bool (*shrink)(struct drm_gem_object *,
> +					    struct ww_acquire_ctx *));
>   int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec,
>   					struct panthor_vm *vm,
>   					u32 slot_count);

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-21 11:49   ` Akash Goel
@ 2026-01-21 14:52     ` Boris Brezillon
  2026-01-28 11:21       ` Akash Goel
  0 siblings, 1 reply; 62+ messages in thread
From: Boris Brezillon @ 2026-01-21 14:52 UTC (permalink / raw)
  To: Akash Goel
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

On Wed, 21 Jan 2026 11:49:34 +0000
Akash Goel <akash.goel@arm.com> wrote:

> Hi Boris,
> 
> On 1/9/26 13:08, Boris Brezillon wrote:
> > From: Akash Goel <akash.goel@arm.com>
> > 
> > This implementation is losely based on the MSM shrinker, and it's
> > relying on the drm_gpuvm eviction/validation infrastructure.
> > 
> > Right now we only support swapout/eviction, but we could add an extra
> > flag to specify when buffer content doesn't need to be preserved to
> > avoid the swapout/swapin dance.
> > 
> > Locking is a bit of a nightmare, but using _trylock() all the way in
> > the reclaim path seems to make lockdep happy. And yes, we might be
> > missing opportunities to reclaim when the system is under heavy GPU
> > load/heavy memory pressure/heavy GPU VM activity, but that's better
> > than no reclaim at all.
> > 
> > Signed-off-by: Akash Goel <akash.goel@arm.com>
> > Co-developed-by: Boris Brezillon <boris.brezillon@collabora.com>
> > Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> > ---
> >   drivers/gpu/drm/panthor/panthor_device.c |  11 +-
> >   drivers/gpu/drm/panthor/panthor_device.h |  73 ++++
> >   drivers/gpu/drm/panthor/panthor_gem.c    | 427 ++++++++++++++++++++++-
> >   drivers/gpu/drm/panthor/panthor_gem.h    |  67 ++++
> >   drivers/gpu/drm/panthor/panthor_mmu.c    | 338 +++++++++++++++++-
> >   drivers/gpu/drm/panthor/panthor_mmu.h    |   8 +
> >   6 files changed, 901 insertions(+), 23 deletions(-)
> >   
> 
> > diff --git a/drivers/gpu/drm/panthor/panthor_device.h b/drivers/gpu/drm/panthor/panthor_device.h
> > index f35e52b9546a..bc348aa9634e 100644
> > --- a/drivers/gpu/drm/panthor/panthor_device.h
> > +++ b/drivers/gpu/drm/panthor/panthor_device.h
> > @@ -14,6 +14,7 @@
> >   #include <linux/spinlock.h>
> >   
> >   #include <drm/drm_device.h>
> > +#include <drm/drm_gem.h>
> >   #include <drm/drm_mm.h>
> >   #include <drm/gpu_scheduler.h>
> >   #include <drm/panthor_drm.h>
> > @@ -157,6 +158,78 @@ struct panthor_device {
> >   	/** @devfreq: Device frequency scaling management data. */
> >   	struct panthor_devfreq *devfreq;
> >   
> >   
> > +static bool is_gpu_mapped(struct panthor_gem_object *bo,
> > +			  enum panthor_gem_reclaim_state *state)
> > +{
> > +	struct drm_gpuvm *vm = NULL;
> > +	struct drm_gpuvm_bo *vm_bo;
> > +
> > +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> > +		if (!vm) {
> > +			*state = PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> > +			vm = vm_bo->vm;
> > +		} else if (vm != vm_bo->vm) {
> > +			*state = PANTHOR_GEM_GPU_MAPPED_SHARED;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return !!vm;
> > +}
> > +
> > +static enum panthor_gem_reclaim_state
> > +panthor_gem_evaluate_reclaim_state_locked(struct panthor_gem_object *bo)
> > +{
> > +	enum panthor_gem_reclaim_state gpu_mapped_state;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +	lockdep_assert_held(&bo->base.gpuva.lock);
> > +
> > +	/* If pages have not been allocated, there's nothing to reclaim. */
> > +	if (!bo->backing.pages)
> > +		return PANTHOR_GEM_UNRECLAIMABLE;
> > +
> > +	/* If memory is pinned, we prevent reclaim. */
> > +	if (refcount_read(&bo->backing.pin_count))
> > +		return PANTHOR_GEM_UNRECLAIMABLE;
> > +
> > +	if (is_gpu_mapped(bo, &gpu_mapped_state))
> > +		return gpu_mapped_state;
> > +
> > +	if (refcount_read(&bo->cmap.mmap_count) && bo->backing.pages)
> > +		return PANTHOR_GEM_MMAPPED;
> > +
> > +	return PANTHOR_GEM_UNUSED;
> > +}
> > +
> > +void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
> > +					     enum panthor_gem_reclaim_state *old_statep)
> > +{
> > +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> > +	enum panthor_gem_reclaim_state old_state = bo->reclaim_state;
> > +	enum panthor_gem_reclaim_state new_state;
> > +	bool was_gpu_mapped, is_gpu_mapped;
> > +
> > +	if (old_statep)
> > +		*old_statep = old_state;
> > +
> > +	new_state = panthor_gem_evaluate_reclaim_state_locked(bo);
> > +	if (new_state == old_state)
> > +		return;
> > +
> > +	was_gpu_mapped = old_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> > +			 old_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> > +	is_gpu_mapped = new_state == PANTHOR_GEM_GPU_MAPPED_SHARED ||
> > +			new_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE;
> > +
> > +	if (is_gpu_mapped && !was_gpu_mapped)
> > +		ptdev->reclaim.gpu_mapped_count += bo->base.size >> PAGE_SHIFT;
> > +	else if (!is_gpu_mapped && was_gpu_mapped)
> > +		ptdev->reclaim.gpu_mapped_count -= bo->base.size >> PAGE_SHIFT;
> > +
> > +	switch (new_state) {
> > +	case PANTHOR_GEM_UNUSED:
> > +		drm_gem_lru_move_tail(&ptdev->reclaim.unused, &bo->base);
> > +		break;
> > +	case PANTHOR_GEM_MMAPPED:
> > +		drm_gem_lru_move_tail(&ptdev->reclaim.mmapped, &bo->base);
> > +		break;
> > +	case PANTHOR_GEM_GPU_MAPPED_PRIVATE:
> > +		panthor_vm_update_bo_reclaim_lru_locked(bo);
> > +		break;
> > +	case PANTHOR_GEM_GPU_MAPPED_SHARED:
> > +		drm_gem_lru_move_tail(&ptdev->reclaim.gpu_mapped_shared, &bo->base);
> > +		break;
> > +	case PANTHOR_GEM_UNRECLAIMABLE:
> > +		drm_gem_lru_remove(&bo->base);
> > +		break;
> > +	default:
> > +		break;
> > +	}
> > +
> > +	bo->reclaim_state = new_state;
> > +}
> > +
> >   static void
> >   panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
> >   {
> > @@ -153,8 +249,12 @@ static int panthor_gem_backing_pin_locked(struct panthor_gem_object *bo)
> >   		return 0;
> >   
> >   	ret = panthor_gem_backing_get_pages_locked(bo);
> > -	if (!ret)
> > +	if (!ret) {
> >   		refcount_set(&bo->backing.pin_count, 1);
> > +		mutex_lock(&bo->base.gpuva.lock);
> > +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +		mutex_unlock(&bo->base.gpuva.lock);
> > +	}
> >   
> >   	return ret;
> >   }
> > @@ -167,8 +267,12 @@ static void panthor_gem_backing_unpin_locked(struct panthor_gem_object *bo)
> >   	/* We don't release anything when pin_count drops to zero.
> >   	 * Pages stay there until an explicit cleanup is requested.
> >   	 */
> > -	if (!refcount_dec_not_one(&bo->backing.pin_count))
> > +	if (!refcount_dec_not_one(&bo->backing.pin_count)) {
> >   		refcount_set(&bo->backing.pin_count, 0);
> > +		mutex_lock(&bo->base.gpuva.lock);
> > +		panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +		mutex_unlock(&bo->base.gpuva.lock);
> > +	}
> >   }
> >   
> >   static void
> > @@ -531,6 +635,49 @@ void panthor_gem_unpin(struct panthor_gem_object *bo)
> >   	dma_resv_unlock(bo->base.resv);
> >   }
> >   
> > +
> > +static void panthor_gem_evict_locked(struct panthor_gem_object *bo)
> > +{
> > +	dma_resv_assert_held(bo->base.resv);
> > +	lockdep_assert_held(&bo->base.gpuva.lock);
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, drm_gem_is_imported(&bo->base)))
> > +		return;
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, refcount_read(&bo->backing.pin_count)))
> > +		return;
> > +
> > +	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
> > +		return;
> > +
> > +	panthor_gem_dev_map_cleanup(bo);
> > +	panthor_gem_backing_cleanup(bo);
> > +	panthor_gem_update_reclaim_state_locked(bo, NULL);
> > +}
> > +  
> 
> > +
> > +static bool should_wait(enum panthor_gem_reclaim_state reclaim_state)
> > +{
> > +	return reclaim_state == PANTHOR_GEM_GPU_MAPPED_PRIVATE ||
> > +	       reclaim_state == PANTHOR_GEM_GPU_MAPPED_SHARED;
> > +}
> > +
> > +bool panthor_gem_try_evict(struct drm_gem_object *obj,
> > +			   struct ww_acquire_ctx *ticket)
> > +{
> > +	/*
> > +	 * Track last locked entry for unwinding locks in error and
> > +	 * success paths
> > +	 */
> > +	struct panthor_gem_object *bo = to_panthor_bo(obj);
> > +	struct drm_gpuvm_bo *vm_bo, *last_locked = NULL;
> > +	enum panthor_gem_reclaim_state old_state;
> > +	int ret = 0;
> > +
> > +	/* To avoid potential lock ordering issue between bo_gpuva and
> > +	 * mapping->i_mmap_rwsem, unmap the pages from CPU side before
> > +	 * acquring the bo_gpuva lock. As the bo_resv lock is held, CPU
> > +	 * page fault handler won't be able to map in the pages whilst
> > +	 * eviction is in progress.
> > +	 */
> > +	drm_vma_node_unmap(&bo->base.vma_node, bo->base.dev->anon_inode->i_mapping);
> > +
> > +	/* We take this lock when walking the list to prevent
> > +	 * insertion/deletion.
> > +	 */
> > +	/* We can only trylock in that path, because
> > +	 * - allocation might happen while some of these locks are held
> > +	 * - lock ordering is different in other paths
> > +	 *     vm_resv -> bo_resv -> bo_gpuva
> > +	 *     vs
> > +	 *     bo_resv -> bo_gpuva -> vm_resv
> > +	 *
> > +	 * If we fail to lock that's fine, we back off and will get
> > +	 * back to it later.
> > +	 */
> > +	if (!mutex_trylock(&bo->base.gpuva.lock))
> > +		return false;
> > +
> > +	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> > +		struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> > +
> > +		if (resv == obj->resv)
> > +			continue;
> > +
> > +		if (!dma_resv_trylock(resv)) {
> > +			ret = -EDEADLK;
> > +			goto out_unlock;
> > +		}
> > +
> > +		last_locked = vm_bo;
> > +	}
> > +
> > +	/* Update the state before trying to evict the buffer, if the state was
> > +	 * updated to something that's harder to reclaim (higher value in the
> > +	 * enum), skip it (will be processed when the relevant LRU is).
> > +	 */
> > +	panthor_gem_update_reclaim_state_locked(bo, &old_state);
> > +	if (old_state < bo->reclaim_state) {
> > +		ret = -EAGAIN;
> > +		goto out_unlock;
> > +	}
> > +
> > +	/* Wait was too long, skip. */
> > +	if (should_wait(bo->reclaim_state) &&
> > +	    dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP, false, 10) <= 0) {
> > +		ret = -ETIMEDOUT;
> > +		goto out_unlock;
> > +	}
> > +
> > +	/* Couldn't teardown the GPU mappings? Skip. */
> > +	ret = panthor_vm_evict_bo_mappings_locked(bo);
> > +	if (ret)
> > +		goto out_unlock;
> > +
> > +	/* If everything went fine, evict the object. */
> > +	panthor_gem_evict_locked(bo);
> > +
> > +out_unlock:
> > +	if (last_locked) {
> > +		drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> > +			struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
> > +
> > +			if (resv == obj->resv)
> > +				continue;
> > +
> > +			dma_resv_unlock(resv);
> > +
> > +			if (last_locked == vm_bo)
> > +				break;
> > +		}
> > +	}
> > +	mutex_unlock(&bo->base.gpuva.lock);
> > +
> > +	return ret == 0;
> > +}  
> 
> 
> > diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> > index 3290e0b5facb..ffd821b3be46 100644
> > --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> > +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> > @@ -1,6 +1,7 @@
> >   // SPDX-License-Identifier: GPL-2.0 or MIT
> >   /* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
> >   /* Copyright 2023 Collabora ltd. */
> > +/* Copyright 2025 ARM Limited. All rights reserved. */
> >   
> >   #include <drm/drm_debugfs.h>
> >   #include <drm/drm_drv.h>
> > @@ -131,6 +132,9 @@ struct panthor_vma {
> >   	 * Only map related flags are accepted.
> >   	 */
> >   	u32 flags;
> > +
> > +	/** @evicted: True if the VMA has been evicted. */
> > +	bool evicted;
> >   };
> >   
> >   /**
> >   
> > +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
> > +{
> > +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> > +	struct panthor_vm *vm = NULL;
> > +	struct drm_gpuvm_bo *vm_bo;
> > +
> > +	dma_resv_assert_held(bo->base.resv);
> > +	lockdep_assert_held(&bo->base.gpuva.lock);
> > +
> > +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> > +		/* We're only supposed to have one vm_bo in the list if we get there. */
> > +		drm_WARN_ON(&ptdev->base, vm);
> > +		vm = container_of(vm_bo->vm, struct panthor_vm, base);
> > +
> > +		mutex_lock(&ptdev->reclaim.lock);
> > +		drm_gem_lru_move_tail_locked(&vm->reclaim.lru, &bo->base);
> > +		if (list_empty(&vm->reclaim.lru_node))
> > +			list_move(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> > +		mutex_unlock(&ptdev->reclaim.lock);
> > +	}
> > +}
> > +
> > +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
> > +{
> > +	struct drm_gpuvm_bo *vm_bo;
> > +
> > +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> > +		struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> > +		struct drm_gpuva *va;
> > +
> > +		if (!mutex_trylock(&vm->op_lock))
> > +			return -EDEADLK;
> > +
> > +		drm_gpuvm_bo_evict(vm_bo, true);
> > +		drm_gpuvm_bo_for_each_va(va, vm_bo) {
> > +			struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
> > +
> > +			panthor_vm_lock_region(vm, va->va.addr, va->va.range);
> > +			panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);  
> 
> On further testing, I ran into a kernel warning issue.
> 
> https://elixir.bootlin.com/linux/v6.18-rc5/source/drivers/iommu/io-pgtable-arm.c#L641
> 
> https://elixir.bootlin.com/linux/v6.18-rc5/source/drivers/gpu/drm/panthor/panthor_mmu.c#L930
> 
> Call trace:
>   __arm_lpae_unmap+0x570/0x5c8 (P)
>   __arm_lpae_unmap+0x144/0x5c8
>   __arm_lpae_unmap+0x144/0x5c8
>   arm_lpae_unmap_pages+0xe8/0x120
>   panthor_vm_unmap_pages+0x1f0/0x3f8 [panthor]
>   panthor_vm_evict_bo_mappings_locked+0xf4/0x1d8 [panthor]
>   panthor_gem_try_evict+0x190/0x7c8 [panthor]
>   drm_gem_lru_scan+0x388/0x698
> 
> Following sequence leads to the kernel warnings.
> 
> - BO is mapped to GPU and is in GPU_MAPPED_PRIVATE state.
> 
> - Shrinker callback gets invoked and that BO is evicted. As a result BO 
> is moved to UNRECLAIMABLE state.
> 
> - Userspace accesses the evicted BO and panthor_gem_fault() gets the 
> backing pages again. BO is moved back to GPU_MAPPED_PRIVATE state (even 
> though technically the BO is still in evicted state, both vm_bo->evicted 
> and vma->evicted are TRUE.
> 
> - Shrinker callback is invoked again and tries to evict the same BO.
> 
> - panthor_vm_evict_bo_mappings_locked() calls panthor_vm_unmap_pages() 
> and the kernel warnings are emiited as PTEs are already invalid.

Yep, it looks like the other side of the problem pointed out by Steve:
CPU mappings can make the buffer reclaimable again, but those are still
evicted from the VM PoV.

> 
> 
> Made the following change locally to avoid the warning.
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c 
> b/drivers/gpu/drm/panthor/panthor_mmu.c
> index ffd821b3be46..e0a1dda1675f 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -2344,6 +2344,8 @@ int panthor_vm_evict_bo_mappings_locked(struct 
> panthor_gem_object *bo)
>                  drm_gpuvm_bo_for_each_va(va, vm_bo) {
>                          struct panthor_vma *vma = container_of(va, 
> struct panthor_vma, base);
> 
> +                       if (vma->evicted)
> +                               continue;
>                          panthor_vm_lock_region(vm, va->va.addr, 
> va->va.range);
>                          panthor_vm_unmap_pages(vm, va->va.addr, 
> va->va.range);
>                          panthor_vm_unlock_region(vm);
> 
> 
> 
> Do you think we can also update is_gpu_mapped() so that an evicted BO 
> moves to MMAPPED state, instead of GPU_MAPPED_PRIVATE state, on CPU 
> access ?.
> 
> Not sure if the following change makes sense.
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c 
> b/drivers/gpu/drm/panthor/panthor_gem.c
> index 6e91c5022d0d..8a8411fed195 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -125,6 +125,8 @@ static bool is_gpu_mapped(struct panthor_gem_object *bo,
>          struct drm_gpuvm_bo *vm_bo;
> 
>          drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> +               if (vm_bo->evicted)
> +                       continue;
>                  if (!vm) {
>                          *state = PANTHOR_GEM_GPU_MAPPED_PRIVATE;
>                          vm = vm_bo->vm;
> 
> 
> Please let me know.

Yep, this looks correct. I'll add that to my list of fixups.

Thanks,

Boris

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object
  2026-01-21 11:11       ` Akash Goel
@ 2026-01-21 15:17         ` Boris Brezillon
  0 siblings, 0 replies; 62+ messages in thread
From: Boris Brezillon @ 2026-01-21 15:17 UTC (permalink / raw)
  To: Akash Goel
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

On Wed, 21 Jan 2026 11:11:01 +0000
Akash Goel <akash.goel@arm.com> wrote:

> Hi Boris, Steve
> 
> 
> On 1/12/26 14:17, Boris Brezillon wrote:
> > On Mon, 12 Jan 2026 12:06:17 +0000
> > Steven Price <steven.price@arm.com> wrote:
> >   
> >> On 09/01/2026 13:07, Boris Brezillon wrote:  
> >>> While drm_gem_shmem_object does most of the job we need it to do, the
> >>> way sub-resources (pages, sgt, vmap) are handled and their lifetimes
> >>> gets in the way of BO reclaim. There has been attempts to address
> >>> that [1], but in the meantime, new gem_shmem users were introduced
> >>> (accel drivers), and some of them manually free some of these resources.
> >>> This makes things harder to control/sanitize/validate.
> >>>
> >>> Thomas Zimmerman is not a huge fan of enforcing lifetimes of sub-resources
> >>> and forcing gem_shmem users to go through new gem_shmem helpers when they
> >>> need manual control of some sort, and I believe this is a dead end if
> >>> we don't force users to follow some stricter rules through carefully
> >>> designed helpers, because there will always be one user doing crazy things
> >>> with gem_shmem_object internals, which ends up tripping out the common
> >>> helpers when they are called.
> >>>
> >>> The consensus we reached was that we would be better off forking
> >>> gem_shmem in panthor. So here we are, parting ways with gem_shmem. The
> >>> current transition tries to minimize the changes, but there are still
> >>> some aspects that are different, the main one being that we no longer
> >>> have a pages_use_count, and pages stays around until the GEM object is
> >>> destroyed (or when evicted once we've added a shrinker). The sgt also
> >>> no longer retains pages. This is losely based on how msm does things by
> >>> the way.  
> >>
> >>  From a reviewing perspective it's a little tricky trying to match up the
> >> implementation to shmem because of these changes. I don't know how
> >> difficult it would be to split the changes to a patch which literally
> >> copies (with renames) from shmem, followed by simplifying out the parts
> >> we don't want.  
> > 
> > It's a bit annoying as the new implementation is not based on shmem at
> > all, but if you think it helps the review, I can try what you're
> > suggesting. I mean, I'm not convinced it will be significantly easier
> > to review with this extra step, since the new logic is different enough
> > (especially when it comes to resource refcounting) that it needs a
> > careful review anyway (which you started doing here).
> >   
> >>
> >> Of course the main issue is going to be getting some proper testing of
> >> this (especially with the shrinker added).  
> > 
> > For the shrinker, the best I can propose for now is extending the
> > IGT tests I've added. For close-to-real-usecases testing of the shmem ->
> > custom transition (this commit), making sure the g610 jobs we have in
> > mesa CI still passes is a start. If you have other ideas, I'd be happy
> > to give them a try.
> >   
> >>  
> >>>
> >>> If there's any interest in sharing code (probably with msm, since the
> >>> panthor shrinker is going to be losely based on the msm implementation),
> >>> we can always change gears and do that once we have everything
> >>> working/merged.
> >>>
> >>> [1]https://patchwork.kernel.org/project/dri-devel/patch/20240105184624.508603-1-dmitry.osipenko@collabora.com/
> >>>
> >>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> >>> ---
> >>>   drivers/gpu/drm/panthor/Kconfig         |   1 -
> >>>   drivers/gpu/drm/panthor/panthor_drv.c   |   7 +-
> >>>   drivers/gpu/drm/panthor/panthor_fw.c    |  16 +-
> >>>   drivers/gpu/drm/panthor/panthor_gem.c   | 696 ++++++++++++++++++++----
> >>>   drivers/gpu/drm/panthor/panthor_gem.h   |  62 ++-
> >>>   drivers/gpu/drm/panthor/panthor_mmu.c   |  49 +-
> >>>   drivers/gpu/drm/panthor/panthor_sched.c |   9 +-
> >>>   7 files changed, 666 insertions(+), 174 deletions(-)
> >>>
> >>> diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig
> >>> index 55b40ad07f3b..911e7f4810c3 100644
> >>> --- a/drivers/gpu/drm/panthor/Kconfig
> >>> +++ b/drivers/gpu/drm/panthor/Kconfig
> >>> @@ -8,7 +8,6 @@ config DRM_PANTHOR
> >>>   	depends on MMU
> >>>   	select DEVFREQ_GOV_SIMPLE_ONDEMAND
> >>>   	select DRM_EXEC
> >>> -	select DRM_GEM_SHMEM_HELPER
> >>>   	select DRM_GPUVM
> >>>   	select DRM_SCHED
> >>>   	select IOMMU_IO_PGTABLE_LPAE
> >>> diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c
> >>> index 52c27a60c84a..90e9abc22d9e 100644
> >>> --- a/drivers/gpu/drm/panthor/panthor_drv.c
> >>> +++ b/drivers/gpu/drm/panthor/panthor_drv.c
> >>> @@ -19,6 +19,7 @@
> >>>   #include <drm/drm_debugfs.h>
> >>>   #include <drm/drm_drv.h>
> >>>   #include <drm/drm_exec.h>
> >>> +#include <drm/drm_file.h>
> >>>   #include <drm/drm_ioctl.h>
> >>>   #include <drm/drm_print.h>
> >>>   #include <drm/drm_syncobj.h>
> >>> @@ -1457,7 +1458,7 @@ static int panthor_ioctl_bo_query_info(struct drm_device *ddev, void *data,
> >>>   	args->create_flags = bo->flags;
> >>>   
> >>>   	args->extra_flags = 0;
> >>> -	if (drm_gem_is_imported(&bo->base.base))
> >>> +	if (drm_gem_is_imported(&bo->base))
> >>>   		args->extra_flags |= DRM_PANTHOR_BO_IS_IMPORTED;
> >>>   
> >>>   	drm_gem_object_put(obj);
> >>> @@ -1671,8 +1672,7 @@ static const struct drm_driver panthor_drm_driver = {
> >>>   	.major = 1,
> >>>   	.minor = 7,
> >>>   
> >>> -	.gem_create_object = panthor_gem_create_object,
> >>> -	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
> >>> +	.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
> >>>   	.gem_prime_import = panthor_gem_prime_import,
> >>>   #ifdef CONFIG_DEBUG_FS
> >>>   	.debugfs_init = panthor_debugfs_init,
> >>> @@ -1822,3 +1822,4 @@ module_exit(panthor_exit);
> >>>   MODULE_AUTHOR("Panthor Project Developers");
> >>>   MODULE_DESCRIPTION("Panthor DRM Driver");
> >>>   MODULE_LICENSE("Dual MIT/GPL");
> >>> +MODULE_IMPORT_NS("DMA_BUF");
> >>> diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
> >>> index a64ec8756bed..f135cf2130b8 100644
> >>> --- a/drivers/gpu/drm/panthor/panthor_fw.c
> >>> +++ b/drivers/gpu/drm/panthor/panthor_fw.c
> >>> @@ -627,7 +627,6 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
> >>>   		u32 cache_mode = hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_CACHE_MODE_MASK;
> >>>   		struct panthor_gem_object *bo;
> >>>   		u32 vm_map_flags = 0;
> >>> -		struct sg_table *sgt;
> >>>   		u64 va = hdr.va.start;
> >>>   
> >>>   		if (!(hdr.flags & CSF_FW_BINARY_IFACE_ENTRY_WR))
> >>> @@ -665,11 +664,12 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev,
> >>>   		panthor_fw_init_section_mem(ptdev, section);
> >>>   
> >>>   		bo = to_panthor_bo(section->mem->obj);
> >>> -		sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
> >>> -		if (IS_ERR(sgt))
> >>> -			return PTR_ERR(sgt);
> >>>   
> >>> -		dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> >>> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
> >>> +		if (drm_WARN_ON_ONCE(&ptdev->base, !bo->dmap.sgt))
> >>> +			return -EINVAL;
> >>> +
> >>> +		dma_sync_sgtable_for_device(ptdev->base.dev, bo->dmap.sgt, DMA_TO_DEVICE);
> >>>   	}
> >>>   
> >>>   	if (hdr.va.start == CSF_MCU_SHARED_REGION_START)
> >>> @@ -729,8 +729,10 @@ panthor_reload_fw_sections(struct panthor_device *ptdev, bool full_reload)
> >>>   			continue;
> >>>   
> >>>   		panthor_fw_init_section_mem(ptdev, section);
> >>> -		sgt = drm_gem_shmem_get_pages_sgt(&to_panthor_bo(section->mem->obj)->base);
> >>> -		if (!drm_WARN_ON(&ptdev->base, IS_ERR_OR_NULL(sgt)))
> >>> +
> >>> +		/* An sgt should have been requested when the kernel BO was GPU-mapped. */
> >>> +		sgt = to_panthor_bo(section->mem->obj)->dmap.sgt;
> >>> +		if (!drm_WARN_ON_ONCE(&ptdev->base, !sgt))
> >>>   			dma_sync_sgtable_for_device(ptdev->base.dev, sgt, DMA_TO_DEVICE);
> >>>   	}
> >>>   }
> >>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
> >>> index 4b3d82f001d8..0e52c7a07c87 100644
> >>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> >>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> >>> @@ -8,9 +8,11 @@
> >>>   #include <linux/dma-mapping.h>
> >>>   #include <linux/err.h>
> >>>   #include <linux/slab.h>
> >>> +#include <linux/vmalloc.h>
> >>>   
> >>>   #include <drm/drm_debugfs.h>
> >>>   #include <drm/drm_file.h>
> >>> +#include <drm/drm_prime.h>
> >>>   #include <drm/drm_print.h>
> >>>   #include <drm/panthor_drm.h>
> >>>   
> >>> @@ -44,7 +46,7 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo)
> >>>   
> >>>   static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
> >>>   {
> >>> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> >>> +	struct panthor_device *ptdev = container_of(bo->base.dev,
> >>>   						    struct panthor_device, base);
> >>>   
> >>>   	bo->debugfs.creator.tgid = current->group_leader->pid;
> >>> @@ -57,7 +59,7 @@ static void panthor_gem_debugfs_bo_add(struct panthor_gem_object *bo)
> >>>   
> >>>   static void panthor_gem_debugfs_bo_rm(struct panthor_gem_object *bo)
> >>>   {
> >>> -	struct panthor_device *ptdev = container_of(bo->base.base.dev,
> >>> +	struct panthor_device *ptdev = container_of(bo->base.dev,
> >>>   						    struct panthor_device, base);
> >>>   
> >>>   	if (list_empty(&bo->debugfs.node))
> >>> @@ -80,9 +82,9 @@ static void panthor_gem_debugfs_bo_init(struct panthor_gem_object *bo) {}
> >>>   #endif
> >>>   
> >>>   static bool
> >>> -should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> >>> +should_map_wc(struct panthor_gem_object *bo)
> >>>   {
> >>> -	struct panthor_device *ptdev = container_of(bo->base.base.dev, struct panthor_device, base);
> >>> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> >>>   
> >>>   	/* We can't do uncached mappings if the device is coherent,
> >>>   	 * because the zeroing done by the shmem layer at page allocation
> >>> @@ -112,6 +114,208 @@ should_map_wc(struct panthor_gem_object *bo, struct panthor_vm *exclusive_vm)
> >>>   	return true;
> >>>   }
> >>>   
> >>> +static void
> >>> +panthor_gem_backing_cleanup(struct panthor_gem_object *bo)
> >>> +{
> >>> +	if (!bo->backing.pages)
> >>> +		return;
> >>> +
> >>> +	drm_gem_put_pages(&bo->base, bo->backing.pages, true, false);
> >>> +	bo->backing.pages = NULL;
> >>> +}
> >>> +
> >>> +static int
> >>> +panthor_gem_backing_get_pages_locked(struct panthor_gem_object *bo)
> >>> +{
> >>> +	dma_resv_assert_held(bo->base.resv);
> >>> +
> >>> +	if (bo->backing.pages)
> >>> +		return 0;
> >>> +
> >>> +	bo->backing.pages = drm_gem_get_pages(&bo->base);
> >>> +	if (IS_ERR(bo->backing.pages)) {
> >>> +		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
> >>> +			    bo->backing.pages);
> >>> +		return PTR_ERR(bo->backing.pages);  
> >>
> >> This leaves bo->backing.pages set to the error value, which means a
> >> future call to panthor_gem_backing_get_pages_locked() for the same
> >> object will return success. Unless there's some 'poisoning' that I
> >> haven't spotted this looks like a bug.  
> > 
> > That's a bug, I'll fix it.
> >   
> 
> I ran into this bug, during the stress testing, which resulted in a crash.
> 
> 
> Unable to handle kernel paging request at virtual address fffffffffffffff4
> KASAN: maybe wild-memory-access in range 
> [0x0003ffffffffffa0-0x0003ffffffffffa7]
> Mem abort info:
>    ESR = 0x0000000096000006
>    EC = 0x25: DABT (current EL), IL = 32 bits
>    SET = 0, FnV = 0
>    EA = 0, S1PTW = 0
>    FSC = 0x06: level 2 translation fault
> Data abort info:
>    ISV = 0, ISS = 0x00000006, ISS2 = 0x00000000
>    CM = 0, WnR = 0, TnD = 0, TagAccess = 0
>    GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
> swapper pgtable: 4k pages, 48-bit VAs, pgdp=0000000084026000
> [fffffffffffffff4] pgd=0000000000000000, p4d=0000000088a6d403, 
> pud=0000000088a6e403, pmd=0000000000000000
> pc : nonblocking_page_setup+0x90/0x108 [panthor]
> lr : nonblocking_page_setup+0x8c/0x108 [panthor]
> sp : ffff80008bdd7a80
> Call trace:
>   nonblocking_page_setup+0x90/0x108 [panthor] (P)
>   panthor_gem_fault+0x84/0x218 [panthor]
>   __do_fault+0x78/0x3d0
>   __handle_mm_fault+0xe20/0x23a0
>   handle_mm_fault+0x10c/0x438
>   do_page_fault+0x234/0x958
>   do_translation_fault+0xa0/0xd8
>   do_mem_abort+0x68/0x100
>   el0_da+0x54/0x1d8
>   el0t_64_sync_handler+0xd0/0xe8
>   el0t_64_sync+0x198/0x1a0
> 
> 
> First the call to panthor_gem_backing_get_pages_locked(), from 
> blocking_page_setup(), failed due to low memory.
> 
> panthor_gem_fault() returned VM_FAULT_RETRY as mmap_lock_held was 0.
> 
> Then the crash happened inside nonblocking_page_setup() when
> panthor_gem_fault() was called for the 2nd time.
> 
> 
> Made the following change locally to avoid the issue.
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c 
> b/drivers/gpu/drm/panthor/panthor_gem.c
> index 6e91c5022d0d..b31a4606a8c6 100644
> --- a/drivers/gpu/drm/panthor/panthor_gem.c
> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
> @@ -234,7 +234,7 @@ panthor_gem_backing_get_pages_locked(struct 
> panthor_gem_object *bo)
>          if (IS_ERR(bo->backing.pages)) {
>                  drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
>                              bo->backing.pages);
> -               return PTR_ERR(bo->backing.pages);
> +               return PTR_ERR(xchg(&bo->backing.pages, NULL));

That's clever, but after searching for such patterns in the linux code
base, I couldn't find a match (xchg only seems to be used for atomic
updates), so I'd be tempted to stick to the usual

		ret = PTR_ERR(bo->backing.pages);
		bo->backing.pages = NULL;
		return ret;

or

	pages = xxx()
	if (IS_ERR(pages)) {
		drm_dbg_kms(bo->base.dev, "Failed to get pages (%pe)\n",
			    bo->backing.pages);
		return PTR_ERR(pages);
	}

	bo->backing.pages = pages;

Anyway, I'm adding this to my fixup stack.

Thanks,

Boris

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-21 14:52     ` Boris Brezillon
@ 2026-01-28 11:21       ` Akash Goel
  2026-01-28 15:52         ` Boris Brezillon
  0 siblings, 1 reply; 62+ messages in thread
From: Akash Goel @ 2026-01-28 11:21 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

Hi Boris,

On 1/21/26 14:52, Boris Brezillon wrote:
> On Wed, 21 Jan 2026 11:49:34 +0000
> Akash Goel <akash.goel@arm.com> wrote:
> 
>> Hi Boris,
>>
>> On 1/9/26 13:08, Boris Brezillon wrote:
>>> From: Akash Goel <akash.goel@arm.com>
>>>
>>> This implementation is losely based on the MSM shrinker, and it's
>>> relying on the drm_gpuvm eviction/validation infrastructure.
>>>
>>> Right now we only support swapout/eviction, but we could add an extra
>>> flag to specify when buffer content doesn't need to be preserved to
>>> avoid the swapout/swapin dance.
>>>
>>> Locking is a bit of a nightmare, but using _trylock() all the way in
>>> the reclaim path seems to make lockdep happy. And yes, we might be
>>> missing opportunities to reclaim when the system is under heavy GPU
>>> load/heavy memory pressure/heavy GPU VM activity, but that's better
>>> than no reclaim at all.
>>>
>>> Signed-off-by: Akash Goel <akash.goel@arm.com>
>>> Co-developed-by: Boris Brezillon <boris.brezillon@collabora.com>
>>> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
>>> ---
>>>    drivers/gpu/drm/panthor/panthor_device.c |  11 +-
>>>    drivers/gpu/drm/panthor/panthor_device.h |  73 ++++
>>>    drivers/gpu/drm/panthor/panthor_gem.c    | 427 ++++++++++++++++++++++-
>>>    drivers/gpu/drm/panthor/panthor_gem.h    |  67 ++++
>>>    drivers/gpu/drm/panthor/panthor_mmu.c    | 338 +++++++++++++++++-
>>>    drivers/gpu/drm/panthor/panthor_mmu.h    |   8 +
>>>    6 files changed, 901 insertions(+), 23 deletions(-)
>>>    
>>

>>
>>>    
>>>    /**
>>>    
>>> +void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
>>> +	struct panthor_vm *vm = NULL;
>>> +	struct drm_gpuvm_bo *vm_bo;
>>> +
>>> +	dma_resv_assert_held(bo->base.resv);
>>> +	lockdep_assert_held(&bo->base.gpuva.lock);
>>> +
>>> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
>>> +		/* We're only supposed to have one vm_bo in the list if we get there. */
>>> +		drm_WARN_ON(&ptdev->base, vm);
>>> +		vm = container_of(vm_bo->vm, struct panthor_vm, base);
>>> +
>>> +		mutex_lock(&ptdev->reclaim.lock);
>>> +		drm_gem_lru_move_tail_locked(&vm->reclaim.lru, &bo->base);
>>> +		if (list_empty(&vm->reclaim.lru_node))
>>> +			list_move(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
>>> +		mutex_unlock(&ptdev->reclaim.lock);
>>> +	}
>>> +}
>>> +
>>> +int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
>>> +{
>>> +	struct drm_gpuvm_bo *vm_bo;
>>> +
>>> +	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
>>> +		struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
>>> +		struct drm_gpuva *va;
>>> +
>>> +		if (!mutex_trylock(&vm->op_lock))
>>> +			return -EDEADLK;
>>> +
>>> +		drm_gpuvm_bo_evict(vm_bo, true);
>>> +		drm_gpuvm_bo_for_each_va(va, vm_bo) {
>>> +			struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
>>> +
>>> +			panthor_vm_lock_region(vm, va->va.addr, va->va.range);
>>> +			panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);
>>
>> On further testing, I ran into a kernel warning issue.
>>
>> https://elixir.bootlin.com/linux/v6.18-rc5/source/drivers/iommu/io-pgtable-arm.c#L641
>>
>> https://elixir.bootlin.com/linux/v6.18-rc5/source/drivers/gpu/drm/panthor/panthor_mmu.c#L930
>>
>> Call trace:
>>    __arm_lpae_unmap+0x570/0x5c8 (P)
>>    __arm_lpae_unmap+0x144/0x5c8
>>    __arm_lpae_unmap+0x144/0x5c8
>>    arm_lpae_unmap_pages+0xe8/0x120
>>    panthor_vm_unmap_pages+0x1f0/0x3f8 [panthor]
>>    panthor_vm_evict_bo_mappings_locked+0xf4/0x1d8 [panthor]
>>    panthor_gem_try_evict+0x190/0x7c8 [panthor]
>>    drm_gem_lru_scan+0x388/0x698
>>
>> Following sequence leads to the kernel warnings.
>>
>> - BO is mapped to GPU and is in GPU_MAPPED_PRIVATE state.
>>
>> - Shrinker callback gets invoked and that BO is evicted. As a result BO
>> is moved to UNRECLAIMABLE state.
>>
>> - Userspace accesses the evicted BO and panthor_gem_fault() gets the
>> backing pages again. BO is moved back to GPU_MAPPED_PRIVATE state (even
>> though technically the BO is still in evicted state, both vm_bo->evicted
>> and vma->evicted are TRUE.
>>
>> - Shrinker callback is invoked again and tries to evict the same BO.
>>
>> - panthor_vm_evict_bo_mappings_locked() calls panthor_vm_unmap_pages()
>> and the kernel warnings are emiited as PTEs are already invalid.
> 
> Yep, it looks like the other side of the problem pointed out by Steve:
> CPU mappings can make the buffer reclaimable again, but those are still
> evicted from the VM PoV.
> 
>>
>>
>> Made the following change locally to avoid the warning.
>>
>> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c
>> b/drivers/gpu/drm/panthor/panthor_mmu.c
>> index ffd821b3be46..e0a1dda1675f 100644
>> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
>> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
>> @@ -2344,6 +2344,8 @@ int panthor_vm_evict_bo_mappings_locked(struct
>> panthor_gem_object *bo)
>>                   drm_gpuvm_bo_for_each_va(va, vm_bo) {
>>                           struct panthor_vma *vma = container_of(va,
>> struct panthor_vma, base);
>>
>> +                       if (vma->evicted)
>> +                               continue;
>>                           panthor_vm_lock_region(vm, va->va.addr,
>> va->va.range);
>>                           panthor_vm_unmap_pages(vm, va->va.addr,
>> va->va.range);
>>                           panthor_vm_unlock_region(vm);
>>
>>
>>
>> Do you think we can also update is_gpu_mapped() so that an evicted BO
>> moves to MMAPPED state, instead of GPU_MAPPED_PRIVATE state, on CPU
>> access ?.
>>
>> Not sure if the following change makes sense.
>>
>> diff --git a/drivers/gpu/drm/panthor/panthor_gem.c
>> b/drivers/gpu/drm/panthor/panthor_gem.c
>> index 6e91c5022d0d..8a8411fed195 100644
>> --- a/drivers/gpu/drm/panthor/panthor_gem.c
>> +++ b/drivers/gpu/drm/panthor/panthor_gem.c
>> @@ -125,6 +125,8 @@ static bool is_gpu_mapped(struct panthor_gem_object *bo,
>>           struct drm_gpuvm_bo *vm_bo;
>>
>>           drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
>> +               if (vm_bo->evicted)
>> +                       continue;
>>                   if (!vm) {
>>                           *state = PANTHOR_GEM_GPU_MAPPED_PRIVATE;
>>                           vm = vm_bo->vm;
>>
>>
>> Please let me know.
> 
> Yep, this looks correct. I'll add that to my list of fixups.
> 

Thanks.

Sorry I have a doubt.

Should we update the panthor_gem_sync() function to ensure pages can't 
get reclaimed whlilst they are being synced ?.

panthor_ioctl_bo_sync() takes a reference on the BO before calling 
panthor_gem_sync(). But I think due to this patch, the backking pages 
can get released in the middle of sync loop.

	sgt = panthor_gem_get_dev_sgt(bo);
	if (IS_ERR(sgt))
		return PTR_ERR(sgt);
	for_each_sgtable_dma_sg(sgt, sgl, count) {
		dma_addr_t paddr = sg_dma_address(sgl);
		size_t len = sg_dma_len(sgl);

		dma_sync_single_for_device();
		dma_sync_single_for_cpu();
	}


Please can you confirm.

Best regards
Akash


> Thanks,
> 
> Boris

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-28 11:21       ` Akash Goel
@ 2026-01-28 15:52         ` Boris Brezillon
  2026-01-28 16:26           ` Akash Goel
  0 siblings, 1 reply; 62+ messages in thread
From: Boris Brezillon @ 2026-01-28 15:52 UTC (permalink / raw)
  To: Akash Goel
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

Hi Akash,

On Wed, 28 Jan 2026 11:21:43 +0000
Akash Goel <akash.goel@arm.com> wrote:

> Sorry I have a doubt.
> 
> Should we update the panthor_gem_sync() function to ensure pages can't 
> get reclaimed whlilst they are being synced ?.
> 
> panthor_ioctl_bo_sync() takes a reference on the BO before calling 
> panthor_gem_sync(). But I think due to this patch, the backking pages 
> can get released in the middle of sync loop.
> 
> 	sgt = panthor_gem_get_dev_sgt(bo);
> 	if (IS_ERR(sgt))
> 		return PTR_ERR(sgt);
> 	for_each_sgtable_dma_sg(sgt, sgl, count) {
> 		dma_addr_t paddr = sg_dma_address(sgl);
> 		size_t len = sg_dma_len(sgl);
> 
> 		dma_sync_single_for_device();
> 		dma_sync_single_for_cpu();
> 	}
> 
> 
> Please can you confirm.

I think you're right. We either need to pin/unpin around a sync
operation, or we need to take the resv lock to make sure the GEM is not
reclaimed while we do that. It probably makes sense to do the latter,
since we don't want to prematurely pull back the data from the swap
just to synchronize CPU accesses (the
dma_unmap_sgtable() in panthor_gem_dev_map_cleanup_locked() should have
synchronized things for us on a swapout).

I'll fix that before posting a v2.

Thanks,

Boris

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v1 9/9] drm/panthor: Add a GEM shrinker
  2026-01-28 15:52         ` Boris Brezillon
@ 2026-01-28 16:26           ` Akash Goel
  0 siblings, 0 replies; 62+ messages in thread
From: Akash Goel @ 2026-01-28 16:26 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Steven Price, Liviu Dudau, Adrián Larumbe, dri-devel,
	David Airlie, Simona Vetter, Rob Clark, Sean Paul, Konrad Dybcio,
	Akhil P Oommen, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Dmitry Osipenko, Chris Diamand,
	Danilo Krummrich, Matthew Brost, Thomas Hellström,
	Alice Ryhl, kernel, nd

Hi Boris,

On 1/28/26 15:52, Boris Brezillon wrote:
> Hi Akash,
> 
> On Wed, 28 Jan 2026 11:21:43 +0000
> Akash Goel <akash.goel@arm.com> wrote:
> 
>> Sorry I have a doubt.
>>
>> Should we update the panthor_gem_sync() function to ensure pages can't
>> get reclaimed whlilst they are being synced ?.
>>
>> panthor_ioctl_bo_sync() takes a reference on the BO before calling
>> panthor_gem_sync(). But I think due to this patch, the backking pages
>> can get released in the middle of sync loop.
>>
>> 	sgt = panthor_gem_get_dev_sgt(bo);
>> 	if (IS_ERR(sgt))
>> 		return PTR_ERR(sgt);
>> 	for_each_sgtable_dma_sg(sgt, sgl, count) {
>> 		dma_addr_t paddr = sg_dma_address(sgl);
>> 		size_t len = sg_dma_len(sgl);
>>
>> 		dma_sync_single_for_device();
>> 		dma_sync_single_for_cpu();
>> 	}
>>
>>
>> Please can you confirm.
> 
> I think you're right. We either need to pin/unpin around a sync
> operation, or we need to take the resv lock to make sure the GEM is not
> reclaimed while we do that. It probably makes sense to do the latter,

Fully agree with you. The latter option is better.


> since we don't want to prematurely pull back the data from the swap
> just to synchronize CPU accesses (the
> dma_unmap_sgtable() in panthor_gem_dev_map_cleanup_locked() should have
> synchronized things for us on a swapout).
> 

Right, the sync would happen inside dma_unmap_sgtable() on swapout.

I think we may hit the following warning, inside 
panthor_gem_dev_map_get_sgt_locked(), if BO_SYNC ioctl is invoked for an 
evicted BO.

	if (drm_WARN_ON_ONCE(bo->base.dev, !bo->backing.pages))
		return ERR_PTR(-EINVAL);

So we would need to take resv lock inside panthor_gem_sync() and also 
return early if bo->backing.pages is NULL.


> I'll fix that before posting a v2.
> 

Thank you.

Best regards
Akash


> Thanks,
> 
> Boris

^ permalink raw reply	[flat|nested] 62+ messages in thread

end of thread, other threads:[~2026-01-28 16:27 UTC | newest]

Thread overview: 62+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-09 13:07 [PATCH v1 0/9] drm/panthor: Add a GEM shrinker Boris Brezillon
2026-01-09 13:07 ` [PATCH v1 1/9] drm/gem: Consider GEM object reclaimable if shrinking fails Boris Brezillon
2026-01-12  9:25   ` Alice Ryhl
2026-01-12 10:02     ` Boris Brezillon
2026-01-15 13:28   ` Liviu Dudau
2026-01-09 13:07 ` [PATCH v1 2/9] drm/gpuvm: Validate BOs in the extobj list when VM is resv protected Boris Brezillon
2026-01-09 19:38   ` Danilo Krummrich
2026-01-12  7:30     ` Boris Brezillon
2026-01-09 13:07 ` [PATCH v1 3/9] drm/panthor: Move panthor_gems_debugfs_init() to panthor_gem.c Boris Brezillon
2026-01-12 11:27   ` Steven Price
2026-01-15 13:39   ` Liviu Dudau
2026-01-09 13:07 ` [PATCH v1 4/9] drm/panthor: Group panthor_kernel_bo_xxx() helpers Boris Brezillon
2026-01-12 11:29   ` Steven Price
2026-01-15 13:41   ` Liviu Dudau
2026-01-09 13:07 ` [PATCH v1 5/9] drm/panthor: Part ways with drm_gem_shmem_object Boris Brezillon
2026-01-12 12:06   ` Steven Price
2026-01-12 14:17     ` Boris Brezillon
2026-01-12 16:03       ` Steven Price
2026-01-12 16:45         ` Boris Brezillon
2026-01-21 11:11       ` Akash Goel
2026-01-21 15:17         ` Boris Brezillon
2026-01-15 16:51   ` Liviu Dudau
2026-01-15 17:27     ` Boris Brezillon
2026-01-15 17:45       ` Liviu Dudau
2026-01-16 12:09         ` Steven Price
2026-01-09 13:07 ` [PATCH v1 6/9] drm/panthor: Lazily allocate pages on mmap() Boris Brezillon
2026-01-12 12:15   ` Steven Price
2026-01-12 14:32     ` Boris Brezillon
2026-01-12 16:41       ` Steven Price
2026-01-12 16:50         ` Boris Brezillon
2026-01-15 17:34   ` Liviu Dudau
2026-01-15 19:27     ` Boris Brezillon
2026-01-16  8:19   ` kernel test robot
2026-01-09 13:07 ` [PATCH v1 7/9] drm/panthor: Split panthor_vm_prepare_map_op_ctx() to prepare for reclaim Boris Brezillon
2026-01-12 12:21   ` Steven Price
2026-01-15 17:40   ` Liviu Dudau
2026-01-09 13:08 ` [PATCH v1 8/9] drm/panthor: Track the number of mmap on a BO Boris Brezillon
2026-01-12 12:33   ` Steven Price
2026-01-12 14:39     ` Boris Brezillon
2026-01-12 15:19       ` Alice Ryhl
2026-01-12 15:49         ` Boris Brezillon
2026-01-12 15:51           ` Alice Ryhl
2026-01-12 16:06             ` Boris Brezillon
2026-01-12 16:49       ` Steven Price
2026-01-12 16:59         ` Boris Brezillon
2026-01-12 17:10           ` Steven Price
2026-01-12 17:18             ` Boris Brezillon
2026-01-13 12:26             ` Boris Brezillon
2026-01-09 13:08 ` [PATCH v1 9/9] drm/panthor: Add a GEM shrinker Boris Brezillon
2026-01-14 15:05   ` Steven Price
2026-01-15 10:50     ` Boris Brezillon
2026-01-15 11:24       ` Steven Price
2026-01-15 12:01         ` Boris Brezillon
2026-01-15 13:56   ` Akash Goel
2026-01-15 14:36     ` Boris Brezillon
2026-01-15 14:37     ` Boris Brezillon
2026-01-21 11:49   ` Akash Goel
2026-01-21 14:52     ` Boris Brezillon
2026-01-28 11:21       ` Akash Goel
2026-01-28 15:52         ` Boris Brezillon
2026-01-28 16:26           ` Akash Goel
2026-01-12  8:37 ` [PATCH v1 0/9] " Boris Brezillon

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.