| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/10 13:43 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE"
],
"FocusSymbols": [
"collapse_scan_pmd",
"collapse_run_pmd",
"collapse_scan_anon_pmd",
"madvise_collapse",
"collapse_possible_orders",
"collapse_vma_revalidate",
"collapse_control_release",
"mthp_collapse",
"collapse_scan_mm_slot",
"khugepaged_do_scan"
],
"KMSANReasoning": "The patch refactors the Transparent HugePage (THP) collapse infrastructure. It introduces `struct collapse_policy` to distinguish policy configurations between khugepaged background passes and explicit `MADV_COLLAPSE` operations, splits scanning and execution into `collapse_scan_pmd()` and `collapse_run_pmd()`, and relocates `madvise_collapse()` to `mm/madvise.c`.\n\nAll newly introduced and modified fields within `struct collapse_control` are explicitly initialized before use (via `collapse_control_init()`, `collapse_scan_reset()`, and scan assignments prior to running). No kernel-to-user memory transfers (`copy_to_user`, info-leaks, padding leaks) are involved, nor is there any branching on uninitialized data. Any potential regressions or bugs in this patch involve locking, page table traversal, refcounting (e.g., `struct file` or `struct folio`), or VMA revalidation, which are comprehensively covered by standard KASAN, LOCKDEP, and page allocator debug assertions. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch refactors the Transparent Hugepage (THP) collapse engine in the memory management subsystem (mm/). It splits the collapse logic into distinct scan and run phases (collapse_scan_pmd and collapse_run_pmd), introduces collapse_policy to generalize khugepaged and madvise behavior, restructures mmap_lock release/revalidation semantics, moves madvise_collapse into mm/madvise.c, and adds new WARN_ON_ONCE assertions. This code is reachable from user space via the madvise(..., MADV_COLLAPSE) syscall and background khugepaged scanning, making it well-suited for fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/10 13:43 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 7eeddd98adba18e6e64be0360b1e4fd5fb6af557\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Thu Sep 10 13:43:21 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 35262aa3e67ef..36315972156b5 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -17434,6 +17434,7 @@ F:\tDocumentation/admin-guide/mm/transhuge.rst\n F:\tinclude/linux/huge_mm.h\n F:\tinclude/linux/khugepaged.h\n F:\tinclude/trace/events/huge_memory.h\n+F:\tmm/collapse.h\n F:\tmm/huge_memory.c\n F:\tmm/khugepaged.c\n F:\tmm/mm_slot.h\ndiff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h\nindex c745f7ad22987..8ca0fa3be2acb 100644\n--- a/include/linux/huge_mm.h\n+++ b/include/linux/huge_mm.h\n@@ -510,8 +510,6 @@ change_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,\n \n int hugepage_madvise(struct vm_area_struct *vma, vm_flags_t *vm_flags,\n \t\t int advice);\n-int madvise_collapse(struct vm_area_struct *vma, unsigned long start,\n-\t\t unsigned long end, bool *lock_dropped);\n void vma_adjust_trans_huge(struct vm_area_struct *vma, unsigned long start,\n \t\t\t unsigned long end, struct vm_area_struct *next);\n spinlock_t *__pmd_trans_huge_lock(pmd_t *pmd, struct vm_area_struct *vma);\n@@ -715,13 +713,6 @@ static inline int hugepage_madvise(struct vm_area_struct *vma,\n \treturn -EINVAL;\n }\n \n-static inline int madvise_collapse(struct vm_area_struct *vma,\n-\t\t\t\t unsigned long start,\n-\t\t\t\t unsigned long end, bool *lock_dropped)\n-{\n-\treturn -EINVAL;\n-}\n-\n static inline void vma_adjust_trans_huge(struct vm_area_struct *vma,\n \t\t\t\t\t unsigned long start,\n \t\t\t\t\t unsigned long end,\ndiff --git a/mm/collapse.h b/mm/collapse.h\nnew file mode 100644\nindex 0000000000000..1ebbbf63fb256\n--- /dev/null\n+++ b/mm/collapse.h\n@@ -0,0 +1,153 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#ifndef __MM_COLLAPSE_H\n+#define __MM_COLLAPSE_H\n+\n+#include \u003clinux/mm.h\u003e\n+#include \u003clinux/nodemask.h\u003e\n+#include \u003clinux/pgtable.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+#define COLLAPSE_MAX_PTES_LIMIT\t\t(HPAGE_PMD_NR - 1)\n+#define COLLAPSE_MIN_MTHP_ORDER\t\t2\n+\n+enum scan_result {\n+\tSCAN_FAIL,\n+\tSCAN_SUCCEED,\n+\tSCAN_NO_PTE_TABLE,\n+\tSCAN_PMD_MAPPED,\n+\tSCAN_EXCEED_NONE_PTE,\n+\tSCAN_EXCEED_SWAP_PTE,\n+\tSCAN_EXCEED_SHARED_PTE,\n+\tSCAN_PTE_NON_PRESENT,\n+\tSCAN_PTE_UFFD,\n+\tSCAN_PTE_MAPPED_HUGEPAGE,\n+\tSCAN_LACK_REFERENCED_PAGE,\n+\tSCAN_PAGE_NULL,\n+\tSCAN_SCAN_ABORT,\n+\tSCAN_PAGE_COUNT,\n+\tSCAN_PAGE_LRU,\n+\tSCAN_PAGE_LOCK,\n+\tSCAN_PAGE_ANON,\n+\tSCAN_PAGE_LAZYFREE,\n+\tSCAN_PAGE_COMPOUND,\n+\tSCAN_ANY_PROCESS,\n+\tSCAN_VMA_NULL,\n+\tSCAN_VMA_CHECK,\n+\tSCAN_ADDRESS_RANGE,\n+\tSCAN_DEL_PAGE_LRU,\n+\tSCAN_ALLOC_HUGE_PAGE_FAIL,\n+\tSCAN_CGROUP_CHARGE_FAIL,\n+\tSCAN_TRUNCATED,\n+\tSCAN_PAGE_HAS_PRIVATE,\n+\tSCAN_STORE_FAILED,\n+\tSCAN_COPY_MC,\n+\tSCAN_PAGE_FILLED,\n+\tSCAN_PAGE_DIRTY_OR_WRITEBACK,\n+};\n+\n+/* What a collapse is allowed to do, decided by the caller that asks for it */\n+struct collapse_policy {\n+\t/* Limits, stated per PMD; HPAGE_PMD_NR means \"no limit\" */\n+\tunsigned int max_ptes_none;\n+\tunsigned int max_ptes_swap;\n+\tunsigned int max_ptes_shared;\n+\n+\t/* Take no swapped-out or shared PTE into a sub-PMD collapse */\n+\tbool strict_sub_pmd;\n+\n+\t/* Leave clean lazyfree folios to reclaim rather than collapse them */\n+\tbool skip_lazyfree;\n+\n+\t/* Refuse a range with no sign of use */\n+\tbool require_referenced;\n+\n+\t/* Map the PMD over a file collapse instead of leaving it to a fault */\n+\tbool install_pmd;\n+\n+\t/* Write dirty pages back and retry once instead of refusing them */\n+\tbool writeback_dirty;\n+\n+\t/* How hard to try for a destination folio */\n+\tgfp_t gfp;\n+\n+\t/* Which VMAs are eligible, as thp_vma_allowable_orders() spells it */\n+\tenum tva_type tva_type;\n+};\n+\n+struct collapse_control {\n+\tstruct collapse_policy policy;\n+\n+\t/* Num pages scanned per node */\n+\tu32 node_load[MAX_NUMNODES];\n+\n+\t/* Num pages scanned (see khugepaged_pages_to_scan) */\n+\tunsigned int progress;\n+\n+\t/* nodemask for allocation fallback */\n+\tnodemask_t alloc_nmask;\n+\n+\t/* Each bit marks a PTE the scan accepted as a collapse source */\n+\tDECLARE_BITMAP(eligible_ptes, MAX_PTRS_PER_PTE);\n+\n+\t/*\n+\t * What a scan found and the run after it needs. Live only between the\n+\t * two, and read by nobody else.\n+\t *\n+\t * The file side takes a reference while it still has the VMA, since a\n+\t * file collapse works on the page cache and never sees one; the run is\n+\t * what gives it back. A scan that found the PMD folio already in the\n+\t * cache leaves only the PTE table to retract.\n+\t */\n+\tunsigned long scan_orders;\n+\tint scan_referenced;\n+\tint scan_unmapped;\n+\tstruct file *scan_file;\n+\tpgoff_t scan_pgoff;\n+\tbool scan_retract_only;\n+};\n+\n+/* Which orders a VMA may collapse to, zero when it may not collapse at all */\n+unsigned long collapse_possible_orders(struct vm_area_struct *vma,\n+\t\tvm_flags_t vm_flags, enum tva_type tva_flags);\n+\n+/*\n+ * A caller states what it allows in cc-\u003epolicy and then hands over one PTE\n+ * table's worth of a VMA at a time:\n+ *\n+ * collapse_control_init(cc) once, before the first table\n+ * collapse_scan_pmd(vma, addr, ...) per table\n+ * collapse_run_pmd(mm, addr, cc) when a scan found work\n+ * collapse_control_release(cc) once, when done with the control\n+ *\n+ * The caller holds mmap_lock for reading over the scan and passes an address\n+ * within @vma, aligned to the PTE table the scan is to judge.\n+ *\n+ * The scan returns with that lock still held. It only reads, and almost every\n+ * table it is offered has nothing to collapse, so a caller walks a whole VMA\n+ * under the one lock it took to get there. SCAN_SUCCEED means there is\n+ * something to collapse; anything else is why there is not.\n+ *\n+ * The run is called without the lock and returns without it, taking what it\n+ * needs in between: what it does -- allocate, isolate, copy, flush -- is slow\n+ * enough that a writer would wait behind it. The caller gives the lock up\n+ * first, and with it @vma and anything derived under it, so a caller carrying\n+ * on has to look up again with collapse_vma_revalidate(). The run revalidates\n+ * for itself rather than trusting what the scan saw.\n+ *\n+ * A scan that found something has to be run: the file side takes a reference on\n+ * the file while it still has the VMA to take it from, and the run is what\n+ * gives it back.\n+ */\n+void collapse_control_init(struct collapse_control *cc);\n+void collapse_control_release(struct collapse_control *cc);\n+enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,\n+\t\tunsigned long addr, struct collapse_control *cc,\n+\t\tunsigned long orders);\n+enum scan_result collapse_run_pmd(struct mm_struct *mm, unsigned long addr,\n+\t\tstruct collapse_control *cc);\n+enum scan_result collapse_vma_revalidate(struct mm_struct *mm,\n+\t\tunsigned long address, bool expect_anon,\n+\t\tstruct vm_area_struct **vmap, struct collapse_control *cc,\n+\t\tunsigned int order);\n+\n+#endif\t/* __MM_COLLAPSE_H */\ndiff --git a/mm/khugepaged.c b/mm/khugepaged.c\nindex e13d233b9967c..1deb74cf28af0 100644\n--- a/mm/khugepaged.c\n+++ b/mm/khugepaged.c\n@@ -26,44 +26,10 @@\n #include \u003clinux/cleanup.h\u003e\n \n #include \u003casm/tlb.h\u003e\n+#include \"collapse.h\"\n #include \"internal.h\"\n-#include \"page_alloc.h\"\n #include \"mm_slot.h\"\n-\n-enum scan_result {\n-\tSCAN_FAIL,\n-\tSCAN_SUCCEED,\n-\tSCAN_NO_PTE_TABLE,\n-\tSCAN_PMD_MAPPED,\n-\tSCAN_EXCEED_NONE_PTE,\n-\tSCAN_EXCEED_SWAP_PTE,\n-\tSCAN_EXCEED_SHARED_PTE,\n-\tSCAN_PTE_NON_PRESENT,\n-\tSCAN_PTE_UFFD,\n-\tSCAN_PTE_MAPPED_HUGEPAGE,\n-\tSCAN_LACK_REFERENCED_PAGE,\n-\tSCAN_PAGE_NULL,\n-\tSCAN_SCAN_ABORT,\n-\tSCAN_PAGE_COUNT,\n-\tSCAN_PAGE_LRU,\n-\tSCAN_PAGE_LOCK,\n-\tSCAN_PAGE_ANON,\n-\tSCAN_PAGE_LAZYFREE,\n-\tSCAN_PAGE_COMPOUND,\n-\tSCAN_ANY_PROCESS,\n-\tSCAN_VMA_NULL,\n-\tSCAN_VMA_CHECK,\n-\tSCAN_ADDRESS_RANGE,\n-\tSCAN_DEL_PAGE_LRU,\n-\tSCAN_ALLOC_HUGE_PAGE_FAIL,\n-\tSCAN_CGROUP_CHARGE_FAIL,\n-\tSCAN_TRUNCATED,\n-\tSCAN_PAGE_HAS_PRIVATE,\n-\tSCAN_STORE_FAILED,\n-\tSCAN_COPY_MC,\n-\tSCAN_PAGE_FILLED,\n-\tSCAN_PAGE_DIRTY_OR_WRITEBACK,\n-};\n+#include \"page_alloc.h\"\n \n #define CREATE_TRACE_POINTS\n #include \u003ctrace/events/huge_memory.h\u003e\n@@ -91,7 +57,6 @@ static DECLARE_WAIT_QUEUE_HEAD(khugepaged_wait);\n *\n * Note that these are only respected if collapse was initiated by khugepaged.\n */\n-#define KHUGEPAGED_MAX_PTES_LIMIT (HPAGE_PMD_NR - 1)\n unsigned int khugepaged_max_ptes_none __read_mostly;\n static unsigned int khugepaged_max_ptes_swap __read_mostly;\n static unsigned int khugepaged_max_ptes_shared __read_mostly;\n@@ -101,24 +66,6 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);\n \n static struct kmem_cache *mm_slot_cache __ro_after_init;\n \n-#define KHUGEPAGED_MIN_MTHP_ORDER\t2\n-\n-struct collapse_control {\n-\tbool is_khugepaged;\n-\n-\t/* Num pages scanned per node */\n-\tu32 node_load[MAX_NUMNODES];\n-\n-\t/* Num pages scanned (see khugepaged_pages_to_scan) */\n-\tunsigned int progress;\n-\n-\t/* nodemask for allocation fallback */\n-\tnodemask_t alloc_nmask;\n-\n-\t/* Each bit represents a single occupied (!none/zero) page. */\n-\tDECLARE_BITMAP(mthp_present_ptes, MAX_PTRS_PER_PTE);\n-};\n-\n /**\n * struct khugepaged_scan - cursor for scanning\n * @mm_head: the head of the mm list to scan\n@@ -267,7 +214,7 @@ static ssize_t max_ptes_none_store(struct kobject *kobj,\n \tunsigned long max_ptes_none;\n \n \terr = kstrtoul(buf, 10, \u0026max_ptes_none);\n-\tif (err || max_ptes_none \u003e KHUGEPAGED_MAX_PTES_LIMIT)\n+\tif (err || max_ptes_none \u003e COLLAPSE_MAX_PTES_LIMIT)\n \t\treturn -EINVAL;\n \n \tkhugepaged_max_ptes_none = max_ptes_none;\n@@ -292,7 +239,7 @@ static ssize_t max_ptes_swap_store(struct kobject *kobj,\n \tunsigned long max_ptes_swap;\n \n \terr = kstrtoul(buf, 10, \u0026max_ptes_swap);\n-\tif (err || max_ptes_swap \u003e KHUGEPAGED_MAX_PTES_LIMIT)\n+\tif (err || max_ptes_swap \u003e COLLAPSE_MAX_PTES_LIMIT)\n \t\treturn -EINVAL;\n \n \tkhugepaged_max_ptes_swap = max_ptes_swap;\n@@ -318,7 +265,7 @@ static ssize_t max_ptes_shared_store(struct kobject *kobj,\n \tunsigned long max_ptes_shared;\n \n \terr = kstrtoul(buf, 10, \u0026max_ptes_shared);\n-\tif (err || max_ptes_shared \u003e KHUGEPAGED_MAX_PTES_LIMIT)\n+\tif (err || max_ptes_shared \u003e COLLAPSE_MAX_PTES_LIMIT)\n \t\treturn -EINVAL;\n \n \tkhugepaged_max_ptes_shared = max_ptes_shared;\n@@ -367,30 +314,27 @@ static bool pte_none_or_zero(pte_t pte)\n static unsigned int collapse_max_ptes_none(struct collapse_control *cc,\n \t\tstruct vm_area_struct *vma, unsigned int order)\n {\n-\tconst unsigned int max_ptes_none = khugepaged_max_ptes_none;\n+\tconst unsigned int max_ptes_none = cc-\u003epolicy.max_ptes_none;\n \n \tif (vma \u0026\u0026 userfaultfd_armed(vma))\n \t\treturn 0;\n-\t/* for MADV_COLLAPSE, allow any empty/shared zeropage PTEs */\n-\tif (!cc-\u003eis_khugepaged)\n-\t\treturn HPAGE_PMD_NR;\n-\t/* for PMD collapse, respect the user defined maximum */\n-\tif (is_pmd_order(order))\n+\t/* The limit as given, at the PMD order and wherever it is not capped */\n+\tif (is_pmd_order(order) || !cc-\u003epolicy.strict_sub_pmd)\n \t\treturn max_ptes_none;\n \t/*\n-\t * for mTHP collapse with the sysctl value set to KHUGEPAGED_MAX_PTES_LIMIT,\n+\t * for mTHP collapse with the sysctl value set to COLLAPSE_MAX_PTES_LIMIT,\n \t * scale the maximum number of PTEs to the order of the collapse.\n \t */\n-\tif (max_ptes_none == KHUGEPAGED_MAX_PTES_LIMIT)\n+\tif (max_ptes_none == COLLAPSE_MAX_PTES_LIMIT)\n \t\treturn (1 \u003c\u003c order) - 1;\n \t/*\n-\t * For mTHP collapse of values other than 0 or KHUGEPAGED_MAX_PTES_LIMIT,\n+\t * For mTHP collapse of values other than 0 or COLLAPSE_MAX_PTES_LIMIT,\n \t * emit a warning and return 0.\n \t */\n \tif (max_ptes_none)\n \t\tpr_warn_once(\"mTHP collapse does not support max_ptes_none\"\n \t\t \" values other than 0 or %u, defaulting to 0.\\n\",\n-\t\t KHUGEPAGED_MAX_PTES_LIMIT);\n+\t\t COLLAPSE_MAX_PTES_LIMIT);\n \treturn 0;\n }\n \n@@ -407,19 +351,12 @@ static unsigned int collapse_max_ptes_shared(struct collapse_control *cc,\n \t\tunsigned int order)\n {\n \t/*\n-\t * For MADV_COLLAPSE, do not restrict the number of PTEs that map shared\n-\t * anonymous pages.\n-\t */\n-\tif (!cc-\u003eis_khugepaged)\n-\t\treturn HPAGE_PMD_NR;\n-\t/*\n-\t * for mTHP collapse do not allow collapsing anonymous memory pages that\n-\t * are shared between processes.\n+\t * A sub-PMD window held to the strict rule takes no shared page at all:\n+\t * an mTHP is not worth the CoW-breaking.\n \t */\n-\tif (!is_pmd_order(order))\n+\tif (!is_pmd_order(order) \u0026\u0026 cc-\u003epolicy.strict_sub_pmd)\n \t\treturn 0;\n-\t/* for PMD collapse, respect the user defined maximum */\n-\treturn khugepaged_max_ptes_shared;\n+\treturn cc-\u003epolicy.max_ptes_shared;\n }\n \n /**\n@@ -435,16 +372,12 @@ static unsigned int collapse_max_ptes_swap(struct collapse_control *cc,\n \t\tunsigned int order)\n {\n \t/*\n-\t * For MADV_COLLAPSE, do not restrict the number PTEs entries or\n-\t * pagecache entries that are non-present.\n+\t * A sub-PMD window held to the strict rule takes nothing non-present:\n+\t * reading pages back to build an mTHP is not worth the latency.\n \t */\n-\tif (!cc-\u003eis_khugepaged)\n-\t\treturn HPAGE_PMD_NR;\n-\t/* for mTHP collapse do not allow any non-present PTEs or pagecache entries */\n-\tif (!is_pmd_order(order))\n+\tif (!is_pmd_order(order) \u0026\u0026 cc-\u003epolicy.strict_sub_pmd)\n \t\treturn 0;\n-\t/* for PMD collapse, respect the user defined maximum */\n-\treturn khugepaged_max_ptes_swap;\n+\treturn cc-\u003epolicy.max_ptes_swap;\n }\n \n int hugepage_madvise(struct vm_area_struct *vma,\n@@ -476,7 +409,7 @@ int __init khugepaged_init(void)\n \t\treturn -ENOMEM;\n \n \tkhugepaged_pages_to_scan = HPAGE_PMD_NR * 8;\n-\tkhugepaged_max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;\n+\tkhugepaged_max_ptes_none = COLLAPSE_MAX_PTES_LIMIT;\n \tkhugepaged_max_ptes_swap = HPAGE_PMD_NR / 8;\n \tkhugepaged_max_ptes_shared = HPAGE_PMD_NR / 2;\n \n@@ -565,7 +498,7 @@ void __khugepaged_enter(struct mm_struct *mm)\n * Check what orders are possible based on the vma and collapse type.\n * This is used to determine if mTHP collapse is a viable option.\n */\n-static unsigned long collapse_possible_orders(struct vm_area_struct *vma,\n+unsigned long collapse_possible_orders(struct vm_area_struct *vma,\n \t\tvm_flags_t vm_flags, enum tva_type tva_flags)\n {\n \tunsigned long orders;\n@@ -579,17 +512,11 @@ static unsigned long collapse_possible_orders(struct vm_area_struct *vma,\n \treturn thp_vma_allowable_orders(vma, vm_flags, tva_flags, orders);\n }\n \n-static bool collapse_possible(struct vm_area_struct *vma,\n-\t\tvm_flags_t vm_flags, enum tva_type tva_flags)\n-{\n-\treturn collapse_possible_orders(vma, vm_flags, tva_flags);\n-}\n-\n void khugepaged_enter_vma(struct vm_area_struct *vma,\n \t\t\t vm_flags_t vm_flags)\n {\n-\tif (!mm_flags_test(MMF_VM_HUGEPAGE, vma-\u003evm_mm) \u0026\u0026 hugepage_enabled()\n-\t \u0026\u0026 collapse_possible(vma, vm_flags, TVA_KHUGEPAGED))\n+\tif (!mm_flags_test(MMF_VM_HUGEPAGE, vma-\u003evm_mm) \u0026\u0026 hugepage_enabled() \u0026\u0026\n+\t collapse_possible_orders(vma, vm_flags, TVA_KHUGEPAGED))\n \t\t__khugepaged_enter(vma-\u003evm_mm);\n }\n \n@@ -623,11 +550,11 @@ void __khugepaged_exit(struct mm_struct *mm)\n \t}\n }\n \n-static void collapse_control_init_scan(struct collapse_control *cc)\n+static void collapse_scan_reset(struct collapse_control *cc)\n {\n \tmemset(cc-\u003enode_load, 0, sizeof(cc-\u003enode_load));\n \tnodes_clear(cc-\u003ealloc_nmask);\n-\tbitmap_zero(cc-\u003emthp_present_ptes, MAX_PTRS_PER_PTE);\n+\tbitmap_zero(cc-\u003eeligible_ptes, MAX_PTRS_PER_PTE);\n }\n \n static void release_pte_folio(struct folio *folio)\n@@ -731,7 +658,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n \t\t * If the vma has the VM_DROPPABLE flag, the collapse will\n \t\t * preserve the lazyfree property without needing to skip.\n \t\t */\n-\t\tif (cc-\u003eis_khugepaged \u0026\u0026 !(vma-\u003evm_flags \u0026 VM_DROPPABLE) \u0026\u0026\n+\t\tif (cc-\u003epolicy.skip_lazyfree \u0026\u0026 !(vma-\u003evm_flags \u0026 VM_DROPPABLE) \u0026\u0026\n \t\t folio_test_lazyfree(folio) \u0026\u0026 !pte_dirty(pteval)) {\n \t\t\tresult = SCAN_PAGE_LAZYFREE;\n \t\t\tgoto out;\n@@ -820,12 +747,12 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n \t\tif (folio_test_large(folio))\n \t\t\tlist_add_tail(\u0026folio-\u003elru, compound_pagelist);\n next:\n-\t\tif (cc-\u003eis_khugepaged \u0026\u0026\n+\t\tif (cc-\u003epolicy.require_referenced \u0026\u0026\n \t\t folio_pte_referenced(folio, vma, addr, pteval))\n \t\t\treferenced++;\n \t}\n \n-\tif (unlikely(cc-\u003eis_khugepaged \u0026\u0026 !referenced)) {\n+\tif (unlikely(cc-\u003epolicy.require_referenced \u0026\u0026 !referenced)) {\n \t\tresult = SCAN_LACK_REFERENCED_PAGE;\n \t} else {\n \t\tresult = SCAN_SUCCEED;\n@@ -991,9 +918,7 @@ static void khugepaged_alloc_sleep(void)\n \tremove_wait_queue(\u0026khugepaged_wait, \u0026wait);\n }\n \n-static struct collapse_control khugepaged_collapse_control = {\n-\t.is_khugepaged = true,\n-};\n+static struct collapse_control khugepaged_collapse_control;\n \n static bool collapse_scan_abort(int nid, struct collapse_control *cc)\n {\n@@ -1029,6 +954,21 @@ static inline gfp_t alloc_hugepage_khugepaged_gfpmask(void)\n \treturn khugepaged_defrag() ? GFP_TRANSHUGE : GFP_TRANSHUGE_LIGHT;\n }\n \n+/* khugepaged collapses on its own initiative, so it obeys its own settings */\n+static void collapse_policy_khugepaged(struct collapse_policy *p)\n+{\n+\tp-\u003emax_ptes_none = READ_ONCE(khugepaged_max_ptes_none);\n+\tp-\u003emax_ptes_swap = READ_ONCE(khugepaged_max_ptes_swap);\n+\tp-\u003emax_ptes_shared = READ_ONCE(khugepaged_max_ptes_shared);\n+\tp-\u003estrict_sub_pmd = true;\n+\tp-\u003eskip_lazyfree = true;\n+\tp-\u003erequire_referenced = true;\n+\tp-\u003einstall_pmd = false;\n+\tp-\u003ewriteback_dirty = false;\n+\tp-\u003egfp = alloc_hugepage_khugepaged_gfpmask();\n+\tp-\u003etva_type = TVA_KHUGEPAGED;\n+}\n+\n #ifdef CONFIG_NUMA\n static int collapse_find_target_node(struct collapse_control *cc)\n {\n@@ -1061,13 +1001,12 @@ static int collapse_find_target_node(struct collapse_control *cc)\n * Returns enum scan_result value.\n */\n \n-static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned long address,\n+enum scan_result collapse_vma_revalidate(struct mm_struct *mm, unsigned long address,\n \t\tbool expect_anon, struct vm_area_struct **vmap,\n \t\tstruct collapse_control *cc, unsigned int order)\n {\n \tstruct vm_area_struct *vma;\n-\tenum tva_type type = cc-\u003eis_khugepaged ? TVA_KHUGEPAGED :\n-\t\t\t\t TVA_FORCED_COLLAPSE;\n+\tenum tva_type type = cc-\u003epolicy.tva_type;\n \n \tif (unlikely(collapse_test_exit_or_disable(mm)))\n \t\treturn SCAN_ANY_PROCESS;\n@@ -1250,8 +1189,7 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,\n static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_struct *mm,\n \t\tstruct collapse_control *cc, unsigned int order)\n {\n-\tgfp_t gfp = (cc-\u003eis_khugepaged ? alloc_hugepage_khugepaged_gfpmask() :\n-\t\t GFP_TRANSHUGE);\n+\tgfp_t gfp = cc-\u003epolicy.gfp;\n \tint node = collapse_find_target_node(cc);\n \tstruct folio *folio;\n \n@@ -1311,7 +1249,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s\n \t}\n \n \tmmap_read_lock(mm);\n-\tresult = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,\n+\tresult = collapse_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,\n \t\t\t\t\t \u0026vma, cc, order);\n \tif (result != SCAN_SUCCEED) {\n \t\tmmap_read_unlock(mm);\n@@ -1346,7 +1284,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s\n \t * mmap_lock.\n \t */\n \tmmap_write_lock(mm);\n-\tresult = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,\n+\tresult = collapse_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,\n \t\t\t\t\t \u0026vma, cc, order);\n \tif (result != SCAN_SUCCEED)\n \t\tgoto out_up_write;\n@@ -1482,15 +1420,15 @@ static unsigned int max_order_from_offset(unsigned int offset)\n * mthp_collapse() consumes the bitmap that is generated during\n * collapse_scan_pmd() to determine what regions and mTHP orders fit best.\n *\n- * Each bit in cc-\u003emthp_present_ptes represents a single occupied (!none/zero)\n- * page. We start at the PMD order and check if it is eligible for collapse;\n+ * Each bit in cc-\u003eeligible_ptes marks a PTE the scan accepted as a collapse\n+ * source. We start at the PMD order and check if it is eligible for collapse;\n * if not, we check the left and right halves of the PTE page table we are\n * examining at a lower order.\n *\n- * For each of these, we determine how many PTE entries are occupied in the\n- * range of PTE entries we propose to collapse, then we compare this to a\n- * threshold number of PTE entries which would need to be occupied for a\n- * collapse to be permitted at that order (accounting for max_ptes_none).\n+ * For each of these, we count the eligible PTEs in the range we propose to\n+ * collapse, then we compare this to the number of eligible PTEs the range\n+ * would need for a collapse to be permitted at that order (accounting for\n+ * max_ptes_none).\n *\n * If a collapse is permitted, we attempt to collapse the PTE range into a\n * mTHP.\n@@ -1499,7 +1437,7 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,\n \t\tunsigned long address, int referenced, int unmapped,\n \t\tstruct collapse_control *cc, unsigned long enabled_orders)\n {\n-\tunsigned int nr_occupied_ptes, nr_ptes, max_ptes_none;\n+\tunsigned int nr_eligible_ptes, nr_ptes, max_ptes_none;\n \tenum scan_result last_result = SCAN_FAIL;\n \tint collapsed = 0;\n \tbool alloc_failed = false;\n@@ -1514,18 +1452,18 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,\n \t\t\tgoto next_order;\n \n \t\tmax_ptes_none = collapse_max_ptes_none(cc, NULL, order);\n-\t\tnr_occupied_ptes = bitmap_weight_from(cc-\u003emthp_present_ptes, offset,\n+\t\tnr_eligible_ptes = bitmap_weight_from(cc-\u003eeligible_ptes, offset,\n \t\t\t\t\t\t offset + nr_ptes);\n \n \t\t/*\n \t\t * Swap PTEs accepted during the scan are counted in @unmapped,\n-\t\t * not in the present-PTE bitmap. Account them for the PMD-order\n+\t\t * not in cc-\u003eeligible_ptes. Account them for the PMD-order\n \t\t * candidate.\n \t\t */\n \t\tif (is_pmd_order(order))\n-\t\t\tnr_occupied_ptes += unmapped;\n+\t\t\tnr_eligible_ptes += unmapped;\n \n-\t\tif (nr_occupied_ptes \u003e= nr_ptes - max_ptes_none) {\n+\t\tif (nr_eligible_ptes \u003e= nr_ptes - max_ptes_none) {\n \t\t\tenum scan_result ret;\n \n \t\t\tcollapse_address = address + offset * PAGE_SIZE;\n@@ -1571,8 +1509,8 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,\n \t\t * any smaller order enabled. When at the smallest order\n \t\t * we must always move to the next offset.\n \t\t */\n-\t\tif (order \u003e KHUGEPAGED_MIN_MTHP_ORDER \u0026\u0026\n-\t\t\t(enabled_orders \u0026 GENMASK(order - 1, 0))) {\n+\t\tif (order \u003e COLLAPSE_MIN_MTHP_ORDER \u0026\u0026\n+\t\t (enabled_orders \u0026 GENMASK(order - 1, 0))) {\n \t\t\torder--;\n \t\t\tcontinue;\n \t\t}\n@@ -1597,14 +1535,14 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,\n \treturn last_result;\n }\n \n-static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n-\t\tstruct vm_area_struct *vma, unsigned long start_addr,\n-\t\tbool *lock_dropped, struct collapse_control *cc)\n+static enum scan_result collapse_scan_anon_pmd(struct vm_area_struct *vma,\n+\t\tunsigned long start_addr, struct collapse_control *cc,\n+\t\tunsigned long enabled_orders)\n {\n \tconst unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);\n \tconst unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);\n \tunsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);\n-\tenum tva_type tva_flags = cc-\u003eis_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;\n+\tstruct mm_struct *mm = vma-\u003evm_mm;\n \tpmd_t *pmd;\n \tpte_t *pte, *_pte, pteval;\n \tint i;\n@@ -1614,7 +1552,6 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \tstruct folio *folio = NULL;\n \tunsigned long failed_pfn = -1;\n \tunsigned long addr;\n-\tunsigned long enabled_orders;\n \tspinlock_t *ptl;\n \tint node = NUMA_NO_NODE, unmapped = 0;\n \n@@ -1626,9 +1563,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \t\tgoto out;\n \t}\n \n-\tcollapse_control_init_scan(cc);\n-\n-\tenabled_orders = collapse_possible_orders(vma, vma-\u003evm_flags, tva_flags);\n+\tcollapse_scan_reset(cc);\n \n \t/*\n \t * If PMD is the only enabled order, enforce max_ptes_none, otherwise\n@@ -1636,7 +1571,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \t * is then checked again in mthp_collapse() for each attempted order.\n \t */\n \tif (enabled_orders != BIT(HPAGE_PMD_ORDER))\n-\t\tmax_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;\n+\t\tmax_ptes_none = COLLAPSE_MAX_PTES_LIMIT;\n \n \tpte = pte_offset_map_lock(mm, pmd, start_addr, \u0026ptl);\n \tif (!pte) {\n@@ -1704,7 +1639,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \t\t * If the vma has the VM_DROPPABLE flag, the collapse will\n \t\t * preserve the lazyfree property without needing to skip.\n \t\t */\n-\t\tif (cc-\u003eis_khugepaged \u0026\u0026 !(vma-\u003evm_flags \u0026 VM_DROPPABLE) \u0026\u0026\n+\t\tif (cc-\u003epolicy.skip_lazyfree \u0026\u0026 !(vma-\u003evm_flags \u0026 VM_DROPPABLE) \u0026\u0026\n \t\t folio_test_lazyfree(folio) \u0026\u0026 !pte_dirty(pteval)) {\n \t\t\tresult = SCAN_PAGE_LAZYFREE;\n \t\t\tfailed_pfn = folio_pfn(folio);\n@@ -1731,8 +1666,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \t\t\t}\n \t\t}\n \n-\t\t/* Set bit for occupied pages */\n-\t\t__set_bit(i, cc-\u003emthp_present_ptes);\n+\t\t/* The scan accepted this PTE as a collapse source */\n+\t\t__set_bit(i, cc-\u003eeligible_ptes);\n \t\t/*\n \t\t * Record which node the original page is from and save this\n \t\t * information to cc-\u003enode_load[].\n@@ -1770,13 +1705,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \t\t\tgoto out_unmap;\n \t\t}\n \n-\t\tif (cc-\u003eis_khugepaged \u0026\u0026\n+\t\tif (cc-\u003epolicy.require_referenced \u0026\u0026\n \t\t folio_pte_referenced(folio, vma, addr, pteval))\n \t\t\treferenced++;\n \t}\n-\tif (cc-\u003eis_khugepaged \u0026\u0026\n-\t\t (!referenced ||\n-\t\t (unmapped \u0026\u0026 referenced \u003c HPAGE_PMD_NR / 2))) {\n+\tif (cc-\u003epolicy.require_referenced \u0026\u0026\n+\t (!referenced ||\n+\t (unmapped \u0026\u0026 referenced \u003c HPAGE_PMD_NR / 2))) {\n \t\tresult = SCAN_LACK_REFERENCED_PAGE;\n \t} else {\n \t\tresult = SCAN_SUCCEED;\n@@ -1784,12 +1719,9 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n out_unmap:\n \tpte_unmap_unlock(pte, ptl);\n \tif (result == SCAN_SUCCEED) {\n-\t\t/* collapse_huge_page() expects the lock to be dropped before calling */\n-\t\tmmap_read_unlock(mm);\n-\t\tresult = mthp_collapse(mm, start_addr, referenced,\n-\t\t\t\t unmapped, cc, enabled_orders);\n-\t\t/* mmap_lock was released above, set lock_dropped */\n-\t\t*lock_dropped = true;\n+\t\tcc-\u003escan_orders = enabled_orders;\n+\t\tcc-\u003escan_referenced = referenced;\n+\t\tcc-\u003escan_unmapped = unmapped;\n \t}\n out:\n \ttrace_mm_khugepaged_scan_pmd(mm, failed_pfn, referenced,\n@@ -2635,11 +2567,11 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,\n \txas_unlock_irq(\u0026xas);\n \n \t/*\n-\t * Remove pte page tables, so we can re-fault the page as huge.\n-\t * If MADV_COLLAPSE, adjust result to call try_collapse_pte_mapped_thp().\n+\t * Remove pte page tables, so we can re-fault the page as huge. A\n+\t * caller that wants the PMD mapped now is told to go and do that.\n \t */\n \tretract_page_tables(mapping, start);\n-\tif (cc \u0026\u0026 !cc-\u003eis_khugepaged)\n+\tif (cc-\u003epolicy.install_pmd)\n \t\tresult = SCAN_PTE_MAPPED_HUGEPAGE;\n \tfolio_unlock(new_folio);\n \n@@ -2704,7 +2636,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,\n \n \tpresent = 0;\n \tswap = 0;\n-\tcollapse_control_init_scan(cc);\n+\tcollapse_scan_reset(cc);\n \trcu_read_lock();\n \txas_for_each(\u0026xas, folio, start + HPAGE_PMD_NR - 1) {\n \t\tif (xas_retry(\u0026xas, folio))\n@@ -2786,53 +2718,99 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,\n \telse\n \t\tcc-\u003eprogress += HPAGE_PMD_NR;\n \n-\tif (result == SCAN_SUCCEED) {\n-\t\tif (present \u003c HPAGE_PMD_NR - max_ptes_none) {\n-\t\t\tresult = SCAN_EXCEED_NONE_PTE;\n-\t\t\tcount_vm_event(THP_SCAN_EXCEED_NONE_PTE);\n-\t\t} else {\n-\t\t\tresult = collapse_file(mm, addr, file, start, cc);\n-\t\t}\n+\tif (result == SCAN_SUCCEED \u0026\u0026 present \u003c HPAGE_PMD_NR - max_ptes_none) {\n+\t\tresult = SCAN_EXCEED_NONE_PTE;\n+\t\tcount_vm_event(THP_SCAN_EXCEED_NONE_PTE);\n \t}\n \n-\ttrace_mm_khugepaged_scan_file(mm, failed_pfn, file, present, swap, result);\n+\ttrace_mm_khugepaged_scan_file(mm, failed_pfn, file, present, swap,\n+\t\t\t\t result);\n \treturn result;\n }\n \n-/*\n- * Try to collapse a single PMD starting at a PMD aligned addr, and return\n- * the results.\n- */\n-static enum scan_result collapse_single_pmd(unsigned long addr,\n-\t\tstruct vm_area_struct *vma, bool *lock_dropped,\n-\t\tstruct collapse_control *cc)\n+void collapse_control_init(struct collapse_control *cc)\n+{\n+\tcc-\u003eprogress = 0;\n+\tcc-\u003escan_file = NULL;\n+}\n+\n+void collapse_control_release(struct collapse_control *cc)\n+{\n+\t/* A scan that took a file reference should have been run */\n+\tif (WARN_ON_ONCE(cc-\u003escan_file)) {\n+\t\tfput(cc-\u003escan_file);\n+\t\tcc-\u003escan_file = NULL;\n+\t}\n+}\n+\n+enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,\n+\t\tunsigned long addr, struct collapse_control *cc,\n+\t\tunsigned long orders)\n {\n-\tstruct mm_struct *mm = vma-\u003evm_mm;\n-\tbool triggered_wb = false;\n \tenum scan_result result;\n-\tstruct file *file;\n \tpgoff_t pgoff;\n \n-\tmmap_assert_locked(mm);\n-\n-\tif (vma_is_anonymous(vma)) {\n-\t\tresult = collapse_scan_pmd(mm, vma, addr, lock_dropped, cc);\n-\t\tgoto end;\n+\tmmap_assert_locked(vma-\u003evm_mm);\n+\t/* Whatever the last scan found has to have been run by now */\n+\tif (WARN_ON_ONCE(cc-\u003escan_file)) {\n+\t\tfput(cc-\u003escan_file);\n+\t\tcc-\u003escan_file = NULL;\n \t}\n \n-\tfile = get_file(vma-\u003evm_file);\n-\tpgoff = linear_page_index(vma, addr);\n+\tif (vma_is_anonymous(vma))\n+\t\treturn collapse_scan_anon_pmd(vma, addr, cc, orders);\n \n-\tmmap_read_unlock(mm);\n-\t*lock_dropped = true;\n-retry:\n-\tresult = collapse_scan_file(mm, addr, file, pgoff, cc);\n+\tpgoff = linear_page_index(vma, addr);\n+\tresult = collapse_scan_file(vma-\u003evm_mm, addr, vma-\u003evm_file, pgoff, cc);\n+\tswitch (result) {\n+\tcase SCAN_SUCCEED:\n+\t\tcc-\u003escan_retract_only = false;\n+\t\tbreak;\n+\tcase SCAN_PTE_MAPPED_HUGEPAGE:\n+\t\t/*\n+\t\t * The page cache already holds the PMD folio; what is left is\n+\t\t * to retract the PTE table, which is the run's job.\n+\t\t */\n+\t\tcc-\u003escan_retract_only = true;\n+\t\tresult = SCAN_SUCCEED;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn result;\n+\t}\n \n \t/*\n-\t * For MADV_COLLAPSE, when encountering dirty pages, try to writeback,\n-\t * then retry the collapse one time.\n+\t * A file collapse works on the page cache and never sees a VMA, so take\n+\t * what it needs from this one while it is still here.\n \t */\n-\tif (!cc-\u003eis_khugepaged \u0026\u0026 result == SCAN_PAGE_DIRTY_OR_WRITEBACK \u0026\u0026\n+\tcc-\u003escan_file = get_file(vma-\u003evm_file);\n+\tcc-\u003escan_pgoff = pgoff;\n+\treturn result;\n+}\n+\n+enum scan_result collapse_run_pmd(struct mm_struct *mm, unsigned long addr,\n+\t\tstruct collapse_control *cc)\n+{\n+\tstruct file *file = cc-\u003escan_file;\n+\tbool triggered_wb = false;\n+\tenum scan_result result;\n+\tpgoff_t pgoff;\n+\n+\tif (!file)\n+\t\treturn mthp_collapse(mm, addr, cc-\u003escan_referenced,\n+\t\t\t\t cc-\u003escan_unmapped, cc, cc-\u003escan_orders);\n+\n+\tcc-\u003escan_file = NULL;\n+\tpgoff = cc-\u003escan_pgoff;\n+\n+\tif (cc-\u003escan_retract_only) {\n+\t\tresult = SCAN_PTE_MAPPED_HUGEPAGE;\n+\t\tgoto retract;\n+\t}\n+retry:\n+\tresult = collapse_file(mm, addr, file, pgoff, cc);\n+\n+\t/* Dirty pages are worth a writeback and one more try, if asked for */\n+\tif (cc-\u003epolicy.writeback_dirty \u0026\u0026 result == SCAN_PAGE_DIRTY_OR_WRITEBACK \u0026\u0026\n \t !triggered_wb \u0026\u0026 mapping_can_writeback(file-\u003ef_mapping)) {\n \t\tconst loff_t lstart = (loff_t)pgoff \u003c\u003c PAGE_SHIFT;\n \t\tconst loff_t lend = lstart + HPAGE_PMD_SIZE - 1;\n@@ -2841,22 +2819,24 @@ static enum scan_result collapse_single_pmd(unsigned long addr,\n \t\ttriggered_wb = true;\n \t\tgoto retry;\n \t}\n+retract:\n \tfput(file);\n \n+\t/*\n+\t * A PMD folio is in the page cache, whether the collapse just put it\n+\t * there or found it: retract the PTE table, and map the PMD if asked.\n+\t */\n \tif (result == SCAN_PTE_MAPPED_HUGEPAGE) {\n \t\tmmap_read_lock(mm);\n \t\tif (collapse_test_exit_or_disable(mm))\n \t\t\tresult = SCAN_ANY_PROCESS;\n \t\telse\n \t\t\tresult = try_collapse_pte_mapped_thp(mm, addr,\n-\t\t\t\t\t\t\t !cc-\u003eis_khugepaged);\n+\t\t\t\t\t\t\tcc-\u003epolicy.install_pmd);\n \t\tif (result == SCAN_PMD_MAPPED)\n \t\t\tresult = SCAN_SUCCEED;\n \t\tmmap_read_unlock(mm);\n \t}\n-end:\n-\tif (cc-\u003eis_khugepaged \u0026\u0026 result == SCAN_SUCCEED)\n-\t\t++khugepaged_pages_collapsed;\n \treturn result;\n }\n \n@@ -2899,14 +2879,17 @@ static void collapse_scan_mm_slot(unsigned int progress_max,\n \n \tvma_iter_init(\u0026vmi, mm, khugepaged_scan.address);\n \tfor_each_vma(vmi, vma) {\n-\t\tunsigned long hstart, hend;\n+\t\tunsigned long hstart, hend, orders;\n \n \t\tcond_resched();\n \t\tif (unlikely(collapse_test_exit_or_disable(mm))) {\n \t\t\tcc-\u003eprogress++;\n \t\t\tbreak;\n \t\t}\n-\t\tif (!collapse_possible(vma, vma-\u003evm_flags, TVA_KHUGEPAGED)) {\n+\t\t/* One mask for the whole VMA */\n+\t\torders = collapse_possible_orders(vma, vma-\u003evm_flags,\n+\t\t\t\t\t\t cc-\u003epolicy.tva_type);\n+\t\tif (!orders) {\n \t\t\tcc-\u003eprogress++;\n \t\t\tcontinue;\n \t\t}\n@@ -2921,7 +2904,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,\n \t\tVM_BUG_ON(khugepaged_scan.address \u0026 ~HPAGE_PMD_MASK);\n \n \t\twhile (khugepaged_scan.address \u003c hend) {\n-\t\t\tbool lock_dropped = false;\n+\t\t\tunsigned long addr;\n \n \t\t\tcond_resched();\n \t\t\tif (unlikely(collapse_test_exit_or_disable(mm)))\n@@ -2931,21 +2914,29 @@ static void collapse_scan_mm_slot(unsigned int progress_max,\n \t\t\t\t khugepaged_scan.address + HPAGE_PMD_SIZE \u003e\n \t\t\t\t hend);\n \n-\t\t\t*result = collapse_single_pmd(khugepaged_scan.address,\n-\t\t\t\t\t\t vma, \u0026lock_dropped, cc);\n+\t\t\taddr = khugepaged_scan.address;\n \t\t\t/* move to next address */\n \t\t\tkhugepaged_scan.address += HPAGE_PMD_SIZE;\n-\t\t\tif (lock_dropped)\n-\t\t\t\t/*\n-\t\t\t\t * We released mmap_lock so break loop. Note\n-\t\t\t\t * that we drop mmap_lock before all hugepage\n-\t\t\t\t * allocations, so if allocation fails, we are\n-\t\t\t\t * guaranteed to break here and report the\n-\t\t\t\t * correct result back to caller.\n-\t\t\t\t */\n-\t\t\t\tgoto breakouterloop_mmap_lock;\n-\t\t\tif (cc-\u003eprogress \u003e= progress_max)\n-\t\t\t\tgoto breakouterloop;\n+\n+\t\t\t*result = collapse_scan_pmd(vma, addr, cc, orders);\n+\t\t\t/* Nothing to collapse here, and the lock is still ours */\n+\t\t\tif (*result != SCAN_SUCCEED) {\n+\t\t\t\tif (cc-\u003eprogress \u003e= progress_max)\n+\t\t\t\t\tgoto breakouterloop;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * A collapse takes its own locks and is slow enough\n+\t\t\t * that a writer should not wait behind it, so give the\n+\t\t\t * lock up. That ends this walk: vma and the mm are\n+\t\t\t * whatever the collapse leaves them.\n+\t\t\t */\n+\t\t\tmmap_read_unlock(mm);\n+\t\t\t*result = collapse_run_pmd(mm, addr, cc);\n+\t\t\tif (*result == SCAN_SUCCEED)\n+\t\t\t\tkhugepaged_pages_collapsed++;\n+\t\t\tgoto breakouterloop_mmap_lock;\n \t\t}\n \t}\n breakouterloop:\n@@ -2999,7 +2990,10 @@ static void khugepaged_do_scan(struct collapse_control *cc)\n \n \tlru_add_drain_all();\n \n-\tcc-\u003eprogress = 0;\n+\tcollapse_control_init(cc);\n+\t/* One policy for the whole pass, so every table is judged the same */\n+\tcollapse_policy_khugepaged(\u0026cc-\u003epolicy);\n+\n \twhile (true) {\n \t\tcond_resched();\n \n@@ -3030,6 +3024,8 @@ static void khugepaged_do_scan(struct collapse_control *cc)\n \t\t\tkhugepaged_alloc_sleep();\n \t\t}\n \t}\n+\n+\tcollapse_control_release(cc);\n }\n \n static bool khugepaged_should_wakeup(void)\n@@ -3168,126 +3164,3 @@ bool current_is_khugepaged(void)\n {\n \treturn kthread_func(current) == khugepaged;\n }\n-\n-static int madvise_collapse_errno(enum scan_result r)\n-{\n-\t/*\n-\t * MADV_COLLAPSE breaks from existing madvise(2) conventions to provide\n-\t * actionable feedback to caller, so they may take an appropriate\n-\t * fallback measure depending on the nature of the failure.\n-\t */\n-\tswitch (r) {\n-\tcase SCAN_ALLOC_HUGE_PAGE_FAIL:\n-\t\treturn -ENOMEM;\n-\tcase SCAN_CGROUP_CHARGE_FAIL:\n-\tcase SCAN_EXCEED_NONE_PTE:\n-\t\treturn -EBUSY;\n-\t/* Resource temporary unavailable - trying again might succeed */\n-\tcase SCAN_PAGE_COUNT:\n-\tcase SCAN_PAGE_LOCK:\n-\tcase SCAN_PAGE_LRU:\n-\tcase SCAN_DEL_PAGE_LRU:\n-\tcase SCAN_PAGE_FILLED:\n-\tcase SCAN_PAGE_HAS_PRIVATE:\n-\tcase SCAN_PAGE_DIRTY_OR_WRITEBACK:\n-\t\treturn -EAGAIN;\n-\t/*\n-\t * Other: Trying again likely not to succeed / error intrinsic to\n-\t * specified memory range. khugepaged likely won't be able to collapse\n-\t * either.\n-\t */\n-\tdefault:\n-\t\treturn -EINVAL;\n-\t}\n-}\n-\n-int madvise_collapse(struct vm_area_struct *vma, unsigned long start,\n-\t\t unsigned long end, bool *lock_dropped)\n-{\n-\tstruct collapse_control *cc;\n-\tstruct mm_struct *mm = vma-\u003evm_mm;\n-\tunsigned long hstart, hend, addr;\n-\tenum scan_result last_fail = SCAN_FAIL;\n-\tint thps = 0;\n-\tbool mmap_unlocked = false;\n-\n-\tBUG_ON(vma-\u003evm_start \u003e start);\n-\tBUG_ON(vma-\u003evm_end \u003c end);\n-\n-\tif (!collapse_possible(vma, vma-\u003evm_flags, TVA_FORCED_COLLAPSE))\n-\t\treturn -EINVAL;\n-\n-\thstart = ALIGN(start, HPAGE_PMD_SIZE);\n-\thend = ALIGN_DOWN(end, HPAGE_PMD_SIZE);\n-\n-\tif (hstart \u003e= hend)\n-\t\treturn 0;\n-\n-\tcc = kmalloc_obj(*cc);\n-\tif (!cc)\n-\t\treturn -ENOMEM;\n-\tcc-\u003eis_khugepaged = false;\n-\tcc-\u003eprogress = 0;\n-\n-\tmmgrab(mm);\n-\tlru_add_drain_all();\n-\n-\tfor (addr = hstart; addr \u003c hend; addr += HPAGE_PMD_SIZE) {\n-\t\tenum scan_result result = SCAN_FAIL;\n-\n-\t\tif (mmap_unlocked) {\n-\t\t\tcond_resched();\n-\t\t\tmmap_read_lock(mm);\n-\t\t\tmmap_unlocked = false;\n-\t\t\t*lock_dropped = true;\n-\t\t\tresult = hugepage_vma_revalidate(mm, addr, false, \u0026vma,\n-\t\t\t\t\t\t\t cc, HPAGE_PMD_ORDER);\n-\t\t\tif (result != SCAN_SUCCEED) {\n-\t\t\t\tlast_fail = result;\n-\t\t\t\tgoto out_nolock;\n-\t\t\t}\n-\n-\t\t\thend = min(hend, vma-\u003evm_end \u0026 HPAGE_PMD_MASK);\n-\t\t}\n-\n-\t\tresult = collapse_single_pmd(addr, vma, \u0026mmap_unlocked, cc);\n-\n-\t\tswitch (result) {\n-\t\tcase SCAN_SUCCEED:\n-\t\tcase SCAN_PMD_MAPPED:\n-\t\t\t++thps;\n-\t\t\tbreak;\n-\t\t/* Whitelisted set of results where continuing OK */\n-\t\tcase SCAN_NO_PTE_TABLE:\n-\t\tcase SCAN_PTE_NON_PRESENT:\n-\t\tcase SCAN_PTE_UFFD:\n-\t\tcase SCAN_LACK_REFERENCED_PAGE:\n-\t\tcase SCAN_PAGE_NULL:\n-\t\tcase SCAN_PAGE_COUNT:\n-\t\tcase SCAN_PAGE_LOCK:\n-\t\tcase SCAN_PAGE_COMPOUND:\n-\t\tcase SCAN_PAGE_LRU:\n-\t\tcase SCAN_DEL_PAGE_LRU:\n-\t\t\tlast_fail = result;\n-\t\t\tbreak;\n-\t\tdefault:\n-\t\t\tlast_fail = result;\n-\t\t\t/* Other error, exit */\n-\t\t\tgoto out_maybelock;\n-\t\t}\n-\t}\n-\n-out_maybelock:\n-\t/* Caller expects us to hold mmap_lock on return */\n-\tif (mmap_unlocked) {\n-\t\t*lock_dropped = true;\n-\t\tmmap_read_lock(mm);\n-\t}\n-out_nolock:\n-\tmmap_assert_locked(mm);\n-\tmmdrop(mm);\n-\tkfree(cc);\n-\n-\treturn thps == ((hend - hstart) \u003e\u003e HPAGE_PMD_SHIFT) ? 0\n-\t\t\t: madvise_collapse_errno(last_fail);\n-}\ndiff --git a/mm/madvise.c b/mm/madvise.c\nindex 963337f93a7a1..f75a9d1399803 100644\n--- a/mm/madvise.c\n+++ b/mm/madvise.c\n@@ -38,6 +38,7 @@\n \n #include \"internal.h\"\n #include \"swap.h\"\n+#include \"collapse.h\"\n \n #define __MADV_SET_ANON_VMA_NAME (-1)\n \n@@ -906,6 +907,171 @@ bool madvise_dontneed_free_valid_vma(struct madvise_behavior *madv_behavior)\n \treturn true;\n }\n \n+#ifdef CONFIG_TRANSPARENT_HUGEPAGE\n+\n+/* MADV_COLLAPSE was asked for explicitly, so it is not held to those */\n+static void collapse_policy_forced(struct collapse_policy *p)\n+{\n+\tp-\u003emax_ptes_none = HPAGE_PMD_NR;\n+\tp-\u003emax_ptes_swap = HPAGE_PMD_NR;\n+\tp-\u003emax_ptes_shared = HPAGE_PMD_NR;\n+\tp-\u003estrict_sub_pmd = false;\n+\tp-\u003eskip_lazyfree = false;\n+\tp-\u003erequire_referenced = false;\n+\tp-\u003einstall_pmd = true;\n+\tp-\u003ewriteback_dirty = true;\n+\tp-\u003egfp = GFP_TRANSHUGE;\n+\tp-\u003etva_type = TVA_FORCED_COLLAPSE;\n+}\n+\n+static int madvise_collapse_errno(enum scan_result r)\n+{\n+\t/*\n+\t * MADV_COLLAPSE breaks from existing madvise(2) conventions to provide\n+\t * actionable feedback to caller, so they may take an appropriate\n+\t * fallback measure depending on the nature of the failure.\n+\t */\n+\tswitch (r) {\n+\tcase SCAN_ALLOC_HUGE_PAGE_FAIL:\n+\t\treturn -ENOMEM;\n+\tcase SCAN_CGROUP_CHARGE_FAIL:\n+\tcase SCAN_EXCEED_NONE_PTE:\n+\t\treturn -EBUSY;\n+\t/* Resource temporary unavailable - trying again might succeed */\n+\tcase SCAN_PAGE_COUNT:\n+\tcase SCAN_PAGE_LOCK:\n+\tcase SCAN_PAGE_LRU:\n+\tcase SCAN_DEL_PAGE_LRU:\n+\tcase SCAN_PAGE_FILLED:\n+\tcase SCAN_PAGE_HAS_PRIVATE:\n+\tcase SCAN_PAGE_DIRTY_OR_WRITEBACK:\n+\t\treturn -EAGAIN;\n+\t/*\n+\t * Other: Trying again likely not to succeed / error intrinsic to\n+\t * specified memory range. khugepaged likely won't be able to collapse\n+\t * either.\n+\t */\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t}\n+}\n+\n+static int madvise_collapse(struct madvise_behavior *madv_behavior)\n+{\n+\tstruct madvise_behavior_range *range = \u0026madv_behavior-\u003erange;\n+\tstruct vm_area_struct *vma = madv_behavior-\u003evma;\n+\tstruct mm_struct *mm = madv_behavior-\u003emm;\n+\tstruct collapse_control *cc;\n+\tunsigned long hstart, hend, addr, orders;\n+\tenum scan_result last_fail = SCAN_FAIL;\n+\tint thps = 0;\n+\n+\tBUG_ON(vma-\u003evm_start \u003e range-\u003estart);\n+\tBUG_ON(vma-\u003evm_end \u003c range-\u003eend);\n+\n+\torders = collapse_possible_orders(vma, vma-\u003evm_flags,\n+\t\t\t\t\t TVA_FORCED_COLLAPSE);\n+\tif (!orders)\n+\t\treturn -EINVAL;\n+\n+\thstart = ALIGN(range-\u003estart, HPAGE_PMD_SIZE);\n+\thend = ALIGN_DOWN(range-\u003eend, HPAGE_PMD_SIZE);\n+\n+\tif (hstart \u003e= hend)\n+\t\treturn 0;\n+\n+\tcc = kmalloc_obj(*cc);\n+\tif (!cc)\n+\t\treturn -ENOMEM;\n+\tcollapse_control_init(cc);\n+\tcollapse_policy_forced(\u0026cc-\u003epolicy);\n+\n+\tlru_add_drain_all();\n+\n+\tfor (addr = hstart; addr \u003c hend; addr += HPAGE_PMD_SIZE) {\n+\t\tstruct vm_area_struct *found;\n+\t\tenum scan_result result;\n+\n+\t\t/*\n+\t\t * A collapse gives the lock up, so the VMA has to be found\n+\t\t * again after one: it can shrink while nothing is held. A scan\n+\t\t * that finds nothing to collapse leaves the lock alone, so a\n+\t\t * range that is already collapsed walks on without relocking.\n+\t\t */\n+\t\tif (!vma) {\n+\t\t\tcond_resched();\n+\t\t\tmmap_read_lock(mm);\n+\t\t\tresult = collapse_vma_revalidate(mm, addr, false, \u0026found,\n+\t\t\t\t\t\t\t cc, HPAGE_PMD_ORDER);\n+\t\t\tif (result != SCAN_SUCCEED) {\n+\t\t\t\tlast_fail = result;\n+\t\t\t\tgoto out_locked;\n+\t\t\t}\n+\t\t\tvma = found;\n+\t\t\thend = min(hend, vma-\u003evm_end \u0026 HPAGE_PMD_MASK);\n+\t\t\torders = collapse_possible_orders(vma, vma-\u003evm_flags,\n+\t\t\t\t\t\t\t cc-\u003epolicy.tva_type);\n+\t\t}\n+\n+\t\tresult = collapse_scan_pmd(vma, addr, cc, orders);\n+\t\t/* Nothing to collapse here, and the lock is still ours */\n+\t\tif (result != SCAN_SUCCEED)\n+\t\t\tgoto tally;\n+\n+\t\t/* The collapse takes its own locks, so give this up */\n+\t\tmmap_read_unlock(mm);\n+\t\tmark_mmap_lock_dropped(madv_behavior);\n+\t\tvma = NULL;\n+\n+\t\tresult = collapse_run_pmd(mm, addr, cc);\n+tally:\n+\t\tswitch (result) {\n+\t\tcase SCAN_SUCCEED:\n+\t\tcase SCAN_PMD_MAPPED:\n+\t\t\t++thps;\n+\t\t\tbreak;\n+\t\t/* Whitelisted set of results where continuing OK */\n+\t\tcase SCAN_NO_PTE_TABLE:\n+\t\tcase SCAN_PTE_NON_PRESENT:\n+\t\tcase SCAN_PTE_UFFD:\n+\t\tcase SCAN_LACK_REFERENCED_PAGE:\n+\t\tcase SCAN_PAGE_NULL:\n+\t\tcase SCAN_PAGE_COUNT:\n+\t\tcase SCAN_PAGE_LOCK:\n+\t\tcase SCAN_PAGE_COMPOUND:\n+\t\tcase SCAN_PAGE_LRU:\n+\t\tcase SCAN_DEL_PAGE_LRU:\n+\t\t\tlast_fail = result;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tlast_fail = result;\n+\t\t\t/* Other error, exit */\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+out:\n+\t/* Caller expects us to hold mmap_lock on return */\n+\tif (!vma)\n+\t\tmmap_read_lock(mm);\n+out_locked:\n+\tmmap_assert_locked(mm);\n+\tcollapse_control_release(cc);\n+\tkfree(cc);\n+\n+\treturn thps == ((hend - hstart) \u003e\u003e HPAGE_PMD_SHIFT) ? 0\n+\t\t\t: madvise_collapse_errno(last_fail);\n+}\n+\n+#else\t/* CONFIG_TRANSPARENT_HUGEPAGE */\n+\n+static int madvise_collapse(struct madvise_behavior *madv_behavior)\n+{\n+\treturn -EINVAL;\n+}\n+\n+#endif\t/* CONFIG_TRANSPARENT_HUGEPAGE */\n+\n static long madvise_dontneed_free(struct madvise_behavior *madv_behavior)\n {\n \tstruct mm_struct *mm = madv_behavior-\u003emm;\n@@ -1373,8 +1539,7 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)\n \tcase MADV_DONTNEED_LOCKED:\n \t\treturn madvise_dontneed_free(madv_behavior);\n \tcase MADV_COLLAPSE:\n-\t\treturn madvise_collapse(vma, range-\u003estart, range-\u003eend,\n-\t\t\t\u0026madv_behavior-\u003elock_dropped);\n+\t\treturn madvise_collapse(madv_behavior);\n \tcase MADV_GUARD_INSTALL:\n \t\treturn madvise_guard_install(madv_behavior);\n \tcase MADV_GUARD_REMOVE:\n"
} |
| 2/1 | 2026/09/10 13:43 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE"
],
"FocusSymbols": [
"collapse_scan_pmd",
"collapse_run_pmd",
"collapse_scan_anon_pmd",
"madvise_collapse",
"collapse_possible_orders",
"collapse_vma_revalidate",
"collapse_control_release",
"mthp_collapse",
"collapse_scan_mm_slot",
"khugepaged_do_scan"
],
"Reasoning": "The patch refactors the Transparent Hugepage (THP) collapse engine in the memory management subsystem (mm/). It splits the collapse logic into distinct scan and run phases (collapse_scan_pmd and collapse_run_pmd), introduces collapse_policy to generalize khugepaged and madvise behavior, restructures mmap_lock release/revalidation semantics, moves madvise_collapse into mm/madvise.c, and adds new WARN_ON_ONCE assertions. This code is reachable from user space via the madvise(..., MADV_COLLAPSE) syscall and background khugepaged scanning, making it well-suited for fuzzing.",
"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 7eeddd98adba18e6e64be0360b1e4fd5fb6af557
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Sep 10 13:43:21 2026 +0000
syz-cluster: applied patch under review
diff --git a/MAINTAINERS b/MAINTAINERS
index 35262aa3e67ef..36315972156b5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17434,6 +17434,7 @@ F: Documentation/admin-guide/mm/transhuge.rst
F: include/linux/huge_mm.h
F: include/linux/khugepaged.h
F: include/trace/events/huge_memory.h
+F: mm/collapse.h
F: mm/huge_memory.c
F: mm/khugepaged.c
F: mm/mm_slot.h
diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index c745f7ad22987..8ca0fa3be2acb 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -510,8 +510,6 @@ change_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,
int hugepage_madvise(struct vm_area_struct *vma, vm_flags_t *vm_flags,
int advice);
-int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
- unsigned long end, bool *lock_dropped);
void vma_adjust_trans_huge(struct vm_area_struct *vma, unsigned long start,
unsigned long end, struct vm_area_struct *next);
spinlock_t *__pmd_trans_huge_lock(pmd_t *pmd, struct vm_area_struct *vma);
@@ -715,13 +713,6 @@ static inline int hugepage_madvise(struct vm_area_struct *vma,
return -EINVAL;
}
-static inline int madvise_collapse(struct vm_area_struct *vma,
- unsigned long start,
- unsigned long end, bool *lock_dropped)
-{
- return -EINVAL;
-}
-
static inline void vma_adjust_trans_huge(struct vm_area_struct *vma,
unsigned long start,
unsigned long end,
diff --git a/mm/collapse.h b/mm/collapse.h
new file mode 100644
index 0000000000000..1ebbbf63fb256
--- /dev/null
+++ b/mm/collapse.h
@@ -0,0 +1,153 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __MM_COLLAPSE_H
+#define __MM_COLLAPSE_H
+
+#include <linux/mm.h>
+#include <linux/nodemask.h>
+#include <linux/pgtable.h>
+#include <linux/types.h>
+
+#define COLLAPSE_MAX_PTES_LIMIT (HPAGE_PMD_NR - 1)
+#define COLLAPSE_MIN_MTHP_ORDER 2
+
+enum scan_result {
+ SCAN_FAIL,
+ SCAN_SUCCEED,
+ SCAN_NO_PTE_TABLE,
+ SCAN_PMD_MAPPED,
+ SCAN_EXCEED_NONE_PTE,
+ SCAN_EXCEED_SWAP_PTE,
+ SCAN_EXCEED_SHARED_PTE,
+ SCAN_PTE_NON_PRESENT,
+ SCAN_PTE_UFFD,
+ SCAN_PTE_MAPPED_HUGEPAGE,
+ SCAN_LACK_REFERENCED_PAGE,
+ SCAN_PAGE_NULL,
+ SCAN_SCAN_ABORT,
+ SCAN_PAGE_COUNT,
+ SCAN_PAGE_LRU,
+ SCAN_PAGE_LOCK,
+ SCAN_PAGE_ANON,
+ SCAN_PAGE_LAZYFREE,
+ SCAN_PAGE_COMPOUND,
+ SCAN_ANY_PROCESS,
+ SCAN_VMA_NULL,
+ SCAN_VMA_CHECK,
+ SCAN_ADDRESS_RANGE,
+ SCAN_DEL_PAGE_LRU,
+ SCAN_ALLOC_HUGE_PAGE_FAIL,
+ SCAN_CGROUP_CHARGE_FAIL,
+ SCAN_TRUNCATED,
+ SCAN_PAGE_HAS_PRIVATE,
+ SCAN_STORE_FAILED,
+ SCAN_COPY_MC,
+ SCAN_PAGE_FILLED,
+ SCAN_PAGE_DIRTY_OR_WRITEBACK,
+};
+
+/* What a collapse is allowed to do, decided by the caller that asks for it */
+struct collapse_policy {
+ /* Limits, stated per PMD; HPAGE_PMD_NR means "no limit" */
+ unsigned int max_ptes_none;
+ unsigned int max_ptes_swap;
+ unsigned int max_ptes_shared;
+
+ /* Take no swapped-out or shared PTE into a sub-PMD collapse */
+ bool strict_sub_pmd;
+
+ /* Leave clean lazyfree folios to reclaim rather than collapse them */
+ bool skip_lazyfree;
+
+ /* Refuse a range with no sign of use */
+ bool require_referenced;
+
+ /* Map the PMD over a file collapse instead of leaving it to a fault */
+ bool install_pmd;
+
+ /* Write dirty pages back and retry once instead of refusing them */
+ bool writeback_dirty;
+
+ /* How hard to try for a destination folio */
+ gfp_t gfp;
+
+ /* Which VMAs are eligible, as thp_vma_allowable_orders() spells it */
+ enum tva_type tva_type;
+};
+
+struct collapse_control {
+ struct collapse_policy policy;
+
+ /* Num pages scanned per node */
+ u32 node_load[MAX_NUMNODES];
+
+ /* Num pages scanned (see khugepaged_pages_to_scan) */
+ unsigned int progress;
+
+ /* nodemask for allocation fallback */
+ nodemask_t alloc_nmask;
+
+ /* Each bit marks a PTE the scan accepted as a collapse source */
+ DECLARE_BITMAP(eligible_ptes, MAX_PTRS_PER_PTE);
+
+ /*
+ * What a scan found and the run after it needs. Live only between the
+ * two, and read by nobody else.
+ *
+ * The file side takes a reference while it still has the VMA, since a
+ * file collapse works on the page cache and never sees one; the run is
+ * what gives it back. A scan that found the PMD folio already in the
+ * cache leaves only the PTE table to retract.
+ */
+ unsigned long scan_orders;
+ int scan_referenced;
+ int scan_unmapped;
+ struct file *scan_file;
+ pgoff_t scan_pgoff;
+ bool scan_retract_only;
+};
+
+/* Which orders a VMA may collapse to, zero when it may not collapse at all */
+unsigned long collapse_possible_orders(struct vm_area_struct *vma,
+ vm_flags_t vm_flags, enum tva_type tva_flags);
+
+/*
+ * A caller states what it allows in cc->policy and then hands over one PTE
+ * table's worth of a VMA at a time:
+ *
+ * collapse_control_init(cc) once, before the first table
+ * collapse_scan_pmd(vma, addr, ...) per table
+ * collapse_run_pmd(mm, addr, cc) when a scan found work
+ * collapse_control_release(cc) once, when done with the control
+ *
+ * The caller holds mmap_lock for reading over the scan and passes an address
+ * within @vma, aligned to the PTE table the scan is to judge.
+ *
+ * The scan returns with that lock still held. It only reads, and almost every
+ * table it is offered has nothing to collapse, so a caller walks a whole VMA
+ * under the one lock it took to get there. SCAN_SUCCEED means there is
+ * something to collapse; anything else is why there is not.
+ *
+ * The run is called without the lock and returns without it, taking what it
+ * needs in between: what it does -- allocate, isolate, copy, flush -- is slow
+ * enough that a writer would wait behind it. The caller gives the lock up
+ * first, and with it @vma and anything derived under it, so a caller carrying
+ * on has to look up again with collapse_vma_revalidate(). The run revalidates
+ * for itself rather than trusting what the scan saw.
+ *
+ * A scan that found something has to be run: the file side takes a reference on
+ * the file while it still has the VMA to take it from, and the run is what
+ * gives it back.
+ */
+void collapse_control_init(struct collapse_control *cc);
+void collapse_control_release(struct collapse_control *cc);
+enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,
+ unsigned long addr, struct collapse_control *cc,
+ unsigned long orders);
+enum scan_result collapse_run_pmd(struct mm_struct *mm, unsigned long addr,
+ struct collapse_control *cc);
+enum scan_result collapse_vma_revalidate(struct mm_struct *mm,
+ unsigned long address, bool expect_anon,
+ struct vm_area_struct **vmap, struct collapse_control *cc,
+ unsigned int order);
+
+#endif /* __MM_COLLAPSE_H */
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index e13d233b9967c..1deb74cf28af0 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -26,44 +26,10 @@
#include <linux/cleanup.h>
#include <asm/tlb.h>
+#include "collapse.h"
#include "internal.h"
-#include "page_alloc.h"
#include "mm_slot.h"
-
-enum scan_result {
- SCAN_FAIL,
- SCAN_SUCCEED,
- SCAN_NO_PTE_TABLE,
- SCAN_PMD_MAPPED,
- SCAN_EXCEED_NONE_PTE,
- SCAN_EXCEED_SWAP_PTE,
- SCAN_EXCEED_SHARED_PTE,
- SCAN_PTE_NON_PRESENT,
- SCAN_PTE_UFFD,
- SCAN_PTE_MAPPED_HUGEPAGE,
- SCAN_LACK_REFERENCED_PAGE,
- SCAN_PAGE_NULL,
- SCAN_SCAN_ABORT,
- SCAN_PAGE_COUNT,
- SCAN_PAGE_LRU,
- SCAN_PAGE_LOCK,
- SCAN_PAGE_ANON,
- SCAN_PAGE_LAZYFREE,
- SCAN_PAGE_COMPOUND,
- SCAN_ANY_PROCESS,
- SCAN_VMA_NULL,
- SCAN_VMA_CHECK,
- SCAN_ADDRESS_RANGE,
- SCAN_DEL_PAGE_LRU,
- SCAN_ALLOC_HUGE_PAGE_FAIL,
- SCAN_CGROUP_CHARGE_FAIL,
- SCAN_TRUNCATED,
- SCAN_PAGE_HAS_PRIVATE,
- SCAN_STORE_FAILED,
- SCAN_COPY_MC,
- SCAN_PAGE_FILLED,
- SCAN_PAGE_DIRTY_OR_WRITEBACK,
-};
+#include "page_alloc.h"
#define CREATE_TRACE_POINTS
#include <trace/events/huge_memory.h>
@@ -91,7 +57,6 @@ static DECLARE_WAIT_QUEUE_HEAD(khugepaged_wait);
*
* Note that these are only respected if collapse was initiated by khugepaged.
*/
-#define KHUGEPAGED_MAX_PTES_LIMIT (HPAGE_PMD_NR - 1)
unsigned int khugepaged_max_ptes_none __read_mostly;
static unsigned int khugepaged_max_ptes_swap __read_mostly;
static unsigned int khugepaged_max_ptes_shared __read_mostly;
@@ -101,24 +66,6 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);
static struct kmem_cache *mm_slot_cache __ro_after_init;
-#define KHUGEPAGED_MIN_MTHP_ORDER 2
-
-struct collapse_control {
- bool is_khugepaged;
-
- /* Num pages scanned per node */
- u32 node_load[MAX_NUMNODES];
-
- /* Num pages scanned (see khugepaged_pages_to_scan) */
- unsigned int progress;
-
- /* nodemask for allocation fallback */
- nodemask_t alloc_nmask;
-
- /* Each bit represents a single occupied (!none/zero) page. */
- DECLARE_BITMAP(mthp_present_ptes, MAX_PTRS_PER_PTE);
-};
-
/**
* struct khugepaged_scan - cursor for scanning
* @mm_head: the head of the mm list to scan
@@ -267,7 +214,7 @@ static ssize_t max_ptes_none_store(struct kobject *kobj,
unsigned long max_ptes_none;
err = kstrtoul(buf, 10, &max_ptes_none);
- if (err || max_ptes_none > KHUGEPAGED_MAX_PTES_LIMIT)
+ if (err || max_ptes_none > COLLAPSE_MAX_PTES_LIMIT)
return -EINVAL;
khugepaged_max_ptes_none = max_ptes_none;
@@ -292,7 +239,7 @@ static ssize_t max_ptes_swap_store(struct kobject *kobj,
unsigned long max_ptes_swap;
err = kstrtoul(buf, 10, &max_ptes_swap);
- if (err || max_ptes_swap > KHUGEPAGED_MAX_PTES_LIMIT)
+ if (err || max_ptes_swap > COLLAPSE_MAX_PTES_LIMIT)
return -EINVAL;
khugepaged_max_ptes_swap = max_ptes_swap;
@@ -318,7 +265,7 @@ static ssize_t max_ptes_shared_store(struct kobject *kobj,
unsigned long max_ptes_shared;
err = kstrtoul(buf, 10, &max_ptes_shared);
- if (err || max_ptes_shared > KHUGEPAGED_MAX_PTES_LIMIT)
+ if (err || max_ptes_shared > COLLAPSE_MAX_PTES_LIMIT)
return -EINVAL;
khugepaged_max_ptes_shared = max_ptes_shared;
@@ -367,30 +314,27 @@ static bool pte_none_or_zero(pte_t pte)
static unsigned int collapse_max_ptes_none(struct collapse_control *cc,
struct vm_area_struct *vma, unsigned int order)
{
- const unsigned int max_ptes_none = khugepaged_max_ptes_none;
+ const unsigned int max_ptes_none = cc->policy.max_ptes_none;
if (vma && userfaultfd_armed(vma))
return 0;
- /* for MADV_COLLAPSE, allow any empty/shared zeropage PTEs */
- if (!cc->is_khugepaged)
- return HPAGE_PMD_NR;
- /* for PMD collapse, respect the user defined maximum */
- if (is_pmd_order(order))
+ /* The limit as given, at the PMD order and wherever it is not capped */
+ if (is_pmd_order(order) || !cc->policy.strict_sub_pmd)
return max_ptes_none;
/*
- * for mTHP collapse with the sysctl value set to KHUGEPAGED_MAX_PTES_LIMIT,
+ * for mTHP collapse with the sysctl value set to COLLAPSE_MAX_PTES_LIMIT,
* scale the maximum number of PTEs to the order of the collapse.
*/
- if (max_ptes_none == KHUGEPAGED_MAX_PTES_LIMIT)
+ if (max_ptes_none == COLLAPSE_MAX_PTES_LIMIT)
return (1 << order) - 1;
/*
- * For mTHP collapse of values other than 0 or KHUGEPAGED_MAX_PTES_LIMIT,
+ * For mTHP collapse of values other than 0 or COLLAPSE_MAX_PTES_LIMIT,
* emit a warning and return 0.
*/
if (max_ptes_none)
pr_warn_once("mTHP collapse does not support max_ptes_none"
" values other than 0 or %u, defaulting to 0.\n",
- KHUGEPAGED_MAX_PTES_LIMIT);
+ COLLAPSE_MAX_PTES_LIMIT);
return 0;
}
@@ -407,19 +351,12 @@ static unsigned int collapse_max_ptes_shared(struct collapse_control *cc,
unsigned int order)
{
/*
- * For MADV_COLLAPSE, do not restrict the number of PTEs that map shared
- * anonymous pages.
- */
- if (!cc->is_khugepaged)
- return HPAGE_PMD_NR;
- /*
- * for mTHP collapse do not allow collapsing anonymous memory pages that
- * are shared between processes.
+ * A sub-PMD window held to the strict rule takes no shared page at all:
+ * an mTHP is not worth the CoW-breaking.
*/
- if (!is_pmd_order(order))
+ if (!is_pmd_order(order) && cc->policy.strict_sub_pmd)
return 0;
- /* for PMD collapse, respect the user defined maximum */
- return khugepaged_max_ptes_shared;
+ return cc->policy.max_ptes_shared;
}
/**
@@ -435,16 +372,12 @@ static unsigned int collapse_max_ptes_swap(struct collapse_control *cc,
unsigned int order)
{
/*
- * For MADV_COLLAPSE, do not restrict the number PTEs entries or
- * pagecache entries that are non-present.
+ * A sub-PMD window held to the strict rule takes nothing non-present:
+ * reading pages back to build an mTHP is not worth the latency.
*/
- if (!cc->is_khugepaged)
- return HPAGE_PMD_NR;
- /* for mTHP collapse do not allow any non-present PTEs or pagecache entries */
- if (!is_pmd_order(order))
+ if (!is_pmd_order(order) && cc->policy.strict_sub_pmd)
return 0;
- /* for PMD collapse, respect the user defined maximum */
- return khugepaged_max_ptes_swap;
+ return cc->policy.max_ptes_swap;
}
int hugepage_madvise(struct vm_area_struct *vma,
@@ -476,7 +409,7 @@ int __init khugepaged_init(void)
return -ENOMEM;
khugepaged_pages_to_scan = HPAGE_PMD_NR * 8;
- khugepaged_max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
+ khugepaged_max_ptes_none = COLLAPSE_MAX_PTES_LIMIT;
khugepaged_max_ptes_swap = HPAGE_PMD_NR / 8;
khugepaged_max_ptes_shared = HPAGE_PMD_NR / 2;
@@ -565,7 +498,7 @@ void __khugepaged_enter(struct mm_struct *mm)
* Check what orders are possible based on the vma and collapse type.
* This is used to determine if mTHP collapse is a viable option.
*/
-static unsigned long collapse_possible_orders(struct vm_area_struct *vma,
+unsigned long collapse_possible_orders(struct vm_area_struct *vma,
vm_flags_t vm_flags, enum tva_type tva_flags)
{
unsigned long orders;
@@ -579,17 +512,11 @@ static unsigned long collapse_possible_orders(struct vm_area_struct *vma,
return thp_vma_allowable_orders(vma, vm_flags, tva_flags, orders);
}
-static bool collapse_possible(struct vm_area_struct *vma,
- vm_flags_t vm_flags, enum tva_type tva_flags)
-{
- return collapse_possible_orders(vma, vm_flags, tva_flags);
-}
-
void khugepaged_enter_vma(struct vm_area_struct *vma,
vm_flags_t vm_flags)
{
- if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) && hugepage_enabled()
- && collapse_possible(vma, vm_flags, TVA_KHUGEPAGED))
+ if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) && hugepage_enabled() &&
+ collapse_possible_orders(vma, vm_flags, TVA_KHUGEPAGED))
__khugepaged_enter(vma->vm_mm);
}
@@ -623,11 +550,11 @@ void __khugepaged_exit(struct mm_struct *mm)
}
}
-static void collapse_control_init_scan(struct collapse_control *cc)
+static void collapse_scan_reset(struct collapse_control *cc)
{
memset(cc->node_load, 0, sizeof(cc->node_load));
nodes_clear(cc->alloc_nmask);
- bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
+ bitmap_zero(cc->eligible_ptes, MAX_PTRS_PER_PTE);
}
static void release_pte_folio(struct folio *folio)
@@ -731,7 +658,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
* If the vma has the VM_DROPPABLE flag, the collapse will
* preserve the lazyfree property without needing to skip.
*/
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
+ if (cc->policy.skip_lazyfree && !(vma->vm_flags & VM_DROPPABLE) &&
folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
result = SCAN_PAGE_LAZYFREE;
goto out;
@@ -820,12 +747,12 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
if (folio_test_large(folio))
list_add_tail(&folio->lru, compound_pagelist);
next:
- if (cc->is_khugepaged &&
+ if (cc->policy.require_referenced &&
folio_pte_referenced(folio, vma, addr, pteval))
referenced++;
}
- if (unlikely(cc->is_khugepaged && !referenced)) {
+ if (unlikely(cc->policy.require_referenced && !referenced)) {
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
@@ -991,9 +918,7 @@ static void khugepaged_alloc_sleep(void)
remove_wait_queue(&khugepaged_wait, &wait);
}
-static struct collapse_control khugepaged_collapse_control = {
- .is_khugepaged = true,
-};
+static struct collapse_control khugepaged_collapse_control;
static bool collapse_scan_abort(int nid, struct collapse_control *cc)
{
@@ -1029,6 +954,21 @@ static inline gfp_t alloc_hugepage_khugepaged_gfpmask(void)
return khugepaged_defrag() ? GFP_TRANSHUGE : GFP_TRANSHUGE_LIGHT;
}
+/* khugepaged collapses on its own initiative, so it obeys its own settings */
+static void collapse_policy_khugepaged(struct collapse_policy *p)
+{
+ p->max_ptes_none = READ_ONCE(khugepaged_max_ptes_none);
+ p->max_ptes_swap = READ_ONCE(khugepaged_max_ptes_swap);
+ p->max_ptes_shared = READ_ONCE(khugepaged_max_ptes_shared);
+ p->strict_sub_pmd = true;
+ p->skip_lazyfree = true;
+ p->require_referenced = true;
+ p->install_pmd = false;
+ p->writeback_dirty = false;
+ p->gfp = alloc_hugepage_khugepaged_gfpmask();
+ p->tva_type = TVA_KHUGEPAGED;
+}
+
#ifdef CONFIG_NUMA
static int collapse_find_target_node(struct collapse_control *cc)
{
@@ -1061,13 +1001,12 @@ static int collapse_find_target_node(struct collapse_control *cc)
* Returns enum scan_result value.
*/
-static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned long address,
+enum scan_result collapse_vma_revalidate(struct mm_struct *mm, unsigned long address,
bool expect_anon, struct vm_area_struct **vmap,
struct collapse_control *cc, unsigned int order)
{
struct vm_area_struct *vma;
- enum tva_type type = cc->is_khugepaged ? TVA_KHUGEPAGED :
- TVA_FORCED_COLLAPSE;
+ enum tva_type type = cc->policy.tva_type;
if (unlikely(collapse_test_exit_or_disable(mm)))
return SCAN_ANY_PROCESS;
@@ -1250,8 +1189,7 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_struct *mm,
struct collapse_control *cc, unsigned int order)
{
- gfp_t gfp = (cc->is_khugepaged ? alloc_hugepage_khugepaged_gfpmask() :
- GFP_TRANSHUGE);
+ gfp_t gfp = cc->policy.gfp;
int node = collapse_find_target_node(cc);
struct folio *folio;
@@ -1311,7 +1249,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
}
mmap_read_lock(mm);
- result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
+ result = collapse_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
&vma, cc, order);
if (result != SCAN_SUCCEED) {
mmap_read_unlock(mm);
@@ -1346,7 +1284,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
* mmap_lock.
*/
mmap_write_lock(mm);
- result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
+ result = collapse_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
&vma, cc, order);
if (result != SCAN_SUCCEED)
goto out_up_write;
@@ -1482,15 +1420,15 @@ static unsigned int max_order_from_offset(unsigned int offset)
* mthp_collapse() consumes the bitmap that is generated during
* collapse_scan_pmd() to determine what regions and mTHP orders fit best.
*
- * Each bit in cc->mthp_present_ptes represents a single occupied (!none/zero)
- * page. We start at the PMD order and check if it is eligible for collapse;
+ * Each bit in cc->eligible_ptes marks a PTE the scan accepted as a collapse
+ * source. We start at the PMD order and check if it is eligible for collapse;
* if not, we check the left and right halves of the PTE page table we are
* examining at a lower order.
*
- * For each of these, we determine how many PTE entries are occupied in the
- * range of PTE entries we propose to collapse, then we compare this to a
- * threshold number of PTE entries which would need to be occupied for a
- * collapse to be permitted at that order (accounting for max_ptes_none).
+ * For each of these, we count the eligible PTEs in the range we propose to
+ * collapse, then we compare this to the number of eligible PTEs the range
+ * would need for a collapse to be permitted at that order (accounting for
+ * max_ptes_none).
*
* If a collapse is permitted, we attempt to collapse the PTE range into a
* mTHP.
@@ -1499,7 +1437,7 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
unsigned long address, int referenced, int unmapped,
struct collapse_control *cc, unsigned long enabled_orders)
{
- unsigned int nr_occupied_ptes, nr_ptes, max_ptes_none;
+ unsigned int nr_eligible_ptes, nr_ptes, max_ptes_none;
enum scan_result last_result = SCAN_FAIL;
int collapsed = 0;
bool alloc_failed = false;
@@ -1514,18 +1452,18 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
goto next_order;
max_ptes_none = collapse_max_ptes_none(cc, NULL, order);
- nr_occupied_ptes = bitmap_weight_from(cc->mthp_present_ptes, offset,
+ nr_eligible_ptes = bitmap_weight_from(cc->eligible_ptes, offset,
offset + nr_ptes);
/*
* Swap PTEs accepted during the scan are counted in @unmapped,
- * not in the present-PTE bitmap. Account them for the PMD-order
+ * not in cc->eligible_ptes. Account them for the PMD-order
* candidate.
*/
if (is_pmd_order(order))
- nr_occupied_ptes += unmapped;
+ nr_eligible_ptes += unmapped;
- if (nr_occupied_ptes >= nr_ptes - max_ptes_none) {
+ if (nr_eligible_ptes >= nr_ptes - max_ptes_none) {
enum scan_result ret;
collapse_address = address + offset * PAGE_SIZE;
@@ -1571,8 +1509,8 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
* any smaller order enabled. When at the smallest order
* we must always move to the next offset.
*/
- if (order > KHUGEPAGED_MIN_MTHP_ORDER &&
- (enabled_orders & GENMASK(order - 1, 0))) {
+ if (order > COLLAPSE_MIN_MTHP_ORDER &&
+ (enabled_orders & GENMASK(order - 1, 0))) {
order--;
continue;
}
@@ -1597,14 +1535,14 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
return last_result;
}
-static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
- struct vm_area_struct *vma, unsigned long start_addr,
- bool *lock_dropped, struct collapse_control *cc)
+static enum scan_result collapse_scan_anon_pmd(struct vm_area_struct *vma,
+ unsigned long start_addr, struct collapse_control *cc,
+ unsigned long enabled_orders)
{
const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
- enum tva_type tva_flags = cc->is_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;
+ struct mm_struct *mm = vma->vm_mm;
pmd_t *pmd;
pte_t *pte, *_pte, pteval;
int i;
@@ -1614,7 +1552,6 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
struct folio *folio = NULL;
unsigned long failed_pfn = -1;
unsigned long addr;
- unsigned long enabled_orders;
spinlock_t *ptl;
int node = NUMA_NO_NODE, unmapped = 0;
@@ -1626,9 +1563,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
goto out;
}
- collapse_control_init_scan(cc);
-
- enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags);
+ collapse_scan_reset(cc);
/*
* If PMD is the only enabled order, enforce max_ptes_none, otherwise
@@ -1636,7 +1571,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
* is then checked again in mthp_collapse() for each attempted order.
*/
if (enabled_orders != BIT(HPAGE_PMD_ORDER))
- max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
+ max_ptes_none = COLLAPSE_MAX_PTES_LIMIT;
pte = pte_offset_map_lock(mm, pmd, start_addr, &ptl);
if (!pte) {
@@ -1704,7 +1639,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
* If the vma has the VM_DROPPABLE flag, the collapse will
* preserve the lazyfree property without needing to skip.
*/
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
+ if (cc->policy.skip_lazyfree && !(vma->vm_flags & VM_DROPPABLE) &&
folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
result = SCAN_PAGE_LAZYFREE;
failed_pfn = folio_pfn(folio);
@@ -1731,8 +1666,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
}
}
- /* Set bit for occupied pages */
- __set_bit(i, cc->mthp_present_ptes);
+ /* The scan accepted this PTE as a collapse source */
+ __set_bit(i, cc->eligible_ptes);
/*
* Record which node the original page is from and save this
* information to cc->node_load[].
@@ -1770,13 +1705,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
goto out_unmap;
}
- if (cc->is_khugepaged &&
+ if (cc->policy.require_referenced &&
folio_pte_referenced(folio, vma, addr, pteval))
referenced++;
}
- if (cc->is_khugepaged &&
- (!referenced ||
- (unmapped && referenced < HPAGE_PMD_NR / 2))) {
+ if (cc->policy.require_referenced &&
+ (!referenced ||
+ (unmapped && referenced < HPAGE_PMD_NR / 2))) {
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
@@ -1784,12 +1719,9 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
out_unmap:
pte_unmap_unlock(pte, ptl);
if (result == SCAN_SUCCEED) {
- /* collapse_huge_page() expects the lock to be dropped before calling */
- mmap_read_unlock(mm);
- result = mthp_collapse(mm, start_addr, referenced,
- unmapped, cc, enabled_orders);
- /* mmap_lock was released above, set lock_dropped */
- *lock_dropped = true;
+ cc->scan_orders = enabled_orders;
+ cc->scan_referenced = referenced;
+ cc->scan_unmapped = unmapped;
}
out:
trace_mm_khugepaged_scan_pmd(mm, failed_pfn, referenced,
@@ -2635,11 +2567,11 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
xas_unlock_irq(&xas);
/*
- * Remove pte page tables, so we can re-fault the page as huge.
- * If MADV_COLLAPSE, adjust result to call try_collapse_pte_mapped_thp().
+ * Remove pte page tables, so we can re-fault the page as huge. A
+ * caller that wants the PMD mapped now is told to go and do that.
*/
retract_page_tables(mapping, start);
- if (cc && !cc->is_khugepaged)
+ if (cc->policy.install_pmd)
result = SCAN_PTE_MAPPED_HUGEPAGE;
folio_unlock(new_folio);
@@ -2704,7 +2636,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
present = 0;
swap = 0;
- collapse_control_init_scan(cc);
+ collapse_scan_reset(cc);
rcu_read_lock();
xas_for_each(&xas, folio, start + HPAGE_PMD_NR - 1) {
if (xas_retry(&xas, folio))
@@ -2786,53 +2718,99 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
else
cc->progress += HPAGE_PMD_NR;
- if (result == SCAN_SUCCEED) {
- if (present < HPAGE_PMD_NR - max_ptes_none) {
- result = SCAN_EXCEED_NONE_PTE;
- count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
- } else {
- result = collapse_file(mm, addr, file, start, cc);
- }
+ if (result == SCAN_SUCCEED && present < HPAGE_PMD_NR - max_ptes_none) {
+ result = SCAN_EXCEED_NONE_PTE;
+ count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
}
- trace_mm_khugepaged_scan_file(mm, failed_pfn, file, present, swap, result);
+ trace_mm_khugepaged_scan_file(mm, failed_pfn, file, present, swap,
+ result);
return result;
}
-/*
- * Try to collapse a single PMD starting at a PMD aligned addr, and return
- * the results.
- */
-static enum scan_result collapse_single_pmd(unsigned long addr,
- struct vm_area_struct *vma, bool *lock_dropped,
- struct collapse_control *cc)
+void collapse_control_init(struct collapse_control *cc)
+{
+ cc->progress = 0;
+ cc->scan_file = NULL;
+}
+
+void collapse_control_release(struct collapse_control *cc)
+{
+ /* A scan that took a file reference should have been run */
+ if (WARN_ON_ONCE(cc->scan_file)) {
+ fput(cc->scan_file);
+ cc->scan_file = NULL;
+ }
+}
+
+enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,
+ unsigned long addr, struct collapse_control *cc,
+ unsigned long orders)
{
- struct mm_struct *mm = vma->vm_mm;
- bool triggered_wb = false;
enum scan_result result;
- struct file *file;
pgoff_t pgoff;
- mmap_assert_locked(mm);
-
- if (vma_is_anonymous(vma)) {
- result = collapse_scan_pmd(mm, vma, addr, lock_dropped, cc);
- goto end;
+ mmap_assert_locked(vma->vm_mm);
+ /* Whatever the last scan found has to have been run by now */
+ if (WARN_ON_ONCE(cc->scan_file)) {
+ fput(cc->scan_file);
+ cc->scan_file = NULL;
}
- file = get_file(vma->vm_file);
- pgoff = linear_page_index(vma, addr);
+ if (vma_is_anonymous(vma))
+ return collapse_scan_anon_pmd(vma, addr, cc, orders);
- mmap_read_unlock(mm);
- *lock_dropped = true;
-retry:
- result = collapse_scan_file(mm, addr, file, pgoff, cc);
+ pgoff = linear_page_index(vma, addr);
+ result = collapse_scan_file(vma->vm_mm, addr, vma->vm_file, pgoff, cc);
+ switch (result) {
+ case SCAN_SUCCEED:
+ cc->scan_retract_only = false;
+ break;
+ case SCAN_PTE_MAPPED_HUGEPAGE:
+ /*
+ * The page cache already holds the PMD folio; what is left is
+ * to retract the PTE table, which is the run's job.
+ */
+ cc->scan_retract_only = true;
+ result = SCAN_SUCCEED;
+ break;
+ default:
+ return result;
+ }
/*
- * For MADV_COLLAPSE, when encountering dirty pages, try to writeback,
- * then retry the collapse one time.
+ * A file collapse works on the page cache and never sees a VMA, so take
+ * what it needs from this one while it is still here.
*/
- if (!cc->is_khugepaged && result == SCAN_PAGE_DIRTY_OR_WRITEBACK &&
+ cc->scan_file = get_file(vma->vm_file);
+ cc->scan_pgoff = pgoff;
+ return result;
+}
+
+enum scan_result collapse_run_pmd(struct mm_struct *mm, unsigned long addr,
+ struct collapse_control *cc)
+{
+ struct file *file = cc->scan_file;
+ bool triggered_wb = false;
+ enum scan_result result;
+ pgoff_t pgoff;
+
+ if (!file)
+ return mthp_collapse(mm, addr, cc->scan_referenced,
+ cc->scan_unmapped, cc, cc->scan_orders);
+
+ cc->scan_file = NULL;
+ pgoff = cc->scan_pgoff;
+
+ if (cc->scan_retract_only) {
+ result = SCAN_PTE_MAPPED_HUGEPAGE;
+ goto retract;
+ }
+retry:
+ result = collapse_file(mm, addr, file, pgoff, cc);
+
+ /* Dirty pages are worth a writeback and one more try, if asked for */
+ if (cc->policy.writeback_dirty && result == SCAN_PAGE_DIRTY_OR_WRITEBACK &&
!triggered_wb && mapping_can_writeback(file->f_mapping)) {
const loff_t lstart = (loff_t)pgoff << PAGE_SHIFT;
const loff_t lend = lstart + HPAGE_PMD_SIZE - 1;
@@ -2841,22 +2819,24 @@ static enum scan_result collapse_single_pmd(unsigned long addr,
triggered_wb = true;
goto retry;
}
+retract:
fput(file);
+ /*
+ * A PMD folio is in the page cache, whether the collapse just put it
+ * there or found it: retract the PTE table, and map the PMD if asked.
+ */
if (result == SCAN_PTE_MAPPED_HUGEPAGE) {
mmap_read_lock(mm);
if (collapse_test_exit_or_disable(mm))
result = SCAN_ANY_PROCESS;
else
result = try_collapse_pte_mapped_thp(mm, addr,
- !cc->is_khugepaged);
+ cc->policy.install_pmd);
if (result == SCAN_PMD_MAPPED)
result = SCAN_SUCCEED;
mmap_read_unlock(mm);
}
-end:
- if (cc->is_khugepaged && result == SCAN_SUCCEED)
- ++khugepaged_pages_collapsed;
return result;
}
@@ -2899,14 +2879,17 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
vma_iter_init(&vmi, mm, khugepaged_scan.address);
for_each_vma(vmi, vma) {
- unsigned long hstart, hend;
+ unsigned long hstart, hend, orders;
cond_resched();
if (unlikely(collapse_test_exit_or_disable(mm))) {
cc->progress++;
break;
}
- if (!collapse_possible(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
+ /* One mask for the whole VMA */
+ orders = collapse_possible_orders(vma, vma->vm_flags,
+ cc->policy.tva_type);
+ if (!orders) {
cc->progress++;
continue;
}
@@ -2921,7 +2904,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
VM_BUG_ON(khugepaged_scan.address & ~HPAGE_PMD_MASK);
while (khugepaged_scan.address < hend) {
- bool lock_dropped = false;
+ unsigned long addr;
cond_resched();
if (unlikely(collapse_test_exit_or_disable(mm)))
@@ -2931,21 +2914,29 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
khugepaged_scan.address + HPAGE_PMD_SIZE >
hend);
- *result = collapse_single_pmd(khugepaged_scan.address,
- vma, &lock_dropped, cc);
+ addr = khugepaged_scan.address;
/* move to next address */
khugepaged_scan.address += HPAGE_PMD_SIZE;
- if (lock_dropped)
- /*
- * We released mmap_lock so break loop. Note
- * that we drop mmap_lock before all hugepage
- * allocations, so if allocation fails, we are
- * guaranteed to break here and report the
- * correct result back to caller.
- */
- goto breakouterloop_mmap_lock;
- if (cc->progress >= progress_max)
- goto breakouterloop;
+
+ *result = collapse_scan_pmd(vma, addr, cc, orders);
+ /* Nothing to collapse here, and the lock is still ours */
+ if (*result != SCAN_SUCCEED) {
+ if (cc->progress >= progress_max)
+ goto breakouterloop;
+ continue;
+ }
+
+ /*
+ * A collapse takes its own locks and is slow enough
+ * that a writer should not wait behind it, so give the
+ * lock up. That ends this walk: vma and the mm are
+ * whatever the collapse leaves them.
+ */
+ mmap_read_unlock(mm);
+ *result = collapse_run_pmd(mm, addr, cc);
+ if (*result == SCAN_SUCCEED)
+ khugepaged_pages_collapsed++;
+ goto breakouterloop_mmap_lock;
}
}
breakouterloop:
@@ -2999,7 +2990,10 @@ static void khugepaged_do_scan(struct collapse_control *cc)
lru_add_drain_all();
- cc->progress = 0;
+ collapse_control_init(cc);
+ /* One policy for the whole pass, so every table is judged the same */
+ collapse_policy_khugepaged(&cc->policy);
+
while (true) {
cond_resched();
@@ -3030,6 +3024,8 @@ static void khugepaged_do_scan(struct collapse_control *cc)
khugepaged_alloc_sleep();
}
}
+
+ collapse_control_release(cc);
}
static bool khugepaged_should_wakeup(void)
@@ -3168,126 +3164,3 @@ bool current_is_khugepaged(void)
{
return kthread_func(current) == khugepaged;
}
-
-static int madvise_collapse_errno(enum scan_result r)
-{
- /*
- * MADV_COLLAPSE breaks from existing madvise(2) conventions to provide
- * actionable feedback to caller, so they may take an appropriate
- * fallback measure depending on the nature of the failure.
- */
- switch (r) {
- case SCAN_ALLOC_HUGE_PAGE_FAIL:
- return -ENOMEM;
- case SCAN_CGROUP_CHARGE_FAIL:
- case SCAN_EXCEED_NONE_PTE:
- return -EBUSY;
- /* Resource temporary unavailable - trying again might succeed */
- case SCAN_PAGE_COUNT:
- case SCAN_PAGE_LOCK:
- case SCAN_PAGE_LRU:
- case SCAN_DEL_PAGE_LRU:
- case SCAN_PAGE_FILLED:
- case SCAN_PAGE_HAS_PRIVATE:
- case SCAN_PAGE_DIRTY_OR_WRITEBACK:
- return -EAGAIN;
- /*
- * Other: Trying again likely not to succeed / error intrinsic to
- * specified memory range. khugepaged likely won't be able to collapse
- * either.
- */
- default:
- return -EINVAL;
- }
-}
-
-int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
- unsigned long end, bool *lock_dropped)
-{
- struct collapse_control *cc;
- struct mm_struct *mm = vma->vm_mm;
- unsigned long hstart, hend, addr;
- enum scan_result last_fail = SCAN_FAIL;
- int thps = 0;
- bool mmap_unlocked = false;
-
- BUG_ON(vma->vm_start > start);
- BUG_ON(vma->vm_end < end);
-
- if (!collapse_possible(vma, vma->vm_flags, TVA_FORCED_COLLAPSE))
- return -EINVAL;
-
- hstart = ALIGN(start, HPAGE_PMD_SIZE);
- hend = ALIGN_DOWN(end, HPAGE_PMD_SIZE);
-
- if (hstart >= hend)
- return 0;
-
- cc = kmalloc_obj(*cc);
- if (!cc)
- return -ENOMEM;
- cc->is_khugepaged = false;
- cc->progress = 0;
-
- mmgrab(mm);
- lru_add_drain_all();
-
- for (addr = hstart; addr < hend; addr += HPAGE_PMD_SIZE) {
- enum scan_result result = SCAN_FAIL;
-
- if (mmap_unlocked) {
- cond_resched();
- mmap_read_lock(mm);
- mmap_unlocked = false;
- *lock_dropped = true;
- result = hugepage_vma_revalidate(mm, addr, false, &vma,
- cc, HPAGE_PMD_ORDER);
- if (result != SCAN_SUCCEED) {
- last_fail = result;
- goto out_nolock;
- }
-
- hend = min(hend, vma->vm_end & HPAGE_PMD_MASK);
- }
-
- result = collapse_single_pmd(addr, vma, &mmap_unlocked, cc);
-
- switch (result) {
- case SCAN_SUCCEED:
- case SCAN_PMD_MAPPED:
- ++thps;
- break;
- /* Whitelisted set of results where continuing OK */
- case SCAN_NO_PTE_TABLE:
- case SCAN_PTE_NON_PRESENT:
- case SCAN_PTE_UFFD:
- case SCAN_LACK_REFERENCED_PAGE:
- case SCAN_PAGE_NULL:
- case SCAN_PAGE_COUNT:
- case SCAN_PAGE_LOCK:
- case SCAN_PAGE_COMPOUND:
- case SCAN_PAGE_LRU:
- case SCAN_DEL_PAGE_LRU:
- last_fail = result;
- break;
- default:
- last_fail = result;
- /* Other error, exit */
- goto out_maybelock;
- }
- }
-
-out_maybelock:
- /* Caller expects us to hold mmap_lock on return */
- if (mmap_unlocked) {
- *lock_dropped = true;
- mmap_read_lock(mm);
- }
-out_nolock:
- mmap_assert_locked(mm);
- mmdrop(mm);
- kfree(cc);
-
- return thps == ((hend - hstart) >> HPAGE_PMD_SHIFT) ? 0
- : madvise_collapse_errno(last_fail);
-}
diff --git a/mm/madvise.c b/mm/madvise.c
index 963337f93a7a1..f75a9d1399803 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -38,6 +38,7 @@
#include "internal.h"
#include "swap.h"
+#include "collapse.h"
#define __MADV_SET_ANON_VMA_NAME (-1)
@@ -906,6 +907,171 @@ bool madvise_dontneed_free_valid_vma(struct madvise_behavior *madv_behavior)
return true;
}
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+
+/* MADV_COLLAPSE was asked for explicitly, so it is not held to those */
+static void collapse_policy_forced(struct collapse_policy *p)
+{
+ p->max_ptes_none = HPAGE_PMD_NR;
+ p->max_ptes_swap = HPAGE_PMD_NR;
+ p->max_ptes_shared = HPAGE_PMD_NR;
+ p->strict_sub_pmd = false;
+ p->skip_lazyfree = false;
+ p->require_referenced = false;
+ p->install_pmd = true;
+ p->writeback_dirty = true;
+ p->gfp = GFP_TRANSHUGE;
+ p->tva_type = TVA_FORCED_COLLAPSE;
+}
+
+static int madvise_collapse_errno(enum scan_result r)
+{
+ /*
+ * MADV_COLLAPSE breaks from existing madvise(2) conventions to provide
+ * actionable feedback to caller, so they may take an appropriate
+ * fallback measure depending on the nature of the failure.
+ */
+ switch (r) {
+ case SCAN_ALLOC_HUGE_PAGE_FAIL:
+ return -ENOMEM;
+ case SCAN_CGROUP_CHARGE_FAIL:
+ case SCAN_EXCEED_NONE_PTE:
+ return -EBUSY;
+ /* Resource temporary unavailable - trying again might succeed */
+ case SCAN_PAGE_COUNT:
+ case SCAN_PAGE_LOCK:
+ case SCAN_PAGE_LRU:
+ case SCAN_DEL_PAGE_LRU:
+ case SCAN_PAGE_FILLED:
+ case SCAN_PAGE_HAS_PRIVATE:
+ case SCAN_PAGE_DIRTY_OR_WRITEBACK:
+ return -EAGAIN;
+ /*
+ * Other: Trying again likely not to succeed / error intrinsic to
+ * specified memory range. khugepaged likely won't be able to collapse
+ * either.
+ */
+ default:
+ return -EINVAL;
+ }
+}
+
+static int madvise_collapse(struct madvise_behavior *madv_behavior)
+{
+ struct madvise_behavior_range *range = &madv_behavior->range;
+ struct vm_area_struct *vma = madv_behavior->vma;
+ struct mm_struct *mm = madv_behavior->mm;
+ struct collapse_control *cc;
+ unsigned long hstart, hend, addr, orders;
+ enum scan_result last_fail = SCAN_FAIL;
+ int thps = 0;
+
+ BUG_ON(vma->vm_start > range->start);
+ BUG_ON(vma->vm_end < range->end);
+
+ orders = collapse_possible_orders(vma, vma->vm_flags,
+ TVA_FORCED_COLLAPSE);
+ if (!orders)
+ return -EINVAL;
+
+ hstart = ALIGN(range->start, HPAGE_PMD_SIZE);
+ hend = ALIGN_DOWN(range->end, HPAGE_PMD_SIZE);
+
+ if (hstart >= hend)
+ return 0;
+
+ cc = kmalloc_obj(*cc);
+ if (!cc)
+ return -ENOMEM;
+ collapse_control_init(cc);
+ collapse_policy_forced(&cc->policy);
+
+ lru_add_drain_all();
+
+ for (addr = hstart; addr < hend; addr += HPAGE_PMD_SIZE) {
+ struct vm_area_struct *found;
+ enum scan_result result;
+
+ /*
+ * A collapse gives the lock up, so the VMA has to be found
+ * again after one: it can shrink while nothing is held. A scan
+ * that finds nothing to collapse leaves the lock alone, so a
+ * range that is already collapsed walks on without relocking.
+ */
+ if (!vma) {
+ cond_resched();
+ mmap_read_lock(mm);
+ result = collapse_vma_revalidate(mm, addr, false, &found,
+ cc, HPAGE_PMD_ORDER);
+ if (result != SCAN_SUCCEED) {
+ last_fail = result;
+ goto out_locked;
+ }
+ vma = found;
+ hend = min(hend, vma->vm_end & HPAGE_PMD_MASK);
+ orders = collapse_possible_orders(vma, vma->vm_flags,
+ cc->policy.tva_type);
+ }
+
+ result = collapse_scan_pmd(vma, addr, cc, orders);
+ /* Nothing to collapse here, and the lock is still ours */
+ if (result != SCAN_SUCCEED)
+ goto tally;
+
+ /* The collapse takes its own locks, so give this up */
+ mmap_read_unlock(mm);
+ mark_mmap_lock_dropped(madv_behavior);
+ vma = NULL;
+
+ result = collapse_run_pmd(mm, addr, cc);
+tally:
+ switch (result) {
+ case SCAN_SUCCEED:
+ case SCAN_PMD_MAPPED:
+ ++thps;
+ break;
+ /* Whitelisted set of results where continuing OK */
+ case SCAN_NO_PTE_TABLE:
+ case SCAN_PTE_NON_PRESENT:
+ case SCAN_PTE_UFFD:
+ case SCAN_LACK_REFERENCED_PAGE:
+ case SCAN_PAGE_NULL:
+ case SCAN_PAGE_COUNT:
+ case SCAN_PAGE_LOCK:
+ case SCAN_PAGE_COMPOUND:
+ case SCAN_PAGE_LRU:
+ case SCAN_DEL_PAGE_LRU:
+ last_fail = result;
+ break;
+ default:
+ last_fail = result;
+ /* Other error, exit */
+ goto out;
+ }
+ }
+
+out:
+ /* Caller expects us to hold mmap_lock on return */
+ if (!vma)
+ mmap_read_lock(mm);
+out_locked:
+ mmap_assert_locked(mm);
+ collapse_control_release(cc);
+ kfree(cc);
+
+ return thps == ((hend - hstart) >> HPAGE_PMD_SHIFT) ? 0
+ : madvise_collapse_errno(last_fail);
+}
+
+#else /* CONFIG_TRANSPARENT_HUGEPAGE */
+
+static int madvise_collapse(struct madvise_behavior *madv_behavior)
+{
+ return -EINVAL;
+}
+
+#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
+
static long madvise_dontneed_free(struct madvise_behavior *madv_behavior)
{
struct mm_struct *mm = madv_behavior->mm;
@@ -1373,8 +1539,7 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)
case MADV_DONTNEED_LOCKED:
return madvise_dontneed_free(madv_behavior);
case MADV_COLLAPSE:
- return madvise_collapse(vma, range->start, range->end,
- &madv_behavior->lock_dropped);
+ return madvise_collapse(madv_behavior);
case MADV_GUARD_INSTALL:
return madvise_guard_install(madv_behavior);
case MADV_GUARD_REMOVE:
|
| 3/2 | 2026/09/10 13:43 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 19411 output: 30 thoughts: 516 Thoughts: **Analyzing the Patch Series for Syzkaller Fuzzing Potential** Okay, let's dive into this patch series and see if it's a good candidate for fuzzing with syzkaller. First, I need to get a clear picture of what's been changed. The patch touches the `MAINTAINERS` file, along with `include/linux/huge_mm.h`, a new header file `mm/collapse.h`, and modifications to `mm/khugepaged.c` and `mm/madvise.c`. That's a strong indicator of memory management work, which is always interesting from a fuzzing perspective. Looking at the changes themselves, it's all about refactoring and splitting the collapse functionality into scan and run phases. They're introducing new structures, `collapse_policy` and `collapse_control`, in `mm/collapse.h`. They've moved `madvise_collapse` to `mm/madvise.c` and refactored it to fit the new scan-then-run model. Additionally, there are updates to `khugepaged_do_scan` and `collapse_scan_mm_slot` in `mm/khugepaged.c` to leverage this split architecture with the `collapse_policy_khugepaged`. It's great to see they're replacing a boolean flag (`is_khugepaged`) with more nuanced policy flags – things like `max_ptes_none`, `max_ptes_swap`, and so on. That kind of refactoring often opens up new, subtle code paths that could be problematic. Finally, there are a few warnings and assertions which are a good sign they are thinking about potential edge cases. Now, let's evaluate this based on the core rules. Subsystem? Memory Management (`mm/`) is clearly the target. Reachability? Yes, this is accessible via both the `madvise(..., MADV_COLLAPSE)` syscall *and* the khugepaged daemon, which is an automatic background process. This is excellent! We can hit it directly through the syscall, and we might be able to trigger it indirectly through memory management pressure. Worth fuzzing? Absolutely, a resounding *YES*. I'm marking this with `WorthFuzzing=true`. Next step is to check the `FocusSymbols` and `EnableConfigs`. I need to review Kconfig options related to THP (Transparent Huge Pages), the `TRANSPARENT_HUGEPAGE` config, and MADV_COLLAPSE. I will utilize `grepper` to inspect the `mm/Kconfig` file and find the relevant configurations. |
| 4/2 | 2026/09/10 13:43 | tool | grepper |
0mArgs:{
"Expression": "config TRANSPARENT_HUGEPAGE",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/Kconfig=831=config MM_ID\n--\nmm/Kconfig-833-\nmm/Kconfig:834:menuconfig TRANSPARENT_HUGEPAGE\nmm/Kconfig-835-\tbool \"Transparent Hugepage Support\"\n--\nmm/Kconfig=852=choice\n--\nmm/Kconfig-857-\nmm/Kconfig:858:\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\nmm/Kconfig-859-\t\tbool \"always\"\n--\nmm/Kconfig-864-\nmm/Kconfig:865:\tconfig TRANSPARENT_HUGEPAGE_MADVISE\nmm/Kconfig-866-\t\tbool \"madvise\"\n--\nmm/Kconfig-873-\nmm/Kconfig:874:\tconfig TRANSPARENT_HUGEPAGE_NEVER\nmm/Kconfig-875-\t\tbool \"never\"\n--\nmm/Kconfig=881=choice\n--\nmm/Kconfig-890-\nmm/Kconfig:891:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_NEVER\nmm/Kconfig-892-\t\tbool \"never\"\n--\nmm/Kconfig-900-\nmm/Kconfig:901:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ALWAYS\nmm/Kconfig-902-\t\tbool \"always\"\n--\nmm/Kconfig-908-\nmm/Kconfig:909:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_WITHIN_SIZE\nmm/Kconfig-910-\t\tbool \"within_size\"\n--\nmm/Kconfig-916-\nmm/Kconfig:917:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ADVISE\nmm/Kconfig-918-\t\tbool \"advise\"\n--\nmm/Kconfig=926=choice\n--\nmm/Kconfig-935-\nmm/Kconfig:936:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_NEVER\nmm/Kconfig-937-\t\tbool \"never\"\n--\nmm/Kconfig-944-\nmm/Kconfig:945:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ALWAYS\nmm/Kconfig-946-\t\tbool \"always\"\n--\nmm/Kconfig-952-\nmm/Kconfig:953:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_WITHIN_SIZE\nmm/Kconfig-954-\t\tbool \"within_size\"\n--\nmm/Kconfig-960-\nmm/Kconfig:961:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ADVISE\nmm/Kconfig-962-\t\tbool \"advise\"\n"
} |
| 5/2 | 2026/09/10 13:43 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 21238 output: 27 thoughts: 23 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Investigating the `khugepaged.o` Build Process** Okay, so I need to understand how `khugepaged.o` is being constructed. I'll immediately drill down into the heart of the matter – the `mm/Makefile`. That's the logical place to start digging into the build rules for memory management components. I'm expecting to find the standard object file compilation instructions, likely involving kernel headers and possibly some architecture-specific flags depending on the target. I'll be looking for dependencies, any conditional compilation based on kernel configuration options, and of course, the actual command used to generate the `.o` file. The aim is to get a clear picture of its role in the compilation process, any special build considerations, and how it fits into the broader MM subsystem. |
| 6/2 | 2026/09/10 13:43 | tool | grepper |
0mArgs:{
"Expression": "khugepaged",
"PathPrefix": "mm/Makefile"
}Results: {
"Output": "mm/Makefile=101=obj-$(CONFIG_DEVICE_MIGRATION) += migrate_device.o\nmm/Makefile:102:obj-$(CONFIG_TRANSPARENT_HUGEPAGE) += huge_memory.o khugepaged.o\nmm/Makefile-103-obj-$(CONFIG_PAGE_COUNTER) += page_counter.o\n"
} |
| 7/2 | 2026/09/10 13:43 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 21568 output: 32 thoughts: 48 Thoughts: **Investigating Transparent Huge Pages Configuration** Okay, so the initial clue points to `CONFIG_TRANSPARENT_HUGEPAGE` as the key configuration option I need to focus on. I need to understand this better. Now, let's dive into the `mm/Kconfig` file. I'll search for related options around `TRANSPARENT_HUGEPAGE` to see if there are any dependencies, related features, or different configurations that are exposed. Understanding the broader context around this single config is critical to a complete picture. This should offer clues to how it's enabled, disabled, managed, and any considerations. |
| 8/2 | 2026/09/10 13:43 | tool | read-file |
0mArgs:{
"File": "mm/Kconfig",
"FirstLine": 830,
"LineCount": 40
}Results: {
"Contents": " 830:\t\n 831:\tconfig MM_ID\n 832:\t\tdef_bool n\n 833:\t\n 834:\tmenuconfig TRANSPARENT_HUGEPAGE\n 835:\t\tbool \"Transparent Hugepage Support\"\n 836:\t\tdepends on HAVE_ARCH_TRANSPARENT_HUGEPAGE \u0026\u0026 !PREEMPT_RT\n 837:\t\tselect COMPACTION\n 838:\t\tselect XARRAY_MULTI\n 839:\t\tselect MM_ID\n 840:\t\thelp\n 841:\t\t Transparent Hugepages allows the kernel to use huge pages and\n 842:\t\t huge tlb transparently to the applications whenever possible.\n 843:\t\t This feature can improve computing performance to certain\n 844:\t\t applications by speeding up page faults during memory\n 845:\t\t allocation, by reducing the number of tlb misses and by speeding\n 846:\t\t up the pagetable walking.\n 847:\t\n 848:\t\t If memory constrained on embedded, you may want to say N.\n 849:\t\n 850:\tif TRANSPARENT_HUGEPAGE\n 851:\t\n 852:\tchoice\n 853:\t\tprompt \"Transparent Hugepage Support sysfs defaults\"\n 854:\t\tdefault TRANSPARENT_HUGEPAGE_ALWAYS\n 855:\t\thelp\n 856:\t\t Selects the sysfs defaults for Transparent Hugepage Support.\n 857:\t\n 858:\t\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\n 859:\t\t\tbool \"always\"\n 860:\t\thelp\n 861:\t\t Enabling Transparent Hugepage always, can increase the\n 862:\t\t memory footprint of applications without a guaranteed\n 863:\t\t benefit but it will work automatically for all applications.\n 864:\t\n 865:\t\tconfig TRANSPARENT_HUGEPAGE_MADVISE\n 866:\t\t\tbool \"madvise\"\n 867:\t\thelp\n 868:\t\t Enabling Transparent Hugepage madvise, will only provide a\n 869:\t\t performance improvement benefit to the applications using\n"
} |
| 9/2 | 2026/09/10 13:43 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22337 output: 271 thoughts: 1228 Thoughts: **Analysis of Focus Symbols for THP Collapsing** Okay, let's break down this code. I need to understand the key entry points and modified functions related to FocusSymbols, specifically the THP collapsing logic. My initial scan points to several functions introduced or modified within `mm/collapse.h`, `mm/khugepaged.c`, and `mm/madvise.c`. I see that `madvise_collapse` is defined in `mm/madvise.c`. It's non-static and called by `madvise_vma_behavior`, confirming its significance. The others, `collapse_scan_pmd`, `collapse_run_pmd`, `collapse_vma_revalidate`, `collapse_possible_orders`, `collapse_control_init`, and `collapse_control_release` all seem to be non-static and exported. I verify this by checking their declarations in `mm/collapse.h` and the definitions in `mm/khugepaged.c`. Looking closer, `collapse_scan_anon_pmd`, `mthp_collapse`, `collapse_scan_mm_slot`, and `khugepaged_do_scan` are defined as `static` in `mm/khugepaged.c`. That means they're internal to the module, but they still represent key aspects of the collapsing process. `__collapse_huge_page_isolate` and `alloc_charge_folio` seem relevant. The focus symbols are indeed specific and non-ubiquitous, tied directly to THP collapse. So, to summarize my list of focus symbols: * `collapse_scan_pmd` * `collapse_run_pmd` * `collapse_scan_anon_pmd` * `madvise_collapse` * `collapse_possible_orders` * `collapse_vma_revalidate` * `collapse_control_release` * `mthp_collapse` * `collapse_scan_mm_slot` * `khugepaged_do_scan` The `TRANSPARENT_HUGEPAGE` config is, of course, a prerequisite. From here, I need to articulate the reasoning structure for the fuzzing analysis. The core of the patch refactors the THP collapse infrastructure, decoupling scan and run phases. It introduces `collapse_policy` and `collapse_control` for state management, refactors `madvise_collapse` and `khugepaged_do_scan`, and includes new assertions and locking changes (mmap\_lock handling). Crucially, this logic is accessible from userspace through `madvise(..., MADV_COLLAPSE)` and the khugepaged kernel thread. I've got everything I need; I can now call `set-results`. |
| 10/2 | 2026/09/10 13:43 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE"
],
"FocusSymbols": [
"collapse_scan_pmd",
"collapse_run_pmd",
"collapse_scan_anon_pmd",
"madvise_collapse",
"collapse_possible_orders",
"collapse_vma_revalidate",
"collapse_control_release",
"mthp_collapse",
"collapse_scan_mm_slot",
"khugepaged_do_scan"
],
"Reasoning": "The patch refactors the Transparent Hugepage (THP) collapse engine in the memory management subsystem (mm/). It splits the collapse logic into distinct scan and run phases (collapse_scan_pmd and collapse_run_pmd), introduces collapse_policy to generalize khugepaged and madvise behavior, restructures mmap_lock release/revalidation semantics, moves madvise_collapse into mm/madvise.c, and adds new WARN_ON_ONCE assertions. This code is reachable from user space via the madvise(..., MADV_COLLAPSE) syscall and background khugepaged scanning, making it well-suited for fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE"
],
"FocusSymbols": [
"collapse_scan_pmd",
"collapse_run_pmd",
"collapse_scan_anon_pmd",
"madvise_collapse",
"collapse_possible_orders",
"collapse_vma_revalidate",
"collapse_control_release",
"mthp_collapse",
"collapse_scan_mm_slot",
"khugepaged_do_scan"
],
"Reasoning": "The patch refactors the Transparent Hugepage (THP) collapse engine in the memory management subsystem (mm/). It splits the collapse logic into distinct scan and run phases (collapse_scan_pmd and collapse_run_pmd), introduces collapse_policy to generalize khugepaged and madvise behavior, restructures mmap_lock release/revalidation semantics, moves madvise_collapse into mm/madvise.c, and adds new WARN_ON_ONCE assertions. This code is reachable from user space via the madvise(..., MADV_COLLAPSE) syscall and background khugepaged scanning, making it well-suited for fuzzing.",
"WorthFuzzing": true
} |
| 11/1 | 2026/09/10 13:43 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 12/2 | 2026/09/10 13:43 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch refactors the Transparent HugePage (THP) collapse infrastructure. It introduces `struct collapse_policy` to distinguish policy configurations between khugepaged background passes and explicit `MADV_COLLAPSE` operations, splits scanning and execution into `collapse_scan_pmd()` and `collapse_run_pmd()`, and relocates `madvise_collapse()` to `mm/madvise.c`.\n\nAll newly introduced and modified fields within `struct collapse_control` are explicitly initialized before use (via `collapse_control_init()`, `collapse_scan_reset()`, and scan assignments prior to running). No kernel-to-user memory transfers (`copy_to_user`, info-leaks, padding leaks) are involved, nor is there any branching on uninitialized data. Any potential regressions or bugs in this patch involve locking, page table traversal, refcounting (e.g., `struct file` or `struct folio`), or VMA revalidation, which are comprehensively covered by standard KASAN, LOCKDEP, and page allocator debug assertions. Therefore, dedicated KMSAN fuzzing is not required.",
"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 7eeddd98adba18e6e64be0360b1e4fd5fb6af557
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Sep 10 13:43:21 2026 +0000
syz-cluster: applied patch under review
diff --git a/MAINTAINERS b/MAINTAINERS
index 35262aa3e67ef..36315972156b5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17434,6 +17434,7 @@ F: Documentation/admin-guide/mm/transhuge.rst
F: include/linux/huge_mm.h
F: include/linux/khugepaged.h
F: include/trace/events/huge_memory.h
+F: mm/collapse.h
F: mm/huge_memory.c
F: mm/khugepaged.c
F: mm/mm_slot.h
diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index c745f7ad22987..8ca0fa3be2acb 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -510,8 +510,6 @@ change_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,
int hugepage_madvise(struct vm_area_struct *vma, vm_flags_t *vm_flags,
int advice);
-int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
- unsigned long end, bool *lock_dropped);
void vma_adjust_trans_huge(struct vm_area_struct *vma, unsigned long start,
unsigned long end, struct vm_area_struct *next);
spinlock_t *__pmd_trans_huge_lock(pmd_t *pmd, struct vm_area_struct *vma);
@@ -715,13 +713,6 @@ static inline int hugepage_madvise(struct vm_area_struct *vma,
return -EINVAL;
}
-static inline int madvise_collapse(struct vm_area_struct *vma,
- unsigned long start,
- unsigned long end, bool *lock_dropped)
-{
- return -EINVAL;
-}
-
static inline void vma_adjust_trans_huge(struct vm_area_struct *vma,
unsigned long start,
unsigned long end,
diff --git a/mm/collapse.h b/mm/collapse.h
new file mode 100644
index 0000000000000..1ebbbf63fb256
--- /dev/null
+++ b/mm/collapse.h
@@ -0,0 +1,153 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __MM_COLLAPSE_H
+#define __MM_COLLAPSE_H
+
+#include <linux/mm.h>
+#include <linux/nodemask.h>
+#include <linux/pgtable.h>
+#include <linux/types.h>
+
+#define COLLAPSE_MAX_PTES_LIMIT (HPAGE_PMD_NR - 1)
+#define COLLAPSE_MIN_MTHP_ORDER 2
+
+enum scan_result {
+ SCAN_FAIL,
+ SCAN_SUCCEED,
+ SCAN_NO_PTE_TABLE,
+ SCAN_PMD_MAPPED,
+ SCAN_EXCEED_NONE_PTE,
+ SCAN_EXCEED_SWAP_PTE,
+ SCAN_EXCEED_SHARED_PTE,
+ SCAN_PTE_NON_PRESENT,
+ SCAN_PTE_UFFD,
+ SCAN_PTE_MAPPED_HUGEPAGE,
+ SCAN_LACK_REFERENCED_PAGE,
+ SCAN_PAGE_NULL,
+ SCAN_SCAN_ABORT,
+ SCAN_PAGE_COUNT,
+ SCAN_PAGE_LRU,
+ SCAN_PAGE_LOCK,
+ SCAN_PAGE_ANON,
+ SCAN_PAGE_LAZYFREE,
+ SCAN_PAGE_COMPOUND,
+ SCAN_ANY_PROCESS,
+ SCAN_VMA_NULL,
+ SCAN_VMA_CHECK,
+ SCAN_ADDRESS_RANGE,
+ SCAN_DEL_PAGE_LRU,
+ SCAN_ALLOC_HUGE_PAGE_FAIL,
+ SCAN_CGROUP_CHARGE_FAIL,
+ SCAN_TRUNCATED,
+ SCAN_PAGE_HAS_PRIVATE,
+ SCAN_STORE_FAILED,
+ SCAN_COPY_MC,
+ SCAN_PAGE_FILLED,
+ SCAN_PAGE_DIRTY_OR_WRITEBACK,
+};
+
+/* What a collapse is allowed to do, decided by the caller that asks for it */
+struct collapse_policy {
+ /* Limits, stated per PMD; HPAGE_PMD_NR means "no limit" */
+ unsigned int max_ptes_none;
+ unsigned int max_ptes_swap;
+ unsigned int max_ptes_shared;
+
+ /* Take no swapped-out or shared PTE into a sub-PMD collapse */
+ bool strict_sub_pmd;
+
+ /* Leave clean lazyfree folios to reclaim rather than collapse them */
+ bool skip_lazyfree;
+
+ /* Refuse a range with no sign of use */
+ bool require_referenced;
+
+ /* Map the PMD over a file collapse instead of leaving it to a fault */
+ bool install_pmd;
+
+ /* Write dirty pages back and retry once instead of refusing them */
+ bool writeback_dirty;
+
+ /* How hard to try for a destination folio */
+ gfp_t gfp;
+
+ /* Which VMAs are eligible, as thp_vma_allowable_orders() spells it */
+ enum tva_type tva_type;
+};
+
+struct collapse_control {
+ struct collapse_policy policy;
+
+ /* Num pages scanned per node */
+ u32 node_load[MAX_NUMNODES];
+
+ /* Num pages scanned (see khugepaged_pages_to_scan) */
+ unsigned int progress;
+
+ /* nodemask for allocation fallback */
+ nodemask_t alloc_nmask;
+
+ /* Each bit marks a PTE the scan accepted as a collapse source */
+ DECLARE_BITMAP(eligible_ptes, MAX_PTRS_PER_PTE);
+
+ /*
+ * What a scan found and the run after it needs. Live only between the
+ * two, and read by nobody else.
+ *
+ * The file side takes a reference while it still has the VMA, since a
+ * file collapse works on the page cache and never sees one; the run is
+ * what gives it back. A scan that found the PMD folio already in the
+ * cache leaves only the PTE table to retract.
+ */
+ unsigned long scan_orders;
+ int scan_referenced;
+ int scan_unmapped;
+ struct file *scan_file;
+ pgoff_t scan_pgoff;
+ bool scan_retract_only;
+};
+
+/* Which orders a VMA may collapse to, zero when it may not collapse at all */
+unsigned long collapse_possible_orders(struct vm_area_struct *vma,
+ vm_flags_t vm_flags, enum tva_type tva_flags);
+
+/*
+ * A caller states what it allows in cc->policy and then hands over one PTE
+ * table's worth of a VMA at a time:
+ *
+ * collapse_control_init(cc) once, before the first table
+ * collapse_scan_pmd(vma, addr, ...) per table
+ * collapse_run_pmd(mm, addr, cc) when a scan found work
+ * collapse_control_release(cc) once, when done with the control
+ *
+ * The caller holds mmap_lock for reading over the scan and passes an address
+ * within @vma, aligned to the PTE table the scan is to judge.
+ *
+ * The scan returns with that lock still held. It only reads, and almost every
+ * table it is offered has nothing to collapse, so a caller walks a whole VMA
+ * under the one lock it took to get there. SCAN_SUCCEED means there is
+ * something to collapse; anything else is why there is not.
+ *
+ * The run is called without the lock and returns without it, taking what it
+ * needs in between: what it does -- allocate, isolate, copy, flush -- is slow
+ * enough that a writer would wait behind it. The caller gives the lock up
+ * first, and with it @vma and anything derived under it, so a caller carrying
+ * on has to look up again with collapse_vma_revalidate(). The run revalidates
+ * for itself rather than trusting what the scan saw.
+ *
+ * A scan that found something has to be run: the file side takes a reference on
+ * the file while it still has the VMA to take it from, and the run is what
+ * gives it back.
+ */
+void collapse_control_init(struct collapse_control *cc);
+void collapse_control_release(struct collapse_control *cc);
+enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,
+ unsigned long addr, struct collapse_control *cc,
+ unsigned long orders);
+enum scan_result collapse_run_pmd(struct mm_struct *mm, unsigned long addr,
+ struct collapse_control *cc);
+enum scan_result collapse_vma_revalidate(struct mm_struct *mm,
+ unsigned long address, bool expect_anon,
+ struct vm_area_struct **vmap, struct collapse_control *cc,
+ unsigned int order);
+
+#endif /* __MM_COLLAPSE_H */
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index e13d233b9967c..1deb74cf28af0 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -26,44 +26,10 @@
#include <linux/cleanup.h>
#include <asm/tlb.h>
+#include "collapse.h"
#include "internal.h"
-#include "page_alloc.h"
#include "mm_slot.h"
-
-enum scan_result {
- SCAN_FAIL,
- SCAN_SUCCEED,
- SCAN_NO_PTE_TABLE,
- SCAN_PMD_MAPPED,
- SCAN_EXCEED_NONE_PTE,
- SCAN_EXCEED_SWAP_PTE,
- SCAN_EXCEED_SHARED_PTE,
- SCAN_PTE_NON_PRESENT,
- SCAN_PTE_UFFD,
- SCAN_PTE_MAPPED_HUGEPAGE,
- SCAN_LACK_REFERENCED_PAGE,
- SCAN_PAGE_NULL,
- SCAN_SCAN_ABORT,
- SCAN_PAGE_COUNT,
- SCAN_PAGE_LRU,
- SCAN_PAGE_LOCK,
- SCAN_PAGE_ANON,
- SCAN_PAGE_LAZYFREE,
- SCAN_PAGE_COMPOUND,
- SCAN_ANY_PROCESS,
- SCAN_VMA_NULL,
- SCAN_VMA_CHECK,
- SCAN_ADDRESS_RANGE,
- SCAN_DEL_PAGE_LRU,
- SCAN_ALLOC_HUGE_PAGE_FAIL,
- SCAN_CGROUP_CHARGE_FAIL,
- SCAN_TRUNCATED,
- SCAN_PAGE_HAS_PRIVATE,
- SCAN_STORE_FAILED,
- SCAN_COPY_MC,
- SCAN_PAGE_FILLED,
- SCAN_PAGE_DIRTY_OR_WRITEBACK,
-};
+#include "page_alloc.h"
#define CREATE_TRACE_POINTS
#include <trace/events/huge_memory.h>
@@ -91,7 +57,6 @@ static DECLARE_WAIT_QUEUE_HEAD(khugepaged_wait);
*
* Note that these are only respected if collapse was initiated by khugepaged.
*/
-#define KHUGEPAGED_MAX_PTES_LIMIT (HPAGE_PMD_NR - 1)
unsigned int khugepaged_max_ptes_none __read_mostly;
static unsigned int khugepaged_max_ptes_swap __read_mostly;
static unsigned int khugepaged_max_ptes_shared __read_mostly;
@@ -101,24 +66,6 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);
static struct kmem_cache *mm_slot_cache __ro_after_init;
-#define KHUGEPAGED_MIN_MTHP_ORDER 2
-
-struct collapse_control {
- bool is_khugepaged;
-
- /* Num pages scanned per node */
- u32 node_load[MAX_NUMNODES];
-
- /* Num pages scanned (see khugepaged_pages_to_scan) */
- unsigned int progress;
-
- /* nodemask for allocation fallback */
- nodemask_t alloc_nmask;
-
- /* Each bit represents a single occupied (!none/zero) page. */
- DECLARE_BITMAP(mthp_present_ptes, MAX_PTRS_PER_PTE);
-};
-
/**
* struct khugepaged_scan - cursor for scanning
* @mm_head: the head of the mm list to scan
@@ -267,7 +214,7 @@ static ssize_t max_ptes_none_store(struct kobject *kobj,
unsigned long max_ptes_none;
err = kstrtoul(buf, 10, &max_ptes_none);
- if (err || max_ptes_none > KHUGEPAGED_MAX_PTES_LIMIT)
+ if (err || max_ptes_none > COLLAPSE_MAX_PTES_LIMIT)
return -EINVAL;
khugepaged_max_ptes_none = max_ptes_none;
@@ -292,7 +239,7 @@ static ssize_t max_ptes_swap_store(struct kobject *kobj,
unsigned long max_ptes_swap;
err = kstrtoul(buf, 10, &max_ptes_swap);
- if (err || max_ptes_swap > KHUGEPAGED_MAX_PTES_LIMIT)
+ if (err || max_ptes_swap > COLLAPSE_MAX_PTES_LIMIT)
return -EINVAL;
khugepaged_max_ptes_swap = max_ptes_swap;
@@ -318,7 +265,7 @@ static ssize_t max_ptes_shared_store(struct kobject *kobj,
unsigned long max_ptes_shared;
err = kstrtoul(buf, 10, &max_ptes_shared);
- if (err || max_ptes_shared > KHUGEPAGED_MAX_PTES_LIMIT)
+ if (err || max_ptes_shared > COLLAPSE_MAX_PTES_LIMIT)
return -EINVAL;
khugepaged_max_ptes_shared = max_ptes_shared;
@@ -367,30 +314,27 @@ static bool pte_none_or_zero(pte_t pte)
static unsigned int collapse_max_ptes_none(struct collapse_control *cc,
struct vm_area_struct *vma, unsigned int order)
{
- const unsigned int max_ptes_none = khugepaged_max_ptes_none;
+ const unsigned int max_ptes_none = cc->policy.max_ptes_none;
if (vma && userfaultfd_armed(vma))
return 0;
- /* for MADV_COLLAPSE, allow any empty/shared zeropage PTEs */
- if (!cc->is_khugepaged)
- return HPAGE_PMD_NR;
- /* for PMD collapse, respect the user defined maximum */
- if (is_pmd_order(order))
+ /* The limit as given, at the PMD order and wherever it is not capped */
+ if (is_pmd_order(order) || !cc->policy.strict_sub_pmd)
return max_ptes_none;
/*
- * for mTHP collapse with the sysctl value set to KHUGEPAGED_MAX_PTES_LIMIT,
+ * for mTHP collapse with the sysctl value set to COLLAPSE_MAX_PTES_LIMIT,
* scale the maximum number of PTEs to the order of the collapse.
*/
- if (max_ptes_none == KHUGEPAGED_MAX_PTES_LIMIT)
+ if (max_ptes_none == COLLAPSE_MAX_PTES_LIMIT)
return (1 << order) - 1;
/*
- * For mTHP collapse of values other than 0 or KHUGEPAGED_MAX_PTES_LIMIT,
+ * For mTHP collapse of values other than 0 or COLLAPSE_MAX_PTES_LIMIT,
* emit a warning and return 0.
*/
if (max_ptes_none)
pr_warn_once("mTHP collapse does not support max_ptes_none"
" values other than 0 or %u, defaulting to 0.\n",
- KHUGEPAGED_MAX_PTES_LIMIT);
+ COLLAPSE_MAX_PTES_LIMIT);
return 0;
}
@@ -407,19 +351,12 @@ static unsigned int collapse_max_ptes_shared(struct collapse_control *cc,
unsigned int order)
{
/*
- * For MADV_COLLAPSE, do not restrict the number of PTEs that map shared
- * anonymous pages.
- */
- if (!cc->is_khugepaged)
- return HPAGE_PMD_NR;
- /*
- * for mTHP collapse do not allow collapsing anonymous memory pages that
- * are shared between processes.
+ * A sub-PMD window held to the strict rule takes no shared page at all:
+ * an mTHP is not worth the CoW-breaking.
*/
- if (!is_pmd_order(order))
+ if (!is_pmd_order(order) && cc->policy.strict_sub_pmd)
return 0;
- /* for PMD collapse, respect the user defined maximum */
- return khugepaged_max_ptes_shared;
+ return cc->policy.max_ptes_shared;
}
/**
@@ -435,16 +372,12 @@ static unsigned int collapse_max_ptes_swap(struct collapse_control *cc,
unsigned int order)
{
/*
- * For MADV_COLLAPSE, do not restrict the number PTEs entries or
- * pagecache entries that are non-present.
+ * A sub-PMD window held to the strict rule takes nothing non-present:
+ * reading pages back to build an mTHP is not worth the latency.
*/
- if (!cc->is_khugepaged)
- return HPAGE_PMD_NR;
- /* for mTHP collapse do not allow any non-present PTEs or pagecache entries */
- if (!is_pmd_order(order))
+ if (!is_pmd_order(order) && cc->policy.strict_sub_pmd)
return 0;
- /* for PMD collapse, respect the user defined maximum */
- return khugepaged_max_ptes_swap;
+ return cc->policy.max_ptes_swap;
}
int hugepage_madvise(struct vm_area_struct *vma,
@@ -476,7 +409,7 @@ int __init khugepaged_init(void)
return -ENOMEM;
khugepaged_pages_to_scan = HPAGE_PMD_NR * 8;
- khugepaged_max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
+ khugepaged_max_ptes_none = COLLAPSE_MAX_PTES_LIMIT;
khugepaged_max_ptes_swap = HPAGE_PMD_NR / 8;
khugepaged_max_ptes_shared = HPAGE_PMD_NR / 2;
@@ -565,7 +498,7 @@ void __khugepaged_enter(struct mm_struct *mm)
* Check what orders are possible based on the vma and collapse type.
* This is used to determine if mTHP collapse is a viable option.
*/
-static unsigned long collapse_possible_orders(struct vm_area_struct *vma,
+unsigned long collapse_possible_orders(struct vm_area_struct *vma,
vm_flags_t vm_flags, enum tva_type tva_flags)
{
unsigned long orders;
@@ -579,17 +512,11 @@ static unsigned long collapse_possible_orders(struct vm_area_struct *vma,
return thp_vma_allowable_orders(vma, vm_flags, tva_flags, orders);
}
-static bool collapse_possible(struct vm_area_struct *vma,
- vm_flags_t vm_flags, enum tva_type tva_flags)
-{
- return collapse_possible_orders(vma, vm_flags, tva_flags);
-}
-
void khugepaged_enter_vma(struct vm_area_struct *vma,
vm_flags_t vm_flags)
{
- if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) && hugepage_enabled()
- && collapse_possible(vma, vm_flags, TVA_KHUGEPAGED))
+ if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) && hugepage_enabled() &&
+ collapse_possible_orders(vma, vm_flags, TVA_KHUGEPAGED))
__khugepaged_enter(vma->vm_mm);
}
@@ -623,11 +550,11 @@ void __khugepaged_exit(struct mm_struct *mm)
}
}
-static void collapse_control_init_scan(struct collapse_control *cc)
+static void collapse_scan_reset(struct collapse_control *cc)
{
memset(cc->node_load, 0, sizeof(cc->node_load));
nodes_clear(cc->alloc_nmask);
- bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
+ bitmap_zero(cc->eligible_ptes, MAX_PTRS_PER_PTE);
}
static void release_pte_folio(struct folio *folio)
@@ -731,7 +658,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
* If the vma has the VM_DROPPABLE flag, the collapse will
* preserve the lazyfree property without needing to skip.
*/
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
+ if (cc->policy.skip_lazyfree && !(vma->vm_flags & VM_DROPPABLE) &&
folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
result = SCAN_PAGE_LAZYFREE;
goto out;
@@ -820,12 +747,12 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
if (folio_test_large(folio))
list_add_tail(&folio->lru, compound_pagelist);
next:
- if (cc->is_khugepaged &&
+ if (cc->policy.require_referenced &&
folio_pte_referenced(folio, vma, addr, pteval))
referenced++;
}
- if (unlikely(cc->is_khugepaged && !referenced)) {
+ if (unlikely(cc->policy.require_referenced && !referenced)) {
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
@@ -991,9 +918,7 @@ static void khugepaged_alloc_sleep(void)
remove_wait_queue(&khugepaged_wait, &wait);
}
-static struct collapse_control khugepaged_collapse_control = {
- .is_khugepaged = true,
-};
+static struct collapse_control khugepaged_collapse_control;
static bool collapse_scan_abort(int nid, struct collapse_control *cc)
{
@@ -1029,6 +954,21 @@ static inline gfp_t alloc_hugepage_khugepaged_gfpmask(void)
return khugepaged_defrag() ? GFP_TRANSHUGE : GFP_TRANSHUGE_LIGHT;
}
+/* khugepaged collapses on its own initiative, so it obeys its own settings */
+static void collapse_policy_khugepaged(struct collapse_policy *p)
+{
+ p->max_ptes_none = READ_ONCE(khugepaged_max_ptes_none);
+ p->max_ptes_swap = READ_ONCE(khugepaged_max_ptes_swap);
+ p->max_ptes_shared = READ_ONCE(khugepaged_max_ptes_shared);
+ p->strict_sub_pmd = true;
+ p->skip_lazyfree = true;
+ p->require_referenced = true;
+ p->install_pmd = false;
+ p->writeback_dirty = false;
+ p->gfp = alloc_hugepage_khugepaged_gfpmask();
+ p->tva_type = TVA_KHUGEPAGED;
+}
+
#ifdef CONFIG_NUMA
static int collapse_find_target_node(struct collapse_control *cc)
{
@@ -1061,13 +1001,12 @@ static int collapse_find_target_node(struct collapse_control *cc)
* Returns enum scan_result value.
*/
-static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned long address,
+enum scan_result collapse_vma_revalidate(struct mm_struct *mm, unsigned long address,
bool expect_anon, struct vm_area_struct **vmap,
struct collapse_control *cc, unsigned int order)
{
struct vm_area_struct *vma;
- enum tva_type type = cc->is_khugepaged ? TVA_KHUGEPAGED :
- TVA_FORCED_COLLAPSE;
+ enum tva_type type = cc->policy.tva_type;
if (unlikely(collapse_test_exit_or_disable(mm)))
return SCAN_ANY_PROCESS;
@@ -1250,8 +1189,7 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_struct *mm,
struct collapse_control *cc, unsigned int order)
{
- gfp_t gfp = (cc->is_khugepaged ? alloc_hugepage_khugepaged_gfpmask() :
- GFP_TRANSHUGE);
+ gfp_t gfp = cc->policy.gfp;
int node = collapse_find_target_node(cc);
struct folio *folio;
@@ -1311,7 +1249,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
}
mmap_read_lock(mm);
- result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
+ result = collapse_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
&vma, cc, order);
if (result != SCAN_SUCCEED) {
mmap_read_unlock(mm);
@@ -1346,7 +1284,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
* mmap_lock.
*/
mmap_write_lock(mm);
- result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
+ result = collapse_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
&vma, cc, order);
if (result != SCAN_SUCCEED)
goto out_up_write;
@@ -1482,15 +1420,15 @@ static unsigned int max_order_from_offset(unsigned int offset)
* mthp_collapse() consumes the bitmap that is generated during
* collapse_scan_pmd() to determine what regions and mTHP orders fit best.
*
- * Each bit in cc->mthp_present_ptes represents a single occupied (!none/zero)
- * page. We start at the PMD order and check if it is eligible for collapse;
+ * Each bit in cc->eligible_ptes marks a PTE the scan accepted as a collapse
+ * source. We start at the PMD order and check if it is eligible for collapse;
* if not, we check the left and right halves of the PTE page table we are
* examining at a lower order.
*
- * For each of these, we determine how many PTE entries are occupied in the
- * range of PTE entries we propose to collapse, then we compare this to a
- * threshold number of PTE entries which would need to be occupied for a
- * collapse to be permitted at that order (accounting for max_ptes_none).
+ * For each of these, we count the eligible PTEs in the range we propose to
+ * collapse, then we compare this to the number of eligible PTEs the range
+ * would need for a collapse to be permitted at that order (accounting for
+ * max_ptes_none).
*
* If a collapse is permitted, we attempt to collapse the PTE range into a
* mTHP.
@@ -1499,7 +1437,7 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
unsigned long address, int referenced, int unmapped,
struct collapse_control *cc, unsigned long enabled_orders)
{
- unsigned int nr_occupied_ptes, nr_ptes, max_ptes_none;
+ unsigned int nr_eligible_ptes, nr_ptes, max_ptes_none;
enum scan_result last_result = SCAN_FAIL;
int collapsed = 0;
bool alloc_failed = false;
@@ -1514,18 +1452,18 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
goto next_order;
max_ptes_none = collapse_max_ptes_none(cc, NULL, order);
- nr_occupied_ptes = bitmap_weight_from(cc->mthp_present_ptes, offset,
+ nr_eligible_ptes = bitmap_weight_from(cc->eligible_ptes, offset,
offset + nr_ptes);
/*
* Swap PTEs accepted during the scan are counted in @unmapped,
- * not in the present-PTE bitmap. Account them for the PMD-order
+ * not in cc->eligible_ptes. Account them for the PMD-order
* candidate.
*/
if (is_pmd_order(order))
- nr_occupied_ptes += unmapped;
+ nr_eligible_ptes += unmapped;
- if (nr_occupied_ptes >= nr_ptes - max_ptes_none) {
+ if (nr_eligible_ptes >= nr_ptes - max_ptes_none) {
enum scan_result ret;
collapse_address = address + offset * PAGE_SIZE;
@@ -1571,8 +1509,8 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
* any smaller order enabled. When at the smallest order
* we must always move to the next offset.
*/
- if (order > KHUGEPAGED_MIN_MTHP_ORDER &&
- (enabled_orders & GENMASK(order - 1, 0))) {
+ if (order > COLLAPSE_MIN_MTHP_ORDER &&
+ (enabled_orders & GENMASK(order - 1, 0))) {
order--;
continue;
}
@@ -1597,14 +1535,14 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
return last_result;
}
-static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
- struct vm_area_struct *vma, unsigned long start_addr,
- bool *lock_dropped, struct collapse_control *cc)
+static enum scan_result collapse_scan_anon_pmd(struct vm_area_struct *vma,
+ unsigned long start_addr, struct collapse_control *cc,
+ unsigned long enabled_orders)
{
const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
- enum tva_type tva_flags = cc->is_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;
+ struct mm_struct *mm = vma->vm_mm;
pmd_t *pmd;
pte_t *pte, *_pte, pteval;
int i;
@@ -1614,7 +1552,6 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
struct folio *folio = NULL;
unsigned long failed_pfn = -1;
unsigned long addr;
- unsigned long enabled_orders;
spinlock_t *ptl;
int node = NUMA_NO_NODE, unmapped = 0;
@@ -1626,9 +1563,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
goto out;
}
- collapse_control_init_scan(cc);
-
- enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags);
+ collapse_scan_reset(cc);
/*
* If PMD is the only enabled order, enforce max_ptes_none, otherwise
@@ -1636,7 +1571,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
* is then checked again in mthp_collapse() for each attempted order.
*/
if (enabled_orders != BIT(HPAGE_PMD_ORDER))
- max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
+ max_ptes_none = COLLAPSE_MAX_PTES_LIMIT;
pte = pte_offset_map_lock(mm, pmd, start_addr, &ptl);
if (!pte) {
@@ -1704,7 +1639,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
* If the vma has the VM_DROPPABLE flag, the collapse will
* preserve the lazyfree property without needing to skip.
*/
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
+ if (cc->policy.skip_lazyfree && !(vma->vm_flags & VM_DROPPABLE) &&
folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
result = SCAN_PAGE_LAZYFREE;
failed_pfn = folio_pfn(folio);
@@ -1731,8 +1666,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
}
}
- /* Set bit for occupied pages */
- __set_bit(i, cc->mthp_present_ptes);
+ /* The scan accepted this PTE as a collapse source */
+ __set_bit(i, cc->eligible_ptes);
/*
* Record which node the original page is from and save this
* information to cc->node_load[].
@@ -1770,13 +1705,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
goto out_unmap;
}
- if (cc->is_khugepaged &&
+ if (cc->policy.require_referenced &&
folio_pte_referenced(folio, vma, addr, pteval))
referenced++;
}
- if (cc->is_khugepaged &&
- (!referenced ||
- (unmapped && referenced < HPAGE_PMD_NR / 2))) {
+ if (cc->policy.require_referenced &&
+ (!referenced ||
+ (unmapped && referenced < HPAGE_PMD_NR / 2))) {
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
@@ -1784,12 +1719,9 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
out_unmap:
pte_unmap_unlock(pte, ptl);
if (result == SCAN_SUCCEED) {
- /* collapse_huge_page() expects the lock to be dropped before calling */
- mmap_read_unlock(mm);
- result = mthp_collapse(mm, start_addr, referenced,
- unmapped, cc, enabled_orders);
- /* mmap_lock was released above, set lock_dropped */
- *lock_dropped = true;
+ cc->scan_orders = enabled_orders;
+ cc->scan_referenced = referenced;
+ cc->scan_unmapped = unmapped;
}
out:
trace_mm_khugepaged_scan_pmd(mm, failed_pfn, referenced,
@@ -2635,11 +2567,11 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
xas_unlock_irq(&xas);
/*
- * Remove pte page tables, so we can re-fault the page as huge.
- * If MADV_COLLAPSE, adjust result to call try_collapse_pte_mapped_thp().
+ * Remove pte page tables, so we can re-fault the page as huge. A
+ * caller that wants the PMD mapped now is told to go and do that.
*/
retract_page_tables(mapping, start);
- if (cc && !cc->is_khugepaged)
+ if (cc->policy.install_pmd)
result = SCAN_PTE_MAPPED_HUGEPAGE;
folio_unlock(new_folio);
@@ -2704,7 +2636,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
present = 0;
swap = 0;
- collapse_control_init_scan(cc);
+ collapse_scan_reset(cc);
rcu_read_lock();
xas_for_each(&xas, folio, start + HPAGE_PMD_NR - 1) {
if (xas_retry(&xas, folio))
@@ -2786,53 +2718,99 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
else
cc->progress += HPAGE_PMD_NR;
- if (result == SCAN_SUCCEED) {
- if (present < HPAGE_PMD_NR - max_ptes_none) {
- result = SCAN_EXCEED_NONE_PTE;
- count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
- } else {
- result = collapse_file(mm, addr, file, start, cc);
- }
+ if (result == SCAN_SUCCEED && present < HPAGE_PMD_NR - max_ptes_none) {
+ result = SCAN_EXCEED_NONE_PTE;
+ count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
}
- trace_mm_khugepaged_scan_file(mm, failed_pfn, file, present, swap, result);
+ trace_mm_khugepaged_scan_file(mm, failed_pfn, file, present, swap,
+ result);
return result;
}
-/*
- * Try to collapse a single PMD starting at a PMD aligned addr, and return
- * the results.
- */
-static enum scan_result collapse_single_pmd(unsigned long addr,
- struct vm_area_struct *vma, bool *lock_dropped,
- struct collapse_control *cc)
+void collapse_control_init(struct collapse_control *cc)
+{
+ cc->progress = 0;
+ cc->scan_file = NULL;
+}
+
+void collapse_control_release(struct collapse_control *cc)
+{
+ /* A scan that took a file reference should have been run */
+ if (WARN_ON_ONCE(cc->scan_file)) {
+ fput(cc->scan_file);
+ cc->scan_file = NULL;
+ }
+}
+
+enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,
+ unsigned long addr, struct collapse_control *cc,
+ unsigned long orders)
{
- struct mm_struct *mm = vma->vm_mm;
- bool triggered_wb = false;
enum scan_result result;
- struct file *file;
pgoff_t pgoff;
- mmap_assert_locked(mm);
-
- if (vma_is_anonymous(vma)) {
- result = collapse_scan_pmd(mm, vma, addr, lock_dropped, cc);
- goto end;
+ mmap_assert_locked(vma->vm_mm);
+ /* Whatever the last scan found has to have been run by now */
+ if (WARN_ON_ONCE(cc->scan_file)) {
+ fput(cc->scan_file);
+ cc->scan_file = NULL;
}
- file = get_file(vma->vm_file);
- pgoff = linear_page_index(vma, addr);
+ if (vma_is_anonymous(vma))
+ return collapse_scan_anon_pmd(vma, addr, cc, orders);
- mmap_read_unlock(mm);
- *lock_dropped = true;
-retry:
- result = collapse_scan_file(mm, addr, file, pgoff, cc);
+ pgoff = linear_page_index(vma, addr);
+ result = collapse_scan_file(vma->vm_mm, addr, vma->vm_file, pgoff, cc);
+ switch (result) {
+ case SCAN_SUCCEED:
+ cc->scan_retract_only = false;
+ break;
+ case SCAN_PTE_MAPPED_HUGEPAGE:
+ /*
+ * The page cache already holds the PMD folio; what is left is
+ * to retract the PTE table, which is the run's job.
+ */
+ cc->scan_retract_only = true;
+ result = SCAN_SUCCEED;
+ break;
+ default:
+ return result;
+ }
/*
- * For MADV_COLLAPSE, when encountering dirty pages, try to writeback,
- * then retry the collapse one time.
+ * A file collapse works on the page cache and never sees a VMA, so take
+ * what it needs from this one while it is still here.
*/
- if (!cc->is_khugepaged && result == SCAN_PAGE_DIRTY_OR_WRITEBACK &&
+ cc->scan_file = get_file(vma->vm_file);
+ cc->scan_pgoff = pgoff;
+ return result;
+}
+
+enum scan_result collapse_run_pmd(struct mm_struct *mm, unsigned long addr,
+ struct collapse_control *cc)
+{
+ struct file *file = cc->scan_file;
+ bool triggered_wb = false;
+ enum scan_result result;
+ pgoff_t pgoff;
+
+ if (!file)
+ return mthp_collapse(mm, addr, cc->scan_referenced,
+ cc->scan_unmapped, cc, cc->scan_orders);
+
+ cc->scan_file = NULL;
+ pgoff = cc->scan_pgoff;
+
+ if (cc->scan_retract_only) {
+ result = SCAN_PTE_MAPPED_HUGEPAGE;
+ goto retract;
+ }
+retry:
+ result = collapse_file(mm, addr, file, pgoff, cc);
+
+ /* Dirty pages are worth a writeback and one more try, if asked for */
+ if (cc->policy.writeback_dirty && result == SCAN_PAGE_DIRTY_OR_WRITEBACK &&
!triggered_wb && mapping_can_writeback(file->f_mapping)) {
const loff_t lstart = (loff_t)pgoff << PAGE_SHIFT;
const loff_t lend = lstart + HPAGE_PMD_SIZE - 1;
@@ -2841,22 +2819,24 @@ static enum scan_result collapse_single_pmd(unsigned long addr,
triggered_wb = true;
goto retry;
}
+retract:
fput(file);
+ /*
+ * A PMD folio is in the page cache, whether the collapse just put it
+ * there or found it: retract the PTE table, and map the PMD if asked.
+ */
if (result == SCAN_PTE_MAPPED_HUGEPAGE) {
mmap_read_lock(mm);
if (collapse_test_exit_or_disable(mm))
result = SCAN_ANY_PROCESS;
else
result = try_collapse_pte_mapped_thp(mm, addr,
- !cc->is_khugepaged);
+ cc->policy.install_pmd);
if (result == SCAN_PMD_MAPPED)
result = SCAN_SUCCEED;
mmap_read_unlock(mm);
}
-end:
- if (cc->is_khugepaged && result == SCAN_SUCCEED)
- ++khugepaged_pages_collapsed;
return result;
}
@@ -2899,14 +2879,17 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
vma_iter_init(&vmi, mm, khugepaged_scan.address);
for_each_vma(vmi, vma) {
- unsigned long hstart, hend;
+ unsigned long hstart, hend, orders;
cond_resched();
if (unlikely(collapse_test_exit_or_disable(mm))) {
cc->progress++;
break;
}
- if (!collapse_possible(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
+ /* One mask for the whole VMA */
+ orders = collapse_possible_orders(vma, vma->vm_flags,
+ cc->policy.tva_type);
+ if (!orders) {
cc->progress++;
continue;
}
@@ -2921,7 +2904,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
VM_BUG_ON(khugepaged_scan.address & ~HPAGE_PMD_MASK);
while (khugepaged_scan.address < hend) {
- bool lock_dropped = false;
+ unsigned long addr;
cond_resched();
if (unlikely(collapse_test_exit_or_disable(mm)))
@@ -2931,21 +2914,29 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
khugepaged_scan.address + HPAGE_PMD_SIZE >
hend);
- *result = collapse_single_pmd(khugepaged_scan.address,
- vma, &lock_dropped, cc);
+ addr = khugepaged_scan.address;
/* move to next address */
khugepaged_scan.address += HPAGE_PMD_SIZE;
- if (lock_dropped)
- /*
- * We released mmap_lock so break loop. Note
- * that we drop mmap_lock before all hugepage
- * allocations, so if allocation fails, we are
- * guaranteed to break here and report the
- * correct result back to caller.
- */
- goto breakouterloop_mmap_lock;
- if (cc->progress >= progress_max)
- goto breakouterloop;
+
+ *result = collapse_scan_pmd(vma, addr, cc, orders);
+ /* Nothing to collapse here, and the lock is still ours */
+ if (*result != SCAN_SUCCEED) {
+ if (cc->progress >= progress_max)
+ goto breakouterloop;
+ continue;
+ }
+
+ /*
+ * A collapse takes its own locks and is slow enough
+ * that a writer should not wait behind it, so give the
+ * lock up. That ends this walk: vma and the mm are
+ * whatever the collapse leaves them.
+ */
+ mmap_read_unlock(mm);
+ *result = collapse_run_pmd(mm, addr, cc);
+ if (*result == SCAN_SUCCEED)
+ khugepaged_pages_collapsed++;
+ goto breakouterloop_mmap_lock;
}
}
breakouterloop:
@@ -2999,7 +2990,10 @@ static void khugepaged_do_scan(struct collapse_control *cc)
lru_add_drain_all();
- cc->progress = 0;
+ collapse_control_init(cc);
+ /* One policy for the whole pass, so every table is judged the same */
+ collapse_policy_khugepaged(&cc->policy);
+
while (true) {
cond_resched();
@@ -3030,6 +3024,8 @@ static void khugepaged_do_scan(struct collapse_control *cc)
khugepaged_alloc_sleep();
}
}
+
+ collapse_control_release(cc);
}
static bool khugepaged_should_wakeup(void)
@@ -3168,126 +3164,3 @@ bool current_is_khugepaged(void)
{
return kthread_func(current) == khugepaged;
}
-
-static int madvise_collapse_errno(enum scan_result r)
-{
- /*
- * MADV_COLLAPSE breaks from existing madvise(2) conventions to provide
- * actionable feedback to caller, so they may take an appropriate
- * fallback measure depending on the nature of the failure.
- */
- switch (r) {
- case SCAN_ALLOC_HUGE_PAGE_FAIL:
- return -ENOMEM;
- case SCAN_CGROUP_CHARGE_FAIL:
- case SCAN_EXCEED_NONE_PTE:
- return -EBUSY;
- /* Resource temporary unavailable - trying again might succeed */
- case SCAN_PAGE_COUNT:
- case SCAN_PAGE_LOCK:
- case SCAN_PAGE_LRU:
- case SCAN_DEL_PAGE_LRU:
- case SCAN_PAGE_FILLED:
- case SCAN_PAGE_HAS_PRIVATE:
- case SCAN_PAGE_DIRTY_OR_WRITEBACK:
- return -EAGAIN;
- /*
- * Other: Trying again likely not to succeed / error intrinsic to
- * specified memory range. khugepaged likely won't be able to collapse
- * either.
- */
- default:
- return -EINVAL;
- }
-}
-
-int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
- unsigned long end, bool *lock_dropped)
-{
- struct collapse_control *cc;
- struct mm_struct *mm = vma->vm_mm;
- unsigned long hstart, hend, addr;
- enum scan_result last_fail = SCAN_FAIL;
- int thps = 0;
- bool mmap_unlocked = false;
-
- BUG_ON(vma->vm_start > start);
- BUG_ON(vma->vm_end < end);
-
- if (!collapse_possible(vma, vma->vm_flags, TVA_FORCED_COLLAPSE))
- return -EINVAL;
-
- hstart = ALIGN(start, HPAGE_PMD_SIZE);
- hend = ALIGN_DOWN(end, HPAGE_PMD_SIZE);
-
- if (hstart >= hend)
- return 0;
-
- cc = kmalloc_obj(*cc);
- if (!cc)
- return -ENOMEM;
- cc->is_khugepaged = false;
- cc->progress = 0;
-
- mmgrab(mm);
- lru_add_drain_all();
-
- for (addr = hstart; addr < hend; addr += HPAGE_PMD_SIZE) {
- enum scan_result result = SCAN_FAIL;
-
- if (mmap_unlocked) {
- cond_resched();
- mmap_read_lock(mm);
- mmap_unlocked = false;
- *lock_dropped = true;
- result = hugepage_vma_revalidate(mm, addr, false, &vma,
- cc, HPAGE_PMD_ORDER);
- if (result != SCAN_SUCCEED) {
- last_fail = result;
- goto out_nolock;
- }
-
- hend = min(hend, vma->vm_end & HPAGE_PMD_MASK);
- }
-
- result = collapse_single_pmd(addr, vma, &mmap_unlocked, cc);
-
- switch (result) {
- case SCAN_SUCCEED:
- case SCAN_PMD_MAPPED:
- ++thps;
- break;
- /* Whitelisted set of results where continuing OK */
- case SCAN_NO_PTE_TABLE:
- case SCAN_PTE_NON_PRESENT:
- case SCAN_PTE_UFFD:
- case SCAN_LACK_REFERENCED_PAGE:
- case SCAN_PAGE_NULL:
- case SCAN_PAGE_COUNT:
- case SCAN_PAGE_LOCK:
- case SCAN_PAGE_COMPOUND:
- case SCAN_PAGE_LRU:
- case SCAN_DEL_PAGE_LRU:
- last_fail = result;
- break;
- default:
- last_fail = result;
- /* Other error, exit */
- goto out_maybelock;
- }
- }
-
-out_maybelock:
- /* Caller expects us to hold mmap_lock on return */
- if (mmap_unlocked) {
- *lock_dropped = true;
- mmap_read_lock(mm);
- }
-out_nolock:
- mmap_assert_locked(mm);
- mmdrop(mm);
- kfree(cc);
-
- return thps == ((hend - hstart) >> HPAGE_PMD_SHIFT) ? 0
- : madvise_collapse_errno(last_fail);
-}
diff --git a/mm/madvise.c b/mm/madvise.c
index 963337f93a7a1..f75a9d1399803 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -38,6 +38,7 @@
#include "internal.h"
#include "swap.h"
+#include "collapse.h"
#define __MADV_SET_ANON_VMA_NAME (-1)
@@ -906,6 +907,171 @@ bool madvise_dontneed_free_valid_vma(struct madvise_behavior *madv_behavior)
return true;
}
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+
+/* MADV_COLLAPSE was asked for explicitly, so it is not held to those */
+static void collapse_policy_forced(struct collapse_policy *p)
+{
+ p->max_ptes_none = HPAGE_PMD_NR;
+ p->max_ptes_swap = HPAGE_PMD_NR;
+ p->max_ptes_shared = HPAGE_PMD_NR;
+ p->strict_sub_pmd = false;
+ p->skip_lazyfree = false;
+ p->require_referenced = false;
+ p->install_pmd = true;
+ p->writeback_dirty = true;
+ p->gfp = GFP_TRANSHUGE;
+ p->tva_type = TVA_FORCED_COLLAPSE;
+}
+
+static int madvise_collapse_errno(enum scan_result r)
+{
+ /*
+ * MADV_COLLAPSE breaks from existing madvise(2) conventions to provide
+ * actionable feedback to caller, so they may take an appropriate
+ * fallback measure depending on the nature of the failure.
+ */
+ switch (r) {
+ case SCAN_ALLOC_HUGE_PAGE_FAIL:
+ return -ENOMEM;
+ case SCAN_CGROUP_CHARGE_FAIL:
+ case SCAN_EXCEED_NONE_PTE:
+ return -EBUSY;
+ /* Resource temporary unavailable - trying again might succeed */
+ case SCAN_PAGE_COUNT:
+ case SCAN_PAGE_LOCK:
+ case SCAN_PAGE_LRU:
+ case SCAN_DEL_PAGE_LRU:
+ case SCAN_PAGE_FILLED:
+ case SCAN_PAGE_HAS_PRIVATE:
+ case SCAN_PAGE_DIRTY_OR_WRITEBACK:
+ return -EAGAIN;
+ /*
+ * Other: Trying again likely not to succeed / error intrinsic to
+ * specified memory range. khugepaged likely won't be able to collapse
+ * either.
+ */
+ default:
+ return -EINVAL;
+ }
+}
+
+static int madvise_collapse(struct madvise_behavior *madv_behavior)
+{
+ struct madvise_behavior_range *range = &madv_behavior->range;
+ struct vm_area_struct *vma = madv_behavior->vma;
+ struct mm_struct *mm = madv_behavior->mm;
+ struct collapse_control *cc;
+ unsigned long hstart, hend, addr, orders;
+ enum scan_result last_fail = SCAN_FAIL;
+ int thps = 0;
+
+ BUG_ON(vma->vm_start > range->start);
+ BUG_ON(vma->vm_end < range->end);
+
+ orders = collapse_possible_orders(vma, vma->vm_flags,
+ TVA_FORCED_COLLAPSE);
+ if (!orders)
+ return -EINVAL;
+
+ hstart = ALIGN(range->start, HPAGE_PMD_SIZE);
+ hend = ALIGN_DOWN(range->end, HPAGE_PMD_SIZE);
+
+ if (hstart >= hend)
+ return 0;
+
+ cc = kmalloc_obj(*cc);
+ if (!cc)
+ return -ENOMEM;
+ collapse_control_init(cc);
+ collapse_policy_forced(&cc->policy);
+
+ lru_add_drain_all();
+
+ for (addr = hstart; addr < hend; addr += HPAGE_PMD_SIZE) {
+ struct vm_area_struct *found;
+ enum scan_result result;
+
+ /*
+ * A collapse gives the lock up, so the VMA has to be found
+ * again after one: it can shrink while nothing is held. A scan
+ * that finds nothing to collapse leaves the lock alone, so a
+ * range that is already collapsed walks on without relocking.
+ */
+ if (!vma) {
+ cond_resched();
+ mmap_read_lock(mm);
+ result = collapse_vma_revalidate(mm, addr, false, &found,
+ cc, HPAGE_PMD_ORDER);
+ if (result != SCAN_SUCCEED) {
+ last_fail = result;
+ goto out_locked;
+ }
+ vma = found;
+ hend = min(hend, vma->vm_end & HPAGE_PMD_MASK);
+ orders = collapse_possible_orders(vma, vma->vm_flags,
+ cc->policy.tva_type);
+ }
+
+ result = collapse_scan_pmd(vma, addr, cc, orders);
+ /* Nothing to collapse here, and the lock is still ours */
+ if (result != SCAN_SUCCEED)
+ goto tally;
+
+ /* The collapse takes its own locks, so give this up */
+ mmap_read_unlock(mm);
+ mark_mmap_lock_dropped(madv_behavior);
+ vma = NULL;
+
+ result = collapse_run_pmd(mm, addr, cc);
+tally:
+ switch (result) {
+ case SCAN_SUCCEED:
+ case SCAN_PMD_MAPPED:
+ ++thps;
+ break;
+ /* Whitelisted set of results where continuing OK */
+ case SCAN_NO_PTE_TABLE:
+ case SCAN_PTE_NON_PRESENT:
+ case SCAN_PTE_UFFD:
+ case SCAN_LACK_REFERENCED_PAGE:
+ case SCAN_PAGE_NULL:
+ case SCAN_PAGE_COUNT:
+ case SCAN_PAGE_LOCK:
+ case SCAN_PAGE_COMPOUND:
+ case SCAN_PAGE_LRU:
+ case SCAN_DEL_PAGE_LRU:
+ last_fail = result;
+ break;
+ default:
+ last_fail = result;
+ /* Other error, exit */
+ goto out;
+ }
+ }
+
+out:
+ /* Caller expects us to hold mmap_lock on return */
+ if (!vma)
+ mmap_read_lock(mm);
+out_locked:
+ mmap_assert_locked(mm);
+ collapse_control_release(cc);
+ kfree(cc);
+
+ return thps == ((hend - hstart) >> HPAGE_PMD_SHIFT) ? 0
+ : madvise_collapse_errno(last_fail);
+}
+
+#else /* CONFIG_TRANSPARENT_HUGEPAGE */
+
+static int madvise_collapse(struct madvise_behavior *madv_behavior)
+{
+ return -EINVAL;
+}
+
+#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
+
static long madvise_dontneed_free(struct madvise_behavior *madv_behavior)
{
struct mm_struct *mm = madv_behavior->mm;
@@ -1373,8 +1539,7 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)
case MADV_DONTNEED_LOCKED:
return madvise_dontneed_free(madv_behavior);
case MADV_COLLAPSE:
- return madvise_collapse(vma, range->start, range->end,
- &madv_behavior->lock_dropped);
+ return madvise_collapse(madv_behavior);
case MADV_GUARD_INSTALL:
return madvise_guard_install(madv_behavior);
case MADV_GUARD_REMOVE:
|
| 13/3 | 2026/09/10 13:43 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 18623 output: 18 thoughts: 2414 Thoughts: **Kernel Patch Evaluation for KMSAN Fuzzing Justification** Alright, let's break this down. The objective is to determine whether this kernel patch warrants a dedicated KMSAN fuzzing session, in addition to our standard KASAN efforts. This is a refactoring effort related to huge page collapse logic, specifically touching `khugepaged.c`, `mm/madvise.c`, and `mm/collapse.h`. First, I see the core of the patch is about organizing and clarifying the huge page collapse process. The code is modularized by moving collapse data structures and helper declarations into a new header, `mm/collapse.h`. Also, a new `struct collapse_policy` is introduced to define and encapsulate parameters/policies for collapse operations. Furthermore, `madvise_collapse` is moved from `khugepaged.c` into `madvise.c`. Finally, we have refactoring of scanning and running phases into discrete functions. Now, a critical aspect that draws my attention is the modification to `struct collapse_control`. The old structure contained basic control flags. The new `struct collapse_control` contains a `struct collapse_policy` and other fields that hold transient state between scan and run phases of the collapse. We have `scan_orders`, `scan_referenced`, `scan_unmapped`, `scan_file`, `scan_pgoff`, and `scan_retract_only`. These are all related to specific file-backed or anonymous VMAs being processed. Now, the main concern I see is proper initialization and usage of this new struct, especially given that it is allocated dynamically in `madvise.c` with `kmalloc_obj(*cc)`. My initial reaction is, is this zero-initialized, or are we potentially introducing uninitialized reads. We need to meticulously track initialization in both static (`khugepaged.c`) and dynamic (`madvise.c`) allocation cases. Specifically, I need to check: 1. The static instance in `khugepaged.c` is zero-initialized and initialized through `collapse_control_init` and `collapse_policy_khugepaged`. 2. The dynamic instance in `madvise.c` by `kmalloc_obj(*cc)` vs `kzalloc` is the main thing to watch. I am suspicious, since `kmalloc_obj` does not zero allocate. `collapse_control_init` initializes `progress` to 0, and `scan_file` to `NULL`. The `collapse_policy_forced` function initializes several `struct collapse_policy` members. This looks okay, but I need to make sure I don't miss anything. Then, I need to check how `collapse_scan_reset` behaves. It calls `memset` on `node_load`, clears `alloc_nmask` with `nodes_clear`, and zeros `eligible_ptes` using `bitmap_zero`. I also see that `cc->scan_file` can either be `NULL`, or point to a file, depending on whether we are dealing with a file-backed or anonymous PMD. I'm now tracing the control flow, especially where we have a failure, i.e., `SCAN_SUCCEED` is not returned from `collapse_scan_pmd`. In this case, control jumps to `tally`, but the failure is from calling `collapse_scan_pmd`. `cc` is not read. This is probably safe. Also, I have to compare the new with the old code. I need to check if the old code had similar initialization issues. I need to make sure that the old code was not using uninitialized values, so the change is not a regression. In any case, this is where the potential KMSAN concerns begin. I have to verify that all the members of `struct collapse_control` are properly initialized and that there are no uninitialized reads during both scan and run phases. I need to meticulously review the code. If necessary, I can use grepper or read the files to check all fields and usage across the code, to ensure that this isn't a source of uninitialized reads or memory corruption. |
| 14/3 | 2026/09/10 13:44 | tool | grepper |
0mArgs:{
"Expression": "kmalloc_obj"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 15611 lines.\nUse more precise expression if possible.\n\nDocumentation/RCU/Design/Requirements/Requirements.rst=203=non-\\ ``NULL``, locklessly accessing the ``-\u003ea`` and ``-\u003eb`` fields.\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst-208- 2 {\nDocumentation/RCU/Design/Requirements/Requirements.rst:209: 3 p = kmalloc_obj(*p);\nDocumentation/RCU/Design/Requirements/Requirements.rst-210- 4 if (!p)\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst=225=their rights to reorder this code as follows:\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst-230- 2 {\nDocumentation/RCU/Design/Requirements/Requirements.rst:231: 3 p = kmalloc_obj(*p);\nDocumentation/RCU/Design/Requirements/Requirements.rst-232- 4 if (!p)\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst=261=shows an example of insertion:\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst-266- 2 {\nDocumentation/RCU/Design/Requirements/Requirements.rst:267: 3 p = kmalloc_obj(*p);\nDocumentation/RCU/Design/Requirements/Requirements.rst-268- 4 if (!p)\n--\nDocumentation/RCU/listRCU.rst=267=The RCU version of audit_upd_rule() is as follows::\n--\nDocumentation/RCU/listRCU.rst-278-\t\t\tif (!audit_compare_rule(rule, \u0026e-\u003erule)) {\nDocumentation/RCU/listRCU.rst:279:\t\t\t\tne = kmalloc_obj(*entry, GFP_ATOMIC);\nDocumentation/RCU/listRCU.rst-280-\t\t\t\tif (ne == NULL)\n--\nDocumentation/RCU/rcu_dereference.rst=225=precautions. To see this, consider the following code fragment::\n--\nDocumentation/RCU/rcu_dereference.rst-238-\nDocumentation/RCU/rcu_dereference.rst:239:\t\tp = kmalloc_obj(*p);\nDocumentation/RCU/rcu_dereference.rst-240-\t\tif (p == NULL)\n--\nDocumentation/RCU/rcu_dereference.rst=281=Then one approach is to use locking, for example, as follows::\n--\nDocumentation/RCU/rcu_dereference.rst-295-\nDocumentation/RCU/rcu_dereference.rst:296:\t\tp = kmalloc_obj(*p);\nDocumentation/RCU/rcu_dereference.rst-297-\t\tif (p == NULL)\n--\nDocumentation/RCU/whatisRCU.rst=441=uses of RCU may be found in listRCU.rst and NMI-RCU.rst.\n--\nDocumentation/RCU/whatisRCU.rst-470-\nDocumentation/RCU/whatisRCU.rst:471:\t\tnew_fp = kmalloc_obj(*new_fp);\nDocumentation/RCU/whatisRCU.rst-472-\t\tspin_lock(\u0026foo_mutex);\n--\nDocumentation/RCU/whatisRCU.rst=553=The foo_update_a() function might then be written as follows::\n--\nDocumentation/RCU/whatisRCU.rst-572-\nDocumentation/RCU/whatisRCU.rst:573:\t\tnew_fp = kmalloc_obj(*new_fp);\nDocumentation/RCU/whatisRCU.rst-574-\t\tspin_lock(\u0026foo_mutex);\n--\nDocumentation/core-api/kref.rst=39=kref_init as so::\n--\nDocumentation/core-api/kref.rst-42-\nDocumentation/core-api/kref.rst:43: data = kmalloc_obj(*data);\nDocumentation/core-api/kref.rst-44- if (!data)\n--\nDocumentation/core-api/kref.rst=81=thread to process::\n--\nDocumentation/core-api/kref.rst-102-\tstruct task_struct *task;\nDocumentation/core-api/kref.rst:103:\tdata = kmalloc_obj(*data);\nDocumentation/core-api/kref.rst-104-\tif (!data)\n--\nDocumentation/core-api/memory-allocation.rst=149=conveniently allocate a single object or arrays of objects with kzalloc_obj()\nDocumentation/core-api/memory-allocation.rst:150:and kmalloc_obj() and their array versions kzalloc_objs() and\nDocumentation/core-api/memory-allocation.rst:151:kmalloc_objs(). These helpers only need the type of the object that should be\nDocumentation/core-api/memory-allocation.rst-152-allocated and the count of elements in the array for the array versions.\n--\nDocumentation/kernel-hacking/locking.rst=383=to protect the cache and all the objects within it. Here's the code::\n--\nDocumentation/kernel-hacking/locking.rst-444-\nDocumentation/kernel-hacking/locking.rst:445: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/kernel-hacking/locking.rst-446- return -ENOMEM;\n--\nDocumentation/kernel-hacking/locking.rst=499=which are taken away, and the ``+`` are lines which are added.\n--\nDocumentation/kernel-hacking/locking.rst-519-\nDocumentation/kernel-hacking/locking.rst:520: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/kernel-hacking/locking.rst-521- return -ENOMEM;\n--\nDocumentation/locking/locktypes.rst=498=works perfectly::\n--\nDocumentation/locking/locktypes.rst-500- raw_spin_lock(\u0026lock);\nDocumentation/locking/locktypes.rst:501: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/locking/locktypes.rst-502-\n--\nDocumentation/locking/locktypes.rst=507=preemption on PREEMPT_RT kernels::\n--\nDocumentation/locking/locktypes.rst-509- spin_lock(\u0026lock);\nDocumentation/locking/locktypes.rst:510: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/locking/locktypes.rst-511-\n--\nDocumentation/process/coding-style.rst=938=The kernel provides the following general purpose memory allocators:\nDocumentation/process/coding-style.rst:939:kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), and\nDocumentation/process/coding-style.rst-940-vzalloc(). Please refer to the API documentation for further information\n--\nDocumentation/process/coding-style.rst=944=The preferred form for passing a size of a struct is the following:\n--\nDocumentation/process/coding-style.rst-947-\nDocumentation/process/coding-style.rst:948:\tp = kmalloc_obj(*p, ...);\nDocumentation/process/coding-style.rst-949-\n--\nDocumentation/process/coding-style.rst=958=The preferred form for allocating an array is the following:\n--\nDocumentation/process/coding-style.rst-961-\nDocumentation/process/coding-style.rst:962:\tp = kmalloc_objs(*p, n, ...);\nDocumentation/process/coding-style.rst-963-\n--\nDocumentation/process/deprecated.rst=386=may help with alignment, wrap-around, or additional hardening. The\nDocumentation/process/deprecated.rst:387:kmalloc_obj()-family of macros provide this introspection, which can be\nDocumentation/process/deprecated.rst-388-used for the common code patterns for single, array, and flexible object\n--\nDocumentation/process/deprecated.rst=398=become, respectively::\nDocumentation/process/deprecated.rst-399-\nDocumentation/process/deprecated.rst:400:\tptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst-401-\tptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst:402:\tptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst-403-\tptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst-404-\tptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\nDocumentation/process/deprecated.rst:405:\t__auto_type ptr = kmalloc_obj(struct foo [, gfp] );\nDocumentation/process/deprecated.rst-406-\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=1734=callback::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-1739- ....\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:1740: data = kmalloc_obj(*data);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-1741- substream-\u003eruntime-\u003eprivate_data = data;\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=3302=destructor function is set in the ``private_free`` field::\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3303-\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:3304: struct mydata *p = kmalloc_obj(*p);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3305- hw-\u003eprivate_data = p;\n--\nDocumentation/spi/spi-summary.rst=242=And SOC-specific utility code might look something like::\n--\nDocumentation/spi/spi-summary.rst-251-\nDocumentation/spi/spi-summary.rst:252:\t\tpdata2 = kmalloc_obj(*pdata2);\nDocumentation/spi/spi-summary.rst-253-\t\t*pdata2 = pdata;\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst=403=e tutti gli oggetti che contiene. Ecco il codice::\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-464-\nDocumentation/translations/it_IT/kernel-hacking/locking.rst:465: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-466- return -ENOMEM;\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst=519=sono quelle rimosse, mentre quelle ``+`` sono quelle aggiunte.\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-539-\nDocumentation/translations/it_IT/kernel-hacking/locking.rst:540: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-541- return -ENOMEM;\n--\nDocumentation/translations/it_IT/locking/locktypes.rst=488=memoria. Su un kernel non-PREEMPT_RT il seguente codice funziona perfettamente::\n--\nDocumentation/translations/it_IT/locking/locktypes.rst-490- raw_spin_lock(\u0026lock);\nDocumentation/translations/it_IT/locking/locktypes.rst:491: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/translations/it_IT/locking/locktypes.rst-492-\n--\nDocumentation/translations/it_IT/locking/locktypes.rst=497=PREEMPT_RT::\n--\nDocumentation/translations/it_IT/locking/locktypes.rst-499- spin_lock(\u0026lock);\nDocumentation/translations/it_IT/locking/locktypes.rst:500: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/translations/it_IT/locking/locktypes.rst-501-\n--\nDocumentation/translations/it_IT/process/coding-style.rst=940=Il kernel fornisce i seguenti assegnatori ad uso generico:\nDocumentation/translations/it_IT/process/coding-style.rst:941:kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), e vzalloc().\nDocumentation/translations/it_IT/process/coding-style.rst-942-Per maggiori informazioni, consultate la documentazione dell'API:\n--\nDocumentation/translations/it_IT/process/coding-style.rst=945=Il modo preferito per passare la dimensione di una struttura è il seguente:\n--\nDocumentation/translations/it_IT/process/coding-style.rst-948-\nDocumentation/translations/it_IT/process/coding-style.rst:949:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/it_IT/process/coding-style.rst-950-\n--\nDocumentation/translations/it_IT/process/coding-style.rst=959=Il modo preferito per assegnare un vettore è il seguente:\n--\nDocumentation/translations/it_IT/process/coding-style.rst-962-\nDocumentation/translations/it_IT/process/coding-style.rst:963:\tp = kmalloc_objs(*p, n, ...);\nDocumentation/translations/it_IT/process/coding-style.rst-964-\n--\nDocumentation/translations/it_IT/process/deprecated.rst=428=l'overflow, o per un ulteriore irrobustimento. Le macro della famiglia\nDocumentation/translations/it_IT/process/deprecated.rst:429:kmalloc_obj() forniscono questa introspezione, e possono essere usate\nDocumentation/translations/it_IT/process/deprecated.rst-430-per i più comuni schemi di codice per l'allocazione di un singolo\n--\nDocumentation/translations/it_IT/process/deprecated.rst=441=diventano, rispettivamente::\nDocumentation/translations/it_IT/process/deprecated.rst-442-\nDocumentation/translations/it_IT/process/deprecated.rst:443:\tptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/translations/it_IT/process/deprecated.rst-444-\tptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/translations/it_IT/process/deprecated.rst:445:\tptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/translations/it_IT/process/deprecated.rst-446-\tptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/translations/it_IT/process/deprecated.rst-447-\tptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\nDocumentation/translations/it_IT/process/deprecated.rst:448:\t__auto_type ptr = kmalloc_obj(struct foo [, gfp] );\nDocumentation/translations/it_IT/process/deprecated.rst-449-\n--\nDocumentation/translations/pt_BR/process/deprecated.rst=397=possa ajudar com alinhamento, estouros de capacidade (*wrap-around*) ou\nDocumentation/translations/pt_BR/process/deprecated.rst:398:proteções adicionais (*hardening*). A família de macros kmalloc_obj() fornece\nDocumentation/translations/pt_BR/process/deprecated.rst-399-essa introspecção, que pode ser usada para os padrões de código comuns de\n--\nDocumentation/translations/pt_BR/process/deprecated.rst=410=tornam-se, respectivamente::\nDocumentation/translations/pt_BR/process/deprecated.rst-411-\nDocumentation/translations/pt_BR/process/deprecated.rst:412: ptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-413- ptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst:414: ptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-415- ptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-416- ptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst:417: __auto_type ptr = kmalloc_obj(struct foo [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-418-\n--\nDocumentation/translations/sp_SP/process/coding-style.rst=954=La forma preferida para pasar el tamaño de una estructura es la siguiente:\n--\nDocumentation/translations/sp_SP/process/coding-style.rst-957-\nDocumentation/translations/sp_SP/process/coding-style.rst:958:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/sp_SP/process/coding-style.rst-959-\n--\nDocumentation/translations/zh_CN/core-api/kref.rst=46=kref可以出现在数据结构体中的任何地方。\n--\nDocumentation/translations/zh_CN/core-api/kref.rst-54-\nDocumentation/translations/zh_CN/core-api/kref.rst:55: data = kmalloc_obj(*data);\nDocumentation/translations/zh_CN/core-api/kref.rst-56- if (!data)\n--\nDocumentation/translations/zh_CN/core-api/kref.rst=62=Kref规则\n--\nDocumentation/translations/zh_CN/core-api/kref.rst-108-\tstruct task_struct *task;\nDocumentation/translations/zh_CN/core-api/kref.rst:109:\tdata = kmalloc_obj(*data);\nDocumentation/translations/zh_CN/core-api/kref.rst-110-\tif (!data)\n--\nDocumentation/translations/zh_CN/process/coding-style.rst=810=Documentation/translations/zh_CN/core-api/memory-allocation.rst 。\n--\nDocumentation/translations/zh_CN/process/coding-style.rst-815-\nDocumentation/translations/zh_CN/process/coding-style.rst:816:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/zh_CN/process/coding-style.rst-817-\n--\nDocumentation/translations/zh_TW/process/coding-style.rst=824=Documentation/translations/zh_CN/core-api/memory-allocation.rst 。\n--\nDocumentation/translations/zh_TW/process/coding-style.rst-829-\nDocumentation/translations/zh_TW/process/coding-style.rst:830:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/zh_TW/process/coding-style.rst-831-\n--\narch/alpha/kernel/core_marvel.c=857=marvel_agp_setup(alpha_agp_info *agp)\n--\narch/alpha/kernel/core_marvel.c-863-\narch/alpha/kernel/core_marvel.c:864:\taper = kmalloc_obj(*aper);\narch/alpha/kernel/core_marvel.c-865-\tif (aper == NULL) return -ENOMEM;\n--\narch/alpha/kernel/core_marvel.c=1019=marvel_agp_info(void)\n--\narch/alpha/kernel/core_marvel.c-1061-\t */\narch/alpha/kernel/core_marvel.c:1062:\tagp = kmalloc_obj(*agp);\narch/alpha/kernel/core_marvel.c-1063-\tif (!agp)\n--\narch/alpha/kernel/core_titan.c=590=titan_agp_setup(alpha_agp_info *agp)\n--\narch/alpha/kernel/core_titan.c-596-\narch/alpha/kernel/core_titan.c:597:\taper = kmalloc_obj(struct titan_agp_aperture);\narch/alpha/kernel/core_titan.c-598-\tif (aper == NULL)\n--\narch/alpha/kernel/core_titan.c=731=titan_agp_info(void)\n--\narch/alpha/kernel/core_titan.c-762-\t */\narch/alpha/kernel/core_titan.c:763:\tagp = kmalloc_obj(*agp);\narch/alpha/kernel/core_titan.c-764-\tif (!agp)\n--\narch/alpha/kernel/module.c=29=process_reloc_for_got(Elf64_Rela *rela,\n--\narch/alpha/kernel/module.c-48-\narch/alpha/kernel/module.c:49:\tg = kmalloc_obj(*g);\narch/alpha/kernel/module.c-50-\tg-\u003enext = chains[r_sym].next;\n--\narch/alpha/kernel/pci.c=211=static void pdev_save_srm_config(struct pci_dev *dev)\n--\narch/alpha/kernel/pci.c-223-\narch/alpha/kernel/pci.c:224:\ttmp = kmalloc_obj(*tmp);\narch/alpha/kernel/pci.c-225-\tif (!tmp) {\n--\narch/arc/kernel/unwind.c=359=void *unwind_add_table(struct module *module, const void *table_start,\n--\narch/arc/kernel/unwind.c-368-\narch/arc/kernel/unwind.c:369:\ttable = kmalloc_obj(*table);\narch/arc/kernel/unwind.c-370-\tif (!table)\n--\narch/arm/common/locomo.c=274=static int locomo_suspend(struct platform_device *dev, pm_message_t state)\n--\narch/arm/common/locomo.c-279-\narch/arm/common/locomo.c:280:\tsave = kmalloc_obj(struct locomo_save_data);\narch/arm/common/locomo.c-281-\tif (!save)\n--\narch/arm/common/sa1111.c=964=static int sa1111_suspend_noirq(struct device *dev)\n--\narch/arm/common/sa1111.c-971-\narch/arm/common/sa1111.c:972:\tsave = kmalloc_obj(struct sa1111_save_data);\narch/arm/common/sa1111.c-973-\tif (!save)\n--\narch/arm/kernel/unwind.c=572=struct unwind_table *unwind_table_add(unsigned long start, unsigned long size,\n--\narch/arm/kernel/unwind.c-576-\tunsigned long flags;\narch/arm/kernel/unwind.c:577:\tstruct unwind_table *tab = kmalloc_obj(*tab);\narch/arm/kernel/unwind.c-578-\n--\narch/arm/mach-omap2/omap-iommu.c=53=static struct powerdomain *_get_pwrdm(struct device *dev)\n--\narch/arm/mach-omap2/omap-iommu.c-101-\narch/arm/mach-omap2/omap-iommu.c:102:\tentry = kmalloc_obj(*entry);\narch/arm/mach-omap2/omap-iommu.c-103-\tif (entry) {\n--\narch/arm/mach-omap2/pm34xx.c=406=static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)\n--\narch/arm/mach-omap2/pm34xx.c-412-\narch/arm/mach-omap2/pm34xx.c:413:\tpwrst = kmalloc_obj(struct power_state, GFP_ATOMIC);\narch/arm/mach-omap2/pm34xx.c-414-\tif (!pwrst)\n--\narch/arm/mach-omap2/pm44xx.c=113=static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)\n--\narch/arm/mach-omap2/pm44xx.c-134-\narch/arm/mach-omap2/pm44xx.c:135:\tpwrst = kmalloc_obj(struct power_state, GFP_ATOMIC);\narch/arm/mach-omap2/pm44xx.c-136-\tif (!pwrst)\n--\narch/arm/mm/pgd.c-19-#ifdef CONFIG_ARM_LPAE\narch/arm/mm/pgd.c:20:#define _pgd_alloc(mm)\t\tkmalloc_objs(pgd_t, PTRS_PER_PGD, GFP_KERNEL | __GFP_ZERO)\narch/arm/mm/pgd.c-21-#define _pgd_free(mm, pgd)\tkfree(pgd)\n--\narch/arm/probes/kprobes/test-core.c=764=static int coverage_start(const union decode_item *table)\narch/arm/probes/kprobes/test-core.c-765-{\narch/arm/probes/kprobes/test-core.c:766:\tcoverage.base = kmalloc_objs(struct coverage_entry,\narch/arm/probes/kprobes/test-core.c-767-\t\t\t\t MAX_COVERAGE_ENTRIES);\n--\narch/arm64/kvm/pmu-emul.c=774=void kvm_host_pmu_init(struct arm_pmu *pmu)\n--\narch/arm64/kvm/pmu-emul.c-786-\narch/arm64/kvm/pmu-emul.c:787:\tentry = kmalloc_obj(*entry);\narch/arm64/kvm/pmu-emul.c-788-\tif (!entry)\n--\narch/arm64/kvm/vgic/vgic-debug.c=102=static void *vgic_debug_start(struct seq_file *s, loff_t *pos)\n--\narch/arm64/kvm/vgic/vgic-debug.c-106-\narch/arm64/kvm/vgic/vgic-debug.c:107:\titer = kmalloc_obj(*iter);\narch/arm64/kvm/vgic/vgic-debug.c-108-\tif (!iter)\n--\narch/arm64/kvm/vgic/vgic-debug.c=364=static void *vgic_its_debug_start(struct seq_file *s, loff_t *pos)\n--\narch/arm64/kvm/vgic/vgic-debug.c-377-\narch/arm64/kvm/vgic/vgic-debug.c:378:\titer = kmalloc_obj(*iter);\narch/arm64/kvm/vgic/vgic-debug.c-379-\tif (!iter)\n--\narch/arm64/kvm/vgic/vgic-init.c=747=void __init vgic_set_kvm_info(const struct gic_kvm_info *info)\n--\narch/arm64/kvm/vgic/vgic-init.c-749-\tBUG_ON(gic_kvm_info != NULL);\narch/arm64/kvm/vgic/vgic-init.c:750:\tgic_kvm_info = kmalloc_obj(*gic_kvm_info);\narch/arm64/kvm/vgic/vgic-init.c-751-\tif (gic_kvm_info)\n--\narch/m68k/emu/nfblock.c=97=static int __init nfhd_init_one(int id, u32 blocks, u32 bsize)\n--\narch/m68k/emu/nfblock.c-114-\narch/m68k/emu/nfblock.c:115:\tdev = kmalloc_obj(struct nfhd_device);\narch/m68k/emu/nfblock.c-116-\tif (!dev)\n--\narch/m68k/mm/kmap.c=108=static struct vm_struct *get_io_area(unsigned long size)\n--\narch/m68k/mm/kmap.c-112-\narch/m68k/mm/kmap.c:113:\tarea = kmalloc_obj(*area);\narch/m68k/mm/kmap.c-114-\tif (!area)\n--\narch/mips/alchemy/common/dbdma.c=253=u32 au1xxx_dbdma_chan_alloc(u32 srcid, u32 destid,\n--\narch/mips/alchemy/common/dbdma.c-312-\t\t\t */\narch/mips/alchemy/common/dbdma.c:313:\t\t\tctp = kmalloc_obj(chan_tab_t, GFP_ATOMIC);\narch/mips/alchemy/common/dbdma.c-314-\t\t\tchan_tab_ptr[i] = ctp;\n--\narch/mips/alchemy/common/dbdma.c=391=u32 au1xxx_dbdma_ring_alloc(u32 chanid, int entries)\n--\narch/mips/alchemy/common/dbdma.c-414-\t */\narch/mips/alchemy/common/dbdma.c:415:\tdesc_base = (u32) kmalloc_objs(au1x_ddma_desc_t, entries,\narch/mips/alchemy/common/dbdma.c-416-\t\t\t\t GFP_KERNEL | GFP_DMA);\n--\narch/mips/kernel/module.c=59=static int apply_r_mips_hi16(struct module *me, u32 *location, Elf_Addr v,\n--\narch/mips/kernel/module.c-74-\t */\narch/mips/kernel/module.c:75:\tn = kmalloc_obj(*n);\narch/mips/kernel/module.c-76-\tif (!n)\n--\narch/mips/kernel/vpe.c=311=static int apply_r_mips_hi16(struct module *me, uint32_t *location,\n--\narch/mips/kernel/vpe.c-320-\t */\narch/mips/kernel/vpe.c:321:\tn = kmalloc_obj(*n);\narch/mips/kernel/vpe.c-322-\tif (!n)\n--\narch/parisc/kernel/inventory.c=188=pat_query_module(ulong pcell_loc, ulong mod_index)\n--\narch/parisc/kernel/inventory.c-195-\narch/parisc/kernel/inventory.c:196:\tpa_pdc_cell = kmalloc_obj(*pa_pdc_cell);\narch/parisc/kernel/inventory.c-197-\tif (!pa_pdc_cell)\n--\narch/parisc/kernel/inventory.c=532=add_system_map_addresses(struct parisc_device *dev, int num_addrs, \n--\narch/parisc/kernel/inventory.c-538-\narch/parisc/kernel/inventory.c:539:\tdev-\u003eaddr = kmalloc_objs(*dev-\u003eaddr, num_addrs);\narch/parisc/kernel/inventory.c-540-\tif(!dev-\u003eaddr) {\n--\narch/parisc/kernel/processor.c=81=static int __init processor_probe(struct parisc_device *dev)\n--\narch/parisc/kernel/processor.c-112-\narch/parisc/kernel/processor.c:113:\t\tpa_pdc_cell = kmalloc_obj(*pa_pdc_cell);\narch/parisc/kernel/processor.c-114-\t\tif (!pa_pdc_cell)\n--\narch/parisc/kernel/unwind.c=149=unwind_table_add(const char *name, unsigned long base_addr, \n--\narch/parisc/kernel/unwind.c-159-\narch/parisc/kernel/unwind.c:160:\ttable = kmalloc_obj(struct unwind_table, GFP_USER);\narch/parisc/kernel/unwind.c-161-\tif (table == NULL)\n--\narch/parisc/kernel/unwind.c=406=void unwind_frame_init_from_blocked_task(struct unwind_frame_info *info, struct task_struct *t)\n--\narch/parisc/kernel/unwind.c-410-\narch/parisc/kernel/unwind.c:411:\tr2 = kmalloc_obj(struct pt_regs, GFP_ATOMIC);\narch/parisc/kernel/unwind.c-412-\tif (!r2)\n--\narch/powerpc/kernel/nvram_64.c=984=int __init nvram_scan_partitions(void)\n--\narch/powerpc/kernel/nvram_64.c-1032-\t\t}\narch/powerpc/kernel/nvram_64.c:1033:\t\ttmp_part = kmalloc_obj(*tmp_part);\narch/powerpc/kernel/nvram_64.c-1034-\t\terr = -ENOMEM;\n--\narch/powerpc/kvm/e500_mmu.c=731=int kvm_vcpu_ioctl_config_tlb(struct kvm_vcpu *vcpu,\n--\narch/powerpc/kvm/e500_mmu.c-774-\t\t cfg-\u003earray / PAGE_SIZE;\narch/powerpc/kvm/e500_mmu.c:775:\tpages = kmalloc_objs(*pages, num_pages);\narch/powerpc/kvm/e500_mmu.c-776-\tif (!pages)\n--\narch/powerpc/kvm/e500_mmu.c=898=int kvmppc_e500_tlb_init(struct kvmppc_vcpu_e500 *vcpu_e500)\n--\narch/powerpc/kvm/e500_mmu.c-914-\narch/powerpc/kvm/e500_mmu.c:915:\tvcpu_e500-\u003egtlb_arch = kmalloc_objs(*vcpu_e500-\u003egtlb_arch,\narch/powerpc/kvm/e500_mmu.c-916-\t\t\t\t\t KVM_E500_TLB0_SIZE + KVM_E500_TLB1_SIZE);\n--\narch/powerpc/lib/rheap.c=45=static int grow(rh_info_t * info, int max_blocks)\n--\narch/powerpc/lib/rheap.c-56-\narch/powerpc/lib/rheap.c:57:\tblock = kmalloc_objs(rh_block_t, max_blocks, GFP_ATOMIC);\narch/powerpc/lib/rheap.c-58-\tif (block == NULL)\n--\narch/powerpc/lib/rheap.c=253=rh_info_t *rh_create(unsigned int alignment)\n--\narch/powerpc/lib/rheap.c-260-\narch/powerpc/lib/rheap.c:261:\tinfo = kmalloc_obj(*info, GFP_ATOMIC);\narch/powerpc/lib/rheap.c-262-\tif (info == NULL)\n--\narch/powerpc/mm/book3s64/mmu_context.c=95=static int hash__init_new_context(struct mm_struct *mm)\n--\narch/powerpc/mm/book3s64/mmu_context.c-98-\narch/powerpc/mm/book3s64/mmu_context.c:99:\tmm-\u003econtext.hash_context = kmalloc_obj(struct hash_mm_context);\narch/powerpc/mm/book3s64/mmu_context.c-100-\tif (!mm-\u003econtext.hash_context)\n--\narch/powerpc/mm/book3s64/mmu_context.c-125-\t\tif (current-\u003emm-\u003econtext.hash_context-\u003espt) {\narch/powerpc/mm/book3s64/mmu_context.c:126:\t\t\tmm-\u003econtext.hash_context-\u003espt = kmalloc_obj(struct subpage_prot_table);\narch/powerpc/mm/book3s64/mmu_context.c-127-\t\t\tif (!mm-\u003econtext.hash_context-\u003espt) {\n--\narch/powerpc/perf/hv-24x7.c=623=static int event_uniq_add(struct rb_root *root, const char *name, int nl,\n--\narch/powerpc/perf/hv-24x7.c-650-\narch/powerpc/perf/hv-24x7.c:651:\tdata = kmalloc_obj(*data);\narch/powerpc/perf/hv-24x7.c-652-\tif (!data)\n--\narch/powerpc/perf/hv-24x7.c=755=static int create_events_from_catalog(struct attribute ***events_,\n--\narch/powerpc/perf/hv-24x7.c-908-\narch/powerpc/perf/hv-24x7.c:909:\tevents = kmalloc_objs(*events, attr_max + 1);\narch/powerpc/perf/hv-24x7.c-910-\tif (!events) {\n--\narch/powerpc/perf/hv-24x7.c-914-\narch/powerpc/perf/hv-24x7.c:915:\tevent_descs = kmalloc_objs(*event_descs, event_idx + 1);\narch/powerpc/perf/hv-24x7.c-916-\tif (!event_descs) {\n--\narch/powerpc/perf/hv-24x7.c-920-\narch/powerpc/perf/hv-24x7.c:921:\tevent_long_descs = kmalloc_objs(*event_long_descs, event_idx + 1);\narch/powerpc/perf/hv-24x7.c-922-\tif (!event_long_descs) {\n--\narch/powerpc/platforms/44x/hsta_msi.c=122=static int hsta_msi_probe(struct platform_device *pdev)\n--\narch/powerpc/platforms/44x/hsta_msi.c-153-\narch/powerpc/platforms/44x/hsta_msi.c:154:\tppc4xx_hsta_msi.irq_map = kmalloc_objs(int, irq_count);\narch/powerpc/platforms/44x/hsta_msi.c-155-\tif (!ppc4xx_hsta_msi.irq_map) {\n--\narch/powerpc/platforms/cell/spufs/file.c=44=static int spufs_attr_open(struct inode *inode, struct file *file,\n--\narch/powerpc/platforms/cell/spufs/file.c-49-\narch/powerpc/platforms/cell/spufs/file.c:50:\tattr = kmalloc_obj(*attr);\narch/powerpc/platforms/cell/spufs/file.c-51-\tif (!attr)\n--\narch/powerpc/platforms/pseries/dlpar.c=628=void queue_hotplug_event(struct pseries_hp_errorlog *hp_errlog)\n--\narch/powerpc/platforms/pseries/dlpar.c-636-\narch/powerpc/platforms/pseries/dlpar.c:637:\twork = kmalloc_obj(struct pseries_hp_work, GFP_ATOMIC);\narch/powerpc/platforms/pseries/dlpar.c-638-\tif (work) {\n--\narch/powerpc/platforms/pseries/hvcserver.c=119=int hvcs_get_partner_info(uint32_t unit_address, struct list_head *head,\n--\narch/powerpc/platforms/pseries/hvcserver.c-162-\t\t * hvcs_free_partner_info(). */\narch/powerpc/platforms/pseries/hvcserver.c:163:\t\tnext_partner_info = kmalloc_obj(struct hvcs_partner_info,\narch/powerpc/platforms/pseries/hvcserver.c-164-\t\t\t\t\t\tGFP_ATOMIC);\n--\narch/powerpc/platforms/pseries/lparcfg.c=144=static void show_gpci_data(struct seq_file *m)\n--\narch/powerpc/platforms/pseries/lparcfg.c-149-\narch/powerpc/platforms/pseries/lparcfg.c:150:\tbuf = kmalloc_obj(*buf);\narch/powerpc/platforms/pseries/lparcfg.c-151-\tif (buf == NULL)\n\n"
} |
| 15/3 | 2026/09/10 13:44 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 32473 output: 26 thoughts: 51 Thoughts: Here's my analysis of the provided thought process, tailored for an expert audience: **Analyzing `cc` Field Access in Collapse Operations** Okay, so I need to dig into the details of the `collapse_scan_anon_pmd`, `mthp_collapse`, `collapse_scan_file`, and `collapse_file` functions. The goal is to get a solid grasp on *exactly* how the `cc` (presumably "collapse context" or similar) fields are being accessed and modified within these collapse-related operations. We're talking low-level memory management and page table manipulations here, so understanding the read and write patterns of this structure is critical. I'll be looking for potential race conditions, atomicity guarantees, and any subtle interactions between these functions that might impact the integrity of the collapse process. I also need to verify that all necessary locks are correctly acquired and released around critical sections that manipulate this field. I'll need to pay attention to the specific data structures being accessed, the context in which these functions are called, and any potential optimizations that are being employed, such as write-combining or speculative reads/writes, that might introduce complexity or unexpected behavior. |
| 16/3 | 2026/09/10 13:44 | tool | grepper |
0mArgs:{
"Expression": "struct collapse_control",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/collapse.h=49=struct collapse_policy {\n--\nmm/collapse.h-76-\nmm/collapse.h:77:struct collapse_control {\nmm/collapse.h-78-\tstruct collapse_policy policy;\n--\nmm/collapse.h=110=unsigned long collapse_possible_orders(struct vm_area_struct *vma,\n--\nmm/collapse.h-140- */\nmm/collapse.h:141:void collapse_control_init(struct collapse_control *cc);\nmm/collapse.h:142:void collapse_control_release(struct collapse_control *cc);\nmm/collapse.h-143-enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,\nmm/collapse.h:144:\t\tunsigned long addr, struct collapse_control *cc,\nmm/collapse.h-145-\t\tunsigned long orders);\nmm/collapse.h=146=enum scan_result collapse_run_pmd(struct mm_struct *mm, unsigned long addr,\nmm/collapse.h:147:\t\tstruct collapse_control *cc);\nmm/collapse.h-148-enum scan_result collapse_vma_revalidate(struct mm_struct *mm,\nmm/collapse.h-149-\t\tunsigned long address, bool expect_anon,\nmm/collapse.h:150:\t\tstruct vm_area_struct **vmap, struct collapse_control *cc,\nmm/collapse.h-151-\t\tunsigned int order);\n--\nmm/khugepaged.c=298=static bool pte_none_or_zero(pte_t pte)\n--\nmm/khugepaged.c-313- */\nmm/khugepaged.c:314:static unsigned int collapse_max_ptes_none(struct collapse_control *cc,\nmm/khugepaged.c-315-\t\tstruct vm_area_struct *vma, unsigned int order)\n--\nmm/khugepaged.c-349- */\nmm/khugepaged.c:350:static unsigned int collapse_max_ptes_shared(struct collapse_control *cc,\nmm/khugepaged.c-351-\t\tunsigned int order)\n--\nmm/khugepaged.c-370- */\nmm/khugepaged.c:371:static unsigned int collapse_max_ptes_swap(struct collapse_control *cc,\nmm/khugepaged.c-372-\t\tunsigned int order)\n--\nmm/khugepaged.c=523=void __khugepaged_exit(struct mm_struct *mm)\n--\nmm/khugepaged.c-552-\nmm/khugepaged.c:553:static void collapse_scan_reset(struct collapse_control *cc)\nmm/khugepaged.c-554-{\n--\nmm/khugepaged.c=614=static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\nmm/khugepaged.c:615:\t\tunsigned long start_addr, pte_t *pte, struct collapse_control *cc,\nmm/khugepaged.c-616-\t\tunsigned int order, struct list_head *compound_pagelist)\n--\nmm/khugepaged.c=911=static void khugepaged_alloc_sleep(void)\n--\nmm/khugepaged.c-920-\nmm/khugepaged.c:921:static struct collapse_control khugepaged_collapse_control;\nmm/khugepaged.c-922-\nmm/khugepaged.c:923:static bool collapse_scan_abort(int nid, struct collapse_control *cc)\nmm/khugepaged.c-924-{\n--\nmm/khugepaged.c=958=static void collapse_policy_khugepaged(struct collapse_policy *p)\n--\nmm/khugepaged.c-972-#ifdef CONFIG_NUMA\nmm/khugepaged.c:973:static int collapse_find_target_node(struct collapse_control *cc)\nmm/khugepaged.c-974-{\n--\nmm/khugepaged.c-991-#else\nmm/khugepaged.c:992:static int collapse_find_target_node(struct collapse_control *cc)\nmm/khugepaged.c-993-{\n--\nmm/khugepaged.c=1004=enum scan_result collapse_vma_revalidate(struct mm_struct *mm, unsigned long address,\nmm/khugepaged.c-1005-\t\tbool expect_anon, struct vm_area_struct **vmap,\nmm/khugepaged.c:1006:\t\tstruct collapse_control *cc, unsigned int order)\nmm/khugepaged.c-1007-{\n--\nmm/khugepaged.c=1189=static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_struct *mm,\nmm/khugepaged.c:1190:\t\tstruct collapse_control *cc, unsigned int order)\nmm/khugepaged.c-1191-{\n--\nmm/khugepaged.c=1225=static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long start_addr,\nmm/khugepaged.c:1226:\t\tint referenced, int unmapped, struct collapse_control *cc,\nmm/khugepaged.c-1227-\t\tunsigned int order)\n--\nmm/khugepaged.c=1436=static enum scan_result mthp_collapse(struct mm_struct *mm,\nmm/khugepaged.c-1437-\t\tunsigned long address, int referenced, int unmapped,\nmm/khugepaged.c:1438:\t\tstruct collapse_control *cc, unsigned long enabled_orders)\nmm/khugepaged.c-1439-{\n--\nmm/khugepaged.c=1538=static enum scan_result collapse_scan_anon_pmd(struct vm_area_struct *vma,\nmm/khugepaged.c:1539:\t\tunsigned long start_addr, struct collapse_control *cc,\nmm/khugepaged.c-1540-\t\tunsigned long enabled_orders)\n--\nmm/khugepaged.c=2189=static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,\nmm/khugepaged.c:2190:\t\tstruct file *file, pgoff_t start, struct collapse_control *cc)\nmm/khugepaged.c-2191-{\n--\nmm/khugepaged.c=2623=static enum scan_result collapse_scan_file(struct mm_struct *mm,\nmm/khugepaged.c-2624-\t\tunsigned long addr, struct file *file, pgoff_t start,\nmm/khugepaged.c:2625:\t\tstruct collapse_control *cc)\nmm/khugepaged.c-2626-{\n--\nmm/khugepaged.c-2730-\nmm/khugepaged.c:2731:void collapse_control_init(struct collapse_control *cc)\nmm/khugepaged.c-2732-{\n--\nmm/khugepaged.c-2736-\nmm/khugepaged.c:2737:void collapse_control_release(struct collapse_control *cc)\nmm/khugepaged.c-2738-{\n--\nmm/khugepaged.c=2746=enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,\nmm/khugepaged.c:2747:\t\tunsigned long addr, struct collapse_control *cc,\nmm/khugepaged.c-2748-\t\tunsigned long orders)\n--\nmm/khugepaged.c=2790=enum scan_result collapse_run_pmd(struct mm_struct *mm, unsigned long addr,\nmm/khugepaged.c:2791:\t\tstruct collapse_control *cc)\nmm/khugepaged.c-2792-{\n--\nmm/khugepaged.c=2843=static void collapse_scan_mm_slot(unsigned int progress_max,\nmm/khugepaged.c:2844:\t\tenum scan_result *result, struct collapse_control *cc)\nmm/khugepaged.c-2845-\t__releases(\u0026khugepaged_mm_lock)\n--\nmm/khugepaged.c=2978=static int khugepaged_wait_event(void)\n--\nmm/khugepaged.c-2983-\nmm/khugepaged.c:2984:static void khugepaged_do_scan(struct collapse_control *cc)\nmm/khugepaged.c-2985-{\n--\nmm/madvise.c=959=static int madvise_collapse(struct madvise_behavior *madv_behavior)\n--\nmm/madvise.c-963-\tstruct mm_struct *mm = madv_behavior-\u003emm;\nmm/madvise.c:964:\tstruct collapse_control *cc;\nmm/madvise.c-965-\tunsigned long hstart, hend, addr, orders;\n"
} |
| 17/3 | 2026/09/10 13:44 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 35179 output: 37 thoughts: 34 Thoughts: **Examining `collapse_file` in `mm/khugepaged.c`** Alright, let's dive into `mm/khugepaged.c` and specifically, that `collapse_file` function. I need to refresh my understanding of how `cc` is used within it. I have a hunch it's related to some caching or control flow, but I need to be precise. Time to dig in and see how this particular variable contributes to the overall process of collapsing huge pages. Hopefully, the code is well-commented... I can usually spot the important stuff pretty quickly, so I'm expecting to get this figured out without too much trouble. Let's see what `collapse_file` has in store. |
| 18/3 | 2026/09/10 13:44 | tool | read-file |
0mArgs:{
"File": "mm/khugepaged.c",
"FirstLine": 2190,
"LineCount": 60
}Results: {
"Contents": "2190:\t\t\tstruct file *file, pgoff_t start, struct collapse_control *cc)\n2191:\t{\n2192:\t\tstruct address_space *mapping = file-\u003ef_mapping;\n2193:\t\tstruct page *dst;\n2194:\t\tstruct folio *folio, *tmp, *new_folio;\n2195:\t\tunsigned long new_pfn = -1;\n2196:\t\tpgoff_t index = 0, end = start + HPAGE_PMD_NR;\n2197:\t\tLIST_HEAD(pagelist);\n2198:\t\tXA_STATE_ORDER(xas, \u0026mapping-\u003ei_pages, start, HPAGE_PMD_ORDER);\n2199:\t\tenum scan_result result = SCAN_SUCCEED;\n2200:\t\tint nr_none = 0;\n2201:\t\tbool is_shmem = shmem_file(file);\n2202:\t\n2203:\t\t/*\n2204:\t\t * MADV_COLLAPSE ignores shmem huge config, so do not check shmem\n2205:\t\t *\n2206:\t\t * TODO: once shmem always calls mapping_set_large_folios() on its\n2207:\t\t * mapping, the shmem check can be removed.\n2208:\t\t */\n2209:\t\tVM_WARN_ON_ONCE(!is_shmem \u0026\u0026 !mapping_pmd_folio_support(mapping));\n2210:\t\tVM_WARN_ON_ONCE(start \u0026 (HPAGE_PMD_NR - 1));\n2211:\t\n2212:\t\tresult = alloc_charge_folio(\u0026new_folio, mm, cc, HPAGE_PMD_ORDER);\n2213:\t\tif (result != SCAN_SUCCEED)\n2214:\t\t\tgoto out;\n2215:\t\tnew_pfn = folio_pfn(new_folio);\n2216:\t\n2217:\t\tmapping_set_update(\u0026xas, mapping);\n2218:\t\n2219:\t\t__folio_set_locked(new_folio);\n2220:\t\tif (is_shmem)\n2221:\t\t\t__folio_set_swapbacked(new_folio);\n2222:\t\tnew_folio-\u003eindex = start;\n2223:\t\tnew_folio-\u003emapping = mapping;\n2224:\t\n2225:\t\t/*\n2226:\t\t * Ensure we have slots for all the pages in the range. This is\n2227:\t\t * almost certainly a no-op because most of the pages must be present\n2228:\t\t */\n2229:\t\tdo {\n2230:\t\t\txas_lock_irq(\u0026xas);\n2231:\t\t\txas_create_range(\u0026xas);\n2232:\t\t\tif (!xas_error(\u0026xas))\n2233:\t\t\t\tbreak;\n2234:\t\t\txas_unlock_irq(\u0026xas);\n2235:\t\t\tif (!xas_nomem(\u0026xas, GFP_KERNEL)) {\n2236:\t\t\t\tresult = SCAN_FAIL;\n2237:\t\t\t\tgoto rollback;\n2238:\t\t\t}\n2239:\t\t} while (1);\n2240:\t\n2241:\t\tfor (index = start; index \u003c end;) {\n2242:\t\t\txas_set(\u0026xas, index);\n2243:\t\t\tfolio = xas_load(\u0026xas);\n2244:\t\n2245:\t\t\tVM_BUG_ON(index != xas.xa_index);\n2246:\t\t\tif (is_shmem) {\n2247:\t\t\t\tif (!folio) {\n2248:\t\t\t\t\t/*\n2249:\t\t\t\t\t * Stop if extent has been truncated or\n"
} |
| 19/3 | 2026/09/10 13:44 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 36358 output: 262 thoughts: 2562 Thoughts: **Analysis of Transparent Hugepage Collapse Refactoring for KMSAN Relevance** Alright, let's dive into this code. My immediate concern is ensuring that this refactor doesn't introduce any new risks of reading uninitialized memory, or even worse, leaking uninitialized memory to user space. The core of this patch seems to be about restructuring the transparent hugepage collapse mechanism, moving some functions around, and consolidating the collapse control logic. Specifically, I see it's about `alloc_charge_folio`, `collapse_file`, and the different scan and run functions: `collapse_scan_pmd`, `collapse_run_pmd`, `collapse_scan_anon_pmd`, and `collapse_scan_file`. I've traced the flow, starting with `alloc_charge_folio` which calls `collapse_find_target_node`, which appears to be NUMA-aware, or at least capable. It uses a `collapse_control` (`cc`) structure, and I've looked at the initialization and release functions (`collapse_control_init` and `collapse_control_release`). It's important to understand where and how the `cc` structure is populated, because that's where uninitialized memory risks would be. I notice that `cc->node_load` is reset in `collapse_scan_reset`. This is a good sign. The `collapse_scan_pmd` path handles both anonymous and file-backed VMAs differently. The anonymous path calls `collapse_scan_anon_pmd` which calls `collapse_scan_reset`, so the `cc` struct will be initialized. I also see that, in the file-backed VMA case, `cc->scan_file` is set, and properly released. The most critical part is the flow that occurs when `vma_is_anonymous(vma)` is true. In this case, there's a branch in the `collapse_run_pmd` function where `cc->scan_file` might be null. Fortunately, all the potentially uninitialized members (`scan_referenced`, `scan_unmapped`, and `scan_orders`) are correctly initialized within the anonymous scan branch, inside `collapse_scan_anon_pmd`. I have also reviewed where all fields of the `collapse_control` struct (`cc`) are used. I can verify that all the fields are always initialized before use, either through `collapse_control_init`, `collapse_policy_forced`, `collapse_scan_reset`, or explicit assignments during the scan and run process, which means no uninitialized memory. Now, let's consider the KMSAN aspects. I've checked for any user-space copy operations (`copy_to_user`, potential info leaks), netlink buffers, ioctl calls, or any form of data exposure to user space. There are none. There is no branching on uninitialized data and no allocation of memory that's exposed without zeroing. Even though `cc` is allocated via `kmalloc_obj`, all the fields that are read are explicitly initialized either during the `collapse_control_init` + `collapse_policy_forced` + `collapse_scan_reset` or during scan/run. `cc` is purely kernel-internal. Any potential issues with this refactoring, such as locking, reference counting, pointer bugs, are within the memory management subsystem, which means KASAN and LOCKDEP would catch it. Given all of this, the risks here are primarily about memory management correctness and potential concurrency issues in the THP collapse logic, not about KMSAN-specific uninitialized memory risks. Since KASAN, LOCKDEP and other standard kernel debugging tools will be sufficient, the dedicated KMSAN session is not necessary. Therefore, I conclude that `NeedsKMSAN = false`. |
| 20/3 | 2026/09/10 13:44 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch refactors the Transparent HugePage (THP) collapse infrastructure. It introduces `struct collapse_policy` to distinguish policy configurations between khugepaged background passes and explicit `MADV_COLLAPSE` operations, splits scanning and execution into `collapse_scan_pmd()` and `collapse_run_pmd()`, and relocates `madvise_collapse()` to `mm/madvise.c`.\n\nAll newly introduced and modified fields within `struct collapse_control` are explicitly initialized before use (via `collapse_control_init()`, `collapse_scan_reset()`, and scan assignments prior to running). No kernel-to-user memory transfers (`copy_to_user`, info-leaks, padding leaks) are involved, nor is there any branching on uninitialized data. Any potential regressions or bugs in this patch involve locking, page table traversal, refcounting (e.g., `struct file` or `struct folio`), or VMA revalidation, which are comprehensively covered by standard KASAN, LOCKDEP, and page allocator debug assertions. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch refactors the Transparent HugePage (THP) collapse infrastructure. It introduces `struct collapse_policy` to distinguish policy configurations between khugepaged background passes and explicit `MADV_COLLAPSE` operations, splits scanning and execution into `collapse_scan_pmd()` and `collapse_run_pmd()`, and relocates `madvise_collapse()` to `mm/madvise.c`.\n\nAll newly introduced and modified fields within `struct collapse_control` are explicitly initialized before use (via `collapse_control_init()`, `collapse_scan_reset()`, and scan assignments prior to running). No kernel-to-user memory transfers (`copy_to_user`, info-leaks, padding leaks) are involved, nor is there any branching on uninitialized data. Any potential regressions or bugs in this patch involve locking, page table traversal, refcounting (e.g., `struct file` or `struct folio`), or VMA revalidation, which are comprehensively covered by standard KASAN, LOCKDEP, and page allocator debug assertions. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|