AI Trajectory


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

Results:
{
  "EnableConfigs": [
    "HUGETLBFS",
    "CHR_DEV_SG",
    "USB_MON",
    "SND_DUMMY"
  ],
  "FocusSymbols": [
    "map_discontig_kernel_pages_prepare",
    "map_discontig_kernel_pages_complete",
    "mmap_prepare_validate",
    "mmap_hook_validate",
    "mlock_vma_pages_range",
    "sg_discontig_get",
    "sg_discontig_init",
    "mon_bin_discontig_get",
    "mon_bin_discontig_init",
    "snd_pcm_mmap_status"
  ],
  "KMSANReasoning": "The patch series refactors VMA handling and introduces a discontiguous kernel page mapping framework for mmap_prepare:\n1. It replaces `is_vm_hugetlb_page` with `vma_is_hugetlb` and introduces semantic helper predicates for VMA flag properties (`vma_is_kernel_owned`, `vma_is_fixed_mapping`, `vma_can_merge`, `vma_is_persistent`, `vma_can_gup`).\n2. It introduces `mmap_action_map_discontig_kernel_pages()` along with `struct discontig_kernel_page_state` and `struct discontig_kernel_page_ops`, converting driver mmap hooks (such as cmt_speech, hfi1, sg, and mon_bin) to use `mmap_prepare` and `remap_vmalloc_range`/discontiguous page helpers instead of custom `.fault` handlers.\n3. All newly introduced local structures (e.g., `struct discontig_kernel_page_state state` in `map_discontig_kernel_pages_complete` and `struct vm_area_desc prev_desc` via `compat_set_desc_from_vma`) are zero-initialized with designated initializers or `memset` before any field access.\n4. No kernel data structures or uninitialized struct paddings/buffers are exposed or copied to userspace via syscalls, ioctls, or netlink.\n\nAny potential issues introduced by this refactoring (e.g., page table manipulation, VMA locking, use-after-free, or out-of-bounds page access) fall squarely under KASAN, LOCKDEP, and standard MM assertion checks. There are no uninitialized memory use or info-leak risks requiring a dedicated KMSAN session.",
  "NeedsKMSAN": false,
  "Reasoning": "This patch series introduces substantial changes to core memory management (MM), including replacing VM_SPECIAL with new granular VMA classification helpers (vma_is_kernel_owned, vma_is_fixed_mapping, vma_can_merge, vma_is_persistent, vma_can_gup), adding a new mmap action for mapping discontiguous kernel pages (MMAP_DISCONTIG_KERNEL_PAGES) along with new prepare/complete hooks, adding strict mmap validation logic (mmap_prepare_validate, mmap_hook_validate), refactoring mlock/munlock logic, and migrating several drivers (SCSI generic sg, USB mon_bin, ALSA PCM native) to the new interfaces. These paths are fully reachable and introduce new runtime assertions (WARN_ON_ONCE, VM_WARN_ON_ONCE).",
  "WorthFuzzing": true
}

1/1 2026/09/08 22:26 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 27e2d5e005e5b11519ad4486d292f37520bbd200\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Sep 8 22:26:20 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/filesystems/mmap_prepare.rst b/Documentation/filesystems/mmap_prepare.rst\nindex 82c99c95ad854..a476e1006bf12 100644\n--- a/Documentation/filesystems/mmap_prepare.rst\n+++ b/Documentation/filesystems/mmap_prepare.rst\n@@ -164,5 +164,86 @@ pointer. These are:\n   sufficient entries in the page array to cover the entire range of the\n   described VMA.\n \n+* mmap_action_map_discontig_kernel_pages() - Maps a discontiguous range of\n+  `struct page` pointers over the VMA. They must span from the start of the VMA,\n+  but may terminate prior to the end (leaving the remainder unmapped).\n+\n **NOTE:** The ``action`` field should never normally be manipulated directly,\n rather you ought to use one of these helpers.\n+\n+Discontiguous Actions\n+=====================\n+\n+Some actions can be performed across discontiguous ranges.\n+\n+Map kernel pages\n+----------------\n+\n+To map kernel pages discontiguously, you must provide hooks using ``struct\n+discontig_kernel_page_ops``:\n+\n+.. code-block:: C\n+\n+    struct discontig_kernel_page_ops {\n+        int (*init)(void *vm_private_data, void **private);\n+        int (*get)(struct discontig_kernel_page_state *state);\n+    };\n+\n+The ``init`` hook is optional and allows state to be established before the\n+operation starts, for instance taking a reference count. Nothing is invoked\n+after the operation, so ``init`` must not leave locks held, and state that must\n+be released once the mapping goes away should be released in\n+``vm_ops-\u003eclose``.\n+\n+The ``init`` hook, if provided, is invoked prior to the operation starting. It\n+may update what is pointed to by ``vm_private_data`` and/or ``private``. If an\n+error is returned, then the operation is aborted. The ``private`` field can be\n+reassigned.\n+\n+**NOTE:** The operation may sleep between invocations of ``get``, so locks\n+needed to stabilise state must be taken and released within each hook.\n+\n+The ``get`` handler is the key means through which the operation is\n+executed. The current state of the operation is provided through ``struct\n+discontig_kernel_page_state``:\n+\n+.. code-block:: C\n+\n+    struct discontig_kernel_page_state {\n+        /* Map state. */\n+        unsigned long start;            /* Start address of VMA. */\n+        unsigned long end;              /* End address of VMA. */\n+        unsigned long addr;             /* The current address to be mapped. */\n+        pgoff_t pgoff;                  /* The current pgoff to be mapped. */\n+        unsigned long nr_pages_mapped;  /* The number of pages mapped. */\n+        unsigned long nr_pages_remain;  /* The number of pages remaining. */\n+\n+        /* User-defined state. */\n+        void *vm_private_data;          /* VMA private data. */\n+        void *private;                  /* Mapping private data. */\n+\n+        /* Users should not touch these, use discontig_kernel_map_*() helpers. */\n+        ... internal fields ...\n+    };\n+\n+With ``private`` being an additional user-controllable state variable,\n+initialised via ``mmap_action_map_discontig_kernel_pages()``, and\n+``vm_private_data`` being equal to the ``desc-\u003eprivate_data`` field set in\n+the ``mmap_prepare()`` hook.\n+\n+In the ``get`` hook, the user must choose how to map kernel pages:\n+\n+* ``discontig_kernel_map_abort()`` - Call this to abort the operation, whatever\n+  has been mapped so far will be retained, the rest of the mapping will SIGBUS\n+  if accessed.\n+* ``discontig_kernel_map_page()`` - Maps a single page, correctly handling\n+  compound pages (if the compound page is bigger than the remaining pages in the\n+  VMA, then only those pages that fit will be mapped). For a compound page, the\n+  head page must be passed.\n+* ``discontig_kernel_map_page_range()`` - Map an array of pages of a specified\n+  size. Note that if the number of pages specified exceeds the VMA size then an\n+  error will arise.\n+\n+If an error arises after ``init`` succeeded, the core unmaps the VMA, invoking\n+``vm_ops-\u003eclose`` if set, which is therefore the place to release any state\n+that ``init`` established.\ndiff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c\nindex 9ba86450fe4af..3c1240ffc38df 100644\n--- a/arch/arm64/kvm/mmu.c\n+++ b/arch/arm64/kvm/mmu.c\n@@ -1463,14 +1463,12 @@ static int get_vma_page_shift(struct vm_area_struct *vma, unsigned long hva)\n {\n \tunsigned long pa;\n \n-\tif (is_vm_hugetlb_page(vma) \u0026\u0026 !(vma-\u003evm_flags \u0026 VM_PFNMAP))\n+\tif (vma_is_hugetlb(vma))\n \t\treturn huge_page_shift(hstate_vma(vma));\n \n \tif (!(vma-\u003evm_flags \u0026 VM_PFNMAP))\n \t\treturn PAGE_SHIFT;\n \n-\tVM_BUG_ON(is_vm_hugetlb_page(vma));\n-\n \tpa = (vma-\u003evm_pgoff \u003c\u003c PAGE_SHIFT) + (hva - vma-\u003evm_start);\n \n #ifndef __PAGETABLE_PMD_FOLDED\ndiff --git a/arch/powerpc/mm/book3s64/radix_tlb.c b/arch/powerpc/mm/book3s64/radix_tlb.c\nindex 7de5760164a90..b4603a98224b3 100644\n--- a/arch/powerpc/mm/book3s64/radix_tlb.c\n+++ b/arch/powerpc/mm/book3s64/radix_tlb.c\n@@ -627,7 +627,7 @@ void radix__local_flush_tlb_page(struct vm_area_struct *vma, unsigned long vmadd\n {\n #ifdef CONFIG_HUGETLB_PAGE\n \t/* need the return fix for nohash.c */\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\treturn radix__local_flush_hugetlb_page(vma, vmaddr);\n #endif\n \tradix__local_flush_tlb_page_psize(vma-\u003evm_mm, vmaddr, mmu_virtual_psize);\n@@ -945,7 +945,7 @@ void radix__flush_tlb_page_psize(struct mm_struct *mm, unsigned long vmaddr,\n void radix__flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr)\n {\n #ifdef CONFIG_HUGETLB_PAGE\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\treturn radix__flush_hugetlb_page(vma, vmaddr);\n #endif\n \tradix__flush_tlb_page_psize(vma-\u003evm_mm, vmaddr, mmu_virtual_psize);\n@@ -1113,7 +1113,7 @@ void radix__flush_tlb_range(struct vm_area_struct *vma, unsigned long start,\n \n {\n #ifdef CONFIG_HUGETLB_PAGE\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\treturn radix__flush_hugetlb_tlb_range(vma, start, end);\n #endif\n \ndiff --git a/arch/powerpc/mm/nohash/e500_hugetlbpage.c b/arch/powerpc/mm/nohash/e500_hugetlbpage.c\nindex a134d28a0e4d3..b87623f04be53 100644\n--- a/arch/powerpc/mm/nohash/e500_hugetlbpage.c\n+++ b/arch/powerpc/mm/nohash/e500_hugetlbpage.c\n@@ -180,7 +180,7 @@ book3e_hugetlb_preload(struct vm_area_struct *vma, unsigned long ea, pte_t pte)\n  */\n void __update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep)\n {\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\tbook3e_hugetlb_preload(vma, address, *ptep);\n }\n \ndiff --git a/arch/powerpc/mm/nohash/tlb.c b/arch/powerpc/mm/nohash/tlb.c\nindex 0a650742f3a00..07a2db16c2b15 100644\n--- a/arch/powerpc/mm/nohash/tlb.c\n+++ b/arch/powerpc/mm/nohash/tlb.c\n@@ -278,7 +278,7 @@ void __flush_tlb_page(struct mm_struct *mm, unsigned long vmaddr,\n void flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr)\n {\n #ifdef CONFIG_HUGETLB_PAGE\n-\tif (vma \u0026\u0026 is_vm_hugetlb_page(vma))\n+\tif (vma \u0026\u0026 vma_is_hugetlb(vma))\n \t\tflush_hugetlb_page(vma, vmaddr);\n #endif\n \ndiff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c\nindex 6035b5ec95039..5c5c77f98bf0f 100644\n--- a/arch/riscv/kvm/mmu.c\n+++ b/arch/riscv/kvm/mmu.c\n@@ -664,7 +664,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,\n \t\treturn -EFAULT;\n \t}\n \n-\tis_hugetlb = is_vm_hugetlb_page(vma);\n+\tis_hugetlb = vma_is_hugetlb(vma);\n \tif (is_hugetlb)\n \t\tvma_pageshift = huge_page_shift(hstate_vma(vma));\n \telse\ndiff --git a/arch/riscv/mm/tlbflush.c b/arch/riscv/mm/tlbflush.c\nindex 962db300a1665..a74a7d5258aa1 100644\n--- a/arch/riscv/mm/tlbflush.c\n+++ b/arch/riscv/mm/tlbflush.c\n@@ -149,7 +149,7 @@ void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,\n {\n \tunsigned long stride_size;\n \n-\tif (!is_vm_hugetlb_page(vma)) {\n+\tif (!vma_is_hugetlb(vma)) {\n \t\tstride_size = PAGE_SIZE;\n \t} else {\n \t\tstride_size = huge_page_size(hstate_vma(vma));\ndiff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c\nindex ff63ffb1dbd29..3f6783b93e679 100644\n--- a/arch/s390/mm/gmap_helpers.c\n+++ b/arch/s390/mm/gmap_helpers.c\n@@ -102,7 +102,7 @@ __context_unsafe(/* pte_unmap_unlock() not instrumented */)\n \n \t/* Find the vm address for the guest address */\n \tvma = vma_lookup(mm, vmaddr);\n-\tif (!vma || is_vm_hugetlb_page(vma))\n+\tif (!vma || vma_is_hugetlb(vma))\n \t\treturn;\n \n \t/* Get pointer to the page table entry */\n@@ -139,7 +139,7 @@ void gmap_helper_discard(struct mm_struct *mm, unsigned long vmaddr, unsigned lo\n \t\tvma = find_vma_intersection(mm, vmaddr, end);\n \t\tif (!vma)\n \t\t\treturn;\n-\t\tif (!is_vm_hugetlb_page(vma))\n+\t\tif (!vma_is_hugetlb(vma))\n \t\t\tzap_vma_range(vma, vmaddr, min(end, vma-\u003evm_end) - vmaddr);\n \t\tvmaddr = vma-\u003evm_end;\n \t}\n@@ -247,7 +247,7 @@ static int __gmap_helper_unshare_zeropages(struct mm_struct *mm)\n \t\t * proof to catch unexpected zeropages in other mappings and\n \t\t * fail.\n \t\t */\n-\t\tif ((vma-\u003evm_flags \u0026 VM_PFNMAP) || is_vm_hugetlb_page(vma))\n+\t\tif ((vma-\u003evm_flags \u0026 VM_PFNMAP) || vma_is_hugetlb(vma))\n \t\t\tcontinue;\n \t\taddr = vma-\u003evm_start;\n \ndiff --git a/arch/sparc/mm/init_64.c b/arch/sparc/mm/init_64.c\nindex 103db4683b165..9bbccb5d23a8f 100644\n--- a/arch/sparc/mm/init_64.c\n+++ b/arch/sparc/mm/init_64.c\n@@ -413,7 +413,7 @@ void update_mmu_cache_range(struct vm_fault *vmf, struct vm_area_struct *vma,\n \tif (mm-\u003econtext.hugetlb_pte_count || mm-\u003econtext.thp_pte_count) {\n \t\tunsigned long hugepage_size = PAGE_SIZE;\n \n-\t\tif (is_vm_hugetlb_page(vma))\n+\t\tif (vma_is_hugetlb(vma))\n \t\t\thugepage_size = huge_page_size(hstate_vma(vma));\n \n \t\tif (hugepage_size \u003e= PUD_SIZE) {\ndiff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c\nindex 65a2de82ecd29..0f60c0d076b62 100644\n--- a/arch/x86/kernel/uprobes.c\n+++ b/arch/x86/kernel/uprobes.c\n@@ -715,7 +715,7 @@ static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsign\n \n \t*new_mapping = true;\n \treturn _install_special_mapping(mm, vaddr, PAGE_SIZE,\n-\t\t\t\tVM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_IO,\n+\t\t\t\tVM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_MIXEDMAP,\n \t\t\t\t\u0026tramp_mapping);\n }\n \ndiff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c\nindex a93eee7ddb9e9..fab34fea99c2f 100644\n--- a/drivers/gpu/drm/drm_gpusvm.c\n+++ b/drivers/gpu/drm/drm_gpusvm.c\n@@ -9,9 +9,9 @@\n #include \u003clinux/dma-mapping.h\u003e\n #include \u003clinux/export.h\u003e\n #include \u003clinux/hmm.h\u003e\n-#include \u003clinux/hugetlb_inline.h\u003e\n #include \u003clinux/memremap.h\u003e\n #include \u003clinux/mm_types.h\u003e\n+#include \u003clinux/mm.h\u003e\n #include \u003clinux/slab.h\u003e\n \n #include \u003cdrm/drm_device.h\u003e\n@@ -1141,8 +1141,7 @@ drm_gpusvm_range_find_or_insert(struct drm_gpusvm *gpusvm,\n \t * limitations. If/when migrate_vma_* add more support, this logic will\n \t * have to change.\n \t */\n-\tmigrate_devmem = ctx-\u003edevmem_possible \u0026\u0026\n-\t\tvma_is_anonymous(vas) \u0026\u0026 !is_vm_hugetlb_page(vas);\n+\tmigrate_devmem = ctx-\u003edevmem_possible \u0026\u0026 vma_is_anonymous(vas);\n \n \tchunk_size = drm_gpusvm_range_chunk_size(gpusvm, notifier, vas,\n \t\t\t\t\t\t fault_addr, gpuva_start,\ndiff --git a/drivers/hsi/clients/cmt_speech.c b/drivers/hsi/clients/cmt_speech.c\nindex 7226677ebde7a..801697b74d4f8 100644\n--- a/drivers/hsi/clients/cmt_speech.c\n+++ b/drivers/hsi/clients/cmt_speech.c\n@@ -1084,22 +1084,6 @@ static void cs_hsi_stop(struct cs_hsi_iface *hi)\n \tkfree(hi);\n }\n \n-static vm_fault_t cs_char_vma_fault(struct vm_fault *vmf)\n-{\n-\tstruct cs_char *csdata = vmf-\u003evma-\u003evm_private_data;\n-\tstruct page *page;\n-\n-\tpage = virt_to_page((void *)csdata-\u003emmap_base);\n-\tget_page(page);\n-\tvmf-\u003epage = page;\n-\n-\treturn 0;\n-}\n-\n-static const struct vm_operations_struct cs_char_vm_ops = {\n-\t.fault\t= cs_char_vma_fault,\n-};\n-\n static int cs_char_fasync(int fd, struct file *file, int on)\n {\n \tstruct cs_char *csdata = file-\u003eprivate_data;\n@@ -1256,18 +1240,19 @@ static long cs_char_ioctl(struct file *file, unsigned int cmd,\n \treturn r;\n }\n \n-static int cs_char_mmap(struct file *file, struct vm_area_struct *vma)\n+static int cs_char_mmap_prepare(struct vm_area_desc *desc)\n {\n-\tif (vma-\u003evm_end \u003c vma-\u003evm_start)\n-\t\treturn -EINVAL;\n+\tstruct file *file = desc-\u003efile;\n+\tstruct cs_char *csdata = file-\u003eprivate_data;\n+\tstruct page **pages = (struct page **)\u0026desc-\u003eprivate_data;\n \n-\tif (vma_pages(vma) != 1)\n+\tif (vma_desc_pages(desc) != 1)\n \t\treturn -EINVAL;\n \n-\tvm_flags_set(vma, VM_IO | VM_DONTDUMP | VM_DONTEXPAND);\n-\tvma-\u003evm_ops = \u0026cs_char_vm_ops;\n-\tvma-\u003evm_private_data = file-\u003eprivate_data;\n+\tvma_desc_set_flags(desc, VMA_DONTDUMP_BIT, VMA_DONTEXPAND_BIT);\n \n+\t*pages = virt_to_page((void *)csdata-\u003emmap_base);\n+\tmmap_action_map_kernel_pages_full(desc, pages);\n \treturn 0;\n }\n \n@@ -1353,7 +1338,7 @@ static const struct file_operations cs_char_fops = {\n \t.write\t\t= cs_char_write,\n \t.poll\t\t= cs_char_poll,\n \t.unlocked_ioctl\t= cs_char_ioctl,\n-\t.mmap\t\t= cs_char_mmap,\n+\t.mmap_prepare\t= cs_char_mmap_prepare,\n \t.open\t\t= cs_char_open,\n \t.release\t= cs_char_release,\n \t.fasync\t\t= cs_char_fasync,\ndiff --git a/drivers/infiniband/hw/hfi1/file_ops.c b/drivers/infiniband/hw/hfi1/file_ops.c\nindex dc548e6802e24..7119d734edc7b 100644\n--- a/drivers/infiniband/hw/hfi1/file_ops.c\n+++ b/drivers/infiniband/hw/hfi1/file_ops.c\n@@ -70,7 +70,6 @@ static int set_ctxt_pkey(struct hfi1_ctxtdata *uctxt, unsigned long arg);\n static int ctxt_reset(struct hfi1_ctxtdata *uctxt);\n static int manage_rcvq(struct hfi1_ctxtdata *uctxt, u16 subctxt,\n \t\t       unsigned long arg);\n-static vm_fault_t vma_fault(struct vm_fault *vmf);\n static long hfi1_file_ioctl(struct file *fp, unsigned int cmd,\n \t\t\t    unsigned long arg);\n \n@@ -85,10 +84,6 @@ static const struct file_operations hfi1_file_ops = {\n \t.llseek = noop_llseek,\n };\n \n-static const struct vm_operations_struct vm_ops = {\n-\t.fault = vma_fault,\n-};\n-\n /*\n  * Types of memories mapped into user processes' space\n  */\n@@ -304,13 +299,13 @@ static ssize_t hfi1_write_iter(struct kiocb *kiocb, struct iov_iter *from)\n \treturn reqs;\n }\n \n-static inline void mmap_cdbg(u16 ctxt, u8 subctxt, u8 type, u8 mapio, u8 vmf,\n+static inline void mmap_cdbg(u16 ctxt, u8 subctxt, u8 type, u8 mapio, u8 is_vmalloc,\n \t\t\t     u64 memaddr, void *memvirt, dma_addr_t memdma,\n \t\t\t     ssize_t memlen, struct vm_area_struct *vma)\n {\n \thfi1_cdbg(PROC,\n-\t\t  \"%u:%u type:%u io/vf/dma:%d/%d/%d, addr:0x%llx, len:%lu(%lu), flags:0x%lx\",\n-\t\t  ctxt, subctxt, type, mapio, vmf, !!memdma,\n+\t\t  \"%u:%u type:%u io/vmalloc/dma:%d/%d/%d, addr:0x%llx, len:%lu(%lu), flags:0x%lx\",\n+\t\t  ctxt, subctxt, type, mapio, is_vmalloc, !!memdma,\n \t\t  memaddr ?: (u64)memvirt, memlen,\n \t\t  vma-\u003evm_end - vma-\u003evm_start, vma-\u003evm_flags);\n }\n@@ -325,7 +320,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t\tmemaddr = 0;\n \tvoid *memvirt = NULL;\n \tdma_addr_t memdma = 0;\n-\tu8 subctxt, mapio = 0, vmf = 0, type;\n+\tu8 subctxt, mapio = 0, is_vmalloc = 0, type;\n \tssize_t memlen = 0;\n \tint ret = 0;\n \tu16 ctxt;\n@@ -347,7 +342,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t/*\n \t * vm_pgoff is used as a buffer selector cookie.  Always mmap from\n \t * the beginning.\n-\t */ \n+\t */\n \tvma-\u003evm_pgoff = 0;\n \tflags = vma-\u003evm_flags;\n \n@@ -366,7 +361,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t\t */\n \t\tmemlen = PAGE_ALIGN(uctxt-\u003esc-\u003ecredits * PIO_BLOCK_SIZE);\n \t\tflags \u0026= ~VM_MAYREAD;\n-\t\tflags |= VM_DONTCOPY | VM_DONTEXPAND;\n+\t\tflags |= VM_DONTCOPY;\n \t\tvma-\u003evm_page_prot = pgprot_writecombine(vma-\u003evm_page_prot);\n \t\tmapio = 1;\n \t\tbreak;\n@@ -438,7 +433,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t\t\tmemvirt = uctxt-\u003eegrbufs.buffers[i].addr;\n \t\t\tmemdma = uctxt-\u003eegrbufs.buffers[i].dma;\n \t\t\tvma-\u003evm_end += memlen;\n-\t\t\tmmap_cdbg(ctxt, subctxt, type, mapio, vmf, memaddr,\n+\t\t\tmmap_cdbg(ctxt, subctxt, type, mapio, is_vmalloc, memaddr,\n \t\t\t\t  memvirt, memdma, memlen, vma);\n \t\t\tret = dma_mmap_coherent(\u0026dd-\u003epcidev-\u003edev, vma,\n \t\t\t\t\t\tmemvirt, memdma, memlen);\n@@ -467,7 +462,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t\t * user registers.\n \t\t */\n \t\tmemlen = PAGE_SIZE;\n-\t\tflags |= VM_DONTCOPY | VM_DONTEXPAND;\n+\t\tflags |= VM_DONTCOPY;\n \t\tvma-\u003evm_page_prot = pgprot_noncached(vma-\u003evm_page_prot);\n \t\tmapio = 1;\n \t\tbreak;\n@@ -476,15 +471,10 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t\t * Use the page where this context's flags are. User level\n \t\t * knows where it's own bitmap is within the page.\n \t\t */\n-\t\tmemaddr = (unsigned long)\n-\t\t\t(dd-\u003eevents + uctxt_offset(uctxt)) \u0026 PAGE_MASK;\n+\t\tmemvirt = dd-\u003eevents + uctxt_offset(uctxt);\n+\t\tmemvirt = (void *)(((uintptr_t)memvirt) \u0026 PAGE_MASK);\n \t\tmemlen = PAGE_SIZE;\n-\t\t/*\n-\t\t * v3.7 removes VM_RESERVED but the effect is kept by\n-\t\t * using VM_IO.\n-\t\t */\n-\t\tflags |= VM_IO | VM_DONTEXPAND;\n-\t\tvmf = 1;\n+\t\tis_vmalloc = 1;\n \t\tbreak;\n \tcase STATUS:\n \t\tif (flags \u0026 VM_WRITE) {\n@@ -493,7 +483,6 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t\t}\n \t\tmemaddr = kvirt_to_phys((void *)dd-\u003estatus);\n \t\tmemlen = PAGE_SIZE;\n-\t\tflags |= VM_IO | VM_DONTEXPAND;\n \t\tbreak;\n \tcase RTAIL:\n \t\tif (!HFI1_CAP_IS_USET(DMA_RTAIL)) {\n@@ -514,23 +503,20 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t\tflags \u0026= ~VM_MAYWRITE;\n \t\tbreak;\n \tcase SUBCTXT_UREGS:\n-\t\tmemaddr = (u64)uctxt-\u003esubctxt_uregbase;\n+\t\tmemvirt = uctxt-\u003esubctxt_uregbase;\n \t\tmemlen = PAGE_SIZE;\n-\t\tflags |= VM_IO | VM_DONTEXPAND;\n-\t\tvmf = 1;\n+\t\tis_vmalloc = 1;\n \t\tbreak;\n \tcase SUBCTXT_RCV_HDRQ:\n-\t\tmemaddr = (u64)uctxt-\u003esubctxt_rcvhdr_base;\n+\t\tmemvirt = uctxt-\u003esubctxt_rcvhdr_base;\n \t\tmemlen = rcvhdrq_size(uctxt) * uctxt-\u003esubctxt_cnt;\n-\t\tflags |= VM_IO | VM_DONTEXPAND;\n-\t\tvmf = 1;\n+\t\tis_vmalloc = 1;\n \t\tbreak;\n \tcase SUBCTXT_EGRBUF:\n-\t\tmemaddr = (u64)uctxt-\u003esubctxt_rcvegrbuf;\n+\t\tmemvirt = uctxt-\u003esubctxt_rcvegrbuf;\n \t\tmemlen = uctxt-\u003eegrbufs.size * uctxt-\u003esubctxt_cnt;\n-\t\tflags |= VM_IO | VM_DONTEXPAND;\n \t\tflags \u0026= ~VM_MAYWRITE;\n-\t\tvmf = 1;\n+\t\tis_vmalloc = 1;\n \t\tbreak;\n \tcase SDMA_COMP: {\n \t\tstruct hfi1_user_sdma_comp_q *cq = fd-\u003ecq;\n@@ -539,10 +525,9 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t\t\tret = -EFAULT;\n \t\t\tgoto done;\n \t\t}\n-\t\tmemaddr = (u64)cq-\u003ecomps;\n+\t\tmemvirt = cq-\u003ecomps;\n \t\tmemlen = PAGE_ALIGN(sizeof(*cq-\u003ecomps) * cq-\u003enentries);\n-\t\tflags |= VM_IO | VM_DONTEXPAND;\n-\t\tvmf = 1;\n+\t\tis_vmalloc = 1;\n \t\tbreak;\n \t}\n \tdefault:\n@@ -559,12 +544,10 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \t}\n \n \tvm_flags_reset(vma, flags);\n-\tmmap_cdbg(ctxt, subctxt, type, mapio, vmf, memaddr, memvirt, memdma, \n+\tmmap_cdbg(ctxt, subctxt, type, mapio, is_vmalloc, memaddr, memvirt, memdma,\n \t\t  memlen, vma);\n-\tif (vmf) {\n-\t\tvma-\u003evm_pgoff = PFN_DOWN(memaddr);\n-\t\tvma-\u003evm_ops = \u0026vm_ops;\n-\t\tret = 0;\n+\tif (is_vmalloc) {\n+\t\tret = remap_vmalloc_range(vma, memvirt, 0);\n \t} else if (memdma) {\n \t\tret = dma_mmap_coherent(\u0026dd-\u003epcidev-\u003edev, vma,\n \t\t\t\t\tmemvirt, memdma, memlen);\n@@ -588,24 +571,6 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)\n \treturn ret;\n }\n \n-/*\n- * Local (non-chip) user memory is not mapped right away but as it is\n- * accessed by the user-level code.\n- */\n-static vm_fault_t vma_fault(struct vm_fault *vmf)\n-{\n-\tstruct page *page;\n-\n-\tpage = vmalloc_to_page((void *)(vmf-\u003epgoff \u003c\u003c PAGE_SHIFT));\n-\tif (!page)\n-\t\treturn VM_FAULT_SIGBUS;\n-\n-\tget_page(page);\n-\tvmf-\u003epage = page;\n-\n-\treturn 0;\n-}\n-\n static __poll_t hfi1_poll(struct file *fp, struct poll_table_struct *pt)\n {\n \tstruct hfi1_ctxtdata *uctxt;\ndiff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c\nindex 5408f002e6c01..12837b828b89f 100644\n--- a/drivers/scsi/sg.c\n+++ b/drivers/scsi/sg.c\n@@ -1212,85 +1212,72 @@ sg_fasync(int fd, struct file *filp, int mode)\n \treturn fasync_helper(fd, filp, mode, \u0026sfp-\u003easync_qp);\n }\n \n-static vm_fault_t\n-sg_vma_fault(struct vm_fault *vmf)\n+static int sg_discontig_init(void *vm_private_data, void **private)\n {\n-\tstruct vm_area_struct *vma = vmf-\u003evma;\n-\tSg_fd *sfp;\n-\tunsigned long offset, len, sa;\n-\tSg_scatter_hold *rsv_schp;\n-\tint k, length;\n-\n-\tif ((NULL == vma) || (!(sfp = (Sg_fd *) vma-\u003evm_private_data)))\n-\t\treturn VM_FAULT_SIGBUS;\n-\trsv_schp = \u0026sfp-\u003ereserve;\n-\toffset = vmf-\u003epgoff \u003c\u003c PAGE_SHIFT;\n-\tif (offset \u003e= rsv_schp-\u003ebufflen)\n-\t\treturn VM_FAULT_SIGBUS;\n-\tSCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sfp-\u003eparentdp,\n-\t\t\t\t      \"sg_vma_fault: offset=%lu, scatg=%d\\n\",\n-\t\t\t\t      offset, rsv_schp-\u003ek_use_sg));\n-\tsa = vma-\u003evm_start;\n-\tlength = 1 \u003c\u003c (PAGE_SHIFT + rsv_schp-\u003epage_order);\n-\tfor (k = 0; k \u003c rsv_schp-\u003ek_use_sg \u0026\u0026 sa \u003c vma-\u003evm_end; k++) {\n-\t\tlen = vma-\u003evm_end - sa;\n-\t\tlen = (len \u003c length) ? len : length;\n-\t\tif (offset \u003c len) {\n-\t\t\tstruct page *page = rsv_schp-\u003epages[k] + (offset \u003e\u003e PAGE_SHIFT);\n-\t\t\tget_page(page);\t/* increment page count */\n-\t\t\tvmf-\u003epage = page;\n-\t\t\treturn 0; /* success */\n-\t\t}\n-\t\tsa += len;\n-\t\toffset -= len;\n+\tconst unsigned long req_sz = (unsigned long)*private;\n+\tSg_fd *sfp = vm_private_data;\n+\tSg_scatter_hold *rsv_schp = \u0026sfp-\u003ereserve;\n+\tint err = 0;\n+\n+\tmutex_lock(\u0026sfp-\u003ef_mutex);\n+\tif (req_sz \u003e rsv_schp-\u003ebufflen) {\n+\t\terr = -ENOMEM;\t/* cannot map more than reserved buffer */\n+\t\tgoto out;\n+\t}\n+\tsfp-\u003emmap_called = 1; /* Prevents changes to buffer size. */\n+out:\n+\tmutex_unlock(\u0026sfp-\u003ef_mutex);\n+\treturn err;\n+}\n+\n+static int\n+sg_discontig_get(struct discontig_kernel_page_state *state)\n+{\n+\tSg_fd *sfp = state-\u003evm_private_data;\n+\tSg_scatter_hold *rsv_schp = \u0026sfp-\u003ereserve;\n+\tconst unsigned int order = rsv_schp-\u003epage_order;\n+\tconst pgoff_t nr_pages = state-\u003enr_pages_mapped;\n+\n+\tif (nr_pages \u003e= (rsv_schp-\u003ebufflen \u003e\u003e PAGE_SHIFT)) {\n+\t\tdiscontig_kernel_map_abort(state);\n+\t\treturn 0;\n \t}\n \n-\treturn VM_FAULT_SIGBUS;\n+\tSCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sfp-\u003eparentdp,\n+\t\t\t\t      \"sg_discontig_get: offset=%lu, scatg=%d\\n\",\n+\t\t\t\t      nr_pages \u003c\u003c PAGE_SHIFT, rsv_schp-\u003ek_use_sg));\n+\n+\tdiscontig_kernel_map_page(state, rsv_schp-\u003epages[nr_pages \u003e\u003e order]);\n+\treturn 0;\n }\n \n-static const struct vm_operations_struct sg_mmap_vm_ops = {\n-\t.fault = sg_vma_fault,\n+static const struct discontig_kernel_page_ops sg_discontig_ops = {\n+\t.init = sg_discontig_init,\n+\t.get = sg_discontig_get,\n };\n \n static int\n-sg_mmap(struct file *filp, struct vm_area_struct *vma)\n+sg_mmap_prepare(struct vm_area_desc *desc)\n {\n-\tSg_fd *sfp;\n-\tunsigned long req_sz, len, sa;\n-\tSg_scatter_hold *rsv_schp;\n-\tint k, length;\n-\tint ret = 0;\n+\tSg_fd *sfp = desc-\u003efile-\u003eprivate_data;\n+\tconst unsigned long req_sz = vma_desc_size(desc);\n \n-\tif ((!filp) || (!vma) || (!(sfp = (Sg_fd *) filp-\u003eprivate_data)))\n+\tif (!sfp)\n \t\treturn -ENXIO;\n-\treq_sz = vma-\u003evm_end - vma-\u003evm_start;\n+\n \tSCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sfp-\u003eparentdp,\n \t\t\t\t      \"sg_mmap starting, vm_start=%p, len=%d\\n\",\n-\t\t\t\t      (void *) vma-\u003evm_start, (int) req_sz));\n-\tif (vma-\u003evm_pgoff)\n+\t\t\t\t      (void *) desc-\u003estart, (int) req_sz));\n+\n+\tif (desc-\u003epgoff)\n \t\treturn -EINVAL;\t/* want no offset */\n-\trsv_schp = \u0026sfp-\u003ereserve;\n-\tmutex_lock(\u0026sfp-\u003ef_mutex);\n-\tif (req_sz \u003e rsv_schp-\u003ebufflen) {\n-\t\tret = -ENOMEM;\t/* cannot map more than reserved buffer */\n-\t\tgoto out;\n-\t}\n \n-\tsa = vma-\u003evm_start;\n-\tlength = 1 \u003c\u003c (PAGE_SHIFT + rsv_schp-\u003epage_order);\n-\tfor (k = 0; k \u003c rsv_schp-\u003ek_use_sg \u0026\u0026 sa \u003c vma-\u003evm_end; k++) {\n-\t\tlen = vma-\u003evm_end - sa;\n-\t\tlen = (len \u003c length) ? len : length;\n-\t\tsa += len;\n-\t}\n+\tvma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT);\n+\tdesc-\u003eprivate_data = sfp;\n \n-\tsfp-\u003emmap_called = 1;\n-\tvm_flags_set(vma, VM_IO | VM_DONTEXPAND | VM_DONTDUMP);\n-\tvma-\u003evm_private_data = sfp;\n-\tvma-\u003evm_ops = \u0026sg_mmap_vm_ops;\n-out:\n-\tmutex_unlock(\u0026sfp-\u003ef_mutex);\n-\treturn ret;\n+\tmmap_action_map_discontig_kernel_pages(desc, (void *)req_sz,\n+\t\t\t\t\t       \u0026sg_discontig_ops);\n+\treturn 0;\n }\n \n static void\n@@ -1415,7 +1402,7 @@ static const struct file_operations sg_fops = {\n \t.unlocked_ioctl = sg_ioctl,\n \t.compat_ioctl = compat_ptr_ioctl,\n \t.open = sg_open,\n-\t.mmap = sg_mmap,\n+\t.mmap_prepare = sg_mmap_prepare,\n \t.release = sg_release,\n \t.fasync = sg_fasync,\n };\ndiff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c\nindex 687f6a8981f34..9d00b21a8153b 100644\n--- a/drivers/usb/mon/mon_bin.c\n+++ b/drivers/usb/mon/mon_bin.c\n@@ -1219,6 +1219,15 @@ mon_bin_poll(struct file *file, struct poll_table_struct *wait)\n \treturn mask;\n }\n \n+static void __mon_bin_vma_open(struct mon_reader_bin *rp)\n+{\n+\tunsigned long flags;\n+\n+\tspin_lock_irqsave(\u0026rp-\u003eb_lock, flags);\n+\trp-\u003emmap_active++;\n+\tspin_unlock_irqrestore(\u0026rp-\u003eb_lock, flags);\n+}\n+\n /*\n  * open and close: just keep track of how many times the device is\n  * mapped, to use the proper memory allocation function.\n@@ -1226,64 +1235,79 @@ mon_bin_poll(struct file *file, struct poll_table_struct *wait)\n static void mon_bin_vma_open(struct vm_area_struct *vma)\n {\n \tstruct mon_reader_bin *rp = vma-\u003evm_private_data;\n-\tunsigned long flags;\n \n-\tspin_lock_irqsave(\u0026rp-\u003eb_lock, flags);\n-\trp-\u003emmap_active++;\n-\tspin_unlock_irqrestore(\u0026rp-\u003eb_lock, flags);\n+\t__mon_bin_vma_open(rp);\n }\n \n-static void mon_bin_vma_close(struct vm_area_struct *vma)\n+static void __mon_bin_vma_close(struct mon_reader_bin *rp)\n {\n \tunsigned long flags;\n \n-\tstruct mon_reader_bin *rp = vma-\u003evm_private_data;\n \tspin_lock_irqsave(\u0026rp-\u003eb_lock, flags);\n \trp-\u003emmap_active--;\n \tspin_unlock_irqrestore(\u0026rp-\u003eb_lock, flags);\n }\n \n-/*\n- * Map ring pages to user space.\n- */\n-static vm_fault_t mon_bin_vma_fault(struct vm_fault *vmf)\n+static void mon_bin_vma_close(struct vm_area_struct *vma)\n {\n-\tstruct mon_reader_bin *rp = vmf-\u003evma-\u003evm_private_data;\n+\tstruct mon_reader_bin *rp = vma-\u003evm_private_data;\n+\n+\t__mon_bin_vma_close(rp);\n+}\n+\n+static const struct vm_operations_struct mon_bin_vm_ops = {\n+\t.open =     mon_bin_vma_open,\n+\t.close =    mon_bin_vma_close,\n+};\n+\n+static int mon_bin_discontig_init(void *vm_private_data, void **private)\n+{\n+\tstruct mon_reader_bin *rp = vm_private_data;\n+\n+\t/* Dropped by mon_bin_vma_close() on unmap, including on error. */\n+\t__mon_bin_vma_open(rp);\n+\treturn 0;\n+}\n+\n+static int mon_bin_discontig_get(struct discontig_kernel_page_state *state)\n+{\n+\tstruct mon_reader_bin *rp = state-\u003evm_private_data;\n \tunsigned long offset, chunk_idx;\n-\tstruct page *pageptr;\n \tunsigned long flags;\n \n \tspin_lock_irqsave(\u0026rp-\u003eb_lock, flags);\n-\toffset = vmf-\u003epgoff \u003c\u003c PAGE_SHIFT;\n+\n+\toffset = state-\u003epgoff \u003c\u003c PAGE_SHIFT;\n \tif (offset \u003e= rp-\u003eb_size) {\n \t\tspin_unlock_irqrestore(\u0026rp-\u003eb_lock, flags);\n-\t\treturn VM_FAULT_SIGBUS;\n+\t\tdiscontig_kernel_map_abort(state);\n+\t\treturn 0;\n \t}\n \tchunk_idx = offset / CHUNK_SIZE;\n-\tpageptr = rp-\u003eb_vec[chunk_idx].pg;\n-\tget_page(pageptr);\n-\tvmf-\u003epage = pageptr;\n+\tdiscontig_kernel_map_page(state, rp-\u003eb_vec[chunk_idx].pg);\n+\n \tspin_unlock_irqrestore(\u0026rp-\u003eb_lock, flags);\n \treturn 0;\n }\n \n-static const struct vm_operations_struct mon_bin_vm_ops = {\n-\t.open =     mon_bin_vma_open,\n-\t.close =    mon_bin_vma_close,\n-\t.fault =    mon_bin_vma_fault,\n+static const struct discontig_kernel_page_ops mon_discontig_ops = {\n+\t.init = mon_bin_discontig_init,\n+\t.get = mon_bin_discontig_get,\n };\n \n-static int mon_bin_mmap(struct file *filp, struct vm_area_struct *vma)\n+static int mon_bin_mmap_prepare(struct vm_area_desc *desc)\n {\n-\t/* don't do anything here: \"fault\" will set up page table entries */\n-\tvma-\u003evm_ops = \u0026mon_bin_vm_ops;\n+\tconst struct file *filp = desc-\u003efile;\n \n-\tif (vma-\u003evm_flags \u0026 VM_WRITE)\n+\tif (vma_desc_test(desc, VMA_WRITE_BIT))\n \t\treturn -EPERM;\n \n-\tvm_flags_mod(vma, VM_DONTEXPAND | VM_DONTDUMP, VM_MAYWRITE);\n-\tvma-\u003evm_private_data = filp-\u003eprivate_data;\n-\tmon_bin_vma_open(vma);\n+\tdesc-\u003evm_ops = \u0026mon_bin_vm_ops;\n+\tvma_desc_clear_flags(desc, VMA_MAYWRITE_BIT);\n+\tvma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT);\n+\tdesc-\u003eprivate_data = filp-\u003eprivate_data;\n+\n+\tmmap_action_map_discontig_kernel_pages(desc, NULL, \u0026mon_discontig_ops);\n \treturn 0;\n }\n \n@@ -1298,7 +1322,7 @@ static const struct file_operations mon_fops_binary = {\n \t.compat_ioctl =\tmon_bin_compat_ioctl,\n #endif\n \t.release =\tmon_bin_release,\n-\t.mmap =\t\tmon_bin_mmap,\n+\t.mmap_prepare = mon_bin_mmap_prepare,\n };\n \n static int mon_bin_wait_event(struct file *file, struct mon_reader_bin *rp)\ndiff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c\nindex fd00b86e1ae60..fb359ecc39661 100644\n--- a/drivers/video/fbdev/core/fb_defio.c\n+++ b/drivers/video/fbdev/core/fb_defio.c\n@@ -366,13 +366,13 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)\n {\n \tvma-\u003evm_page_prot = pgprot_decrypted(vma-\u003evm_page_prot);\n \n+\tif (WARN_ON_ONCE(!(info-\u003eflags \u0026 FBINFO_VIRTFB)))\n+\t\treturn -EINVAL;\n \tif (!try_module_get(THIS_MODULE))\n \t\treturn -EINVAL;\n \n \tvma-\u003evm_ops = \u0026fb_deferred_io_vm_ops;\n-\tvm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);\n-\tif (!(info-\u003eflags \u0026 FBINFO_VIRTFB))\n-\t\tvm_flags_set(vma, VM_IO);\n+\tvm_flags_set(vma, VM_MIXEDMAP | VM_DONTEXPAND | VM_DONTDUMP);\n \tvma-\u003evm_private_data = info-\u003efbdefio_state;\n \n \tfb_deferred_io_state_get(info-\u003efbdefio_state); /* released in vma-\u003evm_ops-\u003eclose() */\ndiff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c\nindex c4fdecafd8560..958514a354338 100644\n--- a/drivers/video/fbdev/ssd1307fb.c\n+++ b/drivers/video/fbdev/ssd1307fb.c\n@@ -763,6 +763,8 @@ static int ssd1307fb_probe(struct i2c_client *client)\n \tinfo-\u003efix.smem_start = __pa(vmem);\n \tinfo-\u003efix.smem_len = vmem_size;\n \n+\tinfo-\u003eflags = FBINFO_VIRTFB;\n+\n \tfb_deferred_io_init(info);\n \n \ti2c_set_clientdata(client, info);\ndiff --git a/fs/coredump.c b/fs/coredump.c\nindex ac3cd74808c64..9f729c594c47e 100644\n--- a/fs/coredump.c\n+++ b/fs/coredump.c\n@@ -1608,7 +1608,7 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,\n \t}\n \n \t/* Hugetlb memory check */\n-\tif (is_vm_hugetlb_page(vma)) {\n+\tif (vma_is_hugetlb(vma)) {\n \t\tif ((vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026 FILTER(HUGETLB_SHARED))\n \t\t\tgoto whole;\n \t\tif (!(vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026 FILTER(HUGETLB_PRIVATE))\n@@ -1616,8 +1616,8 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,\n \t\treturn 0;\n \t}\n \n-\t/* Do not dump I/O mapped devices or special mappings */\n-\tif (vma-\u003evm_flags \u0026 VM_IO)\n+\t/* Do not dump memory-mapped I/O, which may have side effects on read. */\n+\tif (vma_test(vma, VMA_IO_BIT))\n \t\treturn 0;\n \n \t/* By default, dump shared memory if mapped from an anonymous file. */\ndiff --git a/fs/fuse/dax.c b/fs/fuse/dax.c\nindex 85cdf0199bc0b..a5994f1c637d9 100644\n--- a/fs/fuse/dax.c\n+++ b/fs/fuse/dax.c\n@@ -826,7 +826,7 @@ int fuse_dax_mmap(struct file *file, struct vm_area_struct *vma)\n {\n \tfile_accessed(file);\n \tvma-\u003evm_ops = \u0026fuse_dax_vm_ops;\n-\tvm_flags_set(vma, VM_MIXEDMAP | VM_HUGEPAGE);\n+\tvma_set_flags(vma, VMA_HUGEPAGE_BIT);\n \treturn 0;\n }\n \ndiff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c\nindex 7611a8470ea26..ba7097d5720c0 100644\n--- a/fs/hugetlbfs/inode.c\n+++ b/fs/hugetlbfs/inode.c\n@@ -108,7 +108,7 @@ static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)\n \t * vma address alignment (but not the pgoff alignment) has\n \t * already been checked by prepare_hugepage_range.  If you add\n \t * any error returns here, do so after setting VM_HUGETLB, so\n-\t * is_vm_hugetlb_page tests below unmap_region go the right\n+\t * vma_is_hugetlb tests below unmap_region go the right\n \t * way when do_mmap unwinds (may be important on powerpc\n \t * and ia64).\n \t */\ndiff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c\nindex e671b4fd8dedd..565e6446bd312 100644\n--- a/fs/proc/task_mmu.c\n+++ b/fs/proc/task_mmu.c\n@@ -3015,7 +3015,7 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,\n \t * hugetlb differs, see pagemap_hugetlb_category().\n \t */\n \tcategories = p-\u003ecur_vma_category;\n-\tif (userfaultfd_wp(vma) \u0026\u0026 !is_vm_hugetlb_page(vma))\n+\tif (userfaultfd_wp(vma) \u0026\u0026 !vma_is_hugetlb(vma))\n \t\tcategories |= PAGE_IS_WRITTEN;\n \n \tif (!pagemap_scan_is_interesting_page(categories, p))\n@@ -3028,7 +3028,7 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,\n \tif (~p-\u003earg.flags \u0026 PM_SCAN_WP_MATCHING)\n \t\treturn ret;\n \n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\terr = pagemap_scan_hugetlb_hole_wp(vma, addr, end);\n \telse\n \t\terr = uffd_wp_range(vma, addr, end - addr, true);\n@@ -3470,7 +3470,7 @@ static int show_numa_map(struct seq_file *m, void *v)\n \t\tseq_puts(m, \" stack\");\n \t}\n \n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\tseq_puts(m, \" huge\");\n \n \t/* Skip walking pages if gate VMA */\n@@ -3499,7 +3499,7 @@ static int show_numa_map(struct seq_file *m, void *v)\n \tif (md-\u003eswapcache)\n \t\tseq_printf(m, \" swapcache=%lu\", md-\u003eswapcache);\n \n-\tif (md-\u003eactive \u003c md-\u003epages \u0026\u0026 !is_vm_hugetlb_page(vma))\n+\tif (md-\u003eactive \u003c md-\u003epages \u0026\u0026 !vma_is_hugetlb(vma))\n \t\tseq_printf(m, \" active=%lu\", md-\u003eactive);\n \n \tif (md-\u003ewriteback)\ndiff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h\nindex bdcc2778ac64f..dfb5dd3bec409 100644\n--- a/include/asm-generic/tlb.h\n+++ b/include/asm-generic/tlb.h\n@@ -11,9 +11,9 @@\n #ifndef _ASM_GENERIC__TLB_H\n #define _ASM_GENERIC__TLB_H\n \n+#include \u003clinux/mm.h\u003e\n #include \u003clinux/mmu_notifier.h\u003e\n #include \u003clinux/swap.h\u003e\n-#include \u003clinux/hugetlb_inline.h\u003e\n #include \u003casm/tlbflush.h\u003e\n #include \u003casm/cacheflush.h\u003e\n \n@@ -486,7 +486,7 @@ tlb_update_vma_flags(struct mmu_gather *tlb, struct vm_area_struct *vma)\n \t * We rely on tlb_end_vma() to issue a flush, such that when we reset\n \t * these values the batch is empty.\n \t */\n-\ttlb-\u003evma_huge = is_vm_hugetlb_page(vma);\n+\ttlb-\u003evma_huge = vma_is_hugetlb(vma);\n \ttlb-\u003evma_exec = !!(vma-\u003evm_flags \u0026 VM_EXEC);\n \n \t/*\ndiff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h\nindex 80a5a03e9cee7..24727ece20fe5 100644\n--- a/include/linux/hugetlb.h\n+++ b/include/linux/hugetlb.h\n@@ -7,7 +7,6 @@\n #include \u003clinux/mm_types.h\u003e\n #include \u003clinux/mmdebug.h\u003e\n #include \u003clinux/fs.h\u003e\n-#include \u003clinux/hugetlb_inline.h\u003e\n #include \u003clinux/cgroup.h\u003e\n #include \u003clinux/page_ref.h\u003e\n #include \u003clinux/list.h\u003e\n@@ -252,14 +251,14 @@ extern void __hugetlb_zap_end(struct vm_area_struct *vma,\n static inline void hugetlb_zap_begin(struct vm_area_struct *vma,\n \t\t\t\t     unsigned long *start, unsigned long *end)\n {\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\t__hugetlb_zap_begin(vma, start, end);\n }\n \n static inline void hugetlb_zap_end(struct vm_area_struct *vma,\n \t\t\t\t   struct zap_details *details)\n {\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\t__hugetlb_zap_end(vma, details);\n }\n \ndiff --git a/include/linux/hugetlb_inline.h b/include/linux/hugetlb_inline.h\ndeleted file mode 100644\nindex 5c29cd3223a1e..0000000000000\n--- a/include/linux/hugetlb_inline.h\n+++ /dev/null\n@@ -1,28 +0,0 @@\n-/* SPDX-License-Identifier: GPL-2.0 */\n-#ifndef _LINUX_HUGETLB_INLINE_H\n-#define _LINUX_HUGETLB_INLINE_H\n-\n-#include \u003clinux/mm.h\u003e\n-\n-#ifdef CONFIG_HUGETLB_PAGE\n-\n-static inline bool is_vma_hugetlb_flags(const vma_flags_t *flags)\n-{\n-\treturn vma_flags_test(flags, VMA_HUGETLB_BIT);\n-}\n-\n-#else\n-\n-static inline bool is_vma_hugetlb_flags(const vma_flags_t *flags)\n-{\n-\treturn false;\n-}\n-\n-#endif\n-\n-static inline bool is_vm_hugetlb_page(const struct vm_area_struct *vma)\n-{\n-\treturn is_vma_hugetlb_flags(\u0026vma-\u003eflags);\n-}\n-\n-#endif\ndiff --git a/include/linux/mm.h b/include/linux/mm.h\nindex c49ef99b4413b..1902d4c774817 100644\n--- a/include/linux/mm.h\n+++ b/include/linux/mm.h\n@@ -576,14 +576,6 @@ enum {\n #define VM_ACCESS_FLAGS (VM_READ | VM_WRITE | VM_EXEC)\n #define VMA_ACCESS_FLAGS mk_vma_flags(VMA_READ_BIT, VMA_WRITE_BIT, VMA_EXEC_BIT)\n \n-/*\n- * Special vmas that are non-mergable, non-mlock()able.\n- */\n-\n-#define VMA_SPECIAL_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_DONTEXPAND_BIT, \\\n-\t\t\t\t       VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT)\n-#define VM_SPECIAL vma_flags_to_legacy(VMA_SPECIAL_FLAGS)\n-\n /*\n  * Physically remapped pages are special. Tell the\n  * rest of the world about it:\n@@ -600,9 +592,6 @@ enum {\n #define VMA_REMAP_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_PFNMAP_BIT,\t\\\n \t\t\t\t     VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT)\n \n-/* This mask prevents VMA from being scanned with khugepaged */\n-#define VM_NO_KHUGEPAGED (VM_SPECIAL | VM_HUGETLB)\n-\n /* This mask defines which mm-\u003edef_flags a process can inherit its parent */\n #define VM_INIT_DEF_MASK\tVM_NOHUGEPAGE\n \n@@ -1612,6 +1601,211 @@ static inline bool vma_is_shared_maywrite(const struct vm_area_struct *vma)\n \treturn is_shared_maywrite(\u0026vma-\u003eflags);\n }\n \n+/**\n+ * vma_flags_is_hugetlb() - Do the specified VMA flags indicate that the\n+ * VMA is a hugetlb mapping?\n+ * @flags: The VMA flags to test.\n+ *\n+ * Returns: true if the flags indicate a hugetlb mapping, false otherwise.\n+ */\n+static inline bool vma_flags_is_hugetlb(const vma_flags_t *flags)\n+{\n+\treturn IS_ENABLED(CONFIG_HUGETLB_PAGE) \u0026\u0026\n+\t       vma_flags_test(flags, VMA_HUGETLB_BIT);\n+}\n+\n+/**\n+ * vma_is_hugetlb() - Is @vma a hugetlb mapping?\n+ * @vma: The VMA to test.\n+ *\n+ * Returns: true if @vma is a hugetlb mapping, false otherwise.\n+ */\n+static inline bool vma_is_hugetlb(const struct vm_area_struct *vma)\n+{\n+\treturn vma_flags_is_hugetlb(\u0026vma-\u003eflags);\n+}\n+\n+/**\n+ * vma_flags_is_kernel_owned() - Do the specified VMA flags indicate that the\n+ * contents of the VMA are owned by the kernel rather than the core mm?\n+ * @flags: The VMA flags to test.\n+ *\n+ * A kernel-owned mapping is one whose contents are established and controlled\n+ * by the kernel, typically a driver, rather than by the core mm's fault and\n+ * rmap machinery.\n+ *\n+ * The mapping may be memory-mapped I/O, kernel-allocated pages or ordinary\n+ * pages the owner has chosen to map itself (shmem via a PFN map, for instance).\n+ *\n+ * But in all cases core mm must not populate, reclaim, migrate, Copy-on-Write\n+ * or merge it of its own accord.\n+ *\n+ * The pages mapped, if any, may or may not be reference counted or map counted.\n+ *\n+ * Returns: true if the flags indicate a kernel-owned mapping.\n+ */\n+static inline bool vma_flags_is_kernel_owned(const vma_flags_t *flags)\n+{\n+\treturn vma_flags_test_any(flags, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT);\n+}\n+\n+/**\n+ * vma_is_kernel_owned() - Are the contents of @vma owned by the kernel?\n+ * @vma: The VMA to test.\n+ *\n+ * See vma_flags_is_kernel_owned() for a description of this property.\n+ *\n+ * Returns: true if the VMA is kernel-owned.\n+ */\n+static inline bool vma_is_kernel_owned(const struct vm_area_struct *vma)\n+{\n+\treturn vma_flags_is_kernel_owned(\u0026vma-\u003eflags);\n+}\n+\n+/**\n+ * vma_flags_is_fixed_mapping() - Do the specified VMA flags indicate that this\n+ * is a fixed mapping that cannot be expanded or merged?\n+ * @flags: The VMA flags to test.\n+ *\n+ * Fixed mappings are those whose size is set at the point of mmap (for\n+ * instance, a kernel-owned mapping of a fixed range of memory), and thus\n+ * cannot be expanded or merged.\n+ *\n+ * Returns: true if the flags indicate a fixed mapping.\n+ */\n+static inline bool vma_flags_is_fixed_mapping(const vma_flags_t *flags)\n+{\n+\t/*\n+\t * VMA_PFNMAP_BIT should imply VMA_DONTEXPAND_BIT, but some callers set\n+\t * only the former.\n+\t */\n+\treturn vma_flags_test_any(flags, VMA_PFNMAP_BIT, VMA_DONTEXPAND_BIT);\n+}\n+\n+/**\n+ * vma_is_fixed_mapping() - Is this VMA a fixed mapping that cannot be\n+ * expanded or merged?\n+ * @vma: The VMA to test.\n+ *\n+ * See vma_flags_is_fixed_mapping() for a description of this property.\n+ *\n+ * Returns: true if the VMA maps a fixed mapping.\n+ */\n+static inline bool vma_is_fixed_mapping(const struct vm_area_struct *vma)\n+{\n+\treturn vma_flags_is_fixed_mapping(\u0026vma-\u003eflags);\n+}\n+\n+/**\n+ * vma_flags_can_merge() - Do the specified VMA flags permit the VMA to be\n+ * merged with another?\n+ * @flags: The VMA flags to test.\n+ * Returns: true if the flags permit merging, false otherwise.\n+ */\n+static inline bool vma_flags_can_merge(const vma_flags_t *flags)\n+{\n+\t/*\n+\t * VMA merging assumes that a VMA's flags and fields completely describe\n+\t * its state.\n+\t *\n+\t * However, kernel-owned mappings may have established state upon mapping\n+\t * not embodied in any attribute of the VMA.\n+\t *\n+\t * Additionally, private (CoW) PFN maps encode the source PFN of the\n+\t * range in vma-\u003evm_pgoff, which may otherwise cause spurious merges.\n+\t */\n+\tif (vma_flags_is_kernel_owned(flags))\n+\t\treturn false;\n+\t/* VMA explicitly marked as being unmergeable. */\n+\tif (vma_flags_is_fixed_mapping(flags))\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+/**\n+ * vma_can_merge() - Do @vma's flags permit it to be merged with another VMA?\n+ * @vma: The VMA to test.\n+ * Returns: true if the flags permit merging, otherwise false.\n+ */\n+static inline bool vma_can_merge(const struct vm_area_struct *vma)\n+{\n+\treturn vma_flags_can_merge(\u0026vma-\u003eflags);\n+}\n+\n+/**\n+ * vma_flags_is_persistent() - Do the specified VMA flags imply that the VMA\n+ * contains persistent data?\n+ * @flags: The VMA flags to test.\n+ *\n+ * Persistent in the sense that - if you write bytes to the mapping - do they\n+ * stay written?\n+ *\n+ * If the kernel or a device could write to the memory independently of\n+ * userland, or the kernel could arbitrarily discard it, then it is not\n+ * persistent.\n+ *\n+ * Returns: true if the flags imply this VMA is persistent, otherwise false.\n+ */\n+static inline bool vma_flags_is_persistent(const vma_flags_t *flags)\n+{\n+\t/* hugetlb is a fixed mapping, but its contents are the user's own. */\n+\tif (vma_flags_is_hugetlb(flags))\n+\t\treturn true;\n+\t/*\n+\t * MMIO mappings may not store what is written and may be changed by the\n+\t * device. Kernel-owned and fixed mappings may be changed by their owner\n+\t * without the user having initiated it.\n+\t */\n+\tif (vma_flags_is_kernel_owned(flags) ||\n+\t    vma_flags_is_fixed_mapping(flags))\n+\t\treturn false;\n+\t/* Droppable memory is discardable by definition. */\n+\treturn !vma_flags_test_single_mask(flags, VMA_DROPPABLE);\n+}\n+\n+/**\n+ * vma_is_persistent() - Does the VMA contain persistent data?\n+ * @vma: The VMA to test.\n+ *\n+ * See vma_flags_is_persistent() for details.\n+ *\n+ * Returns: true if the VMA is persistent, otherwise false.\n+ */\n+static inline bool vma_is_persistent(const struct vm_area_struct *vma)\n+{\n+\treturn vma_flags_is_persistent(\u0026vma-\u003eflags);\n+}\n+\n+/**\n+ * vma_flags_can_gup() - Do the specified VMA flags permit GUP to access the\n+ * mapping's pages?\n+ * @flags: The VMA flags to test.\n+ *\n+ * GUP cannot access pages belonging to mappings whose pages are not permitted\n+ * to be accessed (VMA_PFNMAP_BIT) and must not manipulate or provide access to\n+ * memory-mapped I/O ranges to users (VMA_IO_BIT).\n+ *\n+ * Returns: true if GUP may access pages from the mapping, otherwise false.\n+ */\n+static inline bool vma_flags_can_gup(const vma_flags_t *flags)\n+{\n+\treturn !vma_flags_test_any(flags, VMA_IO_BIT, VMA_PFNMAP_BIT);\n+}\n+\n+/**\n+ * vma_can_gup() - May GUP obtain pages from @vma?\n+ * @vma: The VMA to test.\n+ *\n+ * See vma_flags_can_gup() for details.\n+ *\n+ * Returns: true if GUP may access pages from the mapping, otherwise false.\n+ */\n+static inline bool vma_can_gup(const struct vm_area_struct *vma)\n+{\n+\treturn vma_flags_can_gup(\u0026vma-\u003eflags);\n+}\n+\n /**\n  * vma_kernel_pagesize - Default page size granularity for this VMA.\n  * @vma: The user mapping.\n@@ -4602,7 +4796,7 @@ static inline void mmap_action_map_kernel_pages(struct vm_area_desc *desc,\n {\n \tstruct mmap_action *action = \u0026desc-\u003eaction;\n \n-\taction-\u003etype = MMAP_MAP_KERNEL_PAGES;\n+\taction-\u003etype = MMAP_KERNEL_PAGES;\n \taction-\u003emap_kernel.start = start;\n \taction-\u003emap_kernel.pages = pages;\n \taction-\u003emap_kernel.nr_pages = nr_pages;\n@@ -4626,10 +4820,55 @@ static inline void mmap_action_map_kernel_pages_full(struct vm_area_desc *desc,\n \t\t\t\t     vma_desc_pages(desc));\n }\n \n+static inline\n+void mmap_action_map_discontig_kernel_pages(struct vm_area_desc *desc,\n+\t\tvoid *init_private, const struct discontig_kernel_page_ops *ops)\n+{\n+\tstruct mmap_action *action = \u0026desc-\u003eaction;\n+\n+\taction-\u003etype = MMAP_DISCONTIG_KERNEL_PAGES;\n+\taction-\u003emap_kernel_discontig.init_private = init_private;\n+\taction-\u003emap_kernel_discontig.ops = ops;\n+}\n+\n int mmap_action_prepare(struct vm_area_desc *desc);\n int mmap_action_complete(struct vm_area_struct *vma,\n \t\t\t struct mmap_action *action, bool is_compat);\n \n+static inline void\n+discontig_kernel_map_abort(struct discontig_kernel_page_state *state)\n+{\n+\tstate-\u003eaction = DISCONTIG_KERNEL_PAGE_ABORT;\n+}\n+\n+static inline void\n+discontig_kernel_map_page(struct discontig_kernel_page_state *state,\n+\t\t\t  struct page *page)\n+{\n+\tstruct folio *folio = page_folio(page);\n+\n+\tif (folio_test_large(folio)) {\n+\t\tVM_WARN_ON_ONCE(page != folio_page(folio, 0));\n+\t\tstate-\u003eaction = DISCONTIG_KERNEL_PAGE_MAP_COMPOUND_PAGE;\n+\t\tstate-\u003e__folio = folio;\n+\t\tstate-\u003e__nr_pages = min(state-\u003enr_pages_remain,\n+\t\t\t\t\tfolio_nr_pages(folio));\n+\t} else {\n+\t\tstate-\u003eaction = DISCONTIG_KERNEL_PAGE_MAP_PAGE;\n+\t\tstate-\u003e__page = page;\n+\t\tstate-\u003e__nr_pages = 1;\n+\t}\n+}\n+\n+static inline void\n+discontig_kernel_map_page_range(struct discontig_kernel_page_state *state,\n+\t\t\t\tstruct page **page_arr, unsigned long nr_pages)\n+{\n+\tstate-\u003eaction = DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE;\n+\tstate-\u003e__page_arr = page_arr;\n+\tstate-\u003e__nr_pages = nr_pages;\n+}\n+\n /* Look up the first VMA which exactly match the interval vm_start ... vm_end */\n static inline struct vm_area_struct *find_exact_vma(struct mm_struct *mm,\n \t\t\t\tunsigned long vm_start, unsigned long vm_end)\n@@ -4747,9 +4986,6 @@ int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,\n int vm_insert_page(struct vm_area_struct *, unsigned long addr, struct page *);\n int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,\n \t\t\tstruct page **pages, unsigned long *num);\n-int map_kernel_pages_prepare(struct vm_area_desc *desc);\n-int map_kernel_pages_complete(struct vm_area_struct *vma,\n-\t\t\t      struct mmap_action *action);\n int vm_map_pages(struct vm_area_struct *vma, struct page **pages,\n \t\t\t\tunsigned long num);\n int vm_map_pages_zero(struct vm_area_struct *vma, struct page **pages,\ndiff --git a/include/linux/mm_types.h b/include/linux/mm_types.h\nindex 5413bd10fff2c..0cb4f96039568 100644\n--- a/include/linux/mm_types.h\n+++ b/include/linux/mm_types.h\n@@ -815,11 +815,47 @@ struct pfnmap_track_ctx {\n \n /* What action should be taken after an .mmap_prepare call is complete? */\n enum mmap_action_type {\n-\tMMAP_NOTHING,\t\t/* Mapping is complete, no further action. */\n-\tMMAP_REMAP_PFN,\t\t/* Remap PFN range. */\n-\tMMAP_IO_REMAP_PFN,\t/* I/O remap PFN range. */\n-\tMMAP_SIMPLE_IO_REMAP,\t/* I/O remap with guardrails. */\n-\tMMAP_MAP_KERNEL_PAGES,\t/* Map kernel page range from array. */\n+\tMMAP_NOTHING,\n+\tMMAP_REMAP_PFN,\n+\tMMAP_IO_REMAP_PFN,\n+\tMMAP_SIMPLE_IO_REMAP,\t\t/* I/O remap with guardrails. */\n+\tMMAP_KERNEL_PAGES,\t\t/* Map kernel page range from array. */\n+\tMMAP_DISCONTIG_KERNEL_PAGES,\t/* Map kernel discontig page range. */\n+};\n+\n+enum discontig_kernel_page_action {\n+\tDISCONTIG_KERNEL_PAGE_ABORT,\n+\tDISCONTIG_KERNEL_PAGE_MAP_PAGE,\n+\tDISCONTIG_KERNEL_PAGE_MAP_COMPOUND_PAGE,\n+\tDISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE,\n+};\n+\n+struct discontig_kernel_page_state {\n+\t/* Map state. */\n+\tconst unsigned long start;\t/* Start address of VMA. */\n+\tconst unsigned long end;\t/* End address of VMA. */\n+\tunsigned long addr;\t\t/* The current address to be mapped. */\n+\tpgoff_t pgoff;\t\t\t/* The current pgoff to be mapped. */\n+\tunsigned long nr_pages_mapped;\t/* The number of pages mapped. */\n+\tunsigned long nr_pages_remain;\t/* The number of pages remaining. */\n+\n+\t/* User-defined state. */\n+\tvoid *vm_private_data;\t\t/* VMA private data. */\n+\tvoid *private;\t\t\t/* Mapping private data. */\n+\n+\t/* Users should not touch these, use discontig_kernel_map_*() helpers. */\n+\tenum discontig_kernel_page_action action;\n+\tunion {\n+\t\tstruct page *__page;\n+\t\tstruct folio *__folio;\n+\t\tstruct page **__page_arr;\n+\t};\n+\tunsigned long __nr_pages;\n+};\n+\n+struct discontig_kernel_page_ops {\n+\tint (*init)(void *vm_private_data, void **private);\n+\tint (*get)(struct discontig_kernel_page_state *state);\n };\n \n /*\n@@ -844,6 +880,10 @@ struct mmap_action {\n \t\t\tunsigned long nr_pages;\n \t\t\tpgoff_t pgoff;\n \t\t} map_kernel;\n+\t\tstruct {\n+\t\t\tvoid *init_private;\n+\t\t\tconst struct discontig_kernel_page_ops *ops;\n+\t\t} map_kernel_discontig;\n \t};\n \tenum mmap_action_type type;\n \ndiff --git a/include/linux/pagemap.h b/include/linux/pagemap.h\nindex 939f3a5e973f6..d7d8b312466c2 100644\n--- a/include/linux/pagemap.h\n+++ b/include/linux/pagemap.h\n@@ -14,7 +14,6 @@\n #include \u003clinux/gfp.h\u003e\n #include \u003clinux/bitops.h\u003e\n #include \u003clinux/hardirq.h\u003e /* for in_interrupt() */\n-#include \u003clinux/hugetlb_inline.h\u003e\n \n struct folio_batch;\n \ndiff --git a/include/linux/rmap.h b/include/linux/rmap.h\nindex 0b332770abeed..74cca0e3c7264 100644\n--- a/include/linux/rmap.h\n+++ b/include/linux/rmap.h\n@@ -888,7 +888,7 @@ struct page_vma_mapped_walk {\n static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)\n {\n \t/* HugeTLB pte is set to the relevant page table entry without pte_mapped. */\n-\tif (pvmw-\u003epte \u0026\u0026 !is_vm_hugetlb_page(pvmw-\u003evma))\n+\tif (pvmw-\u003epte \u0026\u0026 !vma_is_hugetlb(pvmw-\u003evma))\n \t\tpte_unmap(pvmw-\u003epte);\n \tif (pvmw-\u003eptl)\n \t\tspin_unlock(pvmw-\u003eptl);\ndiff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h\nindex a4351cffc60ce..a14b8a9ffb7b1 100644\n--- a/include/linux/userfaultfd_k.h\n+++ b/include/linux/userfaultfd_k.h\n@@ -18,7 +18,6 @@\n #include \u003clinux/swap.h\u003e\n #include \u003clinux/leafops.h\u003e\n #include \u003casm-generic/pgtable_uffd.h\u003e\n-#include \u003clinux/hugetlb_inline.h\u003e\n \n /* The set of all possible UFFD-related VM flags. */\n #define __VM_UFFD_FLAGS (VM_UFFD_MISSING | VM_UFFD_MINOR | \\\ndiff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c\nindex 7b6847200b431..b69fe5e343393 100644\n--- a/kernel/bpf/arena.c\n+++ b/kernel/bpf/arena.c\n@@ -620,8 +620,9 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)\n \t * clears VM_MAYEXEC. Set VM_DONTEXPAND to avoid potential change\n \t * of user_vm_start. Set VM_DONTCOPY to prevent arena VMA from\n \t * being copied into the child process on fork.\n+\t * This is a kernel page so set VM_MIXEDMAP.\n \t */\n-\tvm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY);\n+\tvm_flags_set(vma, VM_MIXEDMAP | VM_DONTEXPAND | VM_DONTCOPY);\n \tvma-\u003evm_ops = \u0026arena_vm_ops;\n \treturn 0;\n }\ndiff --git a/kernel/events/core.c b/kernel/events/core.c\nindex a6c8e38a31104..8ca8a68429242 100644\n--- a/kernel/events/core.c\n+++ b/kernel/events/core.c\n@@ -9808,7 +9808,7 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event)\n \n \tif (vma-\u003evm_flags \u0026 VM_LOCKED)\n \t\tflags |= MAP_LOCKED;\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\tflags |= MAP_HUGETLB;\n \n \tif (file) {\ndiff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c\nindex 7709ea8824778..b89cc5cee0027 100644\n--- a/kernel/events/uprobes.c\n+++ b/kernel/events/uprobes.c\n@@ -1726,8 +1726,8 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area)\n \t}\n \n \tvma = _install_special_mapping(mm, area-\u003evaddr, PAGE_SIZE,\n-\t\t\t\tVM_EXEC|VM_MAYEXEC|VM_DONTCOPY|VM_IO|\n-\t\t\t\tVM_SEALED_SYSMAP,\n+\t\t\t\tVM_EXEC|VM_MAYEXEC|VM_DONTCOPY|\n+\t\t\t\tVM_MIXEDMAP|VM_SEALED_SYSMAP,\n \t\t\t\t\u0026xol_mapping);\n \tif (IS_ERR(vma)) {\n \t\tret = PTR_ERR(vma);\ndiff --git a/kernel/sched/fair.c b/kernel/sched/fair.c\nindex 8dff37059faf7..ae6c1a606eb5d 100644\n--- a/kernel/sched/fair.c\n+++ b/kernel/sched/fair.c\n@@ -22,7 +22,6 @@\n  */\n #include \u003clinux/energy_model.h\u003e\n #include \u003clinux/mmap_lock.h\u003e\n-#include \u003clinux/hugetlb_inline.h\u003e\n #include \u003clinux/jiffies.h\u003e\n #include \u003clinux/mm_api.h\u003e\n #include \u003clinux/highmem.h\u003e\n@@ -4212,7 +4211,7 @@ static void task_numa_work(struct callback_head *work)\n \n \tfor (; vma; vma = vma_next(\u0026vmi)) {\n \t\tif (!vma_migratable(vma) || !vma_policy_mof(vma) ||\n-\t\t\tis_vm_hugetlb_page(vma) || (vma-\u003evm_flags \u0026 VM_MIXEDMAP)) {\n+\t\t\tvma_is_hugetlb(vma) || vma_is_kernel_owned(vma)) {\n \t\t\ttrace_sched_skip_vma_numa(mm, vma, NUMAB_SKIP_UNSUITABLE);\n \t\t\tcontinue;\n \t\t}\ndiff --git a/mm/folio.c b/mm/folio.c\nindex 50a6dbe55998e..a3f5c463f6654 100644\n--- a/mm/folio.c\n+++ b/mm/folio.c\n@@ -502,7 +502,7 @@ void folio_add_lru_vma(struct folio *folio, struct vm_area_struct *vma)\n {\n \tVM_BUG_ON_FOLIO(folio_test_lru(folio), folio);\n \n-\tif (unlikely((vma-\u003evm_flags \u0026 (VM_LOCKED | VM_SPECIAL)) == VM_LOCKED))\n+\tif (vma_test(vma, VMA_LOCKED_BIT))\n \t\tmlock_new_folio(folio);\n \telse\n \t\tfolio_add_lru(folio);\ndiff --git a/mm/gup.c b/mm/gup.c\nindex a4036c02e2137..f4d0cfcb602bf 100644\n--- a/mm/gup.c\n+++ b/mm/gup.c\n@@ -621,7 +621,7 @@ static struct page *no_page_table(struct vm_area_struct *vma,\n \t * But we can only make this optimization where a hole would surely\n \t * be zero-filled if handle_mm_fault() actually did handle it.\n \t */\n-\tif (is_vm_hugetlb_page(vma)) {\n+\tif (vma_is_hugetlb(vma)) {\n \t\tstruct hstate *h = hstate_vma(vma);\n \n \t\tif (!hugetlbfs_pagecache_present(h, vma, address))\n@@ -1204,7 +1204,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)\n \tint foreign = (gup_flags \u0026 FOLL_REMOTE);\n \tbool vma_anon = vma_is_anonymous(vma);\n \n-\tif (vm_flags \u0026 (VM_IO | VM_PFNMAP))\n+\tif (!vma_can_gup(vma))\n \t\treturn -EFAULT;\n \n \tif ((gup_flags \u0026 FOLL_ANON) \u0026\u0026 !vma_anon)\n@@ -1213,7 +1213,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)\n \tif ((gup_flags \u0026 FOLL_LONGTERM) \u0026\u0026 vma_is_fsdax(vma))\n \t\treturn -EOPNOTSUPP;\n \n-\tif ((gup_flags \u0026 FOLL_SPLIT_PMD) \u0026\u0026 is_vm_hugetlb_page(vma))\n+\tif ((gup_flags \u0026 FOLL_SPLIT_PMD) \u0026\u0026 vma_is_hugetlb(vma))\n \t\treturn -EOPNOTSUPP;\n \n \tif (vma_is_secretmem(vma))\n@@ -1836,6 +1836,10 @@ long populate_vma_page_range(struct vm_area_struct *vma,\n \tif (!vma_is_accessible(vma))\n \t\treturn -EFAULT;\n \n+\t/* Unreadable VMAs also cannot be faulted in. */\n+\tif (!vma_test(vma, VMA_MAYREAD_BIT))\n+\t\treturn -EFAULT;\n+\n \tgup_flags = FOLL_TOUCH;\n \t/*\n \t * We want to touch writable mappings with a write fault in order\n@@ -1951,7 +1955,7 @@ int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)\n \t\t * range with the first VMA. Also, skip undesirable VMA types.\n \t\t */\n \t\tnend = min(end, vma-\u003evm_end);\n-\t\tif (vma-\u003evm_flags \u0026 (VM_IO | VM_PFNMAP))\n+\t\tif (!vma_can_gup(vma))\n \t\t\tcontinue;\n \t\tif (nstart \u003c vma-\u003evm_start)\n \t\t\tnstart = vma-\u003evm_start;\n@@ -2013,8 +2017,7 @@ static long __get_user_pages_locked(struct mm_struct *mm, unsigned long start,\n \t\t\tbreak;\n \n \t\t/* protect what we can, including chardevs */\n-\t\tif ((vma-\u003evm_flags \u0026 (VM_IO | VM_PFNMAP)) ||\n-\t\t    !(vm_flags \u0026 vma-\u003evm_flags))\n+\t\tif (!vma_can_gup(vma) || !(vm_flags \u0026 vma-\u003evm_flags))\n \t\t\tbreak;\n \n \t\tif (pages) {\ndiff --git a/mm/hmm.c b/mm/hmm.c\nindex 2f1e98c6b6440..e9569b82a1f0c 100644\n--- a/mm/hmm.c\n+++ b/mm/hmm.c\n@@ -595,8 +595,7 @@ static int hmm_vma_walk_test(unsigned long start, unsigned long end,\n \tstruct hmm_range *range = hmm_vma_walk-\u003erange;\n \tstruct vm_area_struct *vma = walk-\u003evma;\n \n-\tif (!(vma-\u003evm_flags \u0026 (VM_IO | VM_PFNMAP)) \u0026\u0026\n-\t    vma-\u003evm_flags \u0026 VM_READ)\n+\tif (vma_can_gup(vma) \u0026\u0026 vma_test(vma, VMA_READ_BIT))\n \t\treturn 0;\n \n \t/*\ndiff --git a/mm/huge_memory.c b/mm/huge_memory.c\nindex dd66c6ad5af13..1ec1cd970ce68 100644\n--- a/mm/huge_memory.c\n+++ b/mm/huge_memory.c\n@@ -110,14 +110,6 @@ static inline bool file_thp_enabled(const struct vm_area_struct *vma)\n \treturn S_ISREG(inode-\u003ei_mode);\n }\n \n-/* If returns true, we are unable to access the VMA's folios. */\n-static bool vma_is_special_huge(const struct vm_area_struct *vma)\n-{\n-\tif (vma_is_dax(vma))\n-\t\treturn false;\n-\treturn vma_test_any(vma, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT);\n-}\n-\n static bool vma_file_bypass_thp_tuneables(const struct vm_area_struct *vma,\n \t\tenum tva_type type)\n {\n@@ -192,7 +184,7 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,\n \t/* Check the intersection of requested and supported orders. */\n \tif (vma_is_anonymous(vma))\n \t\tsupported_orders = THP_ORDERS_ALL_ANON;\n-\telse if (vma_is_dax(vma) || vma_is_special_huge(vma))\n+\telse if (vma_is_dax(vma) || vma_is_kernel_owned(vma))\n \t\tsupported_orders = THP_ORDERS_ALL_SPECIAL_DAX;\n \telse\n \t\tsupported_orders = THP_ORDERS_ALL_FILE_DEFAULT;\n@@ -212,11 +204,14 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,\n \t\treturn in_pf ? orders : 0;\n \n \t/*\n-\t * khugepaged special VMA and hugetlb VMA.\n-\t * Must be checked after dax since some dax mappings may have\n-\t * VM_MIXEDMAP set.\n+\t * khugepaged moves data from VMAs once collapsed, after they have been\n+\t * faulted in, relying on refaulting for file-backed memory.\n+\t *\n+\t * Kernel-owned mappings cannot be reliably reconstructed from page\n+\t * faults, and fixed mappings (including hugetlb) may not be marked as\n+\t * kernel-owned - precisely the mappings which cannot be merged.\n \t */\n-\tif (!in_pf \u0026\u0026 !smaps \u0026\u0026 (vm_flags \u0026 VM_NO_KHUGEPAGED))\n+\tif (!in_pf \u0026\u0026 !smaps \u0026\u0026 !vma_can_merge(vma))\n \t\treturn 0;\n \n \t/*\n@@ -3062,7 +3057,7 @@ int zap_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,\n \torig_pud = pudp_huge_get_and_clear_full(vma, addr, pud, tlb-\u003efullmm);\n \tarch_check_zapped_pud(vma, orig_pud);\n \ttlb_remove_pud_tlb_entry(tlb, pud, addr);\n-\tif (vma_is_special_huge(vma)) {\n+\tif (vma_is_kernel_owned(vma)) {\n \t\tspin_unlock(ptl);\n \t\t/* No zero page support yet */\n \t} else {\n@@ -3218,7 +3213,7 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\n \t\t */\n \t\tif (arch_needs_pgtable_deposit())\n \t\t\tzap_deposited_table(mm, pmd);\n-\t\tif (vma_is_special_huge(vma))\n+\t\tif (vma_is_kernel_owned(vma))\n \t\t\treturn;\n \t\tif (unlikely(pmd_is_migration_entry(old_pmd))) {\n \t\t\tconst softleaf_t old_entry = softleaf_from_pmd(old_pmd);\n@@ -4747,11 +4742,9 @@ static inline bool vma_not_suitable_for_thp_split(struct vm_area_struct *vma)\n {\n \tif (vma_is_dax(vma))\n \t\treturn true;\n-\tif (vma_is_special_huge(vma))\n-\t\treturn true;\n-\tif (vma_test(vma, VMA_IO_BIT))\n+\tif (vma_is_kernel_owned(vma))\n \t\treturn true;\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\treturn true;\n \n \treturn false;\ndiff --git a/mm/hugetlb.c b/mm/hugetlb.c\nindex a69bd463b1aef..d93235491cbca 100644\n--- a/mm/hugetlb.c\n+++ b/mm/hugetlb.c\n@@ -1146,7 +1146,7 @@ static inline struct resv_map *inode_resv_map(struct inode *inode)\n \n static struct resv_map *vma_resv_map(struct vm_area_struct *vma)\n {\n-\tVM_BUG_ON_VMA(!is_vm_hugetlb_page(vma), vma);\n+\tVM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);\n \tif (vma-\u003evm_flags \u0026 VM_MAYSHARE) {\n \t\tstruct address_space *mapping = vma-\u003evm_file-\u003ef_mapping;\n \t\tstruct inode *inode = mapping-\u003ehost;\n@@ -1161,7 +1161,7 @@ static struct resv_map *vma_resv_map(struct vm_area_struct *vma)\n \n static void set_vma_resv_map(struct vm_area_struct *vma, struct resv_map *map)\n {\n-\tVM_WARN_ON_ONCE_VMA(!is_vm_hugetlb_page(vma), vma);\n+\tVM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);\n \tVM_WARN_ON_ONCE_VMA(vma_test(vma, VMA_MAYSHARE_BIT), vma);\n \n \tset_vma_private_data(vma, (unsigned long)map);\n@@ -1169,7 +1169,7 @@ static void set_vma_resv_map(struct vm_area_struct *vma, struct resv_map *map)\n \n static void set_vma_resv_flags(struct vm_area_struct *vma, unsigned long flags)\n {\n-\tVM_WARN_ON_ONCE_VMA(!is_vm_hugetlb_page(vma), vma);\n+\tVM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);\n \tVM_WARN_ON_ONCE_VMA(vma_test(vma, VMA_MAYSHARE_BIT), vma);\n \n \tset_vma_private_data(vma, get_vma_private_data(vma) | flags);\n@@ -1177,7 +1177,7 @@ static void set_vma_resv_flags(struct vm_area_struct *vma, unsigned long flags)\n \n static int is_vma_resv_set(struct vm_area_struct *vma, unsigned long flag)\n {\n-\tVM_BUG_ON_VMA(!is_vm_hugetlb_page(vma), vma);\n+\tVM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);\n \n \treturn (get_vma_private_data(vma) \u0026 flag) != 0;\n }\n@@ -1191,7 +1191,7 @@ bool __vma_private_lock(struct vm_area_struct *vma)\n \n void hugetlb_dup_vma_private(struct vm_area_struct *vma)\n {\n-\tVM_BUG_ON_VMA(!is_vm_hugetlb_page(vma), vma);\n+\tVM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);\n \t/*\n \t * Clear vm_private_data\n \t * - For shared mappings this is a per-vma semaphore that may be\n@@ -5269,7 +5269,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,\n \tunsigned long last_addr_mask;\n \n \ti_mmap_assert_write_locked(vma-\u003evm_file-\u003ef_mapping);\n-\tWARN_ON(!is_vm_hugetlb_page(vma));\n+\tWARN_ON(!vma_is_hugetlb(vma));\n \tBUG_ON(start \u0026 ~huge_page_mask(h));\n \tBUG_ON(end \u0026 ~huge_page_mask(h));\n \n@@ -7495,6 +7495,6 @@ void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)\n  */\n void fixup_hugetlb_reservations(struct vm_area_struct *vma)\n {\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\tclear_vma_resv_huge_pages(vma);\n }\ndiff --git a/mm/internal.h b/mm/internal.h\nindex da14c56fb24e1..6c004037913b3 100644\n--- a/mm/internal.h\n+++ b/mm/internal.h\n@@ -212,6 +212,24 @@ static inline void *folio_raw_mapping(const struct folio *folio)\n \treturn (void *)(mapping \u0026 ~FOLIO_MAPPING_FLAGS);\n }\n \n+/*\n+ * If the VMA has a close hook then close it, and since closing it might leave\n+ * it in an inconsistent state which makes the use of any hooks suspect, clear\n+ * them down by installing dummy empty hooks.\n+ */\n+static inline void vma_close(struct vm_area_struct *vma)\n+{\n+\tif (vma-\u003evm_ops \u0026\u0026 vma-\u003evm_ops-\u003eclose) {\n+\t\tvma-\u003evm_ops-\u003eclose(vma);\n+\n+\t\t/*\n+\t\t * The mapping is in an inconsistent state, and no further hooks\n+\t\t * may be invoked upon it.\n+\t\t */\n+\t\tvma-\u003evm_ops = \u0026vma_dummy_vm_ops;\n+\t}\n+}\n+\n /*\n  * This is a file-backed mapping, and is about to be memory mapped - invoke its\n  * mmap hook and safely handle error conditions. On error, VMA hooks will be\n@@ -224,8 +242,11 @@ static inline void *folio_raw_mapping(const struct folio *folio)\n  */\n static inline int mmap_file(struct file *file, struct vm_area_struct *vma)\n {\n-\tint err = vfs_mmap(file, vma);\n+\tconst unsigned long prev_start = vma-\u003evm_start;\n+\tconst vma_flags_t prev_flags = vma-\u003eflags;\n+\tint err;\n \n+\terr = vfs_mmap(file, vma);\n \t/*\n \t * Either we tried to call the file hook for mmap() and an error arose\n \t * or a driver set vma-\u003evm_ops = NULL intending there to be no VMA\n@@ -238,26 +259,14 @@ static inline int mmap_file(struct file *file, struct vm_area_struct *vma)\n \t */\n \tif (unlikely(err || !vma-\u003evm_ops))\n \t\tvma-\u003evm_ops = \u0026vma_dummy_vm_ops;\n+\tif (unlikely(err))\n+\t\treturn err;\n \n-\treturn err;\n-}\n-\n-/*\n- * If the VMA has a close hook then close it, and since closing it might leave\n- * it in an inconsistent state which makes the use of any hooks suspect, clear\n- * them down by installing dummy empty hooks.\n- */\n-static inline void vma_close(struct vm_area_struct *vma)\n-{\n-\tif (vma-\u003evm_ops \u0026\u0026 vma-\u003evm_ops-\u003eclose) {\n-\t\tvma-\u003evm_ops-\u003eclose(vma);\n+\terr = mmap_hook_validate(prev_start, \u0026prev_flags, vma);\n+\tif (unlikely(err))\n+\t\tvma_close(vma);\n \n-\t\t/*\n-\t\t * The mapping is in an inconsistent state, and no further hooks\n-\t\t * may be invoked upon it.\n-\t\t */\n-\t\tvma-\u003evm_ops = \u0026vma_dummy_vm_ops;\n-\t}\n+\treturn err;\n }\n \n /* unmap_vmas is in mm/memory.c */\n@@ -966,15 +975,7 @@ void mlock_folio(struct folio *folio);\n static inline void mlock_vma_folio(struct folio *folio,\n \t\t\t\tstruct vm_area_struct *vma)\n {\n-\t/*\n-\t * The VM_SPECIAL check here serves two purposes.\n-\t * 1) VM_IO check prevents migration from double-counting during mlock.\n-\t * 2) Although mmap_region() and mlock_fixup() take care that VM_LOCKED\n-\t *    is never left set on a VM_SPECIAL vma, there is an interval while\n-\t *    file-\u003ef_op-\u003emmap() is using vm_insert_page(s), when VM_LOCKED may\n-\t *    still be set while VM_SPECIAL bits are added: so ignore it then.\n-\t */\n-\tif (unlikely((vma-\u003evm_flags \u0026 (VM_LOCKED|VM_SPECIAL)) == VM_LOCKED))\n+\tif (vma_test(vma, VMA_LOCKED_BIT))\n \t\tmlock_folio(folio);\n }\n \n@@ -991,7 +992,7 @@ static inline void munlock_vma_folio(struct folio *folio,\n \t * always munlock the folio and page reclaim will correct it\n \t * if it's wrong.\n \t */\n-\tif (unlikely(vma-\u003evm_flags \u0026 VM_LOCKED))\n+\tif (unlikely(vma_test(vma, VMA_LOCKED_BIT)))\n \t\tmunlock_folio(folio);\n }\n \n@@ -1111,11 +1112,9 @@ static inline struct file *maybe_unlock_mmap_for_io(struct vm_fault *vmf,\n \n static inline bool vma_supports_mlock(const struct vm_area_struct *vma)\n {\n-\tif (vma_test_any_mask(vma, VMA_SPECIAL_FLAGS))\n-\t\treturn false;\n-\tif (vma_test_single_mask(vma, VMA_DROPPABLE))\n+\tif (!vma_is_persistent(vma))\n \t\treturn false;\n-\tif (vma_is_dax(vma) || is_vm_hugetlb_page(vma))\n+\tif (vma_is_dax(vma) || vma_is_hugetlb(vma))\n \t\treturn false;\n \treturn vma != get_gate_vma(current-\u003emm);\n }\n@@ -1508,6 +1507,12 @@ int remap_pfn_range_prepare(struct vm_area_desc *desc);\n int remap_pfn_range_complete(struct vm_area_struct *vma,\n \t\t\t     struct mmap_action *action);\n int simple_ioremap_prepare(struct vm_area_desc *desc);\n+int map_kernel_pages_prepare(struct vm_area_desc *desc);\n+int map_kernel_pages_complete(struct vm_area_struct *vma,\n+\t\t\t      struct mmap_action *action);\n+int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc);\n+int map_discontig_kernel_pages_complete(struct vm_area_struct *vma,\n+\t\t\t\t\tstruct mmap_action *action);\n \n static inline int io_remap_pfn_range_prepare(struct vm_area_desc *desc)\n {\ndiff --git a/mm/ksm.c b/mm/ksm.c\nindex 624f37975e129..f80372bfd4b2f 100644\n--- a/mm/ksm.c\n+++ b/mm/ksm.c\n@@ -747,9 +747,7 @@ static bool ksm_compatible(const struct file *file, vma_flags_t vma_flags)\n \tif (vma_flags_test_any(\u0026vma_flags, VMA_SHARED_BIT, VMA_MAYSHARE_BIT,\n \t\t\t       VMA_HUGETLB_BIT))\n \t\treturn false;\n-\tif (vma_flags_test_single_mask(\u0026vma_flags, VMA_DROPPABLE))\n-\t\treturn false;\n-\tif (vma_flags_test_any_mask(\u0026vma_flags, VMA_SPECIAL_FLAGS))\n+\tif (!vma_flags_is_persistent(\u0026vma_flags))\n \t\treturn false;\n \tif (file_is_dax(file))\n \t\treturn false;\ndiff --git a/mm/madvise.c b/mm/madvise.c\nindex 73c2901b9adbf..f805a4876c875 100644\n--- a/mm/madvise.c\n+++ b/mm/madvise.c\n@@ -880,7 +880,7 @@ bool madvise_dontneed_free_valid_vma(struct madvise_behavior *madv_behavior)\n \tint behavior = madv_behavior-\u003ebehavior;\n \tstruct madvise_behavior_range *range = \u0026madv_behavior-\u003erange;\n \n-\tif (!is_vm_hugetlb_page(vma)) {\n+\tif (!vma_is_hugetlb(vma)) {\n \t\tunsigned int forbidden = VM_PFNMAP;\n \n \t\tif (behavior != MADV_DONTNEED_LOCKED)\n@@ -1055,19 +1055,25 @@ static long madvise_remove(struct madvise_behavior *madv_behavior)\n \treturn error;\n }\n \n-static bool is_valid_guard_vma(struct vm_area_struct *vma, bool allow_locked)\n+static bool is_valid_guard_vma(const struct vm_area_struct *vma,\n+\t\t\t       bool allow_locked)\n {\n-\tvm_flags_t disallowed = VM_SPECIAL | VM_HUGETLB;\n-\n \t/*\n-\t * A user could lock after setting a guard range but that's fine, as\n+\t * A user could lock after setting a guard range but that's fine as\n \t * they'd not be able to fault in. The issue arises when we try to zap\n \t * existing locked VMAs. We don't want to do that.\n \t */\n-\tif (!allow_locked)\n-\t\tdisallowed |= VM_LOCKED;\n+\tif (!allow_locked \u0026\u0026 vma_test(vma, VMA_LOCKED_BIT))\n+\t\treturn false;\n+\t/*\n+\t * Guard regions require a VMA whose page tables are managed solely by\n+\t * the core, which is also what merging requires, so disallow any flags\n+\t * that would prevent a merge.\n+\t */\n+\tif (!vma_can_merge(vma))\n+\t\treturn false;\n \n-\treturn !(vma-\u003evm_flags \u0026 disallowed);\n+\treturn true;\n }\n \n static bool is_guard_pte_marker(pte_t ptent)\n@@ -1394,7 +1400,7 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)\n \t\tnew_flags |= VM_DONTCOPY;\n \t\tbreak;\n \tcase MADV_DOFORK:\n-\t\tif (new_flags \u0026 VM_SPECIAL)\n+\t\tif (!vma_can_merge(vma))\n \t\t\treturn -EINVAL;\n \t\tnew_flags \u0026= ~VM_DONTCOPY;\n \t\tbreak;\n@@ -1413,8 +1419,8 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)\n \t\tnew_flags |= VM_DONTDUMP;\n \t\tbreak;\n \tcase MADV_DODUMP:\n-\t\tif ((!is_vm_hugetlb_page(vma) \u0026\u0026 (new_flags \u0026 VM_SPECIAL)) ||\n-\t\t    (new_flags \u0026 VM_DROPPABLE))\n+\t\t/* Non-persistent memory cannot be dumped. */\n+\t\tif (!vma_is_persistent(vma))\n \t\t\treturn -EINVAL;\n \t\tnew_flags \u0026= ~VM_DONTDUMP;\n \t\tbreak;\ndiff --git a/mm/memory.c b/mm/memory.c\nindex ec63dd6212ac5..cb56d67b17ca3 100644\n--- a/mm/memory.c\n+++ b/mm/memory.c\n@@ -1564,7 +1564,7 @@ copy_page_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma)\n \tif (!vma_needs_copy(dst_vma, src_vma))\n \t\treturn 0;\n \n-\tif (is_vm_hugetlb_page(src_vma))\n+\tif (vma_is_hugetlb(src_vma))\n \t\treturn copy_hugetlb_page_range(dst_mm, src_mm, dst_vma, src_vma);\n \n \t/*\n@@ -2178,7 +2178,7 @@ static void __zap_vma_range(struct mmu_gather *tlb, struct vm_area_struct *vma,\n \tif (vma-\u003evm_file \u0026\u0026 !reaping)\n \t\tuprobe_munmap(vma, start, end);\n \n-\tif (unlikely(is_vm_hugetlb_page(vma))) {\n+\tif (unlikely(vma_is_hugetlb(vma))) {\n \t\tzap_flags_t zap_flags = details ? details-\u003ezap_flags : 0;\n \n \t\tVM_WARN_ON_ONCE(reaping);\n@@ -2313,7 +2313,7 @@ void zap_vma_range_batched(struct mmu_gather *tlb,\n \t */\n \t__zap_vma_range(tlb, vma, address, end, details);\n \tmmu_notifier_invalidate_range_end(\u0026range);\n-\tif (is_vm_hugetlb_page(vma)) {\n+\tif (vma_is_hugetlb(vma)) {\n \t\t/*\n \t\t * flush tlb and free resources before hugetlb_zap_end(), to\n \t\t * avoid concurrent page faults' allocation failure.\n@@ -2343,19 +2343,19 @@ void zap_vma_range(struct vm_area_struct *vma, unsigned long address,\n }\n \n /**\n- * zap_special_vma_range - zap all page table entries in a special vma range\n+ * zap_special_vma_range - zap all page table entries in a kernel-owned VMA\n  * @vma: the vma covering the range to zap\n  * @address: starting address of the range to zap\n  * @size: number of bytes to zap\n  *\n  * This function does nothing when the provided address range is not fully\n- * contained in @vma, or when the @vma is not VM_PFNMAP or VM_MIXEDMAP.\n+ * contained in @vma, or when @vma is not kernel-owned.\n  */\n void zap_special_vma_range(struct vm_area_struct *vma, unsigned long address,\n \t\tunsigned long size)\n {\n \tif (!range_in_vma(vma, address, address + size) ||\n-\t   !(vma-\u003evm_flags \u0026 (VM_PFNMAP | VM_MIXEDMAP)))\n+\t   !vma_is_kernel_owned(vma))\n \t\treturn;\n \n \tzap_vma_range(vma, address, size);\n@@ -2417,11 +2417,11 @@ static bool vm_mixed_zeropage_allowed(struct vm_area_struct *vma)\n \t * be problematic as soon as the zeropage gets replaced by a different\n \t * page due to vma-\u003evm_ops-\u003epfn_mkwrite, because what's mapped would\n \t * now differ to what GUP looked up. FSDAX is incompatible to\n-\t * FOLL_LONGTERM and VM_IO is incompatible to GUP completely (see\n-\t * check_vma_flags).\n+\t * FOLL_LONGTERM and memory-mapped I/O is incompatible to GUP completely\n+\t * (see vma_can_gup()).\n \t */\n \treturn vma-\u003evm_ops \u0026\u0026 vma-\u003evm_ops-\u003epfn_mkwrite \u0026\u0026\n-\t       (vma_is_fsdax(vma) || vma-\u003evm_flags \u0026 VM_IO);\n+\t       (vma_is_fsdax(vma) || vma_test(vma, VMA_IO_BIT));\n }\n \n static int validate_page_before_insert(struct vm_area_struct *vma,\n@@ -2609,17 +2609,23 @@ int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,\n }\n EXPORT_SYMBOL(vm_insert_pages);\n \n+static void __map_kernel_pages_prepare(struct vm_area_desc *desc)\n+{\n+\tif (vma_desc_test(desc, VMA_MIXEDMAP_BIT))\n+\t\treturn;\n+\n+\tVM_WARN_ON_ONCE(mmap_read_trylock(desc-\u003emm));\n+\tVM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));\n+\tvma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);\n+}\n+\n int map_kernel_pages_prepare(struct vm_area_desc *desc)\n {\n \tconst struct mmap_action *action = \u0026desc-\u003eaction;\n \tconst unsigned long addr = action-\u003emap_kernel.start;\n \tunsigned long nr_pages, end;\n \n-\tif (!vma_desc_test(desc, VMA_MIXEDMAP_BIT)) {\n-\t\tVM_WARN_ON_ONCE(mmap_read_trylock(desc-\u003emm));\n-\t\tVM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));\n-\t\tvma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);\n-\t}\n+\t__map_kernel_pages_prepare(desc);\n \n \tnr_pages = action-\u003emap_kernel.nr_pages;\n \tend = addr + PAGE_SIZE * nr_pages;\n@@ -2628,7 +2634,6 @@ int map_kernel_pages_prepare(struct vm_area_desc *desc)\n \n \treturn 0;\n }\n-EXPORT_SYMBOL(map_kernel_pages_prepare);\n \n int map_kernel_pages_complete(struct vm_area_struct *vma,\n \t\t\t      struct mmap_action *action)\n@@ -2640,7 +2645,98 @@ int map_kernel_pages_complete(struct vm_area_struct *vma,\n \t\t\t    action-\u003emap_kernel.pages,\n \t\t\t    \u0026nr_pages, vma-\u003evm_page_prot);\n }\n-EXPORT_SYMBOL(map_kernel_pages_complete);\n+\n+int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc)\n+{\n+\tconst struct mmap_action *action = \u0026desc-\u003eaction;\n+\tconst struct discontig_kernel_page_ops *ops =\n+\t\taction-\u003emap_kernel_discontig.ops;\n+\n+\t/* At minimum need to be able to get pages. */\n+\tif (WARN_ON_ONCE(!ops-\u003eget))\n+\t\treturn -EINVAL;\n+\n+\t__map_kernel_pages_prepare(desc);\n+\treturn 0;\n+}\n+\n+static int apply_discontig_action(struct vm_area_struct *vma,\n+\t\t\t\t  struct discontig_kernel_page_state *state)\n+{\n+\tunsigned long nr_pages = state-\u003e__nr_pages;\n+\tunsigned long addr = state-\u003eaddr;\n+\tunsigned long i;\n+\n+\tif (state-\u003eaction == DISCONTIG_KERNEL_PAGE_MAP_PAGE)\n+\t\treturn insert_page(vma, addr, state-\u003e__page,\n+\t\t\t\t   vma-\u003evm_page_prot, /*mkwrite=*/false);\n+\tif (state-\u003eaction == DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE)\n+\t\treturn insert_pages(vma, addr, state-\u003e__page_arr,\n+\t\t\t\t    \u0026nr_pages, vma-\u003evm_page_prot);\n+\n+\t/* Compound folio - have to iterate through each page. */\n+\tfor (i = 0; i \u003c nr_pages; i++, addr += PAGE_SIZE) {\n+\t\tstruct page *page = folio_page(state-\u003e__folio, i);\n+\t\tint err;\n+\n+\t\terr = insert_page(vma, addr, page, vma-\u003evm_page_prot,\n+\t\t\t\t  /*mkwrite=*/false);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\treturn 0;\n+}\n+\n+int map_discontig_kernel_pages_complete(struct vm_area_struct *vma,\n+\t\t\t\t\tstruct mmap_action *action)\n+{\n+\tconst struct discontig_kernel_page_ops *ops =\n+\t\taction-\u003emap_kernel_discontig.ops;\n+\tstruct discontig_kernel_page_state state = {\n+\t\t.start = vma-\u003evm_start,\n+\t\t.end = vma-\u003evm_end,\n+\t\t.addr = vma-\u003evm_start,\n+\t\t.pgoff = vma-\u003evm_pgoff,\n+\t\t.nr_pages_mapped = 0,\n+\t\t.nr_pages_remain = vma_pages(vma),\n+\t\t.vm_private_data = vma-\u003evm_private_data,\n+\t\t.private = action-\u003emap_kernel_discontig.init_private,\n+\t};\n+\tint err = 0;\n+\n+\tif (ops-\u003einit)\n+\t\terr = ops-\u003einit(vma-\u003evm_private_data, \u0026state.private);\n+\tif (err)\n+\t\treturn err;\n+\n+\tdo {\n+\t\tunsigned long end, pgoff_end;\n+\t\tunsigned long nr_pages;\n+\n+\t\t/* Default to abort. */\n+\t\tstate.action = DISCONTIG_KERNEL_PAGE_ABORT;\n+\t\terr = ops-\u003eget(\u0026state);\n+\t\tif (err || state.action == DISCONTIG_KERNEL_PAGE_ABORT)\n+\t\t\treturn err;\n+\t\tnr_pages = state.__nr_pages;\n+\n+\t\tend = state.addr + PAGE_SIZE * nr_pages;\n+\t\tif (end \u003e vma-\u003evm_end)\n+\t\t\treturn -EINVAL;\n+\t\tpgoff_end = state.pgoff + nr_pages;\n+\n+\t\terr = apply_discontig_action(vma, \u0026state);\n+\t\tif (err)\n+\t\t\treturn err;\n+\n+\t\tstate.addr = end;\n+\t\tstate.pgoff = pgoff_end;\n+\t\tstate.nr_pages_mapped += nr_pages;\n+\t\tstate.nr_pages_remain -= nr_pages;\n+\t} while (state.addr \u003c vma-\u003evm_end);\n+\n+\treturn 0;\n+}\n \n /**\n  * vm_insert_page - insert single page into user vma\n@@ -6837,7 +6933,7 @@ vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,\n \n \tlru_gen_enter_fault(vma);\n \n-\tif (unlikely(is_vm_hugetlb_page(vma)))\n+\tif (unlikely(vma_is_hugetlb(vma)))\n \t\tret = hugetlb_fault(vma-\u003evm_mm, vma, address, flags);\n \telse\n \t\tret = __handle_mm_fault(vma, address, flags);\n@@ -7020,7 +7116,8 @@ int follow_pfnmap_start(struct follow_pfnmap_args *args)\n \tif (unlikely(address \u003c vma-\u003evm_start || address \u003e= vma-\u003evm_end))\n \t\tgoto out;\n \n-\tif (!(vma-\u003evm_flags \u0026 (VM_IO | VM_PFNMAP)))\n+\t/* Only mappings GUP cannot handle are followed here. */\n+\tif (vma_can_gup(vma))\n \t\tgoto out;\n retry:\n \tpgdp = pgd_offset(mm, address);\n@@ -7214,8 +7311,9 @@ static int __access_remote_vm(struct mm_struct *mm, unsigned long addr,\n \t\t\t}\n \n \t\t\t/*\n-\t\t\t * Check if this is a VM_IO | VM_PFNMAP VMA, which\n-\t\t\t * we can access using slightly different code.\n+\t\t\t * GUP failed, perhaps because this is a mapping it\n+\t\t\t * cannot handle (see vma_can_gup()) - such mappings may\n+\t\t\t * provide access via vm_ops-\u003eaccess() instead.\n \t\t\t */\n \t\t\tbytes = 0;\n #ifdef CONFIG_HAVE_IOREMAP_PROT\n@@ -7701,12 +7799,12 @@ void ptlock_free(struct ptdesc *ptdesc)\n \n void vma_pgtable_walk_begin(struct vm_area_struct *vma)\n {\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\thugetlb_vma_lock_read(vma);\n }\n \n void vma_pgtable_walk_end(struct vm_area_struct *vma)\n {\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\thugetlb_vma_unlock_read(vma);\n }\ndiff --git a/mm/mempolicy.c b/mm/mempolicy.c\nindex 2ad0a5f18280a..ed444061631c9 100644\n--- a/mm/mempolicy.c\n+++ b/mm/mempolicy.c\n@@ -2011,7 +2011,8 @@ SYSCALL_DEFINE5(get_mempolicy, int __user *, policy,\n \n bool vma_migratable(struct vm_area_struct *vma)\n {\n-\tif (vma-\u003evm_flags \u0026 (VM_IO | VM_PFNMAP))\n+\t/* Pages which GUP cannot obtain cannot be migrated either. */\n+\tif (!vma_can_gup(vma))\n \t\treturn false;\n \n \t/*\n@@ -2021,7 +2022,7 @@ bool vma_migratable(struct vm_area_struct *vma)\n \tif (vma_is_dax(vma))\n \t\treturn false;\n \n-\tif (is_vm_hugetlb_page(vma) \u0026\u0026\n+\tif (vma_is_hugetlb(vma) \u0026\u0026\n \t\t!hugepage_migration_supported(hstate_vma(vma)))\n \t\treturn false;\n \ndiff --git a/mm/migrate_device.c b/mm/migrate_device.c\nindex 0c437004329d9..b74c0ae427682 100644\n--- a/mm/migrate_device.c\n+++ b/mm/migrate_device.c\n@@ -739,19 +739,21 @@ static void migrate_vma_unmap(struct migrate_vma *migrate)\n  */\n int migrate_vma_setup(struct migrate_vma *args)\n {\n+\tconst struct vm_area_struct *vma = args-\u003evma;\n \tlong nr_pages = (args-\u003eend - args-\u003estart) \u003e\u003e PAGE_SHIFT;\n \n \targs-\u003estart \u0026= PAGE_MASK;\n \targs-\u003eend \u0026= PAGE_MASK;\n-\tif (!args-\u003evma || is_vm_hugetlb_page(args-\u003evma) ||\n-\t    (args-\u003evma-\u003evm_flags \u0026 VM_SPECIAL) || vma_is_dax(args-\u003evma))\n+\tif (!vma)\n+\t\treturn -EINVAL;\n+\tif (vma_is_kernel_owned(vma) || vma_is_fixed_mapping(vma) ||\n+\t    vma_is_dax(vma))\n \t\treturn -EINVAL;\n \tif (nr_pages \u003c= 0)\n \t\treturn -EINVAL;\n-\tif (args-\u003estart \u003c args-\u003evma-\u003evm_start ||\n-\t    args-\u003estart \u003e= args-\u003evma-\u003evm_end)\n+\tif (args-\u003estart \u003c vma-\u003evm_start || args-\u003estart \u003e= vma-\u003evm_end)\n \t\treturn -EINVAL;\n-\tif (args-\u003eend \u003c= args-\u003evma-\u003evm_start || args-\u003eend \u003e args-\u003evma-\u003evm_end)\n+\tif (args-\u003eend \u003c= vma-\u003evm_start || args-\u003eend \u003e vma-\u003evm_end)\n \t\treturn -EINVAL;\n \tif (!args-\u003esrc || !args-\u003edst)\n \t\treturn -EINVAL;\ndiff --git a/mm/mlock.c b/mm/mlock.c\nindex 39215a3eab1fb..4235a1518fc9e 100644\n--- a/mm/mlock.c\n+++ b/mm/mlock.c\n@@ -316,22 +316,10 @@ static inline unsigned int folio_mlock_step(struct folio *folio,\n \treturn folio_pte_batch(folio, pte, ptent, count);\n }\n \n-static inline bool allow_mlock_munlock(struct folio *folio,\n+static inline bool allow_mlock(struct folio *folio,\n \t\tstruct vm_area_struct *vma, unsigned long start,\n \t\tunsigned long end, unsigned int step)\n {\n-\t/*\n-\t * For unlock, allow munlock large folio which is partially\n-\t * mapped to VMA. As it's possible that large folio is\n-\t * mlocked and VMA is split later.\n-\t *\n-\t * During memory pressure, such kind of large folio can\n-\t * be split. And the pages are not in VM_LOCKed VMA\n-\t * can be reclaimed.\n-\t */\n-\tif (!vma_test(vma, VMA_LOCKED_BIT))\n-\t\treturn true;\n-\n \t/* folio_within_range() cannot take KSM, but any small folio is OK */\n \tif (!folio_test_large(folio))\n \t\treturn true;\n@@ -352,6 +340,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,\n \n {\n \tstruct vm_area_struct *vma = walk-\u003evma;\n+\tconst bool lock = walk-\u003eprivate;\n \tspinlock_t *ptl;\n \tpte_t *start_pte, *pte;\n \tpte_t ptent;\n@@ -368,7 +357,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,\n \t\tfolio = pmd_folio(*pmd);\n \t\tif (folio_is_zone_device(folio))\n \t\t\tgoto out;\n-\t\tif (vma_test(vma, VMA_LOCKED_BIT))\n+\t\tif (lock)\n \t\t\tmlock_folio(folio);\n \t\telse\n \t\t\tmunlock_folio(folio);\n@@ -390,10 +379,10 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,\n \t\t\tcontinue;\n \n \t\tstep = folio_mlock_step(folio, pte, addr, end);\n-\t\tif (!allow_mlock_munlock(folio, vma, start, end, step))\n+\t\tif (lock \u0026\u0026 !allow_mlock(folio, vma, start, end, step))\n \t\t\tgoto next_entry;\n \n-\t\tif (vma_test(vma, VMA_LOCKED_BIT))\n+\t\tif (lock)\n \t\t\tmlock_folio(folio);\n \t\telse\n \t\t\tmunlock_folio(folio);\n@@ -428,31 +417,29 @@ static void mlock_vma_pages_range(struct vm_area_struct *vma,\n \t\t.pmd_entry = mlock_pte_range,\n \t\t.walk_lock = PGWALK_WRLOCK_VERIFY,\n \t};\n+\tconst bool lock = vma_flags_test(new_vma_flags, VMA_LOCKED_BIT);\n+\tvma_flags_t walk_flags = *new_vma_flags;\n \n \t/*\n-\t * There is a slight chance that concurrent page migration,\n-\t * or page reclaim finding a page of this now-VMA_LOCKED_BIT vma,\n-\t * will call mlock_vma_folio() and raise page's mlock_count:\n-\t * double counting, leaving the page unevictable indefinitely.\n-\t * Communicate this danger to mlock_vma_folio() with VMA_IO_BIT,\n-\t * which is a VMA_SPECIAL_FLAGS flag not allowed on VMA_LOCKED_BIT vmas.\n-\t * mmap_lock is held in write mode here, so this weird\n-\t * combination should not be visible to other mmap_lock users;\n-\t * but WRITE_ONCE so rmap walkers must see VMA_IO_BIT if VMA_LOCKED_BIT.\n+\t * LOCKONFAULT without LOCKED never otherwise occurs: it marks a walk in\n+\t * progress so that rmap-side callers, which test VMA_LOCKED_BIT, do not\n+\t * count folios, while try_to_unmap_one(), which tests VMA_LOCKED_MASK,\n+\t * still refuses to unmap them.\n \t */\n-\tif (vma_flags_test(new_vma_flags, VMA_LOCKED_BIT))\n-\t\tvma_flags_set(new_vma_flags, VMA_IO_BIT);\n+\tif (lock) {\n+\t\tvma_flags_clear(\u0026walk_flags, VMA_LOCKED_BIT);\n+\t\tvma_flags_set(\u0026walk_flags, VMA_LOCKONFAULT_BIT);\n+\t}\n+\n \tvma_start_write(vma);\n-\tvma_flags_reset_once(vma, new_vma_flags);\n+\tvma_flags_reset_once(vma, \u0026walk_flags);\n \n \tlru_add_drain();\n-\twalk_page_range_vma(vma, start, end, \u0026mlock_walk_ops, NULL);\n+\twalk_page_range_vma(vma, start, end, \u0026mlock_walk_ops, (void *)lock);\n \tlru_add_drain();\n \n-\tif (vma_flags_test(new_vma_flags, VMA_IO_BIT)) {\n-\t\tvma_flags_clear(new_vma_flags, VMA_IO_BIT);\n+\tif (lock)\n \t\tvma_flags_reset_once(vma, new_vma_flags);\n-\t}\n }\n \n /*\ndiff --git a/mm/mmap.c b/mm/mmap.c\nindex 4bf26b0f1e6e3..98449f364af1c 100644\n--- a/mm/mmap.c\n+++ b/mm/mmap.c\n@@ -1786,7 +1786,7 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)\n \t\t/*\n \t\t * Copy/update hugetlb private vma information.\n \t\t */\n-\t\tif (is_vm_hugetlb_page(tmp))\n+\t\tif (vma_is_hugetlb(tmp))\n \t\t\thugetlb_dup_vma_private(tmp);\n \n \t\t/*\ndiff --git a/mm/mmu_gather.c b/mm/mmu_gather.c\nindex 3985d856de7f9..506f005adbdc0 100644\n--- a/mm/mmu_gather.c\n+++ b/mm/mmu_gather.c\n@@ -500,7 +500,7 @@ void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma)\n {\n \ttlb_gather_mmu(tlb, vma-\u003evm_mm);\n \ttlb_update_vma_flags(tlb, vma);\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\t/* All entries have the same size. */\n \t\ttlb_change_page_size(tlb, huge_page_size(hstate_vma(vma)));\n }\ndiff --git a/mm/mprotect.c b/mm/mprotect.c\nindex 2888ee638d872..a1b6d29bf0390 100644\n--- a/mm/mprotect.c\n+++ b/mm/mprotect.c\n@@ -717,7 +717,7 @@ long change_protection(struct mmu_gather *tlb,\n \t    (cp_flags \u0026 MM_CP_UFFD_RWP))\n \t\tnewprot = PAGE_NONE;\n \n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\tpages = hugetlb_change_protection(vma, start, end, newprot,\n \t\t\t\t\t\t  cp_flags);\n \telse\n@@ -783,8 +783,7 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,\n \t * uncommon case, so doesn't need to be very optimized.\n \t */\n \tif (arch_has_pfn_modify_check() \u0026\u0026\n-\t    vma_flags_test_any(\u0026old_vma_flags, VMA_PFNMAP_BIT,\n-\t\t\t       VMA_MIXEDMAP_BIT) \u0026\u0026\n+\t    vma_flags_is_kernel_owned(\u0026old_vma_flags) \u0026\u0026\n \t    !vma_flags_test_any_mask(\u0026new_vma_flags, VMA_ACCESS_FLAGS)) {\n \t\tpgprot_t new_pgprot = vm_get_page_prot(newflags);\n \ndiff --git a/mm/mremap.c b/mm/mremap.c\nindex 7c368440fafe2..1122282a1d6ab 100644\n--- a/mm/mremap.c\n+++ b/mm/mremap.c\n@@ -812,7 +812,7 @@ unsigned long move_page_tables(struct pagetable_move_control *pmc)\n \tif (!pmc-\u003elen_in)\n \t\treturn 0;\n \n-\tif (is_vm_hugetlb_page(pmc-\u003eold))\n+\tif (vma_is_hugetlb(pmc-\u003eold))\n \t\treturn move_hugetlb_page_tables(pmc-\u003eold, pmc-\u003enew, pmc-\u003eold_addr,\n \t\t\t\t\t\tpmc-\u003enew_addr, pmc-\u003elen_in);\n \n@@ -1735,7 +1735,7 @@ static bool vma_multi_allowed(struct vm_area_struct *vma)\n \t/* Known good. */\n \tif (vma_is_shmem(vma))\n \t\treturn true;\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\treturn true;\n \tif (file-\u003ef_op-\u003eget_unmapped_area == thp_get_unmapped_area)\n \t\treturn true;\n@@ -1758,7 +1758,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)\n \t\treturn -EPERM;\n \n \t/* Align to hugetlb page size, if required. */\n-\tif (is_vm_hugetlb_page(vma) \u0026\u0026 !align_hugetlb(vrm))\n+\tif (vma_is_hugetlb(vma) \u0026\u0026 !align_hugetlb(vrm))\n \t\treturn -EINVAL;\n \n \tvrm_set_delta(vrm);\n@@ -1788,8 +1788,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)\n \t\treturn -EINVAL;\n \t}\n \n-\tif ((vrm-\u003eflags \u0026 MREMAP_DONTUNMAP) \u0026\u0026\n-\t    vma_test_any(vma, VMA_DONTEXPAND_BIT, VMA_PFNMAP_BIT))\n+\tif ((vrm-\u003eflags \u0026 MREMAP_DONTUNMAP) \u0026\u0026 vma_is_fixed_mapping(vma))\n \t\treturn -EINVAL;\n \n \t/*\n@@ -1827,7 +1826,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)\n \tif (pgoff + (new_len \u003e\u003e PAGE_SHIFT) \u003c pgoff)\n \t\treturn -EINVAL;\n \n-\tif (vma_test_any(vma, VMA_DONTEXPAND_BIT, VMA_PFNMAP_BIT))\n+\tif (vma_is_fixed_mapping(vma))\n \t\treturn -EFAULT;\n \n \tif (!mlock_future_ok(mm, vma_test(vma, VMA_LOCKED_BIT), vrm-\u003edelta))\ndiff --git a/mm/page_vma_mapped.c b/mm/page_vma_mapped.c\nindex 28e306fdb3a5b..8408aee7571b5 100644\n--- a/mm/page_vma_mapped.c\n+++ b/mm/page_vma_mapped.c\n@@ -109,7 +109,7 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw, unsigned long pte_nr)\n \tunsigned long pfn;\n \tpte_t ptent;\n \n-\tif (is_vm_hugetlb_page(pvmw-\u003evma))\n+\tif (vma_is_hugetlb(pvmw-\u003evma))\n \t\tptent = huge_ptep_get(pvmw-\u003evma-\u003evm_mm, pvmw-\u003eaddress,\n \t\t\t\t      pvmw-\u003epte);\n \telse\n@@ -206,7 +206,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)\n \tif (pvmw-\u003epmd \u0026\u0026 !pvmw-\u003epte)\n \t\treturn not_found(pvmw);\n \n-\tif (unlikely(is_vm_hugetlb_page(vma))) {\n+\tif (unlikely(vma_is_hugetlb(vma))) {\n \t\tstruct hstate *hstate = hstate_vma(vma);\n \t\tunsigned long size = huge_page_size(hstate);\n \t\t/* The only possible mapping was handled on last iteration */\ndiff --git a/mm/pagewalk.c b/mm/pagewalk.c\nindex 7411702a37f58..e6493bbe6919e 100644\n--- a/mm/pagewalk.c\n+++ b/mm/pagewalk.c\n@@ -408,7 +408,7 @@ static int __walk_page_range(unsigned long start, unsigned long end,\n \tint err = 0;\n \tstruct vm_area_struct *vma = walk-\u003evma;\n \tconst struct mm_walk_ops *ops = walk-\u003eops;\n-\tbool is_hugetlb = is_vm_hugetlb_page(vma);\n+\tbool is_hugetlb = vma_is_hugetlb(vma);\n \n \t/* We do not support hugetlb PTE installation. */\n \tif (ops-\u003einstall_pte \u0026\u0026 is_hugetlb)\ndiff --git a/mm/rmap.c b/mm/rmap.c\nindex 5fefe5b060b1c..120c894d2ddec 100644\n--- a/mm/rmap.c\n+++ b/mm/rmap.c\n@@ -2239,9 +2239,11 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,\n \n \t\t/*\n \t\t * If the folio is in an mlock()d vma, we must not swap it out.\n+\t\t * VMA_LOCKONFAULT_BIT alone marks an mlock walk in progress, see\n+\t\t * mlock_vma_pages_range().\n \t\t */\n \t\tif (!(flags \u0026 TTU_IGNORE_MLOCK) \u0026\u0026\n-\t\t    (vma-\u003evm_flags \u0026 VM_LOCKED)) {\n+\t\t    vma_test_any_mask(vma, VMA_LOCKED_MASK)) {\n \t\t\tptes++;\n \n \t\t\t/*\ndiff --git a/mm/swapfile.c b/mm/swapfile.c\nindex 01e7b6b046b67..f90f029bfd5cf 100644\n--- a/mm/swapfile.c\n+++ b/mm/swapfile.c\n@@ -2705,7 +2705,7 @@ static int unuse_mm(struct mm_struct *mm, unsigned int type)\n \tif (check_stable_address_space(mm))\n \t\tgoto unlock;\n \tfor_each_vma(vmi, vma) {\n-\t\tif (vma-\u003eanon_vma \u0026\u0026 !is_vm_hugetlb_page(vma)) {\n+\t\tif (vma-\u003eanon_vma \u0026\u0026 !vma_is_hugetlb(vma)) {\n \t\t\tret = unuse_vma(vma, type);\n \t\t\tif (ret)\n \t\t\t\tbreak;\ndiff --git a/mm/userfaultfd.c b/mm/userfaultfd.c\nindex 79cc7b546f130..ddf0a4a3d3997 100644\n--- a/mm/userfaultfd.c\n+++ b/mm/userfaultfd.c\n@@ -237,7 +237,7 @@ static int mfill_get_vma(struct mfill_state *state)\n \tif ((flags \u0026 MFILL_ATOMIC_WP) \u0026\u0026 !(dst_vma-\u003evm_flags \u0026 VM_UFFD_WP))\n \t\tgoto out_unlock;\n \n-\tif (is_vm_hugetlb_page(dst_vma))\n+\tif (vma_is_hugetlb(dst_vma))\n \t\treturn 0;\n \n \tops = vma_uffd_ops(dst_vma);\n@@ -804,7 +804,7 @@ static __always_inline ssize_t mfill_atomic_hugetlb(\n \t\t}\n \n \t\terr = -ENOENT;\n-\t\tif (!is_vm_hugetlb_page(dst_vma))\n+\t\tif (!vma_is_hugetlb(dst_vma))\n \t\t\tgoto out_unlock_vma;\n \n \t\terr = -EINVAL;\n@@ -967,7 +967,7 @@ static __always_inline ssize_t mfill_atomic(struct userfaultfd_ctx *ctx,\n \t/*\n \t * If this is a HUGETLB vma, pass off to appropriate routine\n \t */\n-\tif (is_vm_hugetlb_page(state.vma))\n+\tif (vma_is_hugetlb(state.vma))\n \t\treturn  mfill_atomic_hugetlb(ctx, state.vma, dst_start,\n \t\t\t\t\t     src_start, len, flags);\n \n@@ -1114,7 +1114,7 @@ static int mwriteprotect_range(struct userfaultfd_ctx *ctx, unsigned long start,\n \t\t\tbreak;\n \t\t}\n \n-\t\tif (is_vm_hugetlb_page(dst_vma)) {\n+\t\tif (vma_is_hugetlb(dst_vma)) {\n \t\t\terr = -EINVAL;\n \t\t\tpage_mask = vma_kernel_pagesize(dst_vma) - 1;\n \t\t\tif ((start \u0026 page_mask) || (len \u0026 page_mask))\n@@ -1172,7 +1172,7 @@ int mrwprotect_range(struct userfaultfd_ctx *ctx, unsigned long start,\n \t\tif (!userfaultfd_rwp(dst_vma))\n \t\t\treturn -ENOENT;\n \n-\t\tif (is_vm_hugetlb_page(dst_vma)) {\n+\t\tif (vma_is_hugetlb(dst_vma)) {\n \t\t\tunsigned long page_mask;\n \n \t\t\tpage_mask = vma_kernel_pagesize(dst_vma) - 1;\n@@ -1754,10 +1754,18 @@ static inline bool move_splits_huge_pmd(unsigned long dst_addr,\n }\n #endif\n \n-static inline bool vma_move_compatible(struct vm_area_struct *vma)\n+static inline bool vma_move_compatible(const struct vm_area_struct *vma)\n {\n-\treturn !(vma-\u003evm_flags \u0026 (VM_PFNMAP | VM_IO |  VM_HUGETLB |\n-\t\t\t\t  VM_MIXEDMAP | VM_SHADOW_STACK));\n+\t/* uffd is generally incompatible with kernel-owned mappings. */\n+\tif (vma_is_kernel_owned(vma))\n+\t\treturn false;\n+\t/* The shadow stack should not be written to by userspace. */\n+\tif (vma_test_single_mask(vma, VMA_SHADOW_STACK))\n+\t\treturn false;\n+\t/* hugetlb mappings cannot be safely moved. */\n+\tif (vma_is_hugetlb(vma))\n+\t\treturn false;\n+\treturn true;\n }\n \n static int validate_move_areas(struct userfaultfd_ctx *ctx,\n@@ -2146,10 +2154,11 @@ static bool vma_can_userfault(struct vm_area_struct *vma, vm_flags_t vm_flags,\n {\n \tconst struct vm_uffd_ops *ops = vma_uffd_ops(vma);\n \n-\tif (vma-\u003evm_flags \u0026 (VM_DROPPABLE | VM_SHADOW_STACK))\n+\t/* Non-persistent memory is inherently not controllable by userspace. */\n+\tif (!vma_is_persistent(vma))\n \t\treturn false;\n-\n-\tif (!is_vm_hugetlb_page(vma) \u0026\u0026 (vma-\u003evm_flags \u0026 VM_SPECIAL))\n+\t/* The shadow stack should not be written to by userspace. */\n+\tif (vma_test_single_mask(vma, VMA_SHADOW_STACK))\n \t\treturn false;\n \n \tvm_flags \u0026= __VM_UFFD_FLAGS;\n@@ -2319,7 +2328,7 @@ static int userfaultfd_register_range(struct userfaultfd_ctx *ctx,\n \t\t */\n \t\tuserfaultfd_set_ctx(vma, ctx, vm_flags);\n \n-\t\tif (is_vm_hugetlb_page(vma) \u0026\u0026 uffd_disable_huge_pmd_share(vma))\n+\t\tif (vma_is_hugetlb(vma) \u0026\u0026 uffd_disable_huge_pmd_share(vma))\n \t\t\thugetlb_unshare_all_pmds(vma);\n \n skip:\n@@ -2895,7 +2904,7 @@ vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)\n \t * (sleepable) vma lock can modify the current task state, that\n \t * must be before explicitly calling set_current_state().\n \t */\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\thugetlb_vma_lock_read(vma);\n \n \tspin_lock_irq(\u0026ctx-\u003efault_pending_wqh.lock);\n@@ -2912,7 +2921,7 @@ vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)\n \tset_current_state(blocking_state);\n \tspin_unlock_irq(\u0026ctx-\u003efault_pending_wqh.lock);\n \n-\tif (is_vm_hugetlb_page(vma)) {\n+\tif (vma_is_hugetlb(vma)) {\n \t\tmust_wait = userfaultfd_huge_must_wait(ctx, vmf, reason);\n \t\thugetlb_vma_unlock_read(vma);\n \t} else {\n@@ -3744,7 +3753,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,\n \t * If the first vma contains huge pages, make sure start address\n \t * is aligned to huge page size.\n \t */\n-\tif (is_vm_hugetlb_page(vma)) {\n+\tif (vma_is_hugetlb(vma)) {\n \t\tunsigned long vma_hpagesize = vma_kernel_pagesize(vma);\n \n \t\tif (start \u0026 (vma_hpagesize - 1))\n@@ -3795,7 +3804,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,\n \t\t * If this vma contains ending address, and huge pages\n \t\t * check alignment.\n \t\t */\n-\t\tif (is_vm_hugetlb_page(cur) \u0026\u0026 end \u003c= cur-\u003evm_end \u0026\u0026\n+\t\tif (vma_is_hugetlb(cur) \u0026\u0026 end \u003c= cur-\u003evm_end \u0026\u0026\n \t\t    end \u003e cur-\u003evm_start) {\n \t\t\tunsigned long vma_hpagesize = vma_kernel_pagesize(cur);\n \n@@ -3831,7 +3840,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,\n \t\t/*\n \t\t * Note vmas containing huge pages\n \t\t */\n-\t\tif (is_vm_hugetlb_page(cur))\n+\t\tif (vma_is_hugetlb(cur))\n \t\t\tbasic_ioctls = true;\n \n \t\tfound = true;\n@@ -3917,7 +3926,7 @@ static int userfaultfd_unregister(struct userfaultfd_ctx *ctx,\n \t * If the first vma contains huge pages, make sure start address\n \t * is aligned to huge page size.\n \t */\n-\tif (is_vm_hugetlb_page(vma)) {\n+\tif (vma_is_hugetlb(vma)) {\n \t\tunsigned long vma_hpagesize = vma_kernel_pagesize(vma);\n \n \t\tif (start \u0026 (vma_hpagesize - 1))\ndiff --git a/mm/util.c b/mm/util.c\nindex bf0513d1d3d08..5a1916d8fdc10 100644\n--- a/mm/util.c\n+++ b/mm/util.c\n@@ -1224,10 +1224,17 @@ EXPORT_SYMBOL(compat_set_desc_from_vma);\n int __compat_vma_mmap(struct vm_area_desc *desc,\n \t\t      struct vm_area_struct *vma)\n {\n+\tstruct vm_area_desc prev_desc;\n \tint err;\n \n+\t/* Derive state prior to mmap_prepare hook. */\n+\tcompat_set_desc_from_vma(\u0026prev_desc, desc-\u003efile, vma);\n \t/* Perform any preparatory tasks for mmap action. */\n \terr = mmap_action_prepare(desc);\n+\tif (err)\n+\t\treturn err;\n+\t/* Check the caller did nothing crazy. */\n+\terr = mmap_prepare_validate(\u0026prev_desc, desc);\n \tif (err)\n \t\treturn err;\n \t/* Update the VMA from the descriptor. */\n@@ -1455,8 +1462,10 @@ int mmap_action_prepare(struct vm_area_desc *desc)\n \t\treturn io_remap_pfn_range_prepare(desc);\n \tcase MMAP_SIMPLE_IO_REMAP:\n \t\treturn simple_ioremap_prepare(desc);\n-\tcase MMAP_MAP_KERNEL_PAGES:\n+\tcase MMAP_KERNEL_PAGES:\n \t\treturn map_kernel_pages_prepare(desc);\n+\tcase MMAP_DISCONTIG_KERNEL_PAGES:\n+\t\treturn map_discontig_kernel_pages_prepare(desc);\n \t}\n \n \tWARN_ON_ONCE(1);\n@@ -1486,9 +1495,12 @@ int mmap_action_complete(struct vm_area_struct *vma,\n \tcase MMAP_REMAP_PFN:\n \t\terr = remap_pfn_range_complete(vma, action);\n \t\tbreak;\n-\tcase MMAP_MAP_KERNEL_PAGES:\n+\tcase MMAP_KERNEL_PAGES:\n \t\terr = map_kernel_pages_complete(vma, action);\n \t\tbreak;\n+\tcase MMAP_DISCONTIG_KERNEL_PAGES:\n+\t\terr = map_discontig_kernel_pages_complete(vma, action);\n+\t\tbreak;\n \tcase MMAP_IO_REMAP_PFN:\n \tcase MMAP_SIMPLE_IO_REMAP:\n \t\t/* Should have been delegated. */\n@@ -1509,7 +1521,8 @@ int mmap_action_prepare(struct vm_area_desc *desc)\n \tcase MMAP_REMAP_PFN:\n \tcase MMAP_IO_REMAP_PFN:\n \tcase MMAP_SIMPLE_IO_REMAP:\n-\tcase MMAP_MAP_KERNEL_PAGES:\n+\tcase MMAP_KERNEL_PAGES:\n+\tcase MMAP_DISCONTIG_KERNEL_PAGES:\n \t\tWARN_ON_ONCE(1); /* nommu cannot handle these. */\n \t\tbreak;\n \t}\n@@ -1530,7 +1543,8 @@ int mmap_action_complete(struct vm_area_struct *vma,\n \tcase MMAP_REMAP_PFN:\n \tcase MMAP_IO_REMAP_PFN:\n \tcase MMAP_SIMPLE_IO_REMAP:\n-\tcase MMAP_MAP_KERNEL_PAGES:\n+\tcase MMAP_KERNEL_PAGES:\n+\tcase MMAP_DISCONTIG_KERNEL_PAGES:\n \t\tWARN_ON_ONCE(1); /* nommu cannot handle this. */\n \n \t\terr = -EINVAL;\ndiff --git a/mm/vma.c b/mm/vma.c\nindex 97567fb7ef33d..ab570e0a7f16c 100644\n--- a/mm/vma.c\n+++ b/mm/vma.c\n@@ -599,7 +599,7 @@ __split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,\n \t * boundary.\n \t */\n \tvma_adjust_trans_huge(vma, vma-\u003evm_start, addr, NULL);\n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\thugetlb_split(vma, addr);\n \n \tif (new_below) {\n@@ -924,13 +924,14 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(\n \n \tvmg-\u003estate = VMA_MERGE_NOMERGE;\n \n+\tif (!vma_flags_can_merge(\u0026vmg-\u003evma_flags))\n+\t\treturn NULL;\n \t/*\n-\t * If a special mapping or if the range being modified is neither at the\n-\t * furthermost left or right side of the VMA, then we have no chance of\n-\t * merging and should abort.\n+\t * If the range being modified is neither at the furthermost left or\n+\t * right side of the VMA, then we have no chance of merging and should\n+\t * abort.\n \t */\n-\tif (vma_flags_test_any_mask(\u0026vmg-\u003evma_flags, VMA_SPECIAL_FLAGS) ||\n-\t    (!left_side \u0026\u0026 !right_side))\n+\tif (!left_side \u0026\u0026 !right_side)\n \t\treturn NULL;\n \n \tif (left_side)\n@@ -1152,9 +1153,11 @@ struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg)\n \n \tvmg-\u003estate = VMA_MERGE_NOMERGE;\n \n-\t/* Special VMAs are unmergeable, also if no prev/next. */\n-\tif (vma_flags_test_any_mask(\u0026vmg-\u003evma_flags, VMA_SPECIAL_FLAGS) ||\n-\t    (!prev \u0026\u0026 !next))\n+\tif (!vma_flags_can_merge(\u0026vmg-\u003evma_flags))\n+\t\treturn NULL;\n+\n+\t/* VMAs with no prev/next are unmergeable. */\n+\tif (!prev \u0026\u0026 !next)\n \t\treturn NULL;\n \n \tcan_merge_left = can_vma_merge_left(vmg);\n@@ -2225,7 +2228,7 @@ bool vma_wants_writenotify(struct vm_area_struct *vma, pgprot_t vm_page_prot)\n \t * Do we need to track softdirty? hugetlb does not support softdirty\n \t * tracking yet.\n \t */\n-\tif (vma_soft_dirty_enabled(vma) \u0026\u0026 !is_vm_hugetlb_page(vma))\n+\tif (vma_soft_dirty_enabled(vma) \u0026\u0026 !vma_is_hugetlb(vma))\n \t\treturn true;\n \n \t/* Do we need write faults for uffd-wp tracking? */\n@@ -2344,7 +2347,7 @@ int mm_take_all_locks(struct mm_struct *mm)\n \t\tif (signal_pending(current))\n \t\t\tgoto out_unlock;\n \t\tif (vma-\u003evm_file \u0026\u0026 vma-\u003evm_file-\u003ef_mapping \u0026\u0026\n-\t\t\t\tis_vm_hugetlb_page(vma))\n+\t\t\t\tvma_is_hugetlb(vma))\n \t\t\tvm_lock_mapping(mm, vma-\u003evm_file-\u003ef_mapping);\n \t}\n \n@@ -2353,7 +2356,7 @@ int mm_take_all_locks(struct mm_struct *mm)\n \t\tif (signal_pending(current))\n \t\t\tgoto out_unlock;\n \t\tif (vma-\u003evm_file \u0026\u0026 vma-\u003evm_file-\u003ef_mapping \u0026\u0026\n-\t\t\t\t!is_vm_hugetlb_page(vma))\n+\t\t\t\t!vma_is_hugetlb(vma))\n \t\t\tvm_lock_mapping(mm, vma-\u003evm_file-\u003ef_mapping);\n \t}\n \n@@ -2578,7 +2581,6 @@ static int __mmap_setup(struct mmap_state *map, struct vm_area_desc *desc,\n \treturn 0;\n }\n \n-\n static int __mmap_new_file_vma(struct mmap_state *map,\n \t\t\t       struct vm_area_struct *vma)\n {\n@@ -2592,6 +2594,11 @@ static int __mmap_new_file_vma(struct mmap_state *map,\n \tif (!map-\u003efile-\u003ef_op-\u003emmap)\n \t\treturn 0;\n \n+\t/*\n+\t * Driver-specified flags may make the lock flags invalid, so clear\n+\t * VMA_LOCKED_MASK and reinstate it afterwards if appropriate.\n+\t */\n+\tvma_clear_flags_mask(vma, VMA_LOCKED_MASK);\n \terror = mmap_file(vma-\u003evm_file, vma);\n \tif (error) {\n \t\tUNMAP_STATE(unmap, vmi, vma, vma-\u003evm_start, vma-\u003evm_end,\n@@ -2605,15 +2612,14 @@ static int __mmap_new_file_vma(struct mmap_state *map,\n \t\treturn error;\n \t}\n \n-\t/* Drivers cannot alter the address of the VMA. */\n-\tWARN_ON_ONCE(map-\u003eaddr != vma-\u003evm_start);\n-\t/*\n-\t * Drivers should not permit writability when previously it was\n-\t * disallowed.\n-\t */\n-\tVM_WARN_ON_ONCE(!vma_flags_same_pair(\u0026map-\u003evma_flags, \u0026vma-\u003eflags) \u0026\u0026\n-\t\t\t!vma_flags_test(\u0026map-\u003evma_flags, VMA_MAYWRITE_BIT) \u0026\u0026\n-\t\t\tvma_test(vma, VMA_MAYWRITE_BIT));\n+\t/* If VMA flags still valid for locked mask, reinstate. */\n+\tif (vma_supports_mlock(vma)) {\n+\t\tconst vma_flags_t mask =\n+\t\t\tvma_flags_and_mask(\u0026map-\u003evma_flags,\n+\t\t\t\t\t   VMA_LOCKED_MASK);\n+\n+\t\tvma_set_flags_mask(vma, mask);\n+\t}\n \n \tmap-\u003efile = vma-\u003evm_file;\n \tmap-\u003evma_flags = vma-\u003eflags;\n@@ -2693,11 +2699,6 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,\n \t\tvma-\u003eflags = map-\u003evma_flags;\n \t}\n \n-#ifdef CONFIG_SPARC64\n-\t/* TODO: Fix SPARC ADI! */\n-\tWARN_ON_ONCE(!arch_validate_flags(map-\u003evm_flags));\n-#endif\n-\n \t/* Lock the VMA since it is modified after insertion into VMA tree */\n \tvma_start_write(vma);\n \tvma_iter_store_new(vmi, vma);\n@@ -2760,6 +2761,96 @@ static void __mmap_complete(struct mmap_state *map, struct vm_area_struct *vma)\n \tvma_set_page_prot(vma);\n }\n \n+/* Check to ensure that the VMA flags of a newly mapped VMA are sane. */\n+static int mmap_validate_vma_flags(const vma_flags_t *flags)\n+{\n+#ifdef CONFIG_SPARC64\n+\tconst vm_flags_t legacy_flags = vma_flags_to_legacy(*flags);\n+\n+\t/* TODO: Fix SPARC ADI! */\n+\tif (WARN_ON_ONCE(!arch_validate_flags(legacy_flags)))\n+\t\treturn -EINVAL;\n+#endif\n+\n+\tif (!vma_flags_is_kernel_owned(flags)) {\n+\t\t/* Only kernel-owned mappings may set VMA_IO_BIT. */\n+\t\tif (WARN_ON_ONCE(vma_flags_test(flags, VMA_IO_BIT)))\n+\t\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* Check to ensure a driver hasn't done something crazy. */\n+static int mmap_validate(unsigned long prev_start,\n+\t\t\t unsigned long curr_start,\n+\t\t\t const vma_flags_t *prev_flags,\n+\t\t\t const vma_flags_t *curr_flags)\n+{\n+\tbool was_maywrite, is_maywrite;\n+\n+\t/* Drivers cannot alter the address of the VMA. */\n+\tif (WARN_ON_ONCE(prev_start != curr_start))\n+\t\treturn -EINVAL;\n+\n+\twas_maywrite = vma_flags_test(prev_flags, VMA_MAYWRITE_BIT);\n+\tis_maywrite = vma_flags_test(curr_flags, VMA_MAYWRITE_BIT);\n+\n+\t/* A driver may not make a previously unwritable mapping writable. */\n+\tif (WARN_ON_ONCE(!was_maywrite \u0026\u0026 is_maywrite))\n+\t\treturn -EINVAL;\n+\n+\t/* Only kernel-owned mappings may clear VMA_MAYWRITE_BIT. */\n+\tif (!vma_flags_is_kernel_owned(curr_flags) \u0026\u0026\n+\t    WARN_ON_ONCE(was_maywrite \u0026\u0026 !is_maywrite))\n+\t\treturn -EINVAL;\n+\n+\treturn mmap_validate_vma_flags(curr_flags);\n+}\n+\n+/**\n+ * mmap_prepare_validate() - Ensure the driver hasn't violated invariants in its\n+ * f_op-\u003emmap_prepare hook.\n+ * @prev_desc: The VMA descriptor prior to the mmap_prepare hook being called.\n+ * @desc: The VMA descriptor after the mmap_prepare hook has been called.\n+ *\n+ * Returns: 0 on success, otherwise an error.\n+ */\n+int mmap_prepare_validate(const struct vm_area_desc *prev_desc,\n+\t\t\t  const struct vm_area_desc *desc)\n+{\n+\t/*\n+\t * It is not valid to execute mmap actions for VMAs which can be merged,\n+\t * as any such merge would leave portions of the mapping incorrectly\n+\t * unmapped.\n+\t */\n+\tif (vma_flags_can_merge(\u0026desc-\u003evma_flags) \u0026\u0026\n+\t    WARN_ON_ONCE(desc-\u003eaction.type != MMAP_NOTHING))\n+\t\treturn -EINVAL;\n+\n+\treturn mmap_validate(prev_desc-\u003estart, desc-\u003estart,\n+\t\t\t     \u0026prev_desc-\u003evma_flags, \u0026desc-\u003evma_flags);\n+}\n+\n+/**\n+ * mmap_hook_validate() - Ensure the driver hasn't violated invariants in\n+ * its f_op-\u003emmap hook.\n+ * @prev_start: The start of the mapping prior to the mmap hook.\n+ * @prev_flags: The VMA flags set for the VMA prior to the mmap hook.\n+ * @vma: The VMA after the hook has been applied.\n+ *\n+ * Returns: 0 on success, otherwise an error.\n+ */\n+int mmap_hook_validate(unsigned long prev_start,\n+\t\t       const vma_flags_t *prev_flags,\n+\t\t       const struct vm_area_struct *vma)\n+{\n+\tconst unsigned long start = vma-\u003evm_start;\n+\tconst vma_flags_t *flags = \u0026vma-\u003eflags;\n+\n+\treturn mmap_validate(prev_start, start, prev_flags, flags);\n+}\n+\n static int call_action_prepare(struct mmap_state *map,\n \t\t\t       struct vm_area_desc *desc)\n {\n@@ -2786,6 +2877,7 @@ static int call_action_prepare(struct mmap_state *map,\n static int call_mmap_prepare(struct mmap_state *map,\n \t\tstruct vm_area_desc *desc)\n {\n+\tconst struct vm_area_desc prev_desc = *desc;\n \tint err;\n \n \t/* Invoke the hook. */\n@@ -2797,10 +2889,16 @@ static int call_mmap_prepare(struct mmap_state *map,\n \tif (!desc-\u003evm_ops)\n \t\treturn -EINVAL;\n \n+\t/* Perform any preparatory tasks for mmap action. */\n \terr = call_action_prepare(map, desc);\n \tif (err)\n \t\treturn err;\n \n+\t/* Check the caller did nothing crazy. */\n+\terr = mmap_prepare_validate(\u0026prev_desc, desc);\n+\tif (err)\n+\t\treturn err;\n+\n \t/* Update fields permitted to be changed. */\n \tmap-\u003epgoff = desc-\u003epgoff;\n \tif (desc-\u003evm_file != map-\u003efile) {\n@@ -2866,7 +2964,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,\n {\n \tstruct mm_struct *mm = current-\u003emm;\n \tstruct vm_area_struct *vma = NULL;\n-\tbool have_mmap_prepare = file \u0026\u0026 file-\u003ef_op-\u003emmap_prepare;\n+\tconst bool have_mmap_prepare = file \u0026\u0026 file-\u003ef_op-\u003emmap_prepare;\n \tVMA_ITERATOR(vmi, mm, addr);\n \tconst pgoff_t anon_pgoff = addr \u003e\u003e PAGE_SHIFT;\n \tMMAP_STATE(map, mm, \u0026vmi, addr, len, pgoff, anon_pgoff, vma_flags, file);\n@@ -2909,7 +3007,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,\n \t\tallocated_new = true;\n \t}\n \n-\tif (have_mmap_prepare \u0026\u0026 !map_is_anon(\u0026map))\n+\tif (have_mmap_prepare \u0026\u0026 allocated_new \u0026\u0026 !map_is_anon(\u0026map))\n \t\tset_vma_user_defined_fields(vma, \u0026map);\n \n \t__mmap_complete(\u0026map, vma);\n@@ -3429,10 +3527,15 @@ int __vm_munmap(unsigned long start, size_t len, bool unlock)\n int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma)\n {\n \tunsigned long charged = vma_pages(vma);\n+\tint err;\n \n \tif (find_vma_intersection(mm, vma-\u003evm_start, vma-\u003evm_end))\n \t\treturn -ENOMEM;\n \n+\terr = mmap_validate_vma_flags(\u0026vma-\u003eflags);\n+\tif (err)\n+\t\treturn err;\n+\n \tif (vma_test(vma, VMA_ACCOUNT_BIT) \u0026\u0026\n \t     security_vm_enough_memory_mm(mm, charged))\n \t\treturn -ENOMEM;\ndiff --git a/mm/vma.h b/mm/vma.h\nindex e97bd2dfa786d..af14ed7265ce3 100644\n--- a/mm/vma.h\n+++ b/mm/vma.h\n@@ -780,14 +780,19 @@ struct vm_area_struct *vm_area_alloc(struct mm_struct *mm);\n struct vm_area_struct *vm_area_dup(struct vm_area_struct *orig);\n void vm_area_free(struct vm_area_struct *vma);\n \n-/* vma_exec.c */\n #ifdef CONFIG_MMU\n+int mmap_prepare_validate(const struct vm_area_desc *prev_desc,\n+\t\t\t  const struct vm_area_desc *desc);\n+\n+int mmap_hook_validate(unsigned long prev_start,\n+\t\t       const vma_flags_t *prev_flags,\n+\t\t       const struct vm_area_struct *vma);\n+\n+/* vma_exec.c */\n int create_init_stack_vma(struct mm_struct *mm, struct vm_area_struct **vmap,\n \t\t\t  unsigned long *top_mem_p);\n int relocate_vma_down(struct vm_area_struct *vma, unsigned long shift);\n-#endif\n \n-#ifdef CONFIG_MMU\n /*\n  * Denies creating a writable executable mapping or gaining executable permissions.\n  *\n@@ -836,6 +841,19 @@ static inline bool map_deny_write_exec(const vma_flags_t *old,\n \n \treturn false;\n }\n+#else\n+static inline int mmap_prepare_validate(const struct vm_area_desc *prev_desc,\n+\t\t\t\t\tconst struct vm_area_desc *desc)\n+{\n+\treturn 0;\n+}\n+\n+static inline int mmap_hook_validate(unsigned long prev_start,\n+\t\t\t\t     const vma_flags_t *prev_flags,\n+\t\t\t\t     const struct vm_area_struct *vma)\n+{\n+\treturn 0;\n+}\n #endif\n \n struct vm_area_struct *__install_special_mapping(struct mm_struct *mm,\ndiff --git a/mm/vma_internal.h b/mm/vma_internal.h\nindex 4d300e7bbaf4c..4f73f0a4db796 100644\n--- a/mm/vma_internal.h\n+++ b/mm/vma_internal.h\n@@ -18,7 +18,6 @@\n #include \u003clinux/fs.h\u003e\n #include \u003clinux/huge_mm.h\u003e\n #include \u003clinux/hugetlb.h\u003e\n-#include \u003clinux/hugetlb_inline.h\u003e\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/ksm.h\u003e\n #include \u003clinux/khugepaged.h\u003e\ndiff --git a/mm/vmscan.c b/mm/vmscan.c\nindex 245f68c75b289..0082afbdbbdd3 100644\n--- a/mm/vmscan.c\n+++ b/mm/vmscan.c\n@@ -3413,13 +3413,14 @@ static int should_skip_vma(unsigned long start, unsigned long end, struct mm_wal\n \tif (!vma_is_accessible(vma))\n \t\treturn true;\n \n-\tif (is_vm_hugetlb_page(vma))\n+\tif (vma_is_hugetlb(vma))\n \t\treturn true;\n \n \tif (!vma_has_recency(vma))\n \t\treturn true;\n \n-\tif (vma-\u003evm_flags \u0026 (VM_LOCKED | VM_SPECIAL))\n+\tif (vma_test(vma, VMA_LOCKED_BIT) || vma_is_kernel_owned(vma) ||\n+\t    vma_is_fixed_mapping(vma))\n \t\treturn true;\n \n \tif (vma == get_gate_vma(vma-\u003evm_mm))\n@@ -4363,8 +4364,8 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)\n \tif (spin_is_contended(pvmw-\u003eptl))\n \t\treturn true;\n \n-\t/* exclude special VMAs containing anon pages from COW */\n-\tif (vma-\u003evm_flags \u0026 VM_SPECIAL)\n+\t/* exclude kernel-owned and fixed VMAs containing anon pages from COW */\n+\tif (vma_is_kernel_owned(vma) || vma_is_fixed_mapping(vma))\n \t\treturn true;\n \n \t/* avoid taking the LRU lock under the PTL when possible */\ndiff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c\nindex c7d91476971cb..545a6f89f9e76 100644\n--- a/security/selinux/selinuxfs.c\n+++ b/security/selinux/selinuxfs.c\n@@ -340,6 +340,9 @@ static int sel_open_policy(struct inode *inode, struct file *filp)\n \tstruct policy_load_memory *plm = NULL;\n \tint rc;\n \n+\tif (filp-\u003ef_mode \u0026 FMODE_WRITE)\n+\t\treturn -EACCES;\n+\n \trc = avc_has_perm(current_sid(), SECINITSID_SECURITY,\n \t\t\t  SECCLASS_SECURITY, SECURITY__READ_POLICY, NULL);\n \tif (rc)\n@@ -424,14 +427,6 @@ static const struct vm_operations_struct sel_mmap_policy_ops = {\n \n static int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma)\n {\n-\tif (vma-\u003evm_flags \u0026 VM_SHARED) {\n-\t\t/* do not allow mprotect to make mapping writable */\n-\t\tvm_flags_clear(vma, VM_MAYWRITE);\n-\n-\t\tif (vma-\u003evm_flags \u0026 VM_WRITE)\n-\t\t\treturn -EACCES;\n-\t}\n-\n \tvm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);\n \tvma-\u003evm_ops = \u0026sel_mmap_policy_ops;\n \ndiff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c\nindex 62324282fcae9..37a157d558325 100644\n--- a/sound/core/pcm_native.c\n+++ b/sound/core/pcm_native.c\n@@ -3760,39 +3760,26 @@ static __poll_t snd_pcm_poll(struct file *file, poll_table *wait)\n /*\n  * mmap status record\n  */\n-static vm_fault_t snd_pcm_mmap_status_fault(struct vm_fault *vmf)\n+static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,\n+\t\t\t       struct vm_area_struct *vma)\n {\n-\tstruct snd_pcm_substream *substream = vmf-\u003evma-\u003evm_private_data;\n+\tconst unsigned long size = vma-\u003evm_end - vma-\u003evm_start;\n \tstruct snd_pcm_runtime *runtime;\n-\t\n-\tif (substream == NULL)\n-\t\treturn VM_FAULT_SIGBUS;\n-\truntime = substream-\u003eruntime;\n-\tvmf-\u003epage = virt_to_page(runtime-\u003estatus);\n-\tget_page(vmf-\u003epage);\n-\treturn 0;\n-}\n+\tstruct page *page;\n \n-static const struct vm_operations_struct snd_pcm_vm_ops_status =\n-{\n-\t.fault =\tsnd_pcm_mmap_status_fault,\n-};\n+\tBUILD_BUG_ON(sizeof(struct snd_pcm_mmap_status) \u003e PAGE_SIZE);\n \n-static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,\n-\t\t\t       struct vm_area_struct *area)\n-{\n-\tlong size;\n-\tif (!(area-\u003evm_flags \u0026 VM_READ))\n+\tif (!(vma-\u003evm_flags \u0026 VM_READ))\n \t\treturn -EINVAL;\n-\tsize = area-\u003evm_end - area-\u003evm_start;\n-\tif (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)))\n+\tif (size != PAGE_SIZE)\n \t\treturn -EINVAL;\n-\tarea-\u003evm_ops = \u0026snd_pcm_vm_ops_status;\n-\tarea-\u003evm_private_data = substream;\n-\tvm_flags_mod(area, VM_DONTEXPAND | VM_DONTDUMP,\n+\n+\tvm_flags_mod(vma, VM_DONTEXPAND | VM_DONTDUMP,\n \t\t     VM_WRITE | VM_MAYWRITE);\n \n-\treturn 0;\n+\truntime = substream-\u003eruntime;\n+\tpage = virt_to_page(runtime-\u003estatus);\n+\treturn vm_insert_page(vma, vma-\u003evm_start, page);\n }\n \n /*\ndiff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h\nindex 16c09dac59d9b..61b08589e5929 100644\n--- a/tools/testing/vma/include/dup.h\n+++ b/tools/testing/vma/include/dup.h\n@@ -352,14 +352,6 @@ enum {\n #define VM_ACCESS_FLAGS (VM_READ | VM_WRITE | VM_EXEC)\n #define VMA_ACCESS_FLAGS mk_vma_flags(VMA_READ_BIT, VMA_WRITE_BIT, VMA_EXEC_BIT)\n \n-/*\n- * Special vmas that are non-mergable, non-mlock()able.\n- */\n-#define VM_SPECIAL (VM_IO | VM_DONTEXPAND | VM_PFNMAP | VM_MIXEDMAP)\n-\n-#define VMA_SPECIAL_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_DONTEXPAND_BIT, \\\n-\t\t\t\t       VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT)\n-\n #define VMA_REMAP_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_PFNMAP_BIT,\t\\\n \t\t\t\t     VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT)\n \n@@ -454,11 +446,12 @@ static __always_inline bool vma_flags_empty(const vma_flags_t *flags)\n \n /* What action should be taken after an .mmap_prepare call is complete? */\n enum mmap_action_type {\n-\tMMAP_NOTHING,\t\t/* Mapping is complete, no further action. */\n-\tMMAP_REMAP_PFN,\t\t/* Remap PFN range. */\n-\tMMAP_IO_REMAP_PFN,\t/* I/O remap PFN range. */\n-\tMMAP_SIMPLE_IO_REMAP,\t/* I/O remap with guardrails. */\n-\tMMAP_MAP_KERNEL_PAGES,\t/* Map kernel page range from an array. */\n+\tMMAP_NOTHING,\n+\tMMAP_REMAP_PFN,\n+\tMMAP_IO_REMAP_PFN,\n+\tMMAP_SIMPLE_IO_REMAP,\t\t/* I/O remap with guardrails. */\n+\tMMAP_KERNEL_PAGES,\t\t/* Map kernel page range from array. */\n+\tMMAP_DISCONTIG_KERNEL_PAGES,\t/* Map kernel discontig page range. */\n };\n \n /*\n@@ -1359,13 +1352,23 @@ static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)\n \treturn file-\u003ef_op-\u003emmap_prepare(desc);\n }\n \n+int mmap_prepare_validate(const struct vm_area_desc *prev_desc,\n+\t\t\t  const struct vm_area_desc *desc);\n+\n static inline int __compat_vma_mmap(struct vm_area_desc *desc,\n \t\tstruct vm_area_struct *vma)\n {\n+\tstruct vm_area_desc prev_desc;\n \tint err;\n \n+\t/* Derive state prior to mmap_prepare hook. */\n+\tcompat_set_desc_from_vma(\u0026prev_desc, desc-\u003efile, vma);\n \t/* Perform any preparatory tasks for mmap action. */\n \terr = mmap_action_prepare(desc);\n+\tif (err)\n+\t\treturn err;\n+\t/* Check the caller did nothing crazy. */\n+\terr = mmap_prepare_validate(\u0026prev_desc, desc);\n \tif (err)\n \t\treturn err;\n \t/* Update the VMA from the descriptor. */\n@@ -1647,3 +1650,34 @@ static inline bool file_is_dev_zero(const struct file *file)\n {\n \treturn file \u0026\u0026 file-\u003ef_op == \u0026zero_fops;\n }\n+\n+static inline bool vma_flags_is_kernel_owned(const vma_flags_t *flags)\n+{\n+\treturn vma_flags_test_any(flags, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT);\n+}\n+\n+static inline bool vma_is_kernel_owned(const struct vm_area_struct *vma)\n+{\n+\treturn vma_flags_is_kernel_owned(\u0026vma-\u003eflags);\n+}\n+\n+static inline bool vma_flags_can_merge(const vma_flags_t *flags)\n+{\n+\t/*\n+\t * VMA merging assumes that the properties of a VMA completely describe\n+\t * the properties of that VMA.\n+\t *\n+\t * However, kernel-owned mappings may have established state upon mapping\n+\t * not embodied in any attribute of the VMA.\n+\t *\n+\t * Additionally, PFN maps encode the source PFN of the range in\n+\t * vma-\u003evm_pgoff, which may otherwise cause spurious merges.\n+\t */\n+\tif (vma_flags_is_kernel_owned(flags))\n+\t\treturn false;\n+\t/* VMA explicitly marked as being unmergeable. */\n+\tif (vma_flags_test(flags, VMA_DONTEXPAND_BIT))\n+\t\treturn false;\n+\n+\treturn true;\n+}\ndiff --git a/tools/testing/vma/include/stubs.h b/tools/testing/vma/include/stubs.h\nindex d6136e19a8af3..48d1dc53df42c 100644\n--- a/tools/testing/vma/include/stubs.h\n+++ b/tools/testing/vma/include/stubs.h\n@@ -193,7 +193,7 @@ static inline bool mapping_can_writeback(struct address_space *mapping)\n \treturn true;\n }\n \n-static inline bool is_vm_hugetlb_page(struct vm_area_struct *vma)\n+static inline bool vma_is_hugetlb(struct vm_area_struct *vma)\n {\n \treturn false;\n }\ndiff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c\nindex acaab282939c0..b26f1a66a1707 100644\n--- a/tools/testing/vma/tests/merge.c\n+++ b/tools/testing/vma/tests/merge.c\n@@ -496,17 +496,11 @@ static bool test_vma_merge_special_flags(void)\n \t\t.mm = \u0026mm,\n \t\t.vmi = \u0026vmi,\n \t};\n-\tvma_flag_t special_flags[] = { VMA_IO_BIT, VMA_DONTEXPAND_BIT,\n+\tvma_flag_t special_flags[] = { VMA_DONTEXPAND_BIT,\n \t\tVMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT };\n-\tvma_flags_t all_special_flags = EMPTY_VMA_FLAGS;\n \tint i;\n \tstruct vm_area_struct *vma_left, *vma;\n \n-\t/* Make sure there aren't new VM_SPECIAL flags. */\n-\tfor (i = 0; i \u003c ARRAY_SIZE(special_flags); i++)\n-\t\tvma_flags_set(\u0026all_special_flags, special_flags[i]);\n-\tASSERT_FLAGS_SAME_MASK(\u0026all_special_flags, VMA_SPECIAL_FLAGS);\n-\n \t/*\n \t * 01234\n \t * AAA\n@@ -520,7 +514,7 @@ static bool test_vma_merge_special_flags(void)\n \t * 01234\n \t * AAA*\n \t *\n-\t * This should merge if not for the VM_SPECIAL flag.\n+\t * This should merge if not for the 'special' flag.\n \t */\n \tvmg_set_range(\u0026vmg, 0x3000, 0x4000, 3, vma_flags);\n \tfor (i = 0; i \u003c ARRAY_SIZE(special_flags); i++) {\n"
}

2/1 2026/09/08 22:26 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "HUGETLBFS",
    "CHR_DEV_SG",
    "USB_MON",
    "SND_DUMMY"
  ],
  "FocusSymbols": [
    "map_discontig_kernel_pages_prepare",
    "map_discontig_kernel_pages_complete",
    "mmap_prepare_validate",
    "mmap_hook_validate",
    "mlock_vma_pages_range",
    "sg_discontig_get",
    "sg_discontig_init",
    "mon_bin_discontig_get",
    "mon_bin_discontig_init",
    "snd_pcm_mmap_status"
  ],
  "Reasoning": "This patch series introduces substantial changes to core memory management (MM), including replacing VM_SPECIAL with new granular VMA classification helpers (vma_is_kernel_owned, vma_is_fixed_mapping, vma_can_merge, vma_is_persistent, vma_can_gup), adding a new mmap action for mapping discontiguous kernel pages (MMAP_DISCONTIG_KERNEL_PAGES) along with new prepare/complete hooks, adding strict mmap validation logic (mmap_prepare_validate, mmap_hook_validate), refactoring mlock/munlock logic, and migrating several drivers (SCSI generic sg, USB mon_bin, ALSA PCM native) to the new interfaces. These paths are fully reachable and introduce new runtime assertions (WARN_ON_ONCE, VM_WARN_ON_ONCE).",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any '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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 27e2d5e005e5b11519ad4486d292f37520bbd200
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Sep 8 22:26:20 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/filesystems/mmap_prepare.rst b/Documentation/filesystems/mmap_prepare.rst
index 82c99c95ad854..a476e1006bf12 100644
--- a/Documentation/filesystems/mmap_prepare.rst
+++ b/Documentation/filesystems/mmap_prepare.rst
@@ -164,5 +164,86 @@ pointer. These are:
   sufficient entries in the page array to cover the entire range of the
   described VMA.
 
+* mmap_action_map_discontig_kernel_pages() - Maps a discontiguous range of
+  `struct page` pointers over the VMA. They must span from the start of the VMA,
+  but may terminate prior to the end (leaving the remainder unmapped).
+
 **NOTE:** The ``action`` field should never normally be manipulated directly,
 rather you ought to use one of these helpers.
+
+Discontiguous Actions
+=====================
+
+Some actions can be performed across discontiguous ranges.
+
+Map kernel pages
+----------------
+
+To map kernel pages discontiguously, you must provide hooks using ``struct
+discontig_kernel_page_ops``:
+
+.. code-block:: C
+
+    struct discontig_kernel_page_ops {
+        int (*init)(void *vm_private_data, void **private);
+        int (*get)(struct discontig_kernel_page_state *state);
+    };
+
+The ``init`` hook is optional and allows state to be established before the
+operation starts, for instance taking a reference count. Nothing is invoked
+after the operation, so ``init`` must not leave locks held, and state that must
+be released once the mapping goes away should be released in
+``vm_ops->close``.
+
+The ``init`` hook, if provided, is invoked prior to the operation starting. It
+may update what is pointed to by ``vm_private_data`` and/or ``private``. If an
+error is returned, then the operation is aborted. The ``private`` field can be
+reassigned.
+
+**NOTE:** The operation may sleep between invocations of ``get``, so locks
+needed to stabilise state must be taken and released within each hook.
+
+The ``get`` handler is the key means through which the operation is
+executed. The current state of the operation is provided through ``struct
+discontig_kernel_page_state``:
+
+.. code-block:: C
+
+    struct discontig_kernel_page_state {
+        /* Map state. */
+        unsigned long start;            /* Start address of VMA. */
+        unsigned long end;              /* End address of VMA. */
+        unsigned long addr;             /* The current address to be mapped. */
+        pgoff_t pgoff;                  /* The current pgoff to be mapped. */
+        unsigned long nr_pages_mapped;  /* The number of pages mapped. */
+        unsigned long nr_pages_remain;  /* The number of pages remaining. */
+
+        /* User-defined state. */
+        void *vm_private_data;          /* VMA private data. */
+        void *private;                  /* Mapping private data. */
+
+        /* Users should not touch these, use discontig_kernel_map_*() helpers. */
+        ... internal fields ...
+    };
+
+With ``private`` being an additional user-controllable state variable,
+initialised via ``mmap_action_map_discontig_kernel_pages()``, and
+``vm_private_data`` being equal to the ``desc->private_data`` field set in
+the ``mmap_prepare()`` hook.
+
+In the ``get`` hook, the user must choose how to map kernel pages:
+
+* ``discontig_kernel_map_abort()`` - Call this to abort the operation, whatever
+  has been mapped so far will be retained, the rest of the mapping will SIGBUS
+  if accessed.
+* ``discontig_kernel_map_page()`` - Maps a single page, correctly handling
+  compound pages (if the compound page is bigger than the remaining pages in the
+  VMA, then only those pages that fit will be mapped). For a compound page, the
+  head page must be passed.
+* ``discontig_kernel_map_page_range()`` - Map an array of pages of a specified
+  size. Note that if the number of pages specified exceeds the VMA size then an
+  error will arise.
+
+If an error arises after ``init`` succeeded, the core unmaps the VMA, invoking
+``vm_ops->close`` if set, which is therefore the place to release any state
+that ``init`` established.
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9ba86450fe4af..3c1240ffc38df 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1463,14 +1463,12 @@ static int get_vma_page_shift(struct vm_area_struct *vma, unsigned long hva)
 {
 	unsigned long pa;
 
-	if (is_vm_hugetlb_page(vma) && !(vma->vm_flags & VM_PFNMAP))
+	if (vma_is_hugetlb(vma))
 		return huge_page_shift(hstate_vma(vma));
 
 	if (!(vma->vm_flags & VM_PFNMAP))
 		return PAGE_SHIFT;
 
-	VM_BUG_ON(is_vm_hugetlb_page(vma));
-
 	pa = (vma->vm_pgoff << PAGE_SHIFT) + (hva - vma->vm_start);
 
 #ifndef __PAGETABLE_PMD_FOLDED
diff --git a/arch/powerpc/mm/book3s64/radix_tlb.c b/arch/powerpc/mm/book3s64/radix_tlb.c
index 7de5760164a90..b4603a98224b3 100644
--- a/arch/powerpc/mm/book3s64/radix_tlb.c
+++ b/arch/powerpc/mm/book3s64/radix_tlb.c
@@ -627,7 +627,7 @@ void radix__local_flush_tlb_page(struct vm_area_struct *vma, unsigned long vmadd
 {
 #ifdef CONFIG_HUGETLB_PAGE
 	/* need the return fix for nohash.c */
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return radix__local_flush_hugetlb_page(vma, vmaddr);
 #endif
 	radix__local_flush_tlb_page_psize(vma->vm_mm, vmaddr, mmu_virtual_psize);
@@ -945,7 +945,7 @@ void radix__flush_tlb_page_psize(struct mm_struct *mm, unsigned long vmaddr,
 void radix__flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr)
 {
 #ifdef CONFIG_HUGETLB_PAGE
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return radix__flush_hugetlb_page(vma, vmaddr);
 #endif
 	radix__flush_tlb_page_psize(vma->vm_mm, vmaddr, mmu_virtual_psize);
@@ -1113,7 +1113,7 @@ void radix__flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
 
 {
 #ifdef CONFIG_HUGETLB_PAGE
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return radix__flush_hugetlb_tlb_range(vma, start, end);
 #endif
 
diff --git a/arch/powerpc/mm/nohash/e500_hugetlbpage.c b/arch/powerpc/mm/nohash/e500_hugetlbpage.c
index a134d28a0e4d3..b87623f04be53 100644
--- a/arch/powerpc/mm/nohash/e500_hugetlbpage.c
+++ b/arch/powerpc/mm/nohash/e500_hugetlbpage.c
@@ -180,7 +180,7 @@ book3e_hugetlb_preload(struct vm_area_struct *vma, unsigned long ea, pte_t pte)
  */
 void __update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		book3e_hugetlb_preload(vma, address, *ptep);
 }
 
diff --git a/arch/powerpc/mm/nohash/tlb.c b/arch/powerpc/mm/nohash/tlb.c
index 0a650742f3a00..07a2db16c2b15 100644
--- a/arch/powerpc/mm/nohash/tlb.c
+++ b/arch/powerpc/mm/nohash/tlb.c
@@ -278,7 +278,7 @@ void __flush_tlb_page(struct mm_struct *mm, unsigned long vmaddr,
 void flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr)
 {
 #ifdef CONFIG_HUGETLB_PAGE
-	if (vma && is_vm_hugetlb_page(vma))
+	if (vma && vma_is_hugetlb(vma))
 		flush_hugetlb_page(vma, vmaddr);
 #endif
 
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 6035b5ec95039..5c5c77f98bf0f 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -664,7 +664,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
 		return -EFAULT;
 	}
 
-	is_hugetlb = is_vm_hugetlb_page(vma);
+	is_hugetlb = vma_is_hugetlb(vma);
 	if (is_hugetlb)
 		vma_pageshift = huge_page_shift(hstate_vma(vma));
 	else
diff --git a/arch/riscv/mm/tlbflush.c b/arch/riscv/mm/tlbflush.c
index 962db300a1665..a74a7d5258aa1 100644
--- a/arch/riscv/mm/tlbflush.c
+++ b/arch/riscv/mm/tlbflush.c
@@ -149,7 +149,7 @@ void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
 {
 	unsigned long stride_size;
 
-	if (!is_vm_hugetlb_page(vma)) {
+	if (!vma_is_hugetlb(vma)) {
 		stride_size = PAGE_SIZE;
 	} else {
 		stride_size = huge_page_size(hstate_vma(vma));
diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c
index ff63ffb1dbd29..3f6783b93e679 100644
--- a/arch/s390/mm/gmap_helpers.c
+++ b/arch/s390/mm/gmap_helpers.c
@@ -102,7 +102,7 @@ __context_unsafe(/* pte_unmap_unlock() not instrumented */)
 
 	/* Find the vm address for the guest address */
 	vma = vma_lookup(mm, vmaddr);
-	if (!vma || is_vm_hugetlb_page(vma))
+	if (!vma || vma_is_hugetlb(vma))
 		return;
 
 	/* Get pointer to the page table entry */
@@ -139,7 +139,7 @@ void gmap_helper_discard(struct mm_struct *mm, unsigned long vmaddr, unsigned lo
 		vma = find_vma_intersection(mm, vmaddr, end);
 		if (!vma)
 			return;
-		if (!is_vm_hugetlb_page(vma))
+		if (!vma_is_hugetlb(vma))
 			zap_vma_range(vma, vmaddr, min(end, vma->vm_end) - vmaddr);
 		vmaddr = vma->vm_end;
 	}
@@ -247,7 +247,7 @@ static int __gmap_helper_unshare_zeropages(struct mm_struct *mm)
 		 * proof to catch unexpected zeropages in other mappings and
 		 * fail.
 		 */
-		if ((vma->vm_flags & VM_PFNMAP) || is_vm_hugetlb_page(vma))
+		if ((vma->vm_flags & VM_PFNMAP) || vma_is_hugetlb(vma))
 			continue;
 		addr = vma->vm_start;
 
diff --git a/arch/sparc/mm/init_64.c b/arch/sparc/mm/init_64.c
index 103db4683b165..9bbccb5d23a8f 100644
--- a/arch/sparc/mm/init_64.c
+++ b/arch/sparc/mm/init_64.c
@@ -413,7 +413,7 @@ void update_mmu_cache_range(struct vm_fault *vmf, struct vm_area_struct *vma,
 	if (mm->context.hugetlb_pte_count || mm->context.thp_pte_count) {
 		unsigned long hugepage_size = PAGE_SIZE;
 
-		if (is_vm_hugetlb_page(vma))
+		if (vma_is_hugetlb(vma))
 			hugepage_size = huge_page_size(hstate_vma(vma));
 
 		if (hugepage_size >= PUD_SIZE) {
diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c
index 65a2de82ecd29..0f60c0d076b62 100644
--- a/arch/x86/kernel/uprobes.c
+++ b/arch/x86/kernel/uprobes.c
@@ -715,7 +715,7 @@ static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsign
 
 	*new_mapping = true;
 	return _install_special_mapping(mm, vaddr, PAGE_SIZE,
-				VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_IO,
+				VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_MIXEDMAP,
 				&tramp_mapping);
 }
 
diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c
index a93eee7ddb9e9..fab34fea99c2f 100644
--- a/drivers/gpu/drm/drm_gpusvm.c
+++ b/drivers/gpu/drm/drm_gpusvm.c
@@ -9,9 +9,9 @@
 #include <linux/dma-mapping.h>
 #include <linux/export.h>
 #include <linux/hmm.h>
-#include <linux/hugetlb_inline.h>
 #include <linux/memremap.h>
 #include <linux/mm_types.h>
+#include <linux/mm.h>
 #include <linux/slab.h>
 
 #include <drm/drm_device.h>
@@ -1141,8 +1141,7 @@ drm_gpusvm_range_find_or_insert(struct drm_gpusvm *gpusvm,
 	 * limitations. If/when migrate_vma_* add more support, this logic will
 	 * have to change.
 	 */
-	migrate_devmem = ctx->devmem_possible &&
-		vma_is_anonymous(vas) && !is_vm_hugetlb_page(vas);
+	migrate_devmem = ctx->devmem_possible && vma_is_anonymous(vas);
 
 	chunk_size = drm_gpusvm_range_chunk_size(gpusvm, notifier, vas,
 						 fault_addr, gpuva_start,
diff --git a/drivers/hsi/clients/cmt_speech.c b/drivers/hsi/clients/cmt_speech.c
index 7226677ebde7a..801697b74d4f8 100644
--- a/drivers/hsi/clients/cmt_speech.c
+++ b/drivers/hsi/clients/cmt_speech.c
@@ -1084,22 +1084,6 @@ static void cs_hsi_stop(struct cs_hsi_iface *hi)
 	kfree(hi);
 }
 
-static vm_fault_t cs_char_vma_fault(struct vm_fault *vmf)
-{
-	struct cs_char *csdata = vmf->vma->vm_private_data;
-	struct page *page;
-
-	page = virt_to_page((void *)csdata->mmap_base);
-	get_page(page);
-	vmf->page = page;
-
-	return 0;
-}
-
-static const struct vm_operations_struct cs_char_vm_ops = {
-	.fault	= cs_char_vma_fault,
-};
-
 static int cs_char_fasync(int fd, struct file *file, int on)
 {
 	struct cs_char *csdata = file->private_data;
@@ -1256,18 +1240,19 @@ static long cs_char_ioctl(struct file *file, unsigned int cmd,
 	return r;
 }
 
-static int cs_char_mmap(struct file *file, struct vm_area_struct *vma)
+static int cs_char_mmap_prepare(struct vm_area_desc *desc)
 {
-	if (vma->vm_end < vma->vm_start)
-		return -EINVAL;
+	struct file *file = desc->file;
+	struct cs_char *csdata = file->private_data;
+	struct page **pages = (struct page **)&desc->private_data;
 
-	if (vma_pages(vma) != 1)
+	if (vma_desc_pages(desc) != 1)
 		return -EINVAL;
 
-	vm_flags_set(vma, VM_IO | VM_DONTDUMP | VM_DONTEXPAND);
-	vma->vm_ops = &cs_char_vm_ops;
-	vma->vm_private_data = file->private_data;
+	vma_desc_set_flags(desc, VMA_DONTDUMP_BIT, VMA_DONTEXPAND_BIT);
 
+	*pages = virt_to_page((void *)csdata->mmap_base);
+	mmap_action_map_kernel_pages_full(desc, pages);
 	return 0;
 }
 
@@ -1353,7 +1338,7 @@ static const struct file_operations cs_char_fops = {
 	.write		= cs_char_write,
 	.poll		= cs_char_poll,
 	.unlocked_ioctl	= cs_char_ioctl,
-	.mmap		= cs_char_mmap,
+	.mmap_prepare	= cs_char_mmap_prepare,
 	.open		= cs_char_open,
 	.release	= cs_char_release,
 	.fasync		= cs_char_fasync,
diff --git a/drivers/infiniband/hw/hfi1/file_ops.c b/drivers/infiniband/hw/hfi1/file_ops.c
index dc548e6802e24..7119d734edc7b 100644
--- a/drivers/infiniband/hw/hfi1/file_ops.c
+++ b/drivers/infiniband/hw/hfi1/file_ops.c
@@ -70,7 +70,6 @@ static int set_ctxt_pkey(struct hfi1_ctxtdata *uctxt, unsigned long arg);
 static int ctxt_reset(struct hfi1_ctxtdata *uctxt);
 static int manage_rcvq(struct hfi1_ctxtdata *uctxt, u16 subctxt,
 		       unsigned long arg);
-static vm_fault_t vma_fault(struct vm_fault *vmf);
 static long hfi1_file_ioctl(struct file *fp, unsigned int cmd,
 			    unsigned long arg);
 
@@ -85,10 +84,6 @@ static const struct file_operations hfi1_file_ops = {
 	.llseek = noop_llseek,
 };
 
-static const struct vm_operations_struct vm_ops = {
-	.fault = vma_fault,
-};
-
 /*
  * Types of memories mapped into user processes' space
  */
@@ -304,13 +299,13 @@ static ssize_t hfi1_write_iter(struct kiocb *kiocb, struct iov_iter *from)
 	return reqs;
 }
 
-static inline void mmap_cdbg(u16 ctxt, u8 subctxt, u8 type, u8 mapio, u8 vmf,
+static inline void mmap_cdbg(u16 ctxt, u8 subctxt, u8 type, u8 mapio, u8 is_vmalloc,
 			     u64 memaddr, void *memvirt, dma_addr_t memdma,
 			     ssize_t memlen, struct vm_area_struct *vma)
 {
 	hfi1_cdbg(PROC,
-		  "%u:%u type:%u io/vf/dma:%d/%d/%d, addr:0x%llx, len:%lu(%lu), flags:0x%lx",
-		  ctxt, subctxt, type, mapio, vmf, !!memdma,
+		  "%u:%u type:%u io/vmalloc/dma:%d/%d/%d, addr:0x%llx, len:%lu(%lu), flags:0x%lx",
+		  ctxt, subctxt, type, mapio, is_vmalloc, !!memdma,
 		  memaddr ?: (u64)memvirt, memlen,
 		  vma->vm_end - vma->vm_start, vma->vm_flags);
 }
@@ -325,7 +320,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		memaddr = 0;
 	void *memvirt = NULL;
 	dma_addr_t memdma = 0;
-	u8 subctxt, mapio = 0, vmf = 0, type;
+	u8 subctxt, mapio = 0, is_vmalloc = 0, type;
 	ssize_t memlen = 0;
 	int ret = 0;
 	u16 ctxt;
@@ -347,7 +342,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 	/*
 	 * vm_pgoff is used as a buffer selector cookie.  Always mmap from
 	 * the beginning.
-	 */ 
+	 */
 	vma->vm_pgoff = 0;
 	flags = vma->vm_flags;
 
@@ -366,7 +361,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		 */
 		memlen = PAGE_ALIGN(uctxt->sc->credits * PIO_BLOCK_SIZE);
 		flags &= ~VM_MAYREAD;
-		flags |= VM_DONTCOPY | VM_DONTEXPAND;
+		flags |= VM_DONTCOPY;
 		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
 		mapio = 1;
 		break;
@@ -438,7 +433,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 			memvirt = uctxt->egrbufs.buffers[i].addr;
 			memdma = uctxt->egrbufs.buffers[i].dma;
 			vma->vm_end += memlen;
-			mmap_cdbg(ctxt, subctxt, type, mapio, vmf, memaddr,
+			mmap_cdbg(ctxt, subctxt, type, mapio, is_vmalloc, memaddr,
 				  memvirt, memdma, memlen, vma);
 			ret = dma_mmap_coherent(&dd->pcidev->dev, vma,
 						memvirt, memdma, memlen);
@@ -467,7 +462,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		 * user registers.
 		 */
 		memlen = PAGE_SIZE;
-		flags |= VM_DONTCOPY | VM_DONTEXPAND;
+		flags |= VM_DONTCOPY;
 		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
 		mapio = 1;
 		break;
@@ -476,15 +471,10 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		 * Use the page where this context's flags are. User level
 		 * knows where it's own bitmap is within the page.
 		 */
-		memaddr = (unsigned long)
-			(dd->events + uctxt_offset(uctxt)) & PAGE_MASK;
+		memvirt = dd->events + uctxt_offset(uctxt);
+		memvirt = (void *)(((uintptr_t)memvirt) & PAGE_MASK);
 		memlen = PAGE_SIZE;
-		/*
-		 * v3.7 removes VM_RESERVED but the effect is kept by
-		 * using VM_IO.
-		 */
-		flags |= VM_IO | VM_DONTEXPAND;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	case STATUS:
 		if (flags & VM_WRITE) {
@@ -493,7 +483,6 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		}
 		memaddr = kvirt_to_phys((void *)dd->status);
 		memlen = PAGE_SIZE;
-		flags |= VM_IO | VM_DONTEXPAND;
 		break;
 	case RTAIL:
 		if (!HFI1_CAP_IS_USET(DMA_RTAIL)) {
@@ -514,23 +503,20 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		flags &= ~VM_MAYWRITE;
 		break;
 	case SUBCTXT_UREGS:
-		memaddr = (u64)uctxt->subctxt_uregbase;
+		memvirt = uctxt->subctxt_uregbase;
 		memlen = PAGE_SIZE;
-		flags |= VM_IO | VM_DONTEXPAND;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	case SUBCTXT_RCV_HDRQ:
-		memaddr = (u64)uctxt->subctxt_rcvhdr_base;
+		memvirt = uctxt->subctxt_rcvhdr_base;
 		memlen = rcvhdrq_size(uctxt) * uctxt->subctxt_cnt;
-		flags |= VM_IO | VM_DONTEXPAND;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	case SUBCTXT_EGRBUF:
-		memaddr = (u64)uctxt->subctxt_rcvegrbuf;
+		memvirt = uctxt->subctxt_rcvegrbuf;
 		memlen = uctxt->egrbufs.size * uctxt->subctxt_cnt;
-		flags |= VM_IO | VM_DONTEXPAND;
 		flags &= ~VM_MAYWRITE;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	case SDMA_COMP: {
 		struct hfi1_user_sdma_comp_q *cq = fd->cq;
@@ -539,10 +525,9 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 			ret = -EFAULT;
 			goto done;
 		}
-		memaddr = (u64)cq->comps;
+		memvirt = cq->comps;
 		memlen = PAGE_ALIGN(sizeof(*cq->comps) * cq->nentries);
-		flags |= VM_IO | VM_DONTEXPAND;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	}
 	default:
@@ -559,12 +544,10 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 	}
 
 	vm_flags_reset(vma, flags);
-	mmap_cdbg(ctxt, subctxt, type, mapio, vmf, memaddr, memvirt, memdma, 
+	mmap_cdbg(ctxt, subctxt, type, mapio, is_vmalloc, memaddr, memvirt, memdma,
 		  memlen, vma);
-	if (vmf) {
-		vma->vm_pgoff = PFN_DOWN(memaddr);
-		vma->vm_ops = &vm_ops;
-		ret = 0;
+	if (is_vmalloc) {
+		ret = remap_vmalloc_range(vma, memvirt, 0);
 	} else if (memdma) {
 		ret = dma_mmap_coherent(&dd->pcidev->dev, vma,
 					memvirt, memdma, memlen);
@@ -588,24 +571,6 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 	return ret;
 }
 
-/*
- * Local (non-chip) user memory is not mapped right away but as it is
- * accessed by the user-level code.
- */
-static vm_fault_t vma_fault(struct vm_fault *vmf)
-{
-	struct page *page;
-
-	page = vmalloc_to_page((void *)(vmf->pgoff << PAGE_SHIFT));
-	if (!page)
-		return VM_FAULT_SIGBUS;
-
-	get_page(page);
-	vmf->page = page;
-
-	return 0;
-}
-
 static __poll_t hfi1_poll(struct file *fp, struct poll_table_struct *pt)
 {
 	struct hfi1_ctxtdata *uctxt;
diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c
index 5408f002e6c01..12837b828b89f 100644
--- a/drivers/scsi/sg.c
+++ b/drivers/scsi/sg.c
@@ -1212,85 +1212,72 @@ sg_fasync(int fd, struct file *filp, int mode)
 	return fasync_helper(fd, filp, mode, &sfp->async_qp);
 }
 
-static vm_fault_t
-sg_vma_fault(struct vm_fault *vmf)
+static int sg_discontig_init(void *vm_private_data, void **private)
 {
-	struct vm_area_struct *vma = vmf->vma;
-	Sg_fd *sfp;
-	unsigned long offset, len, sa;
-	Sg_scatter_hold *rsv_schp;
-	int k, length;
-
-	if ((NULL == vma) || (!(sfp = (Sg_fd *) vma->vm_private_data)))
-		return VM_FAULT_SIGBUS;
-	rsv_schp = &sfp->reserve;
-	offset = vmf->pgoff << PAGE_SHIFT;
-	if (offset >= rsv_schp->bufflen)
-		return VM_FAULT_SIGBUS;
-	SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sfp->parentdp,
-				      "sg_vma_fault: offset=%lu, scatg=%d\n",
-				      offset, rsv_schp->k_use_sg));
-	sa = vma->vm_start;
-	length = 1 << (PAGE_SHIFT + rsv_schp->page_order);
-	for (k = 0; k < rsv_schp->k_use_sg && sa < vma->vm_end; k++) {
-		len = vma->vm_end - sa;
-		len = (len < length) ? len : length;
-		if (offset < len) {
-			struct page *page = rsv_schp->pages[k] + (offset >> PAGE_SHIFT);
-			get_page(page);	/* increment page count */
-			vmf->page = page;
-			return 0; /* success */
-		}
-		sa += len;
-		offset -= len;
+	const unsigned long req_sz = (unsigned long)*private;
+	Sg_fd *sfp = vm_private_data;
+	Sg_scatter_hold *rsv_schp = &sfp->reserve;
+	int err = 0;
+
+	mutex_lock(&sfp->f_mutex);
+	if (req_sz > rsv_schp->bufflen) {
+		err = -ENOMEM;	/* cannot map more than reserved buffer */
+		goto out;
+	}
+	sfp->mmap_called = 1; /* Prevents changes to buffer size. */
+out:
+	mutex_unlock(&sfp->f_mutex);
+	return err;
+}
+
+static int
+sg_discontig_get(struct discontig_kernel_page_state *state)
+{
+	Sg_fd *sfp = state->vm_private_data;
+	Sg_scatter_hold *rsv_schp = &sfp->reserve;
+	const unsigned int order = rsv_schp->page_order;
+	const pgoff_t nr_pages = state->nr_pages_mapped;
+
+	if (nr_pages >= (rsv_schp->bufflen >> PAGE_SHIFT)) {
+		discontig_kernel_map_abort(state);
+		return 0;
 	}
 
-	return VM_FAULT_SIGBUS;
+	SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sfp->parentdp,
+				      "sg_discontig_get: offset=%lu, scatg=%d\n",
+				      nr_pages << PAGE_SHIFT, rsv_schp->k_use_sg));
+
+	discontig_kernel_map_page(state, rsv_schp->pages[nr_pages >> order]);
+	return 0;
 }
 
-static const struct vm_operations_struct sg_mmap_vm_ops = {
-	.fault = sg_vma_fault,
+static const struct discontig_kernel_page_ops sg_discontig_ops = {
+	.init = sg_discontig_init,
+	.get = sg_discontig_get,
 };
 
 static int
-sg_mmap(struct file *filp, struct vm_area_struct *vma)
+sg_mmap_prepare(struct vm_area_desc *desc)
 {
-	Sg_fd *sfp;
-	unsigned long req_sz, len, sa;
-	Sg_scatter_hold *rsv_schp;
-	int k, length;
-	int ret = 0;
+	Sg_fd *sfp = desc->file->private_data;
+	const unsigned long req_sz = vma_desc_size(desc);
 
-	if ((!filp) || (!vma) || (!(sfp = (Sg_fd *) filp->private_data)))
+	if (!sfp)
 		return -ENXIO;
-	req_sz = vma->vm_end - vma->vm_start;
+
 	SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sfp->parentdp,
 				      "sg_mmap starting, vm_start=%p, len=%d\n",
-				      (void *) vma->vm_start, (int) req_sz));
-	if (vma->vm_pgoff)
+				      (void *) desc->start, (int) req_sz));
+
+	if (desc->pgoff)
 		return -EINVAL;	/* want no offset */
-	rsv_schp = &sfp->reserve;
-	mutex_lock(&sfp->f_mutex);
-	if (req_sz > rsv_schp->bufflen) {
-		ret = -ENOMEM;	/* cannot map more than reserved buffer */
-		goto out;
-	}
 
-	sa = vma->vm_start;
-	length = 1 << (PAGE_SHIFT + rsv_schp->page_order);
-	for (k = 0; k < rsv_schp->k_use_sg && sa < vma->vm_end; k++) {
-		len = vma->vm_end - sa;
-		len = (len < length) ? len : length;
-		sa += len;
-	}
+	vma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT);
+	desc->private_data = sfp;
 
-	sfp->mmap_called = 1;
-	vm_flags_set(vma, VM_IO | VM_DONTEXPAND | VM_DONTDUMP);
-	vma->vm_private_data = sfp;
-	vma->vm_ops = &sg_mmap_vm_ops;
-out:
-	mutex_unlock(&sfp->f_mutex);
-	return ret;
+	mmap_action_map_discontig_kernel_pages(desc, (void *)req_sz,
+					       &sg_discontig_ops);
+	return 0;
 }
 
 static void
@@ -1415,7 +1402,7 @@ static const struct file_operations sg_fops = {
 	.unlocked_ioctl = sg_ioctl,
 	.compat_ioctl = compat_ptr_ioctl,
 	.open = sg_open,
-	.mmap = sg_mmap,
+	.mmap_prepare = sg_mmap_prepare,
 	.release = sg_release,
 	.fasync = sg_fasync,
 };
diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c
index 687f6a8981f34..9d00b21a8153b 100644
--- a/drivers/usb/mon/mon_bin.c
+++ b/drivers/usb/mon/mon_bin.c
@@ -1219,6 +1219,15 @@ mon_bin_poll(struct file *file, struct poll_table_struct *wait)
 	return mask;
 }
 
+static void __mon_bin_vma_open(struct mon_reader_bin *rp)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&rp->b_lock, flags);
+	rp->mmap_active++;
+	spin_unlock_irqrestore(&rp->b_lock, flags);
+}
+
 /*
  * open and close: just keep track of how many times the device is
  * mapped, to use the proper memory allocation function.
@@ -1226,64 +1235,79 @@ mon_bin_poll(struct file *file, struct poll_table_struct *wait)
 static void mon_bin_vma_open(struct vm_area_struct *vma)
 {
 	struct mon_reader_bin *rp = vma->vm_private_data;
-	unsigned long flags;
 
-	spin_lock_irqsave(&rp->b_lock, flags);
-	rp->mmap_active++;
-	spin_unlock_irqrestore(&rp->b_lock, flags);
+	__mon_bin_vma_open(rp);
 }
 
-static void mon_bin_vma_close(struct vm_area_struct *vma)
+static void __mon_bin_vma_close(struct mon_reader_bin *rp)
 {
 	unsigned long flags;
 
-	struct mon_reader_bin *rp = vma->vm_private_data;
 	spin_lock_irqsave(&rp->b_lock, flags);
 	rp->mmap_active--;
 	spin_unlock_irqrestore(&rp->b_lock, flags);
 }
 
-/*
- * Map ring pages to user space.
- */
-static vm_fault_t mon_bin_vma_fault(struct vm_fault *vmf)
+static void mon_bin_vma_close(struct vm_area_struct *vma)
 {
-	struct mon_reader_bin *rp = vmf->vma->vm_private_data;
+	struct mon_reader_bin *rp = vma->vm_private_data;
+
+	__mon_bin_vma_close(rp);
+}
+
+static const struct vm_operations_struct mon_bin_vm_ops = {
+	.open =     mon_bin_vma_open,
+	.close =    mon_bin_vma_close,
+};
+
+static int mon_bin_discontig_init(void *vm_private_data, void **private)
+{
+	struct mon_reader_bin *rp = vm_private_data;
+
+	/* Dropped by mon_bin_vma_close() on unmap, including on error. */
+	__mon_bin_vma_open(rp);
+	return 0;
+}
+
+static int mon_bin_discontig_get(struct discontig_kernel_page_state *state)
+{
+	struct mon_reader_bin *rp = state->vm_private_data;
 	unsigned long offset, chunk_idx;
-	struct page *pageptr;
 	unsigned long flags;
 
 	spin_lock_irqsave(&rp->b_lock, flags);
-	offset = vmf->pgoff << PAGE_SHIFT;
+
+	offset = state->pgoff << PAGE_SHIFT;
 	if (offset >= rp->b_size) {
 		spin_unlock_irqrestore(&rp->b_lock, flags);
-		return VM_FAULT_SIGBUS;
+		discontig_kernel_map_abort(state);
+		return 0;
 	}
 	chunk_idx = offset / CHUNK_SIZE;
-	pageptr = rp->b_vec[chunk_idx].pg;
-	get_page(pageptr);
-	vmf->page = pageptr;
+	discontig_kernel_map_page(state, rp->b_vec[chunk_idx].pg);
+
 	spin_unlock_irqrestore(&rp->b_lock, flags);
 	return 0;
 }
 
-static const struct vm_operations_struct mon_bin_vm_ops = {
-	.open =     mon_bin_vma_open,
-	.close =    mon_bin_vma_close,
-	.fault =    mon_bin_vma_fault,
+static const struct discontig_kernel_page_ops mon_discontig_ops = {
+	.init = mon_bin_discontig_init,
+	.get = mon_bin_discontig_get,
 };
 
-static int mon_bin_mmap(struct file *filp, struct vm_area_struct *vma)
+static int mon_bin_mmap_prepare(struct vm_area_desc *desc)
 {
-	/* don't do anything here: "fault" will set up page table entries */
-	vma->vm_ops = &mon_bin_vm_ops;
+	const struct file *filp = desc->file;
 
-	if (vma->vm_flags & VM_WRITE)
+	if (vma_desc_test(desc, VMA_WRITE_BIT))
 		return -EPERM;
 
-	vm_flags_mod(vma, VM_DONTEXPAND | VM_DONTDUMP, VM_MAYWRITE);
-	vma->vm_private_data = filp->private_data;
-	mon_bin_vma_open(vma);
+	desc->vm_ops = &mon_bin_vm_ops;
+	vma_desc_clear_flags(desc, VMA_MAYWRITE_BIT);
+	vma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT);
+	desc->private_data = filp->private_data;
+
+	mmap_action_map_discontig_kernel_pages(desc, NULL, &mon_discontig_ops);
 	return 0;
 }
 
@@ -1298,7 +1322,7 @@ static const struct file_operations mon_fops_binary = {
 	.compat_ioctl =	mon_bin_compat_ioctl,
 #endif
 	.release =	mon_bin_release,
-	.mmap =		mon_bin_mmap,
+	.mmap_prepare = mon_bin_mmap_prepare,
 };
 
 static int mon_bin_wait_event(struct file *file, struct mon_reader_bin *rp)
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
index fd00b86e1ae60..fb359ecc39661 100644
--- a/drivers/video/fbdev/core/fb_defio.c
+++ b/drivers/video/fbdev/core/fb_defio.c
@@ -366,13 +366,13 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
 {
 	vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
 
+	if (WARN_ON_ONCE(!(info->flags & FBINFO_VIRTFB)))
+		return -EINVAL;
 	if (!try_module_get(THIS_MODULE))
 		return -EINVAL;
 
 	vma->vm_ops = &fb_deferred_io_vm_ops;
-	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
-	if (!(info->flags & FBINFO_VIRTFB))
-		vm_flags_set(vma, VM_IO);
+	vm_flags_set(vma, VM_MIXEDMAP | VM_DONTEXPAND | VM_DONTDUMP);
 	vma->vm_private_data = info->fbdefio_state;
 
 	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c
index c4fdecafd8560..958514a354338 100644
--- a/drivers/video/fbdev/ssd1307fb.c
+++ b/drivers/video/fbdev/ssd1307fb.c
@@ -763,6 +763,8 @@ static int ssd1307fb_probe(struct i2c_client *client)
 	info->fix.smem_start = __pa(vmem);
 	info->fix.smem_len = vmem_size;
 
+	info->flags = FBINFO_VIRTFB;
+
 	fb_deferred_io_init(info);
 
 	i2c_set_clientdata(client, info);
diff --git a/fs/coredump.c b/fs/coredump.c
index ac3cd74808c64..9f729c594c47e 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -1608,7 +1608,7 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
 	}
 
 	/* Hugetlb memory check */
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		if ((vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_SHARED))
 			goto whole;
 		if (!(vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_PRIVATE))
@@ -1616,8 +1616,8 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
 		return 0;
 	}
 
-	/* Do not dump I/O mapped devices or special mappings */
-	if (vma->vm_flags & VM_IO)
+	/* Do not dump memory-mapped I/O, which may have side effects on read. */
+	if (vma_test(vma, VMA_IO_BIT))
 		return 0;
 
 	/* By default, dump shared memory if mapped from an anonymous file. */
diff --git a/fs/fuse/dax.c b/fs/fuse/dax.c
index 85cdf0199bc0b..a5994f1c637d9 100644
--- a/fs/fuse/dax.c
+++ b/fs/fuse/dax.c
@@ -826,7 +826,7 @@ int fuse_dax_mmap(struct file *file, struct vm_area_struct *vma)
 {
 	file_accessed(file);
 	vma->vm_ops = &fuse_dax_vm_ops;
-	vm_flags_set(vma, VM_MIXEDMAP | VM_HUGEPAGE);
+	vma_set_flags(vma, VMA_HUGEPAGE_BIT);
 	return 0;
 }
 
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 7611a8470ea26..ba7097d5720c0 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -108,7 +108,7 @@ static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
 	 * vma address alignment (but not the pgoff alignment) has
 	 * already been checked by prepare_hugepage_range.  If you add
 	 * any error returns here, do so after setting VM_HUGETLB, so
-	 * is_vm_hugetlb_page tests below unmap_region go the right
+	 * vma_is_hugetlb tests below unmap_region go the right
 	 * way when do_mmap unwinds (may be important on powerpc
 	 * and ia64).
 	 */
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index e671b4fd8dedd..565e6446bd312 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -3015,7 +3015,7 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,
 	 * hugetlb differs, see pagemap_hugetlb_category().
 	 */
 	categories = p->cur_vma_category;
-	if (userfaultfd_wp(vma) && !is_vm_hugetlb_page(vma))
+	if (userfaultfd_wp(vma) && !vma_is_hugetlb(vma))
 		categories |= PAGE_IS_WRITTEN;
 
 	if (!pagemap_scan_is_interesting_page(categories, p))
@@ -3028,7 +3028,7 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,
 	if (~p->arg.flags & PM_SCAN_WP_MATCHING)
 		return ret;
 
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		err = pagemap_scan_hugetlb_hole_wp(vma, addr, end);
 	else
 		err = uffd_wp_range(vma, addr, end - addr, true);
@@ -3470,7 +3470,7 @@ static int show_numa_map(struct seq_file *m, void *v)
 		seq_puts(m, " stack");
 	}
 
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		seq_puts(m, " huge");
 
 	/* Skip walking pages if gate VMA */
@@ -3499,7 +3499,7 @@ static int show_numa_map(struct seq_file *m, void *v)
 	if (md->swapcache)
 		seq_printf(m, " swapcache=%lu", md->swapcache);
 
-	if (md->active < md->pages && !is_vm_hugetlb_page(vma))
+	if (md->active < md->pages && !vma_is_hugetlb(vma))
 		seq_printf(m, " active=%lu", md->active);
 
 	if (md->writeback)
diff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h
index bdcc2778ac64f..dfb5dd3bec409 100644
--- a/include/asm-generic/tlb.h
+++ b/include/asm-generic/tlb.h
@@ -11,9 +11,9 @@
 #ifndef _ASM_GENERIC__TLB_H
 #define _ASM_GENERIC__TLB_H
 
+#include <linux/mm.h>
 #include <linux/mmu_notifier.h>
 #include <linux/swap.h>
-#include <linux/hugetlb_inline.h>
 #include <asm/tlbflush.h>
 #include <asm/cacheflush.h>
 
@@ -486,7 +486,7 @@ tlb_update_vma_flags(struct mmu_gather *tlb, struct vm_area_struct *vma)
 	 * We rely on tlb_end_vma() to issue a flush, such that when we reset
 	 * these values the batch is empty.
 	 */
-	tlb->vma_huge = is_vm_hugetlb_page(vma);
+	tlb->vma_huge = vma_is_hugetlb(vma);
 	tlb->vma_exec = !!(vma->vm_flags & VM_EXEC);
 
 	/*
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 80a5a03e9cee7..24727ece20fe5 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -7,7 +7,6 @@
 #include <linux/mm_types.h>
 #include <linux/mmdebug.h>
 #include <linux/fs.h>
-#include <linux/hugetlb_inline.h>
 #include <linux/cgroup.h>
 #include <linux/page_ref.h>
 #include <linux/list.h>
@@ -252,14 +251,14 @@ extern void __hugetlb_zap_end(struct vm_area_struct *vma,
 static inline void hugetlb_zap_begin(struct vm_area_struct *vma,
 				     unsigned long *start, unsigned long *end)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		__hugetlb_zap_begin(vma, start, end);
 }
 
 static inline void hugetlb_zap_end(struct vm_area_struct *vma,
 				   struct zap_details *details)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		__hugetlb_zap_end(vma, details);
 }
 
diff --git a/include/linux/hugetlb_inline.h b/include/linux/hugetlb_inline.h
deleted file mode 100644
index 5c29cd3223a1e..0000000000000
--- a/include/linux/hugetlb_inline.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef _LINUX_HUGETLB_INLINE_H
-#define _LINUX_HUGETLB_INLINE_H
-
-#include <linux/mm.h>
-
-#ifdef CONFIG_HUGETLB_PAGE
-
-static inline bool is_vma_hugetlb_flags(const vma_flags_t *flags)
-{
-	return vma_flags_test(flags, VMA_HUGETLB_BIT);
-}
-
-#else
-
-static inline bool is_vma_hugetlb_flags(const vma_flags_t *flags)
-{
-	return false;
-}
-
-#endif
-
-static inline bool is_vm_hugetlb_page(const struct vm_area_struct *vma)
-{
-	return is_vma_hugetlb_flags(&vma->flags);
-}
-
-#endif
diff --git a/include/linux/mm.h b/include/linux/mm.h
index c49ef99b4413b..1902d4c774817 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -576,14 +576,6 @@ enum {
 #define VM_ACCESS_FLAGS (VM_READ | VM_WRITE | VM_EXEC)
 #define VMA_ACCESS_FLAGS mk_vma_flags(VMA_READ_BIT, VMA_WRITE_BIT, VMA_EXEC_BIT)
 
-/*
- * Special vmas that are non-mergable, non-mlock()able.
- */
-
-#define VMA_SPECIAL_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_DONTEXPAND_BIT, \
-				       VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT)
-#define VM_SPECIAL vma_flags_to_legacy(VMA_SPECIAL_FLAGS)
-
 /*
  * Physically remapped pages are special. Tell the
  * rest of the world about it:
@@ -600,9 +592,6 @@ enum {
 #define VMA_REMAP_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_PFNMAP_BIT,	\
 				     VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT)
 
-/* This mask prevents VMA from being scanned with khugepaged */
-#define VM_NO_KHUGEPAGED (VM_SPECIAL | VM_HUGETLB)
-
 /* This mask defines which mm->def_flags a process can inherit its parent */
 #define VM_INIT_DEF_MASK	VM_NOHUGEPAGE
 
@@ -1612,6 +1601,211 @@ static inline bool vma_is_shared_maywrite(const struct vm_area_struct *vma)
 	return is_shared_maywrite(&vma->flags);
 }
 
+/**
+ * vma_flags_is_hugetlb() - Do the specified VMA flags indicate that the
+ * VMA is a hugetlb mapping?
+ * @flags: The VMA flags to test.
+ *
+ * Returns: true if the flags indicate a hugetlb mapping, false otherwise.
+ */
+static inline bool vma_flags_is_hugetlb(const vma_flags_t *flags)
+{
+	return IS_ENABLED(CONFIG_HUGETLB_PAGE) &&
+	       vma_flags_test(flags, VMA_HUGETLB_BIT);
+}
+
+/**
+ * vma_is_hugetlb() - Is @vma a hugetlb mapping?
+ * @vma: The VMA to test.
+ *
+ * Returns: true if @vma is a hugetlb mapping, false otherwise.
+ */
+static inline bool vma_is_hugetlb(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_hugetlb(&vma->flags);
+}
+
+/**
+ * vma_flags_is_kernel_owned() - Do the specified VMA flags indicate that the
+ * contents of the VMA are owned by the kernel rather than the core mm?
+ * @flags: The VMA flags to test.
+ *
+ * A kernel-owned mapping is one whose contents are established and controlled
+ * by the kernel, typically a driver, rather than by the core mm's fault and
+ * rmap machinery.
+ *
+ * The mapping may be memory-mapped I/O, kernel-allocated pages or ordinary
+ * pages the owner has chosen to map itself (shmem via a PFN map, for instance).
+ *
+ * But in all cases core mm must not populate, reclaim, migrate, Copy-on-Write
+ * or merge it of its own accord.
+ *
+ * The pages mapped, if any, may or may not be reference counted or map counted.
+ *
+ * Returns: true if the flags indicate a kernel-owned mapping.
+ */
+static inline bool vma_flags_is_kernel_owned(const vma_flags_t *flags)
+{
+	return vma_flags_test_any(flags, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT);
+}
+
+/**
+ * vma_is_kernel_owned() - Are the contents of @vma owned by the kernel?
+ * @vma: The VMA to test.
+ *
+ * See vma_flags_is_kernel_owned() for a description of this property.
+ *
+ * Returns: true if the VMA is kernel-owned.
+ */
+static inline bool vma_is_kernel_owned(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_kernel_owned(&vma->flags);
+}
+
+/**
+ * vma_flags_is_fixed_mapping() - Do the specified VMA flags indicate that this
+ * is a fixed mapping that cannot be expanded or merged?
+ * @flags: The VMA flags to test.
+ *
+ * Fixed mappings are those whose size is set at the point of mmap (for
+ * instance, a kernel-owned mapping of a fixed range of memory), and thus
+ * cannot be expanded or merged.
+ *
+ * Returns: true if the flags indicate a fixed mapping.
+ */
+static inline bool vma_flags_is_fixed_mapping(const vma_flags_t *flags)
+{
+	/*
+	 * VMA_PFNMAP_BIT should imply VMA_DONTEXPAND_BIT, but some callers set
+	 * only the former.
+	 */
+	return vma_flags_test_any(flags, VMA_PFNMAP_BIT, VMA_DONTEXPAND_BIT);
+}
+
+/**
+ * vma_is_fixed_mapping() - Is this VMA a fixed mapping that cannot be
+ * expanded or merged?
+ * @vma: The VMA to test.
+ *
+ * See vma_flags_is_fixed_mapping() for a description of this property.
+ *
+ * Returns: true if the VMA maps a fixed mapping.
+ */
+static inline bool vma_is_fixed_mapping(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_fixed_mapping(&vma->flags);
+}
+
+/**
+ * vma_flags_can_merge() - Do the specified VMA flags permit the VMA to be
+ * merged with another?
+ * @flags: The VMA flags to test.
+ * Returns: true if the flags permit merging, false otherwise.
+ */
+static inline bool vma_flags_can_merge(const vma_flags_t *flags)
+{
+	/*
+	 * VMA merging assumes that a VMA's flags and fields completely describe
+	 * its state.
+	 *
+	 * However, kernel-owned mappings may have established state upon mapping
+	 * not embodied in any attribute of the VMA.
+	 *
+	 * Additionally, private (CoW) PFN maps encode the source PFN of the
+	 * range in vma->vm_pgoff, which may otherwise cause spurious merges.
+	 */
+	if (vma_flags_is_kernel_owned(flags))
+		return false;
+	/* VMA explicitly marked as being unmergeable. */
+	if (vma_flags_is_fixed_mapping(flags))
+		return false;
+
+	return true;
+}
+
+/**
+ * vma_can_merge() - Do @vma's flags permit it to be merged with another VMA?
+ * @vma: The VMA to test.
+ * Returns: true if the flags permit merging, otherwise false.
+ */
+static inline bool vma_can_merge(const struct vm_area_struct *vma)
+{
+	return vma_flags_can_merge(&vma->flags);
+}
+
+/**
+ * vma_flags_is_persistent() - Do the specified VMA flags imply that the VMA
+ * contains persistent data?
+ * @flags: The VMA flags to test.
+ *
+ * Persistent in the sense that - if you write bytes to the mapping - do they
+ * stay written?
+ *
+ * If the kernel or a device could write to the memory independently of
+ * userland, or the kernel could arbitrarily discard it, then it is not
+ * persistent.
+ *
+ * Returns: true if the flags imply this VMA is persistent, otherwise false.
+ */
+static inline bool vma_flags_is_persistent(const vma_flags_t *flags)
+{
+	/* hugetlb is a fixed mapping, but its contents are the user's own. */
+	if (vma_flags_is_hugetlb(flags))
+		return true;
+	/*
+	 * MMIO mappings may not store what is written and may be changed by the
+	 * device. Kernel-owned and fixed mappings may be changed by their owner
+	 * without the user having initiated it.
+	 */
+	if (vma_flags_is_kernel_owned(flags) ||
+	    vma_flags_is_fixed_mapping(flags))
+		return false;
+	/* Droppable memory is discardable by definition. */
+	return !vma_flags_test_single_mask(flags, VMA_DROPPABLE);
+}
+
+/**
+ * vma_is_persistent() - Does the VMA contain persistent data?
+ * @vma: The VMA to test.
+ *
+ * See vma_flags_is_persistent() for details.
+ *
+ * Returns: true if the VMA is persistent, otherwise false.
+ */
+static inline bool vma_is_persistent(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_persistent(&vma->flags);
+}
+
+/**
+ * vma_flags_can_gup() - Do the specified VMA flags permit GUP to access the
+ * mapping's pages?
+ * @flags: The VMA flags to test.
+ *
+ * GUP cannot access pages belonging to mappings whose pages are not permitted
+ * to be accessed (VMA_PFNMAP_BIT) and must not manipulate or provide access to
+ * memory-mapped I/O ranges to users (VMA_IO_BIT).
+ *
+ * Returns: true if GUP may access pages from the mapping, otherwise false.
+ */
+static inline bool vma_flags_can_gup(const vma_flags_t *flags)
+{
+	return !vma_flags_test_any(flags, VMA_IO_BIT, VMA_PFNMAP_BIT);
+}
+
+/**
+ * vma_can_gup() - May GUP obtain pages from @vma?
+ * @vma: The VMA to test.
+ *
+ * See vma_flags_can_gup() for details.
+ *
+ * Returns: true if GUP may access pages from the mapping, otherwise false.
+ */
+static inline bool vma_can_gup(const struct vm_area_struct *vma)
+{
+	return vma_flags_can_gup(&vma->flags);
+}
+
 /**
  * vma_kernel_pagesize - Default page size granularity for this VMA.
  * @vma: The user mapping.
@@ -4602,7 +4796,7 @@ static inline void mmap_action_map_kernel_pages(struct vm_area_desc *desc,
 {
 	struct mmap_action *action = &desc->action;
 
-	action->type = MMAP_MAP_KERNEL_PAGES;
+	action->type = MMAP_KERNEL_PAGES;
 	action->map_kernel.start = start;
 	action->map_kernel.pages = pages;
 	action->map_kernel.nr_pages = nr_pages;
@@ -4626,10 +4820,55 @@ static inline void mmap_action_map_kernel_pages_full(struct vm_area_desc *desc,
 				     vma_desc_pages(desc));
 }
 
+static inline
+void mmap_action_map_discontig_kernel_pages(struct vm_area_desc *desc,
+		void *init_private, const struct discontig_kernel_page_ops *ops)
+{
+	struct mmap_action *action = &desc->action;
+
+	action->type = MMAP_DISCONTIG_KERNEL_PAGES;
+	action->map_kernel_discontig.init_private = init_private;
+	action->map_kernel_discontig.ops = ops;
+}
+
 int mmap_action_prepare(struct vm_area_desc *desc);
 int mmap_action_complete(struct vm_area_struct *vma,
 			 struct mmap_action *action, bool is_compat);
 
+static inline void
+discontig_kernel_map_abort(struct discontig_kernel_page_state *state)
+{
+	state->action = DISCONTIG_KERNEL_PAGE_ABORT;
+}
+
+static inline void
+discontig_kernel_map_page(struct discontig_kernel_page_state *state,
+			  struct page *page)
+{
+	struct folio *folio = page_folio(page);
+
+	if (folio_test_large(folio)) {
+		VM_WARN_ON_ONCE(page != folio_page(folio, 0));
+		state->action = DISCONTIG_KERNEL_PAGE_MAP_COMPOUND_PAGE;
+		state->__folio = folio;
+		state->__nr_pages = min(state->nr_pages_remain,
+					folio_nr_pages(folio));
+	} else {
+		state->action = DISCONTIG_KERNEL_PAGE_MAP_PAGE;
+		state->__page = page;
+		state->__nr_pages = 1;
+	}
+}
+
+static inline void
+discontig_kernel_map_page_range(struct discontig_kernel_page_state *state,
+				struct page **page_arr, unsigned long nr_pages)
+{
+	state->action = DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE;
+	state->__page_arr = page_arr;
+	state->__nr_pages = nr_pages;
+}
+
 /* Look up the first VMA which exactly match the interval vm_start ... vm_end */
 static inline struct vm_area_struct *find_exact_vma(struct mm_struct *mm,
 				unsigned long vm_start, unsigned long vm_end)
@@ -4747,9 +4986,6 @@ int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
 int vm_insert_page(struct vm_area_struct *, unsigned long addr, struct page *);
 int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,
 			struct page **pages, unsigned long *num);
-int map_kernel_pages_prepare(struct vm_area_desc *desc);
-int map_kernel_pages_complete(struct vm_area_struct *vma,
-			      struct mmap_action *action);
 int vm_map_pages(struct vm_area_struct *vma, struct page **pages,
 				unsigned long num);
 int vm_map_pages_zero(struct vm_area_struct *vma, struct page **pages,
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 5413bd10fff2c..0cb4f96039568 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -815,11 +815,47 @@ struct pfnmap_track_ctx {
 
 /* What action should be taken after an .mmap_prepare call is complete? */
 enum mmap_action_type {
-	MMAP_NOTHING,		/* Mapping is complete, no further action. */
-	MMAP_REMAP_PFN,		/* Remap PFN range. */
-	MMAP_IO_REMAP_PFN,	/* I/O remap PFN range. */
-	MMAP_SIMPLE_IO_REMAP,	/* I/O remap with guardrails. */
-	MMAP_MAP_KERNEL_PAGES,	/* Map kernel page range from array. */
+	MMAP_NOTHING,
+	MMAP_REMAP_PFN,
+	MMAP_IO_REMAP_PFN,
+	MMAP_SIMPLE_IO_REMAP,		/* I/O remap with guardrails. */
+	MMAP_KERNEL_PAGES,		/* Map kernel page range from array. */
+	MMAP_DISCONTIG_KERNEL_PAGES,	/* Map kernel discontig page range. */
+};
+
+enum discontig_kernel_page_action {
+	DISCONTIG_KERNEL_PAGE_ABORT,
+	DISCONTIG_KERNEL_PAGE_MAP_PAGE,
+	DISCONTIG_KERNEL_PAGE_MAP_COMPOUND_PAGE,
+	DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE,
+};
+
+struct discontig_kernel_page_state {
+	/* Map state. */
+	const unsigned long start;	/* Start address of VMA. */
+	const unsigned long end;	/* End address of VMA. */
+	unsigned long addr;		/* The current address to be mapped. */
+	pgoff_t pgoff;			/* The current pgoff to be mapped. */
+	unsigned long nr_pages_mapped;	/* The number of pages mapped. */
+	unsigned long nr_pages_remain;	/* The number of pages remaining. */
+
+	/* User-defined state. */
+	void *vm_private_data;		/* VMA private data. */
+	void *private;			/* Mapping private data. */
+
+	/* Users should not touch these, use discontig_kernel_map_*() helpers. */
+	enum discontig_kernel_page_action action;
+	union {
+		struct page *__page;
+		struct folio *__folio;
+		struct page **__page_arr;
+	};
+	unsigned long __nr_pages;
+};
+
+struct discontig_kernel_page_ops {
+	int (*init)(void *vm_private_data, void **private);
+	int (*get)(struct discontig_kernel_page_state *state);
 };
 
 /*
@@ -844,6 +880,10 @@ struct mmap_action {
 			unsigned long nr_pages;
 			pgoff_t pgoff;
 		} map_kernel;
+		struct {
+			void *init_private;
+			const struct discontig_kernel_page_ops *ops;
+		} map_kernel_discontig;
 	};
 	enum mmap_action_type type;
 
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 939f3a5e973f6..d7d8b312466c2 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -14,7 +14,6 @@
 #include <linux/gfp.h>
 #include <linux/bitops.h>
 #include <linux/hardirq.h> /* for in_interrupt() */
-#include <linux/hugetlb_inline.h>
 
 struct folio_batch;
 
diff --git a/include/linux/rmap.h b/include/linux/rmap.h
index 0b332770abeed..74cca0e3c7264 100644
--- a/include/linux/rmap.h
+++ b/include/linux/rmap.h
@@ -888,7 +888,7 @@ struct page_vma_mapped_walk {
 static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)
 {
 	/* HugeTLB pte is set to the relevant page table entry without pte_mapped. */
-	if (pvmw->pte && !is_vm_hugetlb_page(pvmw->vma))
+	if (pvmw->pte && !vma_is_hugetlb(pvmw->vma))
 		pte_unmap(pvmw->pte);
 	if (pvmw->ptl)
 		spin_unlock(pvmw->ptl);
diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h
index a4351cffc60ce..a14b8a9ffb7b1 100644
--- a/include/linux/userfaultfd_k.h
+++ b/include/linux/userfaultfd_k.h
@@ -18,7 +18,6 @@
 #include <linux/swap.h>
 #include <linux/leafops.h>
 #include <asm-generic/pgtable_uffd.h>
-#include <linux/hugetlb_inline.h>
 
 /* The set of all possible UFFD-related VM flags. */
 #define __VM_UFFD_FLAGS (VM_UFFD_MISSING | VM_UFFD_MINOR | \
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 7b6847200b431..b69fe5e343393 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -620,8 +620,9 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)
 	 * clears VM_MAYEXEC. Set VM_DONTEXPAND to avoid potential change
 	 * of user_vm_start. Set VM_DONTCOPY to prevent arena VMA from
 	 * being copied into the child process on fork.
+	 * This is a kernel page so set VM_MIXEDMAP.
 	 */
-	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY);
+	vm_flags_set(vma, VM_MIXEDMAP | VM_DONTEXPAND | VM_DONTCOPY);
 	vma->vm_ops = &arena_vm_ops;
 	return 0;
 }
diff --git a/kernel/events/core.c b/kernel/events/core.c
index a6c8e38a31104..8ca8a68429242 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -9808,7 +9808,7 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event)
 
 	if (vma->vm_flags & VM_LOCKED)
 		flags |= MAP_LOCKED;
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		flags |= MAP_HUGETLB;
 
 	if (file) {
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index 7709ea8824778..b89cc5cee0027 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -1726,8 +1726,8 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area)
 	}
 
 	vma = _install_special_mapping(mm, area->vaddr, PAGE_SIZE,
-				VM_EXEC|VM_MAYEXEC|VM_DONTCOPY|VM_IO|
-				VM_SEALED_SYSMAP,
+				VM_EXEC|VM_MAYEXEC|VM_DONTCOPY|
+				VM_MIXEDMAP|VM_SEALED_SYSMAP,
 				&xol_mapping);
 	if (IS_ERR(vma)) {
 		ret = PTR_ERR(vma);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8dff37059faf7..ae6c1a606eb5d 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -22,7 +22,6 @@
  */
 #include <linux/energy_model.h>
 #include <linux/mmap_lock.h>
-#include <linux/hugetlb_inline.h>
 #include <linux/jiffies.h>
 #include <linux/mm_api.h>
 #include <linux/highmem.h>
@@ -4212,7 +4211,7 @@ static void task_numa_work(struct callback_head *work)
 
 	for (; vma; vma = vma_next(&vmi)) {
 		if (!vma_migratable(vma) || !vma_policy_mof(vma) ||
-			is_vm_hugetlb_page(vma) || (vma->vm_flags & VM_MIXEDMAP)) {
+			vma_is_hugetlb(vma) || vma_is_kernel_owned(vma)) {
 			trace_sched_skip_vma_numa(mm, vma, NUMAB_SKIP_UNSUITABLE);
 			continue;
 		}
diff --git a/mm/folio.c b/mm/folio.c
index 50a6dbe55998e..a3f5c463f6654 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -502,7 +502,7 @@ void folio_add_lru_vma(struct folio *folio, struct vm_area_struct *vma)
 {
 	VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
 
-	if (unlikely((vma->vm_flags & (VM_LOCKED | VM_SPECIAL)) == VM_LOCKED))
+	if (vma_test(vma, VMA_LOCKED_BIT))
 		mlock_new_folio(folio);
 	else
 		folio_add_lru(folio);
diff --git a/mm/gup.c b/mm/gup.c
index a4036c02e2137..f4d0cfcb602bf 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -621,7 +621,7 @@ static struct page *no_page_table(struct vm_area_struct *vma,
 	 * But we can only make this optimization where a hole would surely
 	 * be zero-filled if handle_mm_fault() actually did handle it.
 	 */
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		struct hstate *h = hstate_vma(vma);
 
 		if (!hugetlbfs_pagecache_present(h, vma, address))
@@ -1204,7 +1204,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)
 	int foreign = (gup_flags & FOLL_REMOTE);
 	bool vma_anon = vma_is_anonymous(vma);
 
-	if (vm_flags & (VM_IO | VM_PFNMAP))
+	if (!vma_can_gup(vma))
 		return -EFAULT;
 
 	if ((gup_flags & FOLL_ANON) && !vma_anon)
@@ -1213,7 +1213,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)
 	if ((gup_flags & FOLL_LONGTERM) && vma_is_fsdax(vma))
 		return -EOPNOTSUPP;
 
-	if ((gup_flags & FOLL_SPLIT_PMD) && is_vm_hugetlb_page(vma))
+	if ((gup_flags & FOLL_SPLIT_PMD) && vma_is_hugetlb(vma))
 		return -EOPNOTSUPP;
 
 	if (vma_is_secretmem(vma))
@@ -1836,6 +1836,10 @@ long populate_vma_page_range(struct vm_area_struct *vma,
 	if (!vma_is_accessible(vma))
 		return -EFAULT;
 
+	/* Unreadable VMAs also cannot be faulted in. */
+	if (!vma_test(vma, VMA_MAYREAD_BIT))
+		return -EFAULT;
+
 	gup_flags = FOLL_TOUCH;
 	/*
 	 * We want to touch writable mappings with a write fault in order
@@ -1951,7 +1955,7 @@ int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)
 		 * range with the first VMA. Also, skip undesirable VMA types.
 		 */
 		nend = min(end, vma->vm_end);
-		if (vma->vm_flags & (VM_IO | VM_PFNMAP))
+		if (!vma_can_gup(vma))
 			continue;
 		if (nstart < vma->vm_start)
 			nstart = vma->vm_start;
@@ -2013,8 +2017,7 @@ static long __get_user_pages_locked(struct mm_struct *mm, unsigned long start,
 			break;
 
 		/* protect what we can, including chardevs */
-		if ((vma->vm_flags & (VM_IO | VM_PFNMAP)) ||
-		    !(vm_flags & vma->vm_flags))
+		if (!vma_can_gup(vma) || !(vm_flags & vma->vm_flags))
 			break;
 
 		if (pages) {
diff --git a/mm/hmm.c b/mm/hmm.c
index 2f1e98c6b6440..e9569b82a1f0c 100644
--- a/mm/hmm.c
+++ b/mm/hmm.c
@@ -595,8 +595,7 @@ static int hmm_vma_walk_test(unsigned long start, unsigned long end,
 	struct hmm_range *range = hmm_vma_walk->range;
 	struct vm_area_struct *vma = walk->vma;
 
-	if (!(vma->vm_flags & (VM_IO | VM_PFNMAP)) &&
-	    vma->vm_flags & VM_READ)
+	if (vma_can_gup(vma) && vma_test(vma, VMA_READ_BIT))
 		return 0;
 
 	/*
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index dd66c6ad5af13..1ec1cd970ce68 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -110,14 +110,6 @@ static inline bool file_thp_enabled(const struct vm_area_struct *vma)
 	return S_ISREG(inode->i_mode);
 }
 
-/* If returns true, we are unable to access the VMA's folios. */
-static bool vma_is_special_huge(const struct vm_area_struct *vma)
-{
-	if (vma_is_dax(vma))
-		return false;
-	return vma_test_any(vma, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT);
-}
-
 static bool vma_file_bypass_thp_tuneables(const struct vm_area_struct *vma,
 		enum tva_type type)
 {
@@ -192,7 +184,7 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
 	/* Check the intersection of requested and supported orders. */
 	if (vma_is_anonymous(vma))
 		supported_orders = THP_ORDERS_ALL_ANON;
-	else if (vma_is_dax(vma) || vma_is_special_huge(vma))
+	else if (vma_is_dax(vma) || vma_is_kernel_owned(vma))
 		supported_orders = THP_ORDERS_ALL_SPECIAL_DAX;
 	else
 		supported_orders = THP_ORDERS_ALL_FILE_DEFAULT;
@@ -212,11 +204,14 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
 		return in_pf ? orders : 0;
 
 	/*
-	 * khugepaged special VMA and hugetlb VMA.
-	 * Must be checked after dax since some dax mappings may have
-	 * VM_MIXEDMAP set.
+	 * khugepaged moves data from VMAs once collapsed, after they have been
+	 * faulted in, relying on refaulting for file-backed memory.
+	 *
+	 * Kernel-owned mappings cannot be reliably reconstructed from page
+	 * faults, and fixed mappings (including hugetlb) may not be marked as
+	 * kernel-owned - precisely the mappings which cannot be merged.
 	 */
-	if (!in_pf && !smaps && (vm_flags & VM_NO_KHUGEPAGED))
+	if (!in_pf && !smaps && !vma_can_merge(vma))
 		return 0;
 
 	/*
@@ -3062,7 +3057,7 @@ int zap_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,
 	orig_pud = pudp_huge_get_and_clear_full(vma, addr, pud, tlb->fullmm);
 	arch_check_zapped_pud(vma, orig_pud);
 	tlb_remove_pud_tlb_entry(tlb, pud, addr);
-	if (vma_is_special_huge(vma)) {
+	if (vma_is_kernel_owned(vma)) {
 		spin_unlock(ptl);
 		/* No zero page support yet */
 	} else {
@@ -3218,7 +3213,7 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
 		 */
 		if (arch_needs_pgtable_deposit())
 			zap_deposited_table(mm, pmd);
-		if (vma_is_special_huge(vma))
+		if (vma_is_kernel_owned(vma))
 			return;
 		if (unlikely(pmd_is_migration_entry(old_pmd))) {
 			const softleaf_t old_entry = softleaf_from_pmd(old_pmd);
@@ -4747,11 +4742,9 @@ static inline bool vma_not_suitable_for_thp_split(struct vm_area_struct *vma)
 {
 	if (vma_is_dax(vma))
 		return true;
-	if (vma_is_special_huge(vma))
-		return true;
-	if (vma_test(vma, VMA_IO_BIT))
+	if (vma_is_kernel_owned(vma))
 		return true;
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return true;
 
 	return false;
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index a69bd463b1aef..d93235491cbca 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1146,7 +1146,7 @@ static inline struct resv_map *inode_resv_map(struct inode *inode)
 
 static struct resv_map *vma_resv_map(struct vm_area_struct *vma)
 {
-	VM_BUG_ON_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 	if (vma->vm_flags & VM_MAYSHARE) {
 		struct address_space *mapping = vma->vm_file->f_mapping;
 		struct inode *inode = mapping->host;
@@ -1161,7 +1161,7 @@ static struct resv_map *vma_resv_map(struct vm_area_struct *vma)
 
 static void set_vma_resv_map(struct vm_area_struct *vma, struct resv_map *map)
 {
-	VM_WARN_ON_ONCE_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 	VM_WARN_ON_ONCE_VMA(vma_test(vma, VMA_MAYSHARE_BIT), vma);
 
 	set_vma_private_data(vma, (unsigned long)map);
@@ -1169,7 +1169,7 @@ static void set_vma_resv_map(struct vm_area_struct *vma, struct resv_map *map)
 
 static void set_vma_resv_flags(struct vm_area_struct *vma, unsigned long flags)
 {
-	VM_WARN_ON_ONCE_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 	VM_WARN_ON_ONCE_VMA(vma_test(vma, VMA_MAYSHARE_BIT), vma);
 
 	set_vma_private_data(vma, get_vma_private_data(vma) | flags);
@@ -1177,7 +1177,7 @@ static void set_vma_resv_flags(struct vm_area_struct *vma, unsigned long flags)
 
 static int is_vma_resv_set(struct vm_area_struct *vma, unsigned long flag)
 {
-	VM_BUG_ON_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 
 	return (get_vma_private_data(vma) & flag) != 0;
 }
@@ -1191,7 +1191,7 @@ bool __vma_private_lock(struct vm_area_struct *vma)
 
 void hugetlb_dup_vma_private(struct vm_area_struct *vma)
 {
-	VM_BUG_ON_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 	/*
 	 * Clear vm_private_data
 	 * - For shared mappings this is a per-vma semaphore that may be
@@ -5269,7 +5269,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
 	unsigned long last_addr_mask;
 
 	i_mmap_assert_write_locked(vma->vm_file->f_mapping);
-	WARN_ON(!is_vm_hugetlb_page(vma));
+	WARN_ON(!vma_is_hugetlb(vma));
 	BUG_ON(start & ~huge_page_mask(h));
 	BUG_ON(end & ~huge_page_mask(h));
 
@@ -7495,6 +7495,6 @@ void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)
  */
 void fixup_hugetlb_reservations(struct vm_area_struct *vma)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		clear_vma_resv_huge_pages(vma);
 }
diff --git a/mm/internal.h b/mm/internal.h
index da14c56fb24e1..6c004037913b3 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -212,6 +212,24 @@ static inline void *folio_raw_mapping(const struct folio *folio)
 	return (void *)(mapping & ~FOLIO_MAPPING_FLAGS);
 }
 
+/*
+ * If the VMA has a close hook then close it, and since closing it might leave
+ * it in an inconsistent state which makes the use of any hooks suspect, clear
+ * them down by installing dummy empty hooks.
+ */
+static inline void vma_close(struct vm_area_struct *vma)
+{
+	if (vma->vm_ops && vma->vm_ops->close) {
+		vma->vm_ops->close(vma);
+
+		/*
+		 * The mapping is in an inconsistent state, and no further hooks
+		 * may be invoked upon it.
+		 */
+		vma->vm_ops = &vma_dummy_vm_ops;
+	}
+}
+
 /*
  * This is a file-backed mapping, and is about to be memory mapped - invoke its
  * mmap hook and safely handle error conditions. On error, VMA hooks will be
@@ -224,8 +242,11 @@ static inline void *folio_raw_mapping(const struct folio *folio)
  */
 static inline int mmap_file(struct file *file, struct vm_area_struct *vma)
 {
-	int err = vfs_mmap(file, vma);
+	const unsigned long prev_start = vma->vm_start;
+	const vma_flags_t prev_flags = vma->flags;
+	int err;
 
+	err = vfs_mmap(file, vma);
 	/*
 	 * Either we tried to call the file hook for mmap() and an error arose
 	 * or a driver set vma->vm_ops = NULL intending there to be no VMA
@@ -238,26 +259,14 @@ static inline int mmap_file(struct file *file, struct vm_area_struct *vma)
 	 */
 	if (unlikely(err || !vma->vm_ops))
 		vma->vm_ops = &vma_dummy_vm_ops;
+	if (unlikely(err))
+		return err;
 
-	return err;
-}
-
-/*
- * If the VMA has a close hook then close it, and since closing it might leave
- * it in an inconsistent state which makes the use of any hooks suspect, clear
- * them down by installing dummy empty hooks.
- */
-static inline void vma_close(struct vm_area_struct *vma)
-{
-	if (vma->vm_ops && vma->vm_ops->close) {
-		vma->vm_ops->close(vma);
+	err = mmap_hook_validate(prev_start, &prev_flags, vma);
+	if (unlikely(err))
+		vma_close(vma);
 
-		/*
-		 * The mapping is in an inconsistent state, and no further hooks
-		 * may be invoked upon it.
-		 */
-		vma->vm_ops = &vma_dummy_vm_ops;
-	}
+	return err;
 }
 
 /* unmap_vmas is in mm/memory.c */
@@ -966,15 +975,7 @@ void mlock_folio(struct folio *folio);
 static inline void mlock_vma_folio(struct folio *folio,
 				struct vm_area_struct *vma)
 {
-	/*
-	 * The VM_SPECIAL check here serves two purposes.
-	 * 1) VM_IO check prevents migration from double-counting during mlock.
-	 * 2) Although mmap_region() and mlock_fixup() take care that VM_LOCKED
-	 *    is never left set on a VM_SPECIAL vma, there is an interval while
-	 *    file->f_op->mmap() is using vm_insert_page(s), when VM_LOCKED may
-	 *    still be set while VM_SPECIAL bits are added: so ignore it then.
-	 */
-	if (unlikely((vma->vm_flags & (VM_LOCKED|VM_SPECIAL)) == VM_LOCKED))
+	if (vma_test(vma, VMA_LOCKED_BIT))
 		mlock_folio(folio);
 }
 
@@ -991,7 +992,7 @@ static inline void munlock_vma_folio(struct folio *folio,
 	 * always munlock the folio and page reclaim will correct it
 	 * if it's wrong.
 	 */
-	if (unlikely(vma->vm_flags & VM_LOCKED))
+	if (unlikely(vma_test(vma, VMA_LOCKED_BIT)))
 		munlock_folio(folio);
 }
 
@@ -1111,11 +1112,9 @@ static inline struct file *maybe_unlock_mmap_for_io(struct vm_fault *vmf,
 
 static inline bool vma_supports_mlock(const struct vm_area_struct *vma)
 {
-	if (vma_test_any_mask(vma, VMA_SPECIAL_FLAGS))
-		return false;
-	if (vma_test_single_mask(vma, VMA_DROPPABLE))
+	if (!vma_is_persistent(vma))
 		return false;
-	if (vma_is_dax(vma) || is_vm_hugetlb_page(vma))
+	if (vma_is_dax(vma) || vma_is_hugetlb(vma))
 		return false;
 	return vma != get_gate_vma(current->mm);
 }
@@ -1508,6 +1507,12 @@ int remap_pfn_range_prepare(struct vm_area_desc *desc);
 int remap_pfn_range_complete(struct vm_area_struct *vma,
 			     struct mmap_action *action);
 int simple_ioremap_prepare(struct vm_area_desc *desc);
+int map_kernel_pages_prepare(struct vm_area_desc *desc);
+int map_kernel_pages_complete(struct vm_area_struct *vma,
+			      struct mmap_action *action);
+int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc);
+int map_discontig_kernel_pages_complete(struct vm_area_struct *vma,
+					struct mmap_action *action);
 
 static inline int io_remap_pfn_range_prepare(struct vm_area_desc *desc)
 {
diff --git a/mm/ksm.c b/mm/ksm.c
index 624f37975e129..f80372bfd4b2f 100644
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -747,9 +747,7 @@ static bool ksm_compatible(const struct file *file, vma_flags_t vma_flags)
 	if (vma_flags_test_any(&vma_flags, VMA_SHARED_BIT, VMA_MAYSHARE_BIT,
 			       VMA_HUGETLB_BIT))
 		return false;
-	if (vma_flags_test_single_mask(&vma_flags, VMA_DROPPABLE))
-		return false;
-	if (vma_flags_test_any_mask(&vma_flags, VMA_SPECIAL_FLAGS))
+	if (!vma_flags_is_persistent(&vma_flags))
 		return false;
 	if (file_is_dax(file))
 		return false;
diff --git a/mm/madvise.c b/mm/madvise.c
index 73c2901b9adbf..f805a4876c875 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -880,7 +880,7 @@ bool madvise_dontneed_free_valid_vma(struct madvise_behavior *madv_behavior)
 	int behavior = madv_behavior->behavior;
 	struct madvise_behavior_range *range = &madv_behavior->range;
 
-	if (!is_vm_hugetlb_page(vma)) {
+	if (!vma_is_hugetlb(vma)) {
 		unsigned int forbidden = VM_PFNMAP;
 
 		if (behavior != MADV_DONTNEED_LOCKED)
@@ -1055,19 +1055,25 @@ static long madvise_remove(struct madvise_behavior *madv_behavior)
 	return error;
 }
 
-static bool is_valid_guard_vma(struct vm_area_struct *vma, bool allow_locked)
+static bool is_valid_guard_vma(const struct vm_area_struct *vma,
+			       bool allow_locked)
 {
-	vm_flags_t disallowed = VM_SPECIAL | VM_HUGETLB;
-
 	/*
-	 * A user could lock after setting a guard range but that's fine, as
+	 * A user could lock after setting a guard range but that's fine as
 	 * they'd not be able to fault in. The issue arises when we try to zap
 	 * existing locked VMAs. We don't want to do that.
 	 */
-	if (!allow_locked)
-		disallowed |= VM_LOCKED;
+	if (!allow_locked && vma_test(vma, VMA_LOCKED_BIT))
+		return false;
+	/*
+	 * Guard regions require a VMA whose page tables are managed solely by
+	 * the core, which is also what merging requires, so disallow any flags
+	 * that would prevent a merge.
+	 */
+	if (!vma_can_merge(vma))
+		return false;
 
-	return !(vma->vm_flags & disallowed);
+	return true;
 }
 
 static bool is_guard_pte_marker(pte_t ptent)
@@ -1394,7 +1400,7 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)
 		new_flags |= VM_DONTCOPY;
 		break;
 	case MADV_DOFORK:
-		if (new_flags & VM_SPECIAL)
+		if (!vma_can_merge(vma))
 			return -EINVAL;
 		new_flags &= ~VM_DONTCOPY;
 		break;
@@ -1413,8 +1419,8 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)
 		new_flags |= VM_DONTDUMP;
 		break;
 	case MADV_DODUMP:
-		if ((!is_vm_hugetlb_page(vma) && (new_flags & VM_SPECIAL)) ||
-		    (new_flags & VM_DROPPABLE))
+		/* Non-persistent memory cannot be dumped. */
+		if (!vma_is_persistent(vma))
 			return -EINVAL;
 		new_flags &= ~VM_DONTDUMP;
 		break;
diff --git a/mm/memory.c b/mm/memory.c
index ec63dd6212ac5..cb56d67b17ca3 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -1564,7 +1564,7 @@ copy_page_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma)
 	if (!vma_needs_copy(dst_vma, src_vma))
 		return 0;
 
-	if (is_vm_hugetlb_page(src_vma))
+	if (vma_is_hugetlb(src_vma))
 		return copy_hugetlb_page_range(dst_mm, src_mm, dst_vma, src_vma);
 
 	/*
@@ -2178,7 +2178,7 @@ static void __zap_vma_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
 	if (vma->vm_file && !reaping)
 		uprobe_munmap(vma, start, end);
 
-	if (unlikely(is_vm_hugetlb_page(vma))) {
+	if (unlikely(vma_is_hugetlb(vma))) {
 		zap_flags_t zap_flags = details ? details->zap_flags : 0;
 
 		VM_WARN_ON_ONCE(reaping);
@@ -2313,7 +2313,7 @@ void zap_vma_range_batched(struct mmu_gather *tlb,
 	 */
 	__zap_vma_range(tlb, vma, address, end, details);
 	mmu_notifier_invalidate_range_end(&range);
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		/*
 		 * flush tlb and free resources before hugetlb_zap_end(), to
 		 * avoid concurrent page faults' allocation failure.
@@ -2343,19 +2343,19 @@ void zap_vma_range(struct vm_area_struct *vma, unsigned long address,
 }
 
 /**
- * zap_special_vma_range - zap all page table entries in a special vma range
+ * zap_special_vma_range - zap all page table entries in a kernel-owned VMA
  * @vma: the vma covering the range to zap
  * @address: starting address of the range to zap
  * @size: number of bytes to zap
  *
  * This function does nothing when the provided address range is not fully
- * contained in @vma, or when the @vma is not VM_PFNMAP or VM_MIXEDMAP.
+ * contained in @vma, or when @vma is not kernel-owned.
  */
 void zap_special_vma_range(struct vm_area_struct *vma, unsigned long address,
 		unsigned long size)
 {
 	if (!range_in_vma(vma, address, address + size) ||
-	   !(vma->vm_flags & (VM_PFNMAP | VM_MIXEDMAP)))
+	   !vma_is_kernel_owned(vma))
 		return;
 
 	zap_vma_range(vma, address, size);
@@ -2417,11 +2417,11 @@ static bool vm_mixed_zeropage_allowed(struct vm_area_struct *vma)
 	 * be problematic as soon as the zeropage gets replaced by a different
 	 * page due to vma->vm_ops->pfn_mkwrite, because what's mapped would
 	 * now differ to what GUP looked up. FSDAX is incompatible to
-	 * FOLL_LONGTERM and VM_IO is incompatible to GUP completely (see
-	 * check_vma_flags).
+	 * FOLL_LONGTERM and memory-mapped I/O is incompatible to GUP completely
+	 * (see vma_can_gup()).
 	 */
 	return vma->vm_ops && vma->vm_ops->pfn_mkwrite &&
-	       (vma_is_fsdax(vma) || vma->vm_flags & VM_IO);
+	       (vma_is_fsdax(vma) || vma_test(vma, VMA_IO_BIT));
 }
 
 static int validate_page_before_insert(struct vm_area_struct *vma,
@@ -2609,17 +2609,23 @@ int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,
 }
 EXPORT_SYMBOL(vm_insert_pages);
 
+static void __map_kernel_pages_prepare(struct vm_area_desc *desc)
+{
+	if (vma_desc_test(desc, VMA_MIXEDMAP_BIT))
+		return;
+
+	VM_WARN_ON_ONCE(mmap_read_trylock(desc->mm));
+	VM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));
+	vma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);
+}
+
 int map_kernel_pages_prepare(struct vm_area_desc *desc)
 {
 	const struct mmap_action *action = &desc->action;
 	const unsigned long addr = action->map_kernel.start;
 	unsigned long nr_pages, end;
 
-	if (!vma_desc_test(desc, VMA_MIXEDMAP_BIT)) {
-		VM_WARN_ON_ONCE(mmap_read_trylock(desc->mm));
-		VM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));
-		vma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);
-	}
+	__map_kernel_pages_prepare(desc);
 
 	nr_pages = action->map_kernel.nr_pages;
 	end = addr + PAGE_SIZE * nr_pages;
@@ -2628,7 +2634,6 @@ int map_kernel_pages_prepare(struct vm_area_desc *desc)
 
 	return 0;
 }
-EXPORT_SYMBOL(map_kernel_pages_prepare);
 
 int map_kernel_pages_complete(struct vm_area_struct *vma,
 			      struct mmap_action *action)
@@ -2640,7 +2645,98 @@ int map_kernel_pages_complete(struct vm_area_struct *vma,
 			    action->map_kernel.pages,
 			    &nr_pages, vma->vm_page_prot);
 }
-EXPORT_SYMBOL(map_kernel_pages_complete);
+
+int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc)
+{
+	const struct mmap_action *action = &desc->action;
+	const struct discontig_kernel_page_ops *ops =
+		action->map_kernel_discontig.ops;
+
+	/* At minimum need to be able to get pages. */
+	if (WARN_ON_ONCE(!ops->get))
+		return -EINVAL;
+
+	__map_kernel_pages_prepare(desc);
+	return 0;
+}
+
+static int apply_discontig_action(struct vm_area_struct *vma,
+				  struct discontig_kernel_page_state *state)
+{
+	unsigned long nr_pages = state->__nr_pages;
+	unsigned long addr = state->addr;
+	unsigned long i;
+
+	if (state->action == DISCONTIG_KERNEL_PAGE_MAP_PAGE)
+		return insert_page(vma, addr, state->__page,
+				   vma->vm_page_prot, /*mkwrite=*/false);
+	if (state->action == DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE)
+		return insert_pages(vma, addr, state->__page_arr,
+				    &nr_pages, vma->vm_page_prot);
+
+	/* Compound folio - have to iterate through each page. */
+	for (i = 0; i < nr_pages; i++, addr += PAGE_SIZE) {
+		struct page *page = folio_page(state->__folio, i);
+		int err;
+
+		err = insert_page(vma, addr, page, vma->vm_page_prot,
+				  /*mkwrite=*/false);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+int map_discontig_kernel_pages_complete(struct vm_area_struct *vma,
+					struct mmap_action *action)
+{
+	const struct discontig_kernel_page_ops *ops =
+		action->map_kernel_discontig.ops;
+	struct discontig_kernel_page_state state = {
+		.start = vma->vm_start,
+		.end = vma->vm_end,
+		.addr = vma->vm_start,
+		.pgoff = vma->vm_pgoff,
+		.nr_pages_mapped = 0,
+		.nr_pages_remain = vma_pages(vma),
+		.vm_private_data = vma->vm_private_data,
+		.private = action->map_kernel_discontig.init_private,
+	};
+	int err = 0;
+
+	if (ops->init)
+		err = ops->init(vma->vm_private_data, &state.private);
+	if (err)
+		return err;
+
+	do {
+		unsigned long end, pgoff_end;
+		unsigned long nr_pages;
+
+		/* Default to abort. */
+		state.action = DISCONTIG_KERNEL_PAGE_ABORT;
+		err = ops->get(&state);
+		if (err || state.action == DISCONTIG_KERNEL_PAGE_ABORT)
+			return err;
+		nr_pages = state.__nr_pages;
+
+		end = state.addr + PAGE_SIZE * nr_pages;
+		if (end > vma->vm_end)
+			return -EINVAL;
+		pgoff_end = state.pgoff + nr_pages;
+
+		err = apply_discontig_action(vma, &state);
+		if (err)
+			return err;
+
+		state.addr = end;
+		state.pgoff = pgoff_end;
+		state.nr_pages_mapped += nr_pages;
+		state.nr_pages_remain -= nr_pages;
+	} while (state.addr < vma->vm_end);
+
+	return 0;
+}
 
 /**
  * vm_insert_page - insert single page into user vma
@@ -6837,7 +6933,7 @@ vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
 
 	lru_gen_enter_fault(vma);
 
-	if (unlikely(is_vm_hugetlb_page(vma)))
+	if (unlikely(vma_is_hugetlb(vma)))
 		ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
 	else
 		ret = __handle_mm_fault(vma, address, flags);
@@ -7020,7 +7116,8 @@ int follow_pfnmap_start(struct follow_pfnmap_args *args)
 	if (unlikely(address < vma->vm_start || address >= vma->vm_end))
 		goto out;
 
-	if (!(vma->vm_flags & (VM_IO | VM_PFNMAP)))
+	/* Only mappings GUP cannot handle are followed here. */
+	if (vma_can_gup(vma))
 		goto out;
 retry:
 	pgdp = pgd_offset(mm, address);
@@ -7214,8 +7311,9 @@ static int __access_remote_vm(struct mm_struct *mm, unsigned long addr,
 			}
 
 			/*
-			 * Check if this is a VM_IO | VM_PFNMAP VMA, which
-			 * we can access using slightly different code.
+			 * GUP failed, perhaps because this is a mapping it
+			 * cannot handle (see vma_can_gup()) - such mappings may
+			 * provide access via vm_ops->access() instead.
 			 */
 			bytes = 0;
 #ifdef CONFIG_HAVE_IOREMAP_PROT
@@ -7701,12 +7799,12 @@ void ptlock_free(struct ptdesc *ptdesc)
 
 void vma_pgtable_walk_begin(struct vm_area_struct *vma)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		hugetlb_vma_lock_read(vma);
 }
 
 void vma_pgtable_walk_end(struct vm_area_struct *vma)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		hugetlb_vma_unlock_read(vma);
 }
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 2ad0a5f18280a..ed444061631c9 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -2011,7 +2011,8 @@ SYSCALL_DEFINE5(get_mempolicy, int __user *, policy,
 
 bool vma_migratable(struct vm_area_struct *vma)
 {
-	if (vma->vm_flags & (VM_IO | VM_PFNMAP))
+	/* Pages which GUP cannot obtain cannot be migrated either. */
+	if (!vma_can_gup(vma))
 		return false;
 
 	/*
@@ -2021,7 +2022,7 @@ bool vma_migratable(struct vm_area_struct *vma)
 	if (vma_is_dax(vma))
 		return false;
 
-	if (is_vm_hugetlb_page(vma) &&
+	if (vma_is_hugetlb(vma) &&
 		!hugepage_migration_supported(hstate_vma(vma)))
 		return false;
 
diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 0c437004329d9..b74c0ae427682 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -739,19 +739,21 @@ static void migrate_vma_unmap(struct migrate_vma *migrate)
  */
 int migrate_vma_setup(struct migrate_vma *args)
 {
+	const struct vm_area_struct *vma = args->vma;
 	long nr_pages = (args->end - args->start) >> PAGE_SHIFT;
 
 	args->start &= PAGE_MASK;
 	args->end &= PAGE_MASK;
-	if (!args->vma || is_vm_hugetlb_page(args->vma) ||
-	    (args->vma->vm_flags & VM_SPECIAL) || vma_is_dax(args->vma))
+	if (!vma)
+		return -EINVAL;
+	if (vma_is_kernel_owned(vma) || vma_is_fixed_mapping(vma) ||
+	    vma_is_dax(vma))
 		return -EINVAL;
 	if (nr_pages <= 0)
 		return -EINVAL;
-	if (args->start < args->vma->vm_start ||
-	    args->start >= args->vma->vm_end)
+	if (args->start < vma->vm_start || args->start >= vma->vm_end)
 		return -EINVAL;
-	if (args->end <= args->vma->vm_start || args->end > args->vma->vm_end)
+	if (args->end <= vma->vm_start || args->end > vma->vm_end)
 		return -EINVAL;
 	if (!args->src || !args->dst)
 		return -EINVAL;
diff --git a/mm/mlock.c b/mm/mlock.c
index 39215a3eab1fb..4235a1518fc9e 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -316,22 +316,10 @@ static inline unsigned int folio_mlock_step(struct folio *folio,
 	return folio_pte_batch(folio, pte, ptent, count);
 }
 
-static inline bool allow_mlock_munlock(struct folio *folio,
+static inline bool allow_mlock(struct folio *folio,
 		struct vm_area_struct *vma, unsigned long start,
 		unsigned long end, unsigned int step)
 {
-	/*
-	 * For unlock, allow munlock large folio which is partially
-	 * mapped to VMA. As it's possible that large folio is
-	 * mlocked and VMA is split later.
-	 *
-	 * During memory pressure, such kind of large folio can
-	 * be split. And the pages are not in VM_LOCKed VMA
-	 * can be reclaimed.
-	 */
-	if (!vma_test(vma, VMA_LOCKED_BIT))
-		return true;
-
 	/* folio_within_range() cannot take KSM, but any small folio is OK */
 	if (!folio_test_large(folio))
 		return true;
@@ -352,6 +340,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
 
 {
 	struct vm_area_struct *vma = walk->vma;
+	const bool lock = walk->private;
 	spinlock_t *ptl;
 	pte_t *start_pte, *pte;
 	pte_t ptent;
@@ -368,7 +357,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
 		folio = pmd_folio(*pmd);
 		if (folio_is_zone_device(folio))
 			goto out;
-		if (vma_test(vma, VMA_LOCKED_BIT))
+		if (lock)
 			mlock_folio(folio);
 		else
 			munlock_folio(folio);
@@ -390,10 +379,10 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
 			continue;
 
 		step = folio_mlock_step(folio, pte, addr, end);
-		if (!allow_mlock_munlock(folio, vma, start, end, step))
+		if (lock && !allow_mlock(folio, vma, start, end, step))
 			goto next_entry;
 
-		if (vma_test(vma, VMA_LOCKED_BIT))
+		if (lock)
 			mlock_folio(folio);
 		else
 			munlock_folio(folio);
@@ -428,31 +417,29 @@ static void mlock_vma_pages_range(struct vm_area_struct *vma,
 		.pmd_entry = mlock_pte_range,
 		.walk_lock = PGWALK_WRLOCK_VERIFY,
 	};
+	const bool lock = vma_flags_test(new_vma_flags, VMA_LOCKED_BIT);
+	vma_flags_t walk_flags = *new_vma_flags;
 
 	/*
-	 * There is a slight chance that concurrent page migration,
-	 * or page reclaim finding a page of this now-VMA_LOCKED_BIT vma,
-	 * will call mlock_vma_folio() and raise page's mlock_count:
-	 * double counting, leaving the page unevictable indefinitely.
-	 * Communicate this danger to mlock_vma_folio() with VMA_IO_BIT,
-	 * which is a VMA_SPECIAL_FLAGS flag not allowed on VMA_LOCKED_BIT vmas.
-	 * mmap_lock is held in write mode here, so this weird
-	 * combination should not be visible to other mmap_lock users;
-	 * but WRITE_ONCE so rmap walkers must see VMA_IO_BIT if VMA_LOCKED_BIT.
+	 * LOCKONFAULT without LOCKED never otherwise occurs: it marks a walk in
+	 * progress so that rmap-side callers, which test VMA_LOCKED_BIT, do not
+	 * count folios, while try_to_unmap_one(), which tests VMA_LOCKED_MASK,
+	 * still refuses to unmap them.
 	 */
-	if (vma_flags_test(new_vma_flags, VMA_LOCKED_BIT))
-		vma_flags_set(new_vma_flags, VMA_IO_BIT);
+	if (lock) {
+		vma_flags_clear(&walk_flags, VMA_LOCKED_BIT);
+		vma_flags_set(&walk_flags, VMA_LOCKONFAULT_BIT);
+	}
+
 	vma_start_write(vma);
-	vma_flags_reset_once(vma, new_vma_flags);
+	vma_flags_reset_once(vma, &walk_flags);
 
 	lru_add_drain();
-	walk_page_range_vma(vma, start, end, &mlock_walk_ops, NULL);
+	walk_page_range_vma(vma, start, end, &mlock_walk_ops, (void *)lock);
 	lru_add_drain();
 
-	if (vma_flags_test(new_vma_flags, VMA_IO_BIT)) {
-		vma_flags_clear(new_vma_flags, VMA_IO_BIT);
+	if (lock)
 		vma_flags_reset_once(vma, new_vma_flags);
-	}
 }
 
 /*
diff --git a/mm/mmap.c b/mm/mmap.c
index 4bf26b0f1e6e3..98449f364af1c 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1786,7 +1786,7 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
 		/*
 		 * Copy/update hugetlb private vma information.
 		 */
-		if (is_vm_hugetlb_page(tmp))
+		if (vma_is_hugetlb(tmp))
 			hugetlb_dup_vma_private(tmp);
 
 		/*
diff --git a/mm/mmu_gather.c b/mm/mmu_gather.c
index 3985d856de7f9..506f005adbdc0 100644
--- a/mm/mmu_gather.c
+++ b/mm/mmu_gather.c
@@ -500,7 +500,7 @@ void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma)
 {
 	tlb_gather_mmu(tlb, vma->vm_mm);
 	tlb_update_vma_flags(tlb, vma);
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		/* All entries have the same size. */
 		tlb_change_page_size(tlb, huge_page_size(hstate_vma(vma)));
 }
diff --git a/mm/mprotect.c b/mm/mprotect.c
index 2888ee638d872..a1b6d29bf0390 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -717,7 +717,7 @@ long change_protection(struct mmu_gather *tlb,
 	    (cp_flags & MM_CP_UFFD_RWP))
 		newprot = PAGE_NONE;
 
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		pages = hugetlb_change_protection(vma, start, end, newprot,
 						  cp_flags);
 	else
@@ -783,8 +783,7 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
 	 * uncommon case, so doesn't need to be very optimized.
 	 */
 	if (arch_has_pfn_modify_check() &&
-	    vma_flags_test_any(&old_vma_flags, VMA_PFNMAP_BIT,
-			       VMA_MIXEDMAP_BIT) &&
+	    vma_flags_is_kernel_owned(&old_vma_flags) &&
 	    !vma_flags_test_any_mask(&new_vma_flags, VMA_ACCESS_FLAGS)) {
 		pgprot_t new_pgprot = vm_get_page_prot(newflags);
 
diff --git a/mm/mremap.c b/mm/mremap.c
index 7c368440fafe2..1122282a1d6ab 100644
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -812,7 +812,7 @@ unsigned long move_page_tables(struct pagetable_move_control *pmc)
 	if (!pmc->len_in)
 		return 0;
 
-	if (is_vm_hugetlb_page(pmc->old))
+	if (vma_is_hugetlb(pmc->old))
 		return move_hugetlb_page_tables(pmc->old, pmc->new, pmc->old_addr,
 						pmc->new_addr, pmc->len_in);
 
@@ -1735,7 +1735,7 @@ static bool vma_multi_allowed(struct vm_area_struct *vma)
 	/* Known good. */
 	if (vma_is_shmem(vma))
 		return true;
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return true;
 	if (file->f_op->get_unmapped_area == thp_get_unmapped_area)
 		return true;
@@ -1758,7 +1758,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
 		return -EPERM;
 
 	/* Align to hugetlb page size, if required. */
-	if (is_vm_hugetlb_page(vma) && !align_hugetlb(vrm))
+	if (vma_is_hugetlb(vma) && !align_hugetlb(vrm))
 		return -EINVAL;
 
 	vrm_set_delta(vrm);
@@ -1788,8 +1788,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
 		return -EINVAL;
 	}
 
-	if ((vrm->flags & MREMAP_DONTUNMAP) &&
-	    vma_test_any(vma, VMA_DONTEXPAND_BIT, VMA_PFNMAP_BIT))
+	if ((vrm->flags & MREMAP_DONTUNMAP) && vma_is_fixed_mapping(vma))
 		return -EINVAL;
 
 	/*
@@ -1827,7 +1826,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
 	if (pgoff + (new_len >> PAGE_SHIFT) < pgoff)
 		return -EINVAL;
 
-	if (vma_test_any(vma, VMA_DONTEXPAND_BIT, VMA_PFNMAP_BIT))
+	if (vma_is_fixed_mapping(vma))
 		return -EFAULT;
 
 	if (!mlock_future_ok(mm, vma_test(vma, VMA_LOCKED_BIT), vrm->delta))
diff --git a/mm/page_vma_mapped.c b/mm/page_vma_mapped.c
index 28e306fdb3a5b..8408aee7571b5 100644
--- a/mm/page_vma_mapped.c
+++ b/mm/page_vma_mapped.c
@@ -109,7 +109,7 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw, unsigned long pte_nr)
 	unsigned long pfn;
 	pte_t ptent;
 
-	if (is_vm_hugetlb_page(pvmw->vma))
+	if (vma_is_hugetlb(pvmw->vma))
 		ptent = huge_ptep_get(pvmw->vma->vm_mm, pvmw->address,
 				      pvmw->pte);
 	else
@@ -206,7 +206,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
 	if (pvmw->pmd && !pvmw->pte)
 		return not_found(pvmw);
 
-	if (unlikely(is_vm_hugetlb_page(vma))) {
+	if (unlikely(vma_is_hugetlb(vma))) {
 		struct hstate *hstate = hstate_vma(vma);
 		unsigned long size = huge_page_size(hstate);
 		/* The only possible mapping was handled on last iteration */
diff --git a/mm/pagewalk.c b/mm/pagewalk.c
index 7411702a37f58..e6493bbe6919e 100644
--- a/mm/pagewalk.c
+++ b/mm/pagewalk.c
@@ -408,7 +408,7 @@ static int __walk_page_range(unsigned long start, unsigned long end,
 	int err = 0;
 	struct vm_area_struct *vma = walk->vma;
 	const struct mm_walk_ops *ops = walk->ops;
-	bool is_hugetlb = is_vm_hugetlb_page(vma);
+	bool is_hugetlb = vma_is_hugetlb(vma);
 
 	/* We do not support hugetlb PTE installation. */
 	if (ops->install_pte && is_hugetlb)
diff --git a/mm/rmap.c b/mm/rmap.c
index 5fefe5b060b1c..120c894d2ddec 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -2239,9 +2239,11 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,
 
 		/*
 		 * If the folio is in an mlock()d vma, we must not swap it out.
+		 * VMA_LOCKONFAULT_BIT alone marks an mlock walk in progress, see
+		 * mlock_vma_pages_range().
 		 */
 		if (!(flags & TTU_IGNORE_MLOCK) &&
-		    (vma->vm_flags & VM_LOCKED)) {
+		    vma_test_any_mask(vma, VMA_LOCKED_MASK)) {
 			ptes++;
 
 			/*
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 01e7b6b046b67..f90f029bfd5cf 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -2705,7 +2705,7 @@ static int unuse_mm(struct mm_struct *mm, unsigned int type)
 	if (check_stable_address_space(mm))
 		goto unlock;
 	for_each_vma(vmi, vma) {
-		if (vma->anon_vma && !is_vm_hugetlb_page(vma)) {
+		if (vma->anon_vma && !vma_is_hugetlb(vma)) {
 			ret = unuse_vma(vma, type);
 			if (ret)
 				break;
diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index 79cc7b546f130..ddf0a4a3d3997 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -237,7 +237,7 @@ static int mfill_get_vma(struct mfill_state *state)
 	if ((flags & MFILL_ATOMIC_WP) && !(dst_vma->vm_flags & VM_UFFD_WP))
 		goto out_unlock;
 
-	if (is_vm_hugetlb_page(dst_vma))
+	if (vma_is_hugetlb(dst_vma))
 		return 0;
 
 	ops = vma_uffd_ops(dst_vma);
@@ -804,7 +804,7 @@ static __always_inline ssize_t mfill_atomic_hugetlb(
 		}
 
 		err = -ENOENT;
-		if (!is_vm_hugetlb_page(dst_vma))
+		if (!vma_is_hugetlb(dst_vma))
 			goto out_unlock_vma;
 
 		err = -EINVAL;
@@ -967,7 +967,7 @@ static __always_inline ssize_t mfill_atomic(struct userfaultfd_ctx *ctx,
 	/*
 	 * If this is a HUGETLB vma, pass off to appropriate routine
 	 */
-	if (is_vm_hugetlb_page(state.vma))
+	if (vma_is_hugetlb(state.vma))
 		return  mfill_atomic_hugetlb(ctx, state.vma, dst_start,
 					     src_start, len, flags);
 
@@ -1114,7 +1114,7 @@ static int mwriteprotect_range(struct userfaultfd_ctx *ctx, unsigned long start,
 			break;
 		}
 
-		if (is_vm_hugetlb_page(dst_vma)) {
+		if (vma_is_hugetlb(dst_vma)) {
 			err = -EINVAL;
 			page_mask = vma_kernel_pagesize(dst_vma) - 1;
 			if ((start & page_mask) || (len & page_mask))
@@ -1172,7 +1172,7 @@ int mrwprotect_range(struct userfaultfd_ctx *ctx, unsigned long start,
 		if (!userfaultfd_rwp(dst_vma))
 			return -ENOENT;
 
-		if (is_vm_hugetlb_page(dst_vma)) {
+		if (vma_is_hugetlb(dst_vma)) {
 			unsigned long page_mask;
 
 			page_mask = vma_kernel_pagesize(dst_vma) - 1;
@@ -1754,10 +1754,18 @@ static inline bool move_splits_huge_pmd(unsigned long dst_addr,
 }
 #endif
 
-static inline bool vma_move_compatible(struct vm_area_struct *vma)
+static inline bool vma_move_compatible(const struct vm_area_struct *vma)
 {
-	return !(vma->vm_flags & (VM_PFNMAP | VM_IO |  VM_HUGETLB |
-				  VM_MIXEDMAP | VM_SHADOW_STACK));
+	/* uffd is generally incompatible with kernel-owned mappings. */
+	if (vma_is_kernel_owned(vma))
+		return false;
+	/* The shadow stack should not be written to by userspace. */
+	if (vma_test_single_mask(vma, VMA_SHADOW_STACK))
+		return false;
+	/* hugetlb mappings cannot be safely moved. */
+	if (vma_is_hugetlb(vma))
+		return false;
+	return true;
 }
 
 static int validate_move_areas(struct userfaultfd_ctx *ctx,
@@ -2146,10 +2154,11 @@ static bool vma_can_userfault(struct vm_area_struct *vma, vm_flags_t vm_flags,
 {
 	const struct vm_uffd_ops *ops = vma_uffd_ops(vma);
 
-	if (vma->vm_flags & (VM_DROPPABLE | VM_SHADOW_STACK))
+	/* Non-persistent memory is inherently not controllable by userspace. */
+	if (!vma_is_persistent(vma))
 		return false;
-
-	if (!is_vm_hugetlb_page(vma) && (vma->vm_flags & VM_SPECIAL))
+	/* The shadow stack should not be written to by userspace. */
+	if (vma_test_single_mask(vma, VMA_SHADOW_STACK))
 		return false;
 
 	vm_flags &= __VM_UFFD_FLAGS;
@@ -2319,7 +2328,7 @@ static int userfaultfd_register_range(struct userfaultfd_ctx *ctx,
 		 */
 		userfaultfd_set_ctx(vma, ctx, vm_flags);
 
-		if (is_vm_hugetlb_page(vma) && uffd_disable_huge_pmd_share(vma))
+		if (vma_is_hugetlb(vma) && uffd_disable_huge_pmd_share(vma))
 			hugetlb_unshare_all_pmds(vma);
 
 skip:
@@ -2895,7 +2904,7 @@ vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)
 	 * (sleepable) vma lock can modify the current task state, that
 	 * must be before explicitly calling set_current_state().
 	 */
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		hugetlb_vma_lock_read(vma);
 
 	spin_lock_irq(&ctx->fault_pending_wqh.lock);
@@ -2912,7 +2921,7 @@ vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)
 	set_current_state(blocking_state);
 	spin_unlock_irq(&ctx->fault_pending_wqh.lock);
 
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		must_wait = userfaultfd_huge_must_wait(ctx, vmf, reason);
 		hugetlb_vma_unlock_read(vma);
 	} else {
@@ -3744,7 +3753,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
 	 * If the first vma contains huge pages, make sure start address
 	 * is aligned to huge page size.
 	 */
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		unsigned long vma_hpagesize = vma_kernel_pagesize(vma);
 
 		if (start & (vma_hpagesize - 1))
@@ -3795,7 +3804,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
 		 * If this vma contains ending address, and huge pages
 		 * check alignment.
 		 */
-		if (is_vm_hugetlb_page(cur) && end <= cur->vm_end &&
+		if (vma_is_hugetlb(cur) && end <= cur->vm_end &&
 		    end > cur->vm_start) {
 			unsigned long vma_hpagesize = vma_kernel_pagesize(cur);
 
@@ -3831,7 +3840,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
 		/*
 		 * Note vmas containing huge pages
 		 */
-		if (is_vm_hugetlb_page(cur))
+		if (vma_is_hugetlb(cur))
 			basic_ioctls = true;
 
 		found = true;
@@ -3917,7 +3926,7 @@ static int userfaultfd_unregister(struct userfaultfd_ctx *ctx,
 	 * If the first vma contains huge pages, make sure start address
 	 * is aligned to huge page size.
 	 */
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		unsigned long vma_hpagesize = vma_kernel_pagesize(vma);
 
 		if (start & (vma_hpagesize - 1))
diff --git a/mm/util.c b/mm/util.c
index bf0513d1d3d08..5a1916d8fdc10 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -1224,10 +1224,17 @@ EXPORT_SYMBOL(compat_set_desc_from_vma);
 int __compat_vma_mmap(struct vm_area_desc *desc,
 		      struct vm_area_struct *vma)
 {
+	struct vm_area_desc prev_desc;
 	int err;
 
+	/* Derive state prior to mmap_prepare hook. */
+	compat_set_desc_from_vma(&prev_desc, desc->file, vma);
 	/* Perform any preparatory tasks for mmap action. */
 	err = mmap_action_prepare(desc);
+	if (err)
+		return err;
+	/* Check the caller did nothing crazy. */
+	err = mmap_prepare_validate(&prev_desc, desc);
 	if (err)
 		return err;
 	/* Update the VMA from the descriptor. */
@@ -1455,8 +1462,10 @@ int mmap_action_prepare(struct vm_area_desc *desc)
 		return io_remap_pfn_range_prepare(desc);
 	case MMAP_SIMPLE_IO_REMAP:
 		return simple_ioremap_prepare(desc);
-	case MMAP_MAP_KERNEL_PAGES:
+	case MMAP_KERNEL_PAGES:
 		return map_kernel_pages_prepare(desc);
+	case MMAP_DISCONTIG_KERNEL_PAGES:
+		return map_discontig_kernel_pages_prepare(desc);
 	}
 
 	WARN_ON_ONCE(1);
@@ -1486,9 +1495,12 @@ int mmap_action_complete(struct vm_area_struct *vma,
 	case MMAP_REMAP_PFN:
 		err = remap_pfn_range_complete(vma, action);
 		break;
-	case MMAP_MAP_KERNEL_PAGES:
+	case MMAP_KERNEL_PAGES:
 		err = map_kernel_pages_complete(vma, action);
 		break;
+	case MMAP_DISCONTIG_KERNEL_PAGES:
+		err = map_discontig_kernel_pages_complete(vma, action);
+		break;
 	case MMAP_IO_REMAP_PFN:
 	case MMAP_SIMPLE_IO_REMAP:
 		/* Should have been delegated. */
@@ -1509,7 +1521,8 @@ int mmap_action_prepare(struct vm_area_desc *desc)
 	case MMAP_REMAP_PFN:
 	case MMAP_IO_REMAP_PFN:
 	case MMAP_SIMPLE_IO_REMAP:
-	case MMAP_MAP_KERNEL_PAGES:
+	case MMAP_KERNEL_PAGES:
+	case MMAP_DISCONTIG_KERNEL_PAGES:
 		WARN_ON_ONCE(1); /* nommu cannot handle these. */
 		break;
 	}
@@ -1530,7 +1543,8 @@ int mmap_action_complete(struct vm_area_struct *vma,
 	case MMAP_REMAP_PFN:
 	case MMAP_IO_REMAP_PFN:
 	case MMAP_SIMPLE_IO_REMAP:
-	case MMAP_MAP_KERNEL_PAGES:
+	case MMAP_KERNEL_PAGES:
+	case MMAP_DISCONTIG_KERNEL_PAGES:
 		WARN_ON_ONCE(1); /* nommu cannot handle this. */
 
 		err = -EINVAL;
diff --git a/mm/vma.c b/mm/vma.c
index 97567fb7ef33d..ab570e0a7f16c 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -599,7 +599,7 @@ __split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,
 	 * boundary.
 	 */
 	vma_adjust_trans_huge(vma, vma->vm_start, addr, NULL);
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		hugetlb_split(vma, addr);
 
 	if (new_below) {
@@ -924,13 +924,14 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
 
 	vmg->state = VMA_MERGE_NOMERGE;
 
+	if (!vma_flags_can_merge(&vmg->vma_flags))
+		return NULL;
 	/*
-	 * If a special mapping or if the range being modified is neither at the
-	 * furthermost left or right side of the VMA, then we have no chance of
-	 * merging and should abort.
+	 * If the range being modified is neither at the furthermost left or
+	 * right side of the VMA, then we have no chance of merging and should
+	 * abort.
 	 */
-	if (vma_flags_test_any_mask(&vmg->vma_flags, VMA_SPECIAL_FLAGS) ||
-	    (!left_side && !right_side))
+	if (!left_side && !right_side)
 		return NULL;
 
 	if (left_side)
@@ -1152,9 +1153,11 @@ struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg)
 
 	vmg->state = VMA_MERGE_NOMERGE;
 
-	/* Special VMAs are unmergeable, also if no prev/next. */
-	if (vma_flags_test_any_mask(&vmg->vma_flags, VMA_SPECIAL_FLAGS) ||
-	    (!prev && !next))
+	if (!vma_flags_can_merge(&vmg->vma_flags))
+		return NULL;
+
+	/* VMAs with no prev/next are unmergeable. */
+	if (!prev && !next)
 		return NULL;
 
 	can_merge_left = can_vma_merge_left(vmg);
@@ -2225,7 +2228,7 @@ bool vma_wants_writenotify(struct vm_area_struct *vma, pgprot_t vm_page_prot)
 	 * Do we need to track softdirty? hugetlb does not support softdirty
 	 * tracking yet.
 	 */
-	if (vma_soft_dirty_enabled(vma) && !is_vm_hugetlb_page(vma))
+	if (vma_soft_dirty_enabled(vma) && !vma_is_hugetlb(vma))
 		return true;
 
 	/* Do we need write faults for uffd-wp tracking? */
@@ -2344,7 +2347,7 @@ int mm_take_all_locks(struct mm_struct *mm)
 		if (signal_pending(current))
 			goto out_unlock;
 		if (vma->vm_file && vma->vm_file->f_mapping &&
-				is_vm_hugetlb_page(vma))
+				vma_is_hugetlb(vma))
 			vm_lock_mapping(mm, vma->vm_file->f_mapping);
 	}
 
@@ -2353,7 +2356,7 @@ int mm_take_all_locks(struct mm_struct *mm)
 		if (signal_pending(current))
 			goto out_unlock;
 		if (vma->vm_file && vma->vm_file->f_mapping &&
-				!is_vm_hugetlb_page(vma))
+				!vma_is_hugetlb(vma))
 			vm_lock_mapping(mm, vma->vm_file->f_mapping);
 	}
 
@@ -2578,7 +2581,6 @@ static int __mmap_setup(struct mmap_state *map, struct vm_area_desc *desc,
 	return 0;
 }
 
-
 static int __mmap_new_file_vma(struct mmap_state *map,
 			       struct vm_area_struct *vma)
 {
@@ -2592,6 +2594,11 @@ static int __mmap_new_file_vma(struct mmap_state *map,
 	if (!map->file->f_op->mmap)
 		return 0;
 
+	/*
+	 * Driver-specified flags may make the lock flags invalid, so clear
+	 * VMA_LOCKED_MASK and reinstate it afterwards if appropriate.
+	 */
+	vma_clear_flags_mask(vma, VMA_LOCKED_MASK);
 	error = mmap_file(vma->vm_file, vma);
 	if (error) {
 		UNMAP_STATE(unmap, vmi, vma, vma->vm_start, vma->vm_end,
@@ -2605,15 +2612,14 @@ static int __mmap_new_file_vma(struct mmap_state *map,
 		return error;
 	}
 
-	/* Drivers cannot alter the address of the VMA. */
-	WARN_ON_ONCE(map->addr != vma->vm_start);
-	/*
-	 * Drivers should not permit writability when previously it was
-	 * disallowed.
-	 */
-	VM_WARN_ON_ONCE(!vma_flags_same_pair(&map->vma_flags, &vma->flags) &&
-			!vma_flags_test(&map->vma_flags, VMA_MAYWRITE_BIT) &&
-			vma_test(vma, VMA_MAYWRITE_BIT));
+	/* If VMA flags still valid for locked mask, reinstate. */
+	if (vma_supports_mlock(vma)) {
+		const vma_flags_t mask =
+			vma_flags_and_mask(&map->vma_flags,
+					   VMA_LOCKED_MASK);
+
+		vma_set_flags_mask(vma, mask);
+	}
 
 	map->file = vma->vm_file;
 	map->vma_flags = vma->flags;
@@ -2693,11 +2699,6 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
 		vma->flags = map->vma_flags;
 	}
 
-#ifdef CONFIG_SPARC64
-	/* TODO: Fix SPARC ADI! */
-	WARN_ON_ONCE(!arch_validate_flags(map->vm_flags));
-#endif
-
 	/* Lock the VMA since it is modified after insertion into VMA tree */
 	vma_start_write(vma);
 	vma_iter_store_new(vmi, vma);
@@ -2760,6 +2761,96 @@ static void __mmap_complete(struct mmap_state *map, struct vm_area_struct *vma)
 	vma_set_page_prot(vma);
 }
 
+/* Check to ensure that the VMA flags of a newly mapped VMA are sane. */
+static int mmap_validate_vma_flags(const vma_flags_t *flags)
+{
+#ifdef CONFIG_SPARC64
+	const vm_flags_t legacy_flags = vma_flags_to_legacy(*flags);
+
+	/* TODO: Fix SPARC ADI! */
+	if (WARN_ON_ONCE(!arch_validate_flags(legacy_flags)))
+		return -EINVAL;
+#endif
+
+	if (!vma_flags_is_kernel_owned(flags)) {
+		/* Only kernel-owned mappings may set VMA_IO_BIT. */
+		if (WARN_ON_ONCE(vma_flags_test(flags, VMA_IO_BIT)))
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* Check to ensure a driver hasn't done something crazy. */
+static int mmap_validate(unsigned long prev_start,
+			 unsigned long curr_start,
+			 const vma_flags_t *prev_flags,
+			 const vma_flags_t *curr_flags)
+{
+	bool was_maywrite, is_maywrite;
+
+	/* Drivers cannot alter the address of the VMA. */
+	if (WARN_ON_ONCE(prev_start != curr_start))
+		return -EINVAL;
+
+	was_maywrite = vma_flags_test(prev_flags, VMA_MAYWRITE_BIT);
+	is_maywrite = vma_flags_test(curr_flags, VMA_MAYWRITE_BIT);
+
+	/* A driver may not make a previously unwritable mapping writable. */
+	if (WARN_ON_ONCE(!was_maywrite && is_maywrite))
+		return -EINVAL;
+
+	/* Only kernel-owned mappings may clear VMA_MAYWRITE_BIT. */
+	if (!vma_flags_is_kernel_owned(curr_flags) &&
+	    WARN_ON_ONCE(was_maywrite && !is_maywrite))
+		return -EINVAL;
+
+	return mmap_validate_vma_flags(curr_flags);
+}
+
+/**
+ * mmap_prepare_validate() - Ensure the driver hasn't violated invariants in its
+ * f_op->mmap_prepare hook.
+ * @prev_desc: The VMA descriptor prior to the mmap_prepare hook being called.
+ * @desc: The VMA descriptor after the mmap_prepare hook has been called.
+ *
+ * Returns: 0 on success, otherwise an error.
+ */
+int mmap_prepare_validate(const struct vm_area_desc *prev_desc,
+			  const struct vm_area_desc *desc)
+{
+	/*
+	 * It is not valid to execute mmap actions for VMAs which can be merged,
+	 * as any such merge would leave portions of the mapping incorrectly
+	 * unmapped.
+	 */
+	if (vma_flags_can_merge(&desc->vma_flags) &&
+	    WARN_ON_ONCE(desc->action.type != MMAP_NOTHING))
+		return -EINVAL;
+
+	return mmap_validate(prev_desc->start, desc->start,
+			     &prev_desc->vma_flags, &desc->vma_flags);
+}
+
+/**
+ * mmap_hook_validate() - Ensure the driver hasn't violated invariants in
+ * its f_op->mmap hook.
+ * @prev_start: The start of the mapping prior to the mmap hook.
+ * @prev_flags: The VMA flags set for the VMA prior to the mmap hook.
+ * @vma: The VMA after the hook has been applied.
+ *
+ * Returns: 0 on success, otherwise an error.
+ */
+int mmap_hook_validate(unsigned long prev_start,
+		       const vma_flags_t *prev_flags,
+		       const struct vm_area_struct *vma)
+{
+	const unsigned long start = vma->vm_start;
+	const vma_flags_t *flags = &vma->flags;
+
+	return mmap_validate(prev_start, start, prev_flags, flags);
+}
+
 static int call_action_prepare(struct mmap_state *map,
 			       struct vm_area_desc *desc)
 {
@@ -2786,6 +2877,7 @@ static int call_action_prepare(struct mmap_state *map,
 static int call_mmap_prepare(struct mmap_state *map,
 		struct vm_area_desc *desc)
 {
+	const struct vm_area_desc prev_desc = *desc;
 	int err;
 
 	/* Invoke the hook. */
@@ -2797,10 +2889,16 @@ static int call_mmap_prepare(struct mmap_state *map,
 	if (!desc->vm_ops)
 		return -EINVAL;
 
+	/* Perform any preparatory tasks for mmap action. */
 	err = call_action_prepare(map, desc);
 	if (err)
 		return err;
 
+	/* Check the caller did nothing crazy. */
+	err = mmap_prepare_validate(&prev_desc, desc);
+	if (err)
+		return err;
+
 	/* Update fields permitted to be changed. */
 	map->pgoff = desc->pgoff;
 	if (desc->vm_file != map->file) {
@@ -2866,7 +2964,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,
 {
 	struct mm_struct *mm = current->mm;
 	struct vm_area_struct *vma = NULL;
-	bool have_mmap_prepare = file && file->f_op->mmap_prepare;
+	const bool have_mmap_prepare = file && file->f_op->mmap_prepare;
 	VMA_ITERATOR(vmi, mm, addr);
 	const pgoff_t anon_pgoff = addr >> PAGE_SHIFT;
 	MMAP_STATE(map, mm, &vmi, addr, len, pgoff, anon_pgoff, vma_flags, file);
@@ -2909,7 +3007,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,
 		allocated_new = true;
 	}
 
-	if (have_mmap_prepare && !map_is_anon(&map))
+	if (have_mmap_prepare && allocated_new && !map_is_anon(&map))
 		set_vma_user_defined_fields(vma, &map);
 
 	__mmap_complete(&map, vma);
@@ -3429,10 +3527,15 @@ int __vm_munmap(unsigned long start, size_t len, bool unlock)
 int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma)
 {
 	unsigned long charged = vma_pages(vma);
+	int err;
 
 	if (find_vma_intersection(mm, vma->vm_start, vma->vm_end))
 		return -ENOMEM;
 
+	err = mmap_validate_vma_flags(&vma->flags);
+	if (err)
+		return err;
+
 	if (vma_test(vma, VMA_ACCOUNT_BIT) &&
 	     security_vm_enough_memory_mm(mm, charged))
 		return -ENOMEM;
diff --git a/mm/vma.h b/mm/vma.h
index e97bd2dfa786d..af14ed7265ce3 100644
--- a/mm/vma.h
+++ b/mm/vma.h
@@ -780,14 +780,19 @@ struct vm_area_struct *vm_area_alloc(struct mm_struct *mm);
 struct vm_area_struct *vm_area_dup(struct vm_area_struct *orig);
 void vm_area_free(struct vm_area_struct *vma);
 
-/* vma_exec.c */
 #ifdef CONFIG_MMU
+int mmap_prepare_validate(const struct vm_area_desc *prev_desc,
+			  const struct vm_area_desc *desc);
+
+int mmap_hook_validate(unsigned long prev_start,
+		       const vma_flags_t *prev_flags,
+		       const struct vm_area_struct *vma);
+
+/* vma_exec.c */
 int create_init_stack_vma(struct mm_struct *mm, struct vm_area_struct **vmap,
 			  unsigned long *top_mem_p);
 int relocate_vma_down(struct vm_area_struct *vma, unsigned long shift);
-#endif
 
-#ifdef CONFIG_MMU
 /*
  * Denies creating a writable executable mapping or gaining executable permissions.
  *
@@ -836,6 +841,19 @@ static inline bool map_deny_write_exec(const vma_flags_t *old,
 
 	return false;
 }
+#else
+static inline int mmap_prepare_validate(const struct vm_area_desc *prev_desc,
+					const struct vm_area_desc *desc)
+{
+	return 0;
+}
+
+static inline int mmap_hook_validate(unsigned long prev_start,
+				     const vma_flags_t *prev_flags,
+				     const struct vm_area_struct *vma)
+{
+	return 0;
+}
 #endif
 
 struct vm_area_struct *__install_special_mapping(struct mm_struct *mm,
diff --git a/mm/vma_internal.h b/mm/vma_internal.h
index 4d300e7bbaf4c..4f73f0a4db796 100644
--- a/mm/vma_internal.h
+++ b/mm/vma_internal.h
@@ -18,7 +18,6 @@
 #include <linux/fs.h>
 #include <linux/huge_mm.h>
 #include <linux/hugetlb.h>
-#include <linux/hugetlb_inline.h>
 #include <linux/kernel.h>
 #include <linux/ksm.h>
 #include <linux/khugepaged.h>
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 245f68c75b289..0082afbdbbdd3 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -3413,13 +3413,14 @@ static int should_skip_vma(unsigned long start, unsigned long end, struct mm_wal
 	if (!vma_is_accessible(vma))
 		return true;
 
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return true;
 
 	if (!vma_has_recency(vma))
 		return true;
 
-	if (vma->vm_flags & (VM_LOCKED | VM_SPECIAL))
+	if (vma_test(vma, VMA_LOCKED_BIT) || vma_is_kernel_owned(vma) ||
+	    vma_is_fixed_mapping(vma))
 		return true;
 
 	if (vma == get_gate_vma(vma->vm_mm))
@@ -4363,8 +4364,8 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)
 	if (spin_is_contended(pvmw->ptl))
 		return true;
 
-	/* exclude special VMAs containing anon pages from COW */
-	if (vma->vm_flags & VM_SPECIAL)
+	/* exclude kernel-owned and fixed VMAs containing anon pages from COW */
+	if (vma_is_kernel_owned(vma) || vma_is_fixed_mapping(vma))
 		return true;
 
 	/* avoid taking the LRU lock under the PTL when possible */
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index c7d91476971cb..545a6f89f9e76 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -340,6 +340,9 @@ static int sel_open_policy(struct inode *inode, struct file *filp)
 	struct policy_load_memory *plm = NULL;
 	int rc;
 
+	if (filp->f_mode & FMODE_WRITE)
+		return -EACCES;
+
 	rc = avc_has_perm(current_sid(), SECINITSID_SECURITY,
 			  SECCLASS_SECURITY, SECURITY__READ_POLICY, NULL);
 	if (rc)
@@ -424,14 +427,6 @@ static const struct vm_operations_struct sel_mmap_policy_ops = {
 
 static int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma)
 {
-	if (vma->vm_flags & VM_SHARED) {
-		/* do not allow mprotect to make mapping writable */
-		vm_flags_clear(vma, VM_MAYWRITE);
-
-		if (vma->vm_flags & VM_WRITE)
-			return -EACCES;
-	}
-
 	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
 	vma->vm_ops = &sel_mmap_policy_ops;
 
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 62324282fcae9..37a157d558325 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -3760,39 +3760,26 @@ static __poll_t snd_pcm_poll(struct file *file, poll_table *wait)
 /*
  * mmap status record
  */
-static vm_fault_t snd_pcm_mmap_status_fault(struct vm_fault *vmf)
+static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,
+			       struct vm_area_struct *vma)
 {
-	struct snd_pcm_substream *substream = vmf->vma->vm_private_data;
+	const unsigned long size = vma->vm_end - vma->vm_start;
 	struct snd_pcm_runtime *runtime;
-	
-	if (substream == NULL)
-		return VM_FAULT_SIGBUS;
-	runtime = substream->runtime;
-	vmf->page = virt_to_page(runtime->status);
-	get_page(vmf->page);
-	return 0;
-}
+	struct page *page;
 
-static const struct vm_operations_struct snd_pcm_vm_ops_status =
-{
-	.fault =	snd_pcm_mmap_status_fault,
-};
+	BUILD_BUG_ON(sizeof(struct snd_pcm_mmap_status) > PAGE_SIZE);
 
-static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,
-			       struct vm_area_struct *area)
-{
-	long size;
-	if (!(area->vm_flags & VM_READ))
+	if (!(vma->vm_flags & VM_READ))
 		return -EINVAL;
-	size = area->vm_end - area->vm_start;
-	if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)))
+	if (size != PAGE_SIZE)
 		return -EINVAL;
-	area->vm_ops = &snd_pcm_vm_ops_status;
-	area->vm_private_data = substream;
-	vm_flags_mod(area, VM_DONTEXPAND | VM_DONTDUMP,
+
+	vm_flags_mod(vma, VM_DONTEXPAND | VM_DONTDUMP,
 		     VM_WRITE | VM_MAYWRITE);
 
-	return 0;
+	runtime = substream->runtime;
+	page = virt_to_page(runtime->status);
+	return vm_insert_page(vma, vma->vm_start, page);
 }
 
 /*
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index 16c09dac59d9b..61b08589e5929 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -352,14 +352,6 @@ enum {
 #define VM_ACCESS_FLAGS (VM_READ | VM_WRITE | VM_EXEC)
 #define VMA_ACCESS_FLAGS mk_vma_flags(VMA_READ_BIT, VMA_WRITE_BIT, VMA_EXEC_BIT)
 
-/*
- * Special vmas that are non-mergable, non-mlock()able.
- */
-#define VM_SPECIAL (VM_IO | VM_DONTEXPAND | VM_PFNMAP | VM_MIXEDMAP)
-
-#define VMA_SPECIAL_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_DONTEXPAND_BIT, \
-				       VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT)
-
 #define VMA_REMAP_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_PFNMAP_BIT,	\
 				     VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT)
 
@@ -454,11 +446,12 @@ static __always_inline bool vma_flags_empty(const vma_flags_t *flags)
 
 /* What action should be taken after an .mmap_prepare call is complete? */
 enum mmap_action_type {
-	MMAP_NOTHING,		/* Mapping is complete, no further action. */
-	MMAP_REMAP_PFN,		/* Remap PFN range. */
-	MMAP_IO_REMAP_PFN,	/* I/O remap PFN range. */
-	MMAP_SIMPLE_IO_REMAP,	/* I/O remap with guardrails. */
-	MMAP_MAP_KERNEL_PAGES,	/* Map kernel page range from an array. */
+	MMAP_NOTHING,
+	MMAP_REMAP_PFN,
+	MMAP_IO_REMAP_PFN,
+	MMAP_SIMPLE_IO_REMAP,		/* I/O remap with guardrails. */
+	MMAP_KERNEL_PAGES,		/* Map kernel page range from array. */
+	MMAP_DISCONTIG_KERNEL_PAGES,	/* Map kernel discontig page range. */
 };
 
 /*
@@ -1359,13 +1352,23 @@ static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
 	return file->f_op->mmap_prepare(desc);
 }
 
+int mmap_prepare_validate(const struct vm_area_desc *prev_desc,
+			  const struct vm_area_desc *desc);
+
 static inline int __compat_vma_mmap(struct vm_area_desc *desc,
 		struct vm_area_struct *vma)
 {
+	struct vm_area_desc prev_desc;
 	int err;
 
+	/* Derive state prior to mmap_prepare hook. */
+	compat_set_desc_from_vma(&prev_desc, desc->file, vma);
 	/* Perform any preparatory tasks for mmap action. */
 	err = mmap_action_prepare(desc);
+	if (err)
+		return err;
+	/* Check the caller did nothing crazy. */
+	err = mmap_prepare_validate(&prev_desc, desc);
 	if (err)
 		return err;
 	/* Update the VMA from the descriptor. */
@@ -1647,3 +1650,34 @@ static inline bool file_is_dev_zero(const struct file *file)
 {
 	return file && file->f_op == &zero_fops;
 }
+
+static inline bool vma_flags_is_kernel_owned(const vma_flags_t *flags)
+{
+	return vma_flags_test_any(flags, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT);
+}
+
+static inline bool vma_is_kernel_owned(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_kernel_owned(&vma->flags);
+}
+
+static inline bool vma_flags_can_merge(const vma_flags_t *flags)
+{
+	/*
+	 * VMA merging assumes that the properties of a VMA completely describe
+	 * the properties of that VMA.
+	 *
+	 * However, kernel-owned mappings may have established state upon mapping
+	 * not embodied in any attribute of the VMA.
+	 *
+	 * Additionally, PFN maps encode the source PFN of the range in
+	 * vma->vm_pgoff, which may otherwise cause spurious merges.
+	 */
+	if (vma_flags_is_kernel_owned(flags))
+		return false;
+	/* VMA explicitly marked as being unmergeable. */
+	if (vma_flags_test(flags, VMA_DONTEXPAND_BIT))
+		return false;
+
+	return true;
+}
diff --git a/tools/testing/vma/include/stubs.h b/tools/testing/vma/include/stubs.h
index d6136e19a8af3..48d1dc53df42c 100644
--- a/tools/testing/vma/include/stubs.h
+++ b/tools/testing/vma/include/stubs.h
@@ -193,7 +193,7 @@ static inline bool mapping_can_writeback(struct address_space *mapping)
 	return true;
 }
 
-static inline bool is_vm_hugetlb_page(struct vm_area_struct *vma)
+static inline bool vma_is_hugetlb(struct vm_area_struct *vma)
 {
 	return false;
 }
diff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c
index acaab282939c0..b26f1a66a1707 100644
--- a/tools/testing/vma/tests/merge.c
+++ b/tools/testing/vma/tests/merge.c
@@ -496,17 +496,11 @@ static bool test_vma_merge_special_flags(void)
 		.mm = &mm,
 		.vmi = &vmi,
 	};
-	vma_flag_t special_flags[] = { VMA_IO_BIT, VMA_DONTEXPAND_BIT,
+	vma_flag_t special_flags[] = { VMA_DONTEXPAND_BIT,
 		VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT };
-	vma_flags_t all_special_flags = EMPTY_VMA_FLAGS;
 	int i;
 	struct vm_area_struct *vma_left, *vma;
 
-	/* Make sure there aren't new VM_SPECIAL flags. */
-	for (i = 0; i < ARRAY_SIZE(special_flags); i++)
-		vma_flags_set(&all_special_flags, special_flags[i]);
-	ASSERT_FLAGS_SAME_MASK(&all_special_flags, VMA_SPECIAL_FLAGS);
-
 	/*
 	 * 01234
 	 * AAA
@@ -520,7 +514,7 @@ static bool test_vma_merge_special_flags(void)
 	 * 01234
 	 * AAA*
 	 *
-	 * This should merge if not for the VM_SPECIAL flag.
+	 * This should merge if not for the 'special' flag.
 	 */
 	vmg_set_range(&vmg, 0x3000, 0x4000, 3, vma_flags);
 	for (i = 0; i < ARRAY_SIZE(special_flags); i++) {

3/2 2026/09/08 22:26 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 51426
							output: 23
							thoughts: 1033
						

Thoughts:
**Initial Assessment: High-Value Fuzzing Target**

Alright, let's break down this patch. My immediate thought is, this is a prime candidate for fuzzing. It's a deep dive into core kernel memory management, touching on virtual memory areas (VMAs), mmap/mremap/madvise, mlock, userfaultfd, khugepaged, GUP, and even SCSI/USB device drivers and ALSA PCM. That's a significant surface area.

First, I see the refactoring of `is_vm_hugetlb_page(vma)` to `vma_is_hugetlb(vma)`, which, at face value, seems like a cosmetic change. However, the introduction of the new VMA helpers in `include/linux/mm.h`, like `vma_flags_is_hugetlb()`, `vma_is_kernel_owned()`, `vma_can_merge()`, etc., tells me that there's a deeper architectural shift. They've also removed `VM_SPECIAL` and `VMA_SPECIAL_FLAGS` and `VM_NO_KHUGEPAGED`. That's a strong indicator of change, and I need to understand the implications of these removals and replacements across the core memory management subsystems.

The replacement of `VM_SPECIAL` usages across the codebase is particularly concerning from a fuzzing perspective, as it touches many memory management code paths. The addition of the `MMAP_DISCONTIG_KERNEL_PAGES` action and the accompanying handlers – `mmap_action_map_discontig_kernel_pages()`, `map_discontig_kernel_pages_prepare()`, etc. – is definitely a new functionality that needs thorough examination.

The introduction of mmap validation functions, `mmap_validate_vma_flags()`, `mmap_validate()`, `mmap_prepare_validate()`, and `mmap_hook_validate()`, and their integration into critical mmap pathways like `mmap_file()`, `call_mmap_prepare()`, `__compat_vma_mmap()`, and `insert_vm_struct()` is a deliberate attempt to bolster the kernel's defensive posture, which is good. That said, anytime they attempt to increase security by adding validation/hooking in important kernel routines is a great area to test for bypasses or bugs.

The mlock/munlock tracking refactoring, specifically the changes to `mlock_vma_pages_range()` and the new check for `vma_test_any_mask(vma, VMA_LOCKED_MASK)` in `try_to_unmap_one()`, warrants close scrutiny. This is likely an area where subtle race conditions or incorrect state transitions could occur.

The updates to various drivers (SCSI, USB, Infiniband, video), filesystems, and sound/core/pcm_native.c are interesting, especially the direct replacement of a fault handler with `vm_insert_page` in `sound/core/pcm_native.c`, which tells me that this patch is touching low-level page management routines. I'll need to pay extra attention to these changes to see how they impact driver behavior and potential interactions with the core MM.

This touches reachability. Adds defensive checks (always good). Modifies mmap, mremap, madvise, and mlock paths. Affects GUP. The scope and depth of these changes make this an ideal candidate for fuzzing. The subsystems involved – Core MM, VMA management, mmap, mprotect, madvise, mlock, userfaultfd, khugepaged, GUP, along with SCSI, USB, and ALSA – form a rich attack surface. The changes in driver code and validation hooks significantly broaden that surface. I'll need to spend some time looking at the configs and symbols touched, but I'm ready to dive into the codebase. `WorthFuzzing = true`.



4/2 2026/09/08 22:26 tool grepper
0m Args:
{
  "Expression": "map_discontig_kernel_pages"
}

Results:
{
  "Output": "Documentation/filesystems/mmap_prepare.rst=142=pointer. These are:\n--\nDocumentation/filesystems/mmap_prepare.rst-166-\nDocumentation/filesystems/mmap_prepare.rst:167:* mmap_action_map_discontig_kernel_pages() - Maps a discontiguous range of\nDocumentation/filesystems/mmap_prepare.rst-168-  `struct page` pointers over the VMA. They must span from the start of the VMA,\n--\nDocumentation/filesystems/mmap_prepare.rst=229=With ``private`` being an additional user-controllable state variable,\nDocumentation/filesystems/mmap_prepare.rst:230:initialised via ``mmap_action_map_discontig_kernel_pages()``, and\nDocumentation/filesystems/mmap_prepare.rst-231-``vm_private_data`` being equal to the ``desc-\u003eprivate_data`` field set in\n--\ndrivers/scsi/sg.c=1260=sg_mmap_prepare(struct vm_area_desc *desc)\n--\ndrivers/scsi/sg.c-1277-\ndrivers/scsi/sg.c:1278:\tmmap_action_map_discontig_kernel_pages(desc, (void *)req_sz,\ndrivers/scsi/sg.c-1279-\t\t\t\t\t       \u0026sg_discontig_ops);\n--\ndrivers/usb/mon/mon_bin.c=1298=static int mon_bin_mmap_prepare(struct vm_area_desc *desc)\n--\ndrivers/usb/mon/mon_bin.c-1309-\ndrivers/usb/mon/mon_bin.c:1310:\tmmap_action_map_discontig_kernel_pages(desc, NULL, \u0026mon_discontig_ops);\ndrivers/usb/mon/mon_bin.c-1311-\treturn 0;\n--\ninclude/linux/mm.h=4823=static inline\ninclude/linux/mm.h:4824:void mmap_action_map_discontig_kernel_pages(struct vm_area_desc *desc,\ninclude/linux/mm.h-4825-\t\tvoid *init_private, const struct discontig_kernel_page_ops *ops)\n--\nmm/internal.h=1511=int map_kernel_pages_complete(struct vm_area_struct *vma,\nmm/internal.h-1512-\t\t\t      struct mmap_action *action);\nmm/internal.h:1513:int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc);\nmm/internal.h:1514:int map_discontig_kernel_pages_complete(struct vm_area_struct *vma,\nmm/internal.h-1515-\t\t\t\t\tstruct mmap_action *action);\n--\nmm/memory.c=2638=int map_kernel_pages_complete(struct vm_area_struct *vma,\n--\nmm/memory.c-2648-\nmm/memory.c:2649:int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc)\nmm/memory.c-2650-{\n--\nmm/memory.c=2663=static int apply_discontig_action(struct vm_area_struct *vma,\n--\nmm/memory.c-2689-\nmm/memory.c:2690:int map_discontig_kernel_pages_complete(struct vm_area_struct *vma,\nmm/memory.c-2691-\t\t\t\t\tstruct mmap_action *action)\n--\nmm/util.c=1447=int mmap_action_prepare(struct vm_area_desc *desc)\n--\nmm/util.c-1467-\tcase MMAP_DISCONTIG_KERNEL_PAGES:\nmm/util.c:1468:\t\treturn map_discontig_kernel_pages_prepare(desc);\nmm/util.c-1469-\t}\n--\nmm/util.c=1487=int mmap_action_complete(struct vm_area_struct *vma,\n--\nmm/util.c-1501-\tcase MMAP_DISCONTIG_KERNEL_PAGES:\nmm/util.c:1502:\t\terr = map_discontig_kernel_pages_complete(vma, action);\nmm/util.c-1503-\t\tbreak;\n"
}

5/2 2026/09/08 22:26 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 54466
							output: 22
							thoughts: 1103
						

Thoughts:
**Focus Symbol Identification for Fuzzing**

Okay, let's break this down. My goal here is to identify the most relevant functions for fuzzing, given that `WorthFuzzing=true`. The key is to avoid the common pitfalls – focusing on ubiquitous lifecycle functions instead of the specific logic that's been added or changed. I need to zero in on the entrypoints and the specific feature logic that's been impacted.

First, I need to make a list of candidate functions, then filter it. I've gone through the files and made a list of potentially interesting functions.

From `mm/memory.c`, I've got: `map_discontig_kernel_pages_prepare`, `map_discontig_kernel_pages_complete`, `map_kernel_pages_prepare`, and `map_kernel_pages_complete`. These seem relevant to memory mapping and could be good targets. Then there are some validate and merge functions from `mm/vma.c`: `mmap_prepare_validate`, `mmap_hook_validate`, and maybe some others.

`mm/mlock.c`, `mm/huge_memory.c`, `mm/madvise.c`, `mm/mremap.c`, and `mm/userfaultfd.c` have some functions as well, but it is less clear which ones are entry points so they can probably be safely ignored.

Then there are a few from device drivers: `sg_mmap_prepare` (in `drivers/scsi/sg.c`), `mon_bin_mmap_prepare` (in `drivers/usb/mon/mon_bin.c`) and `snd_pcm_mmap_status` (in `sound/core/pcm_native.c`). These are likely good targets since they are mmap's, with different structures. `sg_mmap_prepare` and `mon_bin_mmap_prepare` are static inline functions. These could be interesting, particularly if the logic within them has been modified, but I need to make sure to target the callers to avoid the static inline pitfall.

My next step is to ensure that these functions actually have symbols in `vmlinux`. I'm going through the list to check. The functions in question are: `map_discontig_kernel_pages_prepare`, `map_discontig_kernel_pages_complete`, `map_kernel_pages_prepare`, `map_kernel_pages_complete`, `mmap_prepare_validate`, `mmap_hook_validate`. Then, I'm checking the following, since the inlines might be the key to the bug here: `sg_mmap_prepare`, `mon_bin_mmap_prepare`, and `snd_pcm_mmap_status`. I'll need to double check if `snd_pcm_mmap_status` is static. If the issue is in the static inline function, I will need to target the caller.



6/2 2026/09/08 22:26 tool grepper
0m Args:
{
  "Expression": "snd_pcm_mmap_status"
}

Results:
{
  "Output": "Documentation/sound/kernel-api/writing-an-alsa-driver.rst=1463=is the relevant part of this file::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-1513-          /* -- mmap -- */\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:1514:          volatile struct snd_pcm_mmap_status *status;\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-1515-          volatile struct snd_pcm_mmap_control *control;\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=1715=The running status can be referred via ``runtime-\u003estatus``. This is\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:1716:a pointer to a struct snd_pcm_mmap_status record.\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-1717-For example, you can get the current\n--\ninclude/sound/pcm.h=360=struct snd_pcm_runtime {\n--\ninclude/sound/pcm.h-409-\t/* -- mmap -- */\ninclude/sound/pcm.h:410:\tstruct snd_pcm_mmap_status *status;\ninclude/sound/pcm.h-411-\tstruct snd_pcm_mmap_control *control;\n--\ninclude/uapi/sound/asound.h=481=struct snd_pcm_status {\n--\ninclude/uapi/sound/asound.h-506-#ifdef __SND_STRUCT_TIME64\ninclude/uapi/sound/asound.h:507:#define __snd_pcm_mmap_status64\t\tsnd_pcm_mmap_status\ninclude/uapi/sound/asound.h-508-#define __snd_pcm_mmap_control64\tsnd_pcm_mmap_control\n--\ninclude/uapi/sound/asound.h=515=struct __snd_timespec {\n--\ninclude/uapi/sound/asound.h-519-#else\ninclude/uapi/sound/asound.h:520:#define __snd_pcm_mmap_status\t\tsnd_pcm_mmap_status\ninclude/uapi/sound/asound.h-521-#define __snd_pcm_mmap_control\t\tsnd_pcm_mmap_control\n--\ninclude/uapi/sound/asound.h=524=struct __snd_timespec64 {\n--\ninclude/uapi/sound/asound.h-530-\ninclude/uapi/sound/asound.h:531:struct __snd_pcm_mmap_status {\ninclude/uapi/sound/asound.h-532-\tsnd_pcm_state_t state;\t\t/* RO: state - SNDRV_PCM_STATE_XXXX */\n--\ninclude/uapi/sound/asound.h=549=struct __snd_pcm_sync_ptr {\n--\ninclude/uapi/sound/asound.h-551-\tunion {\ninclude/uapi/sound/asound.h:552:\t\tstruct __snd_pcm_mmap_status status;\ninclude/uapi/sound/asound.h-553-\t\tunsigned char reserved[64];\n--\ninclude/uapi/sound/asound.h=568=typedef char __pad_after_uframe[sizeof(__u64) - sizeof(snd_pcm_uframes_t)];\n--\ninclude/uapi/sound/asound.h-570-\ninclude/uapi/sound/asound.h:571:struct __snd_pcm_mmap_status64 {\ninclude/uapi/sound/asound.h-572-\tsnd_pcm_state_t state;\t\t/* RO: state - SNDRV_PCM_STATE_XXXX */\n--\ninclude/uapi/sound/asound.h=594=struct __snd_pcm_sync_ptr64 {\n--\ninclude/uapi/sound/asound.h-597-\tunion {\ninclude/uapi/sound/asound.h:598:\t\tstruct __snd_pcm_mmap_status64 status;\ninclude/uapi/sound/asound.h-599-\t\tunsigned char reserved[64];\n--\nsound/core/pcm.c=873=int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,\n--\nsound/core/pcm.c-939-\nsound/core/pcm.c:940:\tsize = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));\nsound/core/pcm.c-941-\truntime-\u003estatus = alloc_pages_exact(size, GFP_KERNEL);\n--\nsound/core/pcm.c-951-\t\tfree_pages_exact(runtime-\u003estatus,\nsound/core/pcm.c:952:\t\t\t       PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));\nsound/core/pcm.c-953-\t\tkfree(runtime);\n--\nsound/core/pcm.c=978=void snd_pcm_detach_substream(struct snd_pcm_substream *substream)\n--\nsound/core/pcm.c-987-\tfree_pages_exact(runtime-\u003estatus,\nsound/core/pcm.c:988:\t\t       PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));\nsound/core/pcm.c-989-\tfree_pages_exact(runtime-\u003econtrol,\n--\nsound/core/pcm_compat.c=329=static int snd_pcm_ioctl_xfern_compat(struct snd_pcm_substream *substream,\n--\nsound/core/pcm_compat.c-375-/* X32 ABI has 64bit timespec and 64bit alignment */\nsound/core/pcm_compat.c:376:struct snd_pcm_mmap_status_x32 {\nsound/core/pcm_compat.c-377-\tsnd_pcm_state_t state;\n--\nsound/core/pcm_compat.c=392=struct snd_pcm_sync_ptr_x32 {\n--\nsound/core/pcm_compat.c-395-\tunion {\nsound/core/pcm_compat.c:396:\t\tstruct snd_pcm_mmap_status_x32 status;\nsound/core/pcm_compat.c-397-\t\tunsigned char reserved[64];\n--\nsound/core/pcm_compat.c=405=static int snd_pcm_ioctl_sync_ptr_x32(struct snd_pcm_substream *substream,\n--\nsound/core/pcm_compat.c-408-\tstruct snd_pcm_runtime *runtime = substream-\u003eruntime;\nsound/core/pcm_compat.c:409:\tvolatile struct snd_pcm_mmap_status *status;\nsound/core/pcm_compat.c-410-\tvolatile struct snd_pcm_mmap_control *control;\n--\nsound/core/pcm_compat.c-412-\tstruct snd_pcm_mmap_control scontrol;\nsound/core/pcm_compat.c:413:\tstruct snd_pcm_mmap_status sstatus;\nsound/core/pcm_compat.c-414-\tsnd_pcm_uframes_t boundary;\n--\nsound/core/pcm_compat.c=481=static int snd_pcm_ioctl_sync_ptr_buggy(struct snd_pcm_substream *substream,\n--\nsound/core/pcm_compat.c-486-\tstruct __snd_pcm_mmap_control64_buggy *sync_cp;\nsound/core/pcm_compat.c:487:\tvolatile struct snd_pcm_mmap_status *status;\nsound/core/pcm_compat.c-488-\tvolatile struct snd_pcm_mmap_control *control;\n--\nsound/core/pcm_native.c=3161=static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream,\n--\nsound/core/pcm_native.c-3164-\tstruct snd_pcm_runtime *runtime = substream-\u003eruntime;\nsound/core/pcm_native.c:3165:\tvolatile struct snd_pcm_mmap_status *status;\nsound/core/pcm_native.c-3166-\tvolatile struct snd_pcm_mmap_control *control;\n--\nsound/core/pcm_native.c-3168-\tstruct snd_pcm_mmap_control scontrol;\nsound/core/pcm_native.c:3169:\tstruct snd_pcm_mmap_status sstatus;\nsound/core/pcm_native.c-3170-\tint err;\n--\nsound/core/pcm_native.c-3205-\nsound/core/pcm_native.c:3206:struct snd_pcm_mmap_status32 {\nsound/core/pcm_native.c-3207-\tsnd_pcm_state_t state;\n--\nsound/core/pcm_native.c=3220=struct snd_pcm_sync_ptr32 {\n--\nsound/core/pcm_native.c-3222-\tunion {\nsound/core/pcm_native.c:3223:\t\tstruct snd_pcm_mmap_status32 status;\nsound/core/pcm_native.c-3224-\t\tunsigned char reserved[64];\n--\nsound/core/pcm_native.c=3255=static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream,\n--\nsound/core/pcm_native.c-3258-\tstruct snd_pcm_runtime *runtime = substream-\u003eruntime;\nsound/core/pcm_native.c:3259:\tvolatile struct snd_pcm_mmap_status *status;\nsound/core/pcm_native.c-3260-\tvolatile struct snd_pcm_mmap_control *control;\n--\nsound/core/pcm_native.c-3262-\tstruct snd_pcm_mmap_control scontrol;\nsound/core/pcm_native.c:3263:\tstruct snd_pcm_mmap_status sstatus;\nsound/core/pcm_native.c-3264-\tsnd_pcm_uframes_t boundary;\n--\nsound/core/pcm_native.c=3703=static __poll_t snd_pcm_poll(struct file *file, poll_table *wait)\n--\nsound/core/pcm_native.c-3762- */\nsound/core/pcm_native.c:3763:static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,\nsound/core/pcm_native.c-3764-\t\t\t       struct vm_area_struct *vma)\n--\nsound/core/pcm_native.c-3769-\nsound/core/pcm_native.c:3770:\tBUILD_BUG_ON(sizeof(struct snd_pcm_mmap_status) \u003e PAGE_SIZE);\nsound/core/pcm_native.c-3771-\n--\nsound/core/pcm_native.c=3839=static bool pcm_control_mmap_allowed(struct snd_pcm_file *pcm_file)\n--\nsound/core/pcm_native.c-3861-\nsound/core/pcm_native.c:3862:static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,\nsound/core/pcm_native.c-3863-\t\t\t       struct vm_area_struct *area)\n--\nsound/core/pcm_native.c=4055=static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)\n--\nsound/core/pcm_native.c-4076-\t\t\treturn -ENXIO;\nsound/core/pcm_native.c:4077:\t\treturn snd_pcm_mmap_status(substream, file, area);\nsound/core/pcm_native.c-4078-\tcase SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD:\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h=481=struct snd_pcm_status {\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h-506-#ifdef __SND_STRUCT_TIME64\ntools/perf/trace/beauty/include/uapi/sound/asound.h:507:#define __snd_pcm_mmap_status64\t\tsnd_pcm_mmap_status\ntools/perf/trace/beauty/include/uapi/sound/asound.h-508-#define __snd_pcm_mmap_control64\tsnd_pcm_mmap_control\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h=515=struct __snd_timespec {\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h-519-#else\ntools/perf/trace/beauty/include/uapi/sound/asound.h:520:#define __snd_pcm_mmap_status\t\tsnd_pcm_mmap_status\ntools/perf/trace/beauty/include/uapi/sound/asound.h-521-#define __snd_pcm_mmap_control\t\tsnd_pcm_mmap_control\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h=524=struct __snd_timespec64 {\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h-530-\ntools/perf/trace/beauty/include/uapi/sound/asound.h:531:struct __snd_pcm_mmap_status {\ntools/perf/trace/beauty/include/uapi/sound/asound.h-532-\tsnd_pcm_state_t state;\t\t/* RO: state - SNDRV_PCM_STATE_XXXX */\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h=549=struct __snd_pcm_sync_ptr {\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h-551-\tunion {\ntools/perf/trace/beauty/include/uapi/sound/asound.h:552:\t\tstruct __snd_pcm_mmap_status status;\ntools/perf/trace/beauty/include/uapi/sound/asound.h-553-\t\tunsigned char reserved[64];\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h=568=typedef char __pad_after_uframe[sizeof(__u64) - sizeof(snd_pcm_uframes_t)];\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h-570-\ntools/perf/trace/beauty/include/uapi/sound/asound.h:571:struct __snd_pcm_mmap_status64 {\ntools/perf/trace/beauty/include/uapi/sound/asound.h-572-\tsnd_pcm_state_t state;\t\t/* RO: state - SNDRV_PCM_STATE_XXXX */\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h=594=struct __snd_pcm_sync_ptr64 {\n--\ntools/perf/trace/beauty/include/uapi/sound/asound.h-597-\tunion {\ntools/perf/trace/beauty/include/uapi/sound/asound.h:598:\t\tstruct __snd_pcm_mmap_status64 status;\ntools/perf/trace/beauty/include/uapi/sound/asound.h-599-\t\tunsigned char reserved[64];\n"
}

7/2 2026/09/08 22:26 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 60197
							output: 18
							thoughts: 439
						

Thoughts:
**My Investigation into Memory Mapping and Discontiguous Memory in the Kernel**

Okay, let's dive into this mmap situation. My initial focus is on how memory mapping is handled, particularly within the kernel. I'll start with the sound subsystem, specifically `sound/core/pcm_native.c`, and the `snd_pcm_mmap` function. It looks like `snd_pcm_mmap` is the core mmap file operation for PCM devices, which is what I'd expect. I see `snd_pcm_mmap_status`, a static function within the same file, which is interesting to note for its scope. `snd_pcm_mmap` itself is non-static, so it's clearly a file operation that's exposed to the rest of the system.

Now, let's broaden the scope. I'll look at a couple of other drivers to see if there are any patterns. I'll check out the SCSI subsystem in `drivers/scsi/sg.c`. I see functions like `sg_mmap_prepare`, `sg_discontig_init`, and `sg_discontig_get`. The "discontig" naming immediately grabs my attention, suggesting they handle discontiguous memory mapping, which I want to examine. Similarly, in the USB monitor driver, `drivers/usb/mon/mon_bin.c`, there's a similar set of functions: `mon_bin_mmap_prepare`, `mon_bin_discontig_init`, and `mon_bin_discontig_get`. This reinforces the idea of a common pattern in handling mmap operations with discontiguous memory.

I'll move on to the core memory management code in `mm/memory.c`. Here, I'm finding functions like `map_discontig_kernel_pages_prepare`, `map_discontig_kernel_pages_complete`, `map_kernel_pages_prepare`, and `map_kernel_pages_complete`. The names give a hint to the high-level flow of the memory mapping process. Then, there are a few other relevant-looking functions: `zap_special_vma_range`, which also feels important.

I'll poke around in `mm/vma.c` and find `mmap_prepare_validate` and `mmap_hook_validate`. They seem to be focused on validating mmap operations.

The `mm/mlock.c` file gives us `mlock_vma_pages_range`, related to the `mlock` system call, which is also relevant. And `mm/madvise.c` includes `madvise_vma_behavior`.

Next, I'll shift gears slightly and check the configuration options for these drivers, to see how these features can be enabled. My goal is to determine what aspects are configurable at compile time. I want to look for configurations like `USB_MON`, `CHR_DEV_SG`, and `SND_PCM` alongside `HUGETLBFS` and `HUGETLB_PAGE`. I'll want to check the `Kconfig` files for these options to see their dependencies, and any hints on their purpose.



8/2 2026/09/08 22:26 tool grepper
0m Args:
{
  "Expression": "config USB_MON"
}

Results:
{
  "Output": "drivers/usb/mon/Kconfig-5-\ndrivers/usb/mon/Kconfig:6:config USB_MON\ndrivers/usb/mon/Kconfig-7-\ttristate \"USB Monitor\"\n"
}

9/2 2026/09/08 22:26 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 61398
							output: 21
							thoughts: 11
						

Thoughts:
Okay, here's my attempt at summarizing the thought process, as if it were my own, assuming I'm an expert in the field:

**Assessing the `CHR_DEV_SG` Context**

Right, let's take a look at `CHR_DEV_SG`. I need to understand what this specifically refers to in the current context. Is it a component, a process, a dataset, a particular configuration? What are we trying to achieve or troubleshoot? My expertise allows me to quickly determine the possible range of interpretations for something like this. Based on my previous experience, I can immediately start thinking about how to best to understand it. I need to figure out what kind of system or process it relates to. This is where experience and domain knowledge come into play, and I am going to apply them now.



10/2 2026/09/08 22:26 tool grepper
0m Args:
{
  "Expression": "config CHR_DEV_SG"
}

Results:
{
  "Output": "drivers/scsi/Kconfig=116=config BLK_DEV_SR\n--\ndrivers/scsi/Kconfig-130-\ndrivers/scsi/Kconfig:131:config CHR_DEV_SG\ndrivers/scsi/Kconfig-132-\ttristate \"SCSI generic support\"\n"
}

11/2 2026/09/08 22:26 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 61691
							output: 20
							thoughts: 18
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:

**Investigating Audio & Huge Pages**

Alright, let's dive into this. My initial thought is to cross-reference two specific kernel configurations: `SND_PCM` and `HUGETLB_PAGE`. My hunch is that they might be interacting, or that their absence could be contributing to the problems I'm seeing. I'm thinking about performance implications, potential resource contention, or perhaps even subtle bugs that could be exposed by certain audio workloads. My experience tells me that these often crop up when audio devices try to work efficiently, and the underlying memory system is not configured optimally. I need to understand if the configuration or state of these two kernel settings can lead to inefficiencies, unexpected behavior, or even outright failures in audio playback, recording, or processing. I'll need to remember that these have wider effects to, not just audio.



12/2 2026/09/08 22:26 tool grepper
0m Args:
{
  "Expression": "config HUGETLB_PAGE"
}

Results:
{
  "Output": "arch/sh/mm/Kconfig=158=choice\n--\narch/sh/mm/Kconfig-163-\narch/sh/mm/Kconfig:164:config HUGETLB_PAGE_SIZE_64K\narch/sh/mm/Kconfig-165-\tbool \"64kB\"\n--\narch/sh/mm/Kconfig-167-\narch/sh/mm/Kconfig:168:config HUGETLB_PAGE_SIZE_256K\narch/sh/mm/Kconfig-169-\tbool \"256kB\"\n--\narch/sh/mm/Kconfig-171-\narch/sh/mm/Kconfig:172:config HUGETLB_PAGE_SIZE_1MB\narch/sh/mm/Kconfig-173-\tbool \"1MB\"\narch/sh/mm/Kconfig-174-\narch/sh/mm/Kconfig:175:config HUGETLB_PAGE_SIZE_4MB\narch/sh/mm/Kconfig-176-\tbool \"4MB\"\n--\narch/sh/mm/Kconfig-178-\narch/sh/mm/Kconfig:179:config HUGETLB_PAGE_SIZE_64MB\narch/sh/mm/Kconfig-180-\tbool \"64MB\"\n--\nfs/Kconfig=262=if HUGETLBFS\nfs/Kconfig:263:config HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON\nfs/Kconfig-264-\tbool \"HugeTLB Vmemmap Optimization (HVO) defaults to on\"\n--\nfs/Kconfig=271=endif # HUGETLBFS\nfs/Kconfig-272-\nfs/Kconfig:273:config HUGETLB_PAGE\nfs/Kconfig-274-\tdef_bool HUGETLBFS\n--\nfs/Kconfig-276-\nfs/Kconfig:277:config HUGETLB_PAGE_OPTIMIZE_VMEMMAP\nfs/Kconfig-278-\tdef_bool HUGETLB_PAGE\n--\nmm/Kconfig=689=config ARCH_HAS_PMD_SOFTLEAVES\n--\nmm/Kconfig-691-\nmm/Kconfig:692:config HUGETLB_PAGE_SIZE_VARIABLE\nmm/Kconfig-693-\tdef_bool n\n"
}

13/2 2026/09/08 22:26 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 62524
							output: 18
							thoughts: 18
						

Thoughts:
Here's my take on those thoughts:

**Investigating the Sound System Configuration**

Okay, time to dive into this sound system configuration. I need to zero in on a specific element, probably `SND_PCM`. Right, let's start by looking at the `sound/core/Kconfig` file. That's the typical place to find the kernel configuration options related to sound drivers and settings. I suspect that's where I can find the options related to the core Pulse Code Modulation features I'm interested in. I should be able to discern any details about this component of the audio stack right there. Time to see what's what.



14/2 2026/09/08 22:27 tool grepper
0m Args:
{
  "Expression": "config SND_PCM"
}

Results:
{
  "Output": "sound/core/Kconfig=3=config SND_TIMER\n--\nsound/core/Kconfig-5-\nsound/core/Kconfig:6:config SND_PCM\nsound/core/Kconfig-7-\ttristate\n--\nsound/core/Kconfig-9-\nsound/core/Kconfig:10:config SND_PCM_ELD\nsound/core/Kconfig-11-\tbool\nsound/core/Kconfig-12-\nsound/core/Kconfig:13:config SND_PCM_IEC958\nsound/core/Kconfig-14-\tbool\n--\nsound/core/Kconfig=80=config SND_MIXER_OSS\n--\nsound/core/Kconfig-91-\nsound/core/Kconfig:92:config SND_PCM_OSS\nsound/core/Kconfig-93-\ttristate \"OSS PCM (digital audio) API\"\n--\nsound/core/Kconfig-104-\nsound/core/Kconfig:105:config SND_PCM_OSS_PLUGINS\nsound/core/Kconfig-106-\tbool \"OSS PCM (digital audio) API - Include plugin system\"\n--\nsound/core/Kconfig-113-\nsound/core/Kconfig:114:config SND_PCM_TIMER\nsound/core/Kconfig-115-\tbool \"PCM timer interface\" if EXPERT\n--\nsound/core/Kconfig=196=config SND_DEBUG_VERBOSE\n--\nsound/core/Kconfig-204-\nsound/core/Kconfig:205:config SND_PCM_XRUN_DEBUG\nsound/core/Kconfig-206-\tbool \"Enable PCM ring buffer overrun/underrun debugging\"\n--\nsound/drivers/Kconfig=92=config SND_ALOOP\n--\nsound/drivers/Kconfig-111-\nsound/drivers/Kconfig:112:config SND_PCMTEST\nsound/drivers/Kconfig-113-\ttristate \"Virtual PCM test driver\"\n--\nsound/pcmcia/Kconfig-3-\nsound/pcmcia/Kconfig:4:menuconfig SND_PCMCIA\nsound/pcmcia/Kconfig-5-\tbool \"PCMCIA sound devices\"\n"
}

15/2 2026/09/08 22:27 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 63244
							output: 18
							thoughts: 63
						

Thoughts:
**Examining the Sound System Dependencies**

Okay, let's take a look at the dependencies for `SND` and `SND_PCM`. It seems pretty straightforward. I see that `SND_PCM` is actually selected by a few drivers I'm familiar with, like `SND_DUMMY` and `SND_ALOOP`, and even the more general `SND` driver itself. Now I'm curious what *enables* `SND_PCM` in the first place, so I'll need to dig a bit deeper. Time to understand the selection chain here.



16/2 2026/09/08 22:27 tool grepper
0m Args:
{
  "Expression": "select SND_PCM"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 689 lines.\nUse more precise expression if possible.\n\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=3972=Driver with A Single Source File\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3988-       depends on SND\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:3989:       select SND_PCM\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3990-       help\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3994-\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:3995:The line ``select SND_PCM`` specifies that the driver xyz supports PCM.\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3996-In addition to SND_PCM, the following components are supported for\n--\ndrivers/gpu/drm/bridge/synopsys/Kconfig=17=config DRM_DW_HDMI_AHB_AUDIO\n--\ndrivers/gpu/drm/bridge/synopsys/Kconfig-19-\tdepends on DRM_DW_HDMI \u0026\u0026 SND\ndrivers/gpu/drm/bridge/synopsys/Kconfig:20:\tselect SND_PCM\ndrivers/gpu/drm/bridge/synopsys/Kconfig:21:\tselect SND_PCM_ELD\ndrivers/gpu/drm/bridge/synopsys/Kconfig:22:\tselect SND_PCM_IEC958\ndrivers/gpu/drm/bridge/synopsys/Kconfig-23-\thelp\n--\ndrivers/gpu/drm/bridge/synopsys/Kconfig=37=config DRM_DW_HDMI_GP_AUDIO\n--\ndrivers/gpu/drm/bridge/synopsys/Kconfig-39-\tdepends on DRM_DW_HDMI \u0026\u0026 SND\ndrivers/gpu/drm/bridge/synopsys/Kconfig:40:\tselect SND_PCM\ndrivers/gpu/drm/bridge/synopsys/Kconfig:41:\tselect SND_PCM_ELD\ndrivers/gpu/drm/bridge/synopsys/Kconfig:42:\tselect SND_PCM_IEC958\ndrivers/gpu/drm/bridge/synopsys/Kconfig-43-\thelp\n--\ndrivers/gpu/drm/vc4/Kconfig=2=config DRM_VC4\n--\ndrivers/gpu/drm/vc4/Kconfig-20-\tselect DRM_PANEL_BRIDGE\ndrivers/gpu/drm/vc4/Kconfig:21:\tselect SND_PCM\ndrivers/gpu/drm/vc4/Kconfig:22:\tselect SND_PCM_ELD\ndrivers/gpu/drm/vc4/Kconfig-23-\tselect SND_SOC_GENERIC_DMAENGINE_PCM\n--\ndrivers/media/i2c/Kconfig=1049=config VIDEO_TDA1997X\n--\ndrivers/media/i2c/Kconfig-1053-\tselect HDMI\ndrivers/media/i2c/Kconfig:1054:\tselect SND_PCM\ndrivers/media/i2c/Kconfig-1055-\tselect V4L2_FWNODE\n--\ndrivers/media/pci/cobalt/Kconfig=2=config VIDEO_COBALT\n--\ndrivers/media/pci/cobalt/Kconfig-11-\tselect I2C_ALGOBIT\ndrivers/media/pci/cobalt/Kconfig:12:\tselect SND_PCM\ndrivers/media/pci/cobalt/Kconfig-13-\tselect VIDEO_ADV7604\n--\ndrivers/media/pci/cx18/Kconfig=27=config VIDEO_CX18_ALSA\n--\ndrivers/media/pci/cx18/Kconfig-29-\tdepends on VIDEO_CX18 \u0026\u0026 SND\ndrivers/media/pci/cx18/Kconfig:30:\tselect SND_PCM\ndrivers/media/pci/cx18/Kconfig-31-\thelp\n--\ndrivers/media/pci/cx23885/Kconfig=2=config VIDEO_CX23885\n--\ndrivers/media/pci/cx23885/Kconfig-4-\tdepends on DVB_CORE \u0026\u0026 VIDEO_DEV \u0026\u0026 PCI \u0026\u0026 I2C \u0026\u0026 INPUT \u0026\u0026 SND\ndrivers/media/pci/cx23885/Kconfig:5:\tselect SND_PCM\ndrivers/media/pci/cx23885/Kconfig-6-\tselect I2C_ALGOBIT\n--\ndrivers/media/pci/cx25821/Kconfig=14=config VIDEO_CX25821_ALSA\n--\ndrivers/media/pci/cx25821/Kconfig-16-\tdepends on VIDEO_CX25821 \u0026\u0026 SND\ndrivers/media/pci/cx25821/Kconfig:17:\tselect SND_PCM\ndrivers/media/pci/cx25821/Kconfig-18-\thelp\n--\ndrivers/media/pci/cx88/Kconfig=17=config VIDEO_CX88_ALSA\n--\ndrivers/media/pci/cx88/Kconfig-19-\tdepends on VIDEO_CX88 \u0026\u0026 SND\ndrivers/media/pci/cx88/Kconfig:20:\tselect SND_PCM\ndrivers/media/pci/cx88/Kconfig-21-\thelp\n--\ndrivers/media/pci/ivtv/Kconfig=32=config VIDEO_IVTV_ALSA\n--\ndrivers/media/pci/ivtv/Kconfig-34-\tdepends on VIDEO_IVTV \u0026\u0026 SND\ndrivers/media/pci/ivtv/Kconfig:35:\tselect SND_PCM\ndrivers/media/pci/ivtv/Kconfig-36-\thelp\n--\ndrivers/media/pci/saa7134/Kconfig=18=config VIDEO_SAA7134_ALSA\n--\ndrivers/media/pci/saa7134/Kconfig-20-\tdepends on VIDEO_SAA7134 \u0026\u0026 SND\ndrivers/media/pci/saa7134/Kconfig:21:\tselect SND_PCM\ndrivers/media/pci/saa7134/Kconfig-22-\thelp\n--\ndrivers/media/pci/solo6x10/Kconfig=2=config VIDEO_SOLO6X10\n--\ndrivers/media/pci/solo6x10/Kconfig-9-\tselect VIDEOBUF2_DMA_CONTIG\ndrivers/media/pci/solo6x10/Kconfig:10:\tselect SND_PCM\ndrivers/media/pci/solo6x10/Kconfig-11-\thelp\n--\ndrivers/media/pci/tw686x/Kconfig=2=config VIDEO_TW686X\n--\ndrivers/media/pci/tw686x/Kconfig-7-\tselect VIDEOBUF2_DMA_SG\ndrivers/media/pci/tw686x/Kconfig:8:\tselect SND_PCM\ndrivers/media/pci/tw686x/Kconfig-9-\thelp\n--\ndrivers/media/usb/cx231xx/Kconfig=31=config VIDEO_CX231XX_ALSA\n--\ndrivers/media/usb/cx231xx/Kconfig-33-\tdepends on VIDEO_CX231XX \u0026\u0026 SND\ndrivers/media/usb/cx231xx/Kconfig:34:\tselect SND_PCM\ndrivers/media/usb/cx231xx/Kconfig-35-\n--\ndrivers/media/usb/em28xx/Kconfig=23=config VIDEO_EM28XX_ALSA\ndrivers/media/usb/em28xx/Kconfig-24-\tdepends on VIDEO_EM28XX \u0026\u0026 SND\ndrivers/media/usb/em28xx/Kconfig:25:\tselect SND_PCM\ndrivers/media/usb/em28xx/Kconfig-26-\ttristate \"Empia EM28xx ALSA audio module\"\n--\ndrivers/media/usb/go7007/Kconfig=2=config VIDEO_GO7007\n--\ndrivers/media/usb/go7007/Kconfig-8-\tselect CYPRESS_FIRMWARE\ndrivers/media/usb/go7007/Kconfig:9:\tselect SND_PCM\ndrivers/media/usb/go7007/Kconfig-10-\tselect VIDEO_SONY_BTF_MPX if MEDIA_SUBDRV_AUTOSELECT\n--\ndrivers/media/usb/usbtv/Kconfig=2=config VIDEO_USBTV\n--\ndrivers/media/usb/usbtv/Kconfig-4-\tdepends on VIDEO_DEV \u0026\u0026 SND\ndrivers/media/usb/usbtv/Kconfig:5:\tselect SND_PCM\ndrivers/media/usb/usbtv/Kconfig-6-\tselect VIDEOBUF2_VMALLOC\n--\ndrivers/most/Kconfig=36=config MOST_SND\n--\ndrivers/most/Kconfig-38-\tdepends on SND\ndrivers/most/Kconfig:39:\tselect SND_PCM\ndrivers/most/Kconfig-40-\thelp\n--\ndrivers/staging/vc04_services/bcm2835-audio/Kconfig=2=config SND_BCM2835\n--\ndrivers/staging/vc04_services/bcm2835-audio/Kconfig-4-\tdepends on (ARCH_BCM2835 || COMPILE_TEST) \u0026\u0026 SND\ndrivers/staging/vc04_services/bcm2835-audio/Kconfig:5:\tselect SND_PCM\ndrivers/staging/vc04_services/bcm2835-audio/Kconfig-6-\tselect BCM2835_VCHIQ if HAS_DMA\n--\ndrivers/usb/gadget/Kconfig=381=config USB_CONFIGFS_F_UAC1\n--\ndrivers/usb/gadget/Kconfig-385-\tselect USB_LIBCOMPOSITE\ndrivers/usb/gadget/Kconfig:386:\tselect SND_PCM\ndrivers/usb/gadget/Kconfig-387-\tselect USB_U_AUDIO\n--\ndrivers/usb/gadget/Kconfig=399=config USB_CONFIGFS_F_UAC1_LEGACY\n--\ndrivers/usb/gadget/Kconfig-403-\tselect USB_LIBCOMPOSITE\ndrivers/usb/gadget/Kconfig:404:\tselect SND_PCM\ndrivers/usb/gadget/Kconfig-405-\tselect USB_F_UAC1_LEGACY\n--\ndrivers/usb/gadget/Kconfig=412=config USB_CONFIGFS_F_UAC2\n--\ndrivers/usb/gadget/Kconfig-416-\tselect USB_LIBCOMPOSITE\ndrivers/usb/gadget/Kconfig:417:\tselect SND_PCM\ndrivers/usb/gadget/Kconfig-418-\tselect USB_U_AUDIO\n--\ndrivers/usb/gadget/legacy/Kconfig=75=config USB_AUDIO\n--\ndrivers/usb/gadget/legacy/Kconfig-78-\tselect USB_LIBCOMPOSITE\ndrivers/usb/gadget/legacy/Kconfig:79:\tselect SND_PCM\ndrivers/usb/gadget/legacy/Kconfig-80-\tselect USB_F_UAC1 if (GADGET_UAC1 \u0026\u0026 !GADGET_UAC1_LEGACY)\n--\nsound/aoa/Kconfig=2=menuconfig SND_AOA\n--\nsound/aoa/Kconfig-4-\tdepends on PPC_PMAC\nsound/aoa/Kconfig:5:\tselect SND_PCM\nsound/aoa/Kconfig-6-\thelp\n--\nsound/aoa/soundbus/Kconfig=2=config SND_AOA_SOUNDBUS\nsound/aoa/soundbus/Kconfig-3-\ttristate \"Apple Soundbus support\"\nsound/aoa/soundbus/Kconfig:4:\tselect SND_PCM\nsound/aoa/soundbus/Kconfig-5-\thelp\n--\nsound/arm/Kconfig=15=config SND_ARMAACI\n--\nsound/arm/Kconfig-17-\tdepends on ARM_AMBA\nsound/arm/Kconfig:18:\tselect SND_PCM\nsound/arm/Kconfig-19-\tselect SND_AC97_CODEC\n--\nsound/atmel/Kconfig=5=config SND_ATMEL_AC97C\nsound/atmel/Kconfig-6-\ttristate \"Atmel AC97 Controller (AC97C) driver\"\nsound/atmel/Kconfig:7:\tselect SND_PCM\nsound/atmel/Kconfig-8-\tselect SND_AC97_CODEC\n--\nsound/core/Kconfig=42=config SND_CORE_TEST\n--\nsound/core/Kconfig-44-\tdepends on KUNIT\nsound/core/Kconfig:45:\tselect SND_PCM\nsound/core/Kconfig-46-\tdefault KUNIT_ALL_TESTS\n--\nsound/core/Kconfig=92=config SND_PCM_OSS\n--\nsound/core/Kconfig-94-\tdepends on SND_OSSEMUL\nsound/core/Kconfig:95:\tselect SND_PCM\nsound/core/Kconfig-96-\thelp\n--\nsound/drivers/Kconfig=29=config SND_VX_LIB\n--\nsound/drivers/Kconfig-32-\tselect SND_HWDEP\nsound/drivers/Kconfig:33:\tselect SND_PCM\nsound/drivers/Kconfig-34-\nsound/drivers/Kconfig=35=config SND_AC97_CODEC\nsound/drivers/Kconfig-36-\ttristate\nsound/drivers/Kconfig:37:\tselect SND_PCM\nsound/drivers/Kconfig-38-\tselect AC97_BUS\n--\nsound/drivers/Kconfig=49=config SND_PCSP\n--\nsound/drivers/Kconfig-52-\tdepends on INPUT\nsound/drivers/Kconfig:53:\tselect SND_PCM\nsound/drivers/Kconfig-54-\thelp\n--\nsound/drivers/Kconfig=79=config SND_DUMMY\nsound/drivers/Kconfig-80-\ttristate \"Dummy (/dev/null) soundcard\"\nsound/drivers/Kconfig:81:\tselect SND_PCM\nsound/drivers/Kconfig-82-\thelp\n--\nsound/drivers/Kconfig=92=config SND_ALOOP\nsound/drivers/Kconfig-93-\ttristate \"Generic loopback driver (PCM)\"\nsound/drivers/Kconfig:94:\tselect SND_PCM\nsound/drivers/Kconfig-95-\tselect SND_TIMER\n--\nsound/drivers/Kconfig=112=config SND_PCMTEST\n--\nsound/drivers/Kconfig-114-\tdepends on DEBUG_FS\nsound/drivers/Kconfig:115:\tselect SND_PCM\nsound/drivers/Kconfig-116-\thelp\n--\nsound/firewire/Kconfig=11=config SND_FIREWIRE_LIB\nsound/firewire/Kconfig-12-\ttristate\nsound/firewire/Kconfig:13:\tselect SND_PCM\nsound/firewire/Kconfig-14-\tselect SND_RAWMIDI\n--\nsound/hda/codecs/hdmi/Kconfig=14=config SND_HDA_CODEC_HDMI_GENERIC\n--\nsound/hda/codecs/hdmi/Kconfig-16-\tselect SND_DYNAMIC_MINORS\nsound/hda/codecs/hdmi/Kconfig:17:\tselect SND_PCM_ELD\nsound/hda/codecs/hdmi/Kconfig-18-\tdefault y\n--\nsound/hda/common/Kconfig=3=config SND_HDA\nsound/hda/common/Kconfig-4-\ttristate\nsound/hda/common/Kconfig:5:\tselect SND_PCM\nsound/hda/common/Kconfig-6-\tselect SND_VMASTER\n--\nsound/isa/Kconfig=4=config SND_WSS_LIB\nsound/isa/Kconfig-5-\ttristate\nsound/isa/Kconfig:6:\tselect SND_PCM\nsound/isa/Kconfig-7-\tselect SND_TIMER\n--\nsound/isa/Kconfig=12=config SND_SB8_DSP\nsound/isa/Kconfig-13-\ttristate\nsound/isa/Kconfig:14:\tselect SND_PCM\nsound/isa/Kconfig-15-\tselect SND_SB_COMMON\n--\nsound/isa/Kconfig=17=config SND_SB16_DSP\nsound/isa/Kconfig-18-\ttristate\nsound/isa/Kconfig:19:\tselect SND_PCM\nsound/isa/Kconfig-20-\tselect SND_SB_COMMON\n--\nsound/isa/Kconfig=42=config SND_AD1816A\n--\nsound/isa/Kconfig-47-\tselect SND_MPU401_UART\nsound/isa/Kconfig:48:\tselect SND_PCM\nsound/isa/Kconfig-49-\tselect SND_TIMER\n--\nsound/isa/Kconfig=174=config SND_ES1688\n--\nsound/isa/Kconfig-177-\tselect SND_MPU401_UART\nsound/isa/Kconfig:178:\tselect SND_PCM\nsound/isa/Kconfig-179-\thelp\n--\nsound/isa/Kconfig=186=config SND_ES18XX\n--\nsound/isa/Kconfig-189-\tselect SND_MPU401_UART\nsound/isa/Kconfig:190:\tselect SND_PCM\nsound/isa/Kconfig-191-\thelp\n--\nsound/isa/Kconfig=213=config SND_GUSCLASSIC\n--\nsound/isa/Kconfig-215-\tselect SND_RAWMIDI\nsound/isa/Kconfig:216:\tselect SND_PCM\nsound/isa/Kconfig-217-\tselect SND_TIMER\n--\nsound/isa/Kconfig=225=config SND_GUSEXTREME\n--\nsound/isa/Kconfig-228-\tselect SND_MPU401_UART\nsound/isa/Kconfig:229:\tselect SND_PCM\nsound/isa/Kconfig-230-\tselect SND_TIMER\n--\nsound/isa/Kconfig=341=config SND_MIRO\n--\nsound/isa/Kconfig-345-\tselect SND_MPU401_UART\nsound/isa/Kconfig:346:\tselect SND_PCM\nsound/isa/Kconfig-347-\thelp\n--\nsound/isa/Kconfig=441=config SND_MSND_PINNACLE\n--\nsound/isa/Kconfig-445-\tselect SND_MPU401_UART\nsound/isa/Kconfig:446:\tselect SND_PCM\nsound/isa/Kconfig-447-\thelp\n--\nsound/isa/Kconfig=454=config SND_MSND_CLASSIC\n--\nsound/isa/Kconfig-458-\tselect SND_MPU401_UART\nsound/isa/Kconfig:459:\tselect SND_PCM\nsound/isa/Kconfig-460-\thelp\n--\nsound/mips/Kconfig=13=config SND_SGI_O2\n--\nsound/mips/Kconfig-15-\tdepends on SGI_IP32\nsound/mips/Kconfig:16:\tselect SND_PCM\nsound/mips/Kconfig-17-\thelp\n--\nsound/mips/Kconfig=20=config SND_SGI_HAL2\n--\nsound/mips/Kconfig-22-\tdepends on SGI_HAS_HAL2\nsound/mips/Kconfig:23:\tselect SND_PCM\nsound/mips/Kconfig-24-\thelp\n--\nsound/mips/Kconfig=27=config SND_N64\n--\nsound/mips/Kconfig-29-\tdepends on MACH_NINTENDO64 \u0026\u0026 SND=y\nsound/mips/Kconfig:30:\tselect SND_PCM\nsound/mips/Kconfig-31-\thelp\n--\nsound/parisc/Kconfig=13=config SND_HARMONY\nsound/parisc/Kconfig-14-\ttristate \"Harmony/Vivace sound chip\"\nsound/parisc/Kconfig:15:\tselect SND_PCM\nsound/parisc/Kconfig-16-\thelp\n--\nsound/pci/Kconfig=24=config SND_ALS300\nsound/pci/Kconfig-25-\ttristate \"Avance Logic ALS300/ALS300+\"\nsound/pci/Kconfig:26:\tselect SND_PCM\nsound/pci/Kconfig-27-\tselect SND_AC97_CODEC\n--\nsound/pci/Kconfig=36=config SND_ALS4000\n--\nsound/pci/Kconfig-41-\tselect SND_MPU401_UART\nsound/pci/Kconfig:42:\tselect SND_PCM\nsound/pci/Kconfig-43-\tselect SND_SB_COMMON\n--\nsound/pci/Kconfig=65=config SND_ASIHPI\n--\nsound/pci/Kconfig-68-\tselect FW_LOADER\nsound/pci/Kconfig:69:\tselect SND_PCM\nsound/pci/Kconfig-70-\tselect SND_HWDEP\n--\nsound/pci/Kconfig=156=config SND_AZT3328\n--\nsound/pci/Kconfig-159-\tselect SND_MPU401_UART\nsound/pci/Kconfig:160:\tselect SND_PCM\nsound/pci/Kconfig-161-\tselect SND_RAWMIDI\n--\nsound/pci/Kconfig=177=config SND_BT87X\nsound/pci/Kconfig-178-\ttristate \"Bt87x Audio Capture\"\nsound/pci/Kconfig:179:\tselect SND_PCM\nsound/pci/Kconfig-180-\thelp\n--\nsound/pci/Kconfig=211=config SND_CMIPCI\n--\nsound/pci/Kconfig-215-\tselect SND_MPU401_UART\nsound/pci/Kconfig:216:\tselect SND_PCM\nsound/pci/Kconfig-217-\thelp\n--\nsound/pci/Kconfig=228=config SND_OXYGEN\n--\nsound/pci/Kconfig-231-\tselect SND_OXYGEN_LIB\nsound/pci/Kconfig:232:\tselect SND_PCM\nsound/pci/Kconfig-233-\tselect SND_MPU401_UART\n--\nsound/pci/Kconfig=299=config SND_CS5535AUDIO\n--\nsound/pci/Kconfig-303-\tdepends on GPIOLIB_LEGACY || !OLPC\nsound/pci/Kconfig:304:\tselect SND_PCM\nsound/pci/Kconfig-305-\tselect SND_AC97_CODEC\n--\nsound/pci/Kconfig=319=config SND_CTXFI\n--\nsound/pci/Kconfig-321-\tdepends on HAS_IOPORT\nsound/pci/Kconfig:322:\tselect SND_PCM\nsound/pci/Kconfig-323-\thelp\n--\nsound/pci/Kconfig=330=config SND_DARLA20\n--\nsound/pci/Kconfig-332-\tselect FW_LOADER\nsound/pci/Kconfig:333:\tselect SND_PCM\nsound/pci/Kconfig-334-\thelp\n--\nsound/pci/Kconfig=340=config SND_GINA20\n--\nsound/pci/Kconfig-342-\tselect FW_LOADER\nsound/pci/Kconfig:343:\tselect SND_PCM\nsound/pci/Kconfig-344-\thelp\n--\nsound/pci/Kconfig=350=config SND_LAYLA20\n--\nsound/pci/Kconfig-353-\tselect SND_RAWMIDI\nsound/pci/Kconfig:354:\tselect SND_PCM\nsound/pci/Kconfig-355-\thelp\n--\nsound/pci/Kconfig=361=config SND_DARLA24\n--\nsound/pci/Kconfig-363-\tselect FW_LOADER\nsound/pci/Kconfig:364:\tselect SND_PCM\nsound/pci/Kconfig-365-\thelp\n--\nsound/pci/Kconfig=371=config SND_GINA24\n--\nsound/pci/Kconfig-373-\tselect FW_LOADER\nsound/pci/Kconfig:374:\tselect SND_PCM\nsound/pci/Kconfig-375-\thelp\n--\nsound/pci/Kconfig=381=config SND_LAYLA24\n--\nsound/pci/Kconfig-384-\tselect SND_RAWMIDI\nsound/pci/Kconfig:385:\tselect SND_PCM\nsound/pci/Kconfig-386-\thelp\n--\nsound/pci/Kconfig=392=config SND_MONA\n--\nsound/pci/Kconfig-395-\tselect SND_RAWMIDI\nsound/pci/Kconfig:396:\tselect SND_PCM\nsound/pci/Kconfig-397-\thelp\n--\nsound/pci/Kconfig=403=config SND_MIA\n--\nsound/pci/Kconfig-406-\tselect SND_RAWMIDI\nsound/pci/Kconfig:407:\tselect SND_PCM\nsound/pci/Kconfig-408-\thelp\n--\nsound/pci/Kconfig=414=config SND_ECHO3G\n--\nsound/pci/Kconfig-417-\tselect SND_RAWMIDI\nsound/pci/Kconfig:418:\tselect SND_PCM\nsound/pci/Kconfig-419-\thelp\n--\nsound/pci/Kconfig=425=config SND_INDIGO\n--\nsound/pci/Kconfig-427-\tselect FW_LOADER\nsound/pci/Kconfig:428:\tselect SND_PCM\nsound/pci/Kconfig-429-\thelp\n--\nsound/pci/Kconfig=435=config SND_INDIGOIO\n--\nsound/pci/Kconfig-437-\tselect FW_LOADER\nsound/pci/Kconfig:438:\tselect SND_PCM\nsound/pci/Kconfig-439-\thelp\n--\nsound/pci/Kconfig=445=config SND_INDIGODJ\n--\nsound/pci/Kconfig-447-\tselect FW_LOADER\nsound/pci/Kconfig:448:\tselect SND_PCM\nsound/pci/Kconfig-449-\thelp\n--\nsound/pci/Kconfig=455=config SND_INDIGOIOX\n--\nsound/pci/Kconfig-457-\tselect FW_LOADER\nsound/pci/Kconfig:458:\tselect SND_PCM\nsound/pci/Kconfig-459-\thelp\n--\nsound/pci/Kconfig=465=config SND_INDIGODJX\n--\nsound/pci/Kconfig-467-\tselect FW_LOADER\nsound/pci/Kconfig:468:\tselect SND_PCM\nsound/pci/Kconfig-469-\thelp\n--\nsound/pci/Kconfig=514=config SND_ENS1370\n--\nsound/pci/Kconfig-517-\tselect SND_RAWMIDI\nsound/pci/Kconfig:518:\tselect SND_PCM\nsound/pci/Kconfig-519-\thelp\n--\nsound/pci/Kconfig=609=config SND_HDSP\n--\nsound/pci/Kconfig-613-\tselect SND_RAWMIDI\nsound/pci/Kconfig:614:\tselect SND_PCM\nsound/pci/Kconfig-615-\thelp\n--\nsound/pci/Kconfig=625=config SND_HDSPM\n--\nsound/pci/Kconfig-628-\tselect SND_RAWMIDI\nsound/pci/Kconfig:629:\tselect SND_PCM\nsound/pci/Kconfig-630-\thelp\n--\nsound/pci/Kconfig=699=config SND_KORG1212\nsound/pci/Kconfig-700-\ttristate \"Korg 1212 IO\"\nsound/pci/Kconfig:701:\tselect SND_PCM\nsound/pci/Kconfig-702-\thelp\n--\nsound/pci/Kconfig=708=config SND_LOLA\nsound/pci/Kconfig-709-\ttristate \"Digigram Lola\"\nsound/pci/Kconfig:710:\tselect SND_PCM\nsound/pci/Kconfig-711-\thelp\n--\nsound/pci/Kconfig=717=config SND_LX6464ES\n--\nsound/pci/Kconfig-719-\tdepends on HAS_IOPORT_MAP\nsound/pci/Kconfig:720:\tselect SND_PCM\nsound/pci/Kconfig-721-\thelp\n--\nsound/pci/Kconfig=749=config SND_MIXART\n--\nsound/pci/Kconfig-752-\tselect SND_HWDEP\nsound/pci/Kconfig:753:\tselect SND_PCM\nsound/pci/Kconfig-754-\thelp\n--\nsound/pci/Kconfig=770=config SND_PCXHR\n--\nsound/pci/Kconfig-773-\tselect FW_LOADER\nsound/pci/Kconfig:774:\tselect SND_PCM\n\n"
}

17/2 2026/09/08 22:27 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 70966
							output: 353
							thoughts: 608
						

Thoughts:
**My Assessment of the Fuzzing Target**

Okay, let's break this down systematically. First, I need to confirm if there are any specific kernel configurations that might make this patch series more interesting from a fuzzing perspective. The configs listed (`HUGETLBFS`, `CHR_DEV_SG`, `USB_MON`, and `SND_DUMMY`) could definitely introduce unique code paths, so I'll keep them in mind. I'm also considering `SND_ALOOP` and `SND_PCM` as potential alternatives for `SND_DUMMY`, which I'll keep on the back burner.

Next, the "focus symbols" are key. These are the functions added or heavily modified in this series, and they're what I need to dig into. I'm looking at `map_discontig_kernel_pages_prepare`, `map_discontig_kernel_pages_complete`, the various `mmap_validate` functions, `mlock_vma_pages_range`, and the SCSI and ALSA-related discontiguous memory management (`sg_discontig_get`, `sg_discontig_init`, `snd_pcm_mmap_status`, etc.). They stand out. I'm avoiding generic functions because that's not where the novel changes are.

Now, the "WorthFuzzing" section seals the deal. The description of core MM refactoring involving VMA flags, discontiguous kernel page mapping, new VMA validation logic, refactored mlock behavior, and driver adaptations... that's exactly the kind of complex, potentially error-prone code I want to target. And the fact that it's fully reachable in a standard QEMU environment is perfect. The assertions are an additional bonus.

I'm confident in my assessment. The configurations seem reasonable, and I've honed in on the key symbols, as well as the important validation code. This looks like a promising target. I have checked for a double check on configs, focus symbols, and output formatting. Therefore, I will call `set-results`.



18/2 2026/09/08 22:27 tool set-results
0m Args:
{
  "EnableConfigs": [
    "HUGETLBFS",
    "CHR_DEV_SG",
    "USB_MON",
    "SND_DUMMY"
  ],
  "FocusSymbols": [
    "map_discontig_kernel_pages_prepare",
    "map_discontig_kernel_pages_complete",
    "mmap_prepare_validate",
    "mmap_hook_validate",
    "mlock_vma_pages_range",
    "sg_discontig_get",
    "sg_discontig_init",
    "mon_bin_discontig_get",
    "mon_bin_discontig_init",
    "snd_pcm_mmap_status"
  ],
  "Reasoning": "This patch series introduces substantial changes to core memory management (MM), including replacing VM_SPECIAL with new granular VMA classification helpers (vma_is_kernel_owned, vma_is_fixed_mapping, vma_can_merge, vma_is_persistent, vma_can_gup), adding a new mmap action for mapping discontiguous kernel pages (MMAP_DISCONTIG_KERNEL_PAGES) along with new prepare/complete hooks, adding strict mmap validation logic (mmap_prepare_validate, mmap_hook_validate), refactoring mlock/munlock logic, and migrating several drivers (SCSI generic sg, USB mon_bin, ALSA PCM native) to the new interfaces. These paths are fully reachable and introduce new runtime assertions (WARN_ON_ONCE, VM_WARN_ON_ONCE).",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "HUGETLBFS",
    "CHR_DEV_SG",
    "USB_MON",
    "SND_DUMMY"
  ],
  "FocusSymbols": [
    "map_discontig_kernel_pages_prepare",
    "map_discontig_kernel_pages_complete",
    "mmap_prepare_validate",
    "mmap_hook_validate",
    "mlock_vma_pages_range",
    "sg_discontig_get",
    "sg_discontig_init",
    "mon_bin_discontig_get",
    "mon_bin_discontig_init",
    "snd_pcm_mmap_status"
  ],
  "Reasoning": "This patch series introduces substantial changes to core memory management (MM), including replacing VM_SPECIAL with new granular VMA classification helpers (vma_is_kernel_owned, vma_is_fixed_mapping, vma_can_merge, vma_is_persistent, vma_can_gup), adding a new mmap action for mapping discontiguous kernel pages (MMAP_DISCONTIG_KERNEL_PAGES) along with new prepare/complete hooks, adding strict mmap validation logic (mmap_prepare_validate, mmap_hook_validate), refactoring mlock/munlock logic, and migrating several drivers (SCSI generic sg, USB mon_bin, ALSA PCM native) to the new interfaces. These paths are fully reachable and introduce new runtime assertions (WARN_ON_ONCE, VM_WARN_ON_ONCE).",
  "WorthFuzzing": true
}

19/1 2026/09/08 22:27 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

20/2 2026/09/08 22:27 agent kmsan-evaluator
0m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series refactors VMA handling and introduces a discontiguous kernel page mapping framework for mmap_prepare:\n1. It replaces `is_vm_hugetlb_page` with `vma_is_hugetlb` and introduces semantic helper predicates for VMA flag properties (`vma_is_kernel_owned`, `vma_is_fixed_mapping`, `vma_can_merge`, `vma_is_persistent`, `vma_can_gup`).\n2. It introduces `mmap_action_map_discontig_kernel_pages()` along with `struct discontig_kernel_page_state` and `struct discontig_kernel_page_ops`, converting driver mmap hooks (such as cmt_speech, hfi1, sg, and mon_bin) to use `mmap_prepare` and `remap_vmalloc_range`/discontiguous page helpers instead of custom `.fault` handlers.\n3. All newly introduced local structures (e.g., `struct discontig_kernel_page_state state` in `map_discontig_kernel_pages_complete` and `struct vm_area_desc prev_desc` via `compat_set_desc_from_vma`) are zero-initialized with designated initializers or `memset` before any field access.\n4. No kernel data structures or uninitialized struct paddings/buffers are exposed or copied to userspace via syscalls, ioctls, or netlink.\n\nAny potential issues introduced by this refactoring (e.g., page table manipulation, VMA locking, use-after-free, or out-of-bounds page access) fall squarely under KASAN, LOCKDEP, and standard MM assertion checks. There are no uninitialized memory use or info-leak risks requiring a dedicated KMSAN session.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 27e2d5e005e5b11519ad4486d292f37520bbd200
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Sep 8 22:26:20 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/filesystems/mmap_prepare.rst b/Documentation/filesystems/mmap_prepare.rst
index 82c99c95ad854..a476e1006bf12 100644
--- a/Documentation/filesystems/mmap_prepare.rst
+++ b/Documentation/filesystems/mmap_prepare.rst
@@ -164,5 +164,86 @@ pointer. These are:
   sufficient entries in the page array to cover the entire range of the
   described VMA.
 
+* mmap_action_map_discontig_kernel_pages() - Maps a discontiguous range of
+  `struct page` pointers over the VMA. They must span from the start of the VMA,
+  but may terminate prior to the end (leaving the remainder unmapped).
+
 **NOTE:** The ``action`` field should never normally be manipulated directly,
 rather you ought to use one of these helpers.
+
+Discontiguous Actions
+=====================
+
+Some actions can be performed across discontiguous ranges.
+
+Map kernel pages
+----------------
+
+To map kernel pages discontiguously, you must provide hooks using ``struct
+discontig_kernel_page_ops``:
+
+.. code-block:: C
+
+    struct discontig_kernel_page_ops {
+        int (*init)(void *vm_private_data, void **private);
+        int (*get)(struct discontig_kernel_page_state *state);
+    };
+
+The ``init`` hook is optional and allows state to be established before the
+operation starts, for instance taking a reference count. Nothing is invoked
+after the operation, so ``init`` must not leave locks held, and state that must
+be released once the mapping goes away should be released in
+``vm_ops->close``.
+
+The ``init`` hook, if provided, is invoked prior to the operation starting. It
+may update what is pointed to by ``vm_private_data`` and/or ``private``. If an
+error is returned, then the operation is aborted. The ``private`` field can be
+reassigned.
+
+**NOTE:** The operation may sleep between invocations of ``get``, so locks
+needed to stabilise state must be taken and released within each hook.
+
+The ``get`` handler is the key means through which the operation is
+executed. The current state of the operation is provided through ``struct
+discontig_kernel_page_state``:
+
+.. code-block:: C
+
+    struct discontig_kernel_page_state {
+        /* Map state. */
+        unsigned long start;            /* Start address of VMA. */
+        unsigned long end;              /* End address of VMA. */
+        unsigned long addr;             /* The current address to be mapped. */
+        pgoff_t pgoff;                  /* The current pgoff to be mapped. */
+        unsigned long nr_pages_mapped;  /* The number of pages mapped. */
+        unsigned long nr_pages_remain;  /* The number of pages remaining. */
+
+        /* User-defined state. */
+        void *vm_private_data;          /* VMA private data. */
+        void *private;                  /* Mapping private data. */
+
+        /* Users should not touch these, use discontig_kernel_map_*() helpers. */
+        ... internal fields ...
+    };
+
+With ``private`` being an additional user-controllable state variable,
+initialised via ``mmap_action_map_discontig_kernel_pages()``, and
+``vm_private_data`` being equal to the ``desc->private_data`` field set in
+the ``mmap_prepare()`` hook.
+
+In the ``get`` hook, the user must choose how to map kernel pages:
+
+* ``discontig_kernel_map_abort()`` - Call this to abort the operation, whatever
+  has been mapped so far will be retained, the rest of the mapping will SIGBUS
+  if accessed.
+* ``discontig_kernel_map_page()`` - Maps a single page, correctly handling
+  compound pages (if the compound page is bigger than the remaining pages in the
+  VMA, then only those pages that fit will be mapped). For a compound page, the
+  head page must be passed.
+* ``discontig_kernel_map_page_range()`` - Map an array of pages of a specified
+  size. Note that if the number of pages specified exceeds the VMA size then an
+  error will arise.
+
+If an error arises after ``init`` succeeded, the core unmaps the VMA, invoking
+``vm_ops->close`` if set, which is therefore the place to release any state
+that ``init`` established.
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9ba86450fe4af..3c1240ffc38df 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1463,14 +1463,12 @@ static int get_vma_page_shift(struct vm_area_struct *vma, unsigned long hva)
 {
 	unsigned long pa;
 
-	if (is_vm_hugetlb_page(vma) && !(vma->vm_flags & VM_PFNMAP))
+	if (vma_is_hugetlb(vma))
 		return huge_page_shift(hstate_vma(vma));
 
 	if (!(vma->vm_flags & VM_PFNMAP))
 		return PAGE_SHIFT;
 
-	VM_BUG_ON(is_vm_hugetlb_page(vma));
-
 	pa = (vma->vm_pgoff << PAGE_SHIFT) + (hva - vma->vm_start);
 
 #ifndef __PAGETABLE_PMD_FOLDED
diff --git a/arch/powerpc/mm/book3s64/radix_tlb.c b/arch/powerpc/mm/book3s64/radix_tlb.c
index 7de5760164a90..b4603a98224b3 100644
--- a/arch/powerpc/mm/book3s64/radix_tlb.c
+++ b/arch/powerpc/mm/book3s64/radix_tlb.c
@@ -627,7 +627,7 @@ void radix__local_flush_tlb_page(struct vm_area_struct *vma, unsigned long vmadd
 {
 #ifdef CONFIG_HUGETLB_PAGE
 	/* need the return fix for nohash.c */
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return radix__local_flush_hugetlb_page(vma, vmaddr);
 #endif
 	radix__local_flush_tlb_page_psize(vma->vm_mm, vmaddr, mmu_virtual_psize);
@@ -945,7 +945,7 @@ void radix__flush_tlb_page_psize(struct mm_struct *mm, unsigned long vmaddr,
 void radix__flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr)
 {
 #ifdef CONFIG_HUGETLB_PAGE
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return radix__flush_hugetlb_page(vma, vmaddr);
 #endif
 	radix__flush_tlb_page_psize(vma->vm_mm, vmaddr, mmu_virtual_psize);
@@ -1113,7 +1113,7 @@ void radix__flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
 
 {
 #ifdef CONFIG_HUGETLB_PAGE
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return radix__flush_hugetlb_tlb_range(vma, start, end);
 #endif
 
diff --git a/arch/powerpc/mm/nohash/e500_hugetlbpage.c b/arch/powerpc/mm/nohash/e500_hugetlbpage.c
index a134d28a0e4d3..b87623f04be53 100644
--- a/arch/powerpc/mm/nohash/e500_hugetlbpage.c
+++ b/arch/powerpc/mm/nohash/e500_hugetlbpage.c
@@ -180,7 +180,7 @@ book3e_hugetlb_preload(struct vm_area_struct *vma, unsigned long ea, pte_t pte)
  */
 void __update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		book3e_hugetlb_preload(vma, address, *ptep);
 }
 
diff --git a/arch/powerpc/mm/nohash/tlb.c b/arch/powerpc/mm/nohash/tlb.c
index 0a650742f3a00..07a2db16c2b15 100644
--- a/arch/powerpc/mm/nohash/tlb.c
+++ b/arch/powerpc/mm/nohash/tlb.c
@@ -278,7 +278,7 @@ void __flush_tlb_page(struct mm_struct *mm, unsigned long vmaddr,
 void flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr)
 {
 #ifdef CONFIG_HUGETLB_PAGE
-	if (vma && is_vm_hugetlb_page(vma))
+	if (vma && vma_is_hugetlb(vma))
 		flush_hugetlb_page(vma, vmaddr);
 #endif
 
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 6035b5ec95039..5c5c77f98bf0f 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -664,7 +664,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
 		return -EFAULT;
 	}
 
-	is_hugetlb = is_vm_hugetlb_page(vma);
+	is_hugetlb = vma_is_hugetlb(vma);
 	if (is_hugetlb)
 		vma_pageshift = huge_page_shift(hstate_vma(vma));
 	else
diff --git a/arch/riscv/mm/tlbflush.c b/arch/riscv/mm/tlbflush.c
index 962db300a1665..a74a7d5258aa1 100644
--- a/arch/riscv/mm/tlbflush.c
+++ b/arch/riscv/mm/tlbflush.c
@@ -149,7 +149,7 @@ void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
 {
 	unsigned long stride_size;
 
-	if (!is_vm_hugetlb_page(vma)) {
+	if (!vma_is_hugetlb(vma)) {
 		stride_size = PAGE_SIZE;
 	} else {
 		stride_size = huge_page_size(hstate_vma(vma));
diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c
index ff63ffb1dbd29..3f6783b93e679 100644
--- a/arch/s390/mm/gmap_helpers.c
+++ b/arch/s390/mm/gmap_helpers.c
@@ -102,7 +102,7 @@ __context_unsafe(/* pte_unmap_unlock() not instrumented */)
 
 	/* Find the vm address for the guest address */
 	vma = vma_lookup(mm, vmaddr);
-	if (!vma || is_vm_hugetlb_page(vma))
+	if (!vma || vma_is_hugetlb(vma))
 		return;
 
 	/* Get pointer to the page table entry */
@@ -139,7 +139,7 @@ void gmap_helper_discard(struct mm_struct *mm, unsigned long vmaddr, unsigned lo
 		vma = find_vma_intersection(mm, vmaddr, end);
 		if (!vma)
 			return;
-		if (!is_vm_hugetlb_page(vma))
+		if (!vma_is_hugetlb(vma))
 			zap_vma_range(vma, vmaddr, min(end, vma->vm_end) - vmaddr);
 		vmaddr = vma->vm_end;
 	}
@@ -247,7 +247,7 @@ static int __gmap_helper_unshare_zeropages(struct mm_struct *mm)
 		 * proof to catch unexpected zeropages in other mappings and
 		 * fail.
 		 */
-		if ((vma->vm_flags & VM_PFNMAP) || is_vm_hugetlb_page(vma))
+		if ((vma->vm_flags & VM_PFNMAP) || vma_is_hugetlb(vma))
 			continue;
 		addr = vma->vm_start;
 
diff --git a/arch/sparc/mm/init_64.c b/arch/sparc/mm/init_64.c
index 103db4683b165..9bbccb5d23a8f 100644
--- a/arch/sparc/mm/init_64.c
+++ b/arch/sparc/mm/init_64.c
@@ -413,7 +413,7 @@ void update_mmu_cache_range(struct vm_fault *vmf, struct vm_area_struct *vma,
 	if (mm->context.hugetlb_pte_count || mm->context.thp_pte_count) {
 		unsigned long hugepage_size = PAGE_SIZE;
 
-		if (is_vm_hugetlb_page(vma))
+		if (vma_is_hugetlb(vma))
 			hugepage_size = huge_page_size(hstate_vma(vma));
 
 		if (hugepage_size >= PUD_SIZE) {
diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c
index 65a2de82ecd29..0f60c0d076b62 100644
--- a/arch/x86/kernel/uprobes.c
+++ b/arch/x86/kernel/uprobes.c
@@ -715,7 +715,7 @@ static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsign
 
 	*new_mapping = true;
 	return _install_special_mapping(mm, vaddr, PAGE_SIZE,
-				VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_IO,
+				VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_MIXEDMAP,
 				&tramp_mapping);
 }
 
diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c
index a93eee7ddb9e9..fab34fea99c2f 100644
--- a/drivers/gpu/drm/drm_gpusvm.c
+++ b/drivers/gpu/drm/drm_gpusvm.c
@@ -9,9 +9,9 @@
 #include <linux/dma-mapping.h>
 #include <linux/export.h>
 #include <linux/hmm.h>
-#include <linux/hugetlb_inline.h>
 #include <linux/memremap.h>
 #include <linux/mm_types.h>
+#include <linux/mm.h>
 #include <linux/slab.h>
 
 #include <drm/drm_device.h>
@@ -1141,8 +1141,7 @@ drm_gpusvm_range_find_or_insert(struct drm_gpusvm *gpusvm,
 	 * limitations. If/when migrate_vma_* add more support, this logic will
 	 * have to change.
 	 */
-	migrate_devmem = ctx->devmem_possible &&
-		vma_is_anonymous(vas) && !is_vm_hugetlb_page(vas);
+	migrate_devmem = ctx->devmem_possible && vma_is_anonymous(vas);
 
 	chunk_size = drm_gpusvm_range_chunk_size(gpusvm, notifier, vas,
 						 fault_addr, gpuva_start,
diff --git a/drivers/hsi/clients/cmt_speech.c b/drivers/hsi/clients/cmt_speech.c
index 7226677ebde7a..801697b74d4f8 100644
--- a/drivers/hsi/clients/cmt_speech.c
+++ b/drivers/hsi/clients/cmt_speech.c
@@ -1084,22 +1084,6 @@ static void cs_hsi_stop(struct cs_hsi_iface *hi)
 	kfree(hi);
 }
 
-static vm_fault_t cs_char_vma_fault(struct vm_fault *vmf)
-{
-	struct cs_char *csdata = vmf->vma->vm_private_data;
-	struct page *page;
-
-	page = virt_to_page((void *)csdata->mmap_base);
-	get_page(page);
-	vmf->page = page;
-
-	return 0;
-}
-
-static const struct vm_operations_struct cs_char_vm_ops = {
-	.fault	= cs_char_vma_fault,
-};
-
 static int cs_char_fasync(int fd, struct file *file, int on)
 {
 	struct cs_char *csdata = file->private_data;
@@ -1256,18 +1240,19 @@ static long cs_char_ioctl(struct file *file, unsigned int cmd,
 	return r;
 }
 
-static int cs_char_mmap(struct file *file, struct vm_area_struct *vma)
+static int cs_char_mmap_prepare(struct vm_area_desc *desc)
 {
-	if (vma->vm_end < vma->vm_start)
-		return -EINVAL;
+	struct file *file = desc->file;
+	struct cs_char *csdata = file->private_data;
+	struct page **pages = (struct page **)&desc->private_data;
 
-	if (vma_pages(vma) != 1)
+	if (vma_desc_pages(desc) != 1)
 		return -EINVAL;
 
-	vm_flags_set(vma, VM_IO | VM_DONTDUMP | VM_DONTEXPAND);
-	vma->vm_ops = &cs_char_vm_ops;
-	vma->vm_private_data = file->private_data;
+	vma_desc_set_flags(desc, VMA_DONTDUMP_BIT, VMA_DONTEXPAND_BIT);
 
+	*pages = virt_to_page((void *)csdata->mmap_base);
+	mmap_action_map_kernel_pages_full(desc, pages);
 	return 0;
 }
 
@@ -1353,7 +1338,7 @@ static const struct file_operations cs_char_fops = {
 	.write		= cs_char_write,
 	.poll		= cs_char_poll,
 	.unlocked_ioctl	= cs_char_ioctl,
-	.mmap		= cs_char_mmap,
+	.mmap_prepare	= cs_char_mmap_prepare,
 	.open		= cs_char_open,
 	.release	= cs_char_release,
 	.fasync		= cs_char_fasync,
diff --git a/drivers/infiniband/hw/hfi1/file_ops.c b/drivers/infiniband/hw/hfi1/file_ops.c
index dc548e6802e24..7119d734edc7b 100644
--- a/drivers/infiniband/hw/hfi1/file_ops.c
+++ b/drivers/infiniband/hw/hfi1/file_ops.c
@@ -70,7 +70,6 @@ static int set_ctxt_pkey(struct hfi1_ctxtdata *uctxt, unsigned long arg);
 static int ctxt_reset(struct hfi1_ctxtdata *uctxt);
 static int manage_rcvq(struct hfi1_ctxtdata *uctxt, u16 subctxt,
 		       unsigned long arg);
-static vm_fault_t vma_fault(struct vm_fault *vmf);
 static long hfi1_file_ioctl(struct file *fp, unsigned int cmd,
 			    unsigned long arg);
 
@@ -85,10 +84,6 @@ static const struct file_operations hfi1_file_ops = {
 	.llseek = noop_llseek,
 };
 
-static const struct vm_operations_struct vm_ops = {
-	.fault = vma_fault,
-};
-
 /*
  * Types of memories mapped into user processes' space
  */
@@ -304,13 +299,13 @@ static ssize_t hfi1_write_iter(struct kiocb *kiocb, struct iov_iter *from)
 	return reqs;
 }
 
-static inline void mmap_cdbg(u16 ctxt, u8 subctxt, u8 type, u8 mapio, u8 vmf,
+static inline void mmap_cdbg(u16 ctxt, u8 subctxt, u8 type, u8 mapio, u8 is_vmalloc,
 			     u64 memaddr, void *memvirt, dma_addr_t memdma,
 			     ssize_t memlen, struct vm_area_struct *vma)
 {
 	hfi1_cdbg(PROC,
-		  "%u:%u type:%u io/vf/dma:%d/%d/%d, addr:0x%llx, len:%lu(%lu), flags:0x%lx",
-		  ctxt, subctxt, type, mapio, vmf, !!memdma,
+		  "%u:%u type:%u io/vmalloc/dma:%d/%d/%d, addr:0x%llx, len:%lu(%lu), flags:0x%lx",
+		  ctxt, subctxt, type, mapio, is_vmalloc, !!memdma,
 		  memaddr ?: (u64)memvirt, memlen,
 		  vma->vm_end - vma->vm_start, vma->vm_flags);
 }
@@ -325,7 +320,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		memaddr = 0;
 	void *memvirt = NULL;
 	dma_addr_t memdma = 0;
-	u8 subctxt, mapio = 0, vmf = 0, type;
+	u8 subctxt, mapio = 0, is_vmalloc = 0, type;
 	ssize_t memlen = 0;
 	int ret = 0;
 	u16 ctxt;
@@ -347,7 +342,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 	/*
 	 * vm_pgoff is used as a buffer selector cookie.  Always mmap from
 	 * the beginning.
-	 */ 
+	 */
 	vma->vm_pgoff = 0;
 	flags = vma->vm_flags;
 
@@ -366,7 +361,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		 */
 		memlen = PAGE_ALIGN(uctxt->sc->credits * PIO_BLOCK_SIZE);
 		flags &= ~VM_MAYREAD;
-		flags |= VM_DONTCOPY | VM_DONTEXPAND;
+		flags |= VM_DONTCOPY;
 		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
 		mapio = 1;
 		break;
@@ -438,7 +433,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 			memvirt = uctxt->egrbufs.buffers[i].addr;
 			memdma = uctxt->egrbufs.buffers[i].dma;
 			vma->vm_end += memlen;
-			mmap_cdbg(ctxt, subctxt, type, mapio, vmf, memaddr,
+			mmap_cdbg(ctxt, subctxt, type, mapio, is_vmalloc, memaddr,
 				  memvirt, memdma, memlen, vma);
 			ret = dma_mmap_coherent(&dd->pcidev->dev, vma,
 						memvirt, memdma, memlen);
@@ -467,7 +462,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		 * user registers.
 		 */
 		memlen = PAGE_SIZE;
-		flags |= VM_DONTCOPY | VM_DONTEXPAND;
+		flags |= VM_DONTCOPY;
 		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
 		mapio = 1;
 		break;
@@ -476,15 +471,10 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		 * Use the page where this context's flags are. User level
 		 * knows where it's own bitmap is within the page.
 		 */
-		memaddr = (unsigned long)
-			(dd->events + uctxt_offset(uctxt)) & PAGE_MASK;
+		memvirt = dd->events + uctxt_offset(uctxt);
+		memvirt = (void *)(((uintptr_t)memvirt) & PAGE_MASK);
 		memlen = PAGE_SIZE;
-		/*
-		 * v3.7 removes VM_RESERVED but the effect is kept by
-		 * using VM_IO.
-		 */
-		flags |= VM_IO | VM_DONTEXPAND;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	case STATUS:
 		if (flags & VM_WRITE) {
@@ -493,7 +483,6 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		}
 		memaddr = kvirt_to_phys((void *)dd->status);
 		memlen = PAGE_SIZE;
-		flags |= VM_IO | VM_DONTEXPAND;
 		break;
 	case RTAIL:
 		if (!HFI1_CAP_IS_USET(DMA_RTAIL)) {
@@ -514,23 +503,20 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 		flags &= ~VM_MAYWRITE;
 		break;
 	case SUBCTXT_UREGS:
-		memaddr = (u64)uctxt->subctxt_uregbase;
+		memvirt = uctxt->subctxt_uregbase;
 		memlen = PAGE_SIZE;
-		flags |= VM_IO | VM_DONTEXPAND;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	case SUBCTXT_RCV_HDRQ:
-		memaddr = (u64)uctxt->subctxt_rcvhdr_base;
+		memvirt = uctxt->subctxt_rcvhdr_base;
 		memlen = rcvhdrq_size(uctxt) * uctxt->subctxt_cnt;
-		flags |= VM_IO | VM_DONTEXPAND;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	case SUBCTXT_EGRBUF:
-		memaddr = (u64)uctxt->subctxt_rcvegrbuf;
+		memvirt = uctxt->subctxt_rcvegrbuf;
 		memlen = uctxt->egrbufs.size * uctxt->subctxt_cnt;
-		flags |= VM_IO | VM_DONTEXPAND;
 		flags &= ~VM_MAYWRITE;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	case SDMA_COMP: {
 		struct hfi1_user_sdma_comp_q *cq = fd->cq;
@@ -539,10 +525,9 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 			ret = -EFAULT;
 			goto done;
 		}
-		memaddr = (u64)cq->comps;
+		memvirt = cq->comps;
 		memlen = PAGE_ALIGN(sizeof(*cq->comps) * cq->nentries);
-		flags |= VM_IO | VM_DONTEXPAND;
-		vmf = 1;
+		is_vmalloc = 1;
 		break;
 	}
 	default:
@@ -559,12 +544,10 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 	}
 
 	vm_flags_reset(vma, flags);
-	mmap_cdbg(ctxt, subctxt, type, mapio, vmf, memaddr, memvirt, memdma, 
+	mmap_cdbg(ctxt, subctxt, type, mapio, is_vmalloc, memaddr, memvirt, memdma,
 		  memlen, vma);
-	if (vmf) {
-		vma->vm_pgoff = PFN_DOWN(memaddr);
-		vma->vm_ops = &vm_ops;
-		ret = 0;
+	if (is_vmalloc) {
+		ret = remap_vmalloc_range(vma, memvirt, 0);
 	} else if (memdma) {
 		ret = dma_mmap_coherent(&dd->pcidev->dev, vma,
 					memvirt, memdma, memlen);
@@ -588,24 +571,6 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma)
 	return ret;
 }
 
-/*
- * Local (non-chip) user memory is not mapped right away but as it is
- * accessed by the user-level code.
- */
-static vm_fault_t vma_fault(struct vm_fault *vmf)
-{
-	struct page *page;
-
-	page = vmalloc_to_page((void *)(vmf->pgoff << PAGE_SHIFT));
-	if (!page)
-		return VM_FAULT_SIGBUS;
-
-	get_page(page);
-	vmf->page = page;
-
-	return 0;
-}
-
 static __poll_t hfi1_poll(struct file *fp, struct poll_table_struct *pt)
 {
 	struct hfi1_ctxtdata *uctxt;
diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c
index 5408f002e6c01..12837b828b89f 100644
--- a/drivers/scsi/sg.c
+++ b/drivers/scsi/sg.c
@@ -1212,85 +1212,72 @@ sg_fasync(int fd, struct file *filp, int mode)
 	return fasync_helper(fd, filp, mode, &sfp->async_qp);
 }
 
-static vm_fault_t
-sg_vma_fault(struct vm_fault *vmf)
+static int sg_discontig_init(void *vm_private_data, void **private)
 {
-	struct vm_area_struct *vma = vmf->vma;
-	Sg_fd *sfp;
-	unsigned long offset, len, sa;
-	Sg_scatter_hold *rsv_schp;
-	int k, length;
-
-	if ((NULL == vma) || (!(sfp = (Sg_fd *) vma->vm_private_data)))
-		return VM_FAULT_SIGBUS;
-	rsv_schp = &sfp->reserve;
-	offset = vmf->pgoff << PAGE_SHIFT;
-	if (offset >= rsv_schp->bufflen)
-		return VM_FAULT_SIGBUS;
-	SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sfp->parentdp,
-				      "sg_vma_fault: offset=%lu, scatg=%d\n",
-				      offset, rsv_schp->k_use_sg));
-	sa = vma->vm_start;
-	length = 1 << (PAGE_SHIFT + rsv_schp->page_order);
-	for (k = 0; k < rsv_schp->k_use_sg && sa < vma->vm_end; k++) {
-		len = vma->vm_end - sa;
-		len = (len < length) ? len : length;
-		if (offset < len) {
-			struct page *page = rsv_schp->pages[k] + (offset >> PAGE_SHIFT);
-			get_page(page);	/* increment page count */
-			vmf->page = page;
-			return 0; /* success */
-		}
-		sa += len;
-		offset -= len;
+	const unsigned long req_sz = (unsigned long)*private;
+	Sg_fd *sfp = vm_private_data;
+	Sg_scatter_hold *rsv_schp = &sfp->reserve;
+	int err = 0;
+
+	mutex_lock(&sfp->f_mutex);
+	if (req_sz > rsv_schp->bufflen) {
+		err = -ENOMEM;	/* cannot map more than reserved buffer */
+		goto out;
+	}
+	sfp->mmap_called = 1; /* Prevents changes to buffer size. */
+out:
+	mutex_unlock(&sfp->f_mutex);
+	return err;
+}
+
+static int
+sg_discontig_get(struct discontig_kernel_page_state *state)
+{
+	Sg_fd *sfp = state->vm_private_data;
+	Sg_scatter_hold *rsv_schp = &sfp->reserve;
+	const unsigned int order = rsv_schp->page_order;
+	const pgoff_t nr_pages = state->nr_pages_mapped;
+
+	if (nr_pages >= (rsv_schp->bufflen >> PAGE_SHIFT)) {
+		discontig_kernel_map_abort(state);
+		return 0;
 	}
 
-	return VM_FAULT_SIGBUS;
+	SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sfp->parentdp,
+				      "sg_discontig_get: offset=%lu, scatg=%d\n",
+				      nr_pages << PAGE_SHIFT, rsv_schp->k_use_sg));
+
+	discontig_kernel_map_page(state, rsv_schp->pages[nr_pages >> order]);
+	return 0;
 }
 
-static const struct vm_operations_struct sg_mmap_vm_ops = {
-	.fault = sg_vma_fault,
+static const struct discontig_kernel_page_ops sg_discontig_ops = {
+	.init = sg_discontig_init,
+	.get = sg_discontig_get,
 };
 
 static int
-sg_mmap(struct file *filp, struct vm_area_struct *vma)
+sg_mmap_prepare(struct vm_area_desc *desc)
 {
-	Sg_fd *sfp;
-	unsigned long req_sz, len, sa;
-	Sg_scatter_hold *rsv_schp;
-	int k, length;
-	int ret = 0;
+	Sg_fd *sfp = desc->file->private_data;
+	const unsigned long req_sz = vma_desc_size(desc);
 
-	if ((!filp) || (!vma) || (!(sfp = (Sg_fd *) filp->private_data)))
+	if (!sfp)
 		return -ENXIO;
-	req_sz = vma->vm_end - vma->vm_start;
+
 	SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sfp->parentdp,
 				      "sg_mmap starting, vm_start=%p, len=%d\n",
-				      (void *) vma->vm_start, (int) req_sz));
-	if (vma->vm_pgoff)
+				      (void *) desc->start, (int) req_sz));
+
+	if (desc->pgoff)
 		return -EINVAL;	/* want no offset */
-	rsv_schp = &sfp->reserve;
-	mutex_lock(&sfp->f_mutex);
-	if (req_sz > rsv_schp->bufflen) {
-		ret = -ENOMEM;	/* cannot map more than reserved buffer */
-		goto out;
-	}
 
-	sa = vma->vm_start;
-	length = 1 << (PAGE_SHIFT + rsv_schp->page_order);
-	for (k = 0; k < rsv_schp->k_use_sg && sa < vma->vm_end; k++) {
-		len = vma->vm_end - sa;
-		len = (len < length) ? len : length;
-		sa += len;
-	}
+	vma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT);
+	desc->private_data = sfp;
 
-	sfp->mmap_called = 1;
-	vm_flags_set(vma, VM_IO | VM_DONTEXPAND | VM_DONTDUMP);
-	vma->vm_private_data = sfp;
-	vma->vm_ops = &sg_mmap_vm_ops;
-out:
-	mutex_unlock(&sfp->f_mutex);
-	return ret;
+	mmap_action_map_discontig_kernel_pages(desc, (void *)req_sz,
+					       &sg_discontig_ops);
+	return 0;
 }
 
 static void
@@ -1415,7 +1402,7 @@ static const struct file_operations sg_fops = {
 	.unlocked_ioctl = sg_ioctl,
 	.compat_ioctl = compat_ptr_ioctl,
 	.open = sg_open,
-	.mmap = sg_mmap,
+	.mmap_prepare = sg_mmap_prepare,
 	.release = sg_release,
 	.fasync = sg_fasync,
 };
diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c
index 687f6a8981f34..9d00b21a8153b 100644
--- a/drivers/usb/mon/mon_bin.c
+++ b/drivers/usb/mon/mon_bin.c
@@ -1219,6 +1219,15 @@ mon_bin_poll(struct file *file, struct poll_table_struct *wait)
 	return mask;
 }
 
+static void __mon_bin_vma_open(struct mon_reader_bin *rp)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&rp->b_lock, flags);
+	rp->mmap_active++;
+	spin_unlock_irqrestore(&rp->b_lock, flags);
+}
+
 /*
  * open and close: just keep track of how many times the device is
  * mapped, to use the proper memory allocation function.
@@ -1226,64 +1235,79 @@ mon_bin_poll(struct file *file, struct poll_table_struct *wait)
 static void mon_bin_vma_open(struct vm_area_struct *vma)
 {
 	struct mon_reader_bin *rp = vma->vm_private_data;
-	unsigned long flags;
 
-	spin_lock_irqsave(&rp->b_lock, flags);
-	rp->mmap_active++;
-	spin_unlock_irqrestore(&rp->b_lock, flags);
+	__mon_bin_vma_open(rp);
 }
 
-static void mon_bin_vma_close(struct vm_area_struct *vma)
+static void __mon_bin_vma_close(struct mon_reader_bin *rp)
 {
 	unsigned long flags;
 
-	struct mon_reader_bin *rp = vma->vm_private_data;
 	spin_lock_irqsave(&rp->b_lock, flags);
 	rp->mmap_active--;
 	spin_unlock_irqrestore(&rp->b_lock, flags);
 }
 
-/*
- * Map ring pages to user space.
- */
-static vm_fault_t mon_bin_vma_fault(struct vm_fault *vmf)
+static void mon_bin_vma_close(struct vm_area_struct *vma)
 {
-	struct mon_reader_bin *rp = vmf->vma->vm_private_data;
+	struct mon_reader_bin *rp = vma->vm_private_data;
+
+	__mon_bin_vma_close(rp);
+}
+
+static const struct vm_operations_struct mon_bin_vm_ops = {
+	.open =     mon_bin_vma_open,
+	.close =    mon_bin_vma_close,
+};
+
+static int mon_bin_discontig_init(void *vm_private_data, void **private)
+{
+	struct mon_reader_bin *rp = vm_private_data;
+
+	/* Dropped by mon_bin_vma_close() on unmap, including on error. */
+	__mon_bin_vma_open(rp);
+	return 0;
+}
+
+static int mon_bin_discontig_get(struct discontig_kernel_page_state *state)
+{
+	struct mon_reader_bin *rp = state->vm_private_data;
 	unsigned long offset, chunk_idx;
-	struct page *pageptr;
 	unsigned long flags;
 
 	spin_lock_irqsave(&rp->b_lock, flags);
-	offset = vmf->pgoff << PAGE_SHIFT;
+
+	offset = state->pgoff << PAGE_SHIFT;
 	if (offset >= rp->b_size) {
 		spin_unlock_irqrestore(&rp->b_lock, flags);
-		return VM_FAULT_SIGBUS;
+		discontig_kernel_map_abort(state);
+		return 0;
 	}
 	chunk_idx = offset / CHUNK_SIZE;
-	pageptr = rp->b_vec[chunk_idx].pg;
-	get_page(pageptr);
-	vmf->page = pageptr;
+	discontig_kernel_map_page(state, rp->b_vec[chunk_idx].pg);
+
 	spin_unlock_irqrestore(&rp->b_lock, flags);
 	return 0;
 }
 
-static const struct vm_operations_struct mon_bin_vm_ops = {
-	.open =     mon_bin_vma_open,
-	.close =    mon_bin_vma_close,
-	.fault =    mon_bin_vma_fault,
+static const struct discontig_kernel_page_ops mon_discontig_ops = {
+	.init = mon_bin_discontig_init,
+	.get = mon_bin_discontig_get,
 };
 
-static int mon_bin_mmap(struct file *filp, struct vm_area_struct *vma)
+static int mon_bin_mmap_prepare(struct vm_area_desc *desc)
 {
-	/* don't do anything here: "fault" will set up page table entries */
-	vma->vm_ops = &mon_bin_vm_ops;
+	const struct file *filp = desc->file;
 
-	if (vma->vm_flags & VM_WRITE)
+	if (vma_desc_test(desc, VMA_WRITE_BIT))
 		return -EPERM;
 
-	vm_flags_mod(vma, VM_DONTEXPAND | VM_DONTDUMP, VM_MAYWRITE);
-	vma->vm_private_data = filp->private_data;
-	mon_bin_vma_open(vma);
+	desc->vm_ops = &mon_bin_vm_ops;
+	vma_desc_clear_flags(desc, VMA_MAYWRITE_BIT);
+	vma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT);
+	desc->private_data = filp->private_data;
+
+	mmap_action_map_discontig_kernel_pages(desc, NULL, &mon_discontig_ops);
 	return 0;
 }
 
@@ -1298,7 +1322,7 @@ static const struct file_operations mon_fops_binary = {
 	.compat_ioctl =	mon_bin_compat_ioctl,
 #endif
 	.release =	mon_bin_release,
-	.mmap =		mon_bin_mmap,
+	.mmap_prepare = mon_bin_mmap_prepare,
 };
 
 static int mon_bin_wait_event(struct file *file, struct mon_reader_bin *rp)
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
index fd00b86e1ae60..fb359ecc39661 100644
--- a/drivers/video/fbdev/core/fb_defio.c
+++ b/drivers/video/fbdev/core/fb_defio.c
@@ -366,13 +366,13 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
 {
 	vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
 
+	if (WARN_ON_ONCE(!(info->flags & FBINFO_VIRTFB)))
+		return -EINVAL;
 	if (!try_module_get(THIS_MODULE))
 		return -EINVAL;
 
 	vma->vm_ops = &fb_deferred_io_vm_ops;
-	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
-	if (!(info->flags & FBINFO_VIRTFB))
-		vm_flags_set(vma, VM_IO);
+	vm_flags_set(vma, VM_MIXEDMAP | VM_DONTEXPAND | VM_DONTDUMP);
 	vma->vm_private_data = info->fbdefio_state;
 
 	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c
index c4fdecafd8560..958514a354338 100644
--- a/drivers/video/fbdev/ssd1307fb.c
+++ b/drivers/video/fbdev/ssd1307fb.c
@@ -763,6 +763,8 @@ static int ssd1307fb_probe(struct i2c_client *client)
 	info->fix.smem_start = __pa(vmem);
 	info->fix.smem_len = vmem_size;
 
+	info->flags = FBINFO_VIRTFB;
+
 	fb_deferred_io_init(info);
 
 	i2c_set_clientdata(client, info);
diff --git a/fs/coredump.c b/fs/coredump.c
index ac3cd74808c64..9f729c594c47e 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -1608,7 +1608,7 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
 	}
 
 	/* Hugetlb memory check */
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		if ((vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_SHARED))
 			goto whole;
 		if (!(vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_PRIVATE))
@@ -1616,8 +1616,8 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
 		return 0;
 	}
 
-	/* Do not dump I/O mapped devices or special mappings */
-	if (vma->vm_flags & VM_IO)
+	/* Do not dump memory-mapped I/O, which may have side effects on read. */
+	if (vma_test(vma, VMA_IO_BIT))
 		return 0;
 
 	/* By default, dump shared memory if mapped from an anonymous file. */
diff --git a/fs/fuse/dax.c b/fs/fuse/dax.c
index 85cdf0199bc0b..a5994f1c637d9 100644
--- a/fs/fuse/dax.c
+++ b/fs/fuse/dax.c
@@ -826,7 +826,7 @@ int fuse_dax_mmap(struct file *file, struct vm_area_struct *vma)
 {
 	file_accessed(file);
 	vma->vm_ops = &fuse_dax_vm_ops;
-	vm_flags_set(vma, VM_MIXEDMAP | VM_HUGEPAGE);
+	vma_set_flags(vma, VMA_HUGEPAGE_BIT);
 	return 0;
 }
 
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 7611a8470ea26..ba7097d5720c0 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -108,7 +108,7 @@ static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
 	 * vma address alignment (but not the pgoff alignment) has
 	 * already been checked by prepare_hugepage_range.  If you add
 	 * any error returns here, do so after setting VM_HUGETLB, so
-	 * is_vm_hugetlb_page tests below unmap_region go the right
+	 * vma_is_hugetlb tests below unmap_region go the right
 	 * way when do_mmap unwinds (may be important on powerpc
 	 * and ia64).
 	 */
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index e671b4fd8dedd..565e6446bd312 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -3015,7 +3015,7 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,
 	 * hugetlb differs, see pagemap_hugetlb_category().
 	 */
 	categories = p->cur_vma_category;
-	if (userfaultfd_wp(vma) && !is_vm_hugetlb_page(vma))
+	if (userfaultfd_wp(vma) && !vma_is_hugetlb(vma))
 		categories |= PAGE_IS_WRITTEN;
 
 	if (!pagemap_scan_is_interesting_page(categories, p))
@@ -3028,7 +3028,7 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,
 	if (~p->arg.flags & PM_SCAN_WP_MATCHING)
 		return ret;
 
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		err = pagemap_scan_hugetlb_hole_wp(vma, addr, end);
 	else
 		err = uffd_wp_range(vma, addr, end - addr, true);
@@ -3470,7 +3470,7 @@ static int show_numa_map(struct seq_file *m, void *v)
 		seq_puts(m, " stack");
 	}
 
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		seq_puts(m, " huge");
 
 	/* Skip walking pages if gate VMA */
@@ -3499,7 +3499,7 @@ static int show_numa_map(struct seq_file *m, void *v)
 	if (md->swapcache)
 		seq_printf(m, " swapcache=%lu", md->swapcache);
 
-	if (md->active < md->pages && !is_vm_hugetlb_page(vma))
+	if (md->active < md->pages && !vma_is_hugetlb(vma))
 		seq_printf(m, " active=%lu", md->active);
 
 	if (md->writeback)
diff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h
index bdcc2778ac64f..dfb5dd3bec409 100644
--- a/include/asm-generic/tlb.h
+++ b/include/asm-generic/tlb.h
@@ -11,9 +11,9 @@
 #ifndef _ASM_GENERIC__TLB_H
 #define _ASM_GENERIC__TLB_H
 
+#include <linux/mm.h>
 #include <linux/mmu_notifier.h>
 #include <linux/swap.h>
-#include <linux/hugetlb_inline.h>
 #include <asm/tlbflush.h>
 #include <asm/cacheflush.h>
 
@@ -486,7 +486,7 @@ tlb_update_vma_flags(struct mmu_gather *tlb, struct vm_area_struct *vma)
 	 * We rely on tlb_end_vma() to issue a flush, such that when we reset
 	 * these values the batch is empty.
 	 */
-	tlb->vma_huge = is_vm_hugetlb_page(vma);
+	tlb->vma_huge = vma_is_hugetlb(vma);
 	tlb->vma_exec = !!(vma->vm_flags & VM_EXEC);
 
 	/*
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 80a5a03e9cee7..24727ece20fe5 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -7,7 +7,6 @@
 #include <linux/mm_types.h>
 #include <linux/mmdebug.h>
 #include <linux/fs.h>
-#include <linux/hugetlb_inline.h>
 #include <linux/cgroup.h>
 #include <linux/page_ref.h>
 #include <linux/list.h>
@@ -252,14 +251,14 @@ extern void __hugetlb_zap_end(struct vm_area_struct *vma,
 static inline void hugetlb_zap_begin(struct vm_area_struct *vma,
 				     unsigned long *start, unsigned long *end)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		__hugetlb_zap_begin(vma, start, end);
 }
 
 static inline void hugetlb_zap_end(struct vm_area_struct *vma,
 				   struct zap_details *details)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		__hugetlb_zap_end(vma, details);
 }
 
diff --git a/include/linux/hugetlb_inline.h b/include/linux/hugetlb_inline.h
deleted file mode 100644
index 5c29cd3223a1e..0000000000000
--- a/include/linux/hugetlb_inline.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef _LINUX_HUGETLB_INLINE_H
-#define _LINUX_HUGETLB_INLINE_H
-
-#include <linux/mm.h>
-
-#ifdef CONFIG_HUGETLB_PAGE
-
-static inline bool is_vma_hugetlb_flags(const vma_flags_t *flags)
-{
-	return vma_flags_test(flags, VMA_HUGETLB_BIT);
-}
-
-#else
-
-static inline bool is_vma_hugetlb_flags(const vma_flags_t *flags)
-{
-	return false;
-}
-
-#endif
-
-static inline bool is_vm_hugetlb_page(const struct vm_area_struct *vma)
-{
-	return is_vma_hugetlb_flags(&vma->flags);
-}
-
-#endif
diff --git a/include/linux/mm.h b/include/linux/mm.h
index c49ef99b4413b..1902d4c774817 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -576,14 +576,6 @@ enum {
 #define VM_ACCESS_FLAGS (VM_READ | VM_WRITE | VM_EXEC)
 #define VMA_ACCESS_FLAGS mk_vma_flags(VMA_READ_BIT, VMA_WRITE_BIT, VMA_EXEC_BIT)
 
-/*
- * Special vmas that are non-mergable, non-mlock()able.
- */
-
-#define VMA_SPECIAL_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_DONTEXPAND_BIT, \
-				       VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT)
-#define VM_SPECIAL vma_flags_to_legacy(VMA_SPECIAL_FLAGS)
-
 /*
  * Physically remapped pages are special. Tell the
  * rest of the world about it:
@@ -600,9 +592,6 @@ enum {
 #define VMA_REMAP_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_PFNMAP_BIT,	\
 				     VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT)
 
-/* This mask prevents VMA from being scanned with khugepaged */
-#define VM_NO_KHUGEPAGED (VM_SPECIAL | VM_HUGETLB)
-
 /* This mask defines which mm->def_flags a process can inherit its parent */
 #define VM_INIT_DEF_MASK	VM_NOHUGEPAGE
 
@@ -1612,6 +1601,211 @@ static inline bool vma_is_shared_maywrite(const struct vm_area_struct *vma)
 	return is_shared_maywrite(&vma->flags);
 }
 
+/**
+ * vma_flags_is_hugetlb() - Do the specified VMA flags indicate that the
+ * VMA is a hugetlb mapping?
+ * @flags: The VMA flags to test.
+ *
+ * Returns: true if the flags indicate a hugetlb mapping, false otherwise.
+ */
+static inline bool vma_flags_is_hugetlb(const vma_flags_t *flags)
+{
+	return IS_ENABLED(CONFIG_HUGETLB_PAGE) &&
+	       vma_flags_test(flags, VMA_HUGETLB_BIT);
+}
+
+/**
+ * vma_is_hugetlb() - Is @vma a hugetlb mapping?
+ * @vma: The VMA to test.
+ *
+ * Returns: true if @vma is a hugetlb mapping, false otherwise.
+ */
+static inline bool vma_is_hugetlb(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_hugetlb(&vma->flags);
+}
+
+/**
+ * vma_flags_is_kernel_owned() - Do the specified VMA flags indicate that the
+ * contents of the VMA are owned by the kernel rather than the core mm?
+ * @flags: The VMA flags to test.
+ *
+ * A kernel-owned mapping is one whose contents are established and controlled
+ * by the kernel, typically a driver, rather than by the core mm's fault and
+ * rmap machinery.
+ *
+ * The mapping may be memory-mapped I/O, kernel-allocated pages or ordinary
+ * pages the owner has chosen to map itself (shmem via a PFN map, for instance).
+ *
+ * But in all cases core mm must not populate, reclaim, migrate, Copy-on-Write
+ * or merge it of its own accord.
+ *
+ * The pages mapped, if any, may or may not be reference counted or map counted.
+ *
+ * Returns: true if the flags indicate a kernel-owned mapping.
+ */
+static inline bool vma_flags_is_kernel_owned(const vma_flags_t *flags)
+{
+	return vma_flags_test_any(flags, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT);
+}
+
+/**
+ * vma_is_kernel_owned() - Are the contents of @vma owned by the kernel?
+ * @vma: The VMA to test.
+ *
+ * See vma_flags_is_kernel_owned() for a description of this property.
+ *
+ * Returns: true if the VMA is kernel-owned.
+ */
+static inline bool vma_is_kernel_owned(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_kernel_owned(&vma->flags);
+}
+
+/**
+ * vma_flags_is_fixed_mapping() - Do the specified VMA flags indicate that this
+ * is a fixed mapping that cannot be expanded or merged?
+ * @flags: The VMA flags to test.
+ *
+ * Fixed mappings are those whose size is set at the point of mmap (for
+ * instance, a kernel-owned mapping of a fixed range of memory), and thus
+ * cannot be expanded or merged.
+ *
+ * Returns: true if the flags indicate a fixed mapping.
+ */
+static inline bool vma_flags_is_fixed_mapping(const vma_flags_t *flags)
+{
+	/*
+	 * VMA_PFNMAP_BIT should imply VMA_DONTEXPAND_BIT, but some callers set
+	 * only the former.
+	 */
+	return vma_flags_test_any(flags, VMA_PFNMAP_BIT, VMA_DONTEXPAND_BIT);
+}
+
+/**
+ * vma_is_fixed_mapping() - Is this VMA a fixed mapping that cannot be
+ * expanded or merged?
+ * @vma: The VMA to test.
+ *
+ * See vma_flags_is_fixed_mapping() for a description of this property.
+ *
+ * Returns: true if the VMA maps a fixed mapping.
+ */
+static inline bool vma_is_fixed_mapping(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_fixed_mapping(&vma->flags);
+}
+
+/**
+ * vma_flags_can_merge() - Do the specified VMA flags permit the VMA to be
+ * merged with another?
+ * @flags: The VMA flags to test.
+ * Returns: true if the flags permit merging, false otherwise.
+ */
+static inline bool vma_flags_can_merge(const vma_flags_t *flags)
+{
+	/*
+	 * VMA merging assumes that a VMA's flags and fields completely describe
+	 * its state.
+	 *
+	 * However, kernel-owned mappings may have established state upon mapping
+	 * not embodied in any attribute of the VMA.
+	 *
+	 * Additionally, private (CoW) PFN maps encode the source PFN of the
+	 * range in vma->vm_pgoff, which may otherwise cause spurious merges.
+	 */
+	if (vma_flags_is_kernel_owned(flags))
+		return false;
+	/* VMA explicitly marked as being unmergeable. */
+	if (vma_flags_is_fixed_mapping(flags))
+		return false;
+
+	return true;
+}
+
+/**
+ * vma_can_merge() - Do @vma's flags permit it to be merged with another VMA?
+ * @vma: The VMA to test.
+ * Returns: true if the flags permit merging, otherwise false.
+ */
+static inline bool vma_can_merge(const struct vm_area_struct *vma)
+{
+	return vma_flags_can_merge(&vma->flags);
+}
+
+/**
+ * vma_flags_is_persistent() - Do the specified VMA flags imply that the VMA
+ * contains persistent data?
+ * @flags: The VMA flags to test.
+ *
+ * Persistent in the sense that - if you write bytes to the mapping - do they
+ * stay written?
+ *
+ * If the kernel or a device could write to the memory independently of
+ * userland, or the kernel could arbitrarily discard it, then it is not
+ * persistent.
+ *
+ * Returns: true if the flags imply this VMA is persistent, otherwise false.
+ */
+static inline bool vma_flags_is_persistent(const vma_flags_t *flags)
+{
+	/* hugetlb is a fixed mapping, but its contents are the user's own. */
+	if (vma_flags_is_hugetlb(flags))
+		return true;
+	/*
+	 * MMIO mappings may not store what is written and may be changed by the
+	 * device. Kernel-owned and fixed mappings may be changed by their owner
+	 * without the user having initiated it.
+	 */
+	if (vma_flags_is_kernel_owned(flags) ||
+	    vma_flags_is_fixed_mapping(flags))
+		return false;
+	/* Droppable memory is discardable by definition. */
+	return !vma_flags_test_single_mask(flags, VMA_DROPPABLE);
+}
+
+/**
+ * vma_is_persistent() - Does the VMA contain persistent data?
+ * @vma: The VMA to test.
+ *
+ * See vma_flags_is_persistent() for details.
+ *
+ * Returns: true if the VMA is persistent, otherwise false.
+ */
+static inline bool vma_is_persistent(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_persistent(&vma->flags);
+}
+
+/**
+ * vma_flags_can_gup() - Do the specified VMA flags permit GUP to access the
+ * mapping's pages?
+ * @flags: The VMA flags to test.
+ *
+ * GUP cannot access pages belonging to mappings whose pages are not permitted
+ * to be accessed (VMA_PFNMAP_BIT) and must not manipulate or provide access to
+ * memory-mapped I/O ranges to users (VMA_IO_BIT).
+ *
+ * Returns: true if GUP may access pages from the mapping, otherwise false.
+ */
+static inline bool vma_flags_can_gup(const vma_flags_t *flags)
+{
+	return !vma_flags_test_any(flags, VMA_IO_BIT, VMA_PFNMAP_BIT);
+}
+
+/**
+ * vma_can_gup() - May GUP obtain pages from @vma?
+ * @vma: The VMA to test.
+ *
+ * See vma_flags_can_gup() for details.
+ *
+ * Returns: true if GUP may access pages from the mapping, otherwise false.
+ */
+static inline bool vma_can_gup(const struct vm_area_struct *vma)
+{
+	return vma_flags_can_gup(&vma->flags);
+}
+
 /**
  * vma_kernel_pagesize - Default page size granularity for this VMA.
  * @vma: The user mapping.
@@ -4602,7 +4796,7 @@ static inline void mmap_action_map_kernel_pages(struct vm_area_desc *desc,
 {
 	struct mmap_action *action = &desc->action;
 
-	action->type = MMAP_MAP_KERNEL_PAGES;
+	action->type = MMAP_KERNEL_PAGES;
 	action->map_kernel.start = start;
 	action->map_kernel.pages = pages;
 	action->map_kernel.nr_pages = nr_pages;
@@ -4626,10 +4820,55 @@ static inline void mmap_action_map_kernel_pages_full(struct vm_area_desc *desc,
 				     vma_desc_pages(desc));
 }
 
+static inline
+void mmap_action_map_discontig_kernel_pages(struct vm_area_desc *desc,
+		void *init_private, const struct discontig_kernel_page_ops *ops)
+{
+	struct mmap_action *action = &desc->action;
+
+	action->type = MMAP_DISCONTIG_KERNEL_PAGES;
+	action->map_kernel_discontig.init_private = init_private;
+	action->map_kernel_discontig.ops = ops;
+}
+
 int mmap_action_prepare(struct vm_area_desc *desc);
 int mmap_action_complete(struct vm_area_struct *vma,
 			 struct mmap_action *action, bool is_compat);
 
+static inline void
+discontig_kernel_map_abort(struct discontig_kernel_page_state *state)
+{
+	state->action = DISCONTIG_KERNEL_PAGE_ABORT;
+}
+
+static inline void
+discontig_kernel_map_page(struct discontig_kernel_page_state *state,
+			  struct page *page)
+{
+	struct folio *folio = page_folio(page);
+
+	if (folio_test_large(folio)) {
+		VM_WARN_ON_ONCE(page != folio_page(folio, 0));
+		state->action = DISCONTIG_KERNEL_PAGE_MAP_COMPOUND_PAGE;
+		state->__folio = folio;
+		state->__nr_pages = min(state->nr_pages_remain,
+					folio_nr_pages(folio));
+	} else {
+		state->action = DISCONTIG_KERNEL_PAGE_MAP_PAGE;
+		state->__page = page;
+		state->__nr_pages = 1;
+	}
+}
+
+static inline void
+discontig_kernel_map_page_range(struct discontig_kernel_page_state *state,
+				struct page **page_arr, unsigned long nr_pages)
+{
+	state->action = DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE;
+	state->__page_arr = page_arr;
+	state->__nr_pages = nr_pages;
+}
+
 /* Look up the first VMA which exactly match the interval vm_start ... vm_end */
 static inline struct vm_area_struct *find_exact_vma(struct mm_struct *mm,
 				unsigned long vm_start, unsigned long vm_end)
@@ -4747,9 +4986,6 @@ int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
 int vm_insert_page(struct vm_area_struct *, unsigned long addr, struct page *);
 int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,
 			struct page **pages, unsigned long *num);
-int map_kernel_pages_prepare(struct vm_area_desc *desc);
-int map_kernel_pages_complete(struct vm_area_struct *vma,
-			      struct mmap_action *action);
 int vm_map_pages(struct vm_area_struct *vma, struct page **pages,
 				unsigned long num);
 int vm_map_pages_zero(struct vm_area_struct *vma, struct page **pages,
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 5413bd10fff2c..0cb4f96039568 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -815,11 +815,47 @@ struct pfnmap_track_ctx {
 
 /* What action should be taken after an .mmap_prepare call is complete? */
 enum mmap_action_type {
-	MMAP_NOTHING,		/* Mapping is complete, no further action. */
-	MMAP_REMAP_PFN,		/* Remap PFN range. */
-	MMAP_IO_REMAP_PFN,	/* I/O remap PFN range. */
-	MMAP_SIMPLE_IO_REMAP,	/* I/O remap with guardrails. */
-	MMAP_MAP_KERNEL_PAGES,	/* Map kernel page range from array. */
+	MMAP_NOTHING,
+	MMAP_REMAP_PFN,
+	MMAP_IO_REMAP_PFN,
+	MMAP_SIMPLE_IO_REMAP,		/* I/O remap with guardrails. */
+	MMAP_KERNEL_PAGES,		/* Map kernel page range from array. */
+	MMAP_DISCONTIG_KERNEL_PAGES,	/* Map kernel discontig page range. */
+};
+
+enum discontig_kernel_page_action {
+	DISCONTIG_KERNEL_PAGE_ABORT,
+	DISCONTIG_KERNEL_PAGE_MAP_PAGE,
+	DISCONTIG_KERNEL_PAGE_MAP_COMPOUND_PAGE,
+	DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE,
+};
+
+struct discontig_kernel_page_state {
+	/* Map state. */
+	const unsigned long start;	/* Start address of VMA. */
+	const unsigned long end;	/* End address of VMA. */
+	unsigned long addr;		/* The current address to be mapped. */
+	pgoff_t pgoff;			/* The current pgoff to be mapped. */
+	unsigned long nr_pages_mapped;	/* The number of pages mapped. */
+	unsigned long nr_pages_remain;	/* The number of pages remaining. */
+
+	/* User-defined state. */
+	void *vm_private_data;		/* VMA private data. */
+	void *private;			/* Mapping private data. */
+
+	/* Users should not touch these, use discontig_kernel_map_*() helpers. */
+	enum discontig_kernel_page_action action;
+	union {
+		struct page *__page;
+		struct folio *__folio;
+		struct page **__page_arr;
+	};
+	unsigned long __nr_pages;
+};
+
+struct discontig_kernel_page_ops {
+	int (*init)(void *vm_private_data, void **private);
+	int (*get)(struct discontig_kernel_page_state *state);
 };
 
 /*
@@ -844,6 +880,10 @@ struct mmap_action {
 			unsigned long nr_pages;
 			pgoff_t pgoff;
 		} map_kernel;
+		struct {
+			void *init_private;
+			const struct discontig_kernel_page_ops *ops;
+		} map_kernel_discontig;
 	};
 	enum mmap_action_type type;
 
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 939f3a5e973f6..d7d8b312466c2 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -14,7 +14,6 @@
 #include <linux/gfp.h>
 #include <linux/bitops.h>
 #include <linux/hardirq.h> /* for in_interrupt() */
-#include <linux/hugetlb_inline.h>
 
 struct folio_batch;
 
diff --git a/include/linux/rmap.h b/include/linux/rmap.h
index 0b332770abeed..74cca0e3c7264 100644
--- a/include/linux/rmap.h
+++ b/include/linux/rmap.h
@@ -888,7 +888,7 @@ struct page_vma_mapped_walk {
 static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)
 {
 	/* HugeTLB pte is set to the relevant page table entry without pte_mapped. */
-	if (pvmw->pte && !is_vm_hugetlb_page(pvmw->vma))
+	if (pvmw->pte && !vma_is_hugetlb(pvmw->vma))
 		pte_unmap(pvmw->pte);
 	if (pvmw->ptl)
 		spin_unlock(pvmw->ptl);
diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h
index a4351cffc60ce..a14b8a9ffb7b1 100644
--- a/include/linux/userfaultfd_k.h
+++ b/include/linux/userfaultfd_k.h
@@ -18,7 +18,6 @@
 #include <linux/swap.h>
 #include <linux/leafops.h>
 #include <asm-generic/pgtable_uffd.h>
-#include <linux/hugetlb_inline.h>
 
 /* The set of all possible UFFD-related VM flags. */
 #define __VM_UFFD_FLAGS (VM_UFFD_MISSING | VM_UFFD_MINOR | \
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 7b6847200b431..b69fe5e343393 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -620,8 +620,9 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)
 	 * clears VM_MAYEXEC. Set VM_DONTEXPAND to avoid potential change
 	 * of user_vm_start. Set VM_DONTCOPY to prevent arena VMA from
 	 * being copied into the child process on fork.
+	 * This is a kernel page so set VM_MIXEDMAP.
 	 */
-	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY);
+	vm_flags_set(vma, VM_MIXEDMAP | VM_DONTEXPAND | VM_DONTCOPY);
 	vma->vm_ops = &arena_vm_ops;
 	return 0;
 }
diff --git a/kernel/events/core.c b/kernel/events/core.c
index a6c8e38a31104..8ca8a68429242 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -9808,7 +9808,7 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event)
 
 	if (vma->vm_flags & VM_LOCKED)
 		flags |= MAP_LOCKED;
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		flags |= MAP_HUGETLB;
 
 	if (file) {
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index 7709ea8824778..b89cc5cee0027 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -1726,8 +1726,8 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area)
 	}
 
 	vma = _install_special_mapping(mm, area->vaddr, PAGE_SIZE,
-				VM_EXEC|VM_MAYEXEC|VM_DONTCOPY|VM_IO|
-				VM_SEALED_SYSMAP,
+				VM_EXEC|VM_MAYEXEC|VM_DONTCOPY|
+				VM_MIXEDMAP|VM_SEALED_SYSMAP,
 				&xol_mapping);
 	if (IS_ERR(vma)) {
 		ret = PTR_ERR(vma);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8dff37059faf7..ae6c1a606eb5d 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -22,7 +22,6 @@
  */
 #include <linux/energy_model.h>
 #include <linux/mmap_lock.h>
-#include <linux/hugetlb_inline.h>
 #include <linux/jiffies.h>
 #include <linux/mm_api.h>
 #include <linux/highmem.h>
@@ -4212,7 +4211,7 @@ static void task_numa_work(struct callback_head *work)
 
 	for (; vma; vma = vma_next(&vmi)) {
 		if (!vma_migratable(vma) || !vma_policy_mof(vma) ||
-			is_vm_hugetlb_page(vma) || (vma->vm_flags & VM_MIXEDMAP)) {
+			vma_is_hugetlb(vma) || vma_is_kernel_owned(vma)) {
 			trace_sched_skip_vma_numa(mm, vma, NUMAB_SKIP_UNSUITABLE);
 			continue;
 		}
diff --git a/mm/folio.c b/mm/folio.c
index 50a6dbe55998e..a3f5c463f6654 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -502,7 +502,7 @@ void folio_add_lru_vma(struct folio *folio, struct vm_area_struct *vma)
 {
 	VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
 
-	if (unlikely((vma->vm_flags & (VM_LOCKED | VM_SPECIAL)) == VM_LOCKED))
+	if (vma_test(vma, VMA_LOCKED_BIT))
 		mlock_new_folio(folio);
 	else
 		folio_add_lru(folio);
diff --git a/mm/gup.c b/mm/gup.c
index a4036c02e2137..f4d0cfcb602bf 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -621,7 +621,7 @@ static struct page *no_page_table(struct vm_area_struct *vma,
 	 * But we can only make this optimization where a hole would surely
 	 * be zero-filled if handle_mm_fault() actually did handle it.
 	 */
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		struct hstate *h = hstate_vma(vma);
 
 		if (!hugetlbfs_pagecache_present(h, vma, address))
@@ -1204,7 +1204,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)
 	int foreign = (gup_flags & FOLL_REMOTE);
 	bool vma_anon = vma_is_anonymous(vma);
 
-	if (vm_flags & (VM_IO | VM_PFNMAP))
+	if (!vma_can_gup(vma))
 		return -EFAULT;
 
 	if ((gup_flags & FOLL_ANON) && !vma_anon)
@@ -1213,7 +1213,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)
 	if ((gup_flags & FOLL_LONGTERM) && vma_is_fsdax(vma))
 		return -EOPNOTSUPP;
 
-	if ((gup_flags & FOLL_SPLIT_PMD) && is_vm_hugetlb_page(vma))
+	if ((gup_flags & FOLL_SPLIT_PMD) && vma_is_hugetlb(vma))
 		return -EOPNOTSUPP;
 
 	if (vma_is_secretmem(vma))
@@ -1836,6 +1836,10 @@ long populate_vma_page_range(struct vm_area_struct *vma,
 	if (!vma_is_accessible(vma))
 		return -EFAULT;
 
+	/* Unreadable VMAs also cannot be faulted in. */
+	if (!vma_test(vma, VMA_MAYREAD_BIT))
+		return -EFAULT;
+
 	gup_flags = FOLL_TOUCH;
 	/*
 	 * We want to touch writable mappings with a write fault in order
@@ -1951,7 +1955,7 @@ int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)
 		 * range with the first VMA. Also, skip undesirable VMA types.
 		 */
 		nend = min(end, vma->vm_end);
-		if (vma->vm_flags & (VM_IO | VM_PFNMAP))
+		if (!vma_can_gup(vma))
 			continue;
 		if (nstart < vma->vm_start)
 			nstart = vma->vm_start;
@@ -2013,8 +2017,7 @@ static long __get_user_pages_locked(struct mm_struct *mm, unsigned long start,
 			break;
 
 		/* protect what we can, including chardevs */
-		if ((vma->vm_flags & (VM_IO | VM_PFNMAP)) ||
-		    !(vm_flags & vma->vm_flags))
+		if (!vma_can_gup(vma) || !(vm_flags & vma->vm_flags))
 			break;
 
 		if (pages) {
diff --git a/mm/hmm.c b/mm/hmm.c
index 2f1e98c6b6440..e9569b82a1f0c 100644
--- a/mm/hmm.c
+++ b/mm/hmm.c
@@ -595,8 +595,7 @@ static int hmm_vma_walk_test(unsigned long start, unsigned long end,
 	struct hmm_range *range = hmm_vma_walk->range;
 	struct vm_area_struct *vma = walk->vma;
 
-	if (!(vma->vm_flags & (VM_IO | VM_PFNMAP)) &&
-	    vma->vm_flags & VM_READ)
+	if (vma_can_gup(vma) && vma_test(vma, VMA_READ_BIT))
 		return 0;
 
 	/*
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index dd66c6ad5af13..1ec1cd970ce68 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -110,14 +110,6 @@ static inline bool file_thp_enabled(const struct vm_area_struct *vma)
 	return S_ISREG(inode->i_mode);
 }
 
-/* If returns true, we are unable to access the VMA's folios. */
-static bool vma_is_special_huge(const struct vm_area_struct *vma)
-{
-	if (vma_is_dax(vma))
-		return false;
-	return vma_test_any(vma, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT);
-}
-
 static bool vma_file_bypass_thp_tuneables(const struct vm_area_struct *vma,
 		enum tva_type type)
 {
@@ -192,7 +184,7 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
 	/* Check the intersection of requested and supported orders. */
 	if (vma_is_anonymous(vma))
 		supported_orders = THP_ORDERS_ALL_ANON;
-	else if (vma_is_dax(vma) || vma_is_special_huge(vma))
+	else if (vma_is_dax(vma) || vma_is_kernel_owned(vma))
 		supported_orders = THP_ORDERS_ALL_SPECIAL_DAX;
 	else
 		supported_orders = THP_ORDERS_ALL_FILE_DEFAULT;
@@ -212,11 +204,14 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
 		return in_pf ? orders : 0;
 
 	/*
-	 * khugepaged special VMA and hugetlb VMA.
-	 * Must be checked after dax since some dax mappings may have
-	 * VM_MIXEDMAP set.
+	 * khugepaged moves data from VMAs once collapsed, after they have been
+	 * faulted in, relying on refaulting for file-backed memory.
+	 *
+	 * Kernel-owned mappings cannot be reliably reconstructed from page
+	 * faults, and fixed mappings (including hugetlb) may not be marked as
+	 * kernel-owned - precisely the mappings which cannot be merged.
 	 */
-	if (!in_pf && !smaps && (vm_flags & VM_NO_KHUGEPAGED))
+	if (!in_pf && !smaps && !vma_can_merge(vma))
 		return 0;
 
 	/*
@@ -3062,7 +3057,7 @@ int zap_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,
 	orig_pud = pudp_huge_get_and_clear_full(vma, addr, pud, tlb->fullmm);
 	arch_check_zapped_pud(vma, orig_pud);
 	tlb_remove_pud_tlb_entry(tlb, pud, addr);
-	if (vma_is_special_huge(vma)) {
+	if (vma_is_kernel_owned(vma)) {
 		spin_unlock(ptl);
 		/* No zero page support yet */
 	} else {
@@ -3218,7 +3213,7 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
 		 */
 		if (arch_needs_pgtable_deposit())
 			zap_deposited_table(mm, pmd);
-		if (vma_is_special_huge(vma))
+		if (vma_is_kernel_owned(vma))
 			return;
 		if (unlikely(pmd_is_migration_entry(old_pmd))) {
 			const softleaf_t old_entry = softleaf_from_pmd(old_pmd);
@@ -4747,11 +4742,9 @@ static inline bool vma_not_suitable_for_thp_split(struct vm_area_struct *vma)
 {
 	if (vma_is_dax(vma))
 		return true;
-	if (vma_is_special_huge(vma))
-		return true;
-	if (vma_test(vma, VMA_IO_BIT))
+	if (vma_is_kernel_owned(vma))
 		return true;
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return true;
 
 	return false;
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index a69bd463b1aef..d93235491cbca 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1146,7 +1146,7 @@ static inline struct resv_map *inode_resv_map(struct inode *inode)
 
 static struct resv_map *vma_resv_map(struct vm_area_struct *vma)
 {
-	VM_BUG_ON_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 	if (vma->vm_flags & VM_MAYSHARE) {
 		struct address_space *mapping = vma->vm_file->f_mapping;
 		struct inode *inode = mapping->host;
@@ -1161,7 +1161,7 @@ static struct resv_map *vma_resv_map(struct vm_area_struct *vma)
 
 static void set_vma_resv_map(struct vm_area_struct *vma, struct resv_map *map)
 {
-	VM_WARN_ON_ONCE_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 	VM_WARN_ON_ONCE_VMA(vma_test(vma, VMA_MAYSHARE_BIT), vma);
 
 	set_vma_private_data(vma, (unsigned long)map);
@@ -1169,7 +1169,7 @@ static void set_vma_resv_map(struct vm_area_struct *vma, struct resv_map *map)
 
 static void set_vma_resv_flags(struct vm_area_struct *vma, unsigned long flags)
 {
-	VM_WARN_ON_ONCE_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 	VM_WARN_ON_ONCE_VMA(vma_test(vma, VMA_MAYSHARE_BIT), vma);
 
 	set_vma_private_data(vma, get_vma_private_data(vma) | flags);
@@ -1177,7 +1177,7 @@ static void set_vma_resv_flags(struct vm_area_struct *vma, unsigned long flags)
 
 static int is_vma_resv_set(struct vm_area_struct *vma, unsigned long flag)
 {
-	VM_BUG_ON_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 
 	return (get_vma_private_data(vma) & flag) != 0;
 }
@@ -1191,7 +1191,7 @@ bool __vma_private_lock(struct vm_area_struct *vma)
 
 void hugetlb_dup_vma_private(struct vm_area_struct *vma)
 {
-	VM_BUG_ON_VMA(!is_vm_hugetlb_page(vma), vma);
+	VM_WARN_ON_ONCE_VMA(!vma_is_hugetlb(vma), vma);
 	/*
 	 * Clear vm_private_data
 	 * - For shared mappings this is a per-vma semaphore that may be
@@ -5269,7 +5269,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
 	unsigned long last_addr_mask;
 
 	i_mmap_assert_write_locked(vma->vm_file->f_mapping);
-	WARN_ON(!is_vm_hugetlb_page(vma));
+	WARN_ON(!vma_is_hugetlb(vma));
 	BUG_ON(start & ~huge_page_mask(h));
 	BUG_ON(end & ~huge_page_mask(h));
 
@@ -7495,6 +7495,6 @@ void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)
  */
 void fixup_hugetlb_reservations(struct vm_area_struct *vma)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		clear_vma_resv_huge_pages(vma);
 }
diff --git a/mm/internal.h b/mm/internal.h
index da14c56fb24e1..6c004037913b3 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -212,6 +212,24 @@ static inline void *folio_raw_mapping(const struct folio *folio)
 	return (void *)(mapping & ~FOLIO_MAPPING_FLAGS);
 }
 
+/*
+ * If the VMA has a close hook then close it, and since closing it might leave
+ * it in an inconsistent state which makes the use of any hooks suspect, clear
+ * them down by installing dummy empty hooks.
+ */
+static inline void vma_close(struct vm_area_struct *vma)
+{
+	if (vma->vm_ops && vma->vm_ops->close) {
+		vma->vm_ops->close(vma);
+
+		/*
+		 * The mapping is in an inconsistent state, and no further hooks
+		 * may be invoked upon it.
+		 */
+		vma->vm_ops = &vma_dummy_vm_ops;
+	}
+}
+
 /*
  * This is a file-backed mapping, and is about to be memory mapped - invoke its
  * mmap hook and safely handle error conditions. On error, VMA hooks will be
@@ -224,8 +242,11 @@ static inline void *folio_raw_mapping(const struct folio *folio)
  */
 static inline int mmap_file(struct file *file, struct vm_area_struct *vma)
 {
-	int err = vfs_mmap(file, vma);
+	const unsigned long prev_start = vma->vm_start;
+	const vma_flags_t prev_flags = vma->flags;
+	int err;
 
+	err = vfs_mmap(file, vma);
 	/*
 	 * Either we tried to call the file hook for mmap() and an error arose
 	 * or a driver set vma->vm_ops = NULL intending there to be no VMA
@@ -238,26 +259,14 @@ static inline int mmap_file(struct file *file, struct vm_area_struct *vma)
 	 */
 	if (unlikely(err || !vma->vm_ops))
 		vma->vm_ops = &vma_dummy_vm_ops;
+	if (unlikely(err))
+		return err;
 
-	return err;
-}
-
-/*
- * If the VMA has a close hook then close it, and since closing it might leave
- * it in an inconsistent state which makes the use of any hooks suspect, clear
- * them down by installing dummy empty hooks.
- */
-static inline void vma_close(struct vm_area_struct *vma)
-{
-	if (vma->vm_ops && vma->vm_ops->close) {
-		vma->vm_ops->close(vma);
+	err = mmap_hook_validate(prev_start, &prev_flags, vma);
+	if (unlikely(err))
+		vma_close(vma);
 
-		/*
-		 * The mapping is in an inconsistent state, and no further hooks
-		 * may be invoked upon it.
-		 */
-		vma->vm_ops = &vma_dummy_vm_ops;
-	}
+	return err;
 }
 
 /* unmap_vmas is in mm/memory.c */
@@ -966,15 +975,7 @@ void mlock_folio(struct folio *folio);
 static inline void mlock_vma_folio(struct folio *folio,
 				struct vm_area_struct *vma)
 {
-	/*
-	 * The VM_SPECIAL check here serves two purposes.
-	 * 1) VM_IO check prevents migration from double-counting during mlock.
-	 * 2) Although mmap_region() and mlock_fixup() take care that VM_LOCKED
-	 *    is never left set on a VM_SPECIAL vma, there is an interval while
-	 *    file->f_op->mmap() is using vm_insert_page(s), when VM_LOCKED may
-	 *    still be set while VM_SPECIAL bits are added: so ignore it then.
-	 */
-	if (unlikely((vma->vm_flags & (VM_LOCKED|VM_SPECIAL)) == VM_LOCKED))
+	if (vma_test(vma, VMA_LOCKED_BIT))
 		mlock_folio(folio);
 }
 
@@ -991,7 +992,7 @@ static inline void munlock_vma_folio(struct folio *folio,
 	 * always munlock the folio and page reclaim will correct it
 	 * if it's wrong.
 	 */
-	if (unlikely(vma->vm_flags & VM_LOCKED))
+	if (unlikely(vma_test(vma, VMA_LOCKED_BIT)))
 		munlock_folio(folio);
 }
 
@@ -1111,11 +1112,9 @@ static inline struct file *maybe_unlock_mmap_for_io(struct vm_fault *vmf,
 
 static inline bool vma_supports_mlock(const struct vm_area_struct *vma)
 {
-	if (vma_test_any_mask(vma, VMA_SPECIAL_FLAGS))
-		return false;
-	if (vma_test_single_mask(vma, VMA_DROPPABLE))
+	if (!vma_is_persistent(vma))
 		return false;
-	if (vma_is_dax(vma) || is_vm_hugetlb_page(vma))
+	if (vma_is_dax(vma) || vma_is_hugetlb(vma))
 		return false;
 	return vma != get_gate_vma(current->mm);
 }
@@ -1508,6 +1507,12 @@ int remap_pfn_range_prepare(struct vm_area_desc *desc);
 int remap_pfn_range_complete(struct vm_area_struct *vma,
 			     struct mmap_action *action);
 int simple_ioremap_prepare(struct vm_area_desc *desc);
+int map_kernel_pages_prepare(struct vm_area_desc *desc);
+int map_kernel_pages_complete(struct vm_area_struct *vma,
+			      struct mmap_action *action);
+int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc);
+int map_discontig_kernel_pages_complete(struct vm_area_struct *vma,
+					struct mmap_action *action);
 
 static inline int io_remap_pfn_range_prepare(struct vm_area_desc *desc)
 {
diff --git a/mm/ksm.c b/mm/ksm.c
index 624f37975e129..f80372bfd4b2f 100644
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -747,9 +747,7 @@ static bool ksm_compatible(const struct file *file, vma_flags_t vma_flags)
 	if (vma_flags_test_any(&vma_flags, VMA_SHARED_BIT, VMA_MAYSHARE_BIT,
 			       VMA_HUGETLB_BIT))
 		return false;
-	if (vma_flags_test_single_mask(&vma_flags, VMA_DROPPABLE))
-		return false;
-	if (vma_flags_test_any_mask(&vma_flags, VMA_SPECIAL_FLAGS))
+	if (!vma_flags_is_persistent(&vma_flags))
 		return false;
 	if (file_is_dax(file))
 		return false;
diff --git a/mm/madvise.c b/mm/madvise.c
index 73c2901b9adbf..f805a4876c875 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -880,7 +880,7 @@ bool madvise_dontneed_free_valid_vma(struct madvise_behavior *madv_behavior)
 	int behavior = madv_behavior->behavior;
 	struct madvise_behavior_range *range = &madv_behavior->range;
 
-	if (!is_vm_hugetlb_page(vma)) {
+	if (!vma_is_hugetlb(vma)) {
 		unsigned int forbidden = VM_PFNMAP;
 
 		if (behavior != MADV_DONTNEED_LOCKED)
@@ -1055,19 +1055,25 @@ static long madvise_remove(struct madvise_behavior *madv_behavior)
 	return error;
 }
 
-static bool is_valid_guard_vma(struct vm_area_struct *vma, bool allow_locked)
+static bool is_valid_guard_vma(const struct vm_area_struct *vma,
+			       bool allow_locked)
 {
-	vm_flags_t disallowed = VM_SPECIAL | VM_HUGETLB;
-
 	/*
-	 * A user could lock after setting a guard range but that's fine, as
+	 * A user could lock after setting a guard range but that's fine as
 	 * they'd not be able to fault in. The issue arises when we try to zap
 	 * existing locked VMAs. We don't want to do that.
 	 */
-	if (!allow_locked)
-		disallowed |= VM_LOCKED;
+	if (!allow_locked && vma_test(vma, VMA_LOCKED_BIT))
+		return false;
+	/*
+	 * Guard regions require a VMA whose page tables are managed solely by
+	 * the core, which is also what merging requires, so disallow any flags
+	 * that would prevent a merge.
+	 */
+	if (!vma_can_merge(vma))
+		return false;
 
-	return !(vma->vm_flags & disallowed);
+	return true;
 }
 
 static bool is_guard_pte_marker(pte_t ptent)
@@ -1394,7 +1400,7 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)
 		new_flags |= VM_DONTCOPY;
 		break;
 	case MADV_DOFORK:
-		if (new_flags & VM_SPECIAL)
+		if (!vma_can_merge(vma))
 			return -EINVAL;
 		new_flags &= ~VM_DONTCOPY;
 		break;
@@ -1413,8 +1419,8 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)
 		new_flags |= VM_DONTDUMP;
 		break;
 	case MADV_DODUMP:
-		if ((!is_vm_hugetlb_page(vma) && (new_flags & VM_SPECIAL)) ||
-		    (new_flags & VM_DROPPABLE))
+		/* Non-persistent memory cannot be dumped. */
+		if (!vma_is_persistent(vma))
 			return -EINVAL;
 		new_flags &= ~VM_DONTDUMP;
 		break;
diff --git a/mm/memory.c b/mm/memory.c
index ec63dd6212ac5..cb56d67b17ca3 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -1564,7 +1564,7 @@ copy_page_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma)
 	if (!vma_needs_copy(dst_vma, src_vma))
 		return 0;
 
-	if (is_vm_hugetlb_page(src_vma))
+	if (vma_is_hugetlb(src_vma))
 		return copy_hugetlb_page_range(dst_mm, src_mm, dst_vma, src_vma);
 
 	/*
@@ -2178,7 +2178,7 @@ static void __zap_vma_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
 	if (vma->vm_file && !reaping)
 		uprobe_munmap(vma, start, end);
 
-	if (unlikely(is_vm_hugetlb_page(vma))) {
+	if (unlikely(vma_is_hugetlb(vma))) {
 		zap_flags_t zap_flags = details ? details->zap_flags : 0;
 
 		VM_WARN_ON_ONCE(reaping);
@@ -2313,7 +2313,7 @@ void zap_vma_range_batched(struct mmu_gather *tlb,
 	 */
 	__zap_vma_range(tlb, vma, address, end, details);
 	mmu_notifier_invalidate_range_end(&range);
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		/*
 		 * flush tlb and free resources before hugetlb_zap_end(), to
 		 * avoid concurrent page faults' allocation failure.
@@ -2343,19 +2343,19 @@ void zap_vma_range(struct vm_area_struct *vma, unsigned long address,
 }
 
 /**
- * zap_special_vma_range - zap all page table entries in a special vma range
+ * zap_special_vma_range - zap all page table entries in a kernel-owned VMA
  * @vma: the vma covering the range to zap
  * @address: starting address of the range to zap
  * @size: number of bytes to zap
  *
  * This function does nothing when the provided address range is not fully
- * contained in @vma, or when the @vma is not VM_PFNMAP or VM_MIXEDMAP.
+ * contained in @vma, or when @vma is not kernel-owned.
  */
 void zap_special_vma_range(struct vm_area_struct *vma, unsigned long address,
 		unsigned long size)
 {
 	if (!range_in_vma(vma, address, address + size) ||
-	   !(vma->vm_flags & (VM_PFNMAP | VM_MIXEDMAP)))
+	   !vma_is_kernel_owned(vma))
 		return;
 
 	zap_vma_range(vma, address, size);
@@ -2417,11 +2417,11 @@ static bool vm_mixed_zeropage_allowed(struct vm_area_struct *vma)
 	 * be problematic as soon as the zeropage gets replaced by a different
 	 * page due to vma->vm_ops->pfn_mkwrite, because what's mapped would
 	 * now differ to what GUP looked up. FSDAX is incompatible to
-	 * FOLL_LONGTERM and VM_IO is incompatible to GUP completely (see
-	 * check_vma_flags).
+	 * FOLL_LONGTERM and memory-mapped I/O is incompatible to GUP completely
+	 * (see vma_can_gup()).
 	 */
 	return vma->vm_ops && vma->vm_ops->pfn_mkwrite &&
-	       (vma_is_fsdax(vma) || vma->vm_flags & VM_IO);
+	       (vma_is_fsdax(vma) || vma_test(vma, VMA_IO_BIT));
 }
 
 static int validate_page_before_insert(struct vm_area_struct *vma,
@@ -2609,17 +2609,23 @@ int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,
 }
 EXPORT_SYMBOL(vm_insert_pages);
 
+static void __map_kernel_pages_prepare(struct vm_area_desc *desc)
+{
+	if (vma_desc_test(desc, VMA_MIXEDMAP_BIT))
+		return;
+
+	VM_WARN_ON_ONCE(mmap_read_trylock(desc->mm));
+	VM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));
+	vma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);
+}
+
 int map_kernel_pages_prepare(struct vm_area_desc *desc)
 {
 	const struct mmap_action *action = &desc->action;
 	const unsigned long addr = action->map_kernel.start;
 	unsigned long nr_pages, end;
 
-	if (!vma_desc_test(desc, VMA_MIXEDMAP_BIT)) {
-		VM_WARN_ON_ONCE(mmap_read_trylock(desc->mm));
-		VM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));
-		vma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);
-	}
+	__map_kernel_pages_prepare(desc);
 
 	nr_pages = action->map_kernel.nr_pages;
 	end = addr + PAGE_SIZE * nr_pages;
@@ -2628,7 +2634,6 @@ int map_kernel_pages_prepare(struct vm_area_desc *desc)
 
 	return 0;
 }
-EXPORT_SYMBOL(map_kernel_pages_prepare);
 
 int map_kernel_pages_complete(struct vm_area_struct *vma,
 			      struct mmap_action *action)
@@ -2640,7 +2645,98 @@ int map_kernel_pages_complete(struct vm_area_struct *vma,
 			    action->map_kernel.pages,
 			    &nr_pages, vma->vm_page_prot);
 }
-EXPORT_SYMBOL(map_kernel_pages_complete);
+
+int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc)
+{
+	const struct mmap_action *action = &desc->action;
+	const struct discontig_kernel_page_ops *ops =
+		action->map_kernel_discontig.ops;
+
+	/* At minimum need to be able to get pages. */
+	if (WARN_ON_ONCE(!ops->get))
+		return -EINVAL;
+
+	__map_kernel_pages_prepare(desc);
+	return 0;
+}
+
+static int apply_discontig_action(struct vm_area_struct *vma,
+				  struct discontig_kernel_page_state *state)
+{
+	unsigned long nr_pages = state->__nr_pages;
+	unsigned long addr = state->addr;
+	unsigned long i;
+
+	if (state->action == DISCONTIG_KERNEL_PAGE_MAP_PAGE)
+		return insert_page(vma, addr, state->__page,
+				   vma->vm_page_prot, /*mkwrite=*/false);
+	if (state->action == DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE)
+		return insert_pages(vma, addr, state->__page_arr,
+				    &nr_pages, vma->vm_page_prot);
+
+	/* Compound folio - have to iterate through each page. */
+	for (i = 0; i < nr_pages; i++, addr += PAGE_SIZE) {
+		struct page *page = folio_page(state->__folio, i);
+		int err;
+
+		err = insert_page(vma, addr, page, vma->vm_page_prot,
+				  /*mkwrite=*/false);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+int map_discontig_kernel_pages_complete(struct vm_area_struct *vma,
+					struct mmap_action *action)
+{
+	const struct discontig_kernel_page_ops *ops =
+		action->map_kernel_discontig.ops;
+	struct discontig_kernel_page_state state = {
+		.start = vma->vm_start,
+		.end = vma->vm_end,
+		.addr = vma->vm_start,
+		.pgoff = vma->vm_pgoff,
+		.nr_pages_mapped = 0,
+		.nr_pages_remain = vma_pages(vma),
+		.vm_private_data = vma->vm_private_data,
+		.private = action->map_kernel_discontig.init_private,
+	};
+	int err = 0;
+
+	if (ops->init)
+		err = ops->init(vma->vm_private_data, &state.private);
+	if (err)
+		return err;
+
+	do {
+		unsigned long end, pgoff_end;
+		unsigned long nr_pages;
+
+		/* Default to abort. */
+		state.action = DISCONTIG_KERNEL_PAGE_ABORT;
+		err = ops->get(&state);
+		if (err || state.action == DISCONTIG_KERNEL_PAGE_ABORT)
+			return err;
+		nr_pages = state.__nr_pages;
+
+		end = state.addr + PAGE_SIZE * nr_pages;
+		if (end > vma->vm_end)
+			return -EINVAL;
+		pgoff_end = state.pgoff + nr_pages;
+
+		err = apply_discontig_action(vma, &state);
+		if (err)
+			return err;
+
+		state.addr = end;
+		state.pgoff = pgoff_end;
+		state.nr_pages_mapped += nr_pages;
+		state.nr_pages_remain -= nr_pages;
+	} while (state.addr < vma->vm_end);
+
+	return 0;
+}
 
 /**
  * vm_insert_page - insert single page into user vma
@@ -6837,7 +6933,7 @@ vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
 
 	lru_gen_enter_fault(vma);
 
-	if (unlikely(is_vm_hugetlb_page(vma)))
+	if (unlikely(vma_is_hugetlb(vma)))
 		ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
 	else
 		ret = __handle_mm_fault(vma, address, flags);
@@ -7020,7 +7116,8 @@ int follow_pfnmap_start(struct follow_pfnmap_args *args)
 	if (unlikely(address < vma->vm_start || address >= vma->vm_end))
 		goto out;
 
-	if (!(vma->vm_flags & (VM_IO | VM_PFNMAP)))
+	/* Only mappings GUP cannot handle are followed here. */
+	if (vma_can_gup(vma))
 		goto out;
 retry:
 	pgdp = pgd_offset(mm, address);
@@ -7214,8 +7311,9 @@ static int __access_remote_vm(struct mm_struct *mm, unsigned long addr,
 			}
 
 			/*
-			 * Check if this is a VM_IO | VM_PFNMAP VMA, which
-			 * we can access using slightly different code.
+			 * GUP failed, perhaps because this is a mapping it
+			 * cannot handle (see vma_can_gup()) - such mappings may
+			 * provide access via vm_ops->access() instead.
 			 */
 			bytes = 0;
 #ifdef CONFIG_HAVE_IOREMAP_PROT
@@ -7701,12 +7799,12 @@ void ptlock_free(struct ptdesc *ptdesc)
 
 void vma_pgtable_walk_begin(struct vm_area_struct *vma)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		hugetlb_vma_lock_read(vma);
 }
 
 void vma_pgtable_walk_end(struct vm_area_struct *vma)
 {
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		hugetlb_vma_unlock_read(vma);
 }
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 2ad0a5f18280a..ed444061631c9 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -2011,7 +2011,8 @@ SYSCALL_DEFINE5(get_mempolicy, int __user *, policy,
 
 bool vma_migratable(struct vm_area_struct *vma)
 {
-	if (vma->vm_flags & (VM_IO | VM_PFNMAP))
+	/* Pages which GUP cannot obtain cannot be migrated either. */
+	if (!vma_can_gup(vma))
 		return false;
 
 	/*
@@ -2021,7 +2022,7 @@ bool vma_migratable(struct vm_area_struct *vma)
 	if (vma_is_dax(vma))
 		return false;
 
-	if (is_vm_hugetlb_page(vma) &&
+	if (vma_is_hugetlb(vma) &&
 		!hugepage_migration_supported(hstate_vma(vma)))
 		return false;
 
diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 0c437004329d9..b74c0ae427682 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -739,19 +739,21 @@ static void migrate_vma_unmap(struct migrate_vma *migrate)
  */
 int migrate_vma_setup(struct migrate_vma *args)
 {
+	const struct vm_area_struct *vma = args->vma;
 	long nr_pages = (args->end - args->start) >> PAGE_SHIFT;
 
 	args->start &= PAGE_MASK;
 	args->end &= PAGE_MASK;
-	if (!args->vma || is_vm_hugetlb_page(args->vma) ||
-	    (args->vma->vm_flags & VM_SPECIAL) || vma_is_dax(args->vma))
+	if (!vma)
+		return -EINVAL;
+	if (vma_is_kernel_owned(vma) || vma_is_fixed_mapping(vma) ||
+	    vma_is_dax(vma))
 		return -EINVAL;
 	if (nr_pages <= 0)
 		return -EINVAL;
-	if (args->start < args->vma->vm_start ||
-	    args->start >= args->vma->vm_end)
+	if (args->start < vma->vm_start || args->start >= vma->vm_end)
 		return -EINVAL;
-	if (args->end <= args->vma->vm_start || args->end > args->vma->vm_end)
+	if (args->end <= vma->vm_start || args->end > vma->vm_end)
 		return -EINVAL;
 	if (!args->src || !args->dst)
 		return -EINVAL;
diff --git a/mm/mlock.c b/mm/mlock.c
index 39215a3eab1fb..4235a1518fc9e 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -316,22 +316,10 @@ static inline unsigned int folio_mlock_step(struct folio *folio,
 	return folio_pte_batch(folio, pte, ptent, count);
 }
 
-static inline bool allow_mlock_munlock(struct folio *folio,
+static inline bool allow_mlock(struct folio *folio,
 		struct vm_area_struct *vma, unsigned long start,
 		unsigned long end, unsigned int step)
 {
-	/*
-	 * For unlock, allow munlock large folio which is partially
-	 * mapped to VMA. As it's possible that large folio is
-	 * mlocked and VMA is split later.
-	 *
-	 * During memory pressure, such kind of large folio can
-	 * be split. And the pages are not in VM_LOCKed VMA
-	 * can be reclaimed.
-	 */
-	if (!vma_test(vma, VMA_LOCKED_BIT))
-		return true;
-
 	/* folio_within_range() cannot take KSM, but any small folio is OK */
 	if (!folio_test_large(folio))
 		return true;
@@ -352,6 +340,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
 
 {
 	struct vm_area_struct *vma = walk->vma;
+	const bool lock = walk->private;
 	spinlock_t *ptl;
 	pte_t *start_pte, *pte;
 	pte_t ptent;
@@ -368,7 +357,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
 		folio = pmd_folio(*pmd);
 		if (folio_is_zone_device(folio))
 			goto out;
-		if (vma_test(vma, VMA_LOCKED_BIT))
+		if (lock)
 			mlock_folio(folio);
 		else
 			munlock_folio(folio);
@@ -390,10 +379,10 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
 			continue;
 
 		step = folio_mlock_step(folio, pte, addr, end);
-		if (!allow_mlock_munlock(folio, vma, start, end, step))
+		if (lock && !allow_mlock(folio, vma, start, end, step))
 			goto next_entry;
 
-		if (vma_test(vma, VMA_LOCKED_BIT))
+		if (lock)
 			mlock_folio(folio);
 		else
 			munlock_folio(folio);
@@ -428,31 +417,29 @@ static void mlock_vma_pages_range(struct vm_area_struct *vma,
 		.pmd_entry = mlock_pte_range,
 		.walk_lock = PGWALK_WRLOCK_VERIFY,
 	};
+	const bool lock = vma_flags_test(new_vma_flags, VMA_LOCKED_BIT);
+	vma_flags_t walk_flags = *new_vma_flags;
 
 	/*
-	 * There is a slight chance that concurrent page migration,
-	 * or page reclaim finding a page of this now-VMA_LOCKED_BIT vma,
-	 * will call mlock_vma_folio() and raise page's mlock_count:
-	 * double counting, leaving the page unevictable indefinitely.
-	 * Communicate this danger to mlock_vma_folio() with VMA_IO_BIT,
-	 * which is a VMA_SPECIAL_FLAGS flag not allowed on VMA_LOCKED_BIT vmas.
-	 * mmap_lock is held in write mode here, so this weird
-	 * combination should not be visible to other mmap_lock users;
-	 * but WRITE_ONCE so rmap walkers must see VMA_IO_BIT if VMA_LOCKED_BIT.
+	 * LOCKONFAULT without LOCKED never otherwise occurs: it marks a walk in
+	 * progress so that rmap-side callers, which test VMA_LOCKED_BIT, do not
+	 * count folios, while try_to_unmap_one(), which tests VMA_LOCKED_MASK,
+	 * still refuses to unmap them.
 	 */
-	if (vma_flags_test(new_vma_flags, VMA_LOCKED_BIT))
-		vma_flags_set(new_vma_flags, VMA_IO_BIT);
+	if (lock) {
+		vma_flags_clear(&walk_flags, VMA_LOCKED_BIT);
+		vma_flags_set(&walk_flags, VMA_LOCKONFAULT_BIT);
+	}
+
 	vma_start_write(vma);
-	vma_flags_reset_once(vma, new_vma_flags);
+	vma_flags_reset_once(vma, &walk_flags);
 
 	lru_add_drain();
-	walk_page_range_vma(vma, start, end, &mlock_walk_ops, NULL);
+	walk_page_range_vma(vma, start, end, &mlock_walk_ops, (void *)lock);
 	lru_add_drain();
 
-	if (vma_flags_test(new_vma_flags, VMA_IO_BIT)) {
-		vma_flags_clear(new_vma_flags, VMA_IO_BIT);
+	if (lock)
 		vma_flags_reset_once(vma, new_vma_flags);
-	}
 }
 
 /*
diff --git a/mm/mmap.c b/mm/mmap.c
index 4bf26b0f1e6e3..98449f364af1c 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1786,7 +1786,7 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
 		/*
 		 * Copy/update hugetlb private vma information.
 		 */
-		if (is_vm_hugetlb_page(tmp))
+		if (vma_is_hugetlb(tmp))
 			hugetlb_dup_vma_private(tmp);
 
 		/*
diff --git a/mm/mmu_gather.c b/mm/mmu_gather.c
index 3985d856de7f9..506f005adbdc0 100644
--- a/mm/mmu_gather.c
+++ b/mm/mmu_gather.c
@@ -500,7 +500,7 @@ void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma)
 {
 	tlb_gather_mmu(tlb, vma->vm_mm);
 	tlb_update_vma_flags(tlb, vma);
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		/* All entries have the same size. */
 		tlb_change_page_size(tlb, huge_page_size(hstate_vma(vma)));
 }
diff --git a/mm/mprotect.c b/mm/mprotect.c
index 2888ee638d872..a1b6d29bf0390 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -717,7 +717,7 @@ long change_protection(struct mmu_gather *tlb,
 	    (cp_flags & MM_CP_UFFD_RWP))
 		newprot = PAGE_NONE;
 
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		pages = hugetlb_change_protection(vma, start, end, newprot,
 						  cp_flags);
 	else
@@ -783,8 +783,7 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
 	 * uncommon case, so doesn't need to be very optimized.
 	 */
 	if (arch_has_pfn_modify_check() &&
-	    vma_flags_test_any(&old_vma_flags, VMA_PFNMAP_BIT,
-			       VMA_MIXEDMAP_BIT) &&
+	    vma_flags_is_kernel_owned(&old_vma_flags) &&
 	    !vma_flags_test_any_mask(&new_vma_flags, VMA_ACCESS_FLAGS)) {
 		pgprot_t new_pgprot = vm_get_page_prot(newflags);
 
diff --git a/mm/mremap.c b/mm/mremap.c
index 7c368440fafe2..1122282a1d6ab 100644
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -812,7 +812,7 @@ unsigned long move_page_tables(struct pagetable_move_control *pmc)
 	if (!pmc->len_in)
 		return 0;
 
-	if (is_vm_hugetlb_page(pmc->old))
+	if (vma_is_hugetlb(pmc->old))
 		return move_hugetlb_page_tables(pmc->old, pmc->new, pmc->old_addr,
 						pmc->new_addr, pmc->len_in);
 
@@ -1735,7 +1735,7 @@ static bool vma_multi_allowed(struct vm_area_struct *vma)
 	/* Known good. */
 	if (vma_is_shmem(vma))
 		return true;
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return true;
 	if (file->f_op->get_unmapped_area == thp_get_unmapped_area)
 		return true;
@@ -1758,7 +1758,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
 		return -EPERM;
 
 	/* Align to hugetlb page size, if required. */
-	if (is_vm_hugetlb_page(vma) && !align_hugetlb(vrm))
+	if (vma_is_hugetlb(vma) && !align_hugetlb(vrm))
 		return -EINVAL;
 
 	vrm_set_delta(vrm);
@@ -1788,8 +1788,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
 		return -EINVAL;
 	}
 
-	if ((vrm->flags & MREMAP_DONTUNMAP) &&
-	    vma_test_any(vma, VMA_DONTEXPAND_BIT, VMA_PFNMAP_BIT))
+	if ((vrm->flags & MREMAP_DONTUNMAP) && vma_is_fixed_mapping(vma))
 		return -EINVAL;
 
 	/*
@@ -1827,7 +1826,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
 	if (pgoff + (new_len >> PAGE_SHIFT) < pgoff)
 		return -EINVAL;
 
-	if (vma_test_any(vma, VMA_DONTEXPAND_BIT, VMA_PFNMAP_BIT))
+	if (vma_is_fixed_mapping(vma))
 		return -EFAULT;
 
 	if (!mlock_future_ok(mm, vma_test(vma, VMA_LOCKED_BIT), vrm->delta))
diff --git a/mm/page_vma_mapped.c b/mm/page_vma_mapped.c
index 28e306fdb3a5b..8408aee7571b5 100644
--- a/mm/page_vma_mapped.c
+++ b/mm/page_vma_mapped.c
@@ -109,7 +109,7 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw, unsigned long pte_nr)
 	unsigned long pfn;
 	pte_t ptent;
 
-	if (is_vm_hugetlb_page(pvmw->vma))
+	if (vma_is_hugetlb(pvmw->vma))
 		ptent = huge_ptep_get(pvmw->vma->vm_mm, pvmw->address,
 				      pvmw->pte);
 	else
@@ -206,7 +206,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
 	if (pvmw->pmd && !pvmw->pte)
 		return not_found(pvmw);
 
-	if (unlikely(is_vm_hugetlb_page(vma))) {
+	if (unlikely(vma_is_hugetlb(vma))) {
 		struct hstate *hstate = hstate_vma(vma);
 		unsigned long size = huge_page_size(hstate);
 		/* The only possible mapping was handled on last iteration */
diff --git a/mm/pagewalk.c b/mm/pagewalk.c
index 7411702a37f58..e6493bbe6919e 100644
--- a/mm/pagewalk.c
+++ b/mm/pagewalk.c
@@ -408,7 +408,7 @@ static int __walk_page_range(unsigned long start, unsigned long end,
 	int err = 0;
 	struct vm_area_struct *vma = walk->vma;
 	const struct mm_walk_ops *ops = walk->ops;
-	bool is_hugetlb = is_vm_hugetlb_page(vma);
+	bool is_hugetlb = vma_is_hugetlb(vma);
 
 	/* We do not support hugetlb PTE installation. */
 	if (ops->install_pte && is_hugetlb)
diff --git a/mm/rmap.c b/mm/rmap.c
index 5fefe5b060b1c..120c894d2ddec 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -2239,9 +2239,11 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,
 
 		/*
 		 * If the folio is in an mlock()d vma, we must not swap it out.
+		 * VMA_LOCKONFAULT_BIT alone marks an mlock walk in progress, see
+		 * mlock_vma_pages_range().
 		 */
 		if (!(flags & TTU_IGNORE_MLOCK) &&
-		    (vma->vm_flags & VM_LOCKED)) {
+		    vma_test_any_mask(vma, VMA_LOCKED_MASK)) {
 			ptes++;
 
 			/*
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 01e7b6b046b67..f90f029bfd5cf 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -2705,7 +2705,7 @@ static int unuse_mm(struct mm_struct *mm, unsigned int type)
 	if (check_stable_address_space(mm))
 		goto unlock;
 	for_each_vma(vmi, vma) {
-		if (vma->anon_vma && !is_vm_hugetlb_page(vma)) {
+		if (vma->anon_vma && !vma_is_hugetlb(vma)) {
 			ret = unuse_vma(vma, type);
 			if (ret)
 				break;
diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index 79cc7b546f130..ddf0a4a3d3997 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -237,7 +237,7 @@ static int mfill_get_vma(struct mfill_state *state)
 	if ((flags & MFILL_ATOMIC_WP) && !(dst_vma->vm_flags & VM_UFFD_WP))
 		goto out_unlock;
 
-	if (is_vm_hugetlb_page(dst_vma))
+	if (vma_is_hugetlb(dst_vma))
 		return 0;
 
 	ops = vma_uffd_ops(dst_vma);
@@ -804,7 +804,7 @@ static __always_inline ssize_t mfill_atomic_hugetlb(
 		}
 
 		err = -ENOENT;
-		if (!is_vm_hugetlb_page(dst_vma))
+		if (!vma_is_hugetlb(dst_vma))
 			goto out_unlock_vma;
 
 		err = -EINVAL;
@@ -967,7 +967,7 @@ static __always_inline ssize_t mfill_atomic(struct userfaultfd_ctx *ctx,
 	/*
 	 * If this is a HUGETLB vma, pass off to appropriate routine
 	 */
-	if (is_vm_hugetlb_page(state.vma))
+	if (vma_is_hugetlb(state.vma))
 		return  mfill_atomic_hugetlb(ctx, state.vma, dst_start,
 					     src_start, len, flags);
 
@@ -1114,7 +1114,7 @@ static int mwriteprotect_range(struct userfaultfd_ctx *ctx, unsigned long start,
 			break;
 		}
 
-		if (is_vm_hugetlb_page(dst_vma)) {
+		if (vma_is_hugetlb(dst_vma)) {
 			err = -EINVAL;
 			page_mask = vma_kernel_pagesize(dst_vma) - 1;
 			if ((start & page_mask) || (len & page_mask))
@@ -1172,7 +1172,7 @@ int mrwprotect_range(struct userfaultfd_ctx *ctx, unsigned long start,
 		if (!userfaultfd_rwp(dst_vma))
 			return -ENOENT;
 
-		if (is_vm_hugetlb_page(dst_vma)) {
+		if (vma_is_hugetlb(dst_vma)) {
 			unsigned long page_mask;
 
 			page_mask = vma_kernel_pagesize(dst_vma) - 1;
@@ -1754,10 +1754,18 @@ static inline bool move_splits_huge_pmd(unsigned long dst_addr,
 }
 #endif
 
-static inline bool vma_move_compatible(struct vm_area_struct *vma)
+static inline bool vma_move_compatible(const struct vm_area_struct *vma)
 {
-	return !(vma->vm_flags & (VM_PFNMAP | VM_IO |  VM_HUGETLB |
-				  VM_MIXEDMAP | VM_SHADOW_STACK));
+	/* uffd is generally incompatible with kernel-owned mappings. */
+	if (vma_is_kernel_owned(vma))
+		return false;
+	/* The shadow stack should not be written to by userspace. */
+	if (vma_test_single_mask(vma, VMA_SHADOW_STACK))
+		return false;
+	/* hugetlb mappings cannot be safely moved. */
+	if (vma_is_hugetlb(vma))
+		return false;
+	return true;
 }
 
 static int validate_move_areas(struct userfaultfd_ctx *ctx,
@@ -2146,10 +2154,11 @@ static bool vma_can_userfault(struct vm_area_struct *vma, vm_flags_t vm_flags,
 {
 	const struct vm_uffd_ops *ops = vma_uffd_ops(vma);
 
-	if (vma->vm_flags & (VM_DROPPABLE | VM_SHADOW_STACK))
+	/* Non-persistent memory is inherently not controllable by userspace. */
+	if (!vma_is_persistent(vma))
 		return false;
-
-	if (!is_vm_hugetlb_page(vma) && (vma->vm_flags & VM_SPECIAL))
+	/* The shadow stack should not be written to by userspace. */
+	if (vma_test_single_mask(vma, VMA_SHADOW_STACK))
 		return false;
 
 	vm_flags &= __VM_UFFD_FLAGS;
@@ -2319,7 +2328,7 @@ static int userfaultfd_register_range(struct userfaultfd_ctx *ctx,
 		 */
 		userfaultfd_set_ctx(vma, ctx, vm_flags);
 
-		if (is_vm_hugetlb_page(vma) && uffd_disable_huge_pmd_share(vma))
+		if (vma_is_hugetlb(vma) && uffd_disable_huge_pmd_share(vma))
 			hugetlb_unshare_all_pmds(vma);
 
 skip:
@@ -2895,7 +2904,7 @@ vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)
 	 * (sleepable) vma lock can modify the current task state, that
 	 * must be before explicitly calling set_current_state().
 	 */
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		hugetlb_vma_lock_read(vma);
 
 	spin_lock_irq(&ctx->fault_pending_wqh.lock);
@@ -2912,7 +2921,7 @@ vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)
 	set_current_state(blocking_state);
 	spin_unlock_irq(&ctx->fault_pending_wqh.lock);
 
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		must_wait = userfaultfd_huge_must_wait(ctx, vmf, reason);
 		hugetlb_vma_unlock_read(vma);
 	} else {
@@ -3744,7 +3753,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
 	 * If the first vma contains huge pages, make sure start address
 	 * is aligned to huge page size.
 	 */
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		unsigned long vma_hpagesize = vma_kernel_pagesize(vma);
 
 		if (start & (vma_hpagesize - 1))
@@ -3795,7 +3804,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
 		 * If this vma contains ending address, and huge pages
 		 * check alignment.
 		 */
-		if (is_vm_hugetlb_page(cur) && end <= cur->vm_end &&
+		if (vma_is_hugetlb(cur) && end <= cur->vm_end &&
 		    end > cur->vm_start) {
 			unsigned long vma_hpagesize = vma_kernel_pagesize(cur);
 
@@ -3831,7 +3840,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
 		/*
 		 * Note vmas containing huge pages
 		 */
-		if (is_vm_hugetlb_page(cur))
+		if (vma_is_hugetlb(cur))
 			basic_ioctls = true;
 
 		found = true;
@@ -3917,7 +3926,7 @@ static int userfaultfd_unregister(struct userfaultfd_ctx *ctx,
 	 * If the first vma contains huge pages, make sure start address
 	 * is aligned to huge page size.
 	 */
-	if (is_vm_hugetlb_page(vma)) {
+	if (vma_is_hugetlb(vma)) {
 		unsigned long vma_hpagesize = vma_kernel_pagesize(vma);
 
 		if (start & (vma_hpagesize - 1))
diff --git a/mm/util.c b/mm/util.c
index bf0513d1d3d08..5a1916d8fdc10 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -1224,10 +1224,17 @@ EXPORT_SYMBOL(compat_set_desc_from_vma);
 int __compat_vma_mmap(struct vm_area_desc *desc,
 		      struct vm_area_struct *vma)
 {
+	struct vm_area_desc prev_desc;
 	int err;
 
+	/* Derive state prior to mmap_prepare hook. */
+	compat_set_desc_from_vma(&prev_desc, desc->file, vma);
 	/* Perform any preparatory tasks for mmap action. */
 	err = mmap_action_prepare(desc);
+	if (err)
+		return err;
+	/* Check the caller did nothing crazy. */
+	err = mmap_prepare_validate(&prev_desc, desc);
 	if (err)
 		return err;
 	/* Update the VMA from the descriptor. */
@@ -1455,8 +1462,10 @@ int mmap_action_prepare(struct vm_area_desc *desc)
 		return io_remap_pfn_range_prepare(desc);
 	case MMAP_SIMPLE_IO_REMAP:
 		return simple_ioremap_prepare(desc);
-	case MMAP_MAP_KERNEL_PAGES:
+	case MMAP_KERNEL_PAGES:
 		return map_kernel_pages_prepare(desc);
+	case MMAP_DISCONTIG_KERNEL_PAGES:
+		return map_discontig_kernel_pages_prepare(desc);
 	}
 
 	WARN_ON_ONCE(1);
@@ -1486,9 +1495,12 @@ int mmap_action_complete(struct vm_area_struct *vma,
 	case MMAP_REMAP_PFN:
 		err = remap_pfn_range_complete(vma, action);
 		break;
-	case MMAP_MAP_KERNEL_PAGES:
+	case MMAP_KERNEL_PAGES:
 		err = map_kernel_pages_complete(vma, action);
 		break;
+	case MMAP_DISCONTIG_KERNEL_PAGES:
+		err = map_discontig_kernel_pages_complete(vma, action);
+		break;
 	case MMAP_IO_REMAP_PFN:
 	case MMAP_SIMPLE_IO_REMAP:
 		/* Should have been delegated. */
@@ -1509,7 +1521,8 @@ int mmap_action_prepare(struct vm_area_desc *desc)
 	case MMAP_REMAP_PFN:
 	case MMAP_IO_REMAP_PFN:
 	case MMAP_SIMPLE_IO_REMAP:
-	case MMAP_MAP_KERNEL_PAGES:
+	case MMAP_KERNEL_PAGES:
+	case MMAP_DISCONTIG_KERNEL_PAGES:
 		WARN_ON_ONCE(1); /* nommu cannot handle these. */
 		break;
 	}
@@ -1530,7 +1543,8 @@ int mmap_action_complete(struct vm_area_struct *vma,
 	case MMAP_REMAP_PFN:
 	case MMAP_IO_REMAP_PFN:
 	case MMAP_SIMPLE_IO_REMAP:
-	case MMAP_MAP_KERNEL_PAGES:
+	case MMAP_KERNEL_PAGES:
+	case MMAP_DISCONTIG_KERNEL_PAGES:
 		WARN_ON_ONCE(1); /* nommu cannot handle this. */
 
 		err = -EINVAL;
diff --git a/mm/vma.c b/mm/vma.c
index 97567fb7ef33d..ab570e0a7f16c 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -599,7 +599,7 @@ __split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,
 	 * boundary.
 	 */
 	vma_adjust_trans_huge(vma, vma->vm_start, addr, NULL);
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		hugetlb_split(vma, addr);
 
 	if (new_below) {
@@ -924,13 +924,14 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
 
 	vmg->state = VMA_MERGE_NOMERGE;
 
+	if (!vma_flags_can_merge(&vmg->vma_flags))
+		return NULL;
 	/*
-	 * If a special mapping or if the range being modified is neither at the
-	 * furthermost left or right side of the VMA, then we have no chance of
-	 * merging and should abort.
+	 * If the range being modified is neither at the furthermost left or
+	 * right side of the VMA, then we have no chance of merging and should
+	 * abort.
 	 */
-	if (vma_flags_test_any_mask(&vmg->vma_flags, VMA_SPECIAL_FLAGS) ||
-	    (!left_side && !right_side))
+	if (!left_side && !right_side)
 		return NULL;
 
 	if (left_side)
@@ -1152,9 +1153,11 @@ struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg)
 
 	vmg->state = VMA_MERGE_NOMERGE;
 
-	/* Special VMAs are unmergeable, also if no prev/next. */
-	if (vma_flags_test_any_mask(&vmg->vma_flags, VMA_SPECIAL_FLAGS) ||
-	    (!prev && !next))
+	if (!vma_flags_can_merge(&vmg->vma_flags))
+		return NULL;
+
+	/* VMAs with no prev/next are unmergeable. */
+	if (!prev && !next)
 		return NULL;
 
 	can_merge_left = can_vma_merge_left(vmg);
@@ -2225,7 +2228,7 @@ bool vma_wants_writenotify(struct vm_area_struct *vma, pgprot_t vm_page_prot)
 	 * Do we need to track softdirty? hugetlb does not support softdirty
 	 * tracking yet.
 	 */
-	if (vma_soft_dirty_enabled(vma) && !is_vm_hugetlb_page(vma))
+	if (vma_soft_dirty_enabled(vma) && !vma_is_hugetlb(vma))
 		return true;
 
 	/* Do we need write faults for uffd-wp tracking? */
@@ -2344,7 +2347,7 @@ int mm_take_all_locks(struct mm_struct *mm)
 		if (signal_pending(current))
 			goto out_unlock;
 		if (vma->vm_file && vma->vm_file->f_mapping &&
-				is_vm_hugetlb_page(vma))
+				vma_is_hugetlb(vma))
 			vm_lock_mapping(mm, vma->vm_file->f_mapping);
 	}
 
@@ -2353,7 +2356,7 @@ int mm_take_all_locks(struct mm_struct *mm)
 		if (signal_pending(current))
 			goto out_unlock;
 		if (vma->vm_file && vma->vm_file->f_mapping &&
-				!is_vm_hugetlb_page(vma))
+				!vma_is_hugetlb(vma))
 			vm_lock_mapping(mm, vma->vm_file->f_mapping);
 	}
 
@@ -2578,7 +2581,6 @@ static int __mmap_setup(struct mmap_state *map, struct vm_area_desc *desc,
 	return 0;
 }
 
-
 static int __mmap_new_file_vma(struct mmap_state *map,
 			       struct vm_area_struct *vma)
 {
@@ -2592,6 +2594,11 @@ static int __mmap_new_file_vma(struct mmap_state *map,
 	if (!map->file->f_op->mmap)
 		return 0;
 
+	/*
+	 * Driver-specified flags may make the lock flags invalid, so clear
+	 * VMA_LOCKED_MASK and reinstate it afterwards if appropriate.
+	 */
+	vma_clear_flags_mask(vma, VMA_LOCKED_MASK);
 	error = mmap_file(vma->vm_file, vma);
 	if (error) {
 		UNMAP_STATE(unmap, vmi, vma, vma->vm_start, vma->vm_end,
@@ -2605,15 +2612,14 @@ static int __mmap_new_file_vma(struct mmap_state *map,
 		return error;
 	}
 
-	/* Drivers cannot alter the address of the VMA. */
-	WARN_ON_ONCE(map->addr != vma->vm_start);
-	/*
-	 * Drivers should not permit writability when previously it was
-	 * disallowed.
-	 */
-	VM_WARN_ON_ONCE(!vma_flags_same_pair(&map->vma_flags, &vma->flags) &&
-			!vma_flags_test(&map->vma_flags, VMA_MAYWRITE_BIT) &&
-			vma_test(vma, VMA_MAYWRITE_BIT));
+	/* If VMA flags still valid for locked mask, reinstate. */
+	if (vma_supports_mlock(vma)) {
+		const vma_flags_t mask =
+			vma_flags_and_mask(&map->vma_flags,
+					   VMA_LOCKED_MASK);
+
+		vma_set_flags_mask(vma, mask);
+	}
 
 	map->file = vma->vm_file;
 	map->vma_flags = vma->flags;
@@ -2693,11 +2699,6 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
 		vma->flags = map->vma_flags;
 	}
 
-#ifdef CONFIG_SPARC64
-	/* TODO: Fix SPARC ADI! */
-	WARN_ON_ONCE(!arch_validate_flags(map->vm_flags));
-#endif
-
 	/* Lock the VMA since it is modified after insertion into VMA tree */
 	vma_start_write(vma);
 	vma_iter_store_new(vmi, vma);
@@ -2760,6 +2761,96 @@ static void __mmap_complete(struct mmap_state *map, struct vm_area_struct *vma)
 	vma_set_page_prot(vma);
 }
 
+/* Check to ensure that the VMA flags of a newly mapped VMA are sane. */
+static int mmap_validate_vma_flags(const vma_flags_t *flags)
+{
+#ifdef CONFIG_SPARC64
+	const vm_flags_t legacy_flags = vma_flags_to_legacy(*flags);
+
+	/* TODO: Fix SPARC ADI! */
+	if (WARN_ON_ONCE(!arch_validate_flags(legacy_flags)))
+		return -EINVAL;
+#endif
+
+	if (!vma_flags_is_kernel_owned(flags)) {
+		/* Only kernel-owned mappings may set VMA_IO_BIT. */
+		if (WARN_ON_ONCE(vma_flags_test(flags, VMA_IO_BIT)))
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* Check to ensure a driver hasn't done something crazy. */
+static int mmap_validate(unsigned long prev_start,
+			 unsigned long curr_start,
+			 const vma_flags_t *prev_flags,
+			 const vma_flags_t *curr_flags)
+{
+	bool was_maywrite, is_maywrite;
+
+	/* Drivers cannot alter the address of the VMA. */
+	if (WARN_ON_ONCE(prev_start != curr_start))
+		return -EINVAL;
+
+	was_maywrite = vma_flags_test(prev_flags, VMA_MAYWRITE_BIT);
+	is_maywrite = vma_flags_test(curr_flags, VMA_MAYWRITE_BIT);
+
+	/* A driver may not make a previously unwritable mapping writable. */
+	if (WARN_ON_ONCE(!was_maywrite && is_maywrite))
+		return -EINVAL;
+
+	/* Only kernel-owned mappings may clear VMA_MAYWRITE_BIT. */
+	if (!vma_flags_is_kernel_owned(curr_flags) &&
+	    WARN_ON_ONCE(was_maywrite && !is_maywrite))
+		return -EINVAL;
+
+	return mmap_validate_vma_flags(curr_flags);
+}
+
+/**
+ * mmap_prepare_validate() - Ensure the driver hasn't violated invariants in its
+ * f_op->mmap_prepare hook.
+ * @prev_desc: The VMA descriptor prior to the mmap_prepare hook being called.
+ * @desc: The VMA descriptor after the mmap_prepare hook has been called.
+ *
+ * Returns: 0 on success, otherwise an error.
+ */
+int mmap_prepare_validate(const struct vm_area_desc *prev_desc,
+			  const struct vm_area_desc *desc)
+{
+	/*
+	 * It is not valid to execute mmap actions for VMAs which can be merged,
+	 * as any such merge would leave portions of the mapping incorrectly
+	 * unmapped.
+	 */
+	if (vma_flags_can_merge(&desc->vma_flags) &&
+	    WARN_ON_ONCE(desc->action.type != MMAP_NOTHING))
+		return -EINVAL;
+
+	return mmap_validate(prev_desc->start, desc->start,
+			     &prev_desc->vma_flags, &desc->vma_flags);
+}
+
+/**
+ * mmap_hook_validate() - Ensure the driver hasn't violated invariants in
+ * its f_op->mmap hook.
+ * @prev_start: The start of the mapping prior to the mmap hook.
+ * @prev_flags: The VMA flags set for the VMA prior to the mmap hook.
+ * @vma: The VMA after the hook has been applied.
+ *
+ * Returns: 0 on success, otherwise an error.
+ */
+int mmap_hook_validate(unsigned long prev_start,
+		       const vma_flags_t *prev_flags,
+		       const struct vm_area_struct *vma)
+{
+	const unsigned long start = vma->vm_start;
+	const vma_flags_t *flags = &vma->flags;
+
+	return mmap_validate(prev_start, start, prev_flags, flags);
+}
+
 static int call_action_prepare(struct mmap_state *map,
 			       struct vm_area_desc *desc)
 {
@@ -2786,6 +2877,7 @@ static int call_action_prepare(struct mmap_state *map,
 static int call_mmap_prepare(struct mmap_state *map,
 		struct vm_area_desc *desc)
 {
+	const struct vm_area_desc prev_desc = *desc;
 	int err;
 
 	/* Invoke the hook. */
@@ -2797,10 +2889,16 @@ static int call_mmap_prepare(struct mmap_state *map,
 	if (!desc->vm_ops)
 		return -EINVAL;
 
+	/* Perform any preparatory tasks for mmap action. */
 	err = call_action_prepare(map, desc);
 	if (err)
 		return err;
 
+	/* Check the caller did nothing crazy. */
+	err = mmap_prepare_validate(&prev_desc, desc);
+	if (err)
+		return err;
+
 	/* Update fields permitted to be changed. */
 	map->pgoff = desc->pgoff;
 	if (desc->vm_file != map->file) {
@@ -2866,7 +2964,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,
 {
 	struct mm_struct *mm = current->mm;
 	struct vm_area_struct *vma = NULL;
-	bool have_mmap_prepare = file && file->f_op->mmap_prepare;
+	const bool have_mmap_prepare = file && file->f_op->mmap_prepare;
 	VMA_ITERATOR(vmi, mm, addr);
 	const pgoff_t anon_pgoff = addr >> PAGE_SHIFT;
 	MMAP_STATE(map, mm, &vmi, addr, len, pgoff, anon_pgoff, vma_flags, file);
@@ -2909,7 +3007,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,
 		allocated_new = true;
 	}
 
-	if (have_mmap_prepare && !map_is_anon(&map))
+	if (have_mmap_prepare && allocated_new && !map_is_anon(&map))
 		set_vma_user_defined_fields(vma, &map);
 
 	__mmap_complete(&map, vma);
@@ -3429,10 +3527,15 @@ int __vm_munmap(unsigned long start, size_t len, bool unlock)
 int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma)
 {
 	unsigned long charged = vma_pages(vma);
+	int err;
 
 	if (find_vma_intersection(mm, vma->vm_start, vma->vm_end))
 		return -ENOMEM;
 
+	err = mmap_validate_vma_flags(&vma->flags);
+	if (err)
+		return err;
+
 	if (vma_test(vma, VMA_ACCOUNT_BIT) &&
 	     security_vm_enough_memory_mm(mm, charged))
 		return -ENOMEM;
diff --git a/mm/vma.h b/mm/vma.h
index e97bd2dfa786d..af14ed7265ce3 100644
--- a/mm/vma.h
+++ b/mm/vma.h
@@ -780,14 +780,19 @@ struct vm_area_struct *vm_area_alloc(struct mm_struct *mm);
 struct vm_area_struct *vm_area_dup(struct vm_area_struct *orig);
 void vm_area_free(struct vm_area_struct *vma);
 
-/* vma_exec.c */
 #ifdef CONFIG_MMU
+int mmap_prepare_validate(const struct vm_area_desc *prev_desc,
+			  const struct vm_area_desc *desc);
+
+int mmap_hook_validate(unsigned long prev_start,
+		       const vma_flags_t *prev_flags,
+		       const struct vm_area_struct *vma);
+
+/* vma_exec.c */
 int create_init_stack_vma(struct mm_struct *mm, struct vm_area_struct **vmap,
 			  unsigned long *top_mem_p);
 int relocate_vma_down(struct vm_area_struct *vma, unsigned long shift);
-#endif
 
-#ifdef CONFIG_MMU
 /*
  * Denies creating a writable executable mapping or gaining executable permissions.
  *
@@ -836,6 +841,19 @@ static inline bool map_deny_write_exec(const vma_flags_t *old,
 
 	return false;
 }
+#else
+static inline int mmap_prepare_validate(const struct vm_area_desc *prev_desc,
+					const struct vm_area_desc *desc)
+{
+	return 0;
+}
+
+static inline int mmap_hook_validate(unsigned long prev_start,
+				     const vma_flags_t *prev_flags,
+				     const struct vm_area_struct *vma)
+{
+	return 0;
+}
 #endif
 
 struct vm_area_struct *__install_special_mapping(struct mm_struct *mm,
diff --git a/mm/vma_internal.h b/mm/vma_internal.h
index 4d300e7bbaf4c..4f73f0a4db796 100644
--- a/mm/vma_internal.h
+++ b/mm/vma_internal.h
@@ -18,7 +18,6 @@
 #include <linux/fs.h>
 #include <linux/huge_mm.h>
 #include <linux/hugetlb.h>
-#include <linux/hugetlb_inline.h>
 #include <linux/kernel.h>
 #include <linux/ksm.h>
 #include <linux/khugepaged.h>
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 245f68c75b289..0082afbdbbdd3 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -3413,13 +3413,14 @@ static int should_skip_vma(unsigned long start, unsigned long end, struct mm_wal
 	if (!vma_is_accessible(vma))
 		return true;
 
-	if (is_vm_hugetlb_page(vma))
+	if (vma_is_hugetlb(vma))
 		return true;
 
 	if (!vma_has_recency(vma))
 		return true;
 
-	if (vma->vm_flags & (VM_LOCKED | VM_SPECIAL))
+	if (vma_test(vma, VMA_LOCKED_BIT) || vma_is_kernel_owned(vma) ||
+	    vma_is_fixed_mapping(vma))
 		return true;
 
 	if (vma == get_gate_vma(vma->vm_mm))
@@ -4363,8 +4364,8 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr)
 	if (spin_is_contended(pvmw->ptl))
 		return true;
 
-	/* exclude special VMAs containing anon pages from COW */
-	if (vma->vm_flags & VM_SPECIAL)
+	/* exclude kernel-owned and fixed VMAs containing anon pages from COW */
+	if (vma_is_kernel_owned(vma) || vma_is_fixed_mapping(vma))
 		return true;
 
 	/* avoid taking the LRU lock under the PTL when possible */
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index c7d91476971cb..545a6f89f9e76 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -340,6 +340,9 @@ static int sel_open_policy(struct inode *inode, struct file *filp)
 	struct policy_load_memory *plm = NULL;
 	int rc;
 
+	if (filp->f_mode & FMODE_WRITE)
+		return -EACCES;
+
 	rc = avc_has_perm(current_sid(), SECINITSID_SECURITY,
 			  SECCLASS_SECURITY, SECURITY__READ_POLICY, NULL);
 	if (rc)
@@ -424,14 +427,6 @@ static const struct vm_operations_struct sel_mmap_policy_ops = {
 
 static int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma)
 {
-	if (vma->vm_flags & VM_SHARED) {
-		/* do not allow mprotect to make mapping writable */
-		vm_flags_clear(vma, VM_MAYWRITE);
-
-		if (vma->vm_flags & VM_WRITE)
-			return -EACCES;
-	}
-
 	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
 	vma->vm_ops = &sel_mmap_policy_ops;
 
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 62324282fcae9..37a157d558325 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -3760,39 +3760,26 @@ static __poll_t snd_pcm_poll(struct file *file, poll_table *wait)
 /*
  * mmap status record
  */
-static vm_fault_t snd_pcm_mmap_status_fault(struct vm_fault *vmf)
+static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,
+			       struct vm_area_struct *vma)
 {
-	struct snd_pcm_substream *substream = vmf->vma->vm_private_data;
+	const unsigned long size = vma->vm_end - vma->vm_start;
 	struct snd_pcm_runtime *runtime;
-	
-	if (substream == NULL)
-		return VM_FAULT_SIGBUS;
-	runtime = substream->runtime;
-	vmf->page = virt_to_page(runtime->status);
-	get_page(vmf->page);
-	return 0;
-}
+	struct page *page;
 
-static const struct vm_operations_struct snd_pcm_vm_ops_status =
-{
-	.fault =	snd_pcm_mmap_status_fault,
-};
+	BUILD_BUG_ON(sizeof(struct snd_pcm_mmap_status) > PAGE_SIZE);
 
-static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,
-			       struct vm_area_struct *area)
-{
-	long size;
-	if (!(area->vm_flags & VM_READ))
+	if (!(vma->vm_flags & VM_READ))
 		return -EINVAL;
-	size = area->vm_end - area->vm_start;
-	if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)))
+	if (size != PAGE_SIZE)
 		return -EINVAL;
-	area->vm_ops = &snd_pcm_vm_ops_status;
-	area->vm_private_data = substream;
-	vm_flags_mod(area, VM_DONTEXPAND | VM_DONTDUMP,
+
+	vm_flags_mod(vma, VM_DONTEXPAND | VM_DONTDUMP,
 		     VM_WRITE | VM_MAYWRITE);
 
-	return 0;
+	runtime = substream->runtime;
+	page = virt_to_page(runtime->status);
+	return vm_insert_page(vma, vma->vm_start, page);
 }
 
 /*
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index 16c09dac59d9b..61b08589e5929 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -352,14 +352,6 @@ enum {
 #define VM_ACCESS_FLAGS (VM_READ | VM_WRITE | VM_EXEC)
 #define VMA_ACCESS_FLAGS mk_vma_flags(VMA_READ_BIT, VMA_WRITE_BIT, VMA_EXEC_BIT)
 
-/*
- * Special vmas that are non-mergable, non-mlock()able.
- */
-#define VM_SPECIAL (VM_IO | VM_DONTEXPAND | VM_PFNMAP | VM_MIXEDMAP)
-
-#define VMA_SPECIAL_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_DONTEXPAND_BIT, \
-				       VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT)
-
 #define VMA_REMAP_FLAGS mk_vma_flags(VMA_IO_BIT, VMA_PFNMAP_BIT,	\
 				     VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT)
 
@@ -454,11 +446,12 @@ static __always_inline bool vma_flags_empty(const vma_flags_t *flags)
 
 /* What action should be taken after an .mmap_prepare call is complete? */
 enum mmap_action_type {
-	MMAP_NOTHING,		/* Mapping is complete, no further action. */
-	MMAP_REMAP_PFN,		/* Remap PFN range. */
-	MMAP_IO_REMAP_PFN,	/* I/O remap PFN range. */
-	MMAP_SIMPLE_IO_REMAP,	/* I/O remap with guardrails. */
-	MMAP_MAP_KERNEL_PAGES,	/* Map kernel page range from an array. */
+	MMAP_NOTHING,
+	MMAP_REMAP_PFN,
+	MMAP_IO_REMAP_PFN,
+	MMAP_SIMPLE_IO_REMAP,		/* I/O remap with guardrails. */
+	MMAP_KERNEL_PAGES,		/* Map kernel page range from array. */
+	MMAP_DISCONTIG_KERNEL_PAGES,	/* Map kernel discontig page range. */
 };
 
 /*
@@ -1359,13 +1352,23 @@ static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
 	return file->f_op->mmap_prepare(desc);
 }
 
+int mmap_prepare_validate(const struct vm_area_desc *prev_desc,
+			  const struct vm_area_desc *desc);
+
 static inline int __compat_vma_mmap(struct vm_area_desc *desc,
 		struct vm_area_struct *vma)
 {
+	struct vm_area_desc prev_desc;
 	int err;
 
+	/* Derive state prior to mmap_prepare hook. */
+	compat_set_desc_from_vma(&prev_desc, desc->file, vma);
 	/* Perform any preparatory tasks for mmap action. */
 	err = mmap_action_prepare(desc);
+	if (err)
+		return err;
+	/* Check the caller did nothing crazy. */
+	err = mmap_prepare_validate(&prev_desc, desc);
 	if (err)
 		return err;
 	/* Update the VMA from the descriptor. */
@@ -1647,3 +1650,34 @@ static inline bool file_is_dev_zero(const struct file *file)
 {
 	return file && file->f_op == &zero_fops;
 }
+
+static inline bool vma_flags_is_kernel_owned(const vma_flags_t *flags)
+{
+	return vma_flags_test_any(flags, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT);
+}
+
+static inline bool vma_is_kernel_owned(const struct vm_area_struct *vma)
+{
+	return vma_flags_is_kernel_owned(&vma->flags);
+}
+
+static inline bool vma_flags_can_merge(const vma_flags_t *flags)
+{
+	/*
+	 * VMA merging assumes that the properties of a VMA completely describe
+	 * the properties of that VMA.
+	 *
+	 * However, kernel-owned mappings may have established state upon mapping
+	 * not embodied in any attribute of the VMA.
+	 *
+	 * Additionally, PFN maps encode the source PFN of the range in
+	 * vma->vm_pgoff, which may otherwise cause spurious merges.
+	 */
+	if (vma_flags_is_kernel_owned(flags))
+		return false;
+	/* VMA explicitly marked as being unmergeable. */
+	if (vma_flags_test(flags, VMA_DONTEXPAND_BIT))
+		return false;
+
+	return true;
+}
diff --git a/tools/testing/vma/include/stubs.h b/tools/testing/vma/include/stubs.h
index d6136e19a8af3..48d1dc53df42c 100644
--- a/tools/testing/vma/include/stubs.h
+++ b/tools/testing/vma/include/stubs.h
@@ -193,7 +193,7 @@ static inline bool mapping_can_writeback(struct address_space *mapping)
 	return true;
 }
 
-static inline bool is_vm_hugetlb_page(struct vm_area_struct *vma)
+static inline bool vma_is_hugetlb(struct vm_area_struct *vma)
 {
 	return false;
 }
diff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c
index acaab282939c0..b26f1a66a1707 100644
--- a/tools/testing/vma/tests/merge.c
+++ b/tools/testing/vma/tests/merge.c
@@ -496,17 +496,11 @@ static bool test_vma_merge_special_flags(void)
 		.mm = &mm,
 		.vmi = &vmi,
 	};
-	vma_flag_t special_flags[] = { VMA_IO_BIT, VMA_DONTEXPAND_BIT,
+	vma_flag_t special_flags[] = { VMA_DONTEXPAND_BIT,
 		VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT };
-	vma_flags_t all_special_flags = EMPTY_VMA_FLAGS;
 	int i;
 	struct vm_area_struct *vma_left, *vma;
 
-	/* Make sure there aren't new VM_SPECIAL flags. */
-	for (i = 0; i < ARRAY_SIZE(special_flags); i++)
-		vma_flags_set(&all_special_flags, special_flags[i]);
-	ASSERT_FLAGS_SAME_MASK(&all_special_flags, VMA_SPECIAL_FLAGS);
-
 	/*
 	 * 01234
 	 * AAA
@@ -520,7 +514,7 @@ static bool test_vma_merge_special_flags(void)
 	 * 01234
 	 * AAA*
 	 *
-	 * This should merge if not for the VM_SPECIAL flag.
+	 * This should merge if not for the 'special' flag.
 	 */
 	vmg_set_range(&vmg, 0x3000, 0x4000, 3, vma_flags);
 	for (i = 0; i < ARRAY_SIZE(special_flags); i++) {

21/3 2026/09/08 22:27 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 50638
							output: 24
							thoughts: 2047
						

Thoughts:
**KMSAN Fuzzing Evaluation for Patch Series**

Okay, let's break this down. My goal is to determine if this patch series warrants a dedicated KMSAN fuzzing session. It's a significant refactoring around how the kernel handles memory mapping, particularly discontiguous kernel pages, and related VMA properties. This means there's a higher chance of introducing issues that KMSAN could catch.

My initial assessment is that this series is *highly* likely to introduce uninitialized memory reads, information leaks, and possibly even more serious vulnerabilities.

Here's my thought process:

*   **Scope and Changes:** The patch series involves a substantial overhaul of memory mapping infrastructure. It touches critical areas like `mmap_prepare.rst`, introduces new VMA flags and associated helper functions (e.g., `vma_flags_is_kernel_owned`, `vma_can_merge`, etc.), defines new actions for discontiguous kernel pages, and modifies several drivers to use the new mechanisms. This is a large surface of potentially new bugs.

*   **Critical Points:**
    *   **New Data Structures:** The introduction of `struct discontig_kernel_page_state` is a key area of concern. The initialization of this struct is crucial. While the initializer zeroes it initially, the logic within `map_discontig_kernel_pages_complete` then updates fields (such as `action`, `__page`, `__folio`, `__page_arr`, and `__nr_pages`). The `ops->get()` callback seems to be responsible for setting these fields, and any mistakes there can lead to memory corruption.
    *   **VMA Validation:** The added VMA validation checks (`mmap_prepare_validate`, `mmap_hook_validate`) are important, but their presence doesn't *guarantee* the absence of issues. They're designed to *catch* some problems, but they're not a complete solution. We need to be wary of bugs that bypass the validation.
    *   **Driver Conversions:** The fact that drivers are being converted to use new functions like `mmap_prepare` and `map_discontig_kernel_pages_complete` is another red flag. Each driver conversion is a potential source of new bugs if the logic isn't perfectly sound.
    *   **`__compat_vma_mmap`:** The involvement of `__compat_vma_mmap` and the function `compat_set_desc_from_vma` also requires examination. I'll need to check how it interacts with the new VMA infrastructure to ensure compatibility and correctness.

*   **Potential Vulnerabilities:**
    *   **Uninitialized Memory:** The most concerning issue is the risk of uninitialized memory reads. The `struct discontig_kernel_page_state` fields must be properly initialized and validated *everywhere* it's used. Incorrect handling in the new functions (`map_discontig_kernel_pages_complete`, `mmap_action_prepare`, and the callback functions) could lead to information leaks or crashes.
    *   **Use-After-Free:** The logic of mapping pages, especially compound pages and discontiguous pages, is complex. If there are inconsistencies in how pages are referenced or released, there could be use-after-free issues.
    *   **Integer Overflow/Underflow:** Since the mapping logic involves calculating page ranges, `nr_pages`, `addr`, etc., I have to carefully check for potential integer overflows or underflows.
    *   **Race Conditions:** Memory mapping can often lead to race conditions. The patch series might introduce new race conditions that could lead to information leaks or even to kernel crashes.

*   **KMSAN Need:** Because this patch series involves significant changes to memory management, incorporates new data structures for which initialization is critical, touches multiple drivers, and has potential complexity, I strongly believe that a dedicated KMSAN fuzzing session is *essential*. This is a high-risk area for uninitialized memory reads, which is exactly what KMSAN is designed to detect.

**Therefore, NeedsKMSAN=true.**

I'll need to meticulously review the code, especially focusing on the initialization and usage of the `struct discontig_kernel_page_state` and the various `ops` callbacks, along with their interaction with VMA properties to ensure that no issues are missed.



22/3 2026/09/08 22:27 tool grepper
0m Args:
{
  "Expression": "compat_set_desc_from_vma"
}

Results:
{
  "Output": "drivers/hv/vmbus_drv.c=1923=static int hv_mmap_ring_buffer_wrapper(struct file *filp, struct kobject *kobj,\n--\ndrivers/hv/vmbus_drv.c-1934-\t */\ndrivers/hv/vmbus_drv.c:1935:\tcompat_set_desc_from_vma(\u0026desc, filp, vma);\ndrivers/hv/vmbus_drv.c-1936-\terr = channel-\u003emmap_prepare_ring_buffer(channel, \u0026desc);\n--\ndrivers/uio/uio.c=820=static int uio_mmap(struct file *filep, struct vm_area_struct *vma)\n--\ndrivers/uio/uio.c-855-\ndrivers/uio/uio.c:856:\t\tcompat_set_desc_from_vma(\u0026desc, filep, vma);\ndrivers/uio/uio.c-857-\t\tret = idev-\u003einfo-\u003emmap_prepare(idev-\u003einfo, \u0026desc);\n--\ninclude/linux/fs.h=2040=static inline bool can_mmap_file(struct file *file)\n--\ninclude/linux/fs.h-2053-\ninclude/linux/fs.h:2054:void compat_set_desc_from_vma(struct vm_area_desc *desc, const struct file *file,\ninclude/linux/fs.h-2055-\t\t\t      const struct vm_area_struct *vma);\n--\nmm/util.c=1163=EXPORT_SYMBOL(flush_dcache_folio);\n--\nmm/util.c-1166-/**\nmm/util.c:1167: * compat_set_desc_from_vma() - assigns VMA descriptor @desc fields from a VMA.\nmm/util.c-1168- * @desc: A VMA descriptor whose fields need to be set.\n--\nmm/util.c-1179- */\nmm/util.c:1180:void compat_set_desc_from_vma(struct vm_area_desc *desc,\nmm/util.c-1181-\t\t\t      const struct file *file,\n--\nmm/util.c-1199-}\nmm/util.c:1200:EXPORT_SYMBOL(compat_set_desc_from_vma);\nmm/util.c-1201-\n--\nmm/util.c-1210- * The function assumes that you have obtained a VMA descriptor @desc from\nmm/util.c:1211: * compat_set_desc_from_vma(), and already executed the mmap_prepare() hook upon\nmm/util.c-1212- * it.\n--\nmm/util.c=1224=int __compat_vma_mmap(struct vm_area_desc *desc,\n--\nmm/util.c-1230-\t/* Derive state prior to mmap_prepare hook. */\nmm/util.c:1231:\tcompat_set_desc_from_vma(\u0026prev_desc, desc-\u003efile, vma);\nmm/util.c-1232-\t/* Perform any preparatory tasks for mmap action. */\n--\nmm/util.c=1273=int compat_vma_mmap(struct file *file, struct vm_area_struct *vma)\n--\nmm/util.c-1278-\nmm/util.c:1279:\tcompat_set_desc_from_vma(\u0026desc, file, vma);\nmm/util.c-1280-\terr = vfs_mmap_prepare(file, \u0026desc);\n--\ntools/testing/vma/include/dup.h=1289=static inline void compat_set_vma_from_desc(struct vm_area_struct *vma,\n--\ntools/testing/vma/include/dup.h-1291-\ntools/testing/vma/include/dup.h:1292:static inline void compat_set_desc_from_vma(struct vm_area_desc *desc,\ntools/testing/vma/include/dup.h-1293-\t\t\t      const struct file *file,\n--\ntools/testing/vma/include/dup.h=1358=static inline int __compat_vma_mmap(struct vm_area_desc *desc,\n--\ntools/testing/vma/include/dup.h-1364-\t/* Derive state prior to mmap_prepare hook. */\ntools/testing/vma/include/dup.h:1365:\tcompat_set_desc_from_vma(\u0026prev_desc, desc-\u003efile, vma);\ntools/testing/vma/include/dup.h-1366-\t/* Perform any preparatory tasks for mmap action. */\n--\ntools/testing/vma/include/dup.h=1380=static inline int compat_vma_mmap(struct file *file, struct vm_area_struct *vma)\n--\ntools/testing/vma/include/dup.h-1385-\ntools/testing/vma/include/dup.h:1386:\tcompat_set_desc_from_vma(\u0026desc, file, vma);\ntools/testing/vma/include/dup.h-1387-\terr = vfs_mmap_prepare(file, \u0026desc);\n"
}

23/3 2026/09/08 22:27 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 54996
							output: 34
							thoughts: 23
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into `compat_set_desc_from_vma`**

Okay, time to dig into the `compat_set_desc_from_vma` function. My gut tells me this is in `mm/util.c`, so I'll start there. I'm assuming it's handling the compatibility layer and virtual memory area descriptions. I'll bet it's involved in mapping or translating descriptors, probably related to legacy architectures or older system calls dealing with virtual memory. Let's see how it actually handles the vma structure and how it constructs or modifies the descriptor. I need to understand its role in ensuring that legacy processes can interact correctly with the current kernel's memory management. I'm expecting to see some careful bit manipulation or checks for different architecture-specific details, particularly if it's dealing with older 32-bit systems or different page sizes. Let's see what the code reveals!



24/3 2026/09/08 22:27 tool read-file
0m Args:
{
  "File": "mm/util.c",
  "FirstLine": 1175,
  "LineCount": 35
}

Results:
{
  "Contents": "1175:\t * the purposes of doing this.\n1176:\t *\n1177:\t * Once the conversion of drivers is complete this function will no longer be\n1178:\t * required and will be removed.\n1179:\t */\n1180:\tvoid compat_set_desc_from_vma(struct vm_area_desc *desc,\n1181:\t\t\t\t      const struct file *file,\n1182:\t\t\t\t      const struct vm_area_struct *vma)\n1183:\t{\n1184:\t\tmemset(desc, 0, sizeof(*desc));\n1185:\t\n1186:\t\tdesc-\u003emm = vma-\u003evm_mm;\n1187:\t\tdesc-\u003efile = (struct file *)file;\n1188:\t\tdesc-\u003estart = vma-\u003evm_start;\n1189:\t\tdesc-\u003eend = vma-\u003evm_end;\n1190:\t\n1191:\t\tdesc-\u003epgoff = vma_start_pgoff(vma);\n1192:\t\tdesc-\u003evm_file = vma-\u003evm_file;\n1193:\t\tdesc-\u003evma_flags = vma-\u003eflags;\n1194:\t\tdesc-\u003epage_prot = vma-\u003evm_page_prot;\n1195:\t\tdesc-\u003evm_ops = vma-\u003evm_ops;\n1196:\t\n1197:\t\t/* Default. */\n1198:\t\tdesc-\u003eaction.type = MMAP_NOTHING;\n1199:\t}\n1200:\tEXPORT_SYMBOL(compat_set_desc_from_vma);\n1201:\t\n1202:\t/**\n1203:\t * __compat_vma_mmap() - Similar to compat_vma_mmap(), only it allows\n1204:\t * flexibility as to how the mmap_prepare callback is invoked, which is useful\n1205:\t * for drivers which invoke nested mmap_prepare callbacks in an mmap() hook.\n1206:\t * @desc: A VMA descriptor upon which an mmap_prepare() hook has already been\n1207:\t * executed.\n1208:\t * @vma: The VMA to which @desc should be applied.\n1209:\t *\n"
}

25/3 2026/09/08 22:27 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 55853
							output: 418
							thoughts: 1845
						

Thoughts:
**Analysis of Proposed Kernel Memory Management Changes**

Okay, let's break this down. First, the `compat_set_desc_from_vma` function initialization of `desc` looks good. `memset` does its job and ensures the structure is initialized. Now, let's run through the meat of the changes to identify any potential KMSAN implications, considering I'm an expert in this area.

The hugetlb modernization is just a rename – purely cosmetic. No risk there. The VMA flag helpers are nice and clean. They encapsulate some existing logic, which is a good practice, and avoids ad-hoc flag checks. The core MM updates using these new semantic helpers are also positive. The new discontiguous kernel page mapping implementation is where it gets interesting, but doesn't seem to present any direct KMSAN concerns on its own.

Now for potential leaks. I'll analyze the drivers one by one:

*   **`cmt_speech`**: It appears kernel pages are being mapped, but `desc->private_data` holds the page pointer. No direct memory leak or uninitialized data copy to user space that I can see. The mapping itself is done with a dedicated function, indicating careful handling.
*   **`hfi1`**: The fault handler is gone and replaced by `remap_vmalloc_range`. This is safer, I don't see anything suspicious in the replacement.
*   **`sg`**: Again, this leverages the new discontig page mapping helpers (`mmap_action_map_discontig_kernel_pages`). Pages are mapped proactively, minimizing fault handling, and that eliminates a potential issue with uninitialized data.
*   **`mon_bin`**:  Same as `sg`. The new helper function is used. Proactive mapping is good for security and reliability.
*   **`snd_pcm_mmap_status`**:  `vm_insert_page` is used, so no fault handler. No worries here.
*   **`selinux`**:  Protects policy file reads if write is expected. Smart.

Alright, let's check for uninitialized variable usage. Digging into the `sg_discontig_init` and `map_discontig_kernel_pages_complete` interaction, `req_sz` seems to be passed around. Upon examining the related functions, `state.private` is initialized with a pointer, which is then passed. It appears the relevant data is properly initialized. I'm satisfied that `sg`'s `req_sz` is properly managed, and isn't a source of uninitialized data usage. The same reasoning applies for `mon_bin_discontig_init`. The check in `map_discontig_kernel_pages_complete` is good – `state` is zeroed. Also, `ops->get`'s logic appears sound and controlled. The `state.action` variable and structure usage looks solid.

Okay, let's step back and compare KMSAN vs KASAN. KASAN is the tool for memory corruption, out-of-bounds access, etc., and LOCKDEP will catch concurrency issues. KMSAN is for uninitialized data use and leaks. Since this patch focuses primarily on VMA management, refactoring VMA flags, and streamlining page mapping, the risks of those bugs are covered. I see no allocation of kernel structures with padding that get sent to user space directly. No `copy_to_user`, `put_user`, socket/netlink/ioctl/bpf info leaks introduced. The structures are initialized, zeroed, or managed carefully. No new uninitialized stack or heap allocations that could be read.

I'll go through the criteria from the prompt. We're looking for whether the patch introduces or modifies:
1.  Kernel structures sent to user space where fields or padding might not be fully initialized/zeroed. - **Not present.**
2.  Conditional logic or branching that depends on potentially uninitialized variables or struct fields. - **Not present.**
3.  Allocation or initialization of complex data structures where uninitialized fields could be read later in reachable code paths. - **Not present.**
4.  Bounds checks, lengths, or logic in a way that allows surrounding code to access uninitialized bytes of existing buffers. - **Not present.**

On the other hand,
The code changes primarily risk out-of-bounds access, array overflows, NULL pointer dereferences, locking deadlocks, or use-after-free bugs. - **Not directly, KASAN and friends are better tools for these.**
All stack/heap structures touched or introduced by the patch are fully zeroed or initialized before being read or copied. - **Confirmed.**
The patch does not introduce any risk of uninitialized memory usage or info-leaks. - **Confirmed.**




26/3 2026/09/08 22:27 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series refactors VMA handling and introduces a discontiguous kernel page mapping framework for mmap_prepare:\n1. It replaces `is_vm_hugetlb_page` with `vma_is_hugetlb` and introduces semantic helper predicates for VMA flag properties (`vma_is_kernel_owned`, `vma_is_fixed_mapping`, `vma_can_merge`, `vma_is_persistent`, `vma_can_gup`).\n2. It introduces `mmap_action_map_discontig_kernel_pages()` along with `struct discontig_kernel_page_state` and `struct discontig_kernel_page_ops`, converting driver mmap hooks (such as cmt_speech, hfi1, sg, and mon_bin) to use `mmap_prepare` and `remap_vmalloc_range`/discontiguous page helpers instead of custom `.fault` handlers.\n3. All newly introduced local structures (e.g., `struct discontig_kernel_page_state state` in `map_discontig_kernel_pages_complete` and `struct vm_area_desc prev_desc` via `compat_set_desc_from_vma`) are zero-initialized with designated initializers or `memset` before any field access.\n4. No kernel data structures or uninitialized struct paddings/buffers are exposed or copied to userspace via syscalls, ioctls, or netlink.\n\nAny potential issues introduced by this refactoring (e.g., page table manipulation, VMA locking, use-after-free, or out-of-bounds page access) fall squarely under KASAN, LOCKDEP, and standard MM assertion checks. There are no uninitialized memory use or info-leak risks requiring a dedicated KMSAN session.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series refactors VMA handling and introduces a discontiguous kernel page mapping framework for mmap_prepare:\n1. It replaces `is_vm_hugetlb_page` with `vma_is_hugetlb` and introduces semantic helper predicates for VMA flag properties (`vma_is_kernel_owned`, `vma_is_fixed_mapping`, `vma_can_merge`, `vma_is_persistent`, `vma_can_gup`).\n2. It introduces `mmap_action_map_discontig_kernel_pages()` along with `struct discontig_kernel_page_state` and `struct discontig_kernel_page_ops`, converting driver mmap hooks (such as cmt_speech, hfi1, sg, and mon_bin) to use `mmap_prepare` and `remap_vmalloc_range`/discontiguous page helpers instead of custom `.fault` handlers.\n3. All newly introduced local structures (e.g., `struct discontig_kernel_page_state state` in `map_discontig_kernel_pages_complete` and `struct vm_area_desc prev_desc` via `compat_set_desc_from_vma`) are zero-initialized with designated initializers or `memset` before any field access.\n4. No kernel data structures or uninitialized struct paddings/buffers are exposed or copied to userspace via syscalls, ioctls, or netlink.\n\nAny potential issues introduced by this refactoring (e.g., page table manipulation, VMA locking, use-after-free, or out-of-bounds page access) fall squarely under KASAN, LOCKDEP, and standard MM assertion checks. There are no uninitialized memory use or info-leak risks requiring a dedicated KMSAN session.",
  "NeedsKMSAN": false
}

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)