AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/16 00:09 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "TEST_HMM",
    "DRM_ACCEL_AMDXDNA",
    "DRM_GPUSVM",
    "DRM_NOUVEAU_SVM",
    "MSHV_ROOT",
    "INFINIBAND_ON_DEMAND_PAGING"
  ],
  "FocusSymbols": [
    "hmm_range_fault_unlocked_timeout",
    "hmm_range_fault",
    "hmm_do_fault",
    "hmm_range_fault_locked",
    "hmm_record_fault",
    "dmirror_range_fault_unlocked",
    "dmirror_fault_unlocked",
    "dmirror_read_unlocked"
  ],
  "Reasoning": "The patch introduces a new unlocked timeout variant for HMM range faults (`hmm_range_fault_unlocked_timeout`) and refactors the existing `hmm_range_fault` to use a locked helper. It also updates several drivers and the HMM test module to use the new unlocked variant. This is a functional change to core memory management and device driver interaction, which should be fuzzed to ensure the new retry and locking logic is robust.",
  "WorthFuzzing": true
}

1/1 2026/07/16 00:09 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit bd08126468a3d288e396fd6e6f69a9fc2c70a659\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Jul 16 00:09:08 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/mm/hmm.rst b/Documentation/mm/hmm.rst\nindex 7d61b7a8b65b79..e021218ada58fe 100644\n--- a/Documentation/mm/hmm.rst\n+++ b/Documentation/mm/hmm.rst\n@@ -156,42 +156,57 @@ During the ops-\u003einvalidate() callback the device driver must perform the\n update action to the range (mark range read only, or fully unmap, etc.). The\n device must complete the update before the driver callback returns.\n \n-When the device driver wants to populate a range of virtual addresses, it can\n-use::\n+When the device driver wants to populate a range of virtual addresses, the\n+normal interface is::\n \n-  int hmm_range_fault(struct hmm_range *range);\n+  int hmm_range_fault_unlocked_timeout(struct hmm_range *range,\n+                                       unsigned long timeout);\n \n It will trigger a page fault on missing or read-only entries if write access is\n requested (see below). Page faults use the generic mm page fault code path just\n-like a CPU page fault. The usage pattern is::\n+like a CPU page fault.\n+\n+The caller must not hold ``mmap_read_lock`` before the call.\n+``hmm_range_fault_unlocked_timeout()`` takes the mmap read lock internally and\n+allows ``handle_mm_fault()`` to drop it during fault handling. This is required\n+for VMAs whose fault handlers may release the mmap lock, for example regions\n+managed by ``userfaultfd``.\n+\n+If the mmap lock is dropped or the range is invalidated, the function refreshes\n+``range-\u003enotifier_seq`` and restarts the walk internally. ``-EINTR`` is returned\n+if mmap lock acquisition is interrupted or a fatal signal is pending during\n+retry handling.\n+\n+The timeout is specified in jiffies; passing ``0`` means retry indefinitely. The\n+timeout exists to preserve caller policy for repeated mmu-notifier invalidation\n+and is checked between retry attempts. HMM does not interrupt page fault\n+handling when the timeout expires, but returns ``-EBUSY`` if the retry budget is\n+exhausted before a stable range is obtained.\n+\n+The usage pattern is::\n \n  int driver_populate_range(...)\n  {\n       struct hmm_range range;\n+      unsigned long timeout;\n       ...\n \n+      timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n       range.notifier = \u0026interval_sub;\n       range.start = ...;\n       range.end = ...;\n       range.hmm_pfns = ...;\n \n-      if (!mmget_not_zero(interval_sub-\u003enotifier.mm))\n+      if (!mmget_not_zero(interval_sub.mm))\n           return -EFAULT;\n \n  again:\n-      range.notifier_seq = mmu_interval_read_begin(\u0026interval_sub);\n-      mmap_read_lock(mm);\n-      ret = hmm_range_fault(\u0026range);\n-      if (ret) {\n-          mmap_read_unlock(mm);\n-          if (ret == -EBUSY)\n-                 goto again;\n-          return ret;\n-      }\n-      mmap_read_unlock(mm);\n+      ret = hmm_range_fault_unlocked_timeout(\u0026range, timeout);\n+      if (ret)\n+          goto out_put;\n \n       take_lock(driver-\u003eupdate);\n-      if (mmu_interval_read_retry(\u0026ni, range.notifier_seq) {\n+      if (mmu_interval_read_retry(range.notifier, range.notifier_seq)) {\n           release_lock(driver-\u003eupdate);\n           goto again;\n       }\n@@ -200,13 +215,31 @@ like a CPU page fault. The usage pattern is::\n        * under the update lock */\n \n       release_lock(driver-\u003eupdate);\n-      return 0;\n+      ret = 0;\n+\n+ out_put:\n+      mmput(interval_sub.mm);\n+      return ret;\n  }\n \n The driver-\u003eupdate lock is the same lock that the driver takes inside its\n invalidate() callback. That lock must be held before calling\n mmu_interval_read_retry() to avoid any race with a concurrent CPU page table\n-update.\n+update. The retry check must use the same notifier and sequence number stored\n+in ``range`` by ``hmm_range_fault_unlocked_timeout()``.\n+\n+Holding the mmap lock across HMM faults\n+=======================================\n+\n+Most callers should use ``hmm_range_fault_unlocked_timeout()``. If a driver\n+really needs to hold the mmap lock across work outside HMM, it can use::\n+\n+  int hmm_range_fault(struct hmm_range *range);\n+\n+The mmap lock must be held by the caller and will remain held on return. This\n+interface cannot support VMAs whose fault handlers need to drop the mmap lock.\n+New callers should prefer ``hmm_range_fault_unlocked_timeout()`` unless they\n+have a specific requirement to keep the mmap lock held across the call.\n \n Leverage default_flags and pfn_flags_mask\n =========================================\n@@ -221,8 +254,8 @@ permission, it sets::\n     range-\u003edefault_flags = HMM_PFN_REQ_FAULT;\n     range-\u003epfn_flags_mask = 0;\n \n-and calls hmm_range_fault() as described above. This will fill fault all pages\n-in the range with at least read permission.\n+and calls the HMM range fault helper as described above. This will fault\n+all pages in the range with at least read permission.\n \n Now let's say the driver wants to do the same except for one page in the range for\n which it wants to have write permission. Now driver set::\n@@ -236,9 +269,9 @@ address == range-\u003estart + (index_of_write \u003c\u003c PAGE_SHIFT) it will fault with\n write permission i.e., if the CPU pte does not have write permission set then HMM\n will call handle_mm_fault().\n \n-After hmm_range_fault completes the flag bits are set to the current state of\n-the page tables, ie HMM_PFN_VALID | HMM_PFN_WRITE will be set if the page is\n-writable.\n+After the HMM range fault helper completes the flag bits are set to the\n+current state of the page tables, ie HMM_PFN_VALID | HMM_PFN_WRITE will be\n+set if the page is writable.\n \n \n Represent and manage device memory from core kernel point of view\ndiff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c\nindex 54486960cbf5e6..b5b4ca263002da 100644\n--- a/drivers/accel/amdxdna/aie2_ctx.c\n+++ b/drivers/accel/amdxdna/aie2_ctx.c\n@@ -1034,7 +1034,7 @@ static int aie2_populate_range(struct amdxdna_gem_obj *abo)\n \tbool found;\n \tint ret;\n \n-\ttimeout = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n+\ttimeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n again:\n \tfound = false;\n \tdown_write(\u0026xdna-\u003enotifier_lock);\n@@ -1061,24 +1061,9 @@ static int aie2_populate_range(struct amdxdna_gem_obj *abo)\n \t\treturn -EFAULT;\n \t}\n \n-\tmapp-\u003erange.notifier_seq = mmu_interval_read_begin(\u0026mapp-\u003enotifier);\n-\tmmap_read_lock(mm);\n-\tret = hmm_range_fault(\u0026mapp-\u003erange);\n-\tmmap_read_unlock(mm);\n-\tif (ret) {\n-\t\tif (time_after(jiffies, timeout)) {\n-\t\t\tret = -ETIME;\n-\t\t\tgoto put_mm;\n-\t\t}\n-\n-\t\tif (ret == -EBUSY) {\n-\t\t\tamdxdna_umap_put(mapp);\n-\t\t\tmmput(mm);\n-\t\t\tgoto again;\n-\t\t}\n-\n+\tret = hmm_range_fault_unlocked_timeout(\u0026mapp-\u003erange, timeout);\n+\tif (ret)\n \t\tgoto put_mm;\n-\t}\n \n \tdown_write(\u0026xdna-\u003enotifier_lock);\n \tif (mmu_interval_read_retry(\u0026mapp-\u003enotifier, mapp-\u003erange.notifier_seq)) {\n@@ -1096,7 +1081,7 @@ static int aie2_populate_range(struct amdxdna_gem_obj *abo)\n put_mm:\n \tamdxdna_umap_put(mapp);\n \tmmput(mm);\n-\treturn ret;\n+\treturn ret == -EBUSY ? -ETIME : ret;\n }\n \n int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job, u64 *seq)\ndiff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c\nindex 958cb605aedd53..de5bbfe58ee945 100644\n--- a/drivers/gpu/drm/drm_gpusvm.c\n+++ b/drivers/gpu/drm/drm_gpusvm.c\n@@ -773,8 +773,7 @@ enum drm_gpusvm_scan_result drm_gpusvm_scan_mm(struct drm_gpusvm_range *range,\n \t\t.end = end,\n \t\t.dev_private_owner = dev_private_owner,\n \t};\n-\tunsigned long timeout =\n-\t\tjiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n+\tunsigned long timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n \tenum drm_gpusvm_scan_result state = DRM_GPUSVM_SCAN_UNPOPULATED, new_state;\n \tunsigned long *pfns;\n \tunsigned long npages = npages_in_range(start, end);\n@@ -788,22 +787,7 @@ enum drm_gpusvm_scan_result drm_gpusvm_scan_mm(struct drm_gpusvm_range *range,\n \thmm_range.hmm_pfns = pfns;\n \n retry:\n-\thmm_range.notifier_seq = mmu_interval_read_begin(notifier);\n-\tmmap_read_lock(range-\u003egpusvm-\u003emm);\n-\n-\twhile (true) {\n-\t\terr = hmm_range_fault(\u0026hmm_range);\n-\t\tif (err == -EBUSY) {\n-\t\t\tif (time_after(jiffies, timeout))\n-\t\t\t\tbreak;\n-\n-\t\t\thmm_range.notifier_seq =\n-\t\t\t\tmmu_interval_read_begin(notifier);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tbreak;\n-\t}\n-\tmmap_read_unlock(range-\u003egpusvm-\u003emm);\n+\terr = hmm_range_fault_unlocked_timeout(\u0026hmm_range, timeout);\n \tif (err)\n \t\tgoto err_free;\n \n@@ -1406,8 +1390,7 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,\n \t\t.dev_private_owner = ctx-\u003edevice_private_page_owner,\n \t};\n \tvoid *zdd;\n-\tunsigned long timeout =\n-\t\tjiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n+\tunsigned long timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n \tunsigned long i, j;\n \tunsigned long npages = npages_in_range(pages_start, pages_end);\n \tunsigned long num_dma_mapped;\n@@ -1422,9 +1405,6 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,\n \tstruct dma_iova_state *state = \u0026svm_pages-\u003estate;\n \n retry:\n-\tif (time_after(jiffies, timeout))\n-\t\treturn -EBUSY;\n-\n \thmm_range.notifier_seq = mmu_interval_read_begin(notifier);\n \tif (drm_gpusvm_pages_valid_unlocked(gpusvm, svm_pages))\n \t\tgoto set_seqno;\n@@ -1439,21 +1419,7 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,\n \t}\n \n \thmm_range.hmm_pfns = pfns;\n-\twhile (true) {\n-\t\tmmap_read_lock(mm);\n-\t\terr = hmm_range_fault(\u0026hmm_range);\n-\t\tmmap_read_unlock(mm);\n-\n-\t\tif (err == -EBUSY) {\n-\t\t\tif (time_after(jiffies, timeout))\n-\t\t\t\tbreak;\n-\n-\t\t\thmm_range.notifier_seq =\n-\t\t\t\tmmu_interval_read_begin(notifier);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tbreak;\n-\t}\n+\terr = hmm_range_fault_unlocked_timeout(\u0026hmm_range, timeout);\n \tmmput(mm);\n \tif (err)\n \t\tgoto err_free;\n@@ -1720,8 +1686,7 @@ int drm_gpusvm_range_evict(struct drm_gpusvm *gpusvm,\n \t\t.end = drm_gpusvm_range_end(range),\n \t\t.dev_private_owner = NULL,\n \t};\n-\tunsigned long timeout =\n-\t\tjiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n+\tunsigned long timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n \tunsigned long *pfns;\n \tunsigned long npages = npages_in_range(drm_gpusvm_range_start(range),\n \t\t\t\t\t       drm_gpusvm_range_end(range));\n@@ -1736,24 +1701,12 @@ int drm_gpusvm_range_evict(struct drm_gpusvm *gpusvm,\n \t\treturn -ENOMEM;\n \n \thmm_range.hmm_pfns = pfns;\n-\twhile (!time_after(jiffies, timeout)) {\n-\t\thmm_range.notifier_seq = mmu_interval_read_begin(notifier);\n-\t\tif (time_after(jiffies, timeout)) {\n-\t\t\terr = -ETIME;\n-\t\t\tbreak;\n-\t\t}\n-\n-\t\tmmap_read_lock(mm);\n-\t\terr = hmm_range_fault(\u0026hmm_range);\n-\t\tmmap_read_unlock(mm);\n-\t\tif (err != -EBUSY)\n-\t\t\tbreak;\n-\t}\n+\terr = hmm_range_fault_unlocked_timeout(\u0026hmm_range, timeout);\n \n \tkvfree(pfns);\n \tmmput(mm);\n \n-\treturn err;\n+\treturn err == -EBUSY ? -ETIME : err;\n }\n EXPORT_SYMBOL_GPL(drm_gpusvm_range_evict);\n \ndiff --git a/drivers/gpu/drm/nouveau/nouveau_svm.c b/drivers/gpu/drm/nouveau/nouveau_svm.c\nindex dcc92131488eab..58735446d7832c 100644\n--- a/drivers/gpu/drm/nouveau/nouveau_svm.c\n+++ b/drivers/gpu/drm/nouveau/nouveau_svm.c\n@@ -678,20 +678,22 @@ static int nouveau_range_fault(struct nouveau_svmm *svmm,\n \trange.end = notifier-\u003enotifier.interval_tree.last + 1;\n \n \twhile (true) {\n-\t\tif (time_after(jiffies, timeout)) {\n+\t\tlong remaining = timeout - jiffies;\n+\n+\t\t/*\n+\t\t * The HMM timeout only bounds retries while HMM is walking and\n+\t\t * faulting the range. This fault is handled by a kernel worker,\n+\t\t * so fatal signals from the faulting process cannot stop an\n+\t\t * endless stream of invalidations here.\n+\t\t */\n+\t\tif (time_after_eq(jiffies, timeout)) {\n \t\t\tret = -EBUSY;\n \t\t\tgoto out;\n \t\t}\n \n-\t\trange.notifier_seq = mmu_interval_read_begin(range.notifier);\n-\t\tmmap_read_lock(mm);\n-\t\tret = hmm_range_fault(\u0026range);\n-\t\tmmap_read_unlock(mm);\n-\t\tif (ret) {\n-\t\t\tif (ret == -EBUSY)\n-\t\t\t\tcontinue;\n+\t\tret = hmm_range_fault_unlocked_timeout(\u0026range, remaining);\n+\t\tif (ret)\n \t\t\tgoto out;\n-\t\t}\n \n \t\tmutex_lock(\u0026svmm-\u003emutex);\n \t\tif (mmu_interval_read_retry(range.notifier,\ndiff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c\nindex 6d65e5b4215295..dddaade31b5dc3 100644\n--- a/drivers/hv/mshv_regions.c\n+++ b/drivers/hv/mshv_regions.c\n@@ -381,46 +381,6 @@ int mshv_region_get(struct mshv_mem_region *region)\n \treturn kref_get_unless_zero(\u0026region-\u003emreg_refcount);\n }\n \n-/**\n- * mshv_region_hmm_fault_and_lock - Handle HMM faults and lock the memory region\n- * @region: Pointer to the memory region structure\n- * @range: Pointer to the HMM range structure\n- *\n- * This function performs the following steps:\n- * 1. Reads the notifier sequence for the HMM range.\n- * 2. Acquires a read lock on the memory map.\n- * 3. Handles HMM faults for the specified range.\n- * 4. Releases the read lock on the memory map.\n- * 5. If successful, locks the memory region mutex.\n- * 6. Verifies if the notifier sequence has changed during the operation.\n- *    If it has, releases the mutex and returns -EBUSY to match with\n- *    hmm_range_fault() return code for repeating.\n- *\n- * Return: 0 on success, a negative error code otherwise.\n- */\n-static int mshv_region_hmm_fault_and_lock(struct mshv_mem_region *region,\n-\t\t\t\t\t  struct hmm_range *range)\n-{\n-\tint ret;\n-\n-\trange-\u003enotifier_seq = mmu_interval_read_begin(range-\u003enotifier);\n-\tmmap_read_lock(region-\u003emreg_mni.mm);\n-\tret = hmm_range_fault(range);\n-\tmmap_read_unlock(region-\u003emreg_mni.mm);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\tmutex_lock(\u0026region-\u003emreg_mutex);\n-\n-\tif (mmu_interval_read_retry(range-\u003enotifier, range-\u003enotifier_seq)) {\n-\t\tmutex_unlock(\u0026region-\u003emreg_mutex);\n-\t\tcond_resched();\n-\t\treturn -EBUSY;\n-\t}\n-\n-\treturn 0;\n-}\n-\n /**\n  * mshv_region_range_fault - Handle memory range faults for a given region.\n  * @region: Pointer to the memory region structure.\n@@ -452,13 +412,19 @@ static int mshv_region_range_fault(struct mshv_mem_region *region,\n \trange.start = region-\u003estart_uaddr + page_offset * HV_HYP_PAGE_SIZE;\n \trange.end = range.start + page_count * HV_HYP_PAGE_SIZE;\n \n-\tdo {\n-\t\tret = mshv_region_hmm_fault_and_lock(region, \u0026range);\n-\t} while (ret == -EBUSY);\n-\n+again:\n+\tret = hmm_range_fault_unlocked_timeout(\u0026range, 0);\n \tif (ret)\n \t\tgoto out;\n \n+\tmutex_lock(\u0026region-\u003emreg_mutex);\n+\n+\tif (mmu_interval_read_retry(range.notifier, range.notifier_seq)) {\n+\t\tmutex_unlock(\u0026region-\u003emreg_mutex);\n+\t\tcond_resched();\n+\t\tgoto again;\n+\t}\n+\n \tfor (i = 0; i \u003c page_count; i++)\n \t\tregion-\u003emreg_pages[page_offset + i] = hmm_pfn_to_page(pfns[i]);\n \ndiff --git a/drivers/infiniband/core/umem_odp.c b/drivers/infiniband/core/umem_odp.c\nindex 404fa1cc32542c..9cc21cd762d989 100644\n--- a/drivers/infiniband/core/umem_odp.c\n+++ b/drivers/infiniband/core/umem_odp.c\n@@ -329,7 +329,7 @@ int ib_umem_odp_map_dma_and_lock(struct ib_umem_odp *umem_odp, u64 user_virt,\n \tstruct mm_struct *owning_mm = umem_odp-\u003eumem.owning_mm;\n \tint pfn_index, dma_index, ret = 0, start_idx;\n \tunsigned int page_shift, hmm_order, pfn_start_idx;\n-\tunsigned long num_pfns, current_seq;\n+\tunsigned long num_pfns;\n \tstruct hmm_range range = {};\n \tunsigned long timeout;\n \n@@ -363,26 +363,18 @@ int ib_umem_odp_map_dma_and_lock(struct ib_umem_odp *umem_odp, u64 user_virt,\n \t}\n \n \trange.hmm_pfns = \u0026(umem_odp-\u003emap.pfn_list[pfn_start_idx]);\n-\ttimeout = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n+\ttimeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);\n \n retry:\n-\tcurrent_seq = range.notifier_seq =\n-\t\tmmu_interval_read_begin(\u0026umem_odp-\u003enotifier);\n-\n-\tmmap_read_lock(owning_mm);\n-\tret = hmm_range_fault(\u0026range);\n-\tmmap_read_unlock(owning_mm);\n-\tif (unlikely(ret)) {\n-\t\tif (ret == -EBUSY \u0026\u0026 !time_after(jiffies, timeout))\n-\t\t\tgoto retry;\n+\tret = hmm_range_fault_unlocked_timeout(\u0026range, timeout);\n+\tif (unlikely(ret))\n \t\tgoto out_put_mm;\n-\t}\n \n \tstart_idx = (range.start - ib_umem_start(umem_odp)) \u003e\u003e page_shift;\n \tdma_index = start_idx;\n \n \tmutex_lock(\u0026umem_odp-\u003eumem_mutex);\n-\tif (mmu_interval_read_retry(\u0026umem_odp-\u003enotifier, current_seq)) {\n+\tif (mmu_interval_read_retry(\u0026umem_odp-\u003enotifier, range.notifier_seq)) {\n \t\tmutex_unlock(\u0026umem_odp-\u003eumem_mutex);\n \t\tgoto retry;\n \t}\ndiff --git a/include/linux/hmm.h b/include/linux/hmm.h\nindex db75ffc949a7ac..6f04e3932f5b0c 100644\n--- a/include/linux/hmm.h\n+++ b/include/linux/hmm.h\n@@ -123,6 +123,8 @@ struct hmm_range {\n  * Please see Documentation/mm/hmm.rst for how to use the range API.\n  */\n int hmm_range_fault(struct hmm_range *range);\n+int hmm_range_fault_unlocked_timeout(struct hmm_range *range,\n+\t\t\t\t     unsigned long timeout);\n \n /*\n  * HMM_RANGE_DEFAULT_TIMEOUT - default timeout (ms) when waiting for a range\ndiff --git a/lib/test_hmm.c b/lib/test_hmm.c\nindex 45c0cb9922184a..6205fb313bd05e 100644\n--- a/lib/test_hmm.c\n+++ b/lib/test_hmm.c\n@@ -389,6 +389,67 @@ static int dmirror_range_fault(struct dmirror *dmirror,\n \treturn ret;\n }\n \n+static int dmirror_range_fault_unlocked(struct dmirror *dmirror,\n+\t\t\t\t\tstruct hmm_range *range,\n+\t\t\t\t\tunsigned long timeout)\n+{\n+\tint ret;\n+\n+\twhile (true) {\n+\t\tret = hmm_range_fault_unlocked_timeout(range, timeout);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\n+\t\tmutex_lock(\u0026dmirror-\u003emutex);\n+\t\tif (mmu_interval_read_retry(range-\u003enotifier,\n+\t\t\t\t\t    range-\u003enotifier_seq)) {\n+\t\t\tmutex_unlock(\u0026dmirror-\u003emutex);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tbreak;\n+\t}\n+\n+\tret = dmirror_do_fault(dmirror, range);\n+\n+\tmutex_unlock(\u0026dmirror-\u003emutex);\n+out:\n+\treturn ret;\n+}\n+\n+static int dmirror_fault_unlocked(struct dmirror *dmirror,\n+\t\t\t\t  unsigned long start,\n+\t\t\t\t  unsigned long end, bool write,\n+\t\t\t\t  unsigned long timeout)\n+{\n+\tstruct mm_struct *mm = dmirror-\u003enotifier.mm;\n+\tunsigned long addr;\n+\tunsigned long pfns[32];\n+\tstruct hmm_range range = {\n+\t\t.notifier = \u0026dmirror-\u003enotifier,\n+\t\t.hmm_pfns = pfns,\n+\t\t.pfn_flags_mask = 0,\n+\t\t.default_flags =\n+\t\t\tHMM_PFN_REQ_FAULT | (write ? HMM_PFN_REQ_WRITE : 0),\n+\t\t.dev_private_owner = dmirror-\u003emdevice,\n+\t};\n+\tint ret = 0;\n+\n+\tif (!mmget_not_zero(mm))\n+\t\treturn -EFAULT;\n+\n+\tfor (addr = start; addr \u003c end; addr = range.end) {\n+\t\trange.start = addr;\n+\t\trange.end = min(addr + (ARRAY_SIZE(pfns) \u003c\u003c PAGE_SHIFT), end);\n+\n+\t\tret = dmirror_range_fault_unlocked(dmirror, \u0026range, timeout);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n+\n+\tmmput(mm);\n+\treturn ret;\n+}\n+\n static int dmirror_fault(struct dmirror *dmirror, unsigned long start,\n \t\t\t unsigned long end, bool write)\n {\n@@ -488,6 +549,48 @@ static int dmirror_read(struct dmirror *dmirror, struct hmm_dmirror_cmd *cmd)\n \treturn ret;\n }\n \n+static int dmirror_read_unlocked(struct dmirror *dmirror,\n+\t\t\t\t struct hmm_dmirror_cmd *cmd,\n+\t\t\t\t unsigned long timeout)\n+{\n+\tstruct dmirror_bounce bounce;\n+\tunsigned long start, end;\n+\tunsigned long size = cmd-\u003enpages \u003c\u003c PAGE_SHIFT;\n+\tint ret;\n+\n+\tstart = cmd-\u003eaddr;\n+\tend = start + size;\n+\tif (end \u003c start)\n+\t\treturn -EINVAL;\n+\n+\tret = dmirror_bounce_init(\u0026bounce, start, size);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\twhile (1) {\n+\t\tmutex_lock(\u0026dmirror-\u003emutex);\n+\t\tret = dmirror_do_read(dmirror, start, end, \u0026bounce);\n+\t\tmutex_unlock(\u0026dmirror-\u003emutex);\n+\t\tif (ret != -ENOENT)\n+\t\t\tbreak;\n+\n+\t\tstart = cmd-\u003eaddr + (bounce.cpages \u003c\u003c PAGE_SHIFT);\n+\t\tret = dmirror_fault_unlocked(dmirror, start, end, false, timeout);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t\tcmd-\u003efaults++;\n+\t}\n+\n+\tif (ret == 0) {\n+\t\tif (copy_to_user(u64_to_user_ptr(cmd-\u003eptr), bounce.ptr,\n+\t\t\t\t bounce.size))\n+\t\t\tret = -EFAULT;\n+\t}\n+\tcmd-\u003ecpages = bounce.cpages;\n+\tdmirror_bounce_fini(\u0026bounce);\n+\treturn ret;\n+}\n+\n static int dmirror_do_write(struct dmirror *dmirror, unsigned long start,\n \t\t\t    unsigned long end, struct dmirror_bounce *bounce)\n {\n@@ -1572,7 +1675,9 @@ static long dmirror_fops_unlocked_ioctl(struct file *filp,\n \t\tdmirror-\u003eflags = cmd.npages;\n \t\tret = 0;\n \t\tbreak;\n-\n+\tcase HMM_DMIRROR_READ_UNLOCKED:\n+\t\tret = dmirror_read_unlocked(dmirror, \u0026cmd, 0);\n+\t\tbreak;\n \tdefault:\n \t\treturn -EINVAL;\n \t}\ndiff --git a/lib/test_hmm_uapi.h b/lib/test_hmm_uapi.h\nindex f94c6d45733828..ea9b0ec404fb55 100644\n--- a/lib/test_hmm_uapi.h\n+++ b/lib/test_hmm_uapi.h\n@@ -38,6 +38,7 @@ struct hmm_dmirror_cmd {\n #define HMM_DMIRROR_CHECK_EXCLUSIVE\t_IOWR('H', 0x06, struct hmm_dmirror_cmd)\n #define HMM_DMIRROR_RELEASE\t\t_IOWR('H', 0x07, struct hmm_dmirror_cmd)\n #define HMM_DMIRROR_FLAGS\t\t_IOWR('H', 0x08, struct hmm_dmirror_cmd)\n+#define HMM_DMIRROR_READ_UNLOCKED\t_IOWR('H', 0x09, struct hmm_dmirror_cmd)\n \n #define HMM_DMIRROR_FLAG_FAIL_ALLOC\t(1ULL \u003c\u003c 0)\n \ndiff --git a/mm/hmm.c b/mm/hmm.c\nindex e5c1f4deed2447..fc2e1cd0cb22eb 100644\n--- a/mm/hmm.c\n+++ b/mm/hmm.c\n@@ -32,9 +32,27 @@\n \n struct hmm_vma_walk {\n \tstruct hmm_range\t*range;\n+\tbool\t\t\t*locked;\n \tunsigned long\t\tlast;\n+\tunsigned long\t\tend;\n+\tunsigned int\t\trequired_fault;\n };\n \n+/*\n+ * Internal sentinel returned by walk callbacks when they need a page fault.\n+ * The callback stores end/required_fault in hmm_vma_walk; the outer loop\n+ * consumes the sentinel and never propagates it to the caller.\n+ */\n+#define HMM_FAULT_PENDING\t-EAGAIN\n+\n+/*\n+ * Internal sentinel returned by hmm_do_fault() when handle_mm_fault()\n+ * completes a page fault with the mmap lock dropped. hmm_do_fault() sets\n+ * *locked = false; the outer loop consumes the sentinel and never propagates\n+ * it to the caller.\n+ */\n+#define HMM_FAULT_UNLOCKED\t-ENOLCK\n+\n enum {\n \tHMM_NEED_FAULT = 1 \u003c\u003c 0,\n \tHMM_NEED_WRITE_FAULT = 1 \u003c\u003c 1,\n@@ -60,37 +78,25 @@ static int hmm_pfns_fill(unsigned long addr, unsigned long end,\n }\n \n /*\n- * hmm_vma_fault() - fault in a range lacking valid pmd or pte(s)\n- * @addr: range virtual start address (inclusive)\n- * @end: range virtual end address (exclusive)\n- * @required_fault: HMM_NEED_* flags\n- * @walk: mm_walk structure\n- * Return: -EBUSY after page fault, or page fault error\n+ * hmm_record_fault() - record a range that needs to be faulted in\n  *\n- * This function will be called whenever pmd_none() or pte_none() returns true,\n- * or whenever there is no page directory covering the virtual address range.\n+ * Called by the walk callbacks when they discover that part of the range\n+ * needs a page fault.  The callback records what to fault and returns\n+ * HMM_FAULT_PENDING; the outer loop in hmm_range_fault_locked() drops\n+ * back out of walk_page_range() and invokes handle_mm_fault() from a context\n+ * where no page-table or hugetlb_vma_lock is held.\n  */\n-static int hmm_vma_fault(unsigned long addr, unsigned long end,\n-\t\t\t unsigned int required_fault, struct mm_walk *walk)\n+static int hmm_record_fault(unsigned long addr, unsigned long end,\n+\t\t\t    unsigned int required_fault,\n+\t\t\t    struct mm_walk *walk)\n {\n \tstruct hmm_vma_walk *hmm_vma_walk = walk-\u003eprivate;\n-\tstruct vm_area_struct *vma = walk-\u003evma;\n-\tunsigned int fault_flags = FAULT_FLAG_REMOTE;\n \n \tWARN_ON_ONCE(!required_fault);\n \thmm_vma_walk-\u003elast = addr;\n-\n-\tif (required_fault \u0026 HMM_NEED_WRITE_FAULT) {\n-\t\tif (!(vma-\u003evm_flags \u0026 VM_WRITE))\n-\t\t\treturn -EPERM;\n-\t\tfault_flags |= FAULT_FLAG_WRITE;\n-\t}\n-\n-\tfor (; addr \u003c end; addr += PAGE_SIZE)\n-\t\tif (handle_mm_fault(vma, addr, fault_flags, NULL) \u0026\n-\t\t    VM_FAULT_ERROR)\n-\t\t\treturn -EFAULT;\n-\treturn -EBUSY;\n+\thmm_vma_walk-\u003eend = end;\n+\thmm_vma_walk-\u003erequired_fault = required_fault;\n+\treturn HMM_FAULT_PENDING;\n }\n \n static unsigned int hmm_pte_need_fault(const struct hmm_vma_walk *hmm_vma_walk,\n@@ -174,7 +180,7 @@ static int hmm_vma_walk_hole(unsigned long addr, unsigned long end,\n \t\treturn hmm_pfns_fill(addr, end, range, HMM_PFN_ERROR);\n \t}\n \tif (required_fault)\n-\t\treturn hmm_vma_fault(addr, end, required_fault, walk);\n+\t\treturn hmm_record_fault(addr, end, required_fault, walk);\n \treturn hmm_pfns_fill(addr, end, range, 0);\n }\n \n@@ -209,7 +215,7 @@ static int hmm_vma_handle_pmd(struct mm_walk *walk, unsigned long addr,\n \trequired_fault =\n \t\thmm_range_need_fault(hmm_vma_walk, hmm_pfns, npages, cpu_flags);\n \tif (required_fault)\n-\t\treturn hmm_vma_fault(addr, end, required_fault, walk);\n+\t\treturn hmm_record_fault(addr, end, required_fault, walk);\n \n \tpfn = pmd_pfn(pmd) + ((addr \u0026 ~PMD_MASK) \u003e\u003e PAGE_SHIFT);\n \tfor (i = 0; addr \u003c end; addr += PAGE_SIZE, i++, pfn++) {\n@@ -328,7 +334,7 @@ static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr,\n fault:\n \tpte_unmap(ptep);\n \t/* Fault any virtual address we were asked to fault */\n-\treturn hmm_vma_fault(addr, end, required_fault, walk);\n+\treturn hmm_record_fault(addr, end, required_fault, walk);\n }\n \n #ifdef CONFIG_ARCH_HAS_PMD_SOFTLEAVES\n@@ -371,7 +377,7 @@ static int hmm_vma_handle_absent_pmd(struct mm_walk *walk, unsigned long start,\n \t\t\t\t\t      npages, 0);\n \tif (required_fault) {\n \t\tif (softleaf_is_device_private(entry))\n-\t\t\treturn hmm_vma_fault(addr, end, required_fault, walk);\n+\t\t\treturn hmm_record_fault(addr, end, required_fault, walk);\n \t\telse\n \t\t\treturn -EFAULT;\n \t}\n@@ -517,7 +523,7 @@ static int hmm_vma_walk_pud(pud_t *pudp, unsigned long start, unsigned long end,\n \t\t\t\t\t\t      npages, cpu_flags);\n \t\tif (required_fault) {\n \t\t\tspin_unlock(ptl);\n-\t\t\treturn hmm_vma_fault(addr, end, required_fault, walk);\n+\t\t\treturn hmm_record_fault(addr, end, required_fault, walk);\n \t\t}\n \n \t\tpfn = pud_pfn(pud) + ((addr \u0026 ~PUD_MASK) \u003e\u003e PAGE_SHIFT);\n@@ -564,21 +570,8 @@ static int hmm_vma_walk_hugetlb_entry(pte_t *pte, unsigned long hmask,\n \trequired_fault =\n \t\thmm_pte_need_fault(hmm_vma_walk, pfn_req_flags, cpu_flags);\n \tif (required_fault) {\n-\t\tint ret;\n-\n \t\tspin_unlock(ptl);\n-\t\thugetlb_vma_unlock_read(vma);\n-\t\t/*\n-\t\t * Avoid deadlock: drop the vma lock before calling\n-\t\t * hmm_vma_fault(), which will itself potentially take and\n-\t\t * drop the vma lock. This is also correct from a\n-\t\t * protection point of view, because there is no further\n-\t\t * use here of either pte or ptl after dropping the vma\n-\t\t * lock.\n-\t\t */\n-\t\tret = hmm_vma_fault(addr, end, required_fault, walk);\n-\t\thugetlb_vma_lock_read(vma);\n-\t\treturn ret;\n+\t\treturn hmm_record_fault(addr, end, required_fault, walk);\n \t}\n \n \tpfn = pte_pfn(entry) + ((start \u0026 ~hmask) \u003e\u003e PAGE_SHIFT);\n@@ -637,29 +630,67 @@ static const struct mm_walk_ops hmm_walk_ops = {\n \t.walk_lock\t= PGWALK_RDLOCK,\n };\n \n-/**\n- * hmm_range_fault - try to fault some address in a virtual address range\n- * @range:\targument structure\n- *\n- * Returns 0 on success or one of the following error codes:\n+/*\n+ * hmm_do_fault - fault in a range recorded by a walk callback\n  *\n- * -EINVAL:\tInvalid arguments or mm or virtual address is in an invalid vma\n- *\t\t(e.g., device file vma).\n- * -ENOMEM:\tOut of memory.\n- * -EPERM:\tInvalid permission (e.g., asking for write and range is read\n- *\t\tonly).\n- * -EBUSY:\tThe range has been invalidated and the caller needs to wait for\n- *\t\tthe invalidation to finish.\n- * -EFAULT:     A page was requested to be valid and could not be made valid\n- *              ie it has no backing VMA or it is illegal to access\n+ * Called from the outer loop in hmm_range_fault_locked() after a callback\n+ * returned HMM_FAULT_PENDING.  At this point we hold only mmap_lock;\n+ * the page-table spinlock and any hugetlb_vma_lock acquired by the walk\n+ * framework have already been released by the unwind.\n  *\n- * This is similar to get_user_pages(), except that it can read the page tables\n- * without mutating them (ie causing faults).\n+ * Returns -EBUSY on success (all pages faulted, caller should re-walk).\n+ * Returns a negative errno on failure.\n  */\n-int hmm_range_fault(struct hmm_range *range)\n+static int hmm_do_fault(struct mm_struct *mm,\n+\t\t\tstruct hmm_vma_walk *hmm_vma_walk)\n+{\n+\tunsigned long addr = hmm_vma_walk-\u003elast;\n+\tunsigned long end = hmm_vma_walk-\u003eend;\n+\tunsigned int required_fault = hmm_vma_walk-\u003erequired_fault;\n+\tunsigned int fault_flags = FAULT_FLAG_REMOTE;\n+\tstruct vm_area_struct *vma;\n+\n+\tif (hmm_vma_walk-\u003elocked)\n+\t\tfault_flags |= FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;\n+\n+\tvma = vma_lookup(mm, addr);\n+\tif (!vma)\n+\t\treturn -EFAULT;\n+\n+\tif (required_fault \u0026 HMM_NEED_WRITE_FAULT) {\n+\t\tif (!(vma-\u003evm_flags \u0026 VM_WRITE))\n+\t\t\treturn -EPERM;\n+\t\tfault_flags |= FAULT_FLAG_WRITE;\n+\t}\n+\n+\tfor (; addr \u003c end; addr += PAGE_SIZE) {\n+\t\tvm_fault_t ret;\n+\n+\t\tret = handle_mm_fault(vma, addr, fault_flags, NULL);\n+\n+\t\tif (ret \u0026 (VM_FAULT_COMPLETED | VM_FAULT_RETRY)) {\n+\t\t\t*hmm_vma_walk-\u003elocked = false;\n+\t\t\treturn HMM_FAULT_UNLOCKED;\n+\t\t}\n+\n+\t\tif (ret \u0026 VM_FAULT_ERROR) {\n+\t\t\tint err = vm_fault_to_errno(ret, 0);\n+\n+\t\t\tif (WARN_ON(!err))\n+\t\t\t\terr = -EINVAL;\n+\n+\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\treturn -EBUSY;\n+}\n+\n+static int hmm_range_fault_locked(struct hmm_range *range, bool *locked)\n {\n \tstruct hmm_vma_walk hmm_vma_walk = {\n \t\t.range = range,\n+\t\t.locked = locked,\n \t\t.last = range-\u003estart,\n \t};\n \tstruct mm_struct *mm = range-\u003enotifier-\u003emm;\n@@ -674,6 +705,22 @@ int hmm_range_fault(struct hmm_range *range)\n \t\t\treturn -EBUSY;\n \t\tret = walk_page_range(mm, hmm_vma_walk.last, range-\u003eend,\n \t\t\t\t      \u0026hmm_walk_ops, \u0026hmm_vma_walk);\n+\t\t/*\n+\t\t * When HMM_FAULT_PENDING is returned a walk callback\n+\t\t * recorded a range that needs handle_mm_fault();\n+\t\t * hmm_do_fault() runs the fault outside walk_page_range()\n+\t\t * (so no page-table or hugetlb_vma_lock is held) and\n+\t\t * returns -EBUSY so the loop re-walks and picks up the\n+\t\t * now-present entries.\n+\t\t */\n+\t\tif (ret == HMM_FAULT_PENDING) {\n+\t\t\tret = hmm_do_fault(mm, \u0026hmm_vma_walk);\n+\t\t\tif (ret == HMM_FAULT_UNLOCKED) {\n+\t\t\t\tif (fatal_signal_pending(current))\n+\t\t\t\t\treturn -EINTR;\n+\t\t\t\treturn -EBUSY;\n+\t\t\t}\n+\t\t}\n \t\t/*\n \t\t * When -EBUSY is returned the loop restarts with\n \t\t * hmm_vma_walk.last set to an address that has not been stored\n@@ -683,8 +730,100 @@ int hmm_range_fault(struct hmm_range *range)\n \t} while (ret == -EBUSY);\n \treturn ret;\n }\n+\n+/**\n+ * hmm_range_fault - try to fault some address in a virtual address range\n+ * @range:\targument structure\n+ *\n+ * Returns 0 on success or one of the following error codes:\n+ *\n+ * -EINVAL:\tInvalid arguments or mm or virtual address is in an invalid vma\n+ *\t\t(e.g., device file vma).\n+ * -ENOMEM:\tOut of memory.\n+ * -EPERM:\tInvalid permission (e.g., asking for write and range is read\n+ *\t\tonly).\n+ * -EBUSY:\tThe range has been invalidated and the caller needs to wait for\n+ *\t\tthe invalidation to finish.\n+ * -EFAULT:     A page was requested to be valid and could not be made valid\n+ *              ie it has no backing VMA or it is illegal to access\n+ *\n+ * This is similar to get_user_pages(), except that it can read the page tables\n+ * without mutating them (ie causing faults).\n+ *\n+ * The mmap lock must be held by the caller and will remain held on return.\n+ * New users should prefer hmm_range_fault_unlocked_timeout() unless they\n+ * specifically need to keep the mmap lock held across the call. This helper\n+ * cannot support VMAs whose fault handlers need to drop the mmap lock.\n+ */\n+int hmm_range_fault(struct hmm_range *range)\n+{\n+\treturn hmm_range_fault_locked(range, NULL);\n+}\n EXPORT_SYMBOL(hmm_range_fault);\n \n+/**\n+ * hmm_range_fault_unlocked_timeout - fault in a range with a retry timeout\n+ * @range:\targument structure\n+ * @timeout:\ttimeout in jiffies for internal -EBUSY retries, or 0 to retry\n+ *\t\tindefinitely\n+ *\n+ * The caller must not hold the mmap lock. The function takes the mmap read\n+ * lock internally and allows handle_mm_fault() to drop it during faults. If\n+ * the mmap lock is dropped or the range is invalidated, the function refreshes\n+ * range-\u003enotifier_seq and restarts the walk internally.\n+ *\n+ * Passing 0 for @timeout retries indefinitely. A non-zero @timeout is a caller\n+ * policy limit for repeated mmu-notifier invalidation retries. HMM does not\n+ * interrupt page fault handling when the timeout expires, but returns -EBUSY\n+ * if the retry budget is exhausted before a stable range is obtained.\n+ *\n+ * Returns 0 on success or one of the error codes documented for\n+ * hmm_range_fault(). -EINTR is returned if mmap_lock acquisition is\n+ * interrupted or a fatal signal is pending during retry handling.\n+ */\n+int hmm_range_fault_unlocked_timeout(struct hmm_range *range,\n+\t\t\t\t     unsigned long timeout)\n+{\n+\tstruct mm_struct *mm = range-\u003enotifier-\u003emm;\n+\tunsigned long deadline = 0;\n+\tbool locked = false;\n+\tint ret;\n+\n+\tdo {\n+\t\tif (fatal_signal_pending(current))\n+\t\t\treturn -EINTR;\n+\n+\t\tif (timeout) {\n+\t\t\t/*\n+\t\t\t * If the previous fault dropped mmap_lock, then the fault\n+\t\t\t * handler made progress. Restart the retry timeout in that\n+\t\t\t * case, but keep the existing deadline for ordinary -EBUSY\n+\t\t\t * retries.\n+\t\t\t */\n+\t\t\tif (!locked)\n+\t\t\t\tdeadline = jiffies + timeout;\n+\n+\t\t\tif (time_after(jiffies, deadline))\n+\t\t\t\treturn -EBUSY;\n+\t\t}\n+\n+\t\trange-\u003enotifier_seq =\n+\t\t\tmmu_interval_read_begin(range-\u003enotifier);\n+\n+\t\tret = mmap_read_lock_killable(mm);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tlocked = true;\n+\t\tret = hmm_range_fault_locked(range, \u0026locked);\n+\t\tif (locked)\n+\t\t\tmmap_read_unlock(mm);\n+\t} while (ret == -EBUSY);\n+\n+\treturn ret;\n+}\n+EXPORT_SYMBOL(hmm_range_fault_unlocked_timeout);\n+\n /**\n  * hmm_dma_map_alloc - Allocate HMM map structure\n  * @dev: device to allocate structure for\ndiff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c\nindex 6fccbdab02ee81..5acb728666f8c1 100644\n--- a/tools/testing/selftests/mm/hmm-tests.c\n+++ b/tools/testing/selftests/mm/hmm-tests.c\n@@ -29,6 +29,10 @@\n #include \u003csys/mman.h\u003e\n #include \u003csys/ioctl.h\u003e\n #include \u003csys/time.h\u003e\n+#include \u003csys/syscall.h\u003e\n+#include \u003csys/eventfd.h\u003e\n+#include \u003clinux/userfaultfd.h\u003e\n+#include \u003cpoll.h\u003e\n \n /*\n  * This is a private UAPI to the kernel test module so it isn't exported\n@@ -2952,4 +2956,150 @@ TEST_F_TIMEOUT(hmm, benchmark_thp_migration, 120)\n \t\t\t\t\t\u0026thp_results, \u0026regular_results);\n \t}\n }\n+/*\n+ * Test that HMM can fault in pages backed by userfaultfd using the\n+ * hmm_range_fault_unlocked_timeout() path with no timeout. This exercises\n+ * the lock-drop retry logic in the HMM framework.\n+ */\n+struct uffd_thread_args {\n+\tint uffd;\n+\tint stop_fd;\n+\tvoid *page_buffer;\n+\tunsigned long page_size;\n+};\n+\n+static void *uffd_handler_thread(void *arg)\n+{\n+\tstruct uffd_thread_args *args = arg;\n+\tstruct uffd_msg msg;\n+\tstruct uffdio_copy copy;\n+\tstruct pollfd pollfd[2];\n+\tint ret;\n+\n+\tpollfd[0].fd = args-\u003euffd;\n+\tpollfd[0].events = POLLIN;\n+\tpollfd[1].fd = args-\u003estop_fd;\n+\tpollfd[1].events = POLLIN;\n+\n+\twhile (1) {\n+\t\tret = poll(pollfd, 2, -1);\n+\t\tif (ret \u003c= 0)\n+\t\t\tbreak;\n+\t\tif (pollfd[1].revents)\n+\t\t\tbreak;\n+\t\tif (!(pollfd[0].revents \u0026 POLLIN))\n+\t\t\tbreak;\n+\n+\t\tret = read(args-\u003euffd, \u0026msg, sizeof(msg));\n+\t\tif (ret != sizeof(msg))\n+\t\t\tbreak;\n+\n+\t\tif (msg.event != UFFD_EVENT_PAGEFAULT)\n+\t\t\tbreak;\n+\n+\t\t/* Fill the page with a known pattern */\n+\t\tmemset(args-\u003epage_buffer, 0xAB, args-\u003epage_size);\n+\n+\t\tcopy.dst = msg.arg.pagefault.address \u0026 ~(args-\u003epage_size - 1);\n+\t\tcopy.src = (unsigned long)args-\u003epage_buffer;\n+\t\tcopy.len = args-\u003epage_size;\n+\t\tcopy.mode = 0;\n+\t\tcopy.copy = 0;\n+\n+\t\tret = ioctl(args-\u003euffd, UFFDIO_COPY, \u0026copy);\n+\t\tif (ret \u003c 0)\n+\t\t\tbreak;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+TEST_F(hmm, userfaultfd_read)\n+{\n+\tstruct hmm_buffer *buffer;\n+\tstruct uffd_thread_args uffd_args;\n+\tunsigned long npages;\n+\tunsigned long size;\n+\tunsigned long i;\n+\tunsigned char *ptr;\n+\tpthread_t thread;\n+\tint uffd;\n+\tint stop_fd;\n+\tint ret;\n+\tstruct uffdio_api api;\n+\tstruct uffdio_register reg;\n+\tuint64_t stop = 1;\n+\tssize_t nwrite;\n+\n+\tnpages = 4;\n+\tsize = npages \u003c\u003c self-\u003epage_shift;\n+\n+\t/* Create userfaultfd */\n+\tuffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);\n+\tif (uffd \u003c 0)\n+\t\tSKIP(return, \"userfaultfd not available\");\n+\n+\tapi.api = UFFD_API;\n+\tapi.features = 0;\n+\tret = ioctl(uffd, UFFDIO_API, \u0026api);\n+\tASSERT_EQ(ret, 0);\n+\n+\tbuffer = malloc(sizeof(*buffer));\n+\tASSERT_NE(buffer, NULL);\n+\n+\tbuffer-\u003efd = -1;\n+\tbuffer-\u003esize = size;\n+\tbuffer-\u003emirror = malloc(size);\n+\tASSERT_NE(buffer-\u003emirror, NULL);\n+\n+\t/* Create anonymous mapping */\n+\tbuffer-\u003eptr = mmap(NULL, size,\n+\t\t\t   PROT_READ | PROT_WRITE,\n+\t\t\t   MAP_PRIVATE | MAP_ANONYMOUS,\n+\t\t\t   -1, 0);\n+\tASSERT_NE(buffer-\u003eptr, MAP_FAILED);\n+\n+\t/* Register the region with userfaultfd */\n+\treg.range.start = (unsigned long)buffer-\u003eptr;\n+\treg.range.len = size;\n+\treg.mode = UFFDIO_REGISTER_MODE_MISSING;\n+\tret = ioctl(uffd, UFFDIO_REGISTER, \u0026reg);\n+\tASSERT_EQ(ret, 0);\n+\n+\t/* Set up the handler thread */\n+\tuffd_args.uffd = uffd;\n+\tstop_fd = eventfd(0, EFD_CLOEXEC);\n+\tASSERT_GE(stop_fd, 0);\n+\tuffd_args.stop_fd = stop_fd;\n+\tuffd_args.page_buffer = malloc(self-\u003epage_size);\n+\tASSERT_NE(uffd_args.page_buffer, NULL);\n+\tuffd_args.page_size = self-\u003epage_size;\n+\n+\tret = pthread_create(\u0026thread, NULL, uffd_handler_thread, \u0026uffd_args);\n+\tASSERT_EQ(ret, 0);\n+\n+\t/*\n+\t * Use the unlocked read path which allows the mmap lock to be\n+\t * dropped during the fault, enabling userfaultfd resolution.\n+\t */\n+\tret = hmm_dmirror_cmd(self-\u003efd, HMM_DMIRROR_READ_UNLOCKED,\n+\t\t\t      buffer, npages);\n+\tASSERT_EQ(ret, 0);\n+\tASSERT_EQ(buffer-\u003ecpages, npages);\n+\n+\t/* Verify the device read the data filled by the uffd handler */\n+\tptr = buffer-\u003emirror;\n+\tfor (i = 0; i \u003c size; ++i)\n+\t\tASSERT_EQ(ptr[i], (unsigned char)0xAB);\n+\n+\tnwrite = write(stop_fd, \u0026stop, sizeof(stop));\n+\tASSERT_EQ(nwrite, sizeof(stop));\n+\tpthread_join(thread, NULL);\n+\tclose(stop_fd);\n+\tfree(uffd_args.page_buffer);\n+\tclose(uffd);\n+\thmm_buffer_free(buffer);\n+}\n+\n+\n TEST_HARNESS_MAIN\n"
}

2/1 2026/07/16 00:09 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "TEST_HMM",
    "DRM_ACCEL_AMDXDNA",
    "DRM_GPUSVM",
    "DRM_NOUVEAU_SVM",
    "MSHV_ROOT",
    "INFINIBAND_ON_DEMAND_PAGING"
  ],
  "FocusSymbols": [
    "hmm_range_fault_unlocked_timeout",
    "hmm_range_fault",
    "hmm_do_fault",
    "hmm_range_fault_locked",
    "hmm_record_fault",
    "dmirror_range_fault_unlocked",
    "dmirror_fault_unlocked",
    "dmirror_read_unlocked"
  ],
  "Reasoning": "The patch introduces a new unlocked timeout variant for HMM range faults (`hmm_range_fault_unlocked_timeout`) and refactors the existing `hmm_range_fault` to use a locked helper. It also updates several drivers and the HMM test module to use the new unlocked variant. This is a functional change to core memory management and device driver interaction, which should be fuzzed to ensure the new retry and locking logic is robust.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit bd08126468a3d288e396fd6e6f69a9fc2c70a659
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Jul 16 00:09:08 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/mm/hmm.rst b/Documentation/mm/hmm.rst
index 7d61b7a8b65b79..e021218ada58fe 100644
--- a/Documentation/mm/hmm.rst
+++ b/Documentation/mm/hmm.rst
@@ -156,42 +156,57 @@ During the ops->invalidate() callback the device driver must perform the
 update action to the range (mark range read only, or fully unmap, etc.). The
 device must complete the update before the driver callback returns.
 
-When the device driver wants to populate a range of virtual addresses, it can
-use::
+When the device driver wants to populate a range of virtual addresses, the
+normal interface is::
 
-  int hmm_range_fault(struct hmm_range *range);
+  int hmm_range_fault_unlocked_timeout(struct hmm_range *range,
+                                       unsigned long timeout);
 
 It will trigger a page fault on missing or read-only entries if write access is
 requested (see below). Page faults use the generic mm page fault code path just
-like a CPU page fault. The usage pattern is::
+like a CPU page fault.
+
+The caller must not hold ``mmap_read_lock`` before the call.
+``hmm_range_fault_unlocked_timeout()`` takes the mmap read lock internally and
+allows ``handle_mm_fault()`` to drop it during fault handling. This is required
+for VMAs whose fault handlers may release the mmap lock, for example regions
+managed by ``userfaultfd``.
+
+If the mmap lock is dropped or the range is invalidated, the function refreshes
+``range->notifier_seq`` and restarts the walk internally. ``-EINTR`` is returned
+if mmap lock acquisition is interrupted or a fatal signal is pending during
+retry handling.
+
+The timeout is specified in jiffies; passing ``0`` means retry indefinitely. The
+timeout exists to preserve caller policy for repeated mmu-notifier invalidation
+and is checked between retry attempts. HMM does not interrupt page fault
+handling when the timeout expires, but returns ``-EBUSY`` if the retry budget is
+exhausted before a stable range is obtained.
+
+The usage pattern is::
 
  int driver_populate_range(...)
  {
       struct hmm_range range;
+      unsigned long timeout;
       ...
 
+      timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
       range.notifier = &interval_sub;
       range.start = ...;
       range.end = ...;
       range.hmm_pfns = ...;
 
-      if (!mmget_not_zero(interval_sub->notifier.mm))
+      if (!mmget_not_zero(interval_sub.mm))
           return -EFAULT;
 
  again:
-      range.notifier_seq = mmu_interval_read_begin(&interval_sub);
-      mmap_read_lock(mm);
-      ret = hmm_range_fault(&range);
-      if (ret) {
-          mmap_read_unlock(mm);
-          if (ret == -EBUSY)
-                 goto again;
-          return ret;
-      }
-      mmap_read_unlock(mm);
+      ret = hmm_range_fault_unlocked_timeout(&range, timeout);
+      if (ret)
+          goto out_put;
 
       take_lock(driver->update);
-      if (mmu_interval_read_retry(&ni, range.notifier_seq) {
+      if (mmu_interval_read_retry(range.notifier, range.notifier_seq)) {
           release_lock(driver->update);
           goto again;
       }
@@ -200,13 +215,31 @@ like a CPU page fault. The usage pattern is::
        * under the update lock */
 
       release_lock(driver->update);
-      return 0;
+      ret = 0;
+
+ out_put:
+      mmput(interval_sub.mm);
+      return ret;
  }
 
 The driver->update lock is the same lock that the driver takes inside its
 invalidate() callback. That lock must be held before calling
 mmu_interval_read_retry() to avoid any race with a concurrent CPU page table
-update.
+update. The retry check must use the same notifier and sequence number stored
+in ``range`` by ``hmm_range_fault_unlocked_timeout()``.
+
+Holding the mmap lock across HMM faults
+=======================================
+
+Most callers should use ``hmm_range_fault_unlocked_timeout()``. If a driver
+really needs to hold the mmap lock across work outside HMM, it can use::
+
+  int hmm_range_fault(struct hmm_range *range);
+
+The mmap lock must be held by the caller and will remain held on return. This
+interface cannot support VMAs whose fault handlers need to drop the mmap lock.
+New callers should prefer ``hmm_range_fault_unlocked_timeout()`` unless they
+have a specific requirement to keep the mmap lock held across the call.
 
 Leverage default_flags and pfn_flags_mask
 =========================================
@@ -221,8 +254,8 @@ permission, it sets::
     range->default_flags = HMM_PFN_REQ_FAULT;
     range->pfn_flags_mask = 0;
 
-and calls hmm_range_fault() as described above. This will fill fault all pages
-in the range with at least read permission.
+and calls the HMM range fault helper as described above. This will fault
+all pages in the range with at least read permission.
 
 Now let's say the driver wants to do the same except for one page in the range for
 which it wants to have write permission. Now driver set::
@@ -236,9 +269,9 @@ address == range->start + (index_of_write << PAGE_SHIFT) it will fault with
 write permission i.e., if the CPU pte does not have write permission set then HMM
 will call handle_mm_fault().
 
-After hmm_range_fault completes the flag bits are set to the current state of
-the page tables, ie HMM_PFN_VALID | HMM_PFN_WRITE will be set if the page is
-writable.
+After the HMM range fault helper completes the flag bits are set to the
+current state of the page tables, ie HMM_PFN_VALID | HMM_PFN_WRITE will be
+set if the page is writable.
 
 
 Represent and manage device memory from core kernel point of view
diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c
index 54486960cbf5e6..b5b4ca263002da 100644
--- a/drivers/accel/amdxdna/aie2_ctx.c
+++ b/drivers/accel/amdxdna/aie2_ctx.c
@@ -1034,7 +1034,7 @@ static int aie2_populate_range(struct amdxdna_gem_obj *abo)
 	bool found;
 	int ret;
 
-	timeout = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
+	timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
 again:
 	found = false;
 	down_write(&xdna->notifier_lock);
@@ -1061,24 +1061,9 @@ static int aie2_populate_range(struct amdxdna_gem_obj *abo)
 		return -EFAULT;
 	}
 
-	mapp->range.notifier_seq = mmu_interval_read_begin(&mapp->notifier);
-	mmap_read_lock(mm);
-	ret = hmm_range_fault(&mapp->range);
-	mmap_read_unlock(mm);
-	if (ret) {
-		if (time_after(jiffies, timeout)) {
-			ret = -ETIME;
-			goto put_mm;
-		}
-
-		if (ret == -EBUSY) {
-			amdxdna_umap_put(mapp);
-			mmput(mm);
-			goto again;
-		}
-
+	ret = hmm_range_fault_unlocked_timeout(&mapp->range, timeout);
+	if (ret)
 		goto put_mm;
-	}
 
 	down_write(&xdna->notifier_lock);
 	if (mmu_interval_read_retry(&mapp->notifier, mapp->range.notifier_seq)) {
@@ -1096,7 +1081,7 @@ static int aie2_populate_range(struct amdxdna_gem_obj *abo)
 put_mm:
 	amdxdna_umap_put(mapp);
 	mmput(mm);
-	return ret;
+	return ret == -EBUSY ? -ETIME : ret;
 }
 
 int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job, u64 *seq)
diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c
index 958cb605aedd53..de5bbfe58ee945 100644
--- a/drivers/gpu/drm/drm_gpusvm.c
+++ b/drivers/gpu/drm/drm_gpusvm.c
@@ -773,8 +773,7 @@ enum drm_gpusvm_scan_result drm_gpusvm_scan_mm(struct drm_gpusvm_range *range,
 		.end = end,
 		.dev_private_owner = dev_private_owner,
 	};
-	unsigned long timeout =
-		jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
+	unsigned long timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
 	enum drm_gpusvm_scan_result state = DRM_GPUSVM_SCAN_UNPOPULATED, new_state;
 	unsigned long *pfns;
 	unsigned long npages = npages_in_range(start, end);
@@ -788,22 +787,7 @@ enum drm_gpusvm_scan_result drm_gpusvm_scan_mm(struct drm_gpusvm_range *range,
 	hmm_range.hmm_pfns = pfns;
 
 retry:
-	hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
-	mmap_read_lock(range->gpusvm->mm);
-
-	while (true) {
-		err = hmm_range_fault(&hmm_range);
-		if (err == -EBUSY) {
-			if (time_after(jiffies, timeout))
-				break;
-
-			hmm_range.notifier_seq =
-				mmu_interval_read_begin(notifier);
-			continue;
-		}
-		break;
-	}
-	mmap_read_unlock(range->gpusvm->mm);
+	err = hmm_range_fault_unlocked_timeout(&hmm_range, timeout);
 	if (err)
 		goto err_free;
 
@@ -1406,8 +1390,7 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
 		.dev_private_owner = ctx->device_private_page_owner,
 	};
 	void *zdd;
-	unsigned long timeout =
-		jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
+	unsigned long timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
 	unsigned long i, j;
 	unsigned long npages = npages_in_range(pages_start, pages_end);
 	unsigned long num_dma_mapped;
@@ -1422,9 +1405,6 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
 	struct dma_iova_state *state = &svm_pages->state;
 
 retry:
-	if (time_after(jiffies, timeout))
-		return -EBUSY;
-
 	hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
 	if (drm_gpusvm_pages_valid_unlocked(gpusvm, svm_pages))
 		goto set_seqno;
@@ -1439,21 +1419,7 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
 	}
 
 	hmm_range.hmm_pfns = pfns;
-	while (true) {
-		mmap_read_lock(mm);
-		err = hmm_range_fault(&hmm_range);
-		mmap_read_unlock(mm);
-
-		if (err == -EBUSY) {
-			if (time_after(jiffies, timeout))
-				break;
-
-			hmm_range.notifier_seq =
-				mmu_interval_read_begin(notifier);
-			continue;
-		}
-		break;
-	}
+	err = hmm_range_fault_unlocked_timeout(&hmm_range, timeout);
 	mmput(mm);
 	if (err)
 		goto err_free;
@@ -1720,8 +1686,7 @@ int drm_gpusvm_range_evict(struct drm_gpusvm *gpusvm,
 		.end = drm_gpusvm_range_end(range),
 		.dev_private_owner = NULL,
 	};
-	unsigned long timeout =
-		jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
+	unsigned long timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
 	unsigned long *pfns;
 	unsigned long npages = npages_in_range(drm_gpusvm_range_start(range),
 					       drm_gpusvm_range_end(range));
@@ -1736,24 +1701,12 @@ int drm_gpusvm_range_evict(struct drm_gpusvm *gpusvm,
 		return -ENOMEM;
 
 	hmm_range.hmm_pfns = pfns;
-	while (!time_after(jiffies, timeout)) {
-		hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
-		if (time_after(jiffies, timeout)) {
-			err = -ETIME;
-			break;
-		}
-
-		mmap_read_lock(mm);
-		err = hmm_range_fault(&hmm_range);
-		mmap_read_unlock(mm);
-		if (err != -EBUSY)
-			break;
-	}
+	err = hmm_range_fault_unlocked_timeout(&hmm_range, timeout);
 
 	kvfree(pfns);
 	mmput(mm);
 
-	return err;
+	return err == -EBUSY ? -ETIME : err;
 }
 EXPORT_SYMBOL_GPL(drm_gpusvm_range_evict);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_svm.c b/drivers/gpu/drm/nouveau/nouveau_svm.c
index dcc92131488eab..58735446d7832c 100644
--- a/drivers/gpu/drm/nouveau/nouveau_svm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_svm.c
@@ -678,20 +678,22 @@ static int nouveau_range_fault(struct nouveau_svmm *svmm,
 	range.end = notifier->notifier.interval_tree.last + 1;
 
 	while (true) {
-		if (time_after(jiffies, timeout)) {
+		long remaining = timeout - jiffies;
+
+		/*
+		 * The HMM timeout only bounds retries while HMM is walking and
+		 * faulting the range. This fault is handled by a kernel worker,
+		 * so fatal signals from the faulting process cannot stop an
+		 * endless stream of invalidations here.
+		 */
+		if (time_after_eq(jiffies, timeout)) {
 			ret = -EBUSY;
 			goto out;
 		}
 
-		range.notifier_seq = mmu_interval_read_begin(range.notifier);
-		mmap_read_lock(mm);
-		ret = hmm_range_fault(&range);
-		mmap_read_unlock(mm);
-		if (ret) {
-			if (ret == -EBUSY)
-				continue;
+		ret = hmm_range_fault_unlocked_timeout(&range, remaining);
+		if (ret)
 			goto out;
-		}
 
 		mutex_lock(&svmm->mutex);
 		if (mmu_interval_read_retry(range.notifier,
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 6d65e5b4215295..dddaade31b5dc3 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -381,46 +381,6 @@ int mshv_region_get(struct mshv_mem_region *region)
 	return kref_get_unless_zero(&region->mreg_refcount);
 }
 
-/**
- * mshv_region_hmm_fault_and_lock - Handle HMM faults and lock the memory region
- * @region: Pointer to the memory region structure
- * @range: Pointer to the HMM range structure
- *
- * This function performs the following steps:
- * 1. Reads the notifier sequence for the HMM range.
- * 2. Acquires a read lock on the memory map.
- * 3. Handles HMM faults for the specified range.
- * 4. Releases the read lock on the memory map.
- * 5. If successful, locks the memory region mutex.
- * 6. Verifies if the notifier sequence has changed during the operation.
- *    If it has, releases the mutex and returns -EBUSY to match with
- *    hmm_range_fault() return code for repeating.
- *
- * Return: 0 on success, a negative error code otherwise.
- */
-static int mshv_region_hmm_fault_and_lock(struct mshv_mem_region *region,
-					  struct hmm_range *range)
-{
-	int ret;
-
-	range->notifier_seq = mmu_interval_read_begin(range->notifier);
-	mmap_read_lock(region->mreg_mni.mm);
-	ret = hmm_range_fault(range);
-	mmap_read_unlock(region->mreg_mni.mm);
-	if (ret)
-		return ret;
-
-	mutex_lock(&region->mreg_mutex);
-
-	if (mmu_interval_read_retry(range->notifier, range->notifier_seq)) {
-		mutex_unlock(&region->mreg_mutex);
-		cond_resched();
-		return -EBUSY;
-	}
-
-	return 0;
-}
-
 /**
  * mshv_region_range_fault - Handle memory range faults for a given region.
  * @region: Pointer to the memory region structure.
@@ -452,13 +412,19 @@ static int mshv_region_range_fault(struct mshv_mem_region *region,
 	range.start = region->start_uaddr + page_offset * HV_HYP_PAGE_SIZE;
 	range.end = range.start + page_count * HV_HYP_PAGE_SIZE;
 
-	do {
-		ret = mshv_region_hmm_fault_and_lock(region, &range);
-	} while (ret == -EBUSY);
-
+again:
+	ret = hmm_range_fault_unlocked_timeout(&range, 0);
 	if (ret)
 		goto out;
 
+	mutex_lock(&region->mreg_mutex);
+
+	if (mmu_interval_read_retry(range.notifier, range.notifier_seq)) {
+		mutex_unlock(&region->mreg_mutex);
+		cond_resched();
+		goto again;
+	}
+
 	for (i = 0; i < page_count; i++)
 		region->mreg_pages[page_offset + i] = hmm_pfn_to_page(pfns[i]);
 
diff --git a/drivers/infiniband/core/umem_odp.c b/drivers/infiniband/core/umem_odp.c
index 404fa1cc32542c..9cc21cd762d989 100644
--- a/drivers/infiniband/core/umem_odp.c
+++ b/drivers/infiniband/core/umem_odp.c
@@ -329,7 +329,7 @@ int ib_umem_odp_map_dma_and_lock(struct ib_umem_odp *umem_odp, u64 user_virt,
 	struct mm_struct *owning_mm = umem_odp->umem.owning_mm;
 	int pfn_index, dma_index, ret = 0, start_idx;
 	unsigned int page_shift, hmm_order, pfn_start_idx;
-	unsigned long num_pfns, current_seq;
+	unsigned long num_pfns;
 	struct hmm_range range = {};
 	unsigned long timeout;
 
@@ -363,26 +363,18 @@ int ib_umem_odp_map_dma_and_lock(struct ib_umem_odp *umem_odp, u64 user_virt,
 	}
 
 	range.hmm_pfns = &(umem_odp->map.pfn_list[pfn_start_idx]);
-	timeout = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
+	timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
 
 retry:
-	current_seq = range.notifier_seq =
-		mmu_interval_read_begin(&umem_odp->notifier);
-
-	mmap_read_lock(owning_mm);
-	ret = hmm_range_fault(&range);
-	mmap_read_unlock(owning_mm);
-	if (unlikely(ret)) {
-		if (ret == -EBUSY && !time_after(jiffies, timeout))
-			goto retry;
+	ret = hmm_range_fault_unlocked_timeout(&range, timeout);
+	if (unlikely(ret))
 		goto out_put_mm;
-	}
 
 	start_idx = (range.start - ib_umem_start(umem_odp)) >> page_shift;
 	dma_index = start_idx;
 
 	mutex_lock(&umem_odp->umem_mutex);
-	if (mmu_interval_read_retry(&umem_odp->notifier, current_seq)) {
+	if (mmu_interval_read_retry(&umem_odp->notifier, range.notifier_seq)) {
 		mutex_unlock(&umem_odp->umem_mutex);
 		goto retry;
 	}
diff --git a/include/linux/hmm.h b/include/linux/hmm.h
index db75ffc949a7ac..6f04e3932f5b0c 100644
--- a/include/linux/hmm.h
+++ b/include/linux/hmm.h
@@ -123,6 +123,8 @@ struct hmm_range {
  * Please see Documentation/mm/hmm.rst for how to use the range API.
  */
 int hmm_range_fault(struct hmm_range *range);
+int hmm_range_fault_unlocked_timeout(struct hmm_range *range,
+				     unsigned long timeout);
 
 /*
  * HMM_RANGE_DEFAULT_TIMEOUT - default timeout (ms) when waiting for a range
diff --git a/lib/test_hmm.c b/lib/test_hmm.c
index 45c0cb9922184a..6205fb313bd05e 100644
--- a/lib/test_hmm.c
+++ b/lib/test_hmm.c
@@ -389,6 +389,67 @@ static int dmirror_range_fault(struct dmirror *dmirror,
 	return ret;
 }
 
+static int dmirror_range_fault_unlocked(struct dmirror *dmirror,
+					struct hmm_range *range,
+					unsigned long timeout)
+{
+	int ret;
+
+	while (true) {
+		ret = hmm_range_fault_unlocked_timeout(range, timeout);
+		if (ret)
+			goto out;
+
+		mutex_lock(&dmirror->mutex);
+		if (mmu_interval_read_retry(range->notifier,
+					    range->notifier_seq)) {
+			mutex_unlock(&dmirror->mutex);
+			continue;
+		}
+		break;
+	}
+
+	ret = dmirror_do_fault(dmirror, range);
+
+	mutex_unlock(&dmirror->mutex);
+out:
+	return ret;
+}
+
+static int dmirror_fault_unlocked(struct dmirror *dmirror,
+				  unsigned long start,
+				  unsigned long end, bool write,
+				  unsigned long timeout)
+{
+	struct mm_struct *mm = dmirror->notifier.mm;
+	unsigned long addr;
+	unsigned long pfns[32];
+	struct hmm_range range = {
+		.notifier = &dmirror->notifier,
+		.hmm_pfns = pfns,
+		.pfn_flags_mask = 0,
+		.default_flags =
+			HMM_PFN_REQ_FAULT | (write ? HMM_PFN_REQ_WRITE : 0),
+		.dev_private_owner = dmirror->mdevice,
+	};
+	int ret = 0;
+
+	if (!mmget_not_zero(mm))
+		return -EFAULT;
+
+	for (addr = start; addr < end; addr = range.end) {
+		range.start = addr;
+		range.end = min(addr + (ARRAY_SIZE(pfns) << PAGE_SHIFT), end);
+
+		ret = dmirror_range_fault_unlocked(dmirror, &range, timeout);
+		if (ret)
+			break;
+	}
+
+	mmput(mm);
+	return ret;
+}
+
 static int dmirror_fault(struct dmirror *dmirror, unsigned long start,
 			 unsigned long end, bool write)
 {
@@ -488,6 +549,48 @@ static int dmirror_read(struct dmirror *dmirror, struct hmm_dmirror_cmd *cmd)
 	return ret;
 }
 
+static int dmirror_read_unlocked(struct dmirror *dmirror,
+				 struct hmm_dmirror_cmd *cmd,
+				 unsigned long timeout)
+{
+	struct dmirror_bounce bounce;
+	unsigned long start, end;
+	unsigned long size = cmd->npages << PAGE_SHIFT;
+	int ret;
+
+	start = cmd->addr;
+	end = start + size;
+	if (end < start)
+		return -EINVAL;
+
+	ret = dmirror_bounce_init(&bounce, start, size);
+	if (ret)
+		return ret;
+
+	while (1) {
+		mutex_lock(&dmirror->mutex);
+		ret = dmirror_do_read(dmirror, start, end, &bounce);
+		mutex_unlock(&dmirror->mutex);
+		if (ret != -ENOENT)
+			break;
+
+		start = cmd->addr + (bounce.cpages << PAGE_SHIFT);
+		ret = dmirror_fault_unlocked(dmirror, start, end, false, timeout);
+		if (ret)
+			break;
+		cmd->faults++;
+	}
+
+	if (ret == 0) {
+		if (copy_to_user(u64_to_user_ptr(cmd->ptr), bounce.ptr,
+				 bounce.size))
+			ret = -EFAULT;
+	}
+	cmd->cpages = bounce.cpages;
+	dmirror_bounce_fini(&bounce);
+	return ret;
+}
+
 static int dmirror_do_write(struct dmirror *dmirror, unsigned long start,
 			    unsigned long end, struct dmirror_bounce *bounce)
 {
@@ -1572,7 +1675,9 @@ static long dmirror_fops_unlocked_ioctl(struct file *filp,
 		dmirror->flags = cmd.npages;
 		ret = 0;
 		break;
-
+	case HMM_DMIRROR_READ_UNLOCKED:
+		ret = dmirror_read_unlocked(dmirror, &cmd, 0);
+		break;
 	default:
 		return -EINVAL;
 	}
diff --git a/lib/test_hmm_uapi.h b/lib/test_hmm_uapi.h
index f94c6d45733828..ea9b0ec404fb55 100644
--- a/lib/test_hmm_uapi.h
+++ b/lib/test_hmm_uapi.h
@@ -38,6 +38,7 @@ struct hmm_dmirror_cmd {
 #define HMM_DMIRROR_CHECK_EXCLUSIVE	_IOWR('H', 0x06, struct hmm_dmirror_cmd)
 #define HMM_DMIRROR_RELEASE		_IOWR('H', 0x07, struct hmm_dmirror_cmd)
 #define HMM_DMIRROR_FLAGS		_IOWR('H', 0x08, struct hmm_dmirror_cmd)
+#define HMM_DMIRROR_READ_UNLOCKED	_IOWR('H', 0x09, struct hmm_dmirror_cmd)
 
 #define HMM_DMIRROR_FLAG_FAIL_ALLOC	(1ULL << 0)
 
diff --git a/mm/hmm.c b/mm/hmm.c
index e5c1f4deed2447..fc2e1cd0cb22eb 100644
--- a/mm/hmm.c
+++ b/mm/hmm.c
@@ -32,9 +32,27 @@
 
 struct hmm_vma_walk {
 	struct hmm_range	*range;
+	bool			*locked;
 	unsigned long		last;
+	unsigned long		end;
+	unsigned int		required_fault;
 };
 
+/*
+ * Internal sentinel returned by walk callbacks when they need a page fault.
+ * The callback stores end/required_fault in hmm_vma_walk; the outer loop
+ * consumes the sentinel and never propagates it to the caller.
+ */
+#define HMM_FAULT_PENDING	-EAGAIN
+
+/*
+ * Internal sentinel returned by hmm_do_fault() when handle_mm_fault()
+ * completes a page fault with the mmap lock dropped. hmm_do_fault() sets
+ * *locked = false; the outer loop consumes the sentinel and never propagates
+ * it to the caller.
+ */
+#define HMM_FAULT_UNLOCKED	-ENOLCK
+
 enum {
 	HMM_NEED_FAULT = 1 << 0,
 	HMM_NEED_WRITE_FAULT = 1 << 1,
@@ -60,37 +78,25 @@ static int hmm_pfns_fill(unsigned long addr, unsigned long end,
 }
 
 /*
- * hmm_vma_fault() - fault in a range lacking valid pmd or pte(s)
- * @addr: range virtual start address (inclusive)
- * @end: range virtual end address (exclusive)
- * @required_fault: HMM_NEED_* flags
- * @walk: mm_walk structure
- * Return: -EBUSY after page fault, or page fault error
+ * hmm_record_fault() - record a range that needs to be faulted in
  *
- * This function will be called whenever pmd_none() or pte_none() returns true,
- * or whenever there is no page directory covering the virtual address range.
+ * Called by the walk callbacks when they discover that part of the range
+ * needs a page fault.  The callback records what to fault and returns
+ * HMM_FAULT_PENDING; the outer loop in hmm_range_fault_locked() drops
+ * back out of walk_page_range() and invokes handle_mm_fault() from a context
+ * where no page-table or hugetlb_vma_lock is held.
  */
-static int hmm_vma_fault(unsigned long addr, unsigned long end,
-			 unsigned int required_fault, struct mm_walk *walk)
+static int hmm_record_fault(unsigned long addr, unsigned long end,
+			    unsigned int required_fault,
+			    struct mm_walk *walk)
 {
 	struct hmm_vma_walk *hmm_vma_walk = walk->private;
-	struct vm_area_struct *vma = walk->vma;
-	unsigned int fault_flags = FAULT_FLAG_REMOTE;
 
 	WARN_ON_ONCE(!required_fault);
 	hmm_vma_walk->last = addr;
-
-	if (required_fault & HMM_NEED_WRITE_FAULT) {
-		if (!(vma->vm_flags & VM_WRITE))
-			return -EPERM;
-		fault_flags |= FAULT_FLAG_WRITE;
-	}
-
-	for (; addr < end; addr += PAGE_SIZE)
-		if (handle_mm_fault(vma, addr, fault_flags, NULL) &
-		    VM_FAULT_ERROR)
-			return -EFAULT;
-	return -EBUSY;
+	hmm_vma_walk->end = end;
+	hmm_vma_walk->required_fault = required_fault;
+	return HMM_FAULT_PENDING;
 }
 
 static unsigned int hmm_pte_need_fault(const struct hmm_vma_walk *hmm_vma_walk,
@@ -174,7 +180,7 @@ static int hmm_vma_walk_hole(unsigned long addr, unsigned long end,
 		return hmm_pfns_fill(addr, end, range, HMM_PFN_ERROR);
 	}
 	if (required_fault)
-		return hmm_vma_fault(addr, end, required_fault, walk);
+		return hmm_record_fault(addr, end, required_fault, walk);
 	return hmm_pfns_fill(addr, end, range, 0);
 }
 
@@ -209,7 +215,7 @@ static int hmm_vma_handle_pmd(struct mm_walk *walk, unsigned long addr,
 	required_fault =
 		hmm_range_need_fault(hmm_vma_walk, hmm_pfns, npages, cpu_flags);
 	if (required_fault)
-		return hmm_vma_fault(addr, end, required_fault, walk);
+		return hmm_record_fault(addr, end, required_fault, walk);
 
 	pfn = pmd_pfn(pmd) + ((addr & ~PMD_MASK) >> PAGE_SHIFT);
 	for (i = 0; addr < end; addr += PAGE_SIZE, i++, pfn++) {
@@ -328,7 +334,7 @@ static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr,
 fault:
 	pte_unmap(ptep);
 	/* Fault any virtual address we were asked to fault */
-	return hmm_vma_fault(addr, end, required_fault, walk);
+	return hmm_record_fault(addr, end, required_fault, walk);
 }
 
 #ifdef CONFIG_ARCH_HAS_PMD_SOFTLEAVES
@@ -371,7 +377,7 @@ static int hmm_vma_handle_absent_pmd(struct mm_walk *walk, unsigned long start,
 					      npages, 0);
 	if (required_fault) {
 		if (softleaf_is_device_private(entry))
-			return hmm_vma_fault(addr, end, required_fault, walk);
+			return hmm_record_fault(addr, end, required_fault, walk);
 		else
 			return -EFAULT;
 	}
@@ -517,7 +523,7 @@ static int hmm_vma_walk_pud(pud_t *pudp, unsigned long start, unsigned long end,
 						      npages, cpu_flags);
 		if (required_fault) {
 			spin_unlock(ptl);
-			return hmm_vma_fault(addr, end, required_fault, walk);
+			return hmm_record_fault(addr, end, required_fault, walk);
 		}
 
 		pfn = pud_pfn(pud) + ((addr & ~PUD_MASK) >> PAGE_SHIFT);
@@ -564,21 +570,8 @@ static int hmm_vma_walk_hugetlb_entry(pte_t *pte, unsigned long hmask,
 	required_fault =
 		hmm_pte_need_fault(hmm_vma_walk, pfn_req_flags, cpu_flags);
 	if (required_fault) {
-		int ret;
-
 		spin_unlock(ptl);
-		hugetlb_vma_unlock_read(vma);
-		/*
-		 * Avoid deadlock: drop the vma lock before calling
-		 * hmm_vma_fault(), which will itself potentially take and
-		 * drop the vma lock. This is also correct from a
-		 * protection point of view, because there is no further
-		 * use here of either pte or ptl after dropping the vma
-		 * lock.
-		 */
-		ret = hmm_vma_fault(addr, end, required_fault, walk);
-		hugetlb_vma_lock_read(vma);
-		return ret;
+		return hmm_record_fault(addr, end, required_fault, walk);
 	}
 
 	pfn = pte_pfn(entry) + ((start & ~hmask) >> PAGE_SHIFT);
@@ -637,29 +630,67 @@ static const struct mm_walk_ops hmm_walk_ops = {
 	.walk_lock	= PGWALK_RDLOCK,
 };
 
-/**
- * hmm_range_fault - try to fault some address in a virtual address range
- * @range:	argument structure
- *
- * Returns 0 on success or one of the following error codes:
+/*
+ * hmm_do_fault - fault in a range recorded by a walk callback
  *
- * -EINVAL:	Invalid arguments or mm or virtual address is in an invalid vma
- *		(e.g., device file vma).
- * -ENOMEM:	Out of memory.
- * -EPERM:	Invalid permission (e.g., asking for write and range is read
- *		only).
- * -EBUSY:	The range has been invalidated and the caller needs to wait for
- *		the invalidation to finish.
- * -EFAULT:     A page was requested to be valid and could not be made valid
- *              ie it has no backing VMA or it is illegal to access
+ * Called from the outer loop in hmm_range_fault_locked() after a callback
+ * returned HMM_FAULT_PENDING.  At this point we hold only mmap_lock;
+ * the page-table spinlock and any hugetlb_vma_lock acquired by the walk
+ * framework have already been released by the unwind.
  *
- * This is similar to get_user_pages(), except that it can read the page tables
- * without mutating them (ie causing faults).
+ * Returns -EBUSY on success (all pages faulted, caller should re-walk).
+ * Returns a negative errno on failure.
  */
-int hmm_range_fault(struct hmm_range *range)
+static int hmm_do_fault(struct mm_struct *mm,
+			struct hmm_vma_walk *hmm_vma_walk)
+{
+	unsigned long addr = hmm_vma_walk->last;
+	unsigned long end = hmm_vma_walk->end;
+	unsigned int required_fault = hmm_vma_walk->required_fault;
+	unsigned int fault_flags = FAULT_FLAG_REMOTE;
+	struct vm_area_struct *vma;
+
+	if (hmm_vma_walk->locked)
+		fault_flags |= FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;
+
+	vma = vma_lookup(mm, addr);
+	if (!vma)
+		return -EFAULT;
+
+	if (required_fault & HMM_NEED_WRITE_FAULT) {
+		if (!(vma->vm_flags & VM_WRITE))
+			return -EPERM;
+		fault_flags |= FAULT_FLAG_WRITE;
+	}
+
+	for (; addr < end; addr += PAGE_SIZE) {
+		vm_fault_t ret;
+
+		ret = handle_mm_fault(vma, addr, fault_flags, NULL);
+
+		if (ret & (VM_FAULT_COMPLETED | VM_FAULT_RETRY)) {
+			*hmm_vma_walk->locked = false;
+			return HMM_FAULT_UNLOCKED;
+		}
+
+		if (ret & VM_FAULT_ERROR) {
+			int err = vm_fault_to_errno(ret, 0);
+
+			if (WARN_ON(!err))
+				err = -EINVAL;
+
+			return err;
+		}
+	}
+
+	return -EBUSY;
+}
+
+static int hmm_range_fault_locked(struct hmm_range *range, bool *locked)
 {
 	struct hmm_vma_walk hmm_vma_walk = {
 		.range = range,
+		.locked = locked,
 		.last = range->start,
 	};
 	struct mm_struct *mm = range->notifier->mm;
@@ -674,6 +705,22 @@ int hmm_range_fault(struct hmm_range *range)
 			return -EBUSY;
 		ret = walk_page_range(mm, hmm_vma_walk.last, range->end,
 				      &hmm_walk_ops, &hmm_vma_walk);
+		/*
+		 * When HMM_FAULT_PENDING is returned a walk callback
+		 * recorded a range that needs handle_mm_fault();
+		 * hmm_do_fault() runs the fault outside walk_page_range()
+		 * (so no page-table or hugetlb_vma_lock is held) and
+		 * returns -EBUSY so the loop re-walks and picks up the
+		 * now-present entries.
+		 */
+		if (ret == HMM_FAULT_PENDING) {
+			ret = hmm_do_fault(mm, &hmm_vma_walk);
+			if (ret == HMM_FAULT_UNLOCKED) {
+				if (fatal_signal_pending(current))
+					return -EINTR;
+				return -EBUSY;
+			}
+		}
 		/*
 		 * When -EBUSY is returned the loop restarts with
 		 * hmm_vma_walk.last set to an address that has not been stored
@@ -683,8 +730,100 @@ int hmm_range_fault(struct hmm_range *range)
 	} while (ret == -EBUSY);
 	return ret;
 }
+
+/**
+ * hmm_range_fault - try to fault some address in a virtual address range
+ * @range:	argument structure
+ *
+ * Returns 0 on success or one of the following error codes:
+ *
+ * -EINVAL:	Invalid arguments or mm or virtual address is in an invalid vma
+ *		(e.g., device file vma).
+ * -ENOMEM:	Out of memory.
+ * -EPERM:	Invalid permission (e.g., asking for write and range is read
+ *		only).
+ * -EBUSY:	The range has been invalidated and the caller needs to wait for
+ *		the invalidation to finish.
+ * -EFAULT:     A page was requested to be valid and could not be made valid
+ *              ie it has no backing VMA or it is illegal to access
+ *
+ * This is similar to get_user_pages(), except that it can read the page tables
+ * without mutating them (ie causing faults).
+ *
+ * The mmap lock must be held by the caller and will remain held on return.
+ * New users should prefer hmm_range_fault_unlocked_timeout() unless they
+ * specifically need to keep the mmap lock held across the call. This helper
+ * cannot support VMAs whose fault handlers need to drop the mmap lock.
+ */
+int hmm_range_fault(struct hmm_range *range)
+{
+	return hmm_range_fault_locked(range, NULL);
+}
 EXPORT_SYMBOL(hmm_range_fault);
 
+/**
+ * hmm_range_fault_unlocked_timeout - fault in a range with a retry timeout
+ * @range:	argument structure
+ * @timeout:	timeout in jiffies for internal -EBUSY retries, or 0 to retry
+ *		indefinitely
+ *
+ * The caller must not hold the mmap lock. The function takes the mmap read
+ * lock internally and allows handle_mm_fault() to drop it during faults. If
+ * the mmap lock is dropped or the range is invalidated, the function refreshes
+ * range->notifier_seq and restarts the walk internally.
+ *
+ * Passing 0 for @timeout retries indefinitely. A non-zero @timeout is a caller
+ * policy limit for repeated mmu-notifier invalidation retries. HMM does not
+ * interrupt page fault handling when the timeout expires, but returns -EBUSY
+ * if the retry budget is exhausted before a stable range is obtained.
+ *
+ * Returns 0 on success or one of the error codes documented for
+ * hmm_range_fault(). -EINTR is returned if mmap_lock acquisition is
+ * interrupted or a fatal signal is pending during retry handling.
+ */
+int hmm_range_fault_unlocked_timeout(struct hmm_range *range,
+				     unsigned long timeout)
+{
+	struct mm_struct *mm = range->notifier->mm;
+	unsigned long deadline = 0;
+	bool locked = false;
+	int ret;
+
+	do {
+		if (fatal_signal_pending(current))
+			return -EINTR;
+
+		if (timeout) {
+			/*
+			 * If the previous fault dropped mmap_lock, then the fault
+			 * handler made progress. Restart the retry timeout in that
+			 * case, but keep the existing deadline for ordinary -EBUSY
+			 * retries.
+			 */
+			if (!locked)
+				deadline = jiffies + timeout;
+
+			if (time_after(jiffies, deadline))
+				return -EBUSY;
+		}
+
+		range->notifier_seq =
+			mmu_interval_read_begin(range->notifier);
+
+		ret = mmap_read_lock_killable(mm);
+		if (ret)
+			return ret;
+
+		locked = true;
+		ret = hmm_range_fault_locked(range, &locked);
+		if (locked)
+			mmap_read_unlock(mm);
+	} while (ret == -EBUSY);
+
+	return ret;
+}
+EXPORT_SYMBOL(hmm_range_fault_unlocked_timeout);
+
 /**
  * hmm_dma_map_alloc - Allocate HMM map structure
  * @dev: device to allocate structure for
diff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c
index 6fccbdab02ee81..5acb728666f8c1 100644
--- a/tools/testing/selftests/mm/hmm-tests.c
+++ b/tools/testing/selftests/mm/hmm-tests.c
@@ -29,6 +29,10 @@
 #include <sys/mman.h>
 #include <sys/ioctl.h>
 #include <sys/time.h>
+#include <sys/syscall.h>
+#include <sys/eventfd.h>
+#include <linux/userfaultfd.h>
+#include <poll.h>
 
 /*
  * This is a private UAPI to the kernel test module so it isn't exported
@@ -2952,4 +2956,150 @@ TEST_F_TIMEOUT(hmm, benchmark_thp_migration, 120)
 					&thp_results, &regular_results);
 	}
 }
+/*
+ * Test that HMM can fault in pages backed by userfaultfd using the
+ * hmm_range_fault_unlocked_timeout() path with no timeout. This exercises
+ * the lock-drop retry logic in the HMM framework.
+ */
+struct uffd_thread_args {
+	int uffd;
+	int stop_fd;
+	void *page_buffer;
+	unsigned long page_size;
+};
+
+static void *uffd_handler_thread(void *arg)
+{
+	struct uffd_thread_args *args = arg;
+	struct uffd_msg msg;
+	struct uffdio_copy copy;
+	struct pollfd pollfd[2];
+	int ret;
+
+	pollfd[0].fd = args->uffd;
+	pollfd[0].events = POLLIN;
+	pollfd[1].fd = args->stop_fd;
+	pollfd[1].events = POLLIN;
+
+	while (1) {
+		ret = poll(pollfd, 2, -1);
+		if (ret <= 0)
+			break;
+		if (pollfd[1].revents)
+			break;
+		if (!(pollfd[0].revents & POLLIN))
+			break;
+
+		ret = read(args->uffd, &msg, sizeof(msg));
+		if (ret != sizeof(msg))
+			break;
+
+		if (msg.event != UFFD_EVENT_PAGEFAULT)
+			break;
+
+		/* Fill the page with a known pattern */
+		memset(args->page_buffer, 0xAB, args->page_size);
+
+		copy.dst = msg.arg.pagefault.address & ~(args->page_size - 1);
+		copy.src = (unsigned long)args->page_buffer;
+		copy.len = args->page_size;
+		copy.mode = 0;
+		copy.copy = 0;
+
+		ret = ioctl(args->uffd, UFFDIO_COPY, &copy);
+		if (ret < 0)
+			break;
+	}
+
+	return NULL;
+}
+
+TEST_F(hmm, userfaultfd_read)
+{
+	struct hmm_buffer *buffer;
+	struct uffd_thread_args uffd_args;
+	unsigned long npages;
+	unsigned long size;
+	unsigned long i;
+	unsigned char *ptr;
+	pthread_t thread;
+	int uffd;
+	int stop_fd;
+	int ret;
+	struct uffdio_api api;
+	struct uffdio_register reg;
+	uint64_t stop = 1;
+	ssize_t nwrite;
+
+	npages = 4;
+	size = npages << self->page_shift;
+
+	/* Create userfaultfd */
+	uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
+	if (uffd < 0)
+		SKIP(return, "userfaultfd not available");
+
+	api.api = UFFD_API;
+	api.features = 0;
+	ret = ioctl(uffd, UFFDIO_API, &api);
+	ASSERT_EQ(ret, 0);
+
+	buffer = malloc(sizeof(*buffer));
+	ASSERT_NE(buffer, NULL);
+
+	buffer->fd = -1;
+	buffer->size = size;
+	buffer->mirror = malloc(size);
+	ASSERT_NE(buffer->mirror, NULL);
+
+	/* Create anonymous mapping */
+	buffer->ptr = mmap(NULL, size,
+			   PROT_READ | PROT_WRITE,
+			   MAP_PRIVATE | MAP_ANONYMOUS,
+			   -1, 0);
+	ASSERT_NE(buffer->ptr, MAP_FAILED);
+
+	/* Register the region with userfaultfd */
+	reg.range.start = (unsigned long)buffer->ptr;
+	reg.range.len = size;
+	reg.mode = UFFDIO_REGISTER_MODE_MISSING;
+	ret = ioctl(uffd, UFFDIO_REGISTER, &reg);
+	ASSERT_EQ(ret, 0);
+
+	/* Set up the handler thread */
+	uffd_args.uffd = uffd;
+	stop_fd = eventfd(0, EFD_CLOEXEC);
+	ASSERT_GE(stop_fd, 0);
+	uffd_args.stop_fd = stop_fd;
+	uffd_args.page_buffer = malloc(self->page_size);
+	ASSERT_NE(uffd_args.page_buffer, NULL);
+	uffd_args.page_size = self->page_size;
+
+	ret = pthread_create(&thread, NULL, uffd_handler_thread, &uffd_args);
+	ASSERT_EQ(ret, 0);
+
+	/*
+	 * Use the unlocked read path which allows the mmap lock to be
+	 * dropped during the fault, enabling userfaultfd resolution.
+	 */
+	ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ_UNLOCKED,
+			      buffer, npages);
+	ASSERT_EQ(ret, 0);
+	ASSERT_EQ(buffer->cpages, npages);
+
+	/* Verify the device read the data filled by the uffd handler */
+	ptr = buffer->mirror;
+	for (i = 0; i < size; ++i)
+		ASSERT_EQ(ptr[i], (unsigned char)0xAB);
+
+	nwrite = write(stop_fd, &stop, sizeof(stop));
+	ASSERT_EQ(nwrite, sizeof(stop));
+	pthread_join(thread, NULL);
+	close(stop_fd);
+	free(uffd_args.page_buffer);
+	close(uffd);
+	hmm_buffer_free(buffer);
+}
+
+
 TEST_HARNESS_MAIN

3/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15667
							output: 26
							thoughts: 376
						

Thoughts:
**My Analysis of the HMM Patch Series**

Okay, so I'm looking at this patch series, and it's making some pretty fundamental changes to the Host Memory Management (HMM) code. It looks like the main thrust is to introduce this new function, `hmm_range_fault_unlocked_timeout`, alongside a locked version of the existing `hmm_range_fault`. I see that the patch touches a bunch of files: `mm/hmm.c`, `include/linux/hmm.h`, the documentation, and then a slew of driver files – AMD, DRM, Nouveau, Hyper-V, Infiniband, and even some test code. Clearly, this is a functional change; it's getting its hands dirty in HMM's core logic and directly impacting how these drivers interact with memory. The addition of `hmm_range_fault_unlocked_timeout`, in particular, indicates a targeted update for driver use.

My next move is to zero in on specific kernel functions for fuzzing. I'm thinking, based on this change, I'll need to focus on: `hmm_range_fault_unlocked_timeout`, `hmm_range_fault`, `hmm_do_fault`, `hmm_range_fault_locked`, and `hmm_record_fault`. These are the key entry points and the related internal functions.

Now, I need to understand the configuration landscape. What enables this HMM functionality? I'll need to check the kernel configuration to understand when `hmm_range_fault` is even compiled. I'll search for `hmm_range_fault` within `mm/hmm.c` and `mm/Kconfig`, but I need to go further. Hmm, I know `mm/hmm.c` is compiled conditionally, depending on some config variables. Let me quickly check the `mm/Makefile` to understand the dependencies and the configurations that control its inclusion.



4/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "hmm.o",
  "PathPrefix": "mm/Makefile"
}

Results:
{
  "Output": "mm/Makefile=138=obj-$(CONFIG_ZONE_DEVICE) += memremap.o\nmm/Makefile:139:obj-$(CONFIG_HMM_MIRROR) += hmm.o\nmm/Makefile-140-obj-$(CONFIG_MEMFD_CREATE) += memfd.o\n"
}

5/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16559
							output: 28
							thoughts: 38
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Hmm, `CONFIG_HMM_MIRROR` Needed?**

Right, so it looks like the current task necessitates the use of `CONFIG_HMM_MIRROR`. A good starting point, as always, is to check how this feature is actually *used* in the codebase. Let's dig into `lib/test_hmm.c`. That's usually where the example configurations and test setups reside. I'm expecting to find either a clear example of how `CONFIG_HMM_MIRROR` is defined, or at the very least, some indications of dependent configurations or conditional compilation based on its presence. This file will probably hold the keys to understanding how this feature has been validated and the assumptions about its dependencies.



6/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "test_hmm.o",
  "PathPrefix": "lib/Makefile"
}

Results:
{
  "Output": "lib/Makefile=102=obj-$(CONFIG_TEST_LOCKUP) += test_lockup.o\nlib/Makefile:103:obj-$(CONFIG_TEST_HMM) += test_hmm.o\nlib/Makefile-104-obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o\n"
}

7/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16892
							output: 30
							thoughts: 99
						

Thoughts:
**Examining the HMM Configuration Landscape**

Okay, so I see we need `CONFIG_TEST_HMM`. My first instinct is to check the relevant driver code, so I'll quickly look at the configurations used in `drivers/gpu/drm/nouveau/nouveau_svm.c` and `drivers/gpu/drm/drm_gpusvm.c`. However, I'm thinking the focus here should be core HMM testing, and `TEST_HMM` seems like it's specifically tailored for that purpose. Let's start with `TEST_HMM` as the more direct path. To make sure, though, let me double-check `mm/Kconfig` for the `HMM_MIRROR` configuration; it's always good to verify dependencies and related options to get the full picture.



8/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "config HMM_MIRROR",
  "PathPrefix": "mm/Kconfig"
}

Results:
{
  "Output": "mm/Kconfig=1228=config ZONE_DEVICE\n--\nmm/Kconfig-1249-#\nmm/Kconfig:1250:config HMM_MIRROR\nmm/Kconfig-1251-\tbool\n"
}

9/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17268
							output: 31
							thoughts: 20
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Decoding the Bool**

Alright, let me break this down. My initial thought is that `HMM_MIRROR` is a Boolean variable, likely controlling some sort of mirroring or reflection behavior within my Hidden Markov Model (HMM) implementation. The fact that it's probably selected by `TEST_HMM` suggests this is related to a testing configuration. I’m guessing, based on prior experience, that this test suite probably toggles various model parameters and configurations to validate the functionality. Therefore, `HMM_MIRROR` is likely a configuration to test a model variation. This setup aims to ensure that the HMM behaves as expected under a reflected or mirrored data space scenario. I'd need more context on the HMM structure and testing framework to pin down the *exact* purpose, but the core idea is clear: it's a test flag, and mirroring is the mechanism.



10/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "config TEST_HMM",
  "PathPrefix": "lib/Kconfig.debug"
}

Results:
{
  "Output": "lib/Kconfig.debug=3318=config TEST_MEMINIT\n--\nlib/Kconfig.debug-3325-\nlib/Kconfig.debug:3326:config TEST_HMM\nlib/Kconfig.debug-3327-\ttristate \"Test HMM (Heterogeneous Memory Management)\"\n"
}

11/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17619
							output: 30
							thoughts: 13
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:

**Analyzing the TEST_HMM Selection**

Right, let's dive in. I need to quickly grasp what `TEST_HMM` has picked. The name itself is a clue; it's likely a specifically designed hidden Markov model for some purpose. The critical question now is, what are the characteristics of that particular model? Is it a basic implementation, or does it incorporate more advanced techniques? What features were considered when selecting `TEST_HMM` in this situation? I'm already formulating questions in my mind, such as "What data was used to train it?", "What are the key state transitions?" and "What objective was this HMM chosen to fulfill?" The goal is to rapidly determine if `TEST_HMM` makes sense, or if I need to dig deeper for its rationale. It's essentially a sanity check before I go any further.



12/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "select.*HMM",
  "PathPrefix": "lib/Kconfig.debug"
}

Results:
{
  "Output": "lib/Kconfig.debug=3326=config TEST_HMM\n--\nlib/Kconfig.debug-3329-\tdepends on DEVICE_PRIVATE\nlib/Kconfig.debug:3330:\tselect HMM_MIRROR\nlib/Kconfig.debug-3331-\tselect MMU_NOTIFIER\n"
}

13/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17954
							output: 37
							thoughts: 106
						

Thoughts:
**Examining the HMM Configuration**

Okay, so `TEST_HMM` is pointing me towards `HMM_MIRROR`. That's the primary selection, noted. Now, let's just make absolutely sure there aren't *any* other potential HMM-related configurations in play. I need to be thorough. Let's see... is `CONFIG_DRM_NOUVEAU_SVM` a factor here? Or perhaps `CONFIG_DRM_GPUSVM`? Those seem relevant to memory management within DRM, so checking them is a must. And what about `CONFIG_INFINIBAND_ON_DEMAND_PAGING`? HMM touches memory, so this is worth a look. Might `CONFIG_MSHV` or `CONFIG_AMDXDNA` have any influence? HMM and virtualization, and then HMM and AMD's DNA architecture.

Alright, I should specifically check that `drivers/accel/amdxdna/Kconfig` file. I'm going to want to see the relationship here, if there is one. Gotta be absolutely sure I haven't missed a dependency or interaction.



14/2 2026/07/16 00:09 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config DRM_AMDXDNA",
  "PathPrefix": "drivers/accel/amdxdna/Kconfig"
}

Results:
{
  "error": "no matches"
}

15/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18354
							output: 37
							thoughts: 30
						

Thoughts:
Here's my thought process, summarized as you requested:

**Navigating the Codebase: Identifying the Relevant Configuration**

Okay, hold on. I've got a modification showing up in `drivers/accel/amdxdna/aie2_ctx.c`. That's a key file for the AMD DDNA acceleration context, so I need to understand what's changed. The first step, as always, is to figure out the associated configuration that governs this part of the driver. I need to track down the configuration that controls how this portion of the code is built and enabled. Time to go hunting for the relevant Kconfig options. I expect there's probably a defconfig or some other build-time flag influencing how that particular AIE2 context is set up. Let's see if the config is in the parent directory, or in the driver's own directory.



16/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "aie2_ctx.o",
  "PathPrefix": "drivers/accel/amdxdna/Makefile"
}

Results:
{
  "Output": "drivers/accel/amdxdna/Makefile=3=amdxdna-y := \\\n--\ndrivers/accel/amdxdna/Makefile-6-\taie_smu.o \\\ndrivers/accel/amdxdna/Makefile:7:\taie2_ctx.o \\\ndrivers/accel/amdxdna/Makefile-8-\taie2_error.o \\\n"
}

17/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18711
							output: 35
							thoughts: 11
						

Thoughts:
Here's my take on the thought process, as if it were my own:

**Initial Assessment: Module Identification**

Okay, so I'm looking at something, and the immediate question is, "What's the name of this particular module or component I'm dealing with?" The critical first step in understanding or troubleshooting any system is pinpointing its specific identity. And that module name, straight up, is `amdxdna`. That's the key piece of information to start with. With that name I can look into relevant documentation, related code and other implementations that may be relevant. Now I know precisely what I'm dealing with, and I can move forward accordingly.



18/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "amdxdna-y",
  "PathPrefix": "drivers/accel/amdxdna/Makefile"
}

Results:
{
  "Output": "drivers/accel/amdxdna/Makefile-2-\ndrivers/accel/amdxdna/Makefile:3:amdxdna-y := \\\ndrivers/accel/amdxdna/Makefile-4-\taie.o \\\n"
}

19/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18966
							output: 35
							thoughts: 12
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Hunting Down the AMD XDNA Configuration**

Right, let's get down to brass tacks. I'm thinking, "Where's the config for `amdxdna`?" This is a key piece of the puzzle. I need to understand how this specific hardware is being initialized, managed, and interacted with. Knowing the configuration is fundamental to just about everything. It dictates the available resources, the clock settings, power profiles, interrupt mappings – the whole shebang. Without it, I'm basically flying blind. Is it a dedicated file? Part of a broader system configuration? Defined in firmware? Maybe it's dynamically generated based on hardware detection? The hunt begins. The nuances here, as we both know, are going to be key to any deep dive. Time to start digging!



20/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "amdxdna.o",
  "PathPrefix": "drivers/accel/amdxdna/Makefile"
}

Results:
{
  "Output": "drivers/accel/amdxdna/Makefile=33=amdxdna-$(CONFIG_DEBUG_FS) += amdxdna_debugfs.o\ndrivers/accel/amdxdna/Makefile:34:obj-$(CONFIG_DRM_ACCEL_AMDXDNA) = amdxdna.o\n"
}

21/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19279
							output: 34
							thoughts: 34
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing the AMD XDNA DRM Acceleration Configuration**

Right, so `CONFIG_DRM_ACCEL_AMDXDNA`... my immediate thought is to understand the scope and implications of enabling this. I need to get a handle on the driver support. The quick path to that is, without a doubt, diving into `drivers/gpu/drm/drm_gpusvm.c`. That file is likely where the core memory management and, therefore, potentially the acceleration hooks for AMD's DNA-related acceleration would be defined. It's almost certain this is where I'll find the specific implementations, the data structures, and the potential interactions with the AMD GPU Virtual Memory (GUSVM) subsystem if there is any. My goal is to determine what aspects of the hardware this configuration variable unlocks and what performance implications are associated with using it. This will help determine what testing is required.



22/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "drm_gpusvm.o",
  "PathPrefix": "drivers/gpu/drm/Makefile"
}

Results:
{
  "Output": "drivers/gpu/drm/Makefile=109=drm_gpusvm_helper-y := \\\ndrivers/gpu/drm/Makefile:110:\tdrm_gpusvm.o\ndrivers/gpu/drm/Makefile-111-drm_gpusvm_helper-$(CONFIG_ZONE_DEVICE) += \\\n"
}

23/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19637
							output: 34
							thoughts: 13
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating the Activation of `drm_gpusvm_helper`**

Alright, so the question is, *what* actually enables the `drm_gpusvm_helper` functionality within the kernel? I need to understand the configuration knobs that are tied to this. Is it a core option buried deep within the DRM subsystem, or is it more closely linked to specific GPU drivers, perhaps through their associated memory management options? I'll probably need to delve into the `Kconfig` files for the DRM (Direct Rendering Manager) and any relevant GPU drivers. I bet it is some kind of memory management or shared virtual memory option.

I'll be looking for dependencies, default values, and any relationships to other features, specifically anything mentioning SVM, or even more broadly, shared memory, and how that interacts with the user space. I need to figure out the granularity here. Is this tied to the enabling of IOMMU or something? Is it always on? If not, what circumstances require it to be enabled? If there is a way to disable it, I will be looking to see if there is any reason to do so.

I'll be curious about the implications of the configuration. Does it require a specific kernel version, and does it come with any security considerations? I need to know how it impacts the overall system performance when it is enabled or disabled. This will guide my understanding of the optimal use cases.



24/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "drm_gpusvm_helper",
  "PathPrefix": "drivers/gpu/drm/Makefile"
}

Results:
{
  "Output": "drivers/gpu/drm/Makefile=107=obj-$(CONFIG_DRM_GPUVM) += drm_gpuvm.o\ndrivers/gpu/drm/Makefile-108-\ndrivers/gpu/drm/Makefile:109:drm_gpusvm_helper-y := \\\ndrivers/gpu/drm/Makefile-110-\tdrm_gpusvm.o\ndrivers/gpu/drm/Makefile:111:drm_gpusvm_helper-$(CONFIG_ZONE_DEVICE) += \\\ndrivers/gpu/drm/Makefile-112-\tdrm_pagemap.o\\\n--\ndrivers/gpu/drm/Makefile-114-\ndrivers/gpu/drm/Makefile:115:obj-$(CONFIG_DRM_GPUSVM) += drm_gpusvm_helper.o\ndrivers/gpu/drm/Makefile-116-\n"
}

25/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20216
							output: 36
							thoughts: 33
						

Thoughts:
Okay, here's my take on those thoughts, framed from my perspective:

**Diving into GPU Virtualization**

Alright, the discussion has landed on `CONFIG_DRM_GPUSVM`. Right away, my mind goes to the likely implementation. I immediately think, *that means I should check the Nouveau driver, specifically `drivers/gpu/drm/nouveau/nouveau_svm.c`.* I'm assuming that's the place where the core GPU System Virtual Machine code, if any exists, would reside. I need to understand how they're handling the shared memory and virtualization components for the Nouveau drivers. Let's see what they are doing.



26/2 2026/07/16 00:09 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "nouveau_svm.o",
  "PathPrefix": "drivers/gpu/drm/nouveau/Makefile"
}

Results:
{
  "error": "no matches"
}

27/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20441
							output: 34
							thoughts: 20
						

Thoughts:
Here's my thought process, as if I were considering this myself:

**Investigating a Modified File**

Okay, hold on a second. I see that `nouveau_svm.c` has been touched. That's a key file in the Nouveau driver, specifically dealing with System Virtual Memory (SVM), which is critical for things like GPU memory management and sharing. My immediate reaction is to track down its configuration. Changes to that file, especially related to memory handling, can have pretty far-reaching implications. It could be a simple bug fix, a performance optimization, or, potentially, a change in how the driver interacts with the hardware, maybe even a new feature. I need to understand the context of this modification before I make any assumptions. First step: diving into its configuration!



28/2 2026/07/16 00:09 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "nouveau_svm",
  "PathPrefix": "drivers/gpu/drm/nouveau/Makefile"
}

Results:
{
  "error": "no matches"
}

29/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20673
							output: 35
							thoughts: 21
						

Thoughts:
Here's my thought process, as if I were considering this issue:

**Delving into the Nouveau Kbuild Structure**

Okay, I'm trying to track down something related to the Nouveau driver. Where would the build configurations be? My first thought is to dive into the core directories. Let's start with `drivers/gpu/drm/nouveau/`. That seems like the logical place, but I want to be certain that I'm on the right track. And if I am, where would the compilation and build configurations be managed? Maybe there's a `Kbuild` file in there? That's typically where these things are defined, especially in the kernel. Let's check `drivers/gpu/drm/nouveau/Kbuild`. I need to make sure I'm looking in the right spot, so let's start there.



30/2 2026/07/16 00:09 tool grepper
0m Args:
{
  "Expression": "nouveau_svm",
  "PathPrefix": "drivers/gpu/drm/nouveau/Kbuild"
}

Results:
{
  "Output": "drivers/gpu/drm/nouveau/Kbuild=40=nouveau-y += nouveau_gem.o\ndrivers/gpu/drm/nouveau/Kbuild:41:nouveau-$(CONFIG_DRM_NOUVEAU_SVM) += nouveau_svm.o\ndrivers/gpu/drm/nouveau/Kbuild-42-nouveau-$(CONFIG_DRM_NOUVEAU_SVM) += nouveau_dmem.o\n"
}

31/2 2026/07/16 00:09 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21025
							output: 32
							thoughts: 32
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Kernel Configuration and Driver Examination**

Right, so I'm seeing a reference to `CONFIG_DRM_NOUVEAU_SVM`. My immediate thought is, "What's the context here?" I need to understand how this flag relates to the overall system configuration, specifically concerning the Nouveau driver and Shared Virtual Memory. Where does this variable fit in?

My next step, and this is almost automatic at this point, is to dive into `drivers/hv/mshv_regions.c`. I'm going to want to understand what's happening with Shared Virtual Memory (SVM), and how this driver, specifically designed for Hyper-V environments, might be interacting with or being affected by the Nouveau driver. This is likely an area where I might see the use of `CONFIG_DRM_NOUVEAU_SVM` and the use of the Nouveau driver. Looking for dependencies or conditional compilations is my primary goal. It's time to dig in and understand the potential implications of this configuration setting within this specific driver.



32/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "mshv_regions.o",
  "PathPrefix": "drivers/hv/Makefile"
}

Results:
{
  "Output": "drivers/hv/Makefile=16=mshv_root-y := mshv_root_main.o mshv_synic.o mshv_eventfd.o mshv_irq.o \\\ndrivers/hv/Makefile:17:\t       mshv_root_hv_call.o mshv_portid_table.o mshv_regions.o\ndrivers/hv/Makefile-18-mshv_root-$(CONFIG_DEBUG_FS) += mshv_debugfs.o\n"
}

33/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21466
							output: 30
							thoughts: 11
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:

**Investigating the `mshv_root` Configuration**

Alright, so the question immediately that pops into my head is, "what *specifically* enables the `mshv_root` feature?" We know it exists, we're familiar with its purpose in general, but the precise mechanism of its activation isn't always immediately obvious. Is it a compile-time flag? A kernel module parameter? Perhaps a specific entry within a particular configuration file, or set of files, within the system?

My experience tells me this isn't a simple on/off switch, but probably something more nuanced. I need to understand the relationship between different system components. Maybe it’s dependent on certain hardware capabilities, or another related feature that must be enabled. I know it isn't likely something that just "is".

I'm thinking I need to trace the code path related to `mshv_root`, or look at the build configuration to figure out when and how the root of this structure is created. Where would the most useful documentation be located? Perhaps the kernel documentation is a good starting point, though it might be a bit verbose.

I'm assuming the specific context is the key, in order to get to the root of the problem.



34/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "mshv_root",
  "PathPrefix": "drivers/hv/Makefile"
}

Results:
{
  "Output": "drivers/hv/Makefile=4=obj-$(CONFIG_HYPERV_BALLOON)\t+= hv_balloon.o\ndrivers/hv/Makefile:5:obj-$(CONFIG_MSHV_ROOT)\t\t+= mshv_root.o\ndrivers/hv/Makefile-6-obj-$(CONFIG_MSHV_VTL)          += mshv_vtl.o\n--\ndrivers/hv/Makefile=15=hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_utils_transport.o\ndrivers/hv/Makefile:16:mshv_root-y := mshv_root_main.o mshv_synic.o mshv_eventfd.o mshv_irq.o \\\ndrivers/hv/Makefile:17:\t       mshv_root_hv_call.o mshv_portid_table.o mshv_regions.o\ndrivers/hv/Makefile:18:mshv_root-$(CONFIG_DEBUG_FS) += mshv_debugfs.o\ndrivers/hv/Makefile:19:mshv_root-$(CONFIG_TRACEPOINTS) += mshv_trace.o\ndrivers/hv/Makefile-20-mshv_vtl-y := mshv_vtl_main.o\n"
}

35/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22115
							output: 37
							thoughts: 32
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating a Hypervisor-Related Infiniband Configuration**

Alright, that `CONFIG_MSHV_ROOT` flag... Hmm, that's immediately suggesting a Microsoft Hyper-V environment, specifically dealing with root-level access. I need to understand how that impacts Infiniband and potentially Open Data Path (ODP) operations.

My first thought is to dig into the Infiniband code, and I know where to start. Let's look at `drivers/infiniband/core/umem_odp.c`. That file is a likely candidate for handling memory management and ODP interaction, which would be central to a hypervisor environment like Hyper-V. I need to see how they're handling User Memory (UMEM) with the flag set to see how root access would affect the configuration. I want to see how that flag changes the handling of memory and access control within the context of Open Data Path, especially in relation to a Hyper-V root instance.



36/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "umem_odp.o",
  "PathPrefix": "drivers/infiniband/core/Makefile"
}

Results:
{
  "Output": "drivers/infiniband/core/Makefile=21=ib_core-$(CONFIG_INFINIBAND_USER_MEM) += umem.o umem_dmabuf.o\ndrivers/infiniband/core/Makefile:22:ib_core-$(CONFIG_INFINIBAND_ON_DEMAND_PAGING) += umem_odp.o\ndrivers/infiniband/core/Makefile-23-ib_core-$(CONFIG_INFINIBAND_USER_ACCESS_CORE) += ib_core_uverbs.o\n"
}

37/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22550
							output: 28
							thoughts: 30
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating `CONFIG_INFINIBAND_ON_DEMAND_PAGING`**

Alright, let's see... the system is pointing me towards `CONFIG_INFINIBAND_ON_DEMAND_PAGING`. I need to understand the implications of this setting. My immediate thought is, "Where's the best place to get a good handle on how this is working?" The logical starting point for understanding an InfiniBand-related feature like on-demand paging is always the relevant testing code. So, let's zero in on that. Let me take a look at `lib/test_hmm.c`. Hopefully, it'll provide some clear examples of how this on-demand paging is tested and used within the kernel's InfiniBand stack. That should give me the context I need to really understand the rationale and implications of this configuration option.



38/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "test_hmm.o",
  "PathPrefix": "lib/Makefile"
}

Results:
{
  "Output": "lib/Makefile=102=obj-$(CONFIG_TEST_LOCKUP) += test_lockup.o\nlib/Makefile:103:obj-$(CONFIG_TEST_HMM) += test_hmm.o\nlib/Makefile-104-obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o\n"
}

39/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22903
							output: 30
							thoughts: 161
						

Thoughts:
**My Configuration Testing Considerations**

Okay, so I'm looking at `CONFIG_TEST_HMM`. My immediate thought is, what are the related configurations I need to enable for effective testing? I need to build a comprehensive list.

Right, so besides `TEST_HMM` itself, I also know that `HMM_MIRROR` is intrinsically linked. Even though `HMM_MIRROR` is selected by `TEST_HMM`, it's core to the functionality, so it needs to be explicitly listed as a key dependency. Let's make sure it's clear.

Then, I'm noting the additional configurations required: `DRM_ACCEL_AMDXDNA`, `DRM_GPUSVM`, `DRM_NOUVEAU_SVM`, `MSHV_ROOT`, and `INFINIBAND_ON_DEMAND_PAGING`. I'll need to keep track of these to ensure all relevant features are enabled during my testing.

Now, a key question: can I get away with just enabling `TEST_HMM` to validate the core logic? Yes, after reviewing the setup, I'm confident that enabling `TEST_HMM` alone is sufficient for the initial core logic testing. I'm satisfied.

Finally, I'm just quickly confirming that `HMM_MIRROR` is selectable.



40/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "config HMM_MIRROR",
  "PathPrefix": "mm/Kconfig"
}

Results:
{
  "Output": "mm/Kconfig=1228=config ZONE_DEVICE\n--\nmm/Kconfig-1249-#\nmm/Kconfig:1250:config HMM_MIRROR\nmm/Kconfig-1251-\tbool\n"
}

41/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23452
							output: 40
							thoughts: 611
						

Thoughts:
**My Analysis of HMM (Hardware Managed Memory) Fault Handling**

Okay, so I'm looking at this code and trying to figure out how these HMM fault handlers are triggered and used. First, this seems to be a boolean config option without a direct prompt, which means it can't be enabled standalone. It's dependent on `TEST_HMM` or one of the device drivers.

My key focus here is on understanding how the fault handling is initiated and how it interacts with the system. I've got a list of important symbols to track: `hmm_range_fault_unlocked_timeout`, `hmm_range_fault`, `hmm_do_fault`, `hmm_range_fault_locked`, and `hmm_record_fault`. I'm starting to build a mental map of how these functions relate to each other. I've already confirmed `hmm_do_fault`, `hmm_range_fault_locked`, and `hmm_record_fault` are static, which is good to know for scoping and visibility. `hmm_range_fault_unlocked_timeout` and `hmm_range_fault` are exported, which implies they have external use cases.

Next, I need to understand how the fault handling interacts with various hardware architectures. I've started scanning the code base for relevant functions. I've identified the following in `lib/test_hmm.c`: `dmirror_range_fault_unlocked`, `dmirror_fault_unlocked`, and `dmirror_read_unlocked` which indicates this module has the test functions. Then I moved on and looked at functions in driver directories.

`drivers/accel/amdxdna/aie2_ctx.c` has `aie2_populate_range`, which has something to do with the fault handling for this specific architecture. I also noticed that the `drivers/gpu/drm/drm_gpusvm.c` module contains the functions `drm_gpusvm_scan_mm`, `drm_gpusvm_get_pages`, and `drm_gpusvm_range_evict`, so clearly GPU and SVM have some involvement. `drivers/gpu/drm/nouveau/nouveau_svm.c` and `drivers/hv/mshv_regions.c` contain `nouveau_range_fault` and `mshv_region_range_fault` which are additional examples.

Finally, `drivers/infiniband/core/umem_odp.c` is important with the function `ib_umem_odp_map_dma_and_lock`, so it is clear that Infiniband is important.

So, just to recap the functions I need to keep in mind, I'm going to list:

*   `hmm_range_fault_unlocked_timeout`
*   `hmm_range_fault`
*   `hmm_do_fault`
*   `hmm_range_fault_locked`
*   `hmm_record_fault`
*   `dmirror_range_fault_unlocked`
*   `dmirror_fault_unlocked`
*   `dmirror_read_unlocked`

These are my focus points. I'm building an understanding of how these core functions interact with potential fault triggers.

Okay, let's verify that the config options align with what I'm seeing: `TEST_HMM`, `DRM_ACCEL_AMDXDNA`, `DRM_GPUSVM`, `DRM_NOUVEAU_SVM`, `MSHV_ROOT`, and `INFINIBAND_ON_DEMAND_PAGING`. I should check if there are other relevant configurations, so let's cross-reference `DRM_ACCEL_AMDXDNA` within the appropriate `Kconfig` file in the driver directory to ensure I'm capturing all related settings.



42/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "config DRM_ACCEL_AMDXDNA",
  "PathPrefix": "drivers/accel/amdxdna/Kconfig"
}

Results:
{
  "Output": "drivers/accel/amdxdna/Kconfig-2-\ndrivers/accel/amdxdna/Kconfig:3:config DRM_ACCEL_AMDXDNA\ndrivers/accel/amdxdna/Kconfig-4-\ttristate \"AMD AI Engine\"\n"
}

43/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25016
							output: 34
							thoughts: 26
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Kernel Configuration Exploration**

Right, that's settled. Time to dig into the details. My immediate thought is to verify the status of `DRM_GPUSVM`. I need to ensure it's properly configured within the kernel's build system. Specifically, I'll be heading over to `drivers/gpu/drm/Kconfig`. That's where all the relevant dependencies and build options for DRM-related features reside. I need to understand if it's enabled, disabled, or built as a module, and if there are any conditional dependencies that might impact its availability. I expect to confirm my assumption that it is enabled, but I need to ensure this is correct before moving forward.



44/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "config DRM_GPUSVM",
  "PathPrefix": "drivers/gpu/drm/Kconfig"
}

Results:
{
  "Output": "drivers/gpu/drm/Kconfig=221=config RUST_DRM_GPUVM\n--\ndrivers/gpu/drm/Kconfig-227-\ndrivers/gpu/drm/Kconfig:228:config DRM_GPUSVM\ndrivers/gpu/drm/Kconfig-229-\ttristate\n"
}

45/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25329
							output: 40
							thoughts: 32
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Checking Nouveau's SVM Configuration**

Yes. Okay, so we're on the right track. Time to dive into the kernel configuration. I need to confirm whether `DRM_NOUVEAU_SVM` is enabled and how it's defined. Specifically, I'm going to look at the `drivers/gpu/drm/nouveau/Kconfig` file. That's the logical place to find the conditional logic and dependencies for the Nouveau driver's System Memory Management (SVM) support. I need to understand if it's even compiled in, if it's a module, and what other options might be relevant for my current investigation. Let's see what we find.



46/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "config DRM_NOUVEAU_SVM",
  "PathPrefix": "drivers/gpu/drm/nouveau/Kconfig"
}

Results:
{
  "Output": "drivers/gpu/drm/nouveau/Kconfig=86=config DRM_NOUVEAU_BACKLIGHT\n--\ndrivers/gpu/drm/nouveau/Kconfig-93-\ndrivers/gpu/drm/nouveau/Kconfig:94:config DRM_NOUVEAU_SVM\ndrivers/gpu/drm/nouveau/Kconfig-95-\tbool \"(EXPERIMENTAL) Enable SVM (Shared Virtual Memory) support\"\n"
}

47/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25680
							output: 32
							thoughts: 22
						

Thoughts:
Okay, here's my summarized thought process:

**Internal Mental Checklist**

Yes. Okay, I've got a handle on the task. Now, where to start... let's pinpoint the relevant configuration variable. I need to make sure I'm looking at the correct file. The logical place to start is the `MSHV_ROOT` variable within the `drivers/hv/Kconfig` file. That seems like the right path to track down that specific configuration option.



48/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "config MSHV_ROOT",
  "PathPrefix": "drivers/hv/Kconfig"
}

Results:
{
  "Output": "drivers/hv/Kconfig=60=config HYPERV_VMBUS\n--\ndrivers/hv/Kconfig-67-\ndrivers/hv/Kconfig:68:config MSHV_ROOT\ndrivers/hv/Kconfig-69-\ttristate \"Microsoft Hyper-V root partition support\"\n"
}

49/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25920
							output: 41
							thoughts: 32
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's inner monologue:

**Checking Infiniband Paging Configuration**

Right. Time to dig in. The initial prompt is pretty straightforward, so I'm going to start with the obvious: confirming the setting for `INFINIBAND_ON_DEMAND_PAGING`. I'll immediately jump to where it's defined: `drivers/infiniband/Kconfig`. No need to waste time elsewhere; this is likely where all the relevant details are housed, and I can quickly establish the current status and relevant dependencies. This is usually the first stop for understanding how a feature is configured within the Infiniband subsystem. Let's see what we've got.



50/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "config INFINIBAND_ON_DEMAND_PAGING",
  "PathPrefix": "drivers/infiniband/Kconfig"
}

Results:
{
  "Output": "drivers/infiniband/Kconfig=44=config INFINIBAND_USER_MEM\n--\ndrivers/infiniband/Kconfig-50-\ndrivers/infiniband/Kconfig:51:config INFINIBAND_ON_DEMAND_PAGING\ndrivers/infiniband/Kconfig-52-\tbool \"InfiniBand on-demand paging support\"\n"
}

51/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26252
							output: 31
							thoughts: 22
						

Thoughts:
Okay, here's my summary, tailored for an expert audience, as if it's a direct stream of my thoughts:

**Checking `TEST_HMM` in `lib/Kconfig.debug`**

Right, here we go. Let's make sure `TEST_HMM` is configured correctly, or at least exists and is behaving as expected. That's a good first step to troubleshooting whatever's going on. We know this specific variable controls the HMM test which we are debugging. This should be straightforward.



52/2 2026/07/16 00:10 tool grepper
0m Args:
{
  "Expression": "config TEST_HMM",
  "PathPrefix": "lib/Kconfig.debug"
}

Results:
{
  "Output": "lib/Kconfig.debug=3318=config TEST_MEMINIT\n--\nlib/Kconfig.debug-3325-\nlib/Kconfig.debug:3326:config TEST_HMM\nlib/Kconfig.debug-3327-\ttristate \"Test HMM (Heterogeneous Memory Management)\"\n"
}

53/2 2026/07/16 00:10 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26508
							output: 261
							thoughts: 251
						

Thoughts:
**My Analysis of the Proposed Memory Management Changes**

Okay, so I'm looking at these configuration symbols: `TEST_HMM`, `DRM_ACCEL_AMDXDNA`, `DRM_GPUSVM`, `DRM_NOUVEAU_SVM`, `MSHV_ROOT`, and `INFINIBAND_ON_DEMAND_PAGING`. Based on the context, I'm already thinking these are related to some form of memory management or device driver interaction, likely around Heterogeneous Memory Management (HMM) given the presence of `TEST_HMM`.

Now, the core symbols of interest seem to be these "fault" related functions: `hmm_range_fault_unlocked_timeout`, `hmm_range_fault`, `hmm_do_fault`, `hmm_range_fault_locked`, `hmm_record_fault`, `dmirror_range_fault_unlocked`, `dmirror_fault_unlocked`, and `dmirror_read_unlocked`. The naming convention strongly suggests a change in how memory faults are handled, specifically around locking and timeout behavior, which also ties in nicely with the configs. I am noting the inclusion of `dmirror` which hints at some form of data mirroring.

From the description, I see this is introducing a new unlocked timeout variant for HMM range faults, `hmm_range_fault_unlocked_timeout`, and it's refactoring the existing `hmm_range_fault` to use a locked helper function. This is significant. It's not just a minor tweak; it's a fundamental shift in how faults are managed, especially around locking semantics. I can see this is likely affecting several drivers and the HMM test module.

My immediate concern is the robustness of the changes. The introduction of unlocked variants always increases the potential for race conditions or other concurrency issues. Therefore, I need to focus on fuzzing these new retry and locking logic, as well as ensure the new locking mechanisms are thoroughly tested. These changes will have substantial impact on device driver behavior, and may cause unexpected changes in system reliability.



54/2 2026/07/16 00:10 tool set-results
0m Args:
{
  "EnableConfigs": [
    "TEST_HMM",
    "DRM_ACCEL_AMDXDNA",
    "DRM_GPUSVM",
    "DRM_NOUVEAU_SVM",
    "MSHV_ROOT",
    "INFINIBAND_ON_DEMAND_PAGING"
  ],
  "FocusSymbols": [
    "hmm_range_fault_unlocked_timeout",
    "hmm_range_fault",
    "hmm_do_fault",
    "hmm_range_fault_locked",
    "hmm_record_fault",
    "dmirror_range_fault_unlocked",
    "dmirror_fault_unlocked",
    "dmirror_read_unlocked"
  ],
  "Reasoning": "The patch introduces a new unlocked timeout variant for HMM range faults (`hmm_range_fault_unlocked_timeout`) and refactors the existing `hmm_range_fault` to use a locked helper. It also updates several drivers and the HMM test module to use the new unlocked variant. This is a functional change to core memory management and device driver interaction, which should be fuzzed to ensure the new retry and locking logic is robust.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "TEST_HMM",
    "DRM_ACCEL_AMDXDNA",
    "DRM_GPUSVM",
    "DRM_NOUVEAU_SVM",
    "MSHV_ROOT",
    "INFINIBAND_ON_DEMAND_PAGING"
  ],
  "FocusSymbols": [
    "hmm_range_fault_unlocked_timeout",
    "hmm_range_fault",
    "hmm_do_fault",
    "hmm_range_fault_locked",
    "hmm_record_fault",
    "dmirror_range_fault_unlocked",
    "dmirror_fault_unlocked",
    "dmirror_read_unlocked"
  ],
  "Reasoning": "The patch introduces a new unlocked timeout variant for HMM range faults (`hmm_range_fault_unlocked_timeout`) and refactors the existing `hmm_range_fault` to use a locked helper. It also updates several drivers and the HMM test module to use the new unlocked variant. This is a functional change to core memory management and device driver interaction, which should be fuzzed to ensure the new retry and locking logic is robust.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)