On 05/03/2026 12:43, 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
> 
> 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]>

You've already got my Reviewed-by, but one minor point below.

> ---
>  drivers/gpu/drm/panthor/panthor_device.c |  11 +-
>  drivers/gpu/drm/panthor/panthor_device.h |  73 ++++
>  drivers/gpu/drm/panthor/panthor_gem.c    | 466 ++++++++++++++++++++++-
>  drivers/gpu/drm/panthor/panthor_gem.h    |  68 ++++
>  drivers/gpu/drm/panthor/panthor_mmu.c    | 352 ++++++++++++++++-
>  drivers/gpu/drm/panthor/panthor_mmu.h    |   8 +
>  6 files changed, 951 insertions(+), 27 deletions(-)
> 

[...]

> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c 
> b/drivers/gpu/drm/panthor/panthor_mmu.c
> index 5d07c1b96e0a..3daf7ee0961e 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c

[...]

> +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;
> +
> +             /* 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;
> +
> +                     panthor_vm_lock_region(vm, va->va.addr, va->va.range);

NIT: You are ignoring the return value here - it might be better to bail
out instead if this happens. Admittedly this is a "should never happen"
case.

Thanks,
Steve

> +                     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. 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 +2706,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 +3055,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);

Reply via email to