Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/gpu/drm/xe/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 101 kB image not shown  

Quelle  xe_vm.c   Sprache: C

 
// SPDX-License-Identifier: MIT
/*
 * Copyright © 2021 Intel Corporation
 */


#include "xe_vm.h"

#include <linux/dma-fence-array.h>
#include <linux/nospec.h>

#include <drm/drm_drv.h>
#include <drm/drm_exec.h>
#include <drm/drm_print.h>
#include <drm/ttm/ttm_tt.h>
#include <uapi/drm/xe_drm.h>
#include <linux/ascii85.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/mm.h>
#include <linux/swap.h>

#include <generated/xe_wa_oob.h>

#include "regs/xe_gtt_defs.h"
#include "xe_assert.h"
#include "xe_bo.h"
#include "xe_device.h"
#include "xe_drm_client.h"
#include "xe_exec_queue.h"
#include "xe_gt_pagefault.h"
#include "xe_gt_tlb_invalidation.h"
#include "xe_migrate.h"
#include "xe_pat.h"
#include "xe_pm.h"
#include "xe_preempt_fence.h"
#include "xe_pt.h"
#include "xe_pxp.h"
#include "xe_res_cursor.h"
#include "xe_svm.h"
#include "xe_sync.h"
#include "xe_trace_bo.h"
#include "xe_wa.h"
#include "xe_hmm.h"

static struct drm_gem_object *xe_vm_obj(struct xe_vm *vm)
{
 return vm->gpuvm.r_obj;
}

/**
 * xe_vma_userptr_check_repin() - Advisory check for repin needed
 * @uvma: The userptr vma
 *
 * Check if the userptr vma has been invalidated since last successful
 * repin. The check is advisory only and can the function can be called
 * without the vm->userptr.notifier_lock held. There is no guarantee that the
 * vma userptr will remain valid after a lockless check, so typically
 * the call needs to be followed by a proper check under the notifier_lock.
 *
 * Return: 0 if userptr vma is valid, -EAGAIN otherwise; repin recommended.
 */

int xe_vma_userptr_check_repin(struct xe_userptr_vma *uvma)
{
 return mmu_interval_check_retry(&uvma->userptr.notifier,
     uvma->userptr.notifier_seq) ?
  -EAGAIN : 0;
}

int xe_vma_userptr_pin_pages(struct xe_userptr_vma *uvma)
{
 struct xe_vma *vma = &uvma->vma;
 struct xe_vm *vm = xe_vma_vm(vma);
 struct xe_device *xe = vm->xe;

 lockdep_assert_held(&vm->lock);
 xe_assert(xe, xe_vma_is_userptr(vma));

 return xe_hmm_userptr_populate_range(uvma, false);
}

static bool preempt_fences_waiting(struct xe_vm *vm)
{
 struct xe_exec_queue *q;

 lockdep_assert_held(&vm->lock);
 xe_vm_assert_held(vm);

 list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
  if (!q->lr.pfence ||
      test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT,
        &q->lr.pfence->flags)) {
   return true;
  }
 }

 return false;
}

static void free_preempt_fences(struct list_head *list)
{
 struct list_head *link, *next;

 list_for_each_safe(link, next, list)
  xe_preempt_fence_free(to_preempt_fence_from_link(link));
}

static int alloc_preempt_fences(struct xe_vm *vm, struct list_head *list,
    unsigned int *count)
{
 lockdep_assert_held(&vm->lock);
 xe_vm_assert_held(vm);

 if (*count >= vm->preempt.num_exec_queues)
  return 0;

 for (; *count < vm->preempt.num_exec_queues; ++(*count)) {
  struct xe_preempt_fence *pfence = xe_preempt_fence_alloc();

  if (IS_ERR(pfence))
   return PTR_ERR(pfence);

  list_move_tail(xe_preempt_fence_link(pfence), list);
 }

 return 0;
}

static int wait_for_existing_preempt_fences(struct xe_vm *vm)
{
 struct xe_exec_queue *q;

 xe_vm_assert_held(vm);

 list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
  if (q->lr.pfence) {
   long timeout = dma_fence_wait(q->lr.pfence, false);

   /* Only -ETIME on fence indicates VM needs to be killed */
   if (timeout < 0 || q->lr.pfence->error == -ETIME)
    return -ETIME;

   dma_fence_put(q->lr.pfence);
   q->lr.pfence = NULL;
  }
 }

 return 0;
}

static bool xe_vm_is_idle(struct xe_vm *vm)
{
 struct xe_exec_queue *q;

 xe_vm_assert_held(vm);
 list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
  if (!xe_exec_queue_is_idle(q))
   return false;
 }

 return true;
}

static void arm_preempt_fences(struct xe_vm *vm, struct list_head *list)
{
 struct list_head *link;
 struct xe_exec_queue *q;

 list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
  struct dma_fence *fence;

  link = list->next;
  xe_assert(vm->xe, link != list);

  fence = xe_preempt_fence_arm(to_preempt_fence_from_link(link),
          q, q->lr.context,
          ++q->lr.seqno);
  dma_fence_put(q->lr.pfence);
  q->lr.pfence = fence;
 }
}

static int add_preempt_fences(struct xe_vm *vm, struct xe_bo *bo)
{
 struct xe_exec_queue *q;
 int err;

 xe_bo_assert_held(bo);

 if (!vm->preempt.num_exec_queues)
  return 0;

 err = dma_resv_reserve_fences(bo->ttm.base.resv, vm->preempt.num_exec_queues);
 if (err)
  return err;

 list_for_each_entry(q, &vm->preempt.exec_queues, lr.link)
  if (q->lr.pfence) {
   dma_resv_add_fence(bo->ttm.base.resv,
        q->lr.pfence,
        DMA_RESV_USAGE_BOOKKEEP);
  }

 return 0;
}

static void resume_and_reinstall_preempt_fences(struct xe_vm *vm,
      struct drm_exec *exec)
{
 struct xe_exec_queue *q;

 lockdep_assert_held(&vm->lock);
 xe_vm_assert_held(vm);

 list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
  q->ops->resume(q);

  drm_gpuvm_resv_add_fence(&vm->gpuvm, exec, q->lr.pfence,
      DMA_RESV_USAGE_BOOKKEEP, DMA_RESV_USAGE_BOOKKEEP);
 }
}

int xe_vm_add_compute_exec_queue(struct xe_vm *vm, struct xe_exec_queue *q)
{
 struct drm_gpuvm_exec vm_exec = {
  .vm = &vm->gpuvm,
  .flags = DRM_EXEC_INTERRUPTIBLE_WAIT,
  .num_fences = 1,
 };
 struct drm_exec *exec = &vm_exec.exec;
 struct dma_fence *pfence;
 int err;
 bool wait;

 xe_assert(vm->xe, xe_vm_in_preempt_fence_mode(vm));

 down_write(&vm->lock);
 err = drm_gpuvm_exec_lock(&vm_exec);
 if (err)
  goto out_up_write;

 pfence = xe_preempt_fence_create(q, q->lr.context,
      ++q->lr.seqno);
 if (IS_ERR(pfence)) {
  err = PTR_ERR(pfence);
  goto out_fini;
 }

 list_add(&q->lr.link, &vm->preempt.exec_queues);
 ++vm->preempt.num_exec_queues;
 q->lr.pfence = pfence;

 down_read(&vm->userptr.notifier_lock);

 drm_gpuvm_resv_add_fence(&vm->gpuvm, exec, pfence,
     DMA_RESV_USAGE_BOOKKEEP, DMA_RESV_USAGE_BOOKKEEP);

 /*
 * Check to see if a preemption on VM is in flight or userptr
 * invalidation, if so trigger this preempt fence to sync state with
 * other preempt fences on the VM.
 */

 wait = __xe_vm_userptr_needs_repin(vm) || preempt_fences_waiting(vm);
 if (wait)
  dma_fence_enable_sw_signaling(pfence);

 up_read(&vm->userptr.notifier_lock);

out_fini:
 drm_exec_fini(exec);
out_up_write:
 up_write(&vm->lock);

 return err;
}
ALLOW_ERROR_INJECTION(xe_vm_add_compute_exec_queue, ERRNO);

/**
 * xe_vm_remove_compute_exec_queue() - Remove compute exec queue from VM
 * @vm: The VM.
 * @q: The exec_queue
 *
 * Note that this function might be called multiple times on the same queue.
 */

void xe_vm_remove_compute_exec_queue(struct xe_vm *vm, struct xe_exec_queue *q)
{
 if (!xe_vm_in_preempt_fence_mode(vm))
  return;

 down_write(&vm->lock);
 if (!list_empty(&q->lr.link)) {
  list_del_init(&q->lr.link);
  --vm->preempt.num_exec_queues;
 }
 if (q->lr.pfence) {
  dma_fence_enable_sw_signaling(q->lr.pfence);
  dma_fence_put(q->lr.pfence);
  q->lr.pfence = NULL;
 }
 up_write(&vm->lock);
}

/**
 * __xe_vm_userptr_needs_repin() - Check whether the VM does have userptrs
 * that need repinning.
 * @vm: The VM.
 *
 * This function checks for whether the VM has userptrs that need repinning,
 * and provides a release-type barrier on the userptr.notifier_lock after
 * checking.
 *
 * Return: 0 if there are no userptrs needing repinning, -EAGAIN if there are.
 */

int __xe_vm_userptr_needs_repin(struct xe_vm *vm)
{
 lockdep_assert_held_read(&vm->userptr.notifier_lock);

 return (list_empty(&vm->userptr.repin_list) &&
  list_empty(&vm->userptr.invalidated)) ? 0 : -EAGAIN;
}

#define XE_VM_REBIND_RETRY_TIMEOUT_MS 1000

/**
 * xe_vm_kill() - VM Kill
 * @vm: The VM.
 * @unlocked: Flag indicates the VM's dma-resv is not held
 *
 * Kill the VM by setting banned flag indicated VM is no longer available for
 * use. If in preempt fence mode, also kill all exec queue attached to the VM.
 */

void xe_vm_kill(struct xe_vm *vm, bool unlocked)
{
 struct xe_exec_queue *q;

 lockdep_assert_held(&vm->lock);

 if (unlocked)
  xe_vm_lock(vm, false);

 vm->flags |= XE_VM_FLAG_BANNED;
 trace_xe_vm_kill(vm);

 list_for_each_entry(q, &vm->preempt.exec_queues, lr.link)
  q->ops->kill(q);

 if (unlocked)
  xe_vm_unlock(vm);

 /* TODO: Inform user the VM is banned */
}

/**
 * xe_vm_validate_should_retry() - Whether to retry after a validate error.
 * @exec: The drm_exec object used for locking before validation.
 * @err: The error returned from ttm_bo_validate().
 * @end: A ktime_t cookie that should be set to 0 before first use and
 * that should be reused on subsequent calls.
 *
 * With multiple active VMs, under memory pressure, it is possible that
 * ttm_bo_validate() run into -EDEADLK and in such case returns -ENOMEM.
 * Until ttm properly handles locking in such scenarios, best thing the
 * driver can do is retry with a timeout. Check if that is necessary, and
 * if so unlock the drm_exec's objects while keeping the ticket to prepare
 * for a rerun.
 *
 * Return: true if a retry after drm_exec_init() is recommended;
 * false otherwise.
 */

bool xe_vm_validate_should_retry(struct drm_exec *exec, int err, ktime_t *end)
{
 ktime_t cur;

 if (err != -ENOMEM)
  return false;

 cur = ktime_get();
 *end = *end ? : ktime_add_ms(cur, XE_VM_REBIND_RETRY_TIMEOUT_MS);
 if (!ktime_before(cur, *end))
  return false;

 msleep(20);
 return true;
}

static int xe_gpuvm_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec *exec)
{
 struct xe_vm *vm = gpuvm_to_vm(vm_bo->vm);
 struct drm_gpuva *gpuva;
 int ret;

 lockdep_assert_held(&vm->lock);
 drm_gpuvm_bo_for_each_va(gpuva, vm_bo)
  list_move_tail(&gpuva_to_vma(gpuva)->combined_links.rebind,
          &vm->rebind_list);

 if (!try_wait_for_completion(&vm->xe->pm_block))
  return -EAGAIN;

 ret = xe_bo_validate(gem_to_xe_bo(vm_bo->obj), vm, false);
 if (ret)
  return ret;

 vm_bo->evicted = false;
 return 0;
}

/**
 * xe_vm_validate_rebind() - Validate buffer objects and rebind vmas
 * @vm: The vm for which we are rebinding.
 * @exec: The struct drm_exec with the locked GEM objects.
 * @num_fences: The number of fences to reserve for the operation, not
 * including rebinds and validations.
 *
 * Validates all evicted gem objects and rebinds their vmas. Note that
 * rebindings may cause evictions and hence the validation-rebind
 * sequence is rerun until there are no more objects to validate.
 *
 * Return: 0 on success, negative error code on error. In particular,
 * may return -EINTR or -ERESTARTSYS if interrupted, and -EDEADLK if
 * the drm_exec transaction needs to be restarted.
 */

int xe_vm_validate_rebind(struct xe_vm *vm, struct drm_exec *exec,
     unsigned int num_fences)
{
 struct drm_gem_object *obj;
 unsigned long index;
 int ret;

 do {
  ret = drm_gpuvm_validate(&vm->gpuvm, exec);
  if (ret)
   return ret;

  ret = xe_vm_rebind(vm, false);
  if (ret)
   return ret;
 } while (!list_empty(&vm->gpuvm.evict.list));

 drm_exec_for_each_locked_object(exec, index, obj) {
  ret = dma_resv_reserve_fences(obj->resv, num_fences);
  if (ret)
   return ret;
 }

 return 0;
}

static int xe_preempt_work_begin(struct drm_exec *exec, struct xe_vm *vm,
     bool *done)
{
 int err;

 err = drm_gpuvm_prepare_vm(&vm->gpuvm, exec, 0);
 if (err)
  return err;

 if (xe_vm_is_idle(vm)) {
  vm->preempt.rebind_deactivated = true;
  *done = true;
  return 0;
 }

 if (!preempt_fences_waiting(vm)) {
  *done = true;
  return 0;
 }

 err = drm_gpuvm_prepare_objects(&vm->gpuvm, exec, 0);
 if (err)
  return err;

 err = wait_for_existing_preempt_fences(vm);
 if (err)
  return err;

 /*
 * Add validation and rebinding to the locking loop since both can
 * cause evictions which may require blocing dma_resv locks.
 * The fence reservation here is intended for the new preempt fences
 * we attach at the end of the rebind work.
 */

 return xe_vm_validate_rebind(vm, exec, vm->preempt.num_exec_queues);
}

static bool vm_suspend_rebind_worker(struct xe_vm *vm)
{
 struct xe_device *xe = vm->xe;
 bool ret = false;

 mutex_lock(&xe->rebind_resume_lock);
 if (!try_wait_for_completion(&vm->xe->pm_block)) {
  ret = true;
  list_move_tail(&vm->preempt.pm_activate_link, &xe->rebind_resume_list);
 }
 mutex_unlock(&xe->rebind_resume_lock);

 return ret;
}

/**
 * xe_vm_resume_rebind_worker() - Resume the rebind worker.
 * @vm: The vm whose preempt worker to resume.
 *
 * Resume a preempt worker that was previously suspended by
 * vm_suspend_rebind_worker().
 */

void xe_vm_resume_rebind_worker(struct xe_vm *vm)
{
 queue_work(vm->xe->ordered_wq, &vm->preempt.rebind_work);
}

static void preempt_rebind_work_func(struct work_struct *w)
{
 struct xe_vm *vm = container_of(w, struct xe_vm, preempt.rebind_work);
 struct drm_exec exec;
 unsigned int fence_count = 0;
 LIST_HEAD(preempt_fences);
 ktime_t end = 0;
 int err = 0;
 long wait;
 int __maybe_unused tries = 0;

 xe_assert(vm->xe, xe_vm_in_preempt_fence_mode(vm));
 trace_xe_vm_rebind_worker_enter(vm);

 down_write(&vm->lock);

 if (xe_vm_is_closed_or_banned(vm)) {
  up_write(&vm->lock);
  trace_xe_vm_rebind_worker_exit(vm);
  return;
 }

retry:
 if (!try_wait_for_completion(&vm->xe->pm_block) && vm_suspend_rebind_worker(vm)) {
  up_write(&vm->lock);
  return;
 }

 if (xe_vm_userptr_check_repin(vm)) {
  err = xe_vm_userptr_pin(vm);
  if (err)
   goto out_unlock_outer;
 }

 drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0);

 drm_exec_until_all_locked(&exec) {
  bool done = false;

  err = xe_preempt_work_begin(&exec, vm, &done);
  drm_exec_retry_on_contention(&exec);
  if (err || done) {
   drm_exec_fini(&exec);
   if (err && xe_vm_validate_should_retry(&exec, err, &end))
    err = -EAGAIN;

   goto out_unlock_outer;
  }
 }

 err = alloc_preempt_fences(vm, &preempt_fences, &fence_count);
 if (err)
  goto out_unlock;

 err = xe_vm_rebind(vm, true);
 if (err)
  goto out_unlock;

 /* Wait on rebinds and munmap style VM unbinds */
 wait = dma_resv_wait_timeout(xe_vm_resv(vm),
         DMA_RESV_USAGE_KERNEL,
         false, MAX_SCHEDULE_TIMEOUT);
 if (wait <= 0) {
  err = -ETIME;
  goto out_unlock;
 }

#define retry_required(__tries, __vm) \
 (IS_ENABLED(CONFIG_DRM_XE_USERPTR_INVAL_INJECT) ? \
 (!(__tries)++ || __xe_vm_userptr_needs_repin(__vm)) : \
 __xe_vm_userptr_needs_repin(__vm))

 down_read(&vm->userptr.notifier_lock);
 if (retry_required(tries, vm)) {
  up_read(&vm->userptr.notifier_lock);
  err = -EAGAIN;
  goto out_unlock;
 }

#undef retry_required

 spin_lock(&vm->xe->ttm.lru_lock);
 ttm_lru_bulk_move_tail(&vm->lru_bulk_move);
 spin_unlock(&vm->xe->ttm.lru_lock);

 /* Point of no return. */
 arm_preempt_fences(vm, &preempt_fences);
 resume_and_reinstall_preempt_fences(vm, &exec);
 up_read(&vm->userptr.notifier_lock);

out_unlock:
 drm_exec_fini(&exec);
out_unlock_outer:
 if (err == -EAGAIN) {
  trace_xe_vm_rebind_worker_retry(vm);
  goto retry;
 }

 if (err) {
  drm_warn(&vm->xe->drm, "VM worker error: %d\n", err);
  xe_vm_kill(vm, true);
 }
 up_write(&vm->lock);

 free_preempt_fences(&preempt_fences);

 trace_xe_vm_rebind_worker_exit(vm);
}

static void __vma_userptr_invalidate(struct xe_vm *vm, struct xe_userptr_vma *uvma)
{
 struct xe_userptr *userptr = &uvma->userptr;
 struct xe_vma *vma = &uvma->vma;
 struct dma_resv_iter cursor;
 struct dma_fence *fence;
 long err;

 /*
 * Tell exec and rebind worker they need to repin and rebind this
 * userptr.
 */

 if (!xe_vm_in_fault_mode(vm) &&
     !(vma->gpuva.flags & XE_VMA_DESTROYED)) {
  spin_lock(&vm->userptr.invalidated_lock);
  list_move_tail(&userptr->invalidate_link,
          &vm->userptr.invalidated);
  spin_unlock(&vm->userptr.invalidated_lock);
 }

 /*
 * Preempt fences turn into schedule disables, pipeline these.
 * Note that even in fault mode, we need to wait for binds and
 * unbinds to complete, and those are attached as BOOKMARK fences
 * to the vm.
 */

 dma_resv_iter_begin(&cursor, xe_vm_resv(vm),
       DMA_RESV_USAGE_BOOKKEEP);
 dma_resv_for_each_fence_unlocked(&cursor, fence)
  dma_fence_enable_sw_signaling(fence);
 dma_resv_iter_end(&cursor);

 err = dma_resv_wait_timeout(xe_vm_resv(vm),
        DMA_RESV_USAGE_BOOKKEEP,
        false, MAX_SCHEDULE_TIMEOUT);
 XE_WARN_ON(err <= 0);

 if (xe_vm_in_fault_mode(vm) && userptr->initial_bind) {
  err = xe_vm_invalidate_vma(vma);
  XE_WARN_ON(err);
 }

 xe_hmm_userptr_unmap(uvma);
}

static bool vma_userptr_invalidate(struct mmu_interval_notifier *mni,
       const struct mmu_notifier_range *range,
       unsigned long cur_seq)
{
 struct xe_userptr_vma *uvma = container_of(mni, typeof(*uvma), userptr.notifier);
 struct xe_vma *vma = &uvma->vma;
 struct xe_vm *vm = xe_vma_vm(vma);

 xe_assert(vm->xe, xe_vma_is_userptr(vma));
 trace_xe_vma_userptr_invalidate(vma);

 if (!mmu_notifier_range_blockable(range))
  return false;

 vm_dbg(&xe_vma_vm(vma)->xe->drm,
        "NOTIFIER: addr=0x%016llx, range=0x%016llx",
  xe_vma_start(vma), xe_vma_size(vma));

 down_write(&vm->userptr.notifier_lock);
 mmu_interval_set_seq(mni, cur_seq);

 __vma_userptr_invalidate(vm, uvma);
 up_write(&vm->userptr.notifier_lock);
 trace_xe_vma_userptr_invalidate_complete(vma);

 return true;
}

static const struct mmu_interval_notifier_ops vma_userptr_notifier_ops = {
 .invalidate = vma_userptr_invalidate,
};

#if IS_ENABLED(CONFIG_DRM_XE_USERPTR_INVAL_INJECT)
/**
 * xe_vma_userptr_force_invalidate() - force invalidate a userptr
 * @uvma: The userptr vma to invalidate
 *
 * Perform a forced userptr invalidation for testing purposes.
 */

void xe_vma_userptr_force_invalidate(struct xe_userptr_vma *uvma)
{
 struct xe_vm *vm = xe_vma_vm(&uvma->vma);

 /* Protect against concurrent userptr pinning */
 lockdep_assert_held(&vm->lock);
 /* Protect against concurrent notifiers */
 lockdep_assert_held(&vm->userptr.notifier_lock);
 /*
 * Protect against concurrent instances of this function and
 * the critical exec sections
 */

 xe_vm_assert_held(vm);

 if (!mmu_interval_read_retry(&uvma->userptr.notifier,
         uvma->userptr.notifier_seq))
  uvma->userptr.notifier_seq -= 2;
 __vma_userptr_invalidate(vm, uvma);
}
#endif

int xe_vm_userptr_pin(struct xe_vm *vm)
{
 struct xe_userptr_vma *uvma, *next;
 int err = 0;

 xe_assert(vm->xe, !xe_vm_in_fault_mode(vm));
 lockdep_assert_held_write(&vm->lock);

 /* Collect invalidated userptrs */
 spin_lock(&vm->userptr.invalidated_lock);
 xe_assert(vm->xe, list_empty(&vm->userptr.repin_list));
 list_for_each_entry_safe(uvma, next, &vm->userptr.invalidated,
     userptr.invalidate_link) {
  list_del_init(&uvma->userptr.invalidate_link);
  list_add_tail(&uvma->userptr.repin_link,
         &vm->userptr.repin_list);
 }
 spin_unlock(&vm->userptr.invalidated_lock);

 /* Pin and move to bind list */
 list_for_each_entry_safe(uvma, next, &vm->userptr.repin_list,
     userptr.repin_link) {
  err = xe_vma_userptr_pin_pages(uvma);
  if (err == -EFAULT) {
   list_del_init(&uvma->userptr.repin_link);
   /*
 * We might have already done the pin once already, but
 * then had to retry before the re-bind happened, due
 * some other condition in the caller, but in the
 * meantime the userptr got dinged by the notifier such
 * that we need to revalidate here, but this time we hit
 * the EFAULT. In such a case make sure we remove
 * ourselves from the rebind list to avoid going down in
 * flames.
 */

   if (!list_empty(&uvma->vma.combined_links.rebind))
    list_del_init(&uvma->vma.combined_links.rebind);

   /* Wait for pending binds */
   xe_vm_lock(vm, false);
   dma_resv_wait_timeout(xe_vm_resv(vm),
           DMA_RESV_USAGE_BOOKKEEP,
           false, MAX_SCHEDULE_TIMEOUT);

   down_read(&vm->userptr.notifier_lock);
   err = xe_vm_invalidate_vma(&uvma->vma);
   up_read(&vm->userptr.notifier_lock);
   xe_vm_unlock(vm);
   if (err)
    break;
  } else {
   if (err)
    break;

   list_del_init(&uvma->userptr.repin_link);
   list_move_tail(&uvma->vma.combined_links.rebind,
           &vm->rebind_list);
  }
 }

 if (err) {
  down_write(&vm->userptr.notifier_lock);
  spin_lock(&vm->userptr.invalidated_lock);
  list_for_each_entry_safe(uvma, next, &vm->userptr.repin_list,
      userptr.repin_link) {
   list_del_init(&uvma->userptr.repin_link);
   list_move_tail(&uvma->userptr.invalidate_link,
           &vm->userptr.invalidated);
  }
  spin_unlock(&vm->userptr.invalidated_lock);
  up_write(&vm->userptr.notifier_lock);
 }
 return err;
}

/**
 * xe_vm_userptr_check_repin() - Check whether the VM might have userptrs
 * that need repinning.
 * @vm: The VM.
 *
 * This function does an advisory check for whether the VM has userptrs that
 * need repinning.
 *
 * Return: 0 if there are no indications of userptrs needing repinning,
 * -EAGAIN if there are.
 */

int xe_vm_userptr_check_repin(struct xe_vm *vm)
{
 return (list_empty_careful(&vm->userptr.repin_list) &&
  list_empty_careful(&vm->userptr.invalidated)) ? 0 : -EAGAIN;
}

static int xe_vma_ops_alloc(struct xe_vma_ops *vops, bool array_of_binds)
{
 int i;

 for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i) {
  if (!vops->pt_update_ops[i].num_ops)
   continue;

  vops->pt_update_ops[i].ops =
   kmalloc_array(vops->pt_update_ops[i].num_ops,
          sizeof(*vops->pt_update_ops[i].ops),
          GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
  if (!vops->pt_update_ops[i].ops)
   return array_of_binds ? -ENOBUFS : -ENOMEM;
 }

 return 0;
}
ALLOW_ERROR_INJECTION(xe_vma_ops_alloc, ERRNO);

static void xe_vma_svm_prefetch_op_fini(struct xe_vma_op *op)
{
 struct xe_vma *vma;

 vma = gpuva_to_vma(op->base.prefetch.va);

 if (op->base.op == DRM_GPUVA_OP_PREFETCH && xe_vma_is_cpu_addr_mirror(vma))
  xa_destroy(&op->prefetch_range.range);
}

static void xe_vma_svm_prefetch_ops_fini(struct xe_vma_ops *vops)
{
 struct xe_vma_op *op;

 if (!(vops->flags & XE_VMA_OPS_FLAG_HAS_SVM_PREFETCH))
  return;

 list_for_each_entry(op, &vops->list, link)
  xe_vma_svm_prefetch_op_fini(op);
}

static void xe_vma_ops_fini(struct xe_vma_ops *vops)
{
 int i;

 xe_vma_svm_prefetch_ops_fini(vops);

 for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i)
  kfree(vops->pt_update_ops[i].ops);
}

static void xe_vma_ops_incr_pt_update_ops(struct xe_vma_ops *vops, u8 tile_mask, int inc_val)
{
 int i;

 if (!inc_val)
  return;

 for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i)
  if (BIT(i) & tile_mask)
   vops->pt_update_ops[i].num_ops += inc_val;
}

static void xe_vm_populate_rebind(struct xe_vma_op *op, struct xe_vma *vma,
      u8 tile_mask)
{
 INIT_LIST_HEAD(&op->link);
 op->tile_mask = tile_mask;
 op->base.op = DRM_GPUVA_OP_MAP;
 op->base.map.va.addr = vma->gpuva.va.addr;
 op->base.map.va.range = vma->gpuva.va.range;
 op->base.map.gem.obj = vma->gpuva.gem.obj;
 op->base.map.gem.offset = vma->gpuva.gem.offset;
 op->map.vma = vma;
 op->map.immediate = true;
 op->map.dumpable = vma->gpuva.flags & XE_VMA_DUMPABLE;
 op->map.is_null = xe_vma_is_null(vma);
}

static int xe_vm_ops_add_rebind(struct xe_vma_ops *vops, struct xe_vma *vma,
    u8 tile_mask)
{
 struct xe_vma_op *op;

 op = kzalloc(sizeof(*op), GFP_KERNEL);
 if (!op)
  return -ENOMEM;

 xe_vm_populate_rebind(op, vma, tile_mask);
 list_add_tail(&op->link, &vops->list);
 xe_vma_ops_incr_pt_update_ops(vops, tile_mask, 1);

 return 0;
}

static struct dma_fence *ops_execute(struct xe_vm *vm,
         struct xe_vma_ops *vops);
static void xe_vma_ops_init(struct xe_vma_ops *vops, struct xe_vm *vm,
       struct xe_exec_queue *q,
       struct xe_sync_entry *syncs, u32 num_syncs);

int xe_vm_rebind(struct xe_vm *vm, bool rebind_worker)
{
 struct dma_fence *fence;
 struct xe_vma *vma, *next;
 struct xe_vma_ops vops;
 struct xe_vma_op *op, *next_op;
 int err, i;

 lockdep_assert_held(&vm->lock);
 if ((xe_vm_in_lr_mode(vm) && !rebind_worker) ||
     list_empty(&vm->rebind_list))
  return 0;

 xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
 for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i)
  vops.pt_update_ops[i].wait_vm_bookkeep = true;

 xe_vm_assert_held(vm);
 list_for_each_entry(vma, &vm->rebind_list, combined_links.rebind) {
  xe_assert(vm->xe, vma->tile_present);

  if (rebind_worker)
   trace_xe_vma_rebind_worker(vma);
  else
   trace_xe_vma_rebind_exec(vma);

  err = xe_vm_ops_add_rebind(&vops, vma,
        vma->tile_present);
  if (err)
   goto free_ops;
 }

 err = xe_vma_ops_alloc(&vops, false);
 if (err)
  goto free_ops;

 fence = ops_execute(vm, &vops);
 if (IS_ERR(fence)) {
  err = PTR_ERR(fence);
 } else {
  dma_fence_put(fence);
  list_for_each_entry_safe(vma, next, &vm->rebind_list,
      combined_links.rebind)
   list_del_init(&vma->combined_links.rebind);
 }
free_ops:
 list_for_each_entry_safe(op, next_op, &vops.list, link) {
  list_del(&op->link);
  kfree(op);
 }
 xe_vma_ops_fini(&vops);

 return err;
}

struct dma_fence *xe_vma_rebind(struct xe_vm *vm, struct xe_vma *vma, u8 tile_mask)
{
 struct dma_fence *fence = NULL;
 struct xe_vma_ops vops;
 struct xe_vma_op *op, *next_op;
 struct xe_tile *tile;
 u8 id;
 int err;

 lockdep_assert_held(&vm->lock);
 xe_vm_assert_held(vm);
 xe_assert(vm->xe, xe_vm_in_fault_mode(vm));

 xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
 for_each_tile(tile, vm->xe, id) {
  vops.pt_update_ops[id].wait_vm_bookkeep = true;
  vops.pt_update_ops[tile->id].q =
   xe_tile_migrate_exec_queue(tile);
 }

 err = xe_vm_ops_add_rebind(&vops, vma, tile_mask);
 if (err)
  return ERR_PTR(err);

 err = xe_vma_ops_alloc(&vops, false);
 if (err) {
  fence = ERR_PTR(err);
  goto free_ops;
 }

 fence = ops_execute(vm, &vops);

free_ops:
 list_for_each_entry_safe(op, next_op, &vops.list, link) {
  list_del(&op->link);
  kfree(op);
 }
 xe_vma_ops_fini(&vops);

 return fence;
}

static void xe_vm_populate_range_rebind(struct xe_vma_op *op,
     struct xe_vma *vma,
     struct xe_svm_range *range,
     u8 tile_mask)
{
 INIT_LIST_HEAD(&op->link);
 op->tile_mask = tile_mask;
 op->base.op = DRM_GPUVA_OP_DRIVER;
 op->subop = XE_VMA_SUBOP_MAP_RANGE;
 op->map_range.vma = vma;
 op->map_range.range = range;
}

static int
xe_vm_ops_add_range_rebind(struct xe_vma_ops *vops,
      struct xe_vma *vma,
      struct xe_svm_range *range,
      u8 tile_mask)
{
 struct xe_vma_op *op;

 op = kzalloc(sizeof(*op), GFP_KERNEL);
 if (!op)
  return -ENOMEM;

 xe_vm_populate_range_rebind(op, vma, range, tile_mask);
 list_add_tail(&op->link, &vops->list);
 xe_vma_ops_incr_pt_update_ops(vops, tile_mask, 1);

 return 0;
}

/**
 * xe_vm_range_rebind() - VM range (re)bind
 * @vm: The VM which the range belongs to.
 * @vma: The VMA which the range belongs to.
 * @range: SVM range to rebind.
 * @tile_mask: Tile mask to bind the range to.
 *
 * (re)bind SVM range setting up GPU page tables for the range.
 *
 * Return: dma fence for rebind to signal completion on succees, ERR_PTR on
 * failure
 */

struct dma_fence *xe_vm_range_rebind(struct xe_vm *vm,
         struct xe_vma *vma,
         struct xe_svm_range *range,
         u8 tile_mask)
{
 struct dma_fence *fence = NULL;
 struct xe_vma_ops vops;
 struct xe_vma_op *op, *next_op;
 struct xe_tile *tile;
 u8 id;
 int err;

 lockdep_assert_held(&vm->lock);
 xe_vm_assert_held(vm);
 xe_assert(vm->xe, xe_vm_in_fault_mode(vm));
 xe_assert(vm->xe, xe_vma_is_cpu_addr_mirror(vma));

 xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
 for_each_tile(tile, vm->xe, id) {
  vops.pt_update_ops[id].wait_vm_bookkeep = true;
  vops.pt_update_ops[tile->id].q =
   xe_tile_migrate_exec_queue(tile);
 }

 err = xe_vm_ops_add_range_rebind(&vops, vma, range, tile_mask);
 if (err)
  return ERR_PTR(err);

 err = xe_vma_ops_alloc(&vops, false);
 if (err) {
  fence = ERR_PTR(err);
  goto free_ops;
 }

 fence = ops_execute(vm, &vops);

free_ops:
 list_for_each_entry_safe(op, next_op, &vops.list, link) {
  list_del(&op->link);
  kfree(op);
 }
 xe_vma_ops_fini(&vops);

 return fence;
}

static void xe_vm_populate_range_unbind(struct xe_vma_op *op,
     struct xe_svm_range *range)
{
 INIT_LIST_HEAD(&op->link);
 op->tile_mask = range->tile_present;
 op->base.op = DRM_GPUVA_OP_DRIVER;
 op->subop = XE_VMA_SUBOP_UNMAP_RANGE;
 op->unmap_range.range = range;
}

static int
xe_vm_ops_add_range_unbind(struct xe_vma_ops *vops,
      struct xe_svm_range *range)
{
 struct xe_vma_op *op;

 op = kzalloc(sizeof(*op), GFP_KERNEL);
 if (!op)
  return -ENOMEM;

 xe_vm_populate_range_unbind(op, range);
 list_add_tail(&op->link, &vops->list);
 xe_vma_ops_incr_pt_update_ops(vops, range->tile_present, 1);

 return 0;
}

/**
 * xe_vm_range_unbind() - VM range unbind
 * @vm: The VM which the range belongs to.
 * @range: SVM range to rebind.
 *
 * Unbind SVM range removing the GPU page tables for the range.
 *
 * Return: dma fence for unbind to signal completion on succees, ERR_PTR on
 * failure
 */

struct dma_fence *xe_vm_range_unbind(struct xe_vm *vm,
         struct xe_svm_range *range)
{
 struct dma_fence *fence = NULL;
 struct xe_vma_ops vops;
 struct xe_vma_op *op, *next_op;
 struct xe_tile *tile;
 u8 id;
 int err;

 lockdep_assert_held(&vm->lock);
 xe_vm_assert_held(vm);
 xe_assert(vm->xe, xe_vm_in_fault_mode(vm));

 if (!range->tile_present)
  return dma_fence_get_stub();

 xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
 for_each_tile(tile, vm->xe, id) {
  vops.pt_update_ops[id].wait_vm_bookkeep = true;
  vops.pt_update_ops[tile->id].q =
   xe_tile_migrate_exec_queue(tile);
 }

 err = xe_vm_ops_add_range_unbind(&vops, range);
 if (err)
  return ERR_PTR(err);

 err = xe_vma_ops_alloc(&vops, false);
 if (err) {
  fence = ERR_PTR(err);
  goto free_ops;
 }

 fence = ops_execute(vm, &vops);

free_ops:
 list_for_each_entry_safe(op, next_op, &vops.list, link) {
  list_del(&op->link);
  kfree(op);
 }
 xe_vma_ops_fini(&vops);

 return fence;
}

static void xe_vma_free(struct xe_vma *vma)
{
 if (xe_vma_is_userptr(vma))
  kfree(to_userptr_vma(vma));
 else
  kfree(vma);
}

#define VMA_CREATE_FLAG_READ_ONLY  BIT(0)
#define VMA_CREATE_FLAG_IS_NULL   BIT(1)
#define VMA_CREATE_FLAG_DUMPABLE  BIT(2)
#define VMA_CREATE_FLAG_IS_SYSTEM_ALLOCATOR BIT(3)

static struct xe_vma *xe_vma_create(struct xe_vm *vm,
        struct xe_bo *bo,
        u64 bo_offset_or_userptr,
        u64 start, u64 end,
        u16 pat_index, unsigned int flags)
{
 struct xe_vma *vma;
 struct xe_tile *tile;
 u8 id;
 bool read_only = (flags & VMA_CREATE_FLAG_READ_ONLY);
 bool is_null = (flags & VMA_CREATE_FLAG_IS_NULL);
 bool dumpable = (flags & VMA_CREATE_FLAG_DUMPABLE);
 bool is_cpu_addr_mirror =
  (flags & VMA_CREATE_FLAG_IS_SYSTEM_ALLOCATOR);

 xe_assert(vm->xe, start < end);
 xe_assert(vm->xe, end < vm->size);

 /*
 * Allocate and ensure that the xe_vma_is_userptr() return
 * matches what was allocated.
 */

 if (!bo && !is_null && !is_cpu_addr_mirror) {
  struct xe_userptr_vma *uvma = kzalloc(sizeof(*uvma), GFP_KERNEL);

  if (!uvma)
   return ERR_PTR(-ENOMEM);

  vma = &uvma->vma;
 } else {
  vma = kzalloc(sizeof(*vma), GFP_KERNEL);
  if (!vma)
   return ERR_PTR(-ENOMEM);

  if (is_cpu_addr_mirror)
   vma->gpuva.flags |= XE_VMA_SYSTEM_ALLOCATOR;
  if (is_null)
   vma->gpuva.flags |= DRM_GPUVA_SPARSE;
  if (bo)
   vma->gpuva.gem.obj = &bo->ttm.base;
 }

 INIT_LIST_HEAD(&vma->combined_links.rebind);

 INIT_LIST_HEAD(&vma->gpuva.gem.entry);
 vma->gpuva.vm = &vm->gpuvm;
 vma->gpuva.va.addr = start;
 vma->gpuva.va.range = end - start + 1;
 if (read_only)
  vma->gpuva.flags |= XE_VMA_READ_ONLY;
 if (dumpable)
  vma->gpuva.flags |= XE_VMA_DUMPABLE;

 for_each_tile(tile, vm->xe, id)
  vma->tile_mask |= 0x1 << id;

 if (vm->xe->info.has_atomic_enable_pte_bit)
  vma->gpuva.flags |= XE_VMA_ATOMIC_PTE_BIT;

 vma->pat_index = pat_index;

 if (bo) {
  struct drm_gpuvm_bo *vm_bo;

  xe_bo_assert_held(bo);

  vm_bo = drm_gpuvm_bo_obtain(vma->gpuva.vm, &bo->ttm.base);
  if (IS_ERR(vm_bo)) {
   xe_vma_free(vma);
   return ERR_CAST(vm_bo);
  }

  drm_gpuvm_bo_extobj_add(vm_bo);
  drm_gem_object_get(&bo->ttm.base);
  vma->gpuva.gem.offset = bo_offset_or_userptr;
  drm_gpuva_link(&vma->gpuva, vm_bo);
  drm_gpuvm_bo_put(vm_bo);
 } else /* userptr or null */ {
  if (!is_null && !is_cpu_addr_mirror) {
   struct xe_userptr *userptr = &to_userptr_vma(vma)->userptr;
   u64 size = end - start + 1;
   int err;

   INIT_LIST_HEAD(&userptr->invalidate_link);
   INIT_LIST_HEAD(&userptr->repin_link);
   vma->gpuva.gem.offset = bo_offset_or_userptr;
   mutex_init(&userptr->unmap_mutex);

   err = mmu_interval_notifier_insert(&userptr->notifier,
          current->mm,
          xe_vma_userptr(vma), size,
          &vma_userptr_notifier_ops);
   if (err) {
    xe_vma_free(vma);
    return ERR_PTR(err);
   }

   userptr->notifier_seq = LONG_MAX;
  }

  xe_vm_get(vm);
 }

 return vma;
}

static void xe_vma_destroy_late(struct xe_vma *vma)
{
 struct xe_vm *vm = xe_vma_vm(vma);

 if (vma->ufence) {
  xe_sync_ufence_put(vma->ufence);
  vma->ufence = NULL;
 }

 if (xe_vma_is_userptr(vma)) {
  struct xe_userptr_vma *uvma = to_userptr_vma(vma);
  struct xe_userptr *userptr = &uvma->userptr;

  if (userptr->sg)
   xe_hmm_userptr_free_sg(uvma);

  /*
 * Since userptr pages are not pinned, we can't remove
 * the notifier until we're sure the GPU is not accessing
 * them anymore
 */

  mmu_interval_notifier_remove(&userptr->notifier);
  mutex_destroy(&userptr->unmap_mutex);
  xe_vm_put(vm);
 } else if (xe_vma_is_null(vma) || xe_vma_is_cpu_addr_mirror(vma)) {
  xe_vm_put(vm);
 } else {
  xe_bo_put(xe_vma_bo(vma));
 }

 xe_vma_free(vma);
}

static void vma_destroy_work_func(struct work_struct *w)
{
 struct xe_vma *vma =
  container_of(w, struct xe_vma, destroy_work);

 xe_vma_destroy_late(vma);
}

static void vma_destroy_cb(struct dma_fence *fence,
      struct dma_fence_cb *cb)
{
 struct xe_vma *vma = container_of(cb, struct xe_vma, destroy_cb);

 INIT_WORK(&vma->destroy_work, vma_destroy_work_func);
 queue_work(system_unbound_wq, &vma->destroy_work);
}

static void xe_vma_destroy(struct xe_vma *vma, struct dma_fence *fence)
{
 struct xe_vm *vm = xe_vma_vm(vma);

 lockdep_assert_held_write(&vm->lock);
 xe_assert(vm->xe, list_empty(&vma->combined_links.destroy));

 if (xe_vma_is_userptr(vma)) {
  xe_assert(vm->xe, vma->gpuva.flags & XE_VMA_DESTROYED);

  spin_lock(&vm->userptr.invalidated_lock);
  xe_assert(vm->xe, list_empty(&to_userptr_vma(vma)->userptr.repin_link));
  list_del(&to_userptr_vma(vma)->userptr.invalidate_link);
  spin_unlock(&vm->userptr.invalidated_lock);
 } else if (!xe_vma_is_null(vma) && !xe_vma_is_cpu_addr_mirror(vma)) {
  xe_bo_assert_held(xe_vma_bo(vma));

  drm_gpuva_unlink(&vma->gpuva);
 }

 xe_vm_assert_held(vm);
 if (fence) {
  int ret = dma_fence_add_callback(fence, &vma->destroy_cb,
       vma_destroy_cb);

  if (ret) {
   XE_WARN_ON(ret != -ENOENT);
   xe_vma_destroy_late(vma);
  }
 } else {
  xe_vma_destroy_late(vma);
 }
}

/**
 * xe_vm_lock_vma() - drm_exec utility to lock a vma
 * @exec: The drm_exec object we're currently locking for.
 * @vma: The vma for witch we want to lock the vm resv and any attached
 * object's resv.
 *
 * Return: 0 on success, negative error code on error. In particular
 * may return -EDEADLK on WW transaction contention and -EINTR if
 * an interruptible wait is terminated by a signal.
 */

int xe_vm_lock_vma(struct drm_exec *exec, struct xe_vma *vma)
{
 struct xe_vm *vm = xe_vma_vm(vma);
 struct xe_bo *bo = xe_vma_bo(vma);
 int err;

 XE_WARN_ON(!vm);

 err = drm_exec_lock_obj(exec, xe_vm_obj(vm));
 if (!err && bo && !bo->vm)
  err = drm_exec_lock_obj(exec, &bo->ttm.base);

 return err;
}

static void xe_vma_destroy_unlocked(struct xe_vma *vma)
{
 struct drm_exec exec;
 int err;

 drm_exec_init(&exec, 0, 0);
 drm_exec_until_all_locked(&exec) {
  err = xe_vm_lock_vma(&exec, vma);
  drm_exec_retry_on_contention(&exec);
  if (XE_WARN_ON(err))
   break;
 }

 xe_vma_destroy(vma, NULL);

 drm_exec_fini(&exec);
}

struct xe_vma *
xe_vm_find_overlapping_vma(struct xe_vm *vm, u64 start, u64 range)
{
 struct drm_gpuva *gpuva;

 lockdep_assert_held(&vm->lock);

 if (xe_vm_is_closed_or_banned(vm))
  return NULL;

 xe_assert(vm->xe, start + range <= vm->size);

 gpuva = drm_gpuva_find_first(&vm->gpuvm, start, range);

 return gpuva ? gpuva_to_vma(gpuva) : NULL;
}

static int xe_vm_insert_vma(struct xe_vm *vm, struct xe_vma *vma)
{
 int err;

 xe_assert(vm->xe, xe_vma_vm(vma) == vm);
 lockdep_assert_held(&vm->lock);

 mutex_lock(&vm->snap_mutex);
 err = drm_gpuva_insert(&vm->gpuvm, &vma->gpuva);
 mutex_unlock(&vm->snap_mutex);
 XE_WARN_ON(err); /* Shouldn't be possible */

 return err;
}

static void xe_vm_remove_vma(struct xe_vm *vm, struct xe_vma *vma)
{
 xe_assert(vm->xe, xe_vma_vm(vma) == vm);
 lockdep_assert_held(&vm->lock);

 mutex_lock(&vm->snap_mutex);
 drm_gpuva_remove(&vma->gpuva);
 mutex_unlock(&vm->snap_mutex);
 if (vm->usm.last_fault_vma == vma)
  vm->usm.last_fault_vma = NULL;
}

static struct drm_gpuva_op *xe_vm_op_alloc(void)
{
 struct xe_vma_op *op;

 op = kzalloc(sizeof(*op), GFP_KERNEL);

 if (unlikely(!op))
  return NULL;

 return &op->base;
}

static void xe_vm_free(struct drm_gpuvm *gpuvm);

static const struct drm_gpuvm_ops gpuvm_ops = {
 .op_alloc = xe_vm_op_alloc,
 .vm_bo_validate = xe_gpuvm_validate,
 .vm_free = xe_vm_free,
};

static u64 pde_encode_pat_index(u16 pat_index)
{
 u64 pte = 0;

 if (pat_index & BIT(0))
  pte |= XE_PPGTT_PTE_PAT0;

 if (pat_index & BIT(1))
  pte |= XE_PPGTT_PTE_PAT1;

 return pte;
}

static u64 pte_encode_pat_index(u16 pat_index, u32 pt_level)
{
 u64 pte = 0;

 if (pat_index & BIT(0))
  pte |= XE_PPGTT_PTE_PAT0;

 if (pat_index & BIT(1))
  pte |= XE_PPGTT_PTE_PAT1;

 if (pat_index & BIT(2)) {
  if (pt_level)
   pte |= XE_PPGTT_PDE_PDPE_PAT2;
  else
   pte |= XE_PPGTT_PTE_PAT2;
 }

 if (pat_index & BIT(3))
  pte |= XELPG_PPGTT_PTE_PAT3;

 if (pat_index & (BIT(4)))
  pte |= XE2_PPGTT_PTE_PAT4;

 return pte;
}

static u64 pte_encode_ps(u32 pt_level)
{
 XE_WARN_ON(pt_level > MAX_HUGEPTE_LEVEL);

 if (pt_level == 1)
  return XE_PDE_PS_2M;
 else if (pt_level == 2)
  return XE_PDPE_PS_1G;

 return 0;
}

static u16 pde_pat_index(struct xe_bo *bo)
{
 struct xe_device *xe = xe_bo_device(bo);
 u16 pat_index;

 /*
 * We only have two bits to encode the PAT index in non-leaf nodes, but
 * these only point to other paging structures so we only need a minimal
 * selection of options. The user PAT index is only for encoding leaf
 * nodes, where we have use of more bits to do the encoding. The
 * non-leaf nodes are instead under driver control so the chosen index
 * here should be distict from the user PAT index. Also the
 * corresponding coherency of the PAT index should be tied to the
 * allocation type of the page table (or at least we should pick
 * something which is always safe).
 */

 if (!xe_bo_is_vram(bo) && bo->ttm.ttm->caching == ttm_cached)
  pat_index = xe->pat.idx[XE_CACHE_WB];
 else
  pat_index = xe->pat.idx[XE_CACHE_NONE];

 xe_assert(xe, pat_index <= 3);

 return pat_index;
}

static u64 xelp_pde_encode_bo(struct xe_bo *bo, u64 bo_offset)
{
 u64 pde;

 pde = xe_bo_addr(bo, bo_offset, XE_PAGE_SIZE);
 pde |= XE_PAGE_PRESENT | XE_PAGE_RW;
 pde |= pde_encode_pat_index(pde_pat_index(bo));

 return pde;
}

static u64 xelp_pte_encode_bo(struct xe_bo *bo, u64 bo_offset,
         u16 pat_index, u32 pt_level)
{
 u64 pte;

 pte = xe_bo_addr(bo, bo_offset, XE_PAGE_SIZE);
 pte |= XE_PAGE_PRESENT | XE_PAGE_RW;
 pte |= pte_encode_pat_index(pat_index, pt_level);
 pte |= pte_encode_ps(pt_level);

 if (xe_bo_is_vram(bo) || xe_bo_is_stolen_devmem(bo))
  pte |= XE_PPGTT_PTE_DM;

 return pte;
}

static u64 xelp_pte_encode_vma(u64 pte, struct xe_vma *vma,
          u16 pat_index, u32 pt_level)
{
 pte |= XE_PAGE_PRESENT;

 if (likely(!xe_vma_read_only(vma)))
  pte |= XE_PAGE_RW;

 pte |= pte_encode_pat_index(pat_index, pt_level);
 pte |= pte_encode_ps(pt_level);

 if (unlikely(xe_vma_is_null(vma)))
  pte |= XE_PTE_NULL;

 return pte;
}

static u64 xelp_pte_encode_addr(struct xe_device *xe, u64 addr,
    u16 pat_index,
    u32 pt_level, bool devmem, u64 flags)
{
 u64 pte;

 /* Avoid passing random bits directly as flags */
 xe_assert(xe, !(flags & ~XE_PTE_PS64));

 pte = addr;
 pte |= XE_PAGE_PRESENT | XE_PAGE_RW;
 pte |= pte_encode_pat_index(pat_index, pt_level);
 pte |= pte_encode_ps(pt_level);

 if (devmem)
  pte |= XE_PPGTT_PTE_DM;

 pte |= flags;

 return pte;
}

static const struct xe_pt_ops xelp_pt_ops = {
 .pte_encode_bo = xelp_pte_encode_bo,
 .pte_encode_vma = xelp_pte_encode_vma,
 .pte_encode_addr = xelp_pte_encode_addr,
 .pde_encode_bo = xelp_pde_encode_bo,
};

static void vm_destroy_work_func(struct work_struct *w);

/**
 * xe_vm_create_scratch() - Setup a scratch memory pagetable tree for the
 * given tile and vm.
 * @xe: xe device.
 * @tile: tile to set up for.
 * @vm: vm to set up for.
 *
 * Sets up a pagetable tree with one page-table per level and a single
 * leaf PTE. All pagetable entries point to the single page-table or,
 * for MAX_HUGEPTE_LEVEL, a NULL huge PTE returning 0 on read and
 * writes become NOPs.
 *
 * Return: 0 on success, negative error code on error.
 */

static int xe_vm_create_scratch(struct xe_device *xe, struct xe_tile *tile,
    struct xe_vm *vm)
{
 u8 id = tile->id;
 int i;

 for (i = MAX_HUGEPTE_LEVEL; i < vm->pt_root[id]->level; i++) {
  vm->scratch_pt[id][i] = xe_pt_create(vm, tile, i);
  if (IS_ERR(vm->scratch_pt[id][i])) {
   int err = PTR_ERR(vm->scratch_pt[id][i]);

   vm->scratch_pt[id][i] = NULL;
   return err;
  }

  xe_pt_populate_empty(tile, vm, vm->scratch_pt[id][i]);
 }

 return 0;
}
ALLOW_ERROR_INJECTION(xe_vm_create_scratch, ERRNO);

static void xe_vm_free_scratch(struct xe_vm *vm)
{
 struct xe_tile *tile;
 u8 id;

 if (!xe_vm_has_scratch(vm))
  return;

 for_each_tile(tile, vm->xe, id) {
  u32 i;

  if (!vm->pt_root[id])
   continue;

  for (i = MAX_HUGEPTE_LEVEL; i < vm->pt_root[id]->level; ++i)
   if (vm->scratch_pt[id][i])
    xe_pt_destroy(vm->scratch_pt[id][i], vm->flags, NULL);
 }
}

struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
{
 struct drm_gem_object *vm_resv_obj;
 struct xe_vm *vm;
 int err, number_tiles = 0;
 struct xe_tile *tile;
 u8 id;

 /*
 * Since the GSCCS is not user-accessible, we don't expect a GSC VM to
 * ever be in faulting mode.
 */

 xe_assert(xe, !((flags & XE_VM_FLAG_GSC) && (flags & XE_VM_FLAG_FAULT_MODE)));

 vm = kzalloc(sizeof(*vm), GFP_KERNEL);
 if (!vm)
  return ERR_PTR(-ENOMEM);

 vm->xe = xe;

 vm->size = 1ull << xe->info.va_bits;
 vm->flags = flags;

 if (xef)
  vm->xef = xe_file_get(xef);
 /**
 * GSC VMs are kernel-owned, only used for PXP ops and can sometimes be
 * manipulated under the PXP mutex. However, the PXP mutex can be taken
 * under a user-VM lock when the PXP session is started at exec_queue
 * creation time. Those are different VMs and therefore there is no risk
 * of deadlock, but we need to tell lockdep that this is the case or it
 * will print a warning.
 */

 if (flags & XE_VM_FLAG_GSC) {
  static struct lock_class_key gsc_vm_key;

  __init_rwsem(&vm->lock, "gsc_vm", &gsc_vm_key);
 } else {
  init_rwsem(&vm->lock);
 }
 mutex_init(&vm->snap_mutex);

 INIT_LIST_HEAD(&vm->rebind_list);

 INIT_LIST_HEAD(&vm->userptr.repin_list);
 INIT_LIST_HEAD(&vm->userptr.invalidated);
 init_rwsem(&vm->userptr.notifier_lock);
 spin_lock_init(&vm->userptr.invalidated_lock);

 ttm_lru_bulk_move_init(&vm->lru_bulk_move);

 INIT_WORK(&vm->destroy_work, vm_destroy_work_func);

 INIT_LIST_HEAD(&vm->preempt.exec_queues);
 vm->preempt.min_run_period_ms = 10; /* FIXME: Wire up to uAPI */

 for_each_tile(tile, xe, id)
  xe_range_fence_tree_init(&vm->rftree[id]);

 vm->pt_ops = &xelp_pt_ops;

 /*
 * Long-running workloads are not protected by the scheduler references.
 * By design, run_job for long-running workloads returns NULL and the
 * scheduler drops all the references of it, hence protecting the VM
 * for this case is necessary.
 */

 if (flags & XE_VM_FLAG_LR_MODE) {
  INIT_WORK(&vm->preempt.rebind_work, preempt_rebind_work_func);
  xe_pm_runtime_get_noresume(xe);
  INIT_LIST_HEAD(&vm->preempt.pm_activate_link);
 }

 if (flags & XE_VM_FLAG_FAULT_MODE) {
  err = xe_svm_init(vm);
  if (err)
   goto err_no_resv;
 }

 vm_resv_obj = drm_gpuvm_resv_object_alloc(&xe->drm);
 if (!vm_resv_obj) {
  err = -ENOMEM;
  goto err_svm_fini;
 }

 drm_gpuvm_init(&vm->gpuvm, "Xe VM", DRM_GPUVM_RESV_PROTECTED, &xe->drm,
         vm_resv_obj, 0, vm->size, 0, 0, &gpuvm_ops);

 drm_gem_object_put(vm_resv_obj);

 err = xe_vm_lock(vm, true);
 if (err)
  goto err_close;

 if (IS_DGFX(xe) && xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K)
  vm->flags |= XE_VM_FLAG_64K;

 for_each_tile(tile, xe, id) {
  if (flags & XE_VM_FLAG_MIGRATION &&
      tile->id != XE_VM_FLAG_TILE_ID(flags))
   continue;

  vm->pt_root[id] = xe_pt_create(vm, tile, xe->info.vm_max_level);
  if (IS_ERR(vm->pt_root[id])) {
   err = PTR_ERR(vm->pt_root[id]);
   vm->pt_root[id] = NULL;
   goto err_unlock_close;
  }
 }

 if (xe_vm_has_scratch(vm)) {
  for_each_tile(tile, xe, id) {
   if (!vm->pt_root[id])
    continue;

   err = xe_vm_create_scratch(xe, tile, vm);
   if (err)
    goto err_unlock_close;
  }
  vm->batch_invalidate_tlb = true;
 }

 if (vm->flags & XE_VM_FLAG_LR_MODE)
  vm->batch_invalidate_tlb = false;

 /* Fill pt_root after allocating scratch tables */
 for_each_tile(tile, xe, id) {
  if (!vm->pt_root[id])
   continue;

  xe_pt_populate_empty(tile, vm, vm->pt_root[id]);
 }
 xe_vm_unlock(vm);

 /* Kernel migration VM shouldn't have a circular loop.. */
 if (!(flags & XE_VM_FLAG_MIGRATION)) {
  for_each_tile(tile, xe, id) {
   struct xe_exec_queue *q;
   u32 create_flags = EXEC_QUEUE_FLAG_VM;

   if (!vm->pt_root[id])
    continue;

   q = xe_exec_queue_create_bind(xe, tile, create_flags, 0);
   if (IS_ERR(q)) {
    err = PTR_ERR(q);
    goto err_close;
   }
   vm->q[id] = q;
   number_tiles++;
  }
 }

 if (number_tiles > 1)
  vm->composite_fence_ctx = dma_fence_context_alloc(1);

 if (xef && xe->info.has_asid) {
  u32 asid;

  down_write(&xe->usm.lock);
  err = xa_alloc_cyclic(&xe->usm.asid_to_vm, &asid, vm,
          XA_LIMIT(1, XE_MAX_ASID - 1),
          &xe->usm.next_asid, GFP_KERNEL);
  up_write(&xe->usm.lock);
  if (err < 0)
   goto err_unlock_close;

  vm->usm.asid = asid;
 }

 trace_xe_vm_create(vm);

 return vm;

err_unlock_close:
 xe_vm_unlock(vm);
err_close:
 xe_vm_close_and_put(vm);
 return ERR_PTR(err);

err_svm_fini:
 if (flags & XE_VM_FLAG_FAULT_MODE) {
  vm->size = 0; /* close the vm */
  xe_svm_fini(vm);
 }
err_no_resv:
 mutex_destroy(&vm->snap_mutex);
 for_each_tile(tile, xe, id)
  xe_range_fence_tree_fini(&vm->rftree[id]);
 ttm_lru_bulk_move_fini(&xe->ttm, &vm->lru_bulk_move);
 if (vm->xef)
  xe_file_put(vm->xef);
 kfree(vm);
 if (flags & XE_VM_FLAG_LR_MODE)
  xe_pm_runtime_put(xe);
 return ERR_PTR(err);
}

static void xe_vm_close(struct xe_vm *vm)
{
 struct xe_device *xe = vm->xe;
 bool bound;
 int idx;

 bound = drm_dev_enter(&xe->drm, &idx);

 down_write(&vm->lock);
 if (xe_vm_in_fault_mode(vm))
  xe_svm_notifier_lock(vm);

 vm->size = 0;

 if (!((vm->flags & XE_VM_FLAG_MIGRATION))) {
  struct xe_tile *tile;
  struct xe_gt *gt;
  u8 id;

  /* Wait for pending binds */
  dma_resv_wait_timeout(xe_vm_resv(vm),
          DMA_RESV_USAGE_BOOKKEEP,
          false, MAX_SCHEDULE_TIMEOUT);

  if (bound) {
   for_each_tile(tile, xe, id)
    if (vm->pt_root[id])
     xe_pt_clear(xe, vm->pt_root[id]);

   for_each_gt(gt, xe, id)
    xe_gt_tlb_invalidation_vm(gt, vm);
  }
 }

 if (xe_vm_in_fault_mode(vm))
  xe_svm_notifier_unlock(vm);
 up_write(&vm->lock);

 if (bound)
  drm_dev_exit(idx);
}

void xe_vm_close_and_put(struct xe_vm *vm)
{
 LIST_HEAD(contested);
 struct xe_device *xe = vm->xe;
 struct xe_tile *tile;
 struct xe_vma *vma, *next_vma;
 struct drm_gpuva *gpuva, *next;
 u8 id;

 xe_assert(xe, !vm->preempt.num_exec_queues);

 xe_vm_close(vm);
 if (xe_vm_in_preempt_fence_mode(vm)) {
  mutex_lock(&xe->rebind_resume_lock);
  list_del_init(&vm->preempt.pm_activate_link);
  mutex_unlock(&xe->rebind_resume_lock);
  flush_work(&vm->preempt.rebind_work);
 }
 if (xe_vm_in_fault_mode(vm))
  xe_svm_close(vm);

 down_write(&vm->lock);
 for_each_tile(tile, xe, id) {
  if (vm->q[id])
   xe_exec_queue_last_fence_put(vm->q[id], vm);
 }
 up_write(&vm->lock);

 for_each_tile(tile, xe, id) {
  if (vm->q[id]) {
   xe_exec_queue_kill(vm->q[id]);
   xe_exec_queue_put(vm->q[id]);
   vm->q[id] = NULL;
  }
 }

 down_write(&vm->lock);
 xe_vm_lock(vm, false);
 drm_gpuvm_for_each_va_safe(gpuva, next, &vm->gpuvm) {
  vma = gpuva_to_vma(gpuva);

  if (xe_vma_has_no_bo(vma)) {
   down_read(&vm->userptr.notifier_lock);
   vma->gpuva.flags |= XE_VMA_DESTROYED;
   up_read(&vm->userptr.notifier_lock);
  }

  xe_vm_remove_vma(vm, vma);

  /* easy case, remove from VMA? */
  if (xe_vma_has_no_bo(vma) || xe_vma_bo(vma)->vm) {
   list_del_init(&vma->combined_links.rebind);
   xe_vma_destroy(vma, NULL);
   continue;
  }

  list_move_tail(&vma->combined_links.destroy, &contested);
  vma->gpuva.flags |= XE_VMA_DESTROYED;
 }

 /*
 * All vm operations will add shared fences to resv.
 * The only exception is eviction for a shared object,
 * but even so, the unbind when evicted would still
 * install a fence to resv. Hence it's safe to
 * destroy the pagetables immediately.
 */

 xe_vm_free_scratch(vm);

 for_each_tile(tile, xe, id) {
  if (vm->pt_root[id]) {
   xe_pt_destroy(vm->pt_root[id], vm->flags, NULL);
   vm->pt_root[id] = NULL;
  }
 }
 xe_vm_unlock(vm);

 /*
 * VM is now dead, cannot re-add nodes to vm->vmas if it's NULL
 * Since we hold a refcount to the bo, we can remove and free
 * the members safely without locking.
 */

 list_for_each_entry_safe(vma, next_vma, &contested,
     combined_links.destroy) {
  list_del_init(&vma->combined_links.destroy);
  xe_vma_destroy_unlocked(vma);
 }

 if (xe_vm_in_fault_mode(vm))
  xe_svm_fini(vm);

 up_write(&vm->lock);

 down_write(&xe->usm.lock);
 if (vm->usm.asid) {
  void *lookup;

  xe_assert(xe, xe->info.has_asid);
  xe_assert(xe, !(vm->flags & XE_VM_FLAG_MIGRATION));

  lookup = xa_erase(&xe->usm.asid_to_vm, vm->usm.asid);
  xe_assert(xe, lookup == vm);
 }
 up_write(&xe->usm.lock);

 for_each_tile(tile, xe, id)
  xe_range_fence_tree_fini(&vm->rftree[id]);

 xe_vm_put(vm);
}

static void vm_destroy_work_func(struct work_struct *w)
{
 struct xe_vm *vm =
  container_of(w, struct xe_vm, destroy_work);
 struct xe_device *xe = vm->xe;
 struct xe_tile *tile;
 u8 id;

 /* xe_vm_close_and_put was not called? */
 xe_assert(xe, !vm->size);

 if (xe_vm_in_preempt_fence_mode(vm))
  flush_work(&vm->preempt.rebind_work);

 mutex_destroy(&vm->snap_mutex);

 if (vm->flags & XE_VM_FLAG_LR_MODE)
  xe_pm_runtime_put(xe);

 for_each_tile(tile, xe, id)
  XE_WARN_ON(vm->pt_root[id]);

 trace_xe_vm_free(vm);

 ttm_lru_bulk_move_fini(&xe->ttm, &vm->lru_bulk_move);

 if (vm->xef)
  xe_file_put(vm->xef);

 kfree(vm);
}

static void xe_vm_free(struct drm_gpuvm *gpuvm)
{
 struct xe_vm *vm = container_of(gpuvm, struct xe_vm, gpuvm);

 /* To destroy the VM we need to be able to sleep */
 queue_work(system_unbound_wq, &vm->destroy_work);
}

struct xe_vm *xe_vm_lookup(struct xe_file *xef, u32 id)
{
 struct xe_vm *vm;

 mutex_lock(&xef->vm.lock);
 vm = xa_load(&xef->vm.xa, id);
 if (vm)
  xe_vm_get(vm);
 mutex_unlock(&xef->vm.lock);

 return vm;
}

u64 xe_vm_pdp4_descriptor(struct xe_vm *vm, struct xe_tile *tile)
{
 return vm->pt_ops->pde_encode_bo(vm->pt_root[tile->id]->bo, 0);
}

static struct xe_exec_queue *
to_wait_exec_queue(struct xe_vm *vm, struct xe_exec_queue *q)
{
 return q ? q : vm->q[0];
}

static struct xe_user_fence *
find_ufence_get(struct xe_sync_entry *syncs, u32 num_syncs)
{
 unsigned int i;

 for (i = 0; i < num_syncs; i++) {
  struct xe_sync_entry *e = &syncs[i];

  if (xe_sync_is_ufence(e))
   return xe_sync_ufence_get(e);
 }

 return NULL;
}

#define ALL_DRM_XE_VM_CREATE_FLAGS (DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE | \
        DRM_XE_VM_CREATE_FLAG_LR_MODE | \
        DRM_XE_VM_CREATE_FLAG_FAULT_MODE)

int xe_vm_create_ioctl(struct drm_device *dev, void *data,
         struct drm_file *file)
{
 struct xe_device *xe = to_xe_device(dev);
 struct xe_file *xef = to_xe_file(file);
 struct drm_xe_vm_create *args = data;
 struct xe_vm *vm;
 u32 id;
 int err;
 u32 flags = 0;

 if (XE_IOCTL_DBG(xe, args->extensions))
  return -EINVAL;

 if (XE_WA(xe_root_mmio_gt(xe), 14016763929))
  args->flags |= DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE;

 if (XE_IOCTL_DBG(xe, args->flags & DRM_XE_VM_CREATE_FLAG_FAULT_MODE &&
    !xe->info.has_usm))
  return -EINVAL;

 if (XE_IOCTL_DBG(xe, args->reserved[0] || args->reserved[1]))
  return -EINVAL;

 if (XE_IOCTL_DBG(xe, args->flags & ~ALL_DRM_XE_VM_CREATE_FLAGS))
  return -EINVAL;

 if (XE_IOCTL_DBG(xe, args->flags & DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE &&
    args->flags & DRM_XE_VM_CREATE_FLAG_FAULT_MODE &&
    !xe->info.needs_scratch))
  return -EINVAL;

 if (XE_IOCTL_DBG(xe, !(args->flags & DRM_XE_VM_CREATE_FLAG_LR_MODE) &&
    args->flags & DRM_XE_VM_CREATE_FLAG_FAULT_MODE))
  return -EINVAL;

 if (args->flags & DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE)
  flags |= XE_VM_FLAG_SCRATCH_PAGE;
 if (args->flags & DRM_XE_VM_CREATE_FLAG_LR_MODE)
  flags |= XE_VM_FLAG_LR_MODE;
 if (args->flags & DRM_XE_VM_CREATE_FLAG_FAULT_MODE)
  flags |= XE_VM_FLAG_FAULT_MODE;

 vm = xe_vm_create(xe, flags, xef);
 if (IS_ERR(vm))
  return PTR_ERR(vm);

#if IS_ENABLED(CONFIG_DRM_XE_DEBUG_MEM)
 /* Warning: Security issue - never enable by default */
 args->reserved[0] = xe_bo_main_addr(vm->pt_root[0]->bo, XE_PAGE_SIZE);
#endif

 /* user id alloc must always be last in ioctl to prevent UAF */
 err = xa_alloc(&xef->vm.xa, &id, vm, xa_limit_32b, GFP_KERNEL);
 if (err)
  goto err_close_and_put;

 args->vm_id = id;

 return 0;

err_close_and_put:
 xe_vm_close_and_put(vm);

 return err;
}

int xe_vm_destroy_ioctl(struct drm_device *dev, void *data,
   struct drm_file *file)
{
 struct xe_device *xe = to_xe_device(dev);
 struct xe_file *xef = to_xe_file(file);
 struct drm_xe_vm_destroy *args = data;
 struct xe_vm *vm;
 int err = 0;

 if (XE_IOCTL_DBG(xe, args->pad) ||
     XE_IOCTL_DBG(xe, args->reserved[0] || args->reserved[1]))
  return -EINVAL;

 mutex_lock(&xef->vm.lock);
 vm = xa_load(&xef->vm.xa, args->vm_id);
 if (XE_IOCTL_DBG(xe, !vm))
  err = -ENOENT;
 else if (XE_IOCTL_DBG(xe, vm->preempt.num_exec_queues))
  err = -EBUSY;
 else
  xa_erase(&xef->vm.xa, args->vm_id);
 mutex_unlock(&xef->vm.lock);

 if (!err)
  xe_vm_close_and_put(vm);

 return err;
}

static bool vma_matches(struct xe_vma *vma, u64 page_addr)
{
 if (page_addr > xe_vma_end(vma) - 1 ||
     page_addr + SZ_4K - 1 < xe_vma_start(vma))
  return false;

 return true;
}

/**
 * xe_vm_find_vma_by_addr() - Find a VMA by its address
 *
 * @vm: the xe_vm the vma belongs to
 * @page_addr: address to look up
 */

struct xe_vma *xe_vm_find_vma_by_addr(struct xe_vm *vm, u64 page_addr)
{
 struct xe_vma *vma = NULL;

 if (vm->usm.last_fault_vma) {   /* Fast lookup */
  if (vma_matches(vm->usm.last_fault_vma, page_addr))
   vma = vm->usm.last_fault_vma;
 }
 if (!vma)
  vma = xe_vm_find_overlapping_vma(vm, page_addr, SZ_4K);

 return vma;
}

static const u32 region_to_mem_type[] = {
 XE_PL_TT,
 XE_PL_VRAM0,
 XE_PL_VRAM1,
};

static void prep_vma_destroy(struct xe_vm *vm, struct xe_vma *vma,
        bool post_commit)
{
 down_read(&vm->userptr.notifier_lock);
 vma->gpuva.flags |= XE_VMA_DESTROYED;
 up_read(&vm->userptr.notifier_lock);
 if (post_commit)
  xe_vm_remove_vma(vm, vma);
}

#undef ULL
#define ULL unsigned long long

#if IS_ENABLED(CONFIG_DRM_XE_DEBUG_VM)
static void print_op(struct xe_device *xe, struct drm_gpuva_op *op)
{
 struct xe_vma *vma;

 switch (op->op) {
 case DRM_GPUVA_OP_MAP:
  vm_dbg(&xe->drm, "MAP: addr=0x%016llx, range=0x%016llx",
         (ULL)op->map.va.addr, (ULL)op->map.va.range);
  break;
 case DRM_GPUVA_OP_REMAP:
  vma = gpuva_to_vma(op->remap.unmap->va);
  vm_dbg(&xe->drm, "REMAP:UNMAP: addr=0x%016llx, range=0x%016llx, keep=%d",
         (ULL)xe_vma_start(vma), (ULL)xe_vma_size(vma),
         op->remap.unmap->keep ? 1 : 0);
  if (op->remap.prev)
   vm_dbg(&xe->drm,
          "REMAP:PREV: addr=0x%016llx, range=0x%016llx",
          (ULL)op->remap.prev->va.addr,
          (ULL)op->remap.prev->va.range);
  if (op->remap.next)
   vm_dbg(&xe->drm,
          "REMAP:NEXT: addr=0x%016llx, range=0x%016llx",
          (ULL)op->remap.next->va.addr,
          (ULL)op->remap.next->va.range);
  break;
 case DRM_GPUVA_OP_UNMAP:
  vma = gpuva_to_vma(op->unmap.va);
  vm_dbg(&xe->drm, "UNMAP: addr=0x%016llx, range=0x%016llx, keep=%d",
         (ULL)xe_vma_start(vma), (ULL)xe_vma_size(vma),
         op->unmap.keep ? 1 : 0);
  break;
 case DRM_GPUVA_OP_PREFETCH:
  vma = gpuva_to_vma(op->prefetch.va);
  vm_dbg(&xe->drm, "PREFETCH: addr=0x%016llx, range=0x%016llx",
         (ULL)xe_vma_start(vma), (ULL)xe_vma_size(vma));
  break;
 default:
  drm_warn(&xe->drm, "NOT POSSIBLE");
 }
}
#else
static void print_op(struct xe_device *xe, struct drm_gpuva_op *op)
{
}
#endif

static bool __xe_vm_needs_clear_scratch_pages(struct xe_vm *vm, u32 bind_flags)
{
 if (!xe_vm_in_fault_mode(vm))
  return false;

 if (!xe_vm_has_scratch(vm))
  return false;

 if (bind_flags & DRM_XE_VM_BIND_FLAG_IMMEDIATE)
  return false;

 return true;
}

static void xe_svm_prefetch_gpuva_ops_fini(struct drm_gpuva_ops *ops)
{
 struct drm_gpuva_op *__op;

 drm_gpuva_for_each_op(__op, ops) {
  struct xe_vma_op *op = gpuva_op_to_vma_op(__op);

  xe_vma_svm_prefetch_op_fini(op);
 }
}

/*
 * Create operations list from IOCTL arguments, setup operations fields so parse
 * and commit steps are decoupled from IOCTL arguments. This step can fail.
 */

static struct drm_gpuva_ops *
vm_bind_ioctl_ops_create(struct xe_vm *vm, struct xe_vma_ops *vops,
    struct xe_bo *bo, u64 bo_offset_or_userptr,
    u64 addr, u64 range,
    u32 operation, u32 flags,
    u32 prefetch_region, u16 pat_index)
{
 struct drm_gem_object *obj = bo ? &bo->ttm.base : NULL;
 struct drm_gpuva_ops *ops;
 struct drm_gpuva_op *__op;
 struct drm_gpuvm_bo *vm_bo;
 u64 range_end = addr + range;
 int err;

 lockdep_assert_held_write(&vm->lock);

 vm_dbg(&vm->xe->drm,
        "op=%d, addr=0x%016llx, range=0x%016llx, bo_offset_or_userptr=0x%016llx",
        operation, (ULL)addr, (ULL)range,
        (ULL)bo_offset_or_userptr);

 switch (operation) {
 case DRM_XE_VM_BIND_OP_MAP:
 case DRM_XE_VM_BIND_OP_MAP_USERPTR:
  ops = drm_gpuvm_sm_map_ops_create(&vm->gpuvm, addr, range,
        obj, bo_offset_or_userptr);
  break;
 case DRM_XE_VM_BIND_OP_UNMAP:
  ops = drm_gpuvm_sm_unmap_ops_create(&vm->gpuvm, addr, range);
  break;
 case DRM_XE_VM_BIND_OP_PREFETCH:
  ops = drm_gpuvm_prefetch_ops_create(&vm->gpuvm, addr, range);
  break;
 case DRM_XE_VM_BIND_OP_UNMAP_ALL:
  xe_assert(vm->xe, bo);

  err = xe_bo_lock(bo, true);
  if (err)
   return ERR_PTR(err);

  vm_bo = drm_gpuvm_bo_obtain(&vm->gpuvm, obj);
  if (IS_ERR(vm_bo)) {
   xe_bo_unlock(bo);
   return ERR_CAST(vm_bo);
  }

  ops = drm_gpuvm_bo_unmap_ops_create(vm_bo);
  drm_gpuvm_bo_put(vm_bo);
  xe_bo_unlock(bo);
  break;
 default:
  drm_warn(&vm->xe->drm, "NOT POSSIBLE");
  ops = ERR_PTR(-EINVAL);
 }
 if (IS_ERR(ops))
  return ops;

 drm_gpuva_for_each_op(__op, ops) {
  struct xe_vma_op *op = gpuva_op_to_vma_op(__op);

  if (__op->op == DRM_GPUVA_OP_MAP) {
   op->map.immediate =
    flags & DRM_XE_VM_BIND_FLAG_IMMEDIATE;
   op->map.read_only =
    flags & DRM_XE_VM_BIND_FLAG_READONLY;
   op->map.is_null = flags & DRM_XE_VM_BIND_FLAG_NULL;
   op->map.is_cpu_addr_mirror = flags &
    DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR;
   op->map.dumpable = flags & DRM_XE_VM_BIND_FLAG_DUMPABLE;
   op->map.pat_index = pat_index;
   op->map.invalidate_on_bind =
    __xe_vm_needs_clear_scratch_pages(vm, flags);
  } else if (__op->op == DRM_GPUVA_OP_PREFETCH) {
   struct xe_vma *vma = gpuva_to_vma(op->base.prefetch.va);
   struct xe_svm_range *svm_range;
   struct drm_gpusvm_ctx ctx = {};
   struct xe_tile *tile;
   u8 id, tile_mask = 0;
   u32 i;

   if (!xe_vma_is_cpu_addr_mirror(vma)) {
    op->prefetch.region = prefetch_region;
    break;
   }

   ctx.read_only = xe_vma_read_only(vma);
   ctx.devmem_possible = IS_DGFX(vm->xe) &&
           IS_ENABLED(CONFIG_DRM_XE_PAGEMAP);

   for_each_tile(tile, vm->xe, id)
    tile_mask |= 0x1 << id;

   xa_init_flags(&op->prefetch_range.range, XA_FLAGS_ALLOC);
   op->prefetch_range.region = prefetch_region;
   op->prefetch_range.ranges_count = 0;
alloc_next_range:
   svm_range = xe_svm_range_find_or_insert(vm, addr, vma, &ctx);

   if (PTR_ERR(svm_range) == -ENOENT) {
    u64 ret = xe_svm_find_vma_start(vm, addr, range_end, vma);

    addr = ret == ULONG_MAX ? 0 : ret;
    if (addr)
     goto alloc_next_range;
    else
     goto print_op_label;
   }

   if (IS_ERR(svm_range)) {
    err = PTR_ERR(svm_range);
    goto unwind_prefetch_ops;
   }

   if (xe_svm_range_validate(vm, svm_range, tile_mask, !!prefetch_region)) {
    xe_svm_range_debug(svm_range, "PREFETCH - RANGE IS VALID");
    goto check_next_range;
   }

   err = xa_alloc(&op->prefetch_range.range,
           &i, svm_range, xa_limit_32b,
           GFP_KERNEL);

   if (err)
    goto unwind_prefetch_ops;

   op->prefetch_range.ranges_count++;
   vops->flags |= XE_VMA_OPS_FLAG_HAS_SVM_PREFETCH;
   xe_svm_range_debug(svm_range, "PREFETCH - RANGE CREATED");
check_next_range:
   if (range_end > xe_svm_range_end(svm_range) &&
       xe_svm_range_end(svm_range) < xe_vma_end(vma)) {
    addr = xe_svm_range_end(svm_range);
    goto alloc_next_range;
   }
  }
print_op_label:
  print_op(vm->xe, __op);
 }

 return ops;

unwind_prefetch_ops:
 xe_svm_prefetch_gpuva_ops_fini(ops);
 drm_gpuva_ops_free(&vm->gpuvm, ops);
 return ERR_PTR(err);
}

ALLOW_ERROR_INJECTION(vm_bind_ioctl_ops_create, ERRNO);

static struct xe_vma *new_vma(struct xe_vm *vm, struct drm_gpuva_op_map *op,
         u16 pat_index, unsigned int flags)
{
 struct xe_bo *bo = op->gem.obj ? gem_to_xe_bo(op->gem.obj) : NULL;
 struct drm_exec exec;
 struct xe_vma *vma;
 int err = 0;

 lockdep_assert_held_write(&vm->lock);

 if (bo) {
  drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0);
  drm_exec_until_all_locked(&exec) {
   err = 0;
   if (!bo->vm) {
    err = drm_exec_lock_obj(&exec, xe_vm_obj(vm));
    drm_exec_retry_on_contention(&exec);
   }
   if (!err) {
    err = drm_exec_lock_obj(&exec, &bo->ttm.base);
    drm_exec_retry_on_contention(&exec);
   }
   if (err) {
    drm_exec_fini(&exec);
    return ERR_PTR(err);
   }
  }
 }
 vma = xe_vma_create(vm, bo, op->gem.offset,
       op->va.addr, op->va.addr +
       op->va.range - 1, pat_index, flags);
 if (IS_ERR(vma))
  goto err_unlock;

 if (xe_vma_is_userptr(vma))
  err = xe_vma_userptr_pin_pages(to_userptr_vma(vma));
 else if (!xe_vma_has_no_bo(vma) && !bo->vm)
  err = add_preempt_fences(vm, bo);

err_unlock:
 if (bo)
  drm_exec_fini(&exec);

 if (err) {
  prep_vma_destroy(vm, vma, false);
  xe_vma_destroy_unlocked(vma);
  vma = ERR_PTR(err);
 }

 return vma;
}

static u64 xe_vma_max_pte_size(struct xe_vma *vma)
{
 if (vma->gpuva.flags & XE_VMA_PTE_1G)
  return SZ_1G;
 else if (vma->gpuva.flags & (XE_VMA_PTE_2M | XE_VMA_PTE_COMPACT))
  return SZ_2M;
 else if (vma->gpuva.flags & XE_VMA_PTE_64K)
  return SZ_64K;
 else if (vma->gpuva.flags & XE_VMA_PTE_4K)
  return SZ_4K;

 return SZ_1G; /* Uninitialized, used max size */
}

static void xe_vma_set_pte_size(struct xe_vma *vma, u64 size)
{
 switch (size) {
 case SZ_1G:
  vma->gpuva.flags |= XE_VMA_PTE_1G;
  break;
 case SZ_2M:
  vma->gpuva.flags |= XE_VMA_PTE_2M;
  break;
 case SZ_64K:
  vma->gpuva.flags |= XE_VMA_PTE_64K;
  break;
 case SZ_4K:
  vma->gpuva.flags |= XE_VMA_PTE_4K;
  break;
 }
}

static int xe_vma_op_commit(struct xe_vm *vm, struct xe_vma_op *op)
{
 int err = 0;

 lockdep_assert_held_write(&vm->lock);

 switch (op->base.op) {
 case DRM_GPUVA_OP_MAP:
  err |= xe_vm_insert_vma(vm, op->map.vma);
  if (!err)
   op->flags |= XE_VMA_OP_COMMITTED;
  break;
 case DRM_GPUVA_OP_REMAP:
 {
  u8 tile_present =
   gpuva_to_vma(op->base.remap.unmap->va)->tile_present;

  prep_vma_destroy(vm, gpuva_to_vma(op->base.remap.unmap->va),
     true);
  op->flags |= XE_VMA_OP_COMMITTED;

  if (op->remap.prev) {
   err |= xe_vm_insert_vma(vm, op->remap.prev);
   if (!err)
    op->flags |= XE_VMA_OP_PREV_COMMITTED;
   if (!err && op->remap.skip_prev) {
    op->remap.prev->tile_present =
     tile_present;
    op->remap.prev = NULL;
   }
  }
  if (op->remap.next) {
   err |= xe_vm_insert_vma(vm, op->remap.next);
   if (!err)
    op->flags |= XE_VMA_OP_NEXT_COMMITTED;
   if (!err && op->remap.skip_next) {
    op->remap.next->tile_present =
     tile_present;
    op->remap.next = NULL;
   }
  }

  /* Adjust for partial unbind after removing VMA from VM */
  if (!err) {
   op->base.remap.unmap->va->va.addr = op->remap.start;
   op->base.remap.unmap->va->va.range = op->remap.range;
  }
  break;
 }
 case DRM_GPUVA_OP_UNMAP:
  prep_vma_destroy(vm, gpuva_to_vma(op->base.unmap.va), true);
  op->flags |= XE_VMA_OP_COMMITTED;
  break;
 case DRM_GPUVA_OP_PREFETCH:
  op->flags |= XE_VMA_OP_COMMITTED;
  break;
 default:
  drm_warn(&vm->xe->drm, "NOT POSSIBLE");
 }

 return err;
}

static int vm_bind_ioctl_ops_parse(struct xe_vm *vm, struct drm_gpuva_ops *ops,
       struct xe_vma_ops *vops)
{
 struct xe_device *xe = vm->xe;
 struct drm_gpuva_op *__op;
 struct xe_tile *tile;
 u8 id, tile_mask = 0;
 int err = 0;

 lockdep_assert_held_write(&vm->lock);

 for_each_tile(tile, vm->xe, id)
  tile_mask |= 0x1 << id;

 drm_gpuva_for_each_op(__op, ops) {
  struct xe_vma_op *op = gpuva_op_to_vma_op(__op);
  struct xe_vma *vma;
  unsigned int flags = 0;

  INIT_LIST_HEAD(&op->link);
  list_add_tail(&op->link, &vops->list);
  op->tile_mask = tile_mask;

  switch (op->base.op) {
  case DRM_GPUVA_OP_MAP:
  {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=97 H=96 G=96

¤ Dauer der Verarbeitung: 0.22 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.