On Mon, Feb 02, 2026 at 12:36:07PM +0100, 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 > > 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]> Best regards, Liviu > --- > drivers/gpu/drm/panthor/panthor_device.c | 11 +- > drivers/gpu/drm/panthor/panthor_device.h | 73 ++++ > drivers/gpu/drm/panthor/panthor_gem.c | 460 ++++++++++++++++++++++- > drivers/gpu/drm/panthor/panthor_gem.h | 70 ++++ > drivers/gpu/drm/panthor/panthor_mmu.c | 345 ++++++++++++++++- > drivers/gpu/drm/panthor/panthor_mmu.h | 8 + > 6 files changed, 938 insertions(+), 29 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 26fe4be10a86..7af9285447c3 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); > } > } > > @@ -203,9 +311,6 @@ panthor_gem_dev_map_get_sgt_locked(struct > panthor_gem_object *bo) > 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); > @@ -534,6 +639,46 @@ void panthor_gem_unpin(struct panthor_gem_object *bo) > panthor_gem_backing_unpin_locked(bo); > } > > +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); > @@ -688,6 +833,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 > @@ -761,7 +910,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); > > @@ -798,6 +949,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); > @@ -956,6 +1108,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) > @@ -982,9 +1135,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) > @@ -1028,7 +1193,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; > } > > /** > @@ -1038,11 +1207,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); > > @@ -1050,6 +1221,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); > @@ -1098,6 +1271,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 > @@ -1109,7 +1288,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) > @@ -1121,6 +1300,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); > > @@ -1129,6 +1312,231 @@ 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 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; > + > + 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; > @@ -1247,10 +1655,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..424249aa4d8a 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,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 8f70749f6bc0..57a4d56c865e 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]); > > @@ -1265,18 +1299,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; > } > @@ -1290,9 +1323,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); > @@ -1892,6 +1935,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); > @@ -2105,7 +2152,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); > @@ -2189,8 +2236,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); > @@ -2204,6 +2253,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) { > @@ -2218,6 +2268,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, > @@ -2247,19 +2298,204 @@ 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; > + > + /* 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); > + 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, > }; > > /** > @@ -2474,6 +2710,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; > @@ -2821,7 +3059,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.52.0 >
