Documentation/mm/hmm.rst | 79 +++++++--- drivers/accel/amdxdna/aie2_ctx.c | 23 +-- drivers/gpu/drm/drm_gpusvm.c | 60 ++------ drivers/gpu/drm/nouveau/nouveau_svm.c | 20 +-- drivers/hv/mshv_regions.c | 54 ++----- drivers/infiniband/core/umem_odp.c | 18 +-- include/linux/hmm.h | 2 + lib/test_hmm.c | 107 +++++++++++++- lib/test_hmm_uapi.h | 1 + mm/hmm.c | 262 +++++++++++++++++++++++++-------- tools/testing/selftests/mm/hmm-tests.c | 150 +++++++++++++++++++ 11 files changed, 557 insertions(+), 219 deletions(-)
This series extends the HMM framework to support userfaultfd-backed memory
by allowing the mmap read lock to be dropped during hmm_range_fault().
Some page fault handlers — most notably userfaultfd — require the mmap lock
to be released so that userspace can resolve the fault. The current HMM
interface never sets FAULT_FLAG_ALLOW_RETRY, making it impossible to fault
in pages from userfaultfd-registered regions.
This series follows the established int *locked pattern from
get_user_pages_remote() in mm/gup.c. A new helper function,
hmm_range_fault_locked(), accepts an int *locked parameter. When the
mmap lock is dropped during fault resolution (VM_FAULT_RETRY or
VM_FAULT_COMPLETED), the function returns 0 with *locked = 0, signalling
the caller to restart its walk. The existing hmm_range_fault() is
refactored into a thin wrapper that passes NULL, preserving current
behavior for all existing callers.
Possible approaches to lift this limitation are documented in
Documentation/mm/hmm.rst.
Changes in v11:
- Reject unstable address spaces in hmm_range_fault_unlocked_timeout()
after taking mmap_lock and before walking page tables.
- Compute the remaining HMM timeout budget before the time_after_eq()
check in drm_gpusvm_get_pages() to make sure it can't result in zero
and lead to infinite HMM range faulting loop.
Changes in v10:
- Included contended mmap_lock acquisition in the
hmm_range_fault_unlocked_timeout() retry budget.
- Dropped the redundant top-level fatal_signal_pending() check in the HMM
unlocked retry loop; mmap_read_lock_killable() now covers that path.
- Restored the absolute outer timeout in drm_gpusvm_get_pages(), since it can
run from GPU page-fault workers and must not rely on the worker task’s fatal
signal state to stop invalidation retries.
Changes in v9:
- Folded the fixups into the full 8-patch series instead of sending a
separate fixup series.
- Clarified that the HMM timeout bounds repeated HMM/mmu-notifier retry
attempts, with the helper refreshing range->notifier_seq internally.
- Kept nouveau’s explicit outer deadline because its retry loop runs in a GPU
fault worker and cannot rely on fatal signals from the faulting process.
- Converted amdxdna, and GPU SVM callers to pass the shole timeout budget to
hmm_range_fault_unlocked_timeout().
Changes in v8:
- Make hmm_range_fault_unlocked_timeout() the primary documented HMM
range-fault API, and move hmm_range_fault() into the “use only if the
caller really must hold mmap_lock” category.
- Clarify that the timeout is a retry budget for repeated mmu-notifier
invalidation retries. HMM does not interrupt an in-progress page fault
when the timeout expires.
- Restart the retry timeout only when handle_mm_fault() dropped
mmap_lock, because that indicates a lock-dropping fault handler such as
userfaultfd made progress. Ordinary -EBUSY retries keep the existing
deadline.
- Remove the attempted timeout selftest. The remaining selftest covers the
intended userfaultfd path by resolving missing-page faults through
HMM_DMIRROR_READ_UNLOCKED and hmm_range_fault_unlocked_timeout(..., 0).
Changes in v7:
- Replaced the unlocked HMM API with
hmm_range_fault_unlocked_timeout(). The helper now takes a timeout in
jiffies, with 0 meaning retry indefinitely.
- Moved -EBUSY retry handling into the HMM helper for the unlocked path.
The helper refreshes range->notifier_seq internally before each retry.
- Switched the unlocked path to mmap_read_lock_killable() and return
-EINTR if mmap lock acquisition is interrupted or a fatal signal is
pending during retry handling.
- Removed the redundant non-timeout hmm_range_fault_unlocked() interface.
- Updated Documentation/mm/hmm.rst and kernel-doc to describe the timeout API
and the intended caller pattern.
- Updated the HMM selftests to use hmm_range_fault_unlocked_timeout()
only, including coverage for the finite-timeout path.
- Added in-tree users of the new helper:
- mshv
- nouveau
- RDMA/umem
- amdxdna
- drm/gpusvm
- Preserved each converted driver’s existing timeout convention:
- unbounded retry where the old code retried indefinitely,
- HMM_RANGE_DEFAULT_TIMEOUT where the old code used that budget,
- existing driver-specific timeout return values such as -ETIME.
- Used max_t(long, timeout - jiffies, 1) when passing remaining time from
absolute jiffies deadlines to avoid unsigned underflow while keeping a
minimum one-jiffy retry window.
- Left callers on hmm_range_fault() when they already need to hold
mmap_lock across surrounding work, such as drm_gpusvm_check_pages().
Changes in v6:
- Reworked the new API from the external int *locked pattern to
hmm_range_fault_unlocked(), which owns mmap_read_lock() internally.
- Changed the dropped-lock contract: hmm_range_fault_unlocked() now returns
-EBUSY when the mmap lock is dropped, and callers restart with a fresh
mmu_interval_read_begin() sequence.
- Kept hmm_range_fault() as the locked variant for existing users, preserving
its caller-held mmap lock contract.
- Added an in-tree user by converting the MSHV region fault path to
hmm_range_fault_unlocked().
- Updated Documentation/mm/hmm.rst and kernel-doc to describe the unlocked
helper and retry pattern.
- Updated commit messages to match the new API and return semantics.
- Kept the userfaultfd HMM selftest using the test_hmm unlocked read ioctl
path.
Changes in v5:
- Rework hmm_range_fault_unlockable() retry handling to retry
VM_FAULT_RETRY internally with FAULT_FLAG_TRIED set, matching the
fixup_user_fault() pattern and avoiding repeated first-retry lock drops.
- Distinguish VM_FAULT_RETRY from VM_FAULT_COMPLETED: retry faults now
reacquire the mmap lock internally, while completed faults return to the
caller with *locked = 0 so the caller can restart with a fresh notifier
sequence.
- Document the two *locked return states, including the -EINTR case when a
fatal signal is pending after the mmap lock has already been dropped.
- Update comments around HMM_FAULT_UNLOCKED and the HMM fault loop to match
the current hmm_range_fault_unlockable() implementation.
Changes in v4:
- Rebased on 7.2-rc1
Changes in v3:
- Return -EFAULT from dmirror_fault_unlockable() when the mirrored mm can
no longer be pinned.
- Add an eventfd stop signal for the userfaultfd handler thread to avoid
waiting for the poll timeout on successful test completion.
Changes in v2:
- Split into a preparatory refactor (new patch 1) that moves
handle_mm_fault() out of the walk callbacks, plus a smaller feature
patch on top. Suggested by David Hildenbrand.
- Hugetlb regions are now supported on the unlockable path; the v1
-EFAULT short-circuit and the hugetlb_vma_lock_read drop/retake
dance are gone.
- Distinct internal sentinels for "needs fault" (HMM_FAULT_PENDING)
and "lock dropped" (HMM_FAULT_UNLOCKED).
- Outer loop now re-walks after a successful internal fault so the
faulted pfns end up in range->hmm_pfns.
- Kernel-doc on hmm_range_fault_unlockable() and the
Documentation/mm/hmm.rst example match the implementation.
- Dropped the mshv driver conversion (v1 patch 2); will post
separately.
- Selftest converted to drive the path through test_hmm with a
userfaultfd handler (new HMM_DMIRROR_READ_UNLOCKABLE ioctl).
---
Stanislav Kinsburskii (8):
mm/hmm: move page fault handling out of walk callbacks
mm/hmm: add hmm_range_fault_unlocked_timeout() for mmap lock-drop support
selftests/mm: add HMM test for mmap lock-dropping faults
mshv: Use hmm_range_fault_unlocked_timeout() for region faults
drm/nouveau: Use hmm_range_fault_unlocked_timeout() for SVM faults
RDMA/umem: Use hmm_range_fault_unlocked_timeout() for ODP faults
accel/amdxdna: Use hmm_range_fault_unlocked_timeout() for range population
drm/gpusvm: Use hmm_range_fault_unlocked_timeout() for range faults
Documentation/mm/hmm.rst | 79 +++++++---
drivers/accel/amdxdna/aie2_ctx.c | 23 +--
drivers/gpu/drm/drm_gpusvm.c | 60 ++------
drivers/gpu/drm/nouveau/nouveau_svm.c | 20 +--
drivers/hv/mshv_regions.c | 54 ++-----
drivers/infiniband/core/umem_odp.c | 18 +--
include/linux/hmm.h | 2 +
lib/test_hmm.c | 107 +++++++++++++-
lib/test_hmm_uapi.h | 1 +
mm/hmm.c | 262 +++++++++++++++++++++++++--------
tools/testing/selftests/mm/hmm-tests.c | 150 +++++++++++++++++++
11 files changed, 557 insertions(+), 219 deletions(-)
---
base-commit: a4271215228d2bd2a50eea03ab0a948b36036948
change-id: 20260722-hmm-v10-727db2bb9d34
Best regards,
--
Stanislav Kinsburskii <skinsburskii@gmail.com>
On Thu, 23 Jul 2026 10:36:32 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:
> This series extends the HMM framework to support userfaultfd-backed memory
> by allowing the mmap read lock to be dropped during hmm_range_fault().
>
Thanks, I've updated mm.git to this version.
AI review suggests there may be some problems. Sorry, I don't recall if
these were considered in previous versions of the patchset:
https://sashiko.dev/#/patchset/20260723-hmm-v10-v11-0-c55b003a4b61@gmail.com
>
> Changes in v11:
> - Reject unstable address spaces in hmm_range_fault_unlocked_timeout()
> after taking mmap_lock and before walking page tables.
> - Compute the remaining HMM timeout budget before the time_after_eq()
> check in drm_gpusvm_get_pages() to make sure it can't result in zero
> and lead to infinite HMM range faulting loop.
Here's how v11 altered mm.git:
drivers/gpu/drm/drm_gpusvm.c | 4 ++--
mm/hmm.c | 6 ++++++
2 files changed, 8 insertions(+), 2 deletions(-)
--- a/drivers/gpu/drm/drm_gpusvm.c~b
+++ a/drivers/gpu/drm/drm_gpusvm.c
@@ -1422,11 +1422,11 @@ int drm_gpusvm_get_pages(struct drm_gpus
struct dma_iova_state *state = &svm_pages->state;
retry:
+ remaining = timeout - jiffies;
+
if (time_after_eq(jiffies, timeout))
return -EBUSY;
- remaining = timeout - jiffies;
-
hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
if (drm_gpusvm_pages_valid_unlocked(gpusvm, svm_pages))
goto set_seqno;
--- a/mm/hmm.c~b
+++ a/mm/hmm.c
@@ -17,6 +17,7 @@
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/mmzone.h>
+#include <linux/oom.h>
#include <linux/pagemap.h>
#include <linux/leafops.h>
#include <linux/hugetlb.h>
@@ -806,6 +807,11 @@ int hmm_range_fault_unlocked_timeout(str
if (ret)
return ret;
+ if (check_stable_address_space(mm)) {
+ mmap_read_unlock(mm);
+ return -EFAULT;
+ }
+
if (timeout && time_after(jiffies, deadline)) {
mmap_read_unlock(mm);
return -EBUSY;
_
On Thu, Jul 23, 2026 at 02:22:42PM -0700, Andrew Morton wrote:
> On Thu, 23 Jul 2026 10:36:32 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:
>
> > This series extends the HMM framework to support userfaultfd-backed memory
> > by allowing the mmap read lock to be dropped during hmm_range_fault().
> >
>
> Thanks, I've updated mm.git to this version.
>
> AI review suggests there may be some problems. Sorry, I don't recall if
> these were considered in previous versions of the patchset:
>
> https://sashiko.dev/#/patchset/20260723-hmm-v10-v11-0-c55b003a4b61@gmail.com
>
These look like new findings, perhaps from a newer model or review prompt.
I do not think they apply here.
The unlockable path sets FAULT_FLAG_ALLOW_RETRY only when HMM provides a
non-NULL lock state pointer. The legacy hmm_range_fault() path passes NULL
and therefore does not opt in to lock-dropping faults. Fault handlers are not
supposed to return VM_FAULT_RETRY/VM_FAULT_COMPLETED with the mmap lock
dropped unless FAULT_FLAG_ALLOW_RETRY allows that, so the NULL locked state is
not expected to be dereferenced on the legacy path.
For the FAULT_FLAG_TRIED concern, HMM is not trying to emulate GUP's exact
single-address retry loop. If a fault drops mmap_lock, HMM has to restart the
range walk because the VMA/page table state may have changed. Resetting the
timeout on that path is intentional: a lock-dropping fault made progress, and
the timeout is meant to bound mmu-notifier retry churn rather than the time
spent servicing faults.
Also, the newly converted HMM users are not using this path for ordinary
file-backed page-cache population, and process-context callers can still be
interrupted by fatal signals. All the existent kernel thread callers are
capped by a timeout.
So I do not think this requires a code change.
Thanks,
Stanislav
> >
> > Changes in v11:
> > - Reject unstable address spaces in hmm_range_fault_unlocked_timeout()
> > after taking mmap_lock and before walking page tables.
> > - Compute the remaining HMM timeout budget before the time_after_eq()
> > check in drm_gpusvm_get_pages() to make sure it can't result in zero
> > and lead to infinite HMM range faulting loop.
>
> Here's how v11 altered mm.git:
>
>
> drivers/gpu/drm/drm_gpusvm.c | 4 ++--
> mm/hmm.c | 6 ++++++
> 2 files changed, 8 insertions(+), 2 deletions(-)
>
> --- a/drivers/gpu/drm/drm_gpusvm.c~b
> +++ a/drivers/gpu/drm/drm_gpusvm.c
> @@ -1422,11 +1422,11 @@ int drm_gpusvm_get_pages(struct drm_gpus
> struct dma_iova_state *state = &svm_pages->state;
>
> retry:
> + remaining = timeout - jiffies;
> +
> if (time_after_eq(jiffies, timeout))
> return -EBUSY;
>
> - remaining = timeout - jiffies;
> -
> hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
> if (drm_gpusvm_pages_valid_unlocked(gpusvm, svm_pages))
> goto set_seqno;
> --- a/mm/hmm.c~b
> +++ a/mm/hmm.c
> @@ -17,6 +17,7 @@
> #include <linux/slab.h>
> #include <linux/sched.h>
> #include <linux/mmzone.h>
> +#include <linux/oom.h>
> #include <linux/pagemap.h>
> #include <linux/leafops.h>
> #include <linux/hugetlb.h>
> @@ -806,6 +807,11 @@ int hmm_range_fault_unlocked_timeout(str
> if (ret)
> return ret;
>
> + if (check_stable_address_space(mm)) {
> + mmap_read_unlock(mm);
> + return -EFAULT;
> + }
> +
> if (timeout && time_after(jiffies, deadline)) {
> mmap_read_unlock(mm);
> return -EBUSY;
> _
>
© 2016 - 2026 Red Hat, Inc.