* [PATCH v4 1/2] drm/amdkfd: disable SVM when the GPUVM cannot cover CPU addresses
2026-10-21 9:59 [PATCH v4 0/2] drm/amdkfd: bound SVM mappings by the GPU address limits Daniel Huang
@ 2026-10-21 9:59 ` Daniel Huang
2026-10-21 9:59 ` [PATCH v4 2/2] drm/amdkfd: bound SVM ranges by the smallest GPU address limit Daniel Huang
1 sibling, 0 replies; 3+ messages in thread
From: Daniel Huang @ 2026-10-21 9:59 UTC (permalink / raw)
To: amd-gfx; +Cc: Felix Kuehling, Christian König, Alex Deucher, Daniel Huang
KFD SVM maps memory in GPUVM at the same virtual address used by
the CPU. However, svm_range_map_to_gpu() passes the corresponding
PFNs to amdgpu_vm_update_range() without checking them against
vm_manager.max_pfn.
If the GPUVM address range is smaller than the CPU address range,
the page-table walker can index beyond the allocated root page-table
entries. Unlike the SVM path, the BO mapping path validates the
address range through amdgpu_vm_verify_parameters().
Only an explicit amdgpu.vm_size override configures a GPUVM smaller
than the driver would select on its own, and that parameter is meant
for driver testing rather than normal end-user configuration.
Reproduced on a Radeon 780M (gfx1103) with amdgpu.vm_size=64.
Repeated hipMallocManaged() allocations attempted to map CPU
address 0x74bd48000000, well beyond the 64 GiB GPUVM limit.
The out-of-bounds root entry contained SLUB poison with
slub_debug=FZPU and recycled heap data without it, resulting in
a general protection fault:
RIP: 0010:amdgpu_vm_cpu_update+0x2f/0x140 [amdgpu]
amdgpu_vm_pte_update_flags+0xfa/0x1e0 [amdgpu]
amdgpu_vm_ptes_update+0x38b/0xa00 [amdgpu]
amdgpu_vm_update_range+0x279/0x930 [amdgpu]
svm_range_map_to_gpu+0x13d/0x400 [amdgpu]
svm_range_validate_and_map+0x8d3/0xaa0 [amdgpu]
svm_ioctl+0x66/0x80 [amdgpu]
kfd_ioctl+0x40a/0x650 [amdgpu]
As discussed in [1], rejecting out-of-range PFNs in
amdgpu_vm_update_range() is too late. SVM must not be initialized
when the GPUVM cannot represent the required CPU addresses.
Prevent this by disabling KFD SVM support when the GPUVM address
range is smaller than AMDGPU_GMC_HOLE_START. This prevents
affected devices from advertising SVM support and excludes them
from SVM mappings. Warn from kfd_update_svm_support_properties(),
which runs after the device memory pgmap is registered, so that
only devices that would otherwise have advertised SVM report the
loss of the capability.
KFD SVM has never been supported before GFXv9: kgd2kfd_init_zone_device()
returns early for GC IP versions below 9.0.1, leaving kfd.pgmap.type at
zero, so KFD_IS_SVM_API_SUPPORTED() is already false and
HSA_CAP_SVMAPI_SUPPORTED is never advertised on GMC v6/v7/v8. The new
condition therefore cannot change behaviour on those generations.
The BO and userptr paths are unaffected. Their GPU virtual address is
chosen by user space independently of the CPU address and validated by
amdgpu_vm_verify_parameters() in amdgpu_vm_bo_map(), so an out of range
address is rejected at map time instead of reaching the page tables.
SVM is the only path whose address is dictated by the CPU mapping.
[1] https://lore.kernel.org/amd-gfx/da43b0e3-5bd9-4aa1-86d3-b733f3bad108@amd.com/
Closes: https://github.com/ROCm/TheRock/issues/9302
Assisted-by: Claude:claude-opus-5
Signed-off-by: Daniel Huang <danielhu@amd.com>
Reviewed-by: Felix Kuehling <Felix.Kuehling@amd.com>
---
drivers/gpu/drm/amd/amdkfd/kfd_svm.h | 15 +++++++++++++--
drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 11 ++++++++++-
2 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
index c7d7adae4476..f61030408f45 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
@@ -203,11 +203,22 @@ struct kfd_process_device *
svm_range_get_pdd_by_node(struct svm_range *prange, struct kfd_node *node);
void svm_range_list_lock_and_flush_work(struct svm_range_list *svms, struct mm_struct *mm);
+/* SVM uses CPU virtual addresses for GPU mappings. Disable SVM if the GPUVM
+ * address range cannot cover the required CPU address range. The parameter
+ * must be named adev, AMDGPU_GMC_HOLE_START dereferences it.
+ */
+static inline bool kfd_svm_vm_covers_user_va(struct amdgpu_device *adev)
+{
+ return (adev->vm_manager.max_pfn << AMDGPU_GPU_PAGE_SHIFT) >=
+ AMDGPU_GMC_HOLE_START;
+}
+
/* SVM API and HMM page migration work together, device memory type
* is initialized to not 0 when page migration register device memory.
*/
-#define KFD_IS_SVM_API_SUPPORTED(adev) ((adev)->kfd.pgmap.type != 0 ||\
- ((adev)->apu_prefer_gtt))
+#define KFD_IS_SVM_API_SUPPORTED(adev) \
+ (((adev)->kfd.pgmap.type != 0 || (adev)->apu_prefer_gtt) && \
+ kfd_svm_vm_covers_user_va(adev))
void svm_range_bo_unref_async(struct svm_range_bo *svm_bo);
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c
index 35b3abe57b80..5d12446b8ea8 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c
@@ -2471,8 +2471,17 @@ void kfd_update_svm_support_properties(struct amdgpu_device *adev)
sys_props.generation_count++;
else
dev_err(adev->dev, "Failed to update SVM support properties. ret=%d\n", ret);
- } else
+ } else {
+#if IS_ENABLED(CONFIG_HSA_AMD_SVM)
+ if ((adev->kfd.pgmap.type || adev->apu_prefer_gtt) &&
+ !kfd_svm_vm_covers_user_va(adev))
+ dev_warn_once(adev->dev,
+ "GPUVM size 0x%llx is too small for KFD SVM; SVM disabled (amdgpu.vm_size=%d)\n",
+ adev->vm_manager.max_pfn << AMDGPU_GPU_PAGE_SHIFT,
+ amdgpu_vm_size);
+#endif
dev->node_props.capability &= ~HSA_CAP_SVMAPI_SUPPORTED;
+ }
}
up_write(&topology_lock);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH v4 2/2] drm/amdkfd: bound SVM ranges by the smallest GPU address limit
2026-10-21 9:59 [PATCH v4 0/2] drm/amdkfd: bound SVM mappings by the GPU address limits Daniel Huang
2026-10-21 9:59 ` [PATCH v4 1/2] drm/amdkfd: disable SVM when the GPUVM cannot cover CPU addresses Daniel Huang
@ 2026-10-21 9:59 ` Daniel Huang
1 sibling, 0 replies; 3+ messages in thread
From: Daniel Huang @ 2026-10-21 9:59 UTC (permalink / raw)
To: amd-gfx; +Cc: Felix Kuehling, Christian König, Alex Deucher, Daniel Huang
KFD SVM maps memory in GPUVM at the same virtual address used by the
CPU, but nothing bounds that address against what the GPU can
represent. kfd_ioctl_svm() checks only page alignment and a non-zero
size, so the PFNs reach amdgpu_vm_update_range() unvalidated. Unlike
the SVM path, the BO mapping path validates the address range through
amdgpu_vm_verify_parameters().
Disabling SVM when the GPUVM cannot cover AMDGPU_GMC_HOLE_START closes
the case where the GPUVM is globally too small, but it compares the
GPUVM against the GPU's own address hole. When the CPU address space is
larger than the GPU's, the comparison succeeds and SVM stays enabled
while individual addresses can still fall outside the GPUVM.
On a 5-level paging CPU with a 4-level GPU, mmap() returns addresses
above the GPU limit when user space passes a hint above
DEFAULT_MAP_WINDOW. Passing such an address to KFD SVM indexes beyond
the allocated root page-table entries in amdgpu_vm_pt_descendant().
This gap was found by the Sashiko AI review bot, during review of the
first patch in this series.
Determine the smallest GPUVM among the SVM capable GPUs of the process
in svm_range_list_init(), where the pdds are already final, and store
it in svm_range_list as the first CPU page index that cannot be mapped.
Reuse shared_resources.gpuvm_size, which the KFD already derives from
vm_manager.max_pfn and the GPUVM address hole, and reports to user
space as pdd->gpuvm_limit.
Validate against that limit where ranges are created, so that an
address the GPUs cannot map is rejected as early as possible and never
results in a range that is silently left unmapped:
- svm_range_is_valid() rejects out of range set_attr and get_attr
ioctls with -EINVAL, before any range is allocated.
- svm_range_create_unregistered_range() rejects an out of range fault
address, which svm_range_restore_pages() reports as -EFAULT. The
range boundaries widen the fault address to the granularity, so the
widened range is clamped rather than the fault refused.
svm_range_map_to_gpu() keeps the same condition as a WARN_ON_ONCE()
immediately before amdgpu_vm_update_range() walks the page tables. The
creation-time bounds above make it unreachable, so it asserts that
invariant rather than checking the caller, and catches a future range
creation path that forgets to apply the bound.
A process wide minimum makes a GPU with a larger GPUVM unusable above
the smallest limit in the process, even for ranges only that GPU would
map. Every SVM capable GMC generation uses a 4-level, 48-bit GPUVM
except one gfx12 variant, so the minimum is uniform in practice.
Link: https://sashiko.dev/#/patchset/20261016145129.69645-1-danielhu%40amd.com
Assisted-by: Claude:claude-opus-5
Signed-off-by: Daniel Huang <danielhu@amd.com>
---
drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 4 +++
drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 43 +++++++++++++++++++++++++--
drivers/gpu/drm/amd/amdkfd/kfd_svm.h | 8 +++++
3 files changed, 52 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
index b9a31489b1c8..a800515baf4a 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
@@ -907,6 +907,10 @@ struct svm_range_list {
atomic_t drain_pagefaults;
struct delayed_work restore_work;
DECLARE_BITMAP(bitmap_supported, MAX_GPU_INSTANCE);
+ /* First CPU page index beyond the smallest GPUVM supported by the
+ * SVM capable GPUs of this process. Zero if none support SVM.
+ */
+ uint64_t max_va_pfn;
struct task_struct *faulting_task;
/* check point ts decides if page fault recovery need be dropped */
atomic64_t checkpoint_ts[MAX_GPU_INSTANCE];
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
index cfb428c72686..fa68fc512331 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
@@ -1504,6 +1504,14 @@ svm_range_map_to_gpu(struct kfd_process_device *pdd, struct svm_range *prange,
return -EINVAL;
}
+ /* Ranges are bounded when they are created, so this asserts that
+ * invariant before amdgpu_vm_update_range() walks the page tables
+ * rather than checking the caller.
+ */
+ if (WARN_ON_ONCE(npages && last_start + npages - 1 >=
+ kfd_svm_gpu_va_limit_pfn(pdd)))
+ return -EINVAL;
+
for (i = offset; i < offset + npages; i++) {
uint64_t gpu_start;
uint64_t gpu_end;
@@ -2960,6 +2968,15 @@ svm_range *svm_range_create_unregistered_range(struct kfd_node *node,
&is_heap_stack))
return NULL;
+ if (p->svms.max_va_pfn) {
+ if (addr >= p->svms.max_va_pfn) {
+ pr_debug("fault address 0x%llx exceeds GPUVM limit 0x%llx\n",
+ addr, p->svms.max_va_pfn);
+ return NULL;
+ }
+ last = min_t(unsigned long, last, p->svms.max_va_pfn - 1);
+ }
+
r = svm_range_check_vm(p, start, last, &bo_s, &bo_l);
if (r != -EADDRINUSE)
r = svm_range_check_vm_userptr(p, start, last, &bo_s, &bo_l);
@@ -3410,9 +3427,18 @@ int svm_range_list_init(struct kfd_process *p)
INIT_LIST_HEAD(&svms->criu_svm_metadata_list);
spin_lock_init(&svms->deferred_list_lock);
- for (i = 0; i < p->n_pdds; i++)
- if (KFD_IS_SVM_API_SUPPORTED(p->pdds[i]->dev->adev))
- bitmap_set(svms->bitmap_supported, i, 1);
+ /* The ranges of this process must fit in every GPU that can map them,
+ * so bound them by the smallest GPUVM among the SVM capable GPUs.
+ */
+ for (i = 0; i < p->n_pdds; i++) {
+ struct kfd_process_device *pdd = p->pdds[i];
+
+ if (!KFD_IS_SVM_API_SUPPORTED(pdd->dev->adev))
+ continue;
+ bitmap_set(svms->bitmap_supported, i, 1);
+ svms->max_va_pfn = min_not_zero(svms->max_va_pfn,
+ kfd_svm_gpu_va_limit_pfn(pdd));
+ }
/* Value of default granularity cannot exceed 0x1B, the
* number of pages supported by a 4-level paging table
@@ -3511,6 +3537,17 @@ svm_range_is_valid(struct kfd_process *p, uint64_t start, uint64_t size)
if (check_add_overflow(start, size << PAGE_SHIFT, &end))
return -EOVERFLOW;
+ /* Reject up front what the GPUs of this process cannot map, rather
+ * than letting the mapping fail later on.
+ */
+ if (p->svms.max_va_pfn &&
+ (end - 1) >> PAGE_SHIFT >= p->svms.max_va_pfn) {
+ pr_debug("CPU[0x%lx 0x%lx] exceeds GPUVM limit 0x%llx\n",
+ start_unchg, (end - 1) >> PAGE_SHIFT,
+ p->svms.max_va_pfn);
+ return -EINVAL;
+ }
+
do {
vma = vma_lookup(p->mm, start);
if (!vma || (vma->vm_flags & device_vma))
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
index f61030408f45..268a545302bd 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h
@@ -213,6 +213,14 @@ static inline bool kfd_svm_vm_covers_user_va(struct amdgpu_device *adev)
AMDGPU_GMC_HOLE_START;
}
+/* First CPU page index that this GPU's GPUVM cannot represent. An SVM range
+ * is mappable on pdd only if its last page lies below this.
+ */
+static inline uint64_t kfd_svm_gpu_va_limit_pfn(struct kfd_process_device *pdd)
+{
+ return pdd->dev->kfd->shared_resources.gpuvm_size >> PAGE_SHIFT;
+}
+
/* SVM API and HMM page migration work together, device memory type
* is initialized to not 0 when page migration register device memory.
*/
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread