Compute kernels often issue memory copies immediately after completion. If the memory being copied is an SVM pointer that was faulted into the device and then bound via userptr, it is undesirable to move that memory. Worse, if userptr is mixed between system and device memory, the bind operation may be rejected. Xe already has the necessary plumbing to support userptr with mixed mappings. This update modifies GPUSVM's get_pages to correctly locate pages in such mixed mapping scenarios. v2: - Rebase (Thomas Hellström) v3: - Remove Fixes tag. v4: - Break out from series since the other patch was merged. - Update patch subject, ensure dri-devel and Maarten are CC'd. Cc: Maarten Lankhorst <maarten.lankhorst@intel.com> Cc: dri-devel@lists.freedesktop.org Signed-off-by: Matthew Brost <matthew.brost@intel.com> Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com> Reviewed-by: Matthew Auld <matthew.auld@intel.com> Reviewed-by: Himal Prasad Ghimiray <himal.prasad.ghimiray@intel.com> Link: https://lore.kernel.org/r/20251015120320.176338-1-thomas.hellstrom@linux.intel.com Acked-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
323 lines
9.2 KiB
C
323 lines
9.2 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2025 Intel Corporation
|
|
*/
|
|
|
|
#include "xe_svm.h"
|
|
#include "xe_userptr.h"
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include "xe_trace_bo.h"
|
|
|
|
/**
|
|
* 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->svm.gpusvm.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.pages.notifier_seq) ?
|
|
-EAGAIN : 0;
|
|
}
|
|
|
|
/**
|
|
* __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 svm.gpusvm.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->svm.gpusvm.notifier_lock);
|
|
|
|
return (list_empty(&vm->userptr.repin_list) &&
|
|
list_empty(&vm->userptr.invalidated)) ? 0 : -EAGAIN;
|
|
}
|
|
|
|
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;
|
|
struct drm_gpusvm_ctx ctx = {
|
|
.read_only = xe_vma_read_only(vma),
|
|
.device_private_page_owner = xe_svm_devm_owner(xe),
|
|
.allow_mixed = true,
|
|
};
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
xe_assert(xe, xe_vma_is_userptr(vma));
|
|
|
|
if (vma->gpuva.flags & XE_VMA_DESTROYED)
|
|
return 0;
|
|
|
|
return drm_gpusvm_get_pages(&vm->svm.gpusvm, &uvma->userptr.pages,
|
|
uvma->userptr.notifier.mm,
|
|
&uvma->userptr.notifier,
|
|
xe_vma_userptr(vma),
|
|
xe_vma_userptr(vma) + xe_vma_size(vma),
|
|
&ctx);
|
|
}
|
|
|
|
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;
|
|
struct drm_gpusvm_ctx ctx = {
|
|
.in_notifier = true,
|
|
.read_only = xe_vma_read_only(vma),
|
|
};
|
|
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);
|
|
}
|
|
|
|
drm_gpusvm_unmap_pages(&vm->svm.gpusvm, &uvma->userptr.pages,
|
|
xe_vma_size(vma) >> PAGE_SHIFT, &ctx);
|
|
}
|
|
|
|
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->svm.gpusvm.notifier_lock);
|
|
mmu_interval_set_seq(mni, cur_seq);
|
|
|
|
__vma_userptr_invalidate(vm, uvma);
|
|
up_write(&vm->svm.gpusvm.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->svm.gpusvm.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.pages.notifier_seq))
|
|
uvma->userptr.pages.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->svm.gpusvm.notifier_lock);
|
|
err = xe_vm_invalidate_vma(&uvma->vma);
|
|
up_read(&vm->svm.gpusvm.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->svm.gpusvm.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->svm.gpusvm.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;
|
|
}
|
|
|
|
int xe_userptr_setup(struct xe_userptr_vma *uvma, unsigned long start,
|
|
unsigned long range)
|
|
{
|
|
struct xe_userptr *userptr = &uvma->userptr;
|
|
int err;
|
|
|
|
INIT_LIST_HEAD(&userptr->invalidate_link);
|
|
INIT_LIST_HEAD(&userptr->repin_link);
|
|
|
|
err = mmu_interval_notifier_insert(&userptr->notifier, current->mm,
|
|
start, range,
|
|
&vma_userptr_notifier_ops);
|
|
if (err)
|
|
return err;
|
|
|
|
userptr->pages.notifier_seq = LONG_MAX;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void xe_userptr_remove(struct xe_userptr_vma *uvma)
|
|
{
|
|
struct xe_vm *vm = xe_vma_vm(&uvma->vma);
|
|
struct xe_userptr *userptr = &uvma->userptr;
|
|
|
|
drm_gpusvm_free_pages(&vm->svm.gpusvm, &uvma->userptr.pages,
|
|
xe_vma_size(&uvma->vma) >> PAGE_SHIFT);
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
void xe_userptr_destroy(struct xe_userptr_vma *uvma)
|
|
{
|
|
struct xe_vm *vm = xe_vma_vm(&uvma->vma);
|
|
|
|
spin_lock(&vm->userptr.invalidated_lock);
|
|
xe_assert(vm->xe, list_empty(&uvma->userptr.repin_link));
|
|
list_del(&uvma->userptr.invalidate_link);
|
|
spin_unlock(&vm->userptr.invalidated_lock);
|
|
}
|