[PATCH] mm/userfaultfd: Remove two userfaultfd fault retvals

From: Peter Xu

Date: Tue Feb 03 2026 - 15:07:58 EST


They're not needed when with vm_uffd_ops. We can remove both of them.
Actually, another side benefit is drivers do not need to process
userfaultfd missing / minor faults anymore in the main fault handler.

This patch will make get_folio_noalloc() required for either MISSING or
MINOR fault, but that's not a problem, as it should be lightweight and the
current only outside-mm user (gmem) will support both anyway.

Signed-off-by: Peter Xu <peterx@xxxxxxxxxx>
---
include/linux/mm_types.h | 15 +-----------
include/linux/userfaultfd_k.h | 2 +-
mm/memory.c | 45 +++++++++++++++++++++++++++++------
mm/shmem.c | 12 ----------
virt/kvm/guest_memfd.c | 20 ----------------
5 files changed, 40 insertions(+), 54 deletions(-)

diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index a6d32470a78a3..3cc8ae7228860 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -1612,10 +1612,6 @@ typedef __bitwise unsigned int vm_fault_t;
* fsync() to complete (for synchronous page faults
* in DAX)
* @VM_FAULT_COMPLETED: ->fault completed, meanwhile mmap lock released
- * @VM_FAULT_UFFD_MINOR: ->fault did not modify page tables and needs
- * handle_userfault(VM_UFFD_MINOR) to complete
- * @VM_FAULT_UFFD_MISSING: ->fault did not modify page tables and needs
- * handle_userfault(VM_UFFD_MISSING) to complete
* @VM_FAULT_HINDEX_MASK: mask HINDEX value
*
*/
@@ -1633,13 +1629,6 @@ enum vm_fault_reason {
VM_FAULT_DONE_COW = (__force vm_fault_t)0x001000,
VM_FAULT_NEEDDSYNC = (__force vm_fault_t)0x002000,
VM_FAULT_COMPLETED = (__force vm_fault_t)0x004000,
-#ifdef CONFIG_USERFAULTFD
- VM_FAULT_UFFD_MINOR = (__force vm_fault_t)0x008000,
- VM_FAULT_UFFD_MISSING = (__force vm_fault_t)0x010000,
-#else
- VM_FAULT_UFFD_MINOR = (__force vm_fault_t)0x000000,
- VM_FAULT_UFFD_MISSING = (__force vm_fault_t)0x000000,
-#endif
VM_FAULT_HINDEX_MASK = (__force vm_fault_t)0x0f0000,
};

@@ -1664,9 +1653,7 @@ enum vm_fault_reason {
{ VM_FAULT_FALLBACK, "FALLBACK" }, \
{ VM_FAULT_DONE_COW, "DONE_COW" }, \
{ VM_FAULT_NEEDDSYNC, "NEEDDSYNC" }, \
- { VM_FAULT_COMPLETED, "COMPLETED" }, \
- { VM_FAULT_UFFD_MINOR, "UFFD_MINOR" }, \
- { VM_FAULT_UFFD_MISSING, "UFFD_MISSING" }
+ { VM_FAULT_COMPLETED, "COMPLETED" }

struct vm_special_mapping {
const char *name; /* The name, e.g. "[vdso]". */
diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h
index 75d5b09f2560c..5923e32de53b5 100644
--- a/include/linux/userfaultfd_k.h
+++ b/include/linux/userfaultfd_k.h
@@ -85,7 +85,7 @@ struct vm_uffd_ops {
/* Checks if a VMA can support userfaultfd */
bool (*can_userfault)(struct vm_area_struct *vma, vm_flags_t vm_flags);
/*
- * Called to resolve UFFDIO_CONTINUE request.
+ * Required by any uffd driver for either MISSING or MINOR fault.
* Should return the folio found at pgoff in the VMA's pagecache if it
* exists or ERR_PTR otherwise.
* The returned folio is locked and with reference held.
diff --git a/mm/memory.c b/mm/memory.c
index 456344938c72b..098febb761acc 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -5338,6 +5338,33 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf)
return VM_FAULT_OOM;
}

+static vm_fault_t fault_process_userfaultfd(struct vm_fault *vmf)
+{
+ struct vm_area_struct *vma = vmf->vma;
+ struct inode *inode = file_inode(vma->vm_file);
+ /*
+ * NOTE: we could double check this hook present when
+ * UFFDIO_REGISTER on MISSING or MINOR for a file driver.
+ */
+ struct folio *folio =
+ vma->vm_ops->uffd_ops->get_folio_noalloc(inode, vmf->pgoff);
+
+ if (!IS_ERR_OR_NULL(folio)) {
+ /*
+ * TODO: provide a flag for get_folio_noalloc() to avoid
+ * locking (or even the extra reference?)
+ */
+ folio_unlock(folio);
+ folio_put(folio);
+ if (userfaultfd_minor(vma))
+ return handle_userfault(vmf, VM_UFFD_MINOR);
+ } else {
+ return handle_userfault(vmf, VM_UFFD_MISSING);
+ }
+
+ return 0;
+}
+
/*
* The mmap_lock must have been held on entry, and may have been
* released depending on flags and vma->vm_ops->fault() return value.
@@ -5370,16 +5397,20 @@ static vm_fault_t __do_fault(struct vm_fault *vmf)
return VM_FAULT_OOM;
}

+ /*
+ * If this is an userfaultfd trap, process it in advance before
+ * triggering the genuine fault handler.
+ */
+ if (userfaultfd_missing(vma) || userfaultfd_minor(vma)) {
+ ret = fault_process_userfaultfd(vmf);
+ if (ret)
+ return ret;
+ }
+
ret = vma->vm_ops->fault(vmf);
if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE | VM_FAULT_RETRY |
- VM_FAULT_DONE_COW | VM_FAULT_UFFD_MINOR |
- VM_FAULT_UFFD_MISSING))) {
- if (ret & VM_FAULT_UFFD_MINOR)
- return handle_userfault(vmf, VM_UFFD_MINOR);
- if (ret & VM_FAULT_UFFD_MISSING)
- return handle_userfault(vmf, VM_UFFD_MISSING);
+ VM_FAULT_DONE_COW)))
return ret;
- }

folio = page_folio(vmf->page);
if (unlikely(PageHWPoison(vmf->page))) {
diff --git a/mm/shmem.c b/mm/shmem.c
index eafd7986fc2ec..5286f28b3e443 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2484,13 +2484,6 @@ static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,
fault_mm = vma ? vma->vm_mm : NULL;

folio = filemap_get_entry(inode->i_mapping, index);
- if (folio && vma && userfaultfd_minor(vma)) {
- if (!xa_is_value(folio))
- folio_put(folio);
- *fault_type = VM_FAULT_UFFD_MINOR;
- return 0;
- }
-
if (xa_is_value(folio)) {
error = shmem_swapin_folio(inode, index, &folio,
sgp, gfp, vma, fault_type);
@@ -2535,11 +2528,6 @@ static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,
* Fast cache lookup and swap lookup did not find it: allocate.
*/

- if (vma && userfaultfd_missing(vma)) {
- *fault_type = VM_FAULT_UFFD_MISSING;
- return 0;
- }
-
/* Find hugepage orders that are allowed for anonymous shmem and tmpfs. */
orders = shmem_allowable_huge_orders(inode, vma, index, write_end, false);
if (orders > 0) {
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 14cca057fc0ec..bd0de685f42f8 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -421,26 +421,6 @@ static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
folio = __filemap_get_folio(inode->i_mapping, vmf->pgoff,
FGP_LOCK | FGP_ACCESSED, 0);

- if (userfaultfd_armed(vmf->vma)) {
- /*
- * If userfaultfd is registered in minor mode and a folio
- * exists, return VM_FAULT_UFFD_MINOR to trigger the
- * userfaultfd handler.
- */
- if (userfaultfd_minor(vmf->vma) && !IS_ERR_OR_NULL(folio)) {
- ret = VM_FAULT_UFFD_MINOR;
- goto out_folio;
- }
-
- /*
- * Check if userfaultfd is registered in missing mode. If so,
- * check if a folio exists in the page cache. If not, return
- * VM_FAULT_UFFD_MISSING to trigger the userfaultfd handler.
- */
- if (userfaultfd_missing(vmf->vma) && IS_ERR_OR_NULL(folio))
- return VM_FAULT_UFFD_MISSING;
- }
-
/* folio not in the pagecache, try to allocate */
if (IS_ERR(folio))
folio = __kvm_gmem_folio_alloc(inode, vmf->pgoff);
--
2.50.1


--
Peter Xu