| @@ -4,9 +4,16 @@ · 3 unmodified lines |
| 4 | 4 | * Author: Rob Clark <robdclark@gmail.com> |
| 5 | 5 | */ |
| 6 | 6 | |
| 7 | +#include "drm/drm_file.h" |
| 8 | +#include "drm/msm_drm.h" |
| 9 | +#include "linux/file.h" |
| 10 | +#include "linux/sync_file.h" |
| 11 | + |
| 7 | 12 | #include "msm_drv.h" |
| 8 | 13 | #include "msm_gem.h" |
| 14 | +#include "msm_gpu.h" |
| 9 | 15 | #include "msm_mmu.h" |
| 16 | +#include "msm_syncobj.h" |
| 10 | 17 | |
| 11 | 18 | #define vm_dbg(fmt, ...) pr_debug("%s:%d: "fmt"\n", __func__, __LINE__, ##__VA_ARGS__) |
| 12 | 19 | |
| @@ -36,6 +43,97 @@ struct msm_vm_unmap_op { · 23 unmodified lines |
| 36 | 43 | uint64_t range; |
| 37 | 44 | }; |
| 38 | 45 | |
| 46 | +/** |
| 47 | + * struct msm_vma_op - A MAP or UNMAP operation |
| 48 | + */ |
| 49 | +struct msm_vm_op { |
| 50 | + /** @op: The operation type */ |
| 51 | + enum { |
| 52 | + MSM_VM_OP_MAP = 1, |
| 53 | + MSM_VM_OP_UNMAP, |
| 54 | + } op; |
| 55 | + union { |
| 56 | + /** @map: Parameters used if op == MSM_VMA_OP_MAP */ |
| 57 | + struct msm_vm_map_op map; |
| 58 | + /** @unmap: Parameters used if op == MSM_VMA_OP_UNMAP */ |
| 59 | + struct msm_vm_unmap_op unmap; |
| 60 | + }; |
| 61 | + /** @node: list head in msm_vm_bind_job::vm_ops */ |
| 62 | + struct list_head node; |
| 63 | + |
| 64 | + /** |
| 65 | + * @obj: backing object for pages to be mapped/unmapped |
| 66 | + * |
| 67 | + * Async unmap ops, in particular, must hold a reference to the |
| 68 | + * original GEM object backing the mapping that will be unmapped. |
| 69 | + * But the same can be required in the map path, for example if |
| 70 | + * there is not a corresponding unmap op, such as process exit. |
| 71 | + * |
| 72 | + * This ensures that the pages backing the mapping are not freed |
| 73 | + * before the mapping is torn down. |
| 74 | + */ |
| 75 | + struct drm_gem_object *obj; |
| 76 | +}; |
| 77 | + |
| 78 | +/** |
| 79 | + * struct msm_vm_bind_job - Tracking for a VM_BIND ioctl |
| 80 | + * |
| 81 | + * A table of userspace requested VM updates (MSM_VM_BIND_OP_UNMAP/MAP/MAP_NULL) |
| 82 | + * gets applied to the vm, generating a list of VM ops (MSM_VM_OP_MAP/UNMAP) |
| 83 | + * which are applied to the pgtables asynchronously. For example a userspace |
| 84 | + * requested MSM_VM_BIND_OP_MAP could end up generating both an MSM_VM_OP_UNMAP |
| 85 | + * to unmap an existing mapping, and a MSM_VM_OP_MAP to apply the new mapping. |
| 86 | + */ |
| 87 | +struct msm_vm_bind_job { |
| 88 | + /** @base: base class for drm_sched jobs */ |
| 89 | + struct drm_sched_job base; |
| 90 | + /** @vm: The VM being operated on */ |
| 91 | + struct drm_gpuvm *vm; |
| 92 | + /** @fence: The fence that is signaled when job completes */ |
| 93 | + struct dma_fence *fence; |
| 94 | + /** @queue: The queue that the job runs on */ |
| 95 | + struct msm_gpu_submitqueue *queue; |
| 96 | + /** @prealloc: Tracking for pre-allocated MMU pgtable pages */ |
| 97 | + struct msm_mmu_prealloc prealloc; |
| 98 | + /** @vm_ops: a list of struct msm_vm_op */ |
| 99 | + struct list_head vm_ops; |
| 100 | + /** @bos_pinned: are the GEM objects being bound pinned? */ |
| 101 | + bool bos_pinned; |
| 102 | + /** @nr_ops: the number of userspace requested ops */ |
| 103 | + unsigned int nr_ops; |
| 104 | + /** |
| 105 | + * @ops: the userspace requested ops |
| 106 | + * |
| 107 | + * The userspace requested ops are copied/parsed and validated |
| 108 | + * before we start applying the updates to try to do as much up- |
| 109 | + * front error checking as possible, to avoid the VM being in an |
| 110 | + * undefined state due to partially executed VM_BIND. |
| 111 | + * |
| 112 | + * This table also serves to hold a reference to the backing GEM |
| 113 | + * objects. |
| 114 | + */ |
| 115 | + struct msm_vm_bind_op { |
| 116 | + uint32_t op; |
| 117 | + uint32_t flags; |
| 118 | + union { |
| 119 | + struct drm_gem_object *obj; |
| 120 | + uint32_t handle; |
| 121 | + }; |
| 122 | + uint64_t obj_offset; |
| 123 | + uint64_t iova; |
| 124 | + uint64_t range; |
| 125 | + } ops[]; |
| 126 | +}; |
| 127 | + |
| 128 | +#define job_foreach_bo(obj, _job) \ |
| 129 | + for (unsigned i = 0; i < (_job)->nr_ops; i++) \ |
| 130 | + if ((obj = (_job)->ops[i].obj)) |
| 131 | + |
| 132 | +static inline struct msm_vm_bind_job *to_msm_vm_bind_job(struct drm_sched_job *job) |
| 133 | +{ |
| 134 | + return container_of(job, struct msm_vm_bind_job, base); |
| 135 | +} |
| 136 | + |
| 39 | 137 | static void |
| 40 | 138 | msm_gem_vm_free(struct drm_gpuvm *gpuvm) |
| 41 | 139 | { |
| @@ -52,6 +150,9 @@ msm_gem_vm_free(struct drm_gpuvm *gpuvm) · 10 unmodified lines |
| 52 | 150 | static void |
| 53 | 151 | vm_unmap_op(struct msm_gem_vm *vm, const struct msm_vm_unmap_op *op) |
| 54 | 152 | { |
| 153 | + if (!vm->managed) |
| 154 | + lockdep_assert_held(&vm->mmu_lock); |
| 155 | + |
| 55 | 156 | vm_dbg("%p: %016llx %016llx", vm, op->iova, op->iova + op->range); |
| 56 | 157 | |
| 57 | 158 | vm->mmu->funcs->unmap(vm->mmu, op->iova, op->range); |
| @@ -60,6 +161,9 @@ vm_unmap_op(struct msm_gem_vm *vm, const struct msm_vm_unmap_op *op) · 2 unmodified lines |
| 60 | 161 | static int |
| 61 | 162 | vm_map_op(struct msm_gem_vm *vm, const struct msm_vm_map_op *op) |
| 62 | 163 | { |
| 164 | + if (!vm->managed) |
| 165 | + lockdep_assert_held(&vm->mmu_lock); |
| 166 | + |
| 63 | 167 | vm_dbg("%p: %016llx %016llx", vm, op->iova, op->iova + op->range); |
| 64 | 168 | |
| 65 | 169 | return vm->mmu->funcs->map(vm->mmu, op->iova, op->sgt, op->offset, |
| @@ -69,17 +173,29 @@ vm_map_op(struct msm_gem_vm *vm, const struct msm_vm_map_op *op) · 3 unmodified lines |
| 69 | 173 | /* Actually unmap memory for the vma */ |
| 70 | 174 | void msm_gem_vma_unmap(struct drm_gpuva *vma) |
| 71 | 175 | { |
| 176 | + struct msm_gem_vm *vm = to_msm_vm(vma->vm); |
| 72 | 177 | struct msm_gem_vma *msm_vma = to_msm_vma(vma); |
| 73 | 178 | |
| 74 | 179 | /* Don't do anything if the memory isn't mapped */ |
| 75 | 180 | if (!msm_vma->mapped) |
| 76 | 181 | return; |
| 77 | 182 | |
| 78 | | − vm_unmap_op(to_msm_vm(vma->vm), &(struct msm_vm_unmap_op){ |
| 183 | + /* |
| 184 | + * The mmu_lock is only needed when preallocation is used. But |
| 185 | + * in that case we don't need to worry about recursion into |
| 186 | + * shrinker |
| 187 | + */ |
| 188 | + if (!vm->managed) |
| 189 | + mutex_lock(&vm->mmu_lock); |
| 190 | + |
| 191 | + vm_unmap_op(vm, &(struct msm_vm_unmap_op){ |
| 79 | 192 | .iova = vma->va.addr, |
| 80 | 193 | .range = vma->va.range, |
| 81 | 194 | }); |
| 82 | 195 | |
| 196 | + if (!vm->managed) |
| 197 | + mutex_unlock(&vm->mmu_lock); |
| 198 | + |
| 83 | 199 | msm_vma->mapped = false; |
| 84 | 200 | } |
| 85 | 201 | |
| @@ -87,6 +203,7 @@ void msm_gem_vma_unmap(struct drm_gpuva *vma) · 1 unmodified line |
| 87 | 203 | int |
| 88 | 204 | msm_gem_vma_map(struct drm_gpuva *vma, int prot, struct sg_table *sgt) |
| 89 | 205 | { |
| 206 | + struct msm_gem_vm *vm = to_msm_vm(vma->vm); |
| 90 | 207 | struct msm_gem_vma *msm_vma = to_msm_vma(vma); |
| 91 | 208 | int ret; |
| 92 | 209 | |
| @@ -98,6 +215,14 @@ msm_gem_vma_map(struct drm_gpuva *vma, int prot, struct sg_table *sgt) · 5 unmodified lines |
| 98 | 215 | |
| 99 | 216 | msm_vma->mapped = true; |
| 100 | 217 | |
| 218 | + /* |
| 219 | + * The mmu_lock is only needed when preallocation is used. But |
| 220 | + * in that case we don't need to worry about recursion into |
| 221 | + * shrinker |
| 222 | + */ |
| 223 | + if (!vm->managed) |
| 224 | + mutex_lock(&vm->mmu_lock); |
| 225 | + |
| 101 | 226 | /* |
| 102 | 227 | * NOTE: iommu/io-pgtable can allocate pages, so we cannot hold |
| 103 | 228 | * a lock across map/unmap which is also used in the job_run() |
| @@ -107,16 +232,19 @@ msm_gem_vma_map(struct drm_gpuva *vma, int prot, struct sg_table *sgt) · 3 unmodified lines |
| 107 | 232 | * Revisit this if we can come up with a scheme to pre-alloc pages |
| 108 | 233 | * for the pgtable in map/unmap ops. |
| 109 | 234 | */ |
| 110 | | − ret = vm_map_op(to_msm_vm(vma->vm), &(struct msm_vm_map_op){ |
| 235 | + ret = vm_map_op(vm, &(struct msm_vm_map_op){ |
| 111 | 236 | .iova = vma->va.addr, |
| 112 | 237 | .range = vma->va.range, |
| 113 | 238 | .offset = vma->gem.offset, |
| 114 | 239 | .sgt = sgt, |
| 115 | 240 | .prot = prot, |
| 116 | 241 | }); |
| 117 | | − if (ret) { |
| 242 | + |
| 243 | + if (!vm->managed) |
| 244 | + mutex_unlock(&vm->mmu_lock); |
| 245 | + |
| 246 | + if (ret) |
| 118 | 247 | msm_vma->mapped = false; |
| 119 | | − } |
| 120 | 248 | |
| 121 | 249 | return ret; |
| 122 | 250 | } |
| @@ -131,6 +259,9 @@ void msm_gem_vma_close(struct drm_gpuva *vma) · 8 unmodified lines |
| 131 | 259 | |
| 132 | 260 | drm_gpuvm_resv_assert_held(&vm->base); |
| 133 | 261 | |
| 262 | + if (vma->gem.obj) |
| 263 | + msm_gem_assert_locked(vma->gem.obj); |
| 264 | + |
| 134 | 265 | if (vma->va.addr && vm->managed) |
| 135 | 266 | drm_mm_remove_node(&msm_vma->node); |
| 136 | 267 | |
| @@ -158,6 +289,7 @@ msm_gem_vma_new(struct drm_gpuvm *gpuvm, struct drm_gem_object *obj, · 21 unmodified lines |
| 158 | 289 | |
| 159 | 290 | if (vm->managed) { |
| 160 | 291 | BUG_ON(offset != 0); |
| 292 | + BUG_ON(!obj); /* NULL mappings not valid for kernel managed VM */ |
| 161 | 293 | ret = drm_mm_insert_node_in_range(&vm->mm, &vma->node, |
| 162 | 294 | obj->size, PAGE_SIZE, 0, |
| 163 | 295 | range_start, range_end, 0); |
| @@ -169,7 +301,8 @@ msm_gem_vma_new(struct drm_gpuvm *gpuvm, struct drm_gem_object *obj, · 5 unmodified lines |
| 169 | 301 | range_end = range_start + obj->size; |
| 170 | 302 | } |
| 171 | 303 | |
| 172 | | − GEM_WARN_ON((range_end - range_start) > obj->size); |
| 304 | + if (obj) |
| 305 | + GEM_WARN_ON((range_end - range_start) > obj->size); |
| 173 | 306 | |
| 174 | 307 | drm_gpuva_init(&vma->base, range_start, range_end - range_start, obj, offset); |
| 175 | 308 | vma->mapped = false; |
| @@ -178,6 +311,9 @@ msm_gem_vma_new(struct drm_gpuvm *gpuvm, struct drm_gem_object *obj, · 2 unmodified lines |
| 178 | 311 | if (ret) |
| 179 | 312 | goto err_free_range; |
| 180 | 313 | |
| 314 | + if (!obj) |
| 315 | + return &vma->base; |
| 316 | + |
| 181 | 317 | vm_bo = drm_gpuvm_bo_obtain(&vm->base, obj); |
| 182 | 318 | if (IS_ERR(vm_bo)) { |
| 183 | 319 | ret = PTR_ERR(vm_bo); |
| @@ -200,11 +336,297 @@ err_free_vma: · 16 unmodified lines |
| 200 | 336 | return ERR_PTR(ret); |
| 201 | 337 | } |
| 202 | 338 | |
| 339 | +static int |
| 340 | +msm_gem_vm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec *exec) |
| 341 | +{ |
| 342 | + struct drm_gem_object *obj = vm_bo->obj; |
| 343 | + struct drm_gpuva *vma; |
| 344 | + int ret; |
| 345 | + |
| 346 | + vm_dbg("validate: %p", obj); |
| 347 | + |
| 348 | + msm_gem_assert_locked(obj); |
| 349 | + |
| 350 | + drm_gpuvm_bo_for_each_va (vma, vm_bo) { |
| 351 | + ret = msm_gem_pin_vma_locked(obj, vma); |
| 352 | + if (ret) |
| 353 | + return ret; |
| 354 | + } |
| 355 | + |
| 356 | + return 0; |
| 357 | +} |
| 358 | + |
| 359 | +struct op_arg { |
| 360 | + unsigned flags; |
| 361 | + struct msm_vm_bind_job *job; |
| 362 | +}; |
| 363 | + |
| 364 | +static void |
| 365 | +vm_op_enqueue(struct op_arg *arg, struct msm_vm_op _op) |
| 366 | +{ |
| 367 | + struct msm_vm_op *op = kmalloc(sizeof(*op), GFP_KERNEL); |
| 368 | + *op = _op; |
| 369 | + list_add_tail(&op->node, &arg->job->vm_ops); |
| 370 | + |
| 371 | + if (op->obj) |
| 372 | + drm_gem_object_get(op->obj); |
| 373 | +} |
| 374 | + |
| 375 | +static struct drm_gpuva * |
| 376 | +vma_from_op(struct op_arg *arg, struct drm_gpuva_op_map *op) |
| 377 | +{ |
| 378 | + return msm_gem_vma_new(arg->job->vm, op->gem.obj, op->gem.offset, |
| 379 | + op->va.addr, op->va.addr + op->va.range); |
| 380 | +} |
| 381 | + |
| 382 | +static int |
| 383 | +msm_gem_vm_sm_step_map(struct drm_gpuva_op *op, void *arg) |
| 384 | +{ |
| 385 | + struct drm_gem_object *obj = op->map.gem.obj; |
| 386 | + struct drm_gpuva *vma; |
| 387 | + struct sg_table *sgt; |
| 388 | + unsigned prot; |
| 389 | + |
| 390 | + vma = vma_from_op(arg, &op->map); |
| 391 | + if (WARN_ON(IS_ERR(vma))) |
| 392 | + return PTR_ERR(vma); |
| 393 | + |
| 394 | + vm_dbg("%p:%p:%p: %016llx %016llx", vma->vm, vma, vma->gem.obj, |
| 395 | + vma->va.addr, vma->va.range); |
| 396 | + |
| 397 | + vma->flags = ((struct op_arg *)arg)->flags; |
| 398 | + |
| 399 | + if (obj) { |
| 400 | + sgt = to_msm_bo(obj)->sgt; |
| 401 | + prot = msm_gem_prot(obj); |
| 402 | + } else { |
| 403 | + sgt = NULL; |
| 404 | + prot = IOMMU_READ | IOMMU_WRITE; |
| 405 | + } |
| 406 | + |
| 407 | + vm_op_enqueue(arg, (struct msm_vm_op){ |
| 408 | + .op = MSM_VM_OP_MAP, |
| 409 | + .map = { |
| 410 | + .sgt = sgt, |
| 411 | + .iova = vma->va.addr, |
| 412 | + .range = vma->va.range, |
| 413 | + .offset = vma->gem.offset, |
| 414 | + .prot = prot, |
| 415 | + }, |
| 416 | + .obj = vma->gem.obj, |
| 417 | + }); |
| 418 | + |
| 419 | + to_msm_vma(vma)->mapped = true; |
| 420 | + |
| 421 | + return 0; |
| 422 | +} |
| 423 | + |
| 424 | +static int |
| 425 | +msm_gem_vm_sm_step_remap(struct drm_gpuva_op *op, void *arg) |
| 426 | +{ |
| 427 | + struct msm_vm_bind_job *job = ((struct op_arg *)arg)->job; |
| 428 | + struct drm_gpuvm *vm = job->vm; |
| 429 | + struct drm_gpuva *orig_vma = op->remap.unmap->va; |
| 430 | + struct drm_gpuva *prev_vma = NULL, *next_vma = NULL; |
| 431 | + struct drm_gpuvm_bo *vm_bo = orig_vma->vm_bo; |
| 432 | + bool mapped = to_msm_vma(orig_vma)->mapped; |
| 433 | + unsigned flags; |
| 434 | + |
| 435 | + vm_dbg("orig_vma: %p:%p:%p: %016llx %016llx", vm, orig_vma, |
| 436 | + orig_vma->gem.obj, orig_vma->va.addr, orig_vma->va.range); |
| 437 | + |
| 438 | + if (mapped) { |
| 439 | + uint64_t unmap_start, unmap_range; |
| 440 | + |
| 441 | + drm_gpuva_op_remap_to_unmap_range(&op->remap, &unmap_start, &unmap_range); |
| 442 | + |
| 443 | + vm_op_enqueue(arg, (struct msm_vm_op){ |
| 444 | + .op = MSM_VM_OP_UNMAP, |
| 445 | + .unmap = { |
| 446 | + .iova = unmap_start, |
| 447 | + .range = unmap_range, |
| 448 | + }, |
| 449 | + .obj = orig_vma->gem.obj, |
| 450 | + }); |
| 451 | + |
| 452 | + /* |
| 453 | + * Part of this GEM obj is still mapped, but we're going to kill the |
| 454 | + * existing VMA and replace it with one or two new ones (ie. two if |
| 455 | + * the unmapped range is in the middle of the existing (unmap) VMA). |
| 456 | + * So just set the state to unmapped: |
| 457 | + */ |
| 458 | + to_msm_vma(orig_vma)->mapped = false; |
| 459 | + } |
| 460 | + |
| 461 | + /* |
| 462 | + * Hold a ref to the vm_bo between the msm_gem_vma_close() and the |
| 463 | + * creation of the new prev/next vma's, in case the vm_bo is tracked |
| 464 | + * in the VM's evict list: |
| 465 | + */ |
| 466 | + if (vm_bo) |
| 467 | + drm_gpuvm_bo_get(vm_bo); |
| 468 | + |
| 469 | + /* |
| 470 | + * The prev_vma and/or next_vma are replacing the unmapped vma, and |
| 471 | + * therefore should preserve it's flags: |
| 472 | + */ |
| 473 | + flags = orig_vma->flags; |
| 474 | + |
| 475 | + msm_gem_vma_close(orig_vma); |
| 476 | + |
| 477 | + if (op->remap.prev) { |
| 478 | + prev_vma = vma_from_op(arg, op->remap.prev); |
| 479 | + if (WARN_ON(IS_ERR(prev_vma))) |
| 480 | + return PTR_ERR(prev_vma); |
| 481 | + |
| 482 | + vm_dbg("prev_vma: %p:%p: %016llx %016llx", vm, prev_vma, prev_vma->va.addr, prev_vma->va.range); |
| 483 | + to_msm_vma(prev_vma)->mapped = mapped; |
| 484 | + prev_vma->flags = flags; |
| 485 | + } |
| 486 | + |
| 487 | + if (op->remap.next) { |
| 488 | + next_vma = vma_from_op(arg, op->remap.next); |
| 489 | + if (WARN_ON(IS_ERR(next_vma))) |
| 490 | + return PTR_ERR(next_vma); |
| 491 | + |
| 492 | + vm_dbg("next_vma: %p:%p: %016llx %016llx", vm, next_vma, next_vma->va.addr, next_vma->va.range); |
| 493 | + to_msm_vma(next_vma)->mapped = mapped; |
| 494 | + next_vma->flags = flags; |
| 495 | + } |
| 496 | + |
| 497 | + if (!mapped) |
| 498 | + drm_gpuvm_bo_evict(vm_bo, true); |
| 499 | + |
| 500 | + /* Drop the previous ref: */ |
| 501 | + drm_gpuvm_bo_put(vm_bo); |
| 502 | + |
| 503 | + return 0; |
| 504 | +} |
| 505 | + |
| 506 | +static int |
| 507 | +msm_gem_vm_sm_step_unmap(struct drm_gpuva_op *op, void *arg) |
| 508 | +{ |
| 509 | + struct drm_gpuva *vma = op->unmap.va; |
| 510 | + struct msm_gem_vma *msm_vma = to_msm_vma(vma); |
| 511 | + |
| 512 | + vm_dbg("%p:%p:%p: %016llx %016llx", vma->vm, vma, vma->gem.obj, |
| 513 | + vma->va.addr, vma->va.range); |
| 514 | + |
| 515 | + if (!msm_vma->mapped) |
| 516 | + goto out_close; |
| 517 | + |
| 518 | + vm_op_enqueue(arg, (struct msm_vm_op){ |
| 519 | + .op = MSM_VM_OP_UNMAP, |
| 520 | + .unmap = { |
| 521 | + .iova = vma->va.addr, |
| 522 | + .range = vma->va.range, |
| 523 | + }, |
| 524 | + .obj = vma->gem.obj, |
| 525 | + }); |
| 526 | + |
| 527 | + msm_vma->mapped = false; |
| 528 | + |
| 529 | +out_close: |
| 530 | + msm_gem_vma_close(vma); |
| 531 | + |
| 532 | + return 0; |
| 533 | +} |
| 534 | + |
| 203 | 535 | static const struct drm_gpuvm_ops msm_gpuvm_ops = { |
| 204 | 536 | .vm_free = msm_gem_vm_free, |
| 537 | + .vm_bo_validate = msm_gem_vm_bo_validate, |
| 538 | + .sm_step_map = msm_gem_vm_sm_step_map, |
| 539 | + .sm_step_remap = msm_gem_vm_sm_step_remap, |
| 540 | + .sm_step_unmap = msm_gem_vm_sm_step_unmap, |
| 205 | 541 | }; |
| 206 | 542 | |
| 543 | +static struct dma_fence * |
| 544 | +msm_vma_job_run(struct drm_sched_job *_job) |
| 545 | +{ |
| 546 | + struct msm_vm_bind_job *job = to_msm_vm_bind_job(_job); |
| 547 | + struct msm_gem_vm *vm = to_msm_vm(job->vm); |
| 548 | + struct drm_gem_object *obj; |
| 549 | + int ret = vm->unusable ? -EINVAL : 0; |
| 550 | + |
| 551 | + vm_dbg(""); |
| 552 | + |
| 553 | + mutex_lock(&vm->mmu_lock); |
| 554 | + vm->mmu->prealloc = &job->prealloc; |
| 555 | + |
| 556 | + while (!list_empty(&job->vm_ops)) { |
| 557 | + struct msm_vm_op *op = |
| 558 | + list_first_entry(&job->vm_ops, struct msm_vm_op, node); |
| 559 | + |
| 560 | + switch (op->op) { |
| 561 | + case MSM_VM_OP_MAP: |
| 562 | + /* |
| 563 | + * On error, stop trying to map new things.. but we |
| 564 | + * still want to process the unmaps (or in particular, |
| 565 | + * the drm_gem_object_put()s) |
| 566 | + */ |
| 567 | + if (!ret) |
| 568 | + ret = vm_map_op(vm, &op->map); |
| 569 | + break; |
| 570 | + case MSM_VM_OP_UNMAP: |
| 571 | + vm_unmap_op(vm, &op->unmap); |
| 572 | + break; |
| 573 | + } |
| 574 | + drm_gem_object_put(op->obj); |
| 575 | + list_del(&op->node); |
| 576 | + kfree(op); |
| 577 | + } |
| 578 | + |
| 579 | + vm->mmu->prealloc = NULL; |
| 580 | + mutex_unlock(&vm->mmu_lock); |
| 581 | + |
| 582 | + /* |
| 583 | + * We failed to perform at least _some_ of the pgtable updates, so |
| 584 | + * now the VM is in an undefined state. Game over! |
| 585 | + */ |
| 586 | + if (ret) |
| 587 | + vm->unusable = true; |
| 588 | + |
| 589 | + job_foreach_bo (obj, job) { |
| 590 | + msm_gem_lock(obj); |
| 591 | + msm_gem_unpin_locked(obj); |
| 592 | + msm_gem_unlock(obj); |
| 593 | + } |
| 594 | + |
| 595 | + /* VM_BIND ops are synchronous, so no fence to wait on: */ |
| 596 | + return NULL; |
| 597 | +} |
| 598 | + |
| 599 | +static void |
| 600 | +msm_vma_job_free(struct drm_sched_job *_job) |
| 601 | +{ |
| 602 | + struct msm_vm_bind_job *job = to_msm_vm_bind_job(_job); |
| 603 | + struct msm_gem_vm *vm = to_msm_vm(job->vm); |
| 604 | + struct drm_gem_object *obj; |
| 605 | + |
| 606 | + vm->mmu->funcs->prealloc_cleanup(vm->mmu, &job->prealloc); |
| 607 | + |
| 608 | + drm_sched_job_cleanup(_job); |
| 609 | + |
| 610 | + job_foreach_bo (obj, job) |
| 611 | + drm_gem_object_put(obj); |
| 612 | + |
| 613 | + msm_submitqueue_put(job->queue); |
| 614 | + dma_fence_put(job->fence); |
| 615 | + |
| 616 | + /* In error paths, we could have unexecuted ops: */ |
| 617 | + while (!list_empty(&job->vm_ops)) { |
| 618 | + struct msm_vm_op *op = |
| 619 | + list_first_entry(&job->vm_ops, struct msm_vm_op, node); |
| 620 | + list_del(&op->node); |
| 621 | + kfree(op); |
| 622 | + } |
| 623 | + |
| 624 | + kfree(job); |
| 625 | +} |
| 626 | + |
| 207 | 627 | static const struct drm_sched_backend_ops msm_vm_bind_ops = { |
| 628 | + .run_job = msm_vma_job_run, |
| 629 | + .free_job = msm_vma_job_free |
| 208 | 630 | }; |
| 209 | 631 | |
| 210 | 632 | /** |
| @@ -268,6 +690,7 @@ msm_gem_vm_create(struct drm_device *drm, struct msm_mmu *mmu, const char *name, · 57 unmodified lines |
| 268 | 690 | drm_gem_object_put(dummy_gem); |
| 269 | 691 | |
| 270 | 692 | vm->mmu = mmu; |
| 693 | + mutex_init(&vm->mmu_lock); |
| 271 | 694 | vm->managed = managed; |
| 272 | 695 | |
| 273 | 696 | drm_mm_init(&vm->mm, va_start, va_size); |
| @@ -280,7 +703,6 @@ err_free_dummy: · 6 unmodified lines |
| 280 | 703 | err_free_vm: |
| 281 | 704 | kfree(vm); |
| 282 | 705 | return ERR_PTR(ret); |
| 283 | | − |
| 284 | 706 | } |
| 285 | 707 | |
| 286 | 708 | /** |
| @@ -296,6 +718,7 @@ msm_gem_vm_close(struct drm_gpuvm *gpuvm) · 9 unmodified lines |
| 296 | 718 | { |
| 297 | 719 | struct msm_gem_vm *vm = to_msm_vm(gpuvm); |
| 298 | 720 | struct drm_gpuva *vma, *tmp; |
| 721 | + struct drm_exec exec; |
| 299 | 722 | |
| 300 | 723 | /* |
| 301 | 724 | * For kernel managed VMs, the VMAs are torn down when the handle is |
| @@ -312,22 +735,656 @@ msm_gem_vm_close(struct drm_gpuvm *gpuvm) · 10 unmodified lines |
| 312 | 735 | drm_sched_fini(&vm->sched); |
| 313 | 736 | |
| 314 | 737 | /* Tear down any remaining mappings: */ |
| 315 | | − dma_resv_lock(drm_gpuvm_resv(gpuvm), NULL); |
| 316 | | − drm_gpuvm_for_each_va_safe (vma, tmp, gpuvm) { |
| 317 | | − struct drm_gem_object *obj = vma->gem.obj; |
| 738 | + drm_exec_init(&exec, 0, 2); |
| 739 | + drm_exec_until_all_locked (&exec) { |
| 740 | + drm_exec_lock_obj(&exec, drm_gpuvm_resv_obj(gpuvm)); |
| 741 | + drm_exec_retry_on_contention(&exec); |
| 318 | 742 | |
| 319 | | − if (obj && obj->resv != drm_gpuvm_resv(gpuvm)) { |
| 320 | | − drm_gem_object_get(obj); |
| 321 | | − msm_gem_lock(obj); |
| 743 | + drm_gpuvm_for_each_va_safe (vma, tmp, gpuvm) { |
| 744 | + struct drm_gem_object *obj = vma->gem.obj; |
| 745 | + |
| 746 | + /* |
| 747 | + * MSM_BO_NO_SHARE objects share the same resv as the |
| 748 | + * VM, in which case the obj is already locked: |
| 749 | + */ |
| 750 | + if (obj && (obj->resv == drm_gpuvm_resv(gpuvm))) |
| 751 | + obj = NULL; |
| 752 | + |
| 753 | + if (obj) { |
| 754 | + drm_exec_lock_obj(&exec, obj); |
| 755 | + drm_exec_retry_on_contention(&exec); |
| 756 | + } |
| 757 | + |
| 758 | + msm_gem_vma_unmap(vma); |
| 759 | + msm_gem_vma_close(vma); |
| 760 | + |
| 761 | + if (obj) { |
| 762 | + drm_exec_unlock_obj(&exec, obj); |
| 763 | + } |
| 322 | 764 | } |
| 765 | + } |
| 766 | + drm_exec_fini(&exec); |
| 767 | +} |
| 768 | + |
| 769 | + |
| 770 | +static struct msm_vm_bind_job * |
| 771 | +vm_bind_job_create(struct drm_device *dev, struct drm_file *file, |
| 772 | + struct msm_gpu_submitqueue *queue, uint32_t nr_ops) |
| 773 | +{ |
| 774 | + struct msm_vm_bind_job *job; |
| 775 | + uint64_t sz; |
| 776 | + int ret; |
| 777 | + |
| 778 | + sz = struct_size(job, ops, nr_ops); |
| 779 | + |
| 780 | + if (sz > SIZE_MAX) |
| 781 | + return ERR_PTR(-ENOMEM); |
| 782 | + |
| 783 | + job = kzalloc(sz, GFP_KERNEL | __GFP_NOWARN); |
| 784 | + if (!job) |
| 785 | + return ERR_PTR(-ENOMEM); |
| 786 | + |
| 787 | + ret = drm_sched_job_init(&job->base, queue->entity, 1, queue, |
| 788 | + file->client_id); |
| 789 | + if (ret) { |
| 790 | + kfree(job); |
| 791 | + return ERR_PTR(ret); |
| 792 | + } |
| 323 | 793 | |
| 324 | | − msm_gem_vma_unmap(vma); |
| 325 | | − msm_gem_vma_close(vma); |
| 794 | + job->vm = msm_context_vm(dev, queue->ctx); |
| 795 | + job->queue = queue; |
| 796 | + INIT_LIST_HEAD(&job->vm_ops); |
| 326 | 797 | |
| 327 | | − if (obj && obj->resv != drm_gpuvm_resv(gpuvm)) { |
| 328 | | − msm_gem_unlock(obj); |
| 329 | | − drm_gem_object_put(obj); |
| 798 | + return job; |
| 799 | +} |
| 800 | + |
| 801 | +static bool invalid_alignment(uint64_t addr) |
| 802 | +{ |
| 803 | + /* |
| 804 | + * Technically this is about GPU alignment, not CPU alignment. But |
| 805 | + * I've not seen any qcom SoC where the SMMU does not support the |
| 806 | + * CPU's smallest page size. |
| 807 | + */ |
| 808 | + return !PAGE_ALIGNED(addr); |
| 809 | +} |
| 810 | + |
| 811 | +static int |
| 812 | +lookup_op(struct msm_vm_bind_job *job, const struct drm_msm_vm_bind_op *op) |
| 813 | +{ |
| 814 | + struct drm_device *dev = job->vm->drm; |
| 815 | + int i = job->nr_ops++; |
| 816 | + int ret = 0; |
| 817 | + |
| 818 | + job->ops[i].op = op->op; |
| 819 | + job->ops[i].handle = op->handle; |
| 820 | + job->ops[i].obj_offset = op->obj_offset; |
| 821 | + job->ops[i].iova = op->iova; |
| 822 | + job->ops[i].range = op->range; |
| 823 | + job->ops[i].flags = op->flags; |
| 824 | + |
| 825 | + if (op->flags & ~MSM_VM_BIND_OP_FLAGS) |
| 826 | + ret = UERR(EINVAL, dev, "invalid flags: %x\n", op->flags); |
| 827 | + |
| 828 | + if (invalid_alignment(op->iova)) |
| 829 | + ret = UERR(EINVAL, dev, "invalid address: %016llx\n", op->iova); |
| 830 | + |
| 831 | + if (invalid_alignment(op->obj_offset)) |
| 832 | + ret = UERR(EINVAL, dev, "invalid bo_offset: %016llx\n", op->obj_offset); |
| 833 | + |
| 834 | + if (invalid_alignment(op->range)) |
| 835 | + ret = UERR(EINVAL, dev, "invalid range: %016llx\n", op->range); |
| 836 | + |
| 837 | + if (!drm_gpuvm_range_valid(job->vm, op->iova, op->range)) |
| 838 | + ret = UERR(EINVAL, dev, "invalid range: %016llx, %016llx\n", op->iova, op->range); |
| 839 | + |
| 840 | + /* |
| 841 | + * MAP must specify a valid handle. But the handle MBZ for |
| 842 | + * UNMAP or MAP_NULL. |
| 843 | + */ |
| 844 | + if (op->op == MSM_VM_BIND_OP_MAP) { |
| 845 | + if (!op->handle) |
| 846 | + ret = UERR(EINVAL, dev, "invalid handle\n"); |
| 847 | + } else if (op->handle) { |
| 848 | + ret = UERR(EINVAL, dev, "handle must be zero\n"); |
| 849 | + } |
| 850 | + |
| 851 | + switch (op->op) { |
| 852 | + case MSM_VM_BIND_OP_MAP: |
| 853 | + case MSM_VM_BIND_OP_MAP_NULL: |
| 854 | + case MSM_VM_BIND_OP_UNMAP: |
| 855 | + break; |
| 856 | + default: |
| 857 | + ret = UERR(EINVAL, dev, "invalid op: %u\n", op->op); |
| 858 | + break; |
| 859 | + } |
| 860 | + |
| 861 | + return ret; |
| 862 | +} |
| 863 | + |
| 864 | +/* |
| 865 | + * ioctl parsing, parameter validation, and GEM handle lookup |
| 866 | + */ |
| 867 | +static int |
| 868 | +vm_bind_job_lookup_ops(struct msm_vm_bind_job *job, struct drm_msm_vm_bind *args, |
| 869 | + struct drm_file *file, int *nr_bos) |
| 870 | +{ |
| 871 | + struct drm_device *dev = job->vm->drm; |
| 872 | + int ret = 0; |
| 873 | + int cnt = 0; |
| 874 | + |
| 875 | + if (args->nr_ops == 1) { |
| 876 | + /* Single op case, the op is inlined: */ |
| 877 | + ret = lookup_op(job, &args->op); |
| 878 | + } else { |
| 879 | + for (unsigned i = 0; i < args->nr_ops; i++) { |
| 880 | + struct drm_msm_vm_bind_op op; |
| 881 | + void __user *userptr = |
| 882 | + u64_to_user_ptr(args->ops + (i * sizeof(op))); |
| 883 | + |
| 884 | + /* make sure we don't have garbage flags, in case we hit |
| 885 | + * error path before flags is initialized: |
| 886 | + */ |
| 887 | + job->ops[i].flags = 0; |
| 888 | + |
| 889 | + if (copy_from_user(&op, userptr, sizeof(op))) { |
| 890 | + ret = -EFAULT; |
| 891 | + break; |
| 892 | + } |
| 893 | + |
| 894 | + ret = lookup_op(job, &op); |
| 895 | + if (ret) |
| 896 | + break; |
| 897 | + } |
| 898 | + } |
| 899 | + |
| 900 | + if (ret) { |
| 901 | + job->nr_ops = 0; |
| 902 | + goto out; |
| 903 | + } |
| 904 | + |
| 905 | + spin_lock(&file->table_lock); |
| 906 | + |
| 907 | + for (unsigned i = 0; i < args->nr_ops; i++) { |
| 908 | + struct drm_gem_object *obj; |
| 909 | + |
| 910 | + if (!job->ops[i].handle) { |
| 911 | + job->ops[i].obj = NULL; |
| 912 | + continue; |
| 913 | + } |
| 914 | + |
| 915 | + /* |
| 916 | + * normally use drm_gem_object_lookup(), but for bulk lookup |
| 917 | + * all under single table_lock just hit object_idr directly: |
| 918 | + */ |
| 919 | + obj = idr_find(&file->object_idr, job->ops[i].handle); |
| 920 | + if (!obj) { |
| 921 | + ret = UERR(EINVAL, dev, "invalid handle %u at index %u\n", job->ops[i].handle, i); |
| 922 | + goto out_unlock; |
| 923 | + } |
| 924 | + |
| 925 | + drm_gem_object_get(obj); |
| 926 | + |
| 927 | + job->ops[i].obj = obj; |
| 928 | + cnt++; |
| 929 | + } |
| 930 | + |
| 931 | + *nr_bos = cnt; |
| 932 | + |
| 933 | +out_unlock: |
| 934 | + spin_unlock(&file->table_lock); |
| 935 | + |
| 936 | +out: |
| 937 | + return ret; |
| 938 | +} |
| 939 | + |
| 940 | +static void |
| 941 | +prealloc_count(struct msm_vm_bind_job *job, |
| 942 | + struct msm_vm_bind_op *first, |
| 943 | + struct msm_vm_bind_op *last) |
| 944 | +{ |
| 945 | + struct msm_mmu *mmu = to_msm_vm(job->vm)->mmu; |
| 946 | + |
| 947 | + if (!first) |
| 948 | + return; |
| 949 | + |
| 950 | + uint64_t start_iova = first->iova; |
| 951 | + uint64_t end_iova = last->iova + last->range; |
| 952 | + |
| 953 | + mmu->funcs->prealloc_count(mmu, &job->prealloc, start_iova, end_iova - start_iova); |
| 954 | +} |
| 955 | + |
| 956 | +static bool |
| 957 | +ops_are_same_pte(struct msm_vm_bind_op *first, struct msm_vm_bind_op *next) |
| 958 | +{ |
| 959 | + /* |
| 960 | + * Last level pte covers 2MB.. so we should merge two ops, from |
| 961 | + * the PoV of figuring out how much pgtable pages to pre-allocate |
| 962 | + * if they land in the same 2MB range: |
| 963 | + */ |
| 964 | + uint64_t pte_mask = ~(SZ_2M - 1); |
| 965 | + return ((first->iova + first->range) & pte_mask) == (next->iova & pte_mask); |
| 966 | +} |
| 967 | + |
| 968 | +/* |
| 969 | + * Determine the amount of memory to prealloc for pgtables. For sparse images, |
| 970 | + * in particular, userspace plays some tricks with the order of page mappings |
| 971 | + * to get the desired swizzle pattern, resulting in a large # of tiny MAP ops. |
| 972 | + * So detect when multiple MAP operations are physically contiguous, and count |
| 973 | + * them as a single mapping. Otherwise the prealloc_count() will not realize |
| 974 | + * they can share pagetable pages and vastly overcount. |
| 975 | + */ |
| 976 | +static void |
| 977 | +vm_bind_prealloc_count(struct msm_vm_bind_job *job) |
| 978 | +{ |
| 979 | + struct msm_vm_bind_op *first = NULL, *last = NULL; |
| 980 | + |
| 981 | + for (int i = 0; i < job->nr_ops; i++) { |
| 982 | + struct msm_vm_bind_op *op = &job->ops[i]; |
| 983 | + |
| 984 | + /* We only care about MAP/MAP_NULL: */ |
| 985 | + if (op->op == MSM_VM_BIND_OP_UNMAP) |
| 986 | + continue; |
| 987 | + |
| 988 | + /* |
| 989 | + * If op is contiguous with last in the current range, then |
| 990 | + * it becomes the new last in the range and we continue |
| 991 | + * looping: |
| 992 | + */ |
| 993 | + if (last && ops_are_same_pte(last, op)) { |
| 994 | + last = op; |
| 995 | + continue; |
| 996 | + } |
| 997 | + |
| 998 | + /* |
| 999 | + * If op is not contiguous with the current range, flush |
| 1000 | + * the current range and start anew: |
| 1001 | + */ |
| 1002 | + prealloc_count(job, first, last); |
| 1003 | + first = last = op; |
| 1004 | + } |
| 1005 | + |
| 1006 | + /* Flush the remaining range: */ |
| 1007 | + prealloc_count(job, first, last); |
| 1008 | +} |
| 1009 | + |
| 1010 | +/* |
| 1011 | + * Lock VM and GEM objects |
| 1012 | + */ |
| 1013 | +static int |
| 1014 | +vm_bind_job_lock_objects(struct msm_vm_bind_job *job, struct drm_exec *exec) |
| 1015 | +{ |
| 1016 | + int ret; |
| 1017 | + |
| 1018 | + /* Lock VM and objects: */ |
| 1019 | + drm_exec_until_all_locked (exec) { |
| 1020 | + ret = drm_exec_lock_obj(exec, drm_gpuvm_resv_obj(job->vm)); |
| 1021 | + drm_exec_retry_on_contention(exec); |
| 1022 | + if (ret) |
| 1023 | + return ret; |
| 1024 | + |
| 1025 | + for (unsigned i = 0; i < job->nr_ops; i++) { |
| 1026 | + const struct msm_vm_bind_op *op = &job->ops[i]; |
| 1027 | + |
| 1028 | + switch (op->op) { |
| 1029 | + case MSM_VM_BIND_OP_UNMAP: |
| 1030 | + ret = drm_gpuvm_sm_unmap_exec_lock(job->vm, exec, |
| 1031 | + op->iova, |
| 1032 | + op->obj_offset); |
| 1033 | + break; |
| 1034 | + case MSM_VM_BIND_OP_MAP: |
| 1035 | + case MSM_VM_BIND_OP_MAP_NULL: |
| 1036 | + ret = drm_gpuvm_sm_map_exec_lock(job->vm, exec, 1, |
| 1037 | + op->iova, op->range, |
| 1038 | + op->obj, op->obj_offset); |
| 1039 | + break; |
| 1040 | + default: |
| 1041 | + /* |
| 1042 | + * lookup_op() should have already thrown an error for |
| 1043 | + * invalid ops |
| 1044 | + */ |
| 1045 | + WARN_ON("unreachable"); |
| 1046 | + } |
| 1047 | + |
| 1048 | + drm_exec_retry_on_contention(exec); |
| 1049 | + if (ret) |
| 1050 | + return ret; |
| 1051 | + } |
| 1052 | + } |
| 1053 | + |
| 1054 | + return 0; |
| 1055 | +} |
| 1056 | + |
| 1057 | +/* |
| 1058 | + * Pin GEM objects, ensuring that we have backing pages. Pinning will move |
| 1059 | + * the object to the pinned LRU so that the shrinker knows to first consider |
| 1060 | + * other objects for evicting. |
| 1061 | + */ |
| 1062 | +static int |
| 1063 | +vm_bind_job_pin_objects(struct msm_vm_bind_job *job) |
| 1064 | +{ |
| 1065 | + struct drm_gem_object *obj; |
| 1066 | + |
| 1067 | + /* |
| 1068 | + * First loop, before holding the LRU lock, avoids holding the |
| 1069 | + * LRU lock while calling msm_gem_pin_vma_locked (which could |
| 1070 | + * trigger get_pages()) |
| 1071 | + */ |
| 1072 | + job_foreach_bo (obj, job) { |
| 1073 | + struct page **pages; |
| 1074 | + |
| 1075 | + pages = msm_gem_get_pages_locked(obj, MSM_MADV_WILLNEED); |
| 1076 | + if (IS_ERR(pages)) |
| 1077 | + return PTR_ERR(pages); |
| 1078 | + } |
| 1079 | + |
| 1080 | + struct msm_drm_private *priv = job->vm->drm->dev_private; |
| 1081 | + |
| 1082 | + /* |
| 1083 | + * A second loop while holding the LRU lock (a) avoids acquiring/dropping |
| 1084 | + * the LRU lock for each individual bo, while (b) avoiding holding the |
| 1085 | + * LRU lock while calling msm_gem_pin_vma_locked() (which could trigger |
| 1086 | + * get_pages() which could trigger reclaim.. and if we held the LRU lock |
| 1087 | + * could trigger deadlock with the shrinker). |
| 1088 | + */ |
| 1089 | + mutex_lock(&priv->lru.lock); |
| 1090 | + job_foreach_bo (obj, job) |
| 1091 | + msm_gem_pin_obj_locked(obj); |
| 1092 | + mutex_unlock(&priv->lru.lock); |
| 1093 | + |
| 1094 | + job->bos_pinned = true; |
| 1095 | + |
| 1096 | + return 0; |
| 1097 | +} |
| 1098 | + |
| 1099 | +/* |
| 1100 | + * Unpin GEM objects. Normally this is done after the bind job is run. |
| 1101 | + */ |
| 1102 | +static void |
| 1103 | +vm_bind_job_unpin_objects(struct msm_vm_bind_job *job) |
| 1104 | +{ |
| 1105 | + struct drm_gem_object *obj; |
| 1106 | + |
| 1107 | + if (!job->bos_pinned) |
| 1108 | + return; |
| 1109 | + |
| 1110 | + job_foreach_bo (obj, job) |
| 1111 | + msm_gem_unpin_locked(obj); |
| 1112 | + |
| 1113 | + job->bos_pinned = false; |
| 1114 | +} |
| 1115 | + |
| 1116 | +/* |
| 1117 | + * Pre-allocate pgtable memory, and translate the VM bind requests into a |
| 1118 | + * sequence of pgtable updates to be applied asynchronously. |
| 1119 | + */ |
| 1120 | +static int |
| 1121 | +vm_bind_job_prepare(struct msm_vm_bind_job *job) |
| 1122 | +{ |
| 1123 | + struct msm_gem_vm *vm = to_msm_vm(job->vm); |
| 1124 | + struct msm_mmu *mmu = vm->mmu; |
| 1125 | + int ret; |
| 1126 | + |
| 1127 | + ret = mmu->funcs->prealloc_allocate(mmu, &job->prealloc); |
| 1128 | + if (ret) |
| 1129 | + return ret; |
| 1130 | + |
| 1131 | + for (unsigned i = 0; i < job->nr_ops; i++) { |
| 1132 | + const struct msm_vm_bind_op *op = &job->ops[i]; |
| 1133 | + struct op_arg arg = { |
| 1134 | + .job = job, |
| 1135 | + }; |
| 1136 | + |
| 1137 | + switch (op->op) { |
| 1138 | + case MSM_VM_BIND_OP_UNMAP: |
| 1139 | + ret = drm_gpuvm_sm_unmap(job->vm, &arg, op->iova, |
| 1140 | + op->range); |
| 1141 | + break; |
| 1142 | + case MSM_VM_BIND_OP_MAP: |
| 1143 | + if (op->flags & MSM_VM_BIND_OP_DUMP) |
| 1144 | + arg.flags |= MSM_VMA_DUMP; |
| 1145 | + fallthrough; |
| 1146 | + case MSM_VM_BIND_OP_MAP_NULL: |
| 1147 | + ret = drm_gpuvm_sm_map(job->vm, &arg, op->iova, |
| 1148 | + op->range, op->obj, op->obj_offset); |
| 1149 | + break; |
| 1150 | + default: |
| 1151 | + /* |
| 1152 | + * lookup_op() should have already thrown an error for |
| 1153 | + * invalid ops |
| 1154 | + */ |
| 1155 | + BUG_ON("unreachable"); |
| 1156 | + } |
| 1157 | + |
| 1158 | + if (ret) { |
| 1159 | + /* |
| 1160 | + * If we've already started modifying the vm, we can't |
| 1161 | + * adequetly describe to userspace the intermediate |
| 1162 | + * state the vm is in. So throw up our hands! |
| 1163 | + */ |
| 1164 | + if (i > 0) |
| 1165 | + vm->unusable = true; |
| 1166 | + return ret; |
| 1167 | + } |
| 1168 | + } |
| 1169 | + |
| 1170 | + return 0; |
| 1171 | +} |
| 1172 | + |
| 1173 | +/* |
| 1174 | + * Attach fences to the GEM objects being bound. This will signify to |
| 1175 | + * the shrinker that they are busy even after dropping the locks (ie. |
| 1176 | + * drm_exec_fini()) |
| 1177 | + */ |
| 1178 | +static void |
| 1179 | +vm_bind_job_attach_fences(struct msm_vm_bind_job *job) |
| 1180 | +{ |
| 1181 | + for (unsigned i = 0; i < job->nr_ops; i++) { |
| 1182 | + struct drm_gem_object *obj = job->ops[i].obj; |
| 1183 | + |
| 1184 | + if (!obj) |
| 1185 | + continue; |
| 1186 | + |
| 1187 | + dma_resv_add_fence(obj->resv, job->fence, |
| 1188 | + DMA_RESV_USAGE_KERNEL); |
| 1189 | + } |
| 1190 | +} |
| 1191 | + |
| 1192 | +int |
| 1193 | +msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file) |
| 1194 | +{ |
| 1195 | + struct msm_drm_private *priv = dev->dev_private; |
| 1196 | + struct drm_msm_vm_bind *args = data; |
| 1197 | + struct msm_context *ctx = file->driver_priv; |
| 1198 | + struct msm_vm_bind_job *job = NULL; |
| 1199 | + struct msm_gpu *gpu = priv->gpu; |
| 1200 | + struct msm_gpu_submitqueue *queue; |
| 1201 | + struct msm_syncobj_post_dep *post_deps = NULL; |
| 1202 | + struct drm_syncobj **syncobjs_to_reset = NULL; |
| 1203 | + struct sync_file *sync_file = NULL; |
| 1204 | + struct dma_fence *fence; |
| 1205 | + int out_fence_fd = -1; |
| 1206 | + int ret, nr_bos = 0; |
| 1207 | + unsigned i; |
| 1208 | + |
| 1209 | + if (!gpu) |
| 1210 | + return -ENXIO; |
| 1211 | + |
| 1212 | + /* |
| 1213 | + * Maybe we could allow just UNMAP ops? OTOH userspace should just |
| 1214 | + * immediately close the device file and all will be torn down. |
| 1215 | + */ |
| 1216 | + if (to_msm_vm(ctx->vm)->unusable) |
| 1217 | + return UERR(EPIPE, dev, "context is unusable"); |
| 1218 | + |
| 1219 | + /* |
| 1220 | + * Technically, you cannot create a VM_BIND submitqueue in the first |
| 1221 | + * place, if you haven't opted in to VM_BIND context. But it is |
| 1222 | + * cleaner / less confusing, to check this case directly. |
| 1223 | + */ |
| 1224 | + if (!msm_context_is_vmbind(ctx)) |
| 1225 | + return UERR(EINVAL, dev, "context does not support vmbind"); |
| 1226 | + |
| 1227 | + if (args->flags & ~MSM_VM_BIND_FLAGS) |
| 1228 | + return UERR(EINVAL, dev, "invalid flags"); |
| 1229 | + |
| 1230 | + queue = msm_submitqueue_get(ctx, args->queue_id); |
| 1231 | + if (!queue) |
| 1232 | + return -ENOENT; |
| 1233 | + |
| 1234 | + if (!(queue->flags & MSM_SUBMITQUEUE_VM_BIND)) { |
| 1235 | + ret = UERR(EINVAL, dev, "Invalid queue type"); |
| 1236 | + goto out_post_unlock; |
| 1237 | + } |
| 1238 | + |
| 1239 | + if (args->flags & MSM_VM_BIND_FENCE_FD_OUT) { |
| 1240 | + out_fence_fd = get_unused_fd_flags(O_CLOEXEC); |
| 1241 | + if (out_fence_fd < 0) { |
| 1242 | + ret = out_fence_fd; |
| 1243 | + goto out_post_unlock; |
| 330 | 1244 | } |
| 331 | 1245 | } |
| 332 | | − dma_resv_unlock(drm_gpuvm_resv(gpuvm)); |
| 1246 | + |
| 1247 | + job = vm_bind_job_create(dev, file, queue, args->nr_ops); |
| 1248 | + if (IS_ERR(job)) { |
| 1249 | + ret = PTR_ERR(job); |
| 1250 | + goto out_post_unlock; |
| 1251 | + } |
| 1252 | + |
| 1253 | + ret = mutex_lock_interruptible(&queue->lock); |
| 1254 | + if (ret) |
| 1255 | + goto out_post_unlock; |
| 1256 | + |
| 1257 | + if (args->flags & MSM_VM_BIND_FENCE_FD_IN) { |
| 1258 | + struct dma_fence *in_fence; |
| 1259 | + |
| 1260 | + in_fence = sync_file_get_fence(args->fence_fd); |
| 1261 | + |
| 1262 | + if (!in_fence) { |
| 1263 | + ret = UERR(EINVAL, dev, "invalid in-fence"); |
| 1264 | + goto out_unlock; |
| 1265 | + } |
| 1266 | + |
| 1267 | + ret = drm_sched_job_add_dependency(&job->base, in_fence); |
| 1268 | + if (ret) |
| 1269 | + goto out_unlock; |
| 1270 | + } |
| 1271 | + |
| 1272 | + if (args->in_syncobjs > 0) { |
| 1273 | + syncobjs_to_reset = msm_syncobj_parse_deps(dev, &job->base, |
| 1274 | + file, args->in_syncobjs, |
| 1275 | + args->nr_in_syncobjs, |
| 1276 | + args->syncobj_stride); |
| 1277 | + if (IS_ERR(syncobjs_to_reset)) { |
| 1278 | + ret = PTR_ERR(syncobjs_to_reset); |
| 1279 | + goto out_unlock; |
| 1280 | + } |
| 1281 | + } |
| 1282 | + |
| 1283 | + if (args->out_syncobjs > 0) { |
| 1284 | + post_deps = msm_syncobj_parse_post_deps(dev, file, |
| 1285 | + args->out_syncobjs, |
| 1286 | + args->nr_out_syncobjs, |
| 1287 | + args->syncobj_stride); |
| 1288 | + if (IS_ERR(post_deps)) { |
| 1289 | + ret = PTR_ERR(post_deps); |
| 1290 | + goto out_unlock; |
| 1291 | + } |
| 1292 | + } |
| 1293 | + |
| 1294 | + ret = vm_bind_job_lookup_ops(job, args, file, &nr_bos); |
| 1295 | + if (ret) |
| 1296 | + goto out_unlock; |
| 1297 | + |
| 1298 | + vm_bind_prealloc_count(job); |
| 1299 | + |
| 1300 | + struct drm_exec exec; |
| 1301 | + unsigned flags = DRM_EXEC_IGNORE_DUPLICATES | DRM_EXEC_INTERRUPTIBLE_WAIT; |
| 1302 | + drm_exec_init(&exec, flags, nr_bos + 1); |
| 1303 | + |
| 1304 | + ret = vm_bind_job_lock_objects(job, &exec); |
| 1305 | + if (ret) |
| 1306 | + goto out; |
| 1307 | + |
| 1308 | + ret = vm_bind_job_pin_objects(job); |
| 1309 | + if (ret) |
| 1310 | + goto out; |
| 1311 | + |
| 1312 | + ret = vm_bind_job_prepare(job); |
| 1313 | + if (ret) |
| 1314 | + goto out; |
| 1315 | + |
| 1316 | + drm_sched_job_arm(&job->base); |
| 1317 | + |
| 1318 | + job->fence = dma_fence_get(&job->base.s_fence->finished); |
| 1319 | + |
| 1320 | + if (args->flags & MSM_VM_BIND_FENCE_FD_OUT) { |
| 1321 | + sync_file = sync_file_create(job->fence); |
| 1322 | + if (!sync_file) { |
| 1323 | + ret = -ENOMEM; |
| 1324 | + } else { |
| 1325 | + fd_install(out_fence_fd, sync_file->file); |
| 1326 | + args->fence_fd = out_fence_fd; |
| 1327 | + } |
| 1328 | + } |
| 1329 | + |
| 1330 | + if (ret) |
| 1331 | + goto out; |
| 1332 | + |
| 1333 | + vm_bind_job_attach_fences(job); |
| 1334 | + |
| 1335 | + /* |
| 1336 | + * The job can be free'd (and fence unref'd) at any point after |
| 1337 | + * drm_sched_entity_push_job(), so we need to hold our own ref |
| 1338 | + */ |
| 1339 | + fence = dma_fence_get(job->fence); |
| 1340 | + |
| 1341 | + drm_sched_entity_push_job(&job->base); |
| 1342 | + |
| 1343 | + msm_syncobj_reset(syncobjs_to_reset, args->nr_in_syncobjs); |
| 1344 | + msm_syncobj_process_post_deps(post_deps, args->nr_out_syncobjs, fence); |
| 1345 | + |
| 1346 | + dma_fence_put(fence); |
| 1347 | + |
| 1348 | +out: |
| 1349 | + if (ret) |
| 1350 | + vm_bind_job_unpin_objects(job); |
| 1351 | + |
| 1352 | + drm_exec_fini(&exec); |
| 1353 | +out_unlock: |
| 1354 | + mutex_unlock(&queue->lock); |
| 1355 | +out_post_unlock: |
| 1356 | + if (ret && (out_fence_fd >= 0)) { |
| 1357 | + put_unused_fd(out_fence_fd); |
| 1358 | + if (sync_file) |
| 1359 | + fput(sync_file->file); |
| 1360 | + } |
| 1361 | + |
| 1362 | + if (!IS_ERR_OR_NULL(job)) { |
| 1363 | + if (ret) |
| 1364 | + msm_vma_job_free(&job->base); |
| 1365 | + } else { |
| 1366 | + /* |
| 1367 | + * If the submit hasn't yet taken ownership of the queue |
| 1368 | + * then we need to drop the reference ourself: |
| 1369 | + */ |
| 1370 | + msm_submitqueue_put(queue); |
| 1371 | + } |
| 1372 | + |
| 1373 | + if (!IS_ERR_OR_NULL(post_deps)) { |
| 1374 | + for (i = 0; i < args->nr_out_syncobjs; ++i) { |
| 1375 | + kfree(post_deps[i].chain); |
| 1376 | + drm_syncobj_put(post_deps[i].syncobj); |
| 1377 | + } |
| 1378 | + kfree(post_deps); |
| 1379 | + } |
| 1380 | + |
| 1381 | + if (!IS_ERR_OR_NULL(syncobjs_to_reset)) { |
| 1382 | + for (i = 0; i < args->nr_in_syncobjs; ++i) { |
| 1383 | + if (syncobjs_to_reset[i]) |
| 1384 | + drm_syncobj_put(syncobjs_to_reset[i]); |
| 1385 | + } |
| 1386 | + kfree(syncobjs_to_reset); |
| 1387 | + } |
| 1388 | + |
| 1389 | + return ret; |
| 333 | 1390 | } |