From b74fae5492d1429c03b57a423d0837a3bdc4b397 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sun, 29 Jun 2025 13:13:25 -0700 Subject: [PATCH] drm/msm: Add VM_BIND throttling A large number of (unsorted or separate) small (<2MB) mappings can cause a lot of, probably unnecessary, prealloc pages. Ie. a single 4k page size mapping will pre-allocate 3 pages (for levels 2-4) for the pagetable. Which can chew up a large amount of unneeded memory. So add a mechanism to put an upper bound on the # of pre-alloc pages. Signed-off-by: Rob Clark Tested-by: Antonino Maniscalco Reviewed-by: Antonino Maniscalco Patchwork: https://patchwork.freedesktop.org/patch/661529/ --- drivers/gpu/drm/msm/msm_gem.h | 20 ++++++++++++++++++++ drivers/gpu/drm/msm/msm_gem_vma.c | 28 ++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h index 5c0c59e4835cf..88239da1cd72f 100644 --- a/drivers/gpu/drm/msm/msm_gem.h +++ b/drivers/gpu/drm/msm/msm_gem.h @@ -75,6 +75,26 @@ struct msm_gem_vm { */ struct drm_gpu_scheduler sched; + /** + * @prealloc_throttle: Used to throttle VM_BIND ops if too much pre- + * allocated memory is in flight. + * + * Because we have to pre-allocate pgtable pages for the worst case + * (ie. new mappings do not share any PTEs with existing mappings) + * we could end up consuming a lot of resources transiently. The + * prealloc_throttle puts an upper bound on that. + */ + struct { + /** @wait: Notified when preallocated resources are released */ + wait_queue_head_t wait; + + /** + * @in_flight: The # of preallocated pgtable pages in-flight + * for queued VM_BIND jobs. + */ + atomic_t in_flight; + } prealloc_throttle; + /** * @mm: Memory management for kernel managed VA allocations * diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c b/drivers/gpu/drm/msm/msm_gem_vma.c index 02491e7dd8165..1ad67dfa94e30 100644 --- a/drivers/gpu/drm/msm/msm_gem_vma.c +++ b/drivers/gpu/drm/msm/msm_gem_vma.c @@ -705,6 +705,8 @@ msm_vma_job_free(struct drm_sched_job *_job) vm->mmu->funcs->prealloc_cleanup(vm->mmu, &job->prealloc); + atomic_sub(job->prealloc.count, &vm->prealloc_throttle.in_flight); + drm_sched_job_cleanup(_job); job_foreach_bo (obj, job) @@ -721,6 +723,8 @@ msm_vma_job_free(struct drm_sched_job *_job) kfree(op); } + wake_up(&vm->prealloc_throttle.wait); + kfree(job); } @@ -783,6 +787,8 @@ msm_gem_vm_create(struct drm_device *drm, struct msm_mmu *mmu, const char *name, ret = drm_sched_init(&vm->sched, &args); if (ret) goto err_free_dummy; + + init_waitqueue_head(&vm->prealloc_throttle.wait); } drm_gpuvm_init(&vm->base, name, flags, drm, dummy_gem, @@ -1090,10 +1096,12 @@ ops_are_same_pte(struct msm_vm_bind_op *first, struct msm_vm_bind_op *next) * them as a single mapping. Otherwise the prealloc_count() will not realize * they can share pagetable pages and vastly overcount. */ -static void +static int vm_bind_prealloc_count(struct msm_vm_bind_job *job) { struct msm_vm_bind_op *first = NULL, *last = NULL; + struct msm_gem_vm *vm = to_msm_vm(job->vm); + int ret; for (int i = 0; i < job->nr_ops; i++) { struct msm_vm_bind_op *op = &job->ops[i]; @@ -1122,6 +1130,20 @@ vm_bind_prealloc_count(struct msm_vm_bind_job *job) /* Flush the remaining range: */ prealloc_count(job, first, last); + + /* + * Now that we know the needed amount to pre-alloc, throttle on pending + * VM_BIND jobs if we already have too much pre-alloc memory in flight + */ + ret = wait_event_interruptible( + vm->prealloc_throttle.wait, + atomic_read(&vm->prealloc_throttle.in_flight) <= 1024); + if (ret) + return ret; + + atomic_add(job->prealloc.count, &vm->prealloc_throttle.in_flight); + + return 0; } /* @@ -1412,7 +1434,9 @@ msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file) if (ret) goto out_unlock; - vm_bind_prealloc_count(job); + ret = vm_bind_prealloc_count(job); + if (ret) + goto out_unlock; struct drm_exec exec; unsigned flags = DRM_EXEC_IGNORE_DUPLICATES | DRM_EXEC_INTERRUPTIBLE_WAIT; -- 2.47.2