Hello,

On 09.03.2026 16:11, Boris Brezillon wrote:
> From: Akash Goel <[email protected]>
>
> 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.
>
> v2:
> - Move gpu_mapped_shared next to the mmapped LRU
> - Add a bunch of missing is_[vm_bo,vma]_evicted() tests
> - Only test mmap_count to check if a BO is mmaped
> - Remove stale comment about shrinker not being a thing
> - Allow pin_count to be non-zero in panthor_gem_swapin_locked()
> - Fix panthor_gem_sync() to check for BO residency before doing the CPU sync
> - Fix the value returned by panthor_gem_shrinker_count() in case some
>   memory has been released
> - Check drmm_mutex_init() ret code
> - Explicitly mention that PANTHOR_GEM_UNRECLAIMABLE is the initial state
>   of all BOs
>
> v3:
> - Make panthor_gem_try_evict() static
> - Collect {A,R}-bs
>
> v4:
> - Update the reclaim_state in panthor_gem_mmap()
> - Don't reclaim GPU-mapped BOs if can_block() returns false
> - Skip evicited vm_bos in panthor_vm_update_bo_reclaim_lru_locked() to
>   avoid spurious WARN_ON()s
> - Explain why we have to do this
>   select_evicted_vma/repopulate_evicted_vma dance
>
> v5:
> - Properly report the reclaimable size in panthor_gem_debugfs_print_bos()
> - Check panthor_vm_lock_region() errors in
>   panthor_vm_evict_bo_mappings_locked()
> - Fix lock order inversion (dma_resv_wait_timeout() inside gpuva.lock)
>
> Signed-off-by: Akash Goel <[email protected]>
> Co-developed-by: Boris Brezillon <[email protected]>
> Signed-off-by: Boris Brezillon <[email protected]>
> Acked-by: Liviu Dudau <[email protected]>
> Reviewed-by: Steven Price <[email protected]>
> ---
>  drivers/gpu/drm/panthor/panthor_device.c |  11 +-
>  drivers/gpu/drm/panthor/panthor_device.h |  73 ++++
>  drivers/gpu/drm/panthor/panthor_gem.c    | 468 ++++++++++++++++++++++-
>  drivers/gpu/drm/panthor/panthor_gem.h    |  68 ++++
>  drivers/gpu/drm/panthor/panthor_mmu.c    | 359 ++++++++++++++++-
>  drivers/gpu/drm/panthor/panthor_mmu.h    |   8 +
>  6 files changed, 960 insertions(+), 27 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 <[email protected]> */
>  /* Copyright 2019 Linaro, Ltd, Rob Herring <[email protected]> */
>  /* 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 b6696f73a536..5cba272f9b4d 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>
> @@ -178,6 +179,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.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.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_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 7c127d908c6f..855ce378afce 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 <[email protected]> */
>  /* 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,103 @@ 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) {
> +             /* Skip evicted GPU mappings. */
> +             if (vm_bo->evicted)
> +                     continue;
> +
> +             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))
> +             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:
> +             drm_WARN(&ptdev->base, true, "invalid GEM reclaim state 
> (%d)\n", new_state);
> +             break;
> +     }
> +
> +     bo->reclaim_state = new_state;
> +}
> +
>  static void
>  panthor_gem_backing_cleanup_locked(struct panthor_gem_object *bo)
>  {
> @@ -157,8 +258,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;
>  }
> @@ -172,6 +277,9 @@ 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.
>                */
> +             mutex_lock(&bo->base.gpuva.lock);
> +             panthor_gem_update_reclaim_state_locked(bo, NULL);
> +             mutex_unlock(&bo->base.gpuva.lock);
>       }
>  }
>
> @@ -535,6 +643,46 @@ 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;
> +
> +     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_locked(bo);
> +     panthor_gem_backing_cleanup_locked(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);
> @@ -607,8 +755,12 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, 
> struct vm_area_struct *v
>
>       if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
>               dma_resv_lock(obj->resv, NULL);
> -             if (!refcount_inc_not_zero(&bo->cmap.mmap_count))
> +             if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
>                       refcount_set(&bo->cmap.mmap_count, 1);
> +                     mutex_lock(&bo->base.gpuva.lock);
> +                     panthor_gem_update_reclaim_state_locked(bo, NULL);
> +                     mutex_unlock(&bo->base.gpuva.lock);
> +             }
>               dma_resv_unlock(obj->resv);
>       }
>
> @@ -690,6 +842,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
> @@ -763,7 +919,9 @@ static void panthor_gem_vm_close(struct vm_area_struct 
> *vma)
>
>       dma_resv_lock(bo->base.resv, NULL);
>       if (refcount_dec_and_test(&bo->cmap.mmap_count)) {
> -             /* Nothing to do, pages are reclaimed lazily. */
> +             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);
>
> @@ -800,6 +958,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);
> @@ -958,6 +1117,7 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>       struct sg_table *sgt;
>       struct scatterlist *sgl;
>       unsigned int count;
> +     int ret;
>
>       /* Make sure the range is in bounds. */
>       if (offset + size < offset || offset + size > bo->base.size)
> @@ -984,9 +1144,21 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>       if (size == 0)
>               return 0;
>
> -     sgt = panthor_gem_get_dev_sgt(bo);
> -     if (IS_ERR(sgt))
> -             return PTR_ERR(sgt);
> +     ret = dma_resv_lock_interruptible(bo->base.resv, NULL);
> +     if (ret)
> +             return ret;
> +
> +     /* If there's no pages, there's no point pulling those back, bail out 
> early. */
> +     if (!bo->backing.pages) {
> +             ret = 0;
> +             goto out_unlock;
> +     }
> +
> +     sgt = panthor_gem_dev_map_get_sgt_locked(bo);
> +     if (IS_ERR(sgt)) {
> +             ret = PTR_ERR(sgt);
> +             goto out_unlock;
> +     }
>
>       for_each_sgtable_dma_sg(sgt, sgl, count) {
>               if (size == 0)
> @@ -1030,7 +1202,11 @@ panthor_gem_sync(struct drm_gem_object *obj, u32 type,
>                       dma_sync_single_for_cpu(dma_dev, paddr, len, 
> DMA_FROM_DEVICE);
>       }
>
> -     return 0;
> +     ret = 0;
> +
> +out_unlock:
> +     dma_resv_unlock(bo->base.resv);
> +     return ret;
>  }
>
>  /**
> @@ -1040,11 +1216,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);
>
> @@ -1052,6 +1230,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);
> @@ -1100,6 +1280,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
> @@ -1111,7 +1297,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)
> @@ -1123,6 +1309,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);
>
> @@ -1131,6 +1321,233 @@ 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 ? count : SHRINK_EMPTY;
> +}
> +
> +static bool panthor_gem_try_evict_no_resv_wait(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;
> +     }
> +
> +     /* 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 bool panthor_gem_try_evict(struct drm_gem_object *obj,
> +                               struct ww_acquire_ctx *ticket)
> +{
> +     struct panthor_gem_object *bo = to_panthor_bo(obj);
> +
> +     /* Wait was too long, skip. */
> +     if (dma_resv_wait_timeout(obj->resv, DMA_RESV_USAGE_BOOKKEEP, false, 
> 10) <= 0)
> +             return false;
> +
> +     return panthor_gem_try_evict_no_resv_wait(&bo->base, ticket);
> +}
> +
> +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_no_resv_wait, 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_no_resv_wait, NULL);
> +     if (freed >= sc->nr_to_scan)
> +             goto out;
> +
> +     if (!can_block(sc))
> +             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;
> +
> +     atomic_set(&ptdev->reclaim.retry_count, 0);
> +
> +     if (freed)
> +             return freed;
> +
> +     /* There's nothing left to reclaim, or the resources are contended. 
> Give up now. */
> +     return SHRINK_STOP;
> +}
> +
> +int panthor_gem_shrinker_init(struct panthor_device *ptdev)
> +{
> +     struct shrinker *shrinker;
> +     int ret;
> +
> +     ret = drmm_mutex_init(&ptdev->base, &ptdev->reclaim.lock);
> +     if (ret)
> +             return ret;
> +
> +     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;
> @@ -1174,6 +1591,7 @@ static void panthor_gem_debugfs_bo_print(struct 
> panthor_gem_object *bo,
>                                        struct seq_file *m,
>                                        struct gem_size_totals *totals)
>  {
> +     enum panthor_gem_reclaim_state reclaim_state = bo->reclaim_state;
>       unsigned int refcount = kref_read(&bo->base.refcount);
>       char creator_info[32] = {};
>       size_t resident_size;
> @@ -1209,6 +1627,8 @@ static void panthor_gem_debugfs_bo_print(struct 
> panthor_gem_object *bo,
>
>       totals->size += bo->base.size;
>       totals->resident += resident_size;
> +     if (reclaim_state != PANTHOR_GEM_UNRECLAIMABLE)
> +             totals->reclaimable += resident_size;
>  }
>
>  static void panthor_gem_debugfs_print_bos(struct panthor_device *ptdev,
> @@ -1249,10 +1669,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..30281fad7327 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 <[email protected]> */
>  /* Copyright 2023 Collabora ltd. */
> +/* Copyright 2025 ARM Limited. All rights reserved. */
>
>  #ifndef __PANTHOR_GEM_H__
>  #define __PANTHOR_GEM_H__
> @@ -93,6 +94,65 @@ 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. It's also the state all GEM
> +      * objects start in, because no memory is allocated until explicitly
> +      * requested by a CPU or GPU map, meaning there's nothing to reclaim
> +      * until such an allocation happens.
> +      */
> +     PANTHOR_GEM_UNRECLAIMABLE,
> +};
> +
>  /**
>   * struct panthor_gem_object - Driver specific GEM object.
>   */
> @@ -109,6 +169,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 +253,11 @@ 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);
> +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 5d07c1b96e0a..4b415aaedaa9 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 <[email protected]> */
>  /* 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;
>  };
>
>  /**
> @@ -689,6 +700,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 at 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.
>        */
> @@ -1084,7 +1105,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);
>  }
>
> @@ -1105,6 +1134,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]);
>
> @@ -1264,18 +1298,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;
>       }
> @@ -1294,6 +1327,13 @@ static int panthor_vm_prepare_map_op_ctx(struct 
> panthor_vm_op_ctx *op_ctx,
>               dma_resv_unlock(panthor_vm_resv(vm));
>       }
>
> +     /* And finally update the BO state. */
> +     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);
> +
>       return 0;
>
>  err_cleanup:
> @@ -1881,6 +1921,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);
> @@ -2094,7 +2138,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);
> @@ -2178,8 +2222,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) {

I think we'd want to make sure we don't map the pages for 'prev' when the 
original unmap
vma is evicted, but we still want to create a new vma for it.

>               struct panthor_gem_object *bo = 
> to_panthor_bo(op->remap.prev->gem.obj);
> @@ -2193,6 +2239,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) {

Same as above.

> @@ -2207,6 +2254,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,
> @@ -2236,19 +2284,221 @@ 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) {
> +             if (vm_bo->evicted)
> +                     continue;

I found this a bit confusing, because this function is called for objects whose 
state is
PANTHOR_GEM_GPU_MAPPED_PRIVATE, described as 'GEM is GPU mapped to only one 
VM'. So that
made me think it was synonymous to panthor_gem_object::exclusive_vm_root_gem 
being set, but
according to this code, a BO is shrinker-private if only one of its VM_BO's 
isn't evicted
at any given time. I guess this definition is what matters wrt the shrinker, 
because it can
go straight pick it up from the VM's reclaim.lru list. Maybe it'd be less 
confusing if
the uAPI header file's description of PANTHOR_GEM_GPU_MAPPED_PRIVATE reflected 
this too?

> +             /* We're only supposed to have one non-evicted 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;
> +     int ret = 0;
> +
> +     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;
> +
> +             /* Skip already evicted GPU mappings. */
> +             if (vm_bo->evicted)
> +                     continue;
> +
> +             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);
> +
> +                     if (vma->evicted)
> +                             continue;
> +
> +                     ret = panthor_vm_lock_region(vm, va->va.addr, 
> va->va.range);
> +                     if (ret)
> +                             break;
> +
> +                     panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);
> +                     panthor_vm_unlock_region(vm);
> +                     vma->evicted = true;
> +             }
> +
> +             mutex_unlock(&vm->op_lock);
> +
> +             if (ret)
> +                     break;
> +     }
> +
> +     return ret;
> +}
> +
> +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. Note that the
> +      * evicted_vma selection was done with the same lock held, but we had
> +      * to release it so we can allocate PTs, because this very same lock
> +      * is taken in a DMA-signalling path.
> +      */
> +     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;
> +
> +             /* Because we had to release the lock between the evicted_vma 
> selection
> +              * and its repopulation, we can't rely solely on pointer 
> equality (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,
>  };
>
>  /**
> @@ -2463,6 +2713,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;
> @@ -2810,7 +3062,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 <[email protected]> */
>  /* 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.53.0

Adrian Larumbe

Reply via email to