From: Huang Rui <ray.huang@amd.com>
To: "Christian König" <christian.koenig@amd.com>,
"Philip Yang" <Philip.Yang@amd.com>,
"Alex Deucher" <alexander.deucher@amd.com>,
"Felix Kuehling" <felix.kuehling@amd.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Matthew Brost" <matthew.brost@intel.com>,
"Rodrigo Vivi" <rodrigo.vivi@intel.com>,
"Thomas Hellström" <thomas.hellstrom@linux.intel.com>,
"Danilo Krummrich" <dakr@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
amd-gfx@lists.freedesktop.org, dri-devel@lists.freedesktop.org
Cc: Xiaogang Chen <xiaogang.chen@amd.com>,
Oak Zeng <Oak.Zeng@amd.com>, "Jenny Liu" <Jenny-Jing.Liu@amd.com>,
Zhu Lingshan <lingshan.zhu@amd.com>,
"Honglei Huang" <honglei1.huang@amd.com>,
Junhua Shen <Junhua.Shen@amd.com>, Yiru Ma <yiru.ma@amd.com>,
Huang Rui <ray.huang@amd.com>, Honglei Huang <honghuan@amd.com>
Subject: [PATCH v9 11/18] drm/amdgpu: add SVM ioctl entry and fault handler module
Date: Tue, 4 Aug 2026 17:42:37 +0800 [thread overview]
Message-ID: <20260804094246.1719318-12-ray.huang@amd.com> (raw)
In-Reply-To: <20260804094246.1719318-1-ray.huang@amd.com>
From: Honglei Huang <honghuan@amd.com>
Add ioctl entry point and fault handling:
amdgpu_svm.c additions:
- amdgpu_svm_copy_attrs: memdup_user for attribute array
- amdgpu_svm_garbage_collector: dequeue and remove unmapped ranges
- amdgpu_svm_clean_queue: drain work list releasing refs
- amdgpu_svm_gc_work_func: static GC work handler
- amdgpu_svm_gc_init/fini/flush: static workqueue lifecycle
- amdgpu_svm_work_init/work_fini: wrappers for GC lifecycle
- amdgpu_svm_sync_work: wrapper for GC flush
- amdgpu_gem_svm_ioctl: validate args, dispatch
SET_ATTR/GET_ATTR/RESET_ATTR with copy_to_user for GET
amdgpu_svm_fault.c (new):
- fault_check_allowed: VMA permission check for fault address
- fault_get_unregistered_attrs: create default attr range for
faulting address not covered by explicit attributes, bounded
by VMA and granularity alignment
- fault_map_range: fault mapping pipeline with retry, GC,
permission check, find/insert range, timestamp dedup,
validity check, get_pages, update_mapping.
- amdgpu_svm_handle_fault: top level entry point from VM fault path
Signed-off-by: Honglei Huang <honghuan@amd.com>
---
drivers/gpu/drm/amd/amdgpu/amdgpu_svm.c | 200 ++++++++
drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.c | 450 ++++++++++++++++++
drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.h | 39 ++
3 files changed, 689 insertions(+)
create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.c
create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.h
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_svm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm.c
index 7dc43470037d2..ac101574bd61d 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_svm.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm.c
@@ -621,4 +621,204 @@ bool amdgpu_svm_is_enabled(struct amdgpu_vm *vm)
return vm->svm != NULL;
}
+static int amdgpu_svm_copy_attrs(const struct drm_amdgpu_gem_svm *args,
+ struct drm_amdgpu_svm_attribute **attrs,
+ size_t *size)
+{
+ if (!args->nattr || args->nattr > AMDGPU_SVM_MAX_ATTRS)
+ return -EINVAL;
+ if (!args->attrs_ptr)
+ return -EINVAL;
+
+ *size = args->nattr * sizeof(**attrs);
+ *attrs = memdup_user(u64_to_user_ptr(args->attrs_ptr), *size);
+
+ return PTR_ERR_OR_ZERO(*attrs);
+}
+
+/**
+ * amdgpu_svm_garbage_collector() - Remove ranges queued for unmapping
+ * @svm: The SVM context.
+ *
+ * Drain the GC list: for each queued range, remove it from the gpusvm trees
+ * and drop the reference held while queued. Caller must hold the SVM lock.
+ *
+ * Return: 0 on success.
+ */
+int amdgpu_svm_garbage_collector(struct amdgpu_svm *svm)
+{
+ struct amdgpu_svm_range_op_ctx op_ctx;
+
+ amdgpu_svm_assert_locked(svm);
+
+ spin_lock(&svm->work_lock);
+ while (amdgpu_svm_range_dequeue_locked(svm, &svm->gc.list, &op_ctx)) {
+ spin_unlock(&svm->work_lock);
+
+ WARN_ON(!(op_ctx.pending_ops & AMDGPU_SVM_RANGE_OP_UNMAP));
+
+ drm_gpusvm_range_remove(&svm->gpusvm,
+ &op_ctx.range->base);
+
+ amdgpu_svm_range_put_if_dequeued(svm, op_ctx.range);
+ spin_lock(&svm->work_lock);
+ }
+ spin_unlock(&svm->work_lock);
+ return 0;
+}
+
+/**
+ * amdgpu_svm_clean_queue() - Drain a work list, releasing range references
+ * @svm: The SVM context.
+ * @work_list: The work list to drain.
+ *
+ * Dequeue every range on @work_list and drop the reference held while it was
+ * queued, without performing any unmapping. Used during teardown.
+ */
+void amdgpu_svm_clean_queue(struct amdgpu_svm *svm,
+ struct list_head *work_list)
+{
+ struct amdgpu_svm_range_op_ctx op_ctx;
+
+ spin_lock(&svm->work_lock);
+ while (amdgpu_svm_range_dequeue_locked(svm, work_list,
+ &op_ctx)) {
+ spin_unlock(&svm->work_lock);
+ amdgpu_svm_range_put_if_dequeued(svm, op_ctx.range);
+ spin_lock(&svm->work_lock);
+ }
+ spin_unlock(&svm->work_lock);
+}
+
+static void amdgpu_svm_gc_work_func(struct work_struct *w)
+{
+ struct amdgpu_svm_gc *gc = container_of(w, struct amdgpu_svm_gc, work);
+ struct amdgpu_svm *svm = container_of(gc, struct amdgpu_svm, gc);
+
+ amdgpu_svm_lock(svm);
+ amdgpu_svm_garbage_collector(svm);
+ amdgpu_svm_unlock(svm);
+}
+
+static int amdgpu_svm_gc_init(struct amdgpu_svm *svm,
+ void (*gc_work_func)(struct work_struct *))
+{
+ svm->gc.wq = alloc_workqueue(AMDGPU_SVM_GC_WQ_NAME,
+ WQ_UNBOUND | WQ_HIGHPRI | WQ_MEM_RECLAIM, 0);
+ if (!svm->gc.wq)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&svm->gc.list);
+ INIT_WORK(&svm->gc.work, gc_work_func);
+
+ return 0;
+}
+
+static void amdgpu_svm_gc_fini(struct amdgpu_svm *svm)
+{
+ flush_work(&svm->gc.work);
+ amdgpu_svm_clean_queue(svm, &svm->gc.list);
+ destroy_workqueue(svm->gc.wq);
+ svm->gc.wq = NULL;
+}
+
+static void amdgpu_svm_gc_flush(struct amdgpu_svm *svm)
+{
+ flush_work(&svm->gc.work);
+}
+
+static int amdgpu_svm_work_init(struct amdgpu_svm *svm,
+ void (*gc_work_func)(struct work_struct *))
+{
+ int ret;
+
+ init_rwsem(&svm->svm_lock);
+ spin_lock_init(&svm->work_lock);
+
+ ret = amdgpu_svm_gc_init(svm, gc_work_func);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void amdgpu_svm_work_fini(struct amdgpu_svm *svm)
+{
+ amdgpu_svm_gc_fini(svm);
+}
+
+void amdgpu_svm_sync_work(struct amdgpu_svm *svm)
+{
+ amdgpu_svm_gc_flush(svm);
+}
+
+/**
+ * amdgpu_gem_svm_ioctl() - SVM GEM ioctl entry point
+ * @dev: The DRM device.
+ * @data: The ioctl argument (struct drm_amdgpu_gem_svm).
+ * @filp: The DRM file.
+ *
+ * Validate the arguments and dispatch to the SET_ATTR / GET_ATTR /
+ * RESET_ATTR operation, copying results back to user space for GET.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int amdgpu_gem_svm_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *filp)
+{
+ struct amdgpu_fpriv *fpriv = filp->driver_priv;
+ struct amdgpu_device *adev = drm_to_adev(dev);
+ struct drm_amdgpu_gem_svm *args = data;
+ struct drm_amdgpu_svm_attribute *attrs = NULL;
+ struct amdgpu_vm *vm;
+ size_t attrs_size = 0;
+ int ret = 0;
+
+ AMDGPU_SVM_DBG("ioctl op=%u va:[0x%llx-0x%llx)-0x%llx nattr=%u\n",
+ args->operation, args->start_addr, args->start_addr + args->size,
+ args->size, args->nattr);
+
+ vm = &fpriv->vm;
+ if (!amdgpu_svm_is_enabled(vm)) {
+ ret = amdgpu_svm_init(adev, vm);
+ if (ret)
+ return ret;
+ }
+
+ if ((args->start_addr & ~PAGE_MASK) || (args->size & ~PAGE_MASK))
+ return -EINVAL;
+
+ if (!args->start_addr || !args->size)
+ return -EINVAL;
+
+ if (args->operation != AMDGPU_SVM_OP_RESET_ATTR) {
+ ret = amdgpu_svm_copy_attrs(args, &attrs, &attrs_size);
+ if (ret)
+ return ret;
+ }
+
+ switch (args->operation) {
+ case AMDGPU_SVM_OP_SET_ATTR:
+ ret = amdgpu_svm_op_set_attr(vm, args->start_addr, args->size,
+ args->nattr, attrs);
+ break;
+ case AMDGPU_SVM_OP_GET_ATTR:
+ ret = amdgpu_svm_op_get_attr(vm, args->start_addr, args->size,
+ args->nattr, attrs);
+ if (!ret && copy_to_user(u64_to_user_ptr(args->attrs_ptr),
+ attrs, attrs_size))
+ ret = -EFAULT;
+ break;
+ case AMDGPU_SVM_OP_RESET_ATTR:
+ ret = amdgpu_svm_op_reset_attr(vm, args->start_addr, args->size);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ kvfree(attrs);
+ return ret;
+}
+
#endif /* CONFIG_DRM_AMDGPU_SVM */
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.c
new file mode 100644
index 0000000000000..f28b3eb14500d
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include "amdgpu_svm.h"
+#include "amdgpu_svm_attr.h"
+#include "amdgpu_svm_fault.h"
+#include "amdgpu_svm_range.h"
+#include "amdgpu.h"
+#include "amdgpu_vm.h"
+#include "amdgpu_gmc.h"
+#include "amdgpu_ih.h"
+#include "amdgpu_trace.h"
+
+#include <drm/drm_exec.h>
+#include <drm/drm_gpusvm.h>
+
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <linux/sched/mm.h>
+
+#if IS_ENABLED(CONFIG_DRM_AMDGPU_SVM)
+
+#define AMDGPU_SVM_RANGE_RETRY_FAULT_PENDING (2UL * NSEC_PER_MSEC)
+
+/**
+ * fault_get_unregistered_attrs() - Create a default attr range for a fault
+ * @svm: The SVM context.
+ * @fault_addr: The faulting address.
+ * @attr_start_page: Lower bound (first page) of the surrounding attribute gap.
+ * @attr_last_page: Upper bound (last page) of the surrounding attribute gap.
+ * @out: Output, the newly created attribute range.
+ *
+ * For a fault that lands in an address gap not covered by any explicit
+ * attribute range, create a default attribute range. Its bounds are the
+ * faulting page's granularity aligned window, clamped to the CPU VMA and to
+ * the surrounding attribute gap [@attr_start_page, @attr_last_page].
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int fault_get_unregistered_attrs(struct amdgpu_svm *svm,
+ unsigned long fault_addr,
+ unsigned long attr_start_page,
+ unsigned long attr_last_page,
+ struct amdgpu_svm_attr_range **out)
+{
+ struct amdgpu_svm_attr_tree *attr_tree = svm->attr_tree;
+ struct amdgpu_svm_attr_range *range;
+ struct amdgpu_svm_attrs attrs;
+ struct mm_struct *mm = svm->gpusvm.mm;
+ struct vm_area_struct *vma;
+ unsigned long fault_page = fault_addr >> PAGE_SHIFT;
+ unsigned long start_page, last_page;
+ unsigned long vma_start_page, vma_last_page;
+
+ amdgpu_svm_attr_set_default(svm, &attrs);
+
+ mmap_read_lock(mm);
+
+ vma = amdgpu_svm_check_vma(mm, fault_addr);
+ if (IS_ERR(vma)) {
+ mmap_read_unlock(mm);
+ AMDGPU_SVM_ERR("get_unregistered_attrs: invalid VMA for fault_addr=0x%lx\n",
+ fault_addr);
+ return PTR_ERR(vma);
+ }
+ vma_start_page = vma->vm_start >> PAGE_SHIFT;
+ vma_last_page = (vma->vm_end >> PAGE_SHIFT) - 1;
+
+ if (vma_is_initial_heap(vma) || vma_is_initial_stack(vma))
+ attrs.preferred_loc = AMDGPU_SVM_LOCATION_SYSMEM;
+
+ mmap_read_unlock(mm);
+
+ start_page = max_t(unsigned long, vma_start_page,
+ ALIGN_DOWN(fault_page, 1UL << attrs.granularity));
+ last_page = min_t(unsigned long, vma_last_page,
+ ALIGN(fault_page + 1, 1UL << attrs.granularity) - 1);
+
+ start_page = max(start_page, attr_start_page);
+ last_page = min(last_page, attr_last_page);
+
+ mutex_lock(&attr_tree->lock);
+ range = amdgpu_svm_attr_range_alloc(start_page, last_page, &attrs);
+ if (!range) {
+ mutex_unlock(&attr_tree->lock);
+ return -ENOMEM;
+ }
+ amdgpu_svm_attr_range_insert_locked(attr_tree, range);
+ mutex_unlock(&attr_tree->lock);
+
+ AMDGPU_SVM_DBG(
+ "Created unregistered range for fault_addr=0x%lx: attr range=[0x%lx-0x%lx] size: 0x%lx attrs={preferred_loc=%d, prefetch_loc=%d, flags=0x%x, granularity=%u, access=%u}\n",
+ fault_addr, amdgpu_svm_attr_start_page(range),
+ amdgpu_svm_attr_last_page(range) + 1,
+ amdgpu_svm_attr_last_page(range) -
+ amdgpu_svm_attr_start_page(range) + 1,
+ range->attrs.preferred_loc, range->attrs.prefetch_loc,
+ range->attrs.flags, range->attrs.granularity,
+ range->attrs.access);
+
+ *out = range;
+ return 0;
+}
+
+/**
+ * fault_check_allowed() - Verify the CPU VMA
+ * @svm: The SVM context.
+ * @fault_addr: The faulting address.
+ * @write_fault: True if the fault was caused by a write.
+ *
+ * Check that the VMA covering @fault_addr grants the requested access
+ *
+ * Return: 0 if allowed, -EPERM if the required permission is missing.
+ */
+static int fault_check_allowed(struct amdgpu_svm *svm,
+ unsigned long fault_addr, bool write_fault)
+{
+ struct mm_struct *mm = svm->gpusvm.mm;
+ struct vm_area_struct *vma;
+ unsigned long requested = VM_READ;
+ int ret = 0;
+
+ if (write_fault)
+ requested |= VM_WRITE;
+
+ mmap_read_lock(mm);
+ vma = vma_lookup(mm, fault_addr);
+ if (vma && (vma->vm_flags & requested) != requested) {
+ AMDGPU_SVM_ERR("fault addr 0x%lx no %s permission\n",
+ fault_addr, write_fault ? "write" : "read");
+ ret = -EPERM;
+ }
+ mmap_read_unlock(mm);
+
+ return ret;
+}
+
+/**
+ * fault_map_range() - Map the faulting range into the GPU page tables
+ * @svm: The SVM context.
+ * @fault_addr: The faulting address.
+ * @attr_range: The attribute range covering @fault_addr.
+ * @write_fault: True if the fault was caused by a write.
+ *
+ * Run the fault mapping pipeline under the SVM lock: drain the garbage
+ * collector, check VMA permissions, find or insert the gpusvm range, skip
+ * work that a concurrent fault already handled, collect the pages and do
+ * the GPU mapping. Transient failures (HMM retry, changed CPU mappings)
+ * are retried a bounded number of times.
+ *
+ * Return: 0 on success or a benign drop, negative error code on failure.
+ */
+static int fault_map_range(struct amdgpu_svm *svm,
+ unsigned long fault_addr,
+ const struct amdgpu_svm_attr_range *attr_range,
+ bool write_fault)
+{
+ const struct amdgpu_svm_attrs *attrs = &attr_range->attrs;
+ bool devmem_possible = amdgpu_svm_devmem_possible(svm);
+ bool need_vram_migration = devmem_possible &&
+ amdgpu_svm_attr_prefer_vram(attrs);
+ struct drm_gpusvm_ctx map_ctx = {
+ .read_only = !!(attrs->flags & AMDGPU_SVM_ATTR_BIT_GPU_RO),
+ .devmem_possible = devmem_possible,
+ .check_pages_threshold = devmem_possible ? SZ_64K : 0,
+ .devmem_only = need_vram_migration,
+ .timeslice_ms = need_vram_migration ? 5 : 0,
+ };
+ struct amdgpu_svm_range *range;
+ ktime_t timestamp = ktime_get_boottime();
+ int retry_count = 3;
+ int ret;
+
+ amdgpu_svm_assert_locked(svm);
+ WARN_ON(!svm->xnack_enabled);
+
+retry:
+ ret = amdgpu_svm_garbage_collector(svm);
+ if (ret) {
+ AMDGPU_SVM_ERR(
+ "fault garbage collector failed: ret=%d, fault_addr=0x%lx\n",
+ ret, fault_addr);
+ return ret;
+ }
+
+ ret = fault_check_allowed(svm, fault_addr, write_fault);
+ if (ret)
+ return ret;
+
+ range = amdgpu_svm_range_find_or_insert(
+ svm, fault_addr,
+ amdgpu_svm_attr_start(attr_range),
+ amdgpu_svm_attr_end(attr_range),
+ &map_ctx);
+ if (IS_ERR(range)) {
+ ret = PTR_ERR(range);
+ /*
+ * -EINVAL: fault_addr out of gpusvm range, or no chunk size
+ * fits within VMA/notifier/attr_range bounds.
+ * -EFAULT: mmget_not_zero failed (mm is dying).
+ * -ENOENT: No VMA at fault_addr.
+ */
+ if (ret == -EFAULT || ret == -ENOENT) {
+ AMDGPU_SVM_DBG("no vma or mm dying: 0x%lx ret=%d\n",
+ fault_addr, ret);
+ ret = 0;
+ goto out;
+ }
+
+ AMDGPU_SVM_ERR("map_fault: range_find_or_insert failed: fault=0x%lx ret=%d\n",
+ fault_addr, ret);
+ return ret;
+ }
+
+ if (write_fault && map_ctx.read_only) {
+ AMDGPU_SVM_WARN("write fault on read-only range: fault=0x%lx range=[0x%lx-0x%lx)\n",
+ fault_addr, drm_gpusvm_range_start(&range->base),
+ drm_gpusvm_range_end(&range->base));
+ }
+
+ if (ktime_before(timestamp, ktime_add_ns(range->validate_timestamp,
+ AMDGPU_SVM_RANGE_RETRY_FAULT_PENDING))) {
+ AMDGPU_SVM_DBG("already restored, skip: fault=0x%lx range=[0x%lx-0x%lx)\n",
+ fault_addr, drm_gpusvm_range_start(&range->base),
+ drm_gpusvm_range_end(&range->base));
+ goto out;
+ }
+
+ if (amdgpu_svm_range_is_valid(range, attrs)) {
+ AMDGPU_SVM_DBG("valid range, skip: fault=0x%lx range=[0x%lx-0x%lx)\n",
+ fault_addr, drm_gpusvm_range_start(&range->base),
+ drm_gpusvm_range_end(&range->base));
+ goto out;
+ }
+
+ AMDGPU_SVM_RANGE_TRACE(range, "PAGE FAULT");
+ /* TODO: add migration*/
+
+ AMDGPU_SVM_RANGE_TRACE(range, "GET PAGES");
+ ret = amdgpu_svm_range_get_pages(svm, &range->base, &map_ctx);
+ if (ret == -EOPNOTSUPP || ret == -EFAULT || ret == -EBUSY) {
+ /*
+ * -EOPNOTSUPP Mixed page types within range.
+ * -EFAULT mm is dying,range was unmapped,DMA mapping failed,
+ * devmem_only requested but system page encountered,
+ * no VMA, page fault error, bad pte/pmd.
+ * -EBUSY HMM retry loop timed out.
+ * -ENOMEM PFN or DMA address array allocation failed.
+ * -EINVAL hmm_range_fault: invalid VMA type.
+ */
+ map_ctx.timeslice_ms <<= 1;
+ if (!map_ctx.devmem_only && --retry_count > 0) {
+ AMDGPU_SVM_DBG(
+ "retry: get_pages failed %d, left=%d: fault=0x%lx range=[0x%lx-0x%lx)\n",
+ ret, retry_count, fault_addr,
+ drm_gpusvm_range_start(&range->base),
+ drm_gpusvm_range_end(&range->base));
+ goto retry;
+ }
+ AMDGPU_SVM_ERR(
+ "map_fault: get_pages retry exhausted, ret=%d: fault=0x%lx range=[0x%lx-0x%lx)\n",
+ ret, fault_addr,
+ drm_gpusvm_range_start(&range->base),
+ drm_gpusvm_range_end(&range->base));
+ }
+
+ if (ret == -EPERM) {
+ /* According to AMD UMD contract, treat -EPERM as a error fault */
+ AMDGPU_SVM_ERR("get_pages -EPERM: fault=0x%lx range=[0x%lx-0x%lx)\n",
+ fault_addr, drm_gpusvm_range_start(&range->base),
+ drm_gpusvm_range_end(&range->base));
+ return ret;
+ }
+
+ if (ret == -ENOMEM) {
+ AMDGPU_SVM_ERR("get_pages -ENOMEM: fault=0x%lx range=[0x%lx-0x%lx)\n",
+ fault_addr, drm_gpusvm_range_start(&range->base),
+ drm_gpusvm_range_end(&range->base));
+ return ret;
+ }
+
+ if (ret) {
+ AMDGPU_SVM_RANGE_TRACE(range, "PAGE FAULT - FAIL PAGE COLLECT");
+ goto out;
+ }
+
+ AMDGPU_SVM_RANGE_TRACE(range, "PAGE FAULT - GPU MAP");
+
+ ret = amdgpu_svm_range_update_mapping(svm, range, attrs,
+ map_ctx.read_only,
+ false, false, false);
+
+ if (ret)
+ goto err_out;
+
+out:
+ return 0;
+
+err_out:
+ if (ret == -EAGAIN && --retry_count > 0) {
+ map_ctx.timeslice_ms <<= 1;
+ AMDGPU_SVM_RANGE_TRACE(range, "PAGE FAULT - RETRY GPU MAP");
+ goto retry;
+ }
+
+ return ret;
+}
+
+/**
+ * amdgpu_svm_handle_fault() - Top-level SVM retry fault handler
+ * @adev: The amdgpu device.
+ * @pasid: PASID of the faulting context.
+ * @fault_page: Faulting page index.
+ * @ts: IH ring timestamp of the fault.
+ * @write_fault: True if the fault was caused by a write.
+ *
+ * Entry point from the VM fault path. Look up the SVM context, drop the
+ * fault if the context is gone/exiting or the mm is dying, filter stale
+ * retry faults using the checkpoint timestamp, then resolve the covering
+ * attribute range (creating a default one if needed) and map it.
+ *
+ * Return: 0 on success or a dropped fault, negative error code on failure.
+ */
+int amdgpu_svm_handle_fault(struct amdgpu_device *adev, uint32_t pasid,
+ uint64_t fault_page, uint64_t ts,
+ bool write_fault)
+{
+ struct amdgpu_svm *svm;
+ struct amdgpu_svm_attr_range *attr_range;
+ unsigned long attr_start_page, attr_last_page;
+ uint64_t fault_addr = fault_page << PAGE_SHIFT;
+ struct mm_struct *mm = NULL;
+ uint64_t ckpt;
+ int ret;
+
+ if (amdgpu_in_reset(adev))
+ return 0;
+
+ svm = amdgpu_svm_lookup_by_pasid(adev, pasid);
+ if (!svm) {
+ AMDGPU_SVM_DBG("no SVM context for pasid %u, drop fault 0x%llx\n",
+ pasid, fault_page << PAGE_SHIFT);
+ return 0;
+ }
+
+ if (atomic_read(&svm->exiting)) {
+ AMDGPU_SVM_DBG("SVM context is exiting for pasid %u, drop fault 0x%llx\n",
+ pasid, fault_page << PAGE_SHIFT);
+ ret = 0;
+ goto out_put;
+ }
+
+ mm = svm->gpusvm.mm;
+ if (!mm || !mmget_not_zero(mm)) {
+ AMDGPU_SVM_DBG("mm dying for pasid %u, drop fault 0x%llx\n",
+ pasid, fault_page << PAGE_SHIFT);
+ mm = NULL;
+ ret = 0;
+ goto out_put;
+ }
+
+ if (!svm->xnack_enabled) {
+ AMDGPU_SVM_ERR("xnack not enabled for pasid %u\n",
+ pasid);
+ ret = -EOPNOTSUPP;
+ goto out_put;
+ }
+
+ ckpt = READ_ONCE(svm->checkpoint_ts);
+ if (ckpt != 0) {
+ if (amdgpu_ih_ts_after_or_equal(ts, ckpt)) {
+ AMDGPU_SVM_DBG(
+ "draining stale retry fault, drop fault 0x%llx ts=%llu checkpoint=%llu\n",
+ fault_addr, ts, ckpt);
+ amdgpu_gmc_filter_faults_remove(
+ adev, fault_page, pasid);
+ ret = 0;
+ goto out_put;
+ } else {
+ WRITE_ONCE(svm->checkpoint_ts, 0);
+ }
+ }
+
+ amdgpu_svm_lock(svm);
+
+ mutex_lock(&svm->attr_tree->lock);
+ attr_range = amdgpu_svm_attr_get_bounds_locked(svm->attr_tree,
+ fault_page,
+ &attr_start_page, &attr_last_page);
+ mutex_unlock(&svm->attr_tree->lock);
+ if (!attr_range) {
+ ret = fault_get_unregistered_attrs(svm, fault_addr,
+ attr_start_page,
+ attr_last_page,
+ &attr_range);
+ if (ret) {
+ if (ret == -EFAULT)
+ goto out_no_vma;
+ goto out_unlock;
+ }
+ }
+ ret = fault_map_range(svm, fault_addr, attr_range,
+ write_fault);
+
+ if (ret == -EAGAIN) {
+ AMDGPU_SVM_DBG("retry fault=0x%llx\n",
+ fault_addr);
+ amdgpu_gmc_filter_faults_remove(adev, fault_page, pasid);
+ ret = 0;
+ }
+
+ goto out_unlock;
+
+out_no_vma:
+ AMDGPU_SVM_ERR("no VMA for fault=0x%llx (stale retry or GPU NULL deref)\n",
+ fault_addr);
+ ret = 0;
+
+out_unlock:
+ amdgpu_svm_unlock(svm);
+
+out_put:
+ if (mm)
+ mmput_async(mm);
+ amdgpu_svm_put(svm);
+ return ret;
+}
+
+#endif /* CONFIG_DRM_AMDGPU_SVM */
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.h
new file mode 100644
index 0000000000000..c169a96600de4
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_fault.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/*
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef __AMDGPU_SVM_FAULT_H__
+#define __AMDGPU_SVM_FAULT_H__
+
+#include <linux/types.h>
+
+struct amdgpu_device;
+struct amdgpu_svm;
+struct amdgpu_svm_attr_range;
+struct amdgpu_svm_attrs;
+
+int amdgpu_svm_handle_fault(struct amdgpu_device *adev, uint32_t pasid,
+ uint64_t fault_page, uint64_t ts,
+ bool write_fault);
+
+#endif /* __AMDGPU_SVM_FAULT_H__ */
--
2.53.0
next prev parent reply other threads:[~2026-08-04 9:44 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-04 9:42 [PATCH v9 00/18] drm/amdgpu: AMDGPU SVM support based on DRM (Phase 1: single GPU, XNACK on) Huang Rui
2026-08-04 9:42 ` [PATCH v9 01/18] drm/amdgpu: add SVM ioctl UAPI definitions Huang Rui
2026-08-11 10:58 ` Christian König
2026-08-11 13:42 ` Huang, Honglei
2026-08-04 9:42 ` [PATCH v9 02/18] drm/amdgpu: add SVM core header and VM integration Huang Rui
2026-08-11 11:02 ` Christian König
2026-08-11 14:06 ` Huang, Honglei
2026-08-12 8:36 ` Christian König
2026-08-12 9:55 ` Huang, Honglei
2026-08-12 12:16 ` Christian König
2026-08-12 13:36 ` Huang Rui
2026-08-13 9:16 ` Huang, Honglei
2026-08-25 7:42 ` Honglei Huang
2026-08-04 9:42 ` [PATCH v9 03/18] drm/amdgpu: implement SVM attribute tree and helper functions Huang Rui
2026-08-04 9:42 ` [PATCH v9 04/18] drm/amdgpu: implement SVM attribute set/get/clear operations Huang Rui
2026-08-04 9:42 ` [PATCH v9 05/18] drm/amdgpu: add SVM range types and work queue interface Huang Rui
2026-08-04 9:42 ` [PATCH v9 06/18] drm/amdgpu/gmc: add get_svm_pte_flags callback Huang Rui
2026-08-04 9:42 ` [PATCH v9 07/18] drm/amdgpu: implement SVM range GPU mapping core Huang Rui
2026-08-04 9:42 ` [PATCH v9 08/18] drm/amdgpu: implement SVM range notifier and GC helpers Huang Rui
2026-08-04 9:42 ` [PATCH v9 09/18] drm/amdgpu: add SVM notifier invalidate callback and checkpoint Huang Rui
2026-08-04 9:42 ` [PATCH v9 10/18] drm/amdgpu: implement SVM initialization and lifecycle Huang Rui
2026-08-04 9:42 ` Huang Rui [this message]
2026-08-04 9:42 ` [PATCH v9 12/18] drm/amdgpu: integrate SVM into build system and VM fault path Huang Rui
2026-08-04 9:42 ` [PATCH v9 13/18] drm/amdgpu: add VRAM migration infrastructure for drm_pagemap Huang Rui
2026-08-04 9:42 ` [PATCH v9 14/18] drm/amdgpu: implement drm_pagemap SDMA migration callbacks Huang Rui
2026-08-04 9:42 ` [PATCH v9 15/18] drm/amdgpu: implement synchronous TTM eviction for SVM BOs Huang Rui
2026-08-04 9:42 ` [PATCH v9 16/18] drm/amdgpu: hook up ZONE_DEVICE registration in device init and reset Huang Rui
2026-08-04 9:42 ` [PATCH v9 17/18] drm/amdgpu: add SVM range migration helpers for drm_pagemap Huang Rui
2026-08-04 9:42 ` [PATCH v9 18/18] drm/amdgpu: integrate VRAM migration into SVM fault and prefetch paths Huang Rui
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260804094246.1719318-12-ray.huang@amd.com \
--to=ray.huang@amd.com \
--cc=Jenny-Jing.Liu@amd.com \
--cc=Junhua.Shen@amd.com \
--cc=Oak.Zeng@amd.com \
--cc=Philip.Yang@amd.com \
--cc=alexander.deucher@amd.com \
--cc=aliceryhl@google.com \
--cc=amd-gfx@lists.freedesktop.org \
--cc=christian.koenig@amd.com \
--cc=dakr@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=felix.kuehling@amd.com \
--cc=honghuan@amd.com \
--cc=honglei1.huang@amd.com \
--cc=lingshan.zhu@amd.com \
--cc=matthew.brost@intel.com \
--cc=rodrigo.vivi@intel.com \
--cc=simona@ffwll.ch \
--cc=thomas.hellstrom@linux.intel.com \
--cc=xiaogang.chen@amd.com \
--cc=yiru.ma@amd.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.