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);