In preparation to disabling GPU functionality split VM_BIND-related
functions (which are used only for the GPU) from the rest of the GEM VMA
implementation.

Signed-off-by: Dmitry Baryshkov <dmitry.barysh...@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/Makefile          |    1 +
 drivers/gpu/drm/msm/msm_gem_vm_bind.c | 1116 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/msm_gem_vma.c     | 1177 +--------------------------------
 drivers/gpu/drm/msm/msm_gem_vma.h     |  105 +++
 4 files changed, 1225 insertions(+), 1174 deletions(-)

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 
0c0dfb25f01b193b10946fae20138caf32cf0ed2..d7876c154b0aa2cb0164c4b1fb7900b1a42db46b
 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -115,6 +115,7 @@ msm-y += \
        msm_gem_shrinker.o \
        msm_gem_submit.o \
        msm_gem_vma.o \
+       msm_gem_vm_bind.o \
        msm_gpu.o \
        msm_gpu_devfreq.o \
        msm_io_utils.o \
diff --git a/drivers/gpu/drm/msm/msm_gem_vm_bind.c 
b/drivers/gpu/drm/msm/msm_gem_vm_bind.c
new file mode 100644
index 
0000000000000000000000000000000000000000..683a5307a609ae7f5c366b4e0ddcdd98039ddea1
--- /dev/null
+++ b/drivers/gpu/drm/msm/msm_gem_vm_bind.c
@@ -0,0 +1,1116 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2016 Red Hat
+ * Author: Rob Clark <robdcl...@gmail.com>
+ */
+
+#include <drm/drm_file.h>
+#include <drm/msm_drm.h>
+
+#include <linux/file.h>
+#include <linux/sync_file.h>
+
+#include "msm_drv.h"
+#include "msm_gem.h"
+#include "msm_gem_vma.h"
+#include "msm_gpu.h"
+#include "msm_mmu.h"
+#include "msm_syncobj.h"
+
+/**
+ * struct msm_vma_op - A MAP or UNMAP operation
+ */
+struct msm_vm_op {
+       /** @op: The operation type */
+       enum {
+               MSM_VM_OP_MAP = 1,
+               MSM_VM_OP_UNMAP,
+       } op;
+       union {
+               /** @map: Parameters used if op == MSM_VMA_OP_MAP */
+               struct msm_vm_map_op map;
+               /** @unmap: Parameters used if op == MSM_VMA_OP_UNMAP */
+               struct msm_vm_unmap_op unmap;
+       };
+       /** @node: list head in msm_vm_bind_job::vm_ops */
+       struct list_head node;
+
+       /**
+        * @obj: backing object for pages to be mapped/unmapped
+        *
+        * Async unmap ops, in particular, must hold a reference to the
+        * original GEM object backing the mapping that will be unmapped.
+        * But the same can be required in the map path, for example if
+        * there is not a corresponding unmap op, such as process exit.
+        *
+        * This ensures that the pages backing the mapping are not freed
+        * before the mapping is torn down.
+        */
+       struct drm_gem_object *obj;
+};
+
+/**
+ * struct msm_vm_bind_job - Tracking for a VM_BIND ioctl
+ *
+ * A table of userspace requested VM updates 
(MSM_VM_BIND_OP_UNMAP/MAP/MAP_NULL)
+ * gets applied to the vm, generating a list of VM ops (MSM_VM_OP_MAP/UNMAP)
+ * which are applied to the pgtables asynchronously.  For example a userspace
+ * requested MSM_VM_BIND_OP_MAP could end up generating both an MSM_VM_OP_UNMAP
+ * to unmap an existing mapping, and a MSM_VM_OP_MAP to apply the new mapping.
+ */
+struct msm_vm_bind_job {
+       /** @base: base class for drm_sched jobs */
+       struct drm_sched_job base;
+       /** @fence: The fence that is signaled when job completes */
+       struct dma_fence *fence;
+       /** @vm: The VM being operated on */
+       struct drm_gpuvm *vm;
+       /** @queue: The queue that the job runs on */
+       struct msm_gpu_submitqueue *queue;
+       /** @prealloc: Tracking for pre-allocated MMU pgtable pages */
+       struct msm_mmu_prealloc prealloc;
+       /** @vm_ops: a list of struct msm_vm_op */
+       struct list_head vm_ops;
+       /** @bos_pinned: are the GEM objects being bound pinned? */
+       bool bos_pinned;
+       /** @nr_ops: the number of userspace requested ops */
+       unsigned int nr_ops;
+       /**
+        * @ops: the userspace requested ops
+        *
+        * The userspace requested ops are copied/parsed and validated
+        * before we start applying the updates to try to do as much up-
+        * front error checking as possible, to avoid the VM being in an
+        * undefined state due to partially executed VM_BIND.
+        *
+        * This table also serves to hold a reference to the backing GEM
+        * objects.
+        */
+       struct msm_vm_bind_op {
+               uint32_t op;
+               uint32_t flags;
+               union {
+                       struct drm_gem_object *obj;
+                       uint32_t handle;
+               };
+               uint64_t obj_offset;
+               uint64_t iova;
+               uint64_t range;
+       } ops[];
+};
+
+#define job_foreach_bo(_obj, _job) \
+       for (unsigned int i = 0; i < (_job)->nr_ops; i++) \
+               if (((_obj) = (_job)->ops[i].obj))
+
+static inline struct msm_vm_bind_job *to_msm_vm_bind_job(struct drm_sched_job 
*job)
+{
+       return container_of(job, struct msm_vm_bind_job, base);
+}
+
+struct op_arg {
+       unsigned int flags;
+       struct msm_vm_bind_job *job;
+       const struct msm_vm_bind_op *op;
+       bool kept;
+};
+
+static void
+vm_op_enqueue(struct op_arg *arg, struct msm_vm_op _op)
+{
+       struct msm_vm_op *op = kmalloc(sizeof(*op), GFP_KERNEL);
+       *op = _op;
+       list_add_tail(&op->node, &arg->job->vm_ops);
+
+       if (op->obj)
+               drm_gem_object_get(op->obj);
+}
+
+static struct drm_gpuva *
+vma_from_op(struct op_arg *arg, struct drm_gpuva_op_map *op)
+{
+       return msm_gem_vma_new(arg->job->vm, op->gem.obj, op->gem.offset,
+                              op->va.addr, op->va.addr + op->va.range);
+}
+
+int msm_gem_vm_sm_step_map(struct drm_gpuva_op *op, void *_arg)
+{
+       struct op_arg *arg = _arg;
+       struct drm_gem_object *obj = op->map.gem.obj;
+       struct drm_gpuva *vma;
+       struct sg_table *sgt;
+       unsigned int prot;
+
+       if (arg->kept)
+               return 0;
+
+       vma = vma_from_op(arg, &op->map);
+       if (WARN_ON(IS_ERR(vma)))
+               return PTR_ERR(vma);
+
+       vm_dbg("%p:%p:%p: %016llx %016llx", vma->vm, vma, vma->gem.obj,
+              vma->va.addr, vma->va.range);
+
+       vma->flags = ((struct op_arg *)arg)->flags;
+
+       if (obj) {
+               sgt = to_msm_bo(obj)->sgt;
+               prot = msm_gem_prot(obj);
+       } else {
+               sgt = NULL;
+               prot = IOMMU_READ | IOMMU_WRITE;
+       }
+
+       vm_op_enqueue(arg, (struct msm_vm_op){
+               .op = MSM_VM_OP_MAP,
+               .map = {
+                       .sgt = sgt,
+                       .iova = vma->va.addr,
+                       .range = vma->va.range,
+                       .offset = vma->gem.offset,
+                       .prot = prot,
+                       .queue_id = arg->job->queue->id,
+               },
+               .obj = vma->gem.obj,
+       });
+
+       to_msm_vma(vma)->mapped = true;
+
+       return 0;
+}
+
+int msm_gem_vm_sm_step_remap(struct drm_gpuva_op *op, void *arg)
+{
+       struct msm_vm_bind_job *job = ((struct op_arg *)arg)->job;
+       struct drm_gpuvm *vm = job->vm;
+       struct drm_gpuva *orig_vma = op->remap.unmap->va;
+       struct drm_gpuva *prev_vma = NULL, *next_vma = NULL;
+       struct drm_gpuvm_bo *vm_bo = orig_vma->vm_bo;
+       bool mapped = to_msm_vma(orig_vma)->mapped;
+       unsigned int flags;
+
+       vm_dbg("orig_vma: %p:%p:%p: %016llx %016llx", vm, orig_vma,
+              orig_vma->gem.obj, orig_vma->va.addr, orig_vma->va.range);
+
+       if (mapped) {
+               uint64_t unmap_start, unmap_range;
+
+               drm_gpuva_op_remap_to_unmap_range(&op->remap, &unmap_start, 
&unmap_range);
+
+               vm_op_enqueue(arg, (struct msm_vm_op){
+                       .op = MSM_VM_OP_UNMAP,
+                       .unmap = {
+                               .iova = unmap_start,
+                               .range = unmap_range,
+                               .queue_id = job->queue->id,
+                       },
+                       .obj = orig_vma->gem.obj,
+               });
+
+               /*
+                * Part of this GEM obj is still mapped, but we're going to 
kill the
+                * existing VMA and replace it with one or two new ones (ie. 
two if
+                * the unmapped range is in the middle of the existing (unmap) 
VMA).
+                * So just set the state to unmapped:
+                */
+               to_msm_vma(orig_vma)->mapped = false;
+       }
+
+       /*
+        * Hold a ref to the vm_bo between the msm_gem_vma_close() and the
+        * creation of the new prev/next vma's, in case the vm_bo is tracked
+        * in the VM's evict list:
+        */
+       if (vm_bo)
+               drm_gpuvm_bo_get(vm_bo);
+
+       /*
+        * The prev_vma and/or next_vma are replacing the unmapped vma, and
+        * therefore should preserve it's flags:
+        */
+       flags = orig_vma->flags;
+
+       msm_gem_vma_close(orig_vma);
+
+       if (op->remap.prev) {
+               prev_vma = vma_from_op(arg, op->remap.prev);
+               if (WARN_ON(IS_ERR(prev_vma)))
+                       return PTR_ERR(prev_vma);
+
+               vm_dbg("prev_vma: %p:%p: %016llx %016llx", vm, prev_vma,
+                      prev_vma->va.addr, prev_vma->va.range);
+               to_msm_vma(prev_vma)->mapped = mapped;
+               prev_vma->flags = flags;
+       }
+
+       if (op->remap.next) {
+               next_vma = vma_from_op(arg, op->remap.next);
+               if (WARN_ON(IS_ERR(next_vma)))
+                       return PTR_ERR(next_vma);
+
+               vm_dbg("next_vma: %p:%p: %016llx %016llx", vm, next_vma,
+                      next_vma->va.addr, next_vma->va.range);
+               to_msm_vma(next_vma)->mapped = mapped;
+               next_vma->flags = flags;
+       }
+
+       if (!mapped)
+               drm_gpuvm_bo_evict(vm_bo, true);
+
+       /* Drop the previous ref: */
+       drm_gpuvm_bo_put(vm_bo);
+
+       return 0;
+}
+
+int msm_gem_vm_sm_step_unmap(struct drm_gpuva_op *op, void *_arg)
+{
+       struct op_arg *arg = _arg;
+       struct drm_gpuva *vma = op->unmap.va;
+       struct msm_gem_vma *msm_vma = to_msm_vma(vma);
+
+       vm_dbg("%p:%p:%p: %016llx %016llx", vma->vm, vma, vma->gem.obj,
+              vma->va.addr, vma->va.range);
+
+       /*
+        * Detect in-place remap.  Turnip does this to change the vma flags,
+        * in particular MSM_VMA_DUMP.  In this case we want to avoid actually
+        * touching the page tables, as that would require synchronization
+        * against SUBMIT jobs running on the GPU.
+        */
+       if (op->unmap.keep &&
+           (arg->op->op == MSM_VM_BIND_OP_MAP) &&
+           (vma->gem.obj == arg->op->obj) &&
+           (vma->gem.offset == arg->op->obj_offset) &&
+           (vma->va.addr == arg->op->iova) &&
+           (vma->va.range == arg->op->range)) {
+               /* We are only expecting a single in-place unmap+map cb pair: */
+               WARN_ON(arg->kept);
+
+               /* Leave the existing VMA in place, but signal that to the map 
cb: */
+               arg->kept = true;
+
+               /* Only flags are changing, so update that in-place: */
+               unsigned int orig_flags = vma->flags & (DRM_GPUVA_USERBITS - 1);
+
+               vma->flags = orig_flags | arg->flags;
+
+               return 0;
+       }
+
+       if (!msm_vma->mapped)
+               goto out_close;
+
+       vm_op_enqueue(arg, (struct msm_vm_op){
+               .op = MSM_VM_OP_UNMAP,
+               .unmap = {
+                       .iova = vma->va.addr,
+                       .range = vma->va.range,
+                       .queue_id = arg->job->queue->id,
+               },
+               .obj = vma->gem.obj,
+       });
+
+       msm_vma->mapped = false;
+
+out_close:
+       msm_gem_vma_close(vma);
+
+       return 0;
+}
+
+static struct dma_fence *
+msm_vma_job_run(struct drm_sched_job *_job)
+{
+       struct msm_vm_bind_job *job = to_msm_vm_bind_job(_job);
+       struct msm_gem_vm *vm = to_msm_vm(job->vm);
+       struct drm_gem_object *obj;
+       int ret = vm->unusable ? -EINVAL : 0;
+
+       vm_dbg("");
+
+       mutex_lock(&vm->mmu_lock);
+       vm->mmu->prealloc = &job->prealloc;
+
+       while (!list_empty(&job->vm_ops)) {
+               struct msm_vm_op *op =
+                       list_first_entry(&job->vm_ops, struct msm_vm_op, node);
+
+               switch (op->op) {
+               case MSM_VM_OP_MAP:
+                       /*
+                        * On error, stop trying to map new things.. but we
+                        * still want to process the unmaps (or in particular,
+                        * the drm_gem_object_put()s)
+                        */
+                       if (!ret)
+                               ret = vm_map_op(vm, &op->map);
+                       break;
+               case MSM_VM_OP_UNMAP:
+                       vm_unmap_op(vm, &op->unmap);
+                       break;
+               }
+               drm_gem_object_put(op->obj);
+               list_del(&op->node);
+               kfree(op);
+       }
+
+       vm->mmu->prealloc = NULL;
+       mutex_unlock(&vm->mmu_lock);
+
+       /*
+        * We failed to perform at least _some_ of the pgtable updates, so
+        * now the VM is in an undefined state.  Game over!
+        */
+       if (ret)
+               msm_gem_vm_unusable(job->vm);
+
+       job_foreach_bo(obj, job) {
+               msm_gem_lock(obj);
+               msm_gem_unpin_locked(obj);
+               msm_gem_unlock(obj);
+       }
+
+       /* VM_BIND ops are synchronous, so no fence to wait on: */
+       return NULL;
+}
+
+static void
+msm_vma_job_free(struct drm_sched_job *_job)
+{
+       struct msm_vm_bind_job *job = to_msm_vm_bind_job(_job);
+       struct msm_gem_vm *vm = to_msm_vm(job->vm);
+       struct drm_gem_object *obj;
+
+       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)
+               drm_gem_object_put(obj);
+
+       msm_submitqueue_put(job->queue);
+       dma_fence_put(job->fence);
+
+       /* In error paths, we could have unexecuted ops: */
+       while (!list_empty(&job->vm_ops)) {
+               struct msm_vm_op *op =
+                       list_first_entry(&job->vm_ops, struct msm_vm_op, node);
+               list_del(&op->node);
+               kfree(op);
+       }
+
+       wake_up(&vm->prealloc_throttle.wait);
+
+       kfree(job);
+}
+
+static const struct drm_sched_backend_ops msm_vm_bind_ops = {
+       .run_job = msm_vma_job_run,
+       .free_job = msm_vma_job_free
+};
+
+int msm_gem_vm_sched_init(struct msm_gem_vm *vm, struct drm_device *drm)
+{
+       struct drm_sched_init_args args = {
+               .ops = &msm_vm_bind_ops,
+               .num_rqs = 1,
+               .credit_limit = 1,
+               .timeout = MAX_SCHEDULE_TIMEOUT,
+               .name = "msm-vm-bind",
+               .dev = drm->dev,
+       };
+       int ret;
+
+       ret = drm_sched_init(&vm->sched, &args);
+       if (ret)
+               return ret;
+
+       init_waitqueue_head(&vm->prealloc_throttle.wait);
+
+       return 0;
+}
+
+void msm_gem_vm_sched_fini(struct msm_gem_vm *vm)
+{
+       /* Kill the scheduler now, so we aren't racing with it for cleanup: */
+       drm_sched_stop(&vm->sched, NULL);
+       drm_sched_fini(&vm->sched);
+}
+
+static struct msm_vm_bind_job *
+vm_bind_job_create(struct drm_device *dev, struct drm_file *file,
+                  struct msm_gpu_submitqueue *queue, uint32_t nr_ops)
+{
+       struct msm_vm_bind_job *job;
+       uint64_t sz;
+       int ret;
+
+       sz = struct_size(job, ops, nr_ops);
+
+       if (sz > SIZE_MAX)
+               return ERR_PTR(-ENOMEM);
+
+       job = kzalloc(sz, GFP_KERNEL | __GFP_NOWARN);
+       if (!job)
+               return ERR_PTR(-ENOMEM);
+
+       ret = drm_sched_job_init(&job->base, queue->entity, 1, queue,
+                                file->client_id);
+       if (ret) {
+               kfree(job);
+               return ERR_PTR(ret);
+       }
+
+       job->vm = msm_context_vm(dev, queue->ctx);
+       job->queue = queue;
+       INIT_LIST_HEAD(&job->vm_ops);
+
+       return job;
+}
+
+static bool invalid_alignment(uint64_t addr)
+{
+       /*
+        * Technically this is about GPU alignment, not CPU alignment.  But
+        * I've not seen any qcom SoC where the SMMU does not support the
+        * CPU's smallest page size.
+        */
+       return !PAGE_ALIGNED(addr);
+}
+
+static int
+lookup_op(struct msm_vm_bind_job *job, const struct drm_msm_vm_bind_op *op)
+{
+       struct drm_device *dev = job->vm->drm;
+       int i = job->nr_ops++;
+       int ret = 0;
+
+       job->ops[i].op = op->op;
+       job->ops[i].handle = op->handle;
+       job->ops[i].obj_offset = op->obj_offset;
+       job->ops[i].iova = op->iova;
+       job->ops[i].range = op->range;
+       job->ops[i].flags = op->flags;
+
+       if (op->flags & ~MSM_VM_BIND_OP_FLAGS)
+               ret = UERR(EINVAL, dev, "invalid flags: %x\n", op->flags);
+
+       if (invalid_alignment(op->iova))
+               ret = UERR(EINVAL, dev, "invalid address: %016llx\n", op->iova);
+
+       if (invalid_alignment(op->obj_offset))
+               ret = UERR(EINVAL, dev, "invalid bo_offset: %016llx\n", 
op->obj_offset);
+
+       if (invalid_alignment(op->range))
+               ret = UERR(EINVAL, dev, "invalid range: %016llx\n", op->range);
+
+       if (!drm_gpuvm_range_valid(job->vm, op->iova, op->range))
+               ret = UERR(EINVAL, dev, "invalid range: %016llx, %016llx\n", 
op->iova, op->range);
+
+       /*
+        * MAP must specify a valid handle.  But the handle MBZ for
+        * UNMAP or MAP_NULL.
+        */
+       if (op->op == MSM_VM_BIND_OP_MAP) {
+               if (!op->handle)
+                       ret = UERR(EINVAL, dev, "invalid handle\n");
+       } else if (op->handle) {
+               ret = UERR(EINVAL, dev, "handle must be zero\n");
+       }
+
+       switch (op->op) {
+       case MSM_VM_BIND_OP_MAP:
+       case MSM_VM_BIND_OP_MAP_NULL:
+       case MSM_VM_BIND_OP_UNMAP:
+               break;
+       default:
+               ret = UERR(EINVAL, dev, "invalid op: %u\n", op->op);
+               break;
+       }
+
+       return ret;
+}
+
+/*
+ * ioctl parsing, parameter validation, and GEM handle lookup
+ */
+static int
+vm_bind_job_lookup_ops(struct msm_vm_bind_job *job, struct drm_msm_vm_bind 
*args,
+                      struct drm_file *file, int *nr_bos)
+{
+       struct drm_device *dev = job->vm->drm;
+       int ret = 0;
+       int cnt = 0;
+       int i = -1;
+
+       if (args->nr_ops == 1) {
+               /* Single op case, the op is inlined: */
+               ret = lookup_op(job, &args->op);
+       } else {
+               for (unsigned int i = 0; i < args->nr_ops; i++) {
+                       struct drm_msm_vm_bind_op op;
+                       void __user *userptr =
+                               u64_to_user_ptr(args->ops + (i * sizeof(op)));
+
+                       /* make sure we don't have garbage flags, in case we hit
+                        * error path before flags is initialized:
+                        */
+                       job->ops[i].flags = 0;
+
+                       if (copy_from_user(&op, userptr, sizeof(op))) {
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = lookup_op(job, &op);
+                       if (ret)
+                               break;
+               }
+       }
+
+       if (ret) {
+               job->nr_ops = 0;
+               goto out;
+       }
+
+       spin_lock(&file->table_lock);
+
+       for (i = 0; i < args->nr_ops; i++) {
+               struct msm_vm_bind_op *op = &job->ops[i];
+               struct drm_gem_object *obj;
+
+               if (!op->handle) {
+                       op->obj = NULL;
+                       continue;
+               }
+
+               /*
+                * normally use drm_gem_object_lookup(), but for bulk lookup
+                * all under single table_lock just hit object_idr directly:
+                */
+               obj = idr_find(&file->object_idr, op->handle);
+               if (!obj) {
+                       ret = UERR(EINVAL, dev, "invalid handle %u at index 
%u\n", op->handle, i);
+                       goto out_unlock;
+               }
+
+               drm_gem_object_get(obj);
+
+               op->obj = obj;
+               cnt++;
+
+               if ((op->range + op->obj_offset) > obj->size) {
+                       ret = UERR(EINVAL, dev, "invalid range: %016llx + 
%016llx > %016zx\n",
+                                  op->range, op->obj_offset, obj->size);
+                       goto out_unlock;
+               }
+       }
+
+       *nr_bos = cnt;
+
+out_unlock:
+       spin_unlock(&file->table_lock);
+
+       if (ret) {
+               for (; i >= 0; i--) {
+                       struct msm_vm_bind_op *op = &job->ops[i];
+
+                       if (!op->obj)
+                               continue;
+
+                       drm_gem_object_put(op->obj);
+                       op->obj = NULL;
+               }
+       }
+out:
+       return ret;
+}
+
+static void
+prealloc_count(struct msm_vm_bind_job *job,
+              struct msm_vm_bind_op *first,
+              struct msm_vm_bind_op *last)
+{
+       struct msm_mmu *mmu = to_msm_vm(job->vm)->mmu;
+
+       if (!first)
+               return;
+
+       uint64_t start_iova = first->iova;
+       uint64_t end_iova = last->iova + last->range;
+
+       mmu->funcs->prealloc_count(mmu, &job->prealloc, start_iova, end_iova - 
start_iova);
+}
+
+static bool
+ops_are_same_pte(struct msm_vm_bind_op *first, struct msm_vm_bind_op *next)
+{
+       /*
+        * Last level pte covers 2MB.. so we should merge two ops, from
+        * the PoV of figuring out how much pgtable pages to pre-allocate
+        * if they land in the same 2MB range:
+        */
+       uint64_t pte_mask = ~(SZ_2M - 1);
+
+       return ((first->iova + first->range) & pte_mask) == (next->iova & 
pte_mask);
+}
+
+/*
+ * Determine the amount of memory to prealloc for pgtables.  For sparse images,
+ * in particular, userspace plays some tricks with the order of page mappings
+ * to get the desired swizzle pattern, resulting in a large # of tiny MAP ops.
+ * So detect when multiple MAP operations are physically contiguous, and count
+ * them as a single mapping.  Otherwise the prealloc_count() will not realize
+ * they can share pagetable pages and vastly overcount.
+ */
+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];
+
+               /* We only care about MAP/MAP_NULL: */
+               if (op->op == MSM_VM_BIND_OP_UNMAP)
+                       continue;
+
+               /*
+                * If op is contiguous with last in the current range, then
+                * it becomes the new last in the range and we continue
+                * looping:
+                */
+               if (last && ops_are_same_pte(last, op)) {
+                       last = op;
+                       continue;
+               }
+
+               /*
+                * If op is not contiguous with the current range, flush
+                * the current range and start anew:
+                */
+               prealloc_count(job, first, last);
+               first = last = op;
+       }
+
+       /* 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;
+}
+
+/*
+ * Lock VM and GEM objects
+ */
+static int
+vm_bind_job_lock_objects(struct msm_vm_bind_job *job, struct drm_exec *exec)
+{
+       int ret;
+
+       /* Lock VM and objects: */
+       drm_exec_until_all_locked(exec) {
+               ret = drm_exec_lock_obj(exec, drm_gpuvm_resv_obj(job->vm));
+               drm_exec_retry_on_contention(exec);
+               if (ret)
+                       return ret;
+
+               for (unsigned int i = 0; i < job->nr_ops; i++) {
+                       const struct msm_vm_bind_op *op = &job->ops[i];
+
+                       switch (op->op) {
+                       case MSM_VM_BIND_OP_UNMAP:
+                               ret = drm_gpuvm_sm_unmap_exec_lock(job->vm, 
exec,
+                                                             op->iova,
+                                                             op->obj_offset);
+                               break;
+                       case MSM_VM_BIND_OP_MAP:
+                       case MSM_VM_BIND_OP_MAP_NULL: {
+                               struct drm_gpuvm_map_req map_req = {
+                                       .map.va.addr = op->iova,
+                                       .map.va.range = op->range,
+                                       .map.gem.obj = op->obj,
+                                       .map.gem.offset = op->obj_offset,
+                               };
+
+                               ret = drm_gpuvm_sm_map_exec_lock(job->vm, exec, 
1, &map_req);
+                               break;
+                       }
+                       default:
+                               /*
+                                * lookup_op() should have already thrown an 
error for
+                                * invalid ops
+                                */
+                               WARN_ON("unreachable");
+                       }
+
+                       drm_exec_retry_on_contention(exec);
+                       if (ret)
+                               return ret;
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * Pin GEM objects, ensuring that we have backing pages.  Pinning will move
+ * the object to the pinned LRU so that the shrinker knows to first consider
+ * other objects for evicting.
+ */
+static int
+vm_bind_job_pin_objects(struct msm_vm_bind_job *job)
+{
+       struct drm_gem_object *obj;
+
+       /*
+        * First loop, before holding the LRU lock, avoids holding the
+        * LRU lock while calling msm_gem_pin_vma_locked (which could
+        * trigger get_pages())
+        */
+       job_foreach_bo(obj, job) {
+               struct page **pages;
+
+               pages = msm_gem_get_pages_locked(obj, MSM_MADV_WILLNEED);
+               if (IS_ERR(pages))
+                       return PTR_ERR(pages);
+       }
+
+       struct msm_drm_private *priv = job->vm->drm->dev_private;
+
+       /*
+        * A second loop while holding the LRU lock (a) avoids 
acquiring/dropping
+        * the LRU lock for each individual bo, while (b) avoiding holding the
+        * LRU lock while calling msm_gem_pin_vma_locked() (which could trigger
+        * get_pages() which could trigger reclaim.. and if we held the LRU lock
+        * could trigger deadlock with the shrinker).
+        */
+       mutex_lock(&priv->lru.lock);
+       job_foreach_bo(obj, job)
+               msm_gem_pin_obj_locked(obj);
+       mutex_unlock(&priv->lru.lock);
+
+       job->bos_pinned = true;
+
+       return 0;
+}
+
+/*
+ * Unpin GEM objects.  Normally this is done after the bind job is run.
+ */
+static void
+vm_bind_job_unpin_objects(struct msm_vm_bind_job *job)
+{
+       struct drm_gem_object *obj;
+
+       if (!job->bos_pinned)
+               return;
+
+       job_foreach_bo(obj, job)
+               msm_gem_unpin_locked(obj);
+
+       job->bos_pinned = false;
+}
+
+/*
+ * Pre-allocate pgtable memory, and translate the VM bind requests into a
+ * sequence of pgtable updates to be applied asynchronously.
+ */
+static int
+vm_bind_job_prepare(struct msm_vm_bind_job *job)
+{
+       struct msm_gem_vm *vm = to_msm_vm(job->vm);
+       struct msm_mmu *mmu = vm->mmu;
+       int ret;
+
+       ret = mmu->funcs->prealloc_allocate(mmu, &job->prealloc);
+       if (ret)
+               return ret;
+
+       for (unsigned int i = 0; i < job->nr_ops; i++) {
+               const struct msm_vm_bind_op *op = &job->ops[i];
+               struct op_arg arg = {
+                       .job = job,
+                       .op = op,
+               };
+
+               switch (op->op) {
+               case MSM_VM_BIND_OP_UNMAP:
+                       ret = drm_gpuvm_sm_unmap(job->vm, &arg, op->iova,
+                                                op->range);
+                       break;
+               case MSM_VM_BIND_OP_MAP:
+                       if (op->flags & MSM_VM_BIND_OP_DUMP)
+                               arg.flags |= MSM_VMA_DUMP;
+                       fallthrough;
+               case MSM_VM_BIND_OP_MAP_NULL: {
+                       struct drm_gpuvm_map_req map_req = {
+                               .map.va.addr = op->iova,
+                               .map.va.range = op->range,
+                               .map.gem.obj = op->obj,
+                               .map.gem.offset = op->obj_offset,
+                       };
+
+                       ret = drm_gpuvm_sm_map(job->vm, &arg, &map_req);
+                       break;
+               }
+               default:
+                       /*
+                        * lookup_op() should have already thrown an error for
+                        * invalid ops
+                        */
+                       BUG_ON("unreachable");
+               }
+
+               if (ret) {
+                       /*
+                        * If we've already started modifying the vm, we can't
+                        * adequetly describe to userspace the intermediate
+                        * state the vm is in.  So throw up our hands!
+                        */
+                       if (i > 0)
+                               msm_gem_vm_unusable(job->vm);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * Attach fences to the GEM objects being bound.  This will signify to
+ * the shrinker that they are busy even after dropping the locks (ie.
+ * drm_exec_fini())
+ */
+static void
+vm_bind_job_attach_fences(struct msm_vm_bind_job *job)
+{
+       for (unsigned int i = 0; i < job->nr_ops; i++) {
+               struct drm_gem_object *obj = job->ops[i].obj;
+
+               if (!obj)
+                       continue;
+
+               dma_resv_add_fence(obj->resv, job->fence,
+                                  DMA_RESV_USAGE_KERNEL);
+       }
+}
+
+int
+msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file)
+{
+       struct msm_drm_private *priv = dev->dev_private;
+       struct drm_msm_vm_bind *args = data;
+       struct msm_context *ctx = file->driver_priv;
+       struct msm_vm_bind_job *job = NULL;
+       struct msm_gpu *gpu = priv->gpu;
+       struct msm_gpu_submitqueue *queue;
+       struct msm_syncobj_post_dep *post_deps = NULL;
+       struct drm_syncobj **syncobjs_to_reset = NULL;
+       struct sync_file *sync_file = NULL;
+       struct dma_fence *fence;
+       int out_fence_fd = -1;
+       int ret, nr_bos = 0;
+       unsigned int i;
+
+       if (!gpu)
+               return -ENXIO;
+
+       /*
+        * Maybe we could allow just UNMAP ops?  OTOH userspace should just
+        * immediately close the device file and all will be torn down.
+        */
+       if (to_msm_vm(ctx->vm)->unusable)
+               return UERR(EPIPE, dev, "context is unusable");
+
+       /*
+        * Technically, you cannot create a VM_BIND submitqueue in the first
+        * place, if you haven't opted in to VM_BIND context.  But it is
+        * cleaner / less confusing, to check this case directly.
+        */
+       if (!msm_context_is_vmbind(ctx))
+               return UERR(EINVAL, dev, "context does not support vmbind");
+
+       if (args->flags & ~MSM_VM_BIND_FLAGS)
+               return UERR(EINVAL, dev, "invalid flags");
+
+       queue = msm_submitqueue_get(ctx, args->queue_id);
+       if (!queue)
+               return -ENOENT;
+
+       if (!(queue->flags & MSM_SUBMITQUEUE_VM_BIND)) {
+               ret = UERR(EINVAL, dev, "Invalid queue type");
+               goto out_post_unlock;
+       }
+
+       if (args->flags & MSM_VM_BIND_FENCE_FD_OUT) {
+               out_fence_fd = get_unused_fd_flags(O_CLOEXEC);
+               if (out_fence_fd < 0) {
+                       ret = out_fence_fd;
+                       goto out_post_unlock;
+               }
+       }
+
+       job = vm_bind_job_create(dev, file, queue, args->nr_ops);
+       if (IS_ERR(job)) {
+               ret = PTR_ERR(job);
+               goto out_post_unlock;
+       }
+
+       ret = mutex_lock_interruptible(&queue->lock);
+       if (ret)
+               goto out_post_unlock;
+
+       if (args->flags & MSM_VM_BIND_FENCE_FD_IN) {
+               struct dma_fence *in_fence;
+
+               in_fence = sync_file_get_fence(args->fence_fd);
+
+               if (!in_fence) {
+                       ret = UERR(EINVAL, dev, "invalid in-fence");
+                       goto out_unlock;
+               }
+
+               ret = drm_sched_job_add_dependency(&job->base, in_fence);
+               if (ret)
+                       goto out_unlock;
+       }
+
+       if (args->in_syncobjs > 0) {
+               syncobjs_to_reset = msm_syncobj_parse_deps(dev, &job->base,
+                                                          file, 
args->in_syncobjs,
+                                                          args->nr_in_syncobjs,
+                                                          
args->syncobj_stride);
+               if (IS_ERR(syncobjs_to_reset)) {
+                       ret = PTR_ERR(syncobjs_to_reset);
+                       goto out_unlock;
+               }
+       }
+
+       if (args->out_syncobjs > 0) {
+               post_deps = msm_syncobj_parse_post_deps(dev, file,
+                                                       args->out_syncobjs,
+                                                       args->nr_out_syncobjs,
+                                                       args->syncobj_stride);
+               if (IS_ERR(post_deps)) {
+                       ret = PTR_ERR(post_deps);
+                       goto out_unlock;
+               }
+       }
+
+       ret = vm_bind_job_lookup_ops(job, args, file, &nr_bos);
+       if (ret)
+               goto out_unlock;
+
+       ret = vm_bind_prealloc_count(job);
+       if (ret)
+               goto out_unlock;
+
+       struct drm_exec exec;
+       unsigned int flags = DRM_EXEC_IGNORE_DUPLICATES | 
DRM_EXEC_INTERRUPTIBLE_WAIT;
+
+       drm_exec_init(&exec, flags, nr_bos + 1);
+
+       ret = vm_bind_job_lock_objects(job, &exec);
+       if (ret)
+               goto out;
+
+       ret = vm_bind_job_pin_objects(job);
+       if (ret)
+               goto out;
+
+       ret = vm_bind_job_prepare(job);
+       if (ret)
+               goto out;
+
+       drm_sched_job_arm(&job->base);
+
+       job->fence = dma_fence_get(&job->base.s_fence->finished);
+
+       if (args->flags & MSM_VM_BIND_FENCE_FD_OUT) {
+               sync_file = sync_file_create(job->fence);
+               if (!sync_file)
+                       ret = -ENOMEM;
+       }
+
+       if (ret)
+               goto out;
+
+       vm_bind_job_attach_fences(job);
+
+       /*
+        * The job can be free'd (and fence unref'd) at any point after
+        * drm_sched_entity_push_job(), so we need to hold our own ref
+        */
+       fence = dma_fence_get(job->fence);
+
+       drm_sched_entity_push_job(&job->base);
+
+       msm_syncobj_reset(syncobjs_to_reset, args->nr_in_syncobjs);
+       msm_syncobj_process_post_deps(post_deps, args->nr_out_syncobjs, fence);
+
+       dma_fence_put(fence);
+
+out:
+       if (ret)
+               vm_bind_job_unpin_objects(job);
+
+       drm_exec_fini(&exec);
+out_unlock:
+       mutex_unlock(&queue->lock);
+out_post_unlock:
+       if (ret) {
+               if (out_fence_fd >= 0)
+                       put_unused_fd(out_fence_fd);
+               if (sync_file)
+                       fput(sync_file->file);
+       } else if (sync_file) {
+               fd_install(out_fence_fd, sync_file->file);
+               args->fence_fd = out_fence_fd;
+       }
+
+       if (!IS_ERR_OR_NULL(job)) {
+               if (ret)
+                       msm_vma_job_free(&job->base);
+       } else {
+               /*
+                * If the submit hasn't yet taken ownership of the queue
+                * then we need to drop the reference ourself:
+                */
+               msm_submitqueue_put(queue);
+       }
+
+       if (!IS_ERR_OR_NULL(post_deps)) {
+               for (i = 0; i < args->nr_out_syncobjs; ++i) {
+                       kfree(post_deps[i].chain);
+                       drm_syncobj_put(post_deps[i].syncobj);
+               }
+               kfree(post_deps);
+       }
+
+       if (!IS_ERR_OR_NULL(syncobjs_to_reset)) {
+               for (i = 0; i < args->nr_in_syncobjs; ++i) {
+                       if (syncobjs_to_reset[i])
+                               drm_syncobj_put(syncobjs_to_reset[i]);
+               }
+               kfree(syncobjs_to_reset);
+       }
+
+       return ret;
+}
diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c 
b/drivers/gpu/drm/msm/msm_gem_vma.c
index 
8316af1723c227f919594446c3721e1a948cbc9e..3f44d1d973137d99aa1a3d9e26739c34e1acc534
 100644
--- a/drivers/gpu/drm/msm/msm_gem_vma.c
+++ b/drivers/gpu/drm/msm/msm_gem_vma.c
@@ -11,150 +11,15 @@
 
 #include "msm_drv.h"
 #include "msm_gem.h"
+#include "msm_gem_vma.h"
 #include "msm_gpu.h"
 #include "msm_mmu.h"
 #include "msm_syncobj.h"
 
-#define vm_dbg(fmt, ...) pr_debug("%s:%d: "fmt"\n", __func__, __LINE__, 
##__VA_ARGS__)
-
 static uint vm_log_shift = 0;
 MODULE_PARM_DESC(vm_log_shift, "Length of VM op log");
 module_param_named(vm_log_shift, vm_log_shift, uint, 0600);
 
-/**
- * struct msm_vm_map_op - create new pgtable mapping
- */
-struct msm_vm_map_op {
-       /** @iova: start address for mapping */
-       uint64_t iova;
-       /** @range: size of the region to map */
-       uint64_t range;
-       /** @offset: offset into @sgt to map */
-       uint64_t offset;
-       /** @sgt: pages to map, or NULL for a PRR mapping */
-       struct sg_table *sgt;
-       /** @prot: the mapping protection flags */
-       int prot;
-
-       /**
-        * @queue_id: The id of the submitqueue the operation is performed
-        * on, or zero for (in particular) UNMAP ops triggered outside of
-        * a submitqueue (ie. process cleanup)
-        */
-       int queue_id;
-};
-
-/**
- * struct msm_vm_unmap_op - unmap a range of pages from pgtable
- */
-struct msm_vm_unmap_op {
-       /** @iova: start address for unmap */
-       uint64_t iova;
-       /** @range: size of region to unmap */
-       uint64_t range;
-
-       /** @reason: The reason for the unmap */
-       const char *reason;
-
-       /**
-        * @queue_id: The id of the submitqueue the operation is performed
-        * on, or zero for (in particular) UNMAP ops triggered outside of
-        * a submitqueue (ie. process cleanup)
-        */
-       int queue_id;
-};
-
-/**
- * struct msm_vma_op - A MAP or UNMAP operation
- */
-struct msm_vm_op {
-       /** @op: The operation type */
-       enum {
-               MSM_VM_OP_MAP = 1,
-               MSM_VM_OP_UNMAP,
-       } op;
-       union {
-               /** @map: Parameters used if op == MSM_VMA_OP_MAP */
-               struct msm_vm_map_op map;
-               /** @unmap: Parameters used if op == MSM_VMA_OP_UNMAP */
-               struct msm_vm_unmap_op unmap;
-       };
-       /** @node: list head in msm_vm_bind_job::vm_ops */
-       struct list_head node;
-
-       /**
-        * @obj: backing object for pages to be mapped/unmapped
-        *
-        * Async unmap ops, in particular, must hold a reference to the
-        * original GEM object backing the mapping that will be unmapped.
-        * But the same can be required in the map path, for example if
-        * there is not a corresponding unmap op, such as process exit.
-        *
-        * This ensures that the pages backing the mapping are not freed
-        * before the mapping is torn down.
-        */
-       struct drm_gem_object *obj;
-};
-
-/**
- * struct msm_vm_bind_job - Tracking for a VM_BIND ioctl
- *
- * A table of userspace requested VM updates 
(MSM_VM_BIND_OP_UNMAP/MAP/MAP_NULL)
- * gets applied to the vm, generating a list of VM ops (MSM_VM_OP_MAP/UNMAP)
- * which are applied to the pgtables asynchronously.  For example a userspace
- * requested MSM_VM_BIND_OP_MAP could end up generating both an MSM_VM_OP_UNMAP
- * to unmap an existing mapping, and a MSM_VM_OP_MAP to apply the new mapping.
- */
-struct msm_vm_bind_job {
-       /** @base: base class for drm_sched jobs */
-       struct drm_sched_job base;
-       /** @vm: The VM being operated on */
-       struct drm_gpuvm *vm;
-       /** @fence: The fence that is signaled when job completes */
-       struct dma_fence *fence;
-       /** @queue: The queue that the job runs on */
-       struct msm_gpu_submitqueue *queue;
-       /** @prealloc: Tracking for pre-allocated MMU pgtable pages */
-       struct msm_mmu_prealloc prealloc;
-       /** @vm_ops: a list of struct msm_vm_op */
-       struct list_head vm_ops;
-       /** @bos_pinned: are the GEM objects being bound pinned? */
-       bool bos_pinned;
-       /** @nr_ops: the number of userspace requested ops */
-       unsigned int nr_ops;
-       /**
-        * @ops: the userspace requested ops
-        *
-        * The userspace requested ops are copied/parsed and validated
-        * before we start applying the updates to try to do as much up-
-        * front error checking as possible, to avoid the VM being in an
-        * undefined state due to partially executed VM_BIND.
-        *
-        * This table also serves to hold a reference to the backing GEM
-        * objects.
-        */
-       struct msm_vm_bind_op {
-               uint32_t op;
-               uint32_t flags;
-               union {
-                       struct drm_gem_object *obj;
-                       uint32_t handle;
-               };
-               uint64_t obj_offset;
-               uint64_t iova;
-               uint64_t range;
-       } ops[];
-};
-
-#define job_foreach_bo(obj, _job) \
-       for (unsigned i = 0; i < (_job)->nr_ops; i++) \
-               if ((obj = (_job)->ops[i].obj))
-
-static inline struct msm_vm_bind_job *to_msm_vm_bind_job(struct drm_sched_job 
*job)
-{
-       return container_of(job, struct msm_vm_bind_job, base);
-}
-
 static void
 msm_gem_vm_free(struct drm_gpuvm *gpuvm)
 {
@@ -221,49 +86,6 @@ msm_gem_vm_unusable(struct drm_gpuvm *gpuvm)
        mutex_unlock(&vm->mmu_lock);
 }
 
-static void
-vm_log(struct msm_gem_vm *vm, const char *op, uint64_t iova, uint64_t range, 
int queue_id)
-{
-       int idx;
-
-       if (!vm->managed)
-               lockdep_assert_held(&vm->mmu_lock);
-
-       vm_dbg("%s:%p:%d: %016llx %016llx", op, vm, queue_id, iova, iova + 
range);
-
-       if (!vm->log)
-               return;
-
-       idx = vm->log_idx;
-       vm->log[idx].op = op;
-       vm->log[idx].iova = iova;
-       vm->log[idx].range = range;
-       vm->log[idx].queue_id = queue_id;
-       vm->log_idx = (vm->log_idx + 1) & ((1 << vm->log_shift) - 1);
-}
-
-static void
-vm_unmap_op(struct msm_gem_vm *vm, const struct msm_vm_unmap_op *op)
-{
-       const char *reason = op->reason;
-
-       if (!reason)
-               reason = "unmap";
-
-       vm_log(vm, reason, op->iova, op->range, op->queue_id);
-
-       vm->mmu->funcs->unmap(vm->mmu, op->iova, op->range);
-}
-
-static int
-vm_map_op(struct msm_gem_vm *vm, const struct msm_vm_map_op *op)
-{
-       vm_log(vm, "map", op->iova, op->range, op->queue_id);
-
-       return vm->mmu->funcs->map(vm->mmu, op->iova, op->sgt, op->offset,
-                                  op->range, op->prot);
-}
-
 /* Actually unmap memory for the vma */
 void msm_gem_vma_unmap(struct drm_gpuva *vma, const char *reason)
 {
@@ -455,219 +277,6 @@ msm_gem_vm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct 
drm_exec *exec)
        return 0;
 }
 
-struct op_arg {
-       unsigned flags;
-       struct msm_vm_bind_job *job;
-       const struct msm_vm_bind_op *op;
-       bool kept;
-};
-
-static void
-vm_op_enqueue(struct op_arg *arg, struct msm_vm_op _op)
-{
-       struct msm_vm_op *op = kmalloc(sizeof(*op), GFP_KERNEL);
-       *op = _op;
-       list_add_tail(&op->node, &arg->job->vm_ops);
-
-       if (op->obj)
-               drm_gem_object_get(op->obj);
-}
-
-static struct drm_gpuva *
-vma_from_op(struct op_arg *arg, struct drm_gpuva_op_map *op)
-{
-       return msm_gem_vma_new(arg->job->vm, op->gem.obj, op->gem.offset,
-                              op->va.addr, op->va.addr + op->va.range);
-}
-
-static int
-msm_gem_vm_sm_step_map(struct drm_gpuva_op *op, void *_arg)
-{
-       struct op_arg *arg = _arg;
-       struct msm_vm_bind_job *job = arg->job;
-       struct drm_gem_object *obj = op->map.gem.obj;
-       struct drm_gpuva *vma;
-       struct sg_table *sgt;
-       unsigned prot;
-
-       if (arg->kept)
-               return 0;
-
-       vma = vma_from_op(arg, &op->map);
-       if (WARN_ON(IS_ERR(vma)))
-               return PTR_ERR(vma);
-
-       vm_dbg("%p:%p:%p: %016llx %016llx", vma->vm, vma, vma->gem.obj,
-              vma->va.addr, vma->va.range);
-
-       vma->flags = ((struct op_arg *)arg)->flags;
-
-       if (obj) {
-               sgt = to_msm_bo(obj)->sgt;
-               prot = msm_gem_prot(obj);
-       } else {
-               sgt = NULL;
-               prot = IOMMU_READ | IOMMU_WRITE;
-       }
-
-       vm_op_enqueue(arg, (struct msm_vm_op){
-               .op = MSM_VM_OP_MAP,
-               .map = {
-                       .sgt = sgt,
-                       .iova = vma->va.addr,
-                       .range = vma->va.range,
-                       .offset = vma->gem.offset,
-                       .prot = prot,
-                       .queue_id = job->queue->id,
-               },
-               .obj = vma->gem.obj,
-       });
-
-       to_msm_vma(vma)->mapped = true;
-
-       return 0;
-}
-
-static int
-msm_gem_vm_sm_step_remap(struct drm_gpuva_op *op, void *arg)
-{
-       struct msm_vm_bind_job *job = ((struct op_arg *)arg)->job;
-       struct drm_gpuvm *vm = job->vm;
-       struct drm_gpuva *orig_vma = op->remap.unmap->va;
-       struct drm_gpuva *prev_vma = NULL, *next_vma = NULL;
-       struct drm_gpuvm_bo *vm_bo = orig_vma->vm_bo;
-       bool mapped = to_msm_vma(orig_vma)->mapped;
-       unsigned flags;
-
-       vm_dbg("orig_vma: %p:%p:%p: %016llx %016llx", vm, orig_vma,
-              orig_vma->gem.obj, orig_vma->va.addr, orig_vma->va.range);
-
-       if (mapped) {
-               uint64_t unmap_start, unmap_range;
-
-               drm_gpuva_op_remap_to_unmap_range(&op->remap, &unmap_start, 
&unmap_range);
-
-               vm_op_enqueue(arg, (struct msm_vm_op){
-                       .op = MSM_VM_OP_UNMAP,
-                       .unmap = {
-                               .iova = unmap_start,
-                               .range = unmap_range,
-                               .queue_id = job->queue->id,
-                       },
-                       .obj = orig_vma->gem.obj,
-               });
-
-               /*
-                * Part of this GEM obj is still mapped, but we're going to 
kill the
-                * existing VMA and replace it with one or two new ones (ie. 
two if
-                * the unmapped range is in the middle of the existing (unmap) 
VMA).
-                * So just set the state to unmapped:
-                */
-               to_msm_vma(orig_vma)->mapped = false;
-       }
-
-       /*
-        * Hold a ref to the vm_bo between the msm_gem_vma_close() and the
-        * creation of the new prev/next vma's, in case the vm_bo is tracked
-        * in the VM's evict list:
-        */
-       if (vm_bo)
-               drm_gpuvm_bo_get(vm_bo);
-
-       /*
-        * The prev_vma and/or next_vma are replacing the unmapped vma, and
-        * therefore should preserve it's flags:
-        */
-       flags = orig_vma->flags;
-
-       msm_gem_vma_close(orig_vma);
-
-       if (op->remap.prev) {
-               prev_vma = vma_from_op(arg, op->remap.prev);
-               if (WARN_ON(IS_ERR(prev_vma)))
-                       return PTR_ERR(prev_vma);
-
-               vm_dbg("prev_vma: %p:%p: %016llx %016llx", vm, prev_vma, 
prev_vma->va.addr, prev_vma->va.range);
-               to_msm_vma(prev_vma)->mapped = mapped;
-               prev_vma->flags = flags;
-       }
-
-       if (op->remap.next) {
-               next_vma = vma_from_op(arg, op->remap.next);
-               if (WARN_ON(IS_ERR(next_vma)))
-                       return PTR_ERR(next_vma);
-
-               vm_dbg("next_vma: %p:%p: %016llx %016llx", vm, next_vma, 
next_vma->va.addr, next_vma->va.range);
-               to_msm_vma(next_vma)->mapped = mapped;
-               next_vma->flags = flags;
-       }
-
-       if (!mapped)
-               drm_gpuvm_bo_evict(vm_bo, true);
-
-       /* Drop the previous ref: */
-       drm_gpuvm_bo_put(vm_bo);
-
-       return 0;
-}
-
-static int
-msm_gem_vm_sm_step_unmap(struct drm_gpuva_op *op, void *_arg)
-{
-       struct op_arg *arg = _arg;
-       struct msm_vm_bind_job *job = arg->job;
-       struct drm_gpuva *vma = op->unmap.va;
-       struct msm_gem_vma *msm_vma = to_msm_vma(vma);
-
-       vm_dbg("%p:%p:%p: %016llx %016llx", vma->vm, vma, vma->gem.obj,
-              vma->va.addr, vma->va.range);
-
-       /*
-        * Detect in-place remap.  Turnip does this to change the vma flags,
-        * in particular MSM_VMA_DUMP.  In this case we want to avoid actually
-        * touching the page tables, as that would require synchronization
-        * against SUBMIT jobs running on the GPU.
-        */
-       if (op->unmap.keep &&
-           (arg->op->op == MSM_VM_BIND_OP_MAP) &&
-           (vma->gem.obj == arg->op->obj) &&
-           (vma->gem.offset == arg->op->obj_offset) &&
-           (vma->va.addr == arg->op->iova) &&
-           (vma->va.range == arg->op->range)) {
-               /* We are only expecting a single in-place unmap+map cb pair: */
-               WARN_ON(arg->kept);
-
-               /* Leave the existing VMA in place, but signal that to the map 
cb: */
-               arg->kept = true;
-
-               /* Only flags are changing, so update that in-place: */
-               unsigned orig_flags = vma->flags & (DRM_GPUVA_USERBITS - 1);
-               vma->flags = orig_flags | arg->flags;
-
-               return 0;
-       }
-
-       if (!msm_vma->mapped)
-               goto out_close;
-
-       vm_op_enqueue(arg, (struct msm_vm_op){
-               .op = MSM_VM_OP_UNMAP,
-               .unmap = {
-                       .iova = vma->va.addr,
-                       .range = vma->va.range,
-                       .queue_id = job->queue->id,
-               },
-               .obj = vma->gem.obj,
-       });
-
-       msm_vma->mapped = false;
-
-out_close:
-       msm_gem_vma_close(vma);
-
-       return 0;
-}
-
 static const struct drm_gpuvm_ops msm_gpuvm_ops = {
        .vm_free = msm_gem_vm_free,
        .vm_bo_validate = msm_gem_vm_bo_validate,
@@ -676,99 +285,6 @@ static const struct drm_gpuvm_ops msm_gpuvm_ops = {
        .sm_step_unmap = msm_gem_vm_sm_step_unmap,
 };
 
-static struct dma_fence *
-msm_vma_job_run(struct drm_sched_job *_job)
-{
-       struct msm_vm_bind_job *job = to_msm_vm_bind_job(_job);
-       struct msm_gem_vm *vm = to_msm_vm(job->vm);
-       struct drm_gem_object *obj;
-       int ret = vm->unusable ? -EINVAL : 0;
-
-       vm_dbg("");
-
-       mutex_lock(&vm->mmu_lock);
-       vm->mmu->prealloc = &job->prealloc;
-
-       while (!list_empty(&job->vm_ops)) {
-               struct msm_vm_op *op =
-                       list_first_entry(&job->vm_ops, struct msm_vm_op, node);
-
-               switch (op->op) {
-               case MSM_VM_OP_MAP:
-                       /*
-                        * On error, stop trying to map new things.. but we
-                        * still want to process the unmaps (or in particular,
-                        * the drm_gem_object_put()s)
-                        */
-                       if (!ret)
-                               ret = vm_map_op(vm, &op->map);
-                       break;
-               case MSM_VM_OP_UNMAP:
-                       vm_unmap_op(vm, &op->unmap);
-                       break;
-               }
-               drm_gem_object_put(op->obj);
-               list_del(&op->node);
-               kfree(op);
-       }
-
-       vm->mmu->prealloc = NULL;
-       mutex_unlock(&vm->mmu_lock);
-
-       /*
-        * We failed to perform at least _some_ of the pgtable updates, so
-        * now the VM is in an undefined state.  Game over!
-        */
-       if (ret)
-               msm_gem_vm_unusable(job->vm);
-
-       job_foreach_bo (obj, job) {
-               msm_gem_lock(obj);
-               msm_gem_unpin_locked(obj);
-               msm_gem_unlock(obj);
-       }
-
-       /* VM_BIND ops are synchronous, so no fence to wait on: */
-       return NULL;
-}
-
-static void
-msm_vma_job_free(struct drm_sched_job *_job)
-{
-       struct msm_vm_bind_job *job = to_msm_vm_bind_job(_job);
-       struct msm_gem_vm *vm = to_msm_vm(job->vm);
-       struct drm_gem_object *obj;
-
-       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)
-               drm_gem_object_put(obj);
-
-       msm_submitqueue_put(job->queue);
-       dma_fence_put(job->fence);
-
-       /* In error paths, we could have unexecuted ops: */
-       while (!list_empty(&job->vm_ops)) {
-               struct msm_vm_op *op =
-                       list_first_entry(&job->vm_ops, struct msm_vm_op, node);
-               list_del(&op->node);
-               kfree(op);
-       }
-
-       wake_up(&vm->prealloc_throttle.wait);
-
-       kfree(job);
-}
-
-static const struct drm_sched_backend_ops msm_vm_bind_ops = {
-       .run_job = msm_vma_job_run,
-       .free_job = msm_vma_job_free
-};
-
 /**
  * msm_gem_vm_create() - Create and initialize a &msm_gem_vm
  * @drm: the drm device
@@ -811,20 +327,9 @@ msm_gem_vm_create(struct drm_device *drm, struct msm_mmu 
*mmu, const char *name,
        }
 
        if (!managed) {
-               struct drm_sched_init_args args = {
-                       .ops = &msm_vm_bind_ops,
-                       .num_rqs = 1,
-                       .credit_limit = 1,
-                       .timeout = MAX_SCHEDULE_TIMEOUT,
-                       .name = "msm-vm-bind",
-                       .dev = drm->dev,
-               };
-
-               ret = drm_sched_init(&vm->sched, &args);
+               ret = msm_gem_vm_sched_init(vm, drm);
                if (ret)
                        goto err_free_dummy;
-
-               init_waitqueue_head(&vm->prealloc_throttle.wait);
        }
 
        drm_gpuvm_init(&vm->base, name, flags, drm, dummy_gem,
@@ -889,9 +394,7 @@ msm_gem_vm_close(struct drm_gpuvm *gpuvm)
        if (vm->last_fence)
                dma_fence_wait(vm->last_fence, false);
 
-       /* Kill the scheduler now, so we aren't racing with it for cleanup: */
-       drm_sched_stop(&vm->sched, NULL);
-       drm_sched_fini(&vm->sched);
+       msm_gem_vm_sched_fini(vm);
 
        /* Tear down any remaining mappings: */
        drm_exec_init(&exec, 0, 2);
@@ -924,677 +427,3 @@ msm_gem_vm_close(struct drm_gpuvm *gpuvm)
        }
        drm_exec_fini(&exec);
 }
-
-
-static struct msm_vm_bind_job *
-vm_bind_job_create(struct drm_device *dev, struct drm_file *file,
-                  struct msm_gpu_submitqueue *queue, uint32_t nr_ops)
-{
-       struct msm_vm_bind_job *job;
-       uint64_t sz;
-       int ret;
-
-       sz = struct_size(job, ops, nr_ops);
-
-       if (sz > SIZE_MAX)
-               return ERR_PTR(-ENOMEM);
-
-       job = kzalloc(sz, GFP_KERNEL | __GFP_NOWARN);
-       if (!job)
-               return ERR_PTR(-ENOMEM);
-
-       ret = drm_sched_job_init(&job->base, queue->entity, 1, queue,
-                                file->client_id);
-       if (ret) {
-               kfree(job);
-               return ERR_PTR(ret);
-       }
-
-       job->vm = msm_context_vm(dev, queue->ctx);
-       job->queue = queue;
-       INIT_LIST_HEAD(&job->vm_ops);
-
-       return job;
-}
-
-static bool invalid_alignment(uint64_t addr)
-{
-       /*
-        * Technically this is about GPU alignment, not CPU alignment.  But
-        * I've not seen any qcom SoC where the SMMU does not support the
-        * CPU's smallest page size.
-        */
-       return !PAGE_ALIGNED(addr);
-}
-
-static int
-lookup_op(struct msm_vm_bind_job *job, const struct drm_msm_vm_bind_op *op)
-{
-       struct drm_device *dev = job->vm->drm;
-       int i = job->nr_ops++;
-       int ret = 0;
-
-       job->ops[i].op = op->op;
-       job->ops[i].handle = op->handle;
-       job->ops[i].obj_offset = op->obj_offset;
-       job->ops[i].iova = op->iova;
-       job->ops[i].range = op->range;
-       job->ops[i].flags = op->flags;
-
-       if (op->flags & ~MSM_VM_BIND_OP_FLAGS)
-               ret = UERR(EINVAL, dev, "invalid flags: %x\n", op->flags);
-
-       if (invalid_alignment(op->iova))
-               ret = UERR(EINVAL, dev, "invalid address: %016llx\n", op->iova);
-
-       if (invalid_alignment(op->obj_offset))
-               ret = UERR(EINVAL, dev, "invalid bo_offset: %016llx\n", 
op->obj_offset);
-
-       if (invalid_alignment(op->range))
-               ret = UERR(EINVAL, dev, "invalid range: %016llx\n", op->range);
-
-       if (!drm_gpuvm_range_valid(job->vm, op->iova, op->range))
-               ret = UERR(EINVAL, dev, "invalid range: %016llx, %016llx\n", 
op->iova, op->range);
-
-       /*
-        * MAP must specify a valid handle.  But the handle MBZ for
-        * UNMAP or MAP_NULL.
-        */
-       if (op->op == MSM_VM_BIND_OP_MAP) {
-               if (!op->handle)
-                       ret = UERR(EINVAL, dev, "invalid handle\n");
-       } else if (op->handle) {
-               ret = UERR(EINVAL, dev, "handle must be zero\n");
-       }
-
-       switch (op->op) {
-       case MSM_VM_BIND_OP_MAP:
-       case MSM_VM_BIND_OP_MAP_NULL:
-       case MSM_VM_BIND_OP_UNMAP:
-               break;
-       default:
-               ret = UERR(EINVAL, dev, "invalid op: %u\n", op->op);
-               break;
-       }
-
-       return ret;
-}
-
-/*
- * ioctl parsing, parameter validation, and GEM handle lookup
- */
-static int
-vm_bind_job_lookup_ops(struct msm_vm_bind_job *job, struct drm_msm_vm_bind 
*args,
-                      struct drm_file *file, int *nr_bos)
-{
-       struct drm_device *dev = job->vm->drm;
-       int ret = 0;
-       int cnt = 0;
-       int i = -1;
-
-       if (args->nr_ops == 1) {
-               /* Single op case, the op is inlined: */
-               ret = lookup_op(job, &args->op);
-       } else {
-               for (unsigned i = 0; i < args->nr_ops; i++) {
-                       struct drm_msm_vm_bind_op op;
-                       void __user *userptr =
-                               u64_to_user_ptr(args->ops + (i * sizeof(op)));
-
-                       /* make sure we don't have garbage flags, in case we hit
-                        * error path before flags is initialized:
-                        */
-                       job->ops[i].flags = 0;
-
-                       if (copy_from_user(&op, userptr, sizeof(op))) {
-                               ret = -EFAULT;
-                               break;
-                       }
-
-                       ret = lookup_op(job, &op);
-                       if (ret)
-                               break;
-               }
-       }
-
-       if (ret) {
-               job->nr_ops = 0;
-               goto out;
-       }
-
-       spin_lock(&file->table_lock);
-
-       for (i = 0; i < args->nr_ops; i++) {
-               struct msm_vm_bind_op *op = &job->ops[i];
-               struct drm_gem_object *obj;
-
-               if (!op->handle) {
-                       op->obj = NULL;
-                       continue;
-               }
-
-               /*
-                * normally use drm_gem_object_lookup(), but for bulk lookup
-                * all under single table_lock just hit object_idr directly:
-                */
-               obj = idr_find(&file->object_idr, op->handle);
-               if (!obj) {
-                       ret = UERR(EINVAL, dev, "invalid handle %u at index 
%u\n", op->handle, i);
-                       goto out_unlock;
-               }
-
-               drm_gem_object_get(obj);
-
-               op->obj = obj;
-               cnt++;
-
-               if ((op->range + op->obj_offset) > obj->size) {
-                       ret = UERR(EINVAL, dev, "invalid range: %016llx + 
%016llx > %016zx\n",
-                                  op->range, op->obj_offset, obj->size);
-                       goto out_unlock;
-               }
-       }
-
-       *nr_bos = cnt;
-
-out_unlock:
-       spin_unlock(&file->table_lock);
-
-       if (ret) {
-               for (; i >= 0; i--) {
-                       struct msm_vm_bind_op *op = &job->ops[i];
-
-                       if (!op->obj)
-                               continue;
-
-                       drm_gem_object_put(op->obj);
-                       op->obj = NULL;
-               }
-       }
-out:
-       return ret;
-}
-
-static void
-prealloc_count(struct msm_vm_bind_job *job,
-              struct msm_vm_bind_op *first,
-              struct msm_vm_bind_op *last)
-{
-       struct msm_mmu *mmu = to_msm_vm(job->vm)->mmu;
-
-       if (!first)
-               return;
-
-       uint64_t start_iova = first->iova;
-       uint64_t end_iova = last->iova + last->range;
-
-       mmu->funcs->prealloc_count(mmu, &job->prealloc, start_iova, end_iova - 
start_iova);
-}
-
-static bool
-ops_are_same_pte(struct msm_vm_bind_op *first, struct msm_vm_bind_op *next)
-{
-       /*
-        * Last level pte covers 2MB.. so we should merge two ops, from
-        * the PoV of figuring out how much pgtable pages to pre-allocate
-        * if they land in the same 2MB range:
-        */
-       uint64_t pte_mask = ~(SZ_2M - 1);
-       return ((first->iova + first->range) & pte_mask) == (next->iova & 
pte_mask);
-}
-
-/*
- * Determine the amount of memory to prealloc for pgtables.  For sparse images,
- * in particular, userspace plays some tricks with the order of page mappings
- * to get the desired swizzle pattern, resulting in a large # of tiny MAP ops.
- * So detect when multiple MAP operations are physically contiguous, and count
- * them as a single mapping.  Otherwise the prealloc_count() will not realize
- * they can share pagetable pages and vastly overcount.
- */
-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];
-
-               /* We only care about MAP/MAP_NULL: */
-               if (op->op == MSM_VM_BIND_OP_UNMAP)
-                       continue;
-
-               /*
-                * If op is contiguous with last in the current range, then
-                * it becomes the new last in the range and we continue
-                * looping:
-                */
-               if (last && ops_are_same_pte(last, op)) {
-                       last = op;
-                       continue;
-               }
-
-               /*
-                * If op is not contiguous with the current range, flush
-                * the current range and start anew:
-                */
-               prealloc_count(job, first, last);
-               first = last = op;
-       }
-
-       /* 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;
-}
-
-/*
- * Lock VM and GEM objects
- */
-static int
-vm_bind_job_lock_objects(struct msm_vm_bind_job *job, struct drm_exec *exec)
-{
-       int ret;
-
-       /* Lock VM and objects: */
-       drm_exec_until_all_locked (exec) {
-               ret = drm_exec_lock_obj(exec, drm_gpuvm_resv_obj(job->vm));
-               drm_exec_retry_on_contention(exec);
-               if (ret)
-                       return ret;
-
-               for (unsigned i = 0; i < job->nr_ops; i++) {
-                       const struct msm_vm_bind_op *op = &job->ops[i];
-
-                       switch (op->op) {
-                       case MSM_VM_BIND_OP_UNMAP:
-                               ret = drm_gpuvm_sm_unmap_exec_lock(job->vm, 
exec,
-                                                             op->iova,
-                                                             op->obj_offset);
-                               break;
-                       case MSM_VM_BIND_OP_MAP:
-                       case MSM_VM_BIND_OP_MAP_NULL: {
-                               struct drm_gpuvm_map_req map_req = {
-                                       .map.va.addr = op->iova,
-                                       .map.va.range = op->range,
-                                       .map.gem.obj = op->obj,
-                                       .map.gem.offset = op->obj_offset,
-                               };
-
-                               ret = drm_gpuvm_sm_map_exec_lock(job->vm, exec, 
1, &map_req);
-                               break;
-                       }
-                       default:
-                               /*
-                                * lookup_op() should have already thrown an 
error for
-                                * invalid ops
-                                */
-                               WARN_ON("unreachable");
-                       }
-
-                       drm_exec_retry_on_contention(exec);
-                       if (ret)
-                               return ret;
-               }
-       }
-
-       return 0;
-}
-
-/*
- * Pin GEM objects, ensuring that we have backing pages.  Pinning will move
- * the object to the pinned LRU so that the shrinker knows to first consider
- * other objects for evicting.
- */
-static int
-vm_bind_job_pin_objects(struct msm_vm_bind_job *job)
-{
-       struct drm_gem_object *obj;
-
-       /*
-        * First loop, before holding the LRU lock, avoids holding the
-        * LRU lock while calling msm_gem_pin_vma_locked (which could
-        * trigger get_pages())
-        */
-       job_foreach_bo (obj, job) {
-               struct page **pages;
-
-               pages = msm_gem_get_pages_locked(obj, MSM_MADV_WILLNEED);
-               if (IS_ERR(pages))
-                       return PTR_ERR(pages);
-       }
-
-       struct msm_drm_private *priv = job->vm->drm->dev_private;
-
-       /*
-        * A second loop while holding the LRU lock (a) avoids 
acquiring/dropping
-        * the LRU lock for each individual bo, while (b) avoiding holding the
-        * LRU lock while calling msm_gem_pin_vma_locked() (which could trigger
-        * get_pages() which could trigger reclaim.. and if we held the LRU lock
-        * could trigger deadlock with the shrinker).
-        */
-       mutex_lock(&priv->lru.lock);
-       job_foreach_bo (obj, job)
-               msm_gem_pin_obj_locked(obj);
-       mutex_unlock(&priv->lru.lock);
-
-       job->bos_pinned = true;
-
-       return 0;
-}
-
-/*
- * Unpin GEM objects.  Normally this is done after the bind job is run.
- */
-static void
-vm_bind_job_unpin_objects(struct msm_vm_bind_job *job)
-{
-       struct drm_gem_object *obj;
-
-       if (!job->bos_pinned)
-               return;
-
-       job_foreach_bo (obj, job)
-               msm_gem_unpin_locked(obj);
-
-       job->bos_pinned = false;
-}
-
-/*
- * Pre-allocate pgtable memory, and translate the VM bind requests into a
- * sequence of pgtable updates to be applied asynchronously.
- */
-static int
-vm_bind_job_prepare(struct msm_vm_bind_job *job)
-{
-       struct msm_gem_vm *vm = to_msm_vm(job->vm);
-       struct msm_mmu *mmu = vm->mmu;
-       int ret;
-
-       ret = mmu->funcs->prealloc_allocate(mmu, &job->prealloc);
-       if (ret)
-               return ret;
-
-       for (unsigned i = 0; i < job->nr_ops; i++) {
-               const struct msm_vm_bind_op *op = &job->ops[i];
-               struct op_arg arg = {
-                       .job = job,
-                       .op = op,
-               };
-
-               switch (op->op) {
-               case MSM_VM_BIND_OP_UNMAP:
-                       ret = drm_gpuvm_sm_unmap(job->vm, &arg, op->iova,
-                                                op->range);
-                       break;
-               case MSM_VM_BIND_OP_MAP:
-                       if (op->flags & MSM_VM_BIND_OP_DUMP)
-                               arg.flags |= MSM_VMA_DUMP;
-                       fallthrough;
-               case MSM_VM_BIND_OP_MAP_NULL: {
-                       struct drm_gpuvm_map_req map_req = {
-                               .map.va.addr = op->iova,
-                               .map.va.range = op->range,
-                               .map.gem.obj = op->obj,
-                               .map.gem.offset = op->obj_offset,
-                       };
-
-                       ret = drm_gpuvm_sm_map(job->vm, &arg, &map_req);
-                       break;
-               }
-               default:
-                       /*
-                        * lookup_op() should have already thrown an error for
-                        * invalid ops
-                        */
-                       BUG_ON("unreachable");
-               }
-
-               if (ret) {
-                       /*
-                        * If we've already started modifying the vm, we can't
-                        * adequetly describe to userspace the intermediate
-                        * state the vm is in.  So throw up our hands!
-                        */
-                       if (i > 0)
-                               msm_gem_vm_unusable(job->vm);
-                       return ret;
-               }
-       }
-
-       return 0;
-}
-
-/*
- * Attach fences to the GEM objects being bound.  This will signify to
- * the shrinker that they are busy even after dropping the locks (ie.
- * drm_exec_fini())
- */
-static void
-vm_bind_job_attach_fences(struct msm_vm_bind_job *job)
-{
-       for (unsigned i = 0; i < job->nr_ops; i++) {
-               struct drm_gem_object *obj = job->ops[i].obj;
-
-               if (!obj)
-                       continue;
-
-               dma_resv_add_fence(obj->resv, job->fence,
-                                  DMA_RESV_USAGE_KERNEL);
-       }
-}
-
-int
-msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file)
-{
-       struct msm_drm_private *priv = dev->dev_private;
-       struct drm_msm_vm_bind *args = data;
-       struct msm_context *ctx = file->driver_priv;
-       struct msm_vm_bind_job *job = NULL;
-       struct msm_gpu *gpu = priv->gpu;
-       struct msm_gpu_submitqueue *queue;
-       struct msm_syncobj_post_dep *post_deps = NULL;
-       struct drm_syncobj **syncobjs_to_reset = NULL;
-       struct sync_file *sync_file = NULL;
-       struct dma_fence *fence;
-       int out_fence_fd = -1;
-       int ret, nr_bos = 0;
-       unsigned i;
-
-       if (!gpu)
-               return -ENXIO;
-
-       /*
-        * Maybe we could allow just UNMAP ops?  OTOH userspace should just
-        * immediately close the device file and all will be torn down.
-        */
-       if (to_msm_vm(ctx->vm)->unusable)
-               return UERR(EPIPE, dev, "context is unusable");
-
-       /*
-        * Technically, you cannot create a VM_BIND submitqueue in the first
-        * place, if you haven't opted in to VM_BIND context.  But it is
-        * cleaner / less confusing, to check this case directly.
-        */
-       if (!msm_context_is_vmbind(ctx))
-               return UERR(EINVAL, dev, "context does not support vmbind");
-
-       if (args->flags & ~MSM_VM_BIND_FLAGS)
-               return UERR(EINVAL, dev, "invalid flags");
-
-       queue = msm_submitqueue_get(ctx, args->queue_id);
-       if (!queue)
-               return -ENOENT;
-
-       if (!(queue->flags & MSM_SUBMITQUEUE_VM_BIND)) {
-               ret = UERR(EINVAL, dev, "Invalid queue type");
-               goto out_post_unlock;
-       }
-
-       if (args->flags & MSM_VM_BIND_FENCE_FD_OUT) {
-               out_fence_fd = get_unused_fd_flags(O_CLOEXEC);
-               if (out_fence_fd < 0) {
-                       ret = out_fence_fd;
-                       goto out_post_unlock;
-               }
-       }
-
-       job = vm_bind_job_create(dev, file, queue, args->nr_ops);
-       if (IS_ERR(job)) {
-               ret = PTR_ERR(job);
-               goto out_post_unlock;
-       }
-
-       ret = mutex_lock_interruptible(&queue->lock);
-       if (ret)
-               goto out_post_unlock;
-
-       if (args->flags & MSM_VM_BIND_FENCE_FD_IN) {
-               struct dma_fence *in_fence;
-
-               in_fence = sync_file_get_fence(args->fence_fd);
-
-               if (!in_fence) {
-                       ret = UERR(EINVAL, dev, "invalid in-fence");
-                       goto out_unlock;
-               }
-
-               ret = drm_sched_job_add_dependency(&job->base, in_fence);
-               if (ret)
-                       goto out_unlock;
-       }
-
-       if (args->in_syncobjs > 0) {
-               syncobjs_to_reset = msm_syncobj_parse_deps(dev, &job->base,
-                                                          file, 
args->in_syncobjs,
-                                                          args->nr_in_syncobjs,
-                                                          
args->syncobj_stride);
-               if (IS_ERR(syncobjs_to_reset)) {
-                       ret = PTR_ERR(syncobjs_to_reset);
-                       goto out_unlock;
-               }
-       }
-
-       if (args->out_syncobjs > 0) {
-               post_deps = msm_syncobj_parse_post_deps(dev, file,
-                                                       args->out_syncobjs,
-                                                       args->nr_out_syncobjs,
-                                                       args->syncobj_stride);
-               if (IS_ERR(post_deps)) {
-                       ret = PTR_ERR(post_deps);
-                       goto out_unlock;
-               }
-       }
-
-       ret = vm_bind_job_lookup_ops(job, args, file, &nr_bos);
-       if (ret)
-               goto out_unlock;
-
-       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;
-       drm_exec_init(&exec, flags, nr_bos + 1);
-
-       ret = vm_bind_job_lock_objects(job, &exec);
-       if (ret)
-               goto out;
-
-       ret = vm_bind_job_pin_objects(job);
-       if (ret)
-               goto out;
-
-       ret = vm_bind_job_prepare(job);
-       if (ret)
-               goto out;
-
-       drm_sched_job_arm(&job->base);
-
-       job->fence = dma_fence_get(&job->base.s_fence->finished);
-
-       if (args->flags & MSM_VM_BIND_FENCE_FD_OUT) {
-               sync_file = sync_file_create(job->fence);
-               if (!sync_file)
-                       ret = -ENOMEM;
-       }
-
-       if (ret)
-               goto out;
-
-       vm_bind_job_attach_fences(job);
-
-       /*
-        * The job can be free'd (and fence unref'd) at any point after
-        * drm_sched_entity_push_job(), so we need to hold our own ref
-        */
-       fence = dma_fence_get(job->fence);
-
-       drm_sched_entity_push_job(&job->base);
-
-       msm_syncobj_reset(syncobjs_to_reset, args->nr_in_syncobjs);
-       msm_syncobj_process_post_deps(post_deps, args->nr_out_syncobjs, fence);
-
-       dma_fence_put(fence);
-
-out:
-       if (ret)
-               vm_bind_job_unpin_objects(job);
-
-       drm_exec_fini(&exec);
-out_unlock:
-       mutex_unlock(&queue->lock);
-out_post_unlock:
-       if (ret) {
-               if (out_fence_fd >= 0)
-                       put_unused_fd(out_fence_fd);
-               if (sync_file)
-                       fput(sync_file->file);
-       } else if (sync_file) {
-               fd_install(out_fence_fd, sync_file->file);
-               args->fence_fd = out_fence_fd;
-       }
-
-       if (!IS_ERR_OR_NULL(job)) {
-               if (ret)
-                       msm_vma_job_free(&job->base);
-       } else {
-               /*
-                * If the submit hasn't yet taken ownership of the queue
-                * then we need to drop the reference ourself:
-                */
-               msm_submitqueue_put(queue);
-       }
-
-       if (!IS_ERR_OR_NULL(post_deps)) {
-               for (i = 0; i < args->nr_out_syncobjs; ++i) {
-                       kfree(post_deps[i].chain);
-                       drm_syncobj_put(post_deps[i].syncobj);
-               }
-               kfree(post_deps);
-       }
-
-       if (!IS_ERR_OR_NULL(syncobjs_to_reset)) {
-               for (i = 0; i < args->nr_in_syncobjs; ++i) {
-                       if (syncobjs_to_reset[i])
-                               drm_syncobj_put(syncobjs_to_reset[i]);
-               }
-               kfree(syncobjs_to_reset);
-       }
-
-       return ret;
-}
diff --git a/drivers/gpu/drm/msm/msm_gem_vma.h 
b/drivers/gpu/drm/msm/msm_gem_vma.h
new file mode 100644
index 
0000000000000000000000000000000000000000..f702f81529e72b86bffb4960408f1912bc65851a
--- /dev/null
+++ b/drivers/gpu/drm/msm/msm_gem_vma.h
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2016 Red Hat
+ * Author: Rob Clark <robdcl...@gmail.com>
+ */
+
+#ifndef _MSM_GEM_VMA_H_
+#define _MSM_GEM_VMA_H_
+
+#define vm_dbg(fmt, ...) pr_debug("%s:%d: "fmt"\n", __func__, __LINE__, 
##__VA_ARGS__)
+
+/**
+ * struct msm_vm_map_op - create new pgtable mapping
+ */
+struct msm_vm_map_op {
+       /** @iova: start address for mapping */
+       uint64_t iova;
+       /** @range: size of the region to map */
+       uint64_t range;
+       /** @offset: offset into @sgt to map */
+       uint64_t offset;
+       /** @sgt: pages to map, or NULL for a PRR mapping */
+       struct sg_table *sgt;
+       /** @prot: the mapping protection flags */
+       int prot;
+
+       /**
+        * @queue_id: The id of the submitqueue the operation is performed
+        * on, or zero for (in particular) UNMAP ops triggered outside of
+        * a submitqueue (ie. process cleanup)
+        */
+       int queue_id;
+};
+
+/**
+ * struct msm_vm_unmap_op - unmap a range of pages from pgtable
+ */
+struct msm_vm_unmap_op {
+       /** @iova: start address for unmap */
+       uint64_t iova;
+       /** @range: size of region to unmap */
+       uint64_t range;
+
+       /** @reason: The reason for the unmap */
+       const char *reason;
+
+       /**
+        * @queue_id: The id of the submitqueue the operation is performed
+        * on, or zero for (in particular) UNMAP ops triggered outside of
+        * a submitqueue (ie. process cleanup)
+        */
+       int queue_id;
+};
+
+static void
+vm_log(struct msm_gem_vm *vm, const char *op, uint64_t iova, uint64_t range, 
int queue_id)
+{
+       int idx;
+
+       if (!vm->managed)
+               lockdep_assert_held(&vm->mmu_lock);
+
+       vm_dbg("%s:%p:%d: %016llx %016llx", op, vm, queue_id, iova, iova + 
range);
+
+       if (!vm->log)
+               return;
+
+       idx = vm->log_idx;
+       vm->log[idx].op = op;
+       vm->log[idx].iova = iova;
+       vm->log[idx].range = range;
+       vm->log[idx].queue_id = queue_id;
+       vm->log_idx = (vm->log_idx + 1) & ((1 << vm->log_shift) - 1);
+}
+
+static void
+vm_unmap_op(struct msm_gem_vm *vm, const struct msm_vm_unmap_op *op)
+{
+       const char *reason = op->reason;
+
+       if (!reason)
+               reason = "unmap";
+
+       vm_log(vm, reason, op->iova, op->range, op->queue_id);
+
+       vm->mmu->funcs->unmap(vm->mmu, op->iova, op->range);
+}
+
+static int
+vm_map_op(struct msm_gem_vm *vm, const struct msm_vm_map_op *op)
+{
+       vm_log(vm, "map", op->iova, op->range, op->queue_id);
+
+       return vm->mmu->funcs->map(vm->mmu, op->iova, op->sgt, op->offset,
+                                  op->range, op->prot);
+}
+
+int msm_gem_vm_sm_step_map(struct drm_gpuva_op *op, void *_arg);
+int msm_gem_vm_sm_step_remap(struct drm_gpuva_op *op, void *arg);
+int msm_gem_vm_sm_step_unmap(struct drm_gpuva_op *op, void *_arg);
+
+int msm_gem_vm_sched_init(struct msm_gem_vm *vm, struct drm_device *drm);
+void msm_gem_vm_sched_fini(struct msm_gem_vm *vm);
+
+#endif

-- 
2.47.3

Reply via email to