AI Trajectory


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

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "All modifications in this patch series are strictly confined to user-space selftests under tools/testing/selftests/mm/. No kernel image (vmlinux) or module code is altered, making the changes non-kernel/non-executable from the perspective of kernel fuzzing.",
  "WorthFuzzing": false
}

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

Results:
{
  "PatchDiff": "commit 68270a1ab82b67973de67ffb4d354065ad5a8691\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Sep 8 15:15:39 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile\nindex 2d5366196e309..308bbad73c11a 100644\n--- a/tools/testing/selftests/mm/Makefile\n+++ b/tools/testing/selftests/mm/Makefile\n@@ -104,6 +104,9 @@ TEST_GEN_FILES += guard-regions\n TEST_GEN_FILES += merge\n TEST_GEN_FILES += rmap\n TEST_GEN_FILES += folio_split_race_test\n+TEST_GEN_FILES += folio_order_check\n+TEST_GEN_FILES += khugepaged_sync_check\n+TEST_GEN_FILES += khugepaged_race\n \n ifneq ($(ARCH),arm64)\n TEST_GEN_FILES += soft-dirty\ndiff --git a/tools/testing/selftests/mm/folio_order_check.c b/tools/testing/selftests/mm/folio_order_check.c\nnew file mode 100644\nindex 0000000000000..fa736c9f701a4\n--- /dev/null\n+++ b/tools/testing/selftests/mm/folio_order_check.c\n@@ -0,0 +1,122 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Self-check for the vm_util folio-order helpers, is_backed_by_folio() and\n+ * is_range_backed_by_order(), which the khugepaged mTHP cases use to detect\n+ * collapse results.  For every anon THP order the kernel supports, fault\n+ * memory in with only that order enabled and require the helpers to report\n+ * exactly that order.\n+ */\n+#define _GNU_SOURCE\n+#include \u003cfcntl.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"kselftest.h\"\n+#include \"vm_util.h\"\n+#include \"hugepage_settings.h\"\n+\n+static int pagemap_fd;\n+static int kpageflags_fd;\n+\n+static char *alloc_aligned(size_t size)\n+{\n+\tsize_t len = size * 2;\n+\tchar *p, *aligned;\n+\n+\tp = mmap(NULL, len, PROT_READ | PROT_WRITE,\n+\t\t MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);\n+\tif (p == MAP_FAILED)\n+\t\tksft_exit_fail_perror(\"mmap()\");\n+\n+\taligned = (char *)ALIGN((uintptr_t)p, size);\n+\tif (aligned != p)\n+\t\tmunmap(p, aligned - p);\n+\tif (aligned + size != p + len)\n+\t\tmunmap(aligned + size, p + len - aligned - size);\n+\n+\treturn aligned;\n+}\n+\n+static void check_order(int order)\n+{\n+\tstruct thp_settings settings = *thp_current_settings();\n+\tsize_t size = psize() \u003c\u003c order;\n+\tbool ok = true;\n+\tchar *p;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c NR_ORDERS; i++)\n+\t\tsettings.hugepages[i].enabled = THP_NEVER;\n+\tif (order)\n+\t\tsettings.hugepages[order].enabled = THP_ALWAYS;\n+\tthp_push_settings(\u0026settings);\n+\n+\tp = alloc_aligned(size);\n+\t*p = 1;\n+\n+\tif (!is_range_backed_by_order(p, size, order, pagemap_fd, kpageflags_fd)) {\n+\t\tksft_print_msg(\"order %d not detected after fault\\n\", order);\n+\t\tok = false;\n+\t}\n+\n+\t/* A lower order must be rejected: the folio is larger */\n+\tif (order \u0026\u0026 is_range_backed_by_order(p, size, order - 1,\n+\t\t\t\t\t      pagemap_fd, kpageflags_fd)) {\n+\t\tksft_print_msg(\"order %d also reported as order %d\\n\",\n+\t\t\t       order, order - 1);\n+\t\tok = false;\n+\t}\n+\n+\t/* A large folio must not pass as order 0 */\n+\tif (order \u0026\u0026 is_range_backed_by_order(p, size, 0,\n+\t\t\t\t\t      pagemap_fd, kpageflags_fd)) {\n+\t\tksft_print_msg(\"order %d also reported as order 0\\n\", order);\n+\t\tok = false;\n+\t}\n+\n+\tmunmap(p, size);\n+\tthp_pop_settings();\n+\n+\tksft_test_result(ok, \"order %d classified\\n\", order);\n+}\n+\n+int main(void)\n+{\n+\tstruct thp_settings settings;\n+\tunsigned long orders;\n+\tint order;\n+\n+\tksft_print_header();\n+\n+\tif (!thp_available())\n+\t\tksft_exit_skip(\"Transparent Hugepages not available\\n\");\n+\n+\tpagemap_fd = open(\"/proc/self/pagemap\", O_RDONLY);\n+\tif (pagemap_fd \u003c 0)\n+\t\tksft_exit_fail_perror(\"open(/proc/self/pagemap)\");\n+\tkpageflags_fd = open(\"/proc/kpageflags\", O_RDONLY);\n+\tif (kpageflags_fd \u003c 0)\n+\t\tksft_exit_skip(\"open(/proc/kpageflags) requires root\\n\");\n+\n+\torders = thp_supported_orders();\n+\tif (!orders)\n+\t\tksft_exit_skip(\"No supported THP orders\\n\");\n+\n+\tksft_set_plan(__builtin_popcountl(orders) + 1);\n+\n+\tthp_save_settings();\n+\tthp_read_settings(\u0026settings);\n+\t/* Base of the settings stack; the bottom entry is never popped */\n+\tthp_push_settings(\u0026settings);\n+\n+\tcheck_order(0);\n+\tfor (order = 1; order \u003c NR_ORDERS; order++) {\n+\t\tif (!(orders \u0026 (1UL \u003c\u003c order)))\n+\t\t\tcontinue;\n+\t\tcheck_order(order);\n+\t}\n+\n+\tksft_finished();\n+}\ndiff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c\nindex e2642eca0d02b..df426f9218e71 100644\n--- a/tools/testing/selftests/mm/hmm-tests.c\n+++ b/tools/testing/selftests/mm/hmm-tests.c\n@@ -65,7 +65,6 @@ enum {\n #define HMM_PATH_MAX    64\n #define NTIMES\t\t10\n \n-#define ALIGN(x, a) (((x) + (a - 1)) \u0026 (~((a) - 1)))\n /* Just the flags we need, copied from mm.h: */\n \n #ifndef FOLL_WRITE\ndiff --git a/tools/testing/selftests/mm/hugepage_settings.c b/tools/testing/selftests/mm/hugepage_settings.c\nindex d7917dce3abac..ca73f9ac8e9b6 100644\n--- a/tools/testing/selftests/mm/hugepage_settings.c\n+++ b/tools/testing/selftests/mm/hugepage_settings.c\n@@ -183,6 +183,13 @@ void thp_read_settings(struct thp_settings *settings)\n \t}\n }\n \n+/* A store to either sleep knob wakes khugepaged, so write only on change */\n+static void thp_update_num(const char *name, unsigned long num)\n+{\n+\tif (thp_read_num(name) != num)\n+\t\tthp_write_num(name, num);\n+}\n+\n void thp_write_settings(struct thp_settings *settings)\n {\n \tstruct khugepaged_settings *khugepaged = \u0026settings-\u003ekhugepaged;\n@@ -198,15 +205,15 @@ void thp_write_settings(struct thp_settings *settings)\n \t\t\tshmem_enabled_strings[settings-\u003eshmem_enabled]);\n \tthp_write_num(\"use_zero_page\", settings-\u003euse_zero_page);\n \n-\tthp_write_num(\"khugepaged/defrag\", khugepaged-\u003edefrag);\n-\tthp_write_num(\"khugepaged/alloc_sleep_millisecs\",\n-\t\t\tkhugepaged-\u003ealloc_sleep_millisecs);\n-\tthp_write_num(\"khugepaged/scan_sleep_millisecs\",\n-\t\t\tkhugepaged-\u003escan_sleep_millisecs);\n-\tthp_write_num(\"khugepaged/max_ptes_none\", khugepaged-\u003emax_ptes_none);\n-\tthp_write_num(\"khugepaged/max_ptes_swap\", khugepaged-\u003emax_ptes_swap);\n-\tthp_write_num(\"khugepaged/max_ptes_shared\", khugepaged-\u003emax_ptes_shared);\n-\tthp_write_num(\"khugepaged/pages_to_scan\", khugepaged-\u003epages_to_scan);\n+\tthp_update_num(\"khugepaged/defrag\", khugepaged-\u003edefrag);\n+\tthp_update_num(\"khugepaged/alloc_sleep_millisecs\",\n+\t\t       khugepaged-\u003ealloc_sleep_millisecs);\n+\tthp_update_num(\"khugepaged/scan_sleep_millisecs\",\n+\t\t       khugepaged-\u003escan_sleep_millisecs);\n+\tthp_update_num(\"khugepaged/max_ptes_none\", khugepaged-\u003emax_ptes_none);\n+\tthp_update_num(\"khugepaged/max_ptes_swap\", khugepaged-\u003emax_ptes_swap);\n+\tthp_update_num(\"khugepaged/max_ptes_shared\", khugepaged-\u003emax_ptes_shared);\n+\tthp_update_num(\"khugepaged/pages_to_scan\", khugepaged-\u003epages_to_scan);\n \n \tif (dev_queue_read_ahead_path[0])\n \t\twrite_num(dev_queue_read_ahead_path, settings-\u003eread_ahead_kb);\n@@ -230,6 +237,41 @@ void thp_write_settings(struct thp_settings *settings)\n \t}\n }\n \n+/*\n+ * Wait for a full khugepaged scan pass that started after this call: the\n+ * pass in progress may already have passed this mm, so full_scans has to\n+ * advance twice.\n+ *\n+ * A store to scan_sleep_millisecs wakes the daemon, but one made while it\n+ * is scanning rather than sleeping is lost, so keep storing until the pass\n+ * lands.\n+ *\n+ * One wake is one pass only if pages_to_scan covers every mm on the list.\n+ */\n+bool khugepaged_full_pass(unsigned int timeout_s)\n+{\n+\tunsigned long deadline_ms = timeout_s * 1000UL;\n+\tunsigned long elapsed_ms = 0, poll_ms = 10;\n+\tunsigned long sleep_ms;\n+\tint pass;\n+\n+\tsleep_ms = thp_read_num(\"khugepaged/scan_sleep_millisecs\");\n+\tfor (pass = 0; pass \u003c 2; pass++) {\n+\t\tunsigned long target =\n+\t\t\tthp_read_num(\"khugepaged/full_scans\") + 1;\n+\n+\t\twhile (thp_read_num(\"khugepaged/full_scans\") \u003c target) {\n+\t\t\tif (elapsed_ms \u003e= deadline_ms)\n+\t\t\t\treturn false;\n+\t\t\tthp_write_num(\"khugepaged/scan_sleep_millisecs\",\n+\t\t\t\t      sleep_ms);\n+\t\t\tusleep(poll_ms * 1000);\n+\t\t\telapsed_ms += poll_ms;\n+\t\t}\n+\t}\n+\treturn true;\n+}\n+\n struct thp_settings *thp_current_settings(void)\n {\n \tif (!settings_index) {\ndiff --git a/tools/testing/selftests/mm/hugepage_settings.h b/tools/testing/selftests/mm/hugepage_settings.h\nindex 726c73c43c05b..2ea169d117962 100644\n--- a/tools/testing/selftests/mm/hugepage_settings.h\n+++ b/tools/testing/selftests/mm/hugepage_settings.h\n@@ -83,10 +83,21 @@ static inline void thp_save_settings(void)\n \thugepage_save_settings(/* thp = */ true, /* hugetlb = */ false);\n }\n \n+bool khugepaged_full_pass(unsigned int timeout_s);\n+\n void thp_set_read_ahead_path(char *path);\n unsigned long thp_supported_orders(void);\n unsigned long thp_shmem_supported_orders(void);\n \n+/*\n+ * The per-order shmem_enabled attribute is created for the orders the page\n+ * cache can hold, not just for shmem, so it answers for regular files too.\n+ */\n+static inline unsigned long thp_file_supported_orders(void)\n+{\n+\treturn thp_shmem_supported_orders();\n+}\n+\n bool thp_available(void);\n bool thp_is_enabled(void);\n \ndiff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c\nindex f82673f5f6b47..335f946eca61e 100644\n--- a/tools/testing/selftests/mm/khugepaged.c\n+++ b/tools/testing/selftests/mm/khugepaged.c\n@@ -31,6 +31,11 @@ static unsigned long page_size;\n static int hpage_pmd_nr;\n static int anon_order;\n static int collapse_order;\n+static bool collapse_order_set;\n+static int collapse_orders[NR_ORDERS];\n+static int nr_collapse_orders;\n+static int pagemap_fd = -1;\n+static int kpageflags_fd = -1;\n \n #define PID_SMAPS \"/proc/self/smaps\"\n #define TEST_FILE \"collapse_test_file\"\n@@ -220,6 +225,29 @@ static bool check_swap(void *addr, unsigned long size)\n \treturn swap;\n }\n \n+static bool swapout_range(void *p, unsigned long size)\n+{\n+\tint i;\n+\n+\t/* keep khugepaged from collapsing the range and swapping it back in */\n+\tif (madvise(p, size, MADV_NOHUGEPAGE))\n+\t\tksft_exit_fail_perror(\"madvise(MADV_NOHUGEPAGE)\");\n+\n+\t/*\n+\t * Retry several times because MADV_PAGEOUT is best effort.  Sleep\n+\t * between the retries to give outstanding writeback a chance to\n+\t * finish.\n+\t */\n+\tfor (i = 0; i \u003c 40; i++) {\n+\t\tif (madvise(p, size, MADV_PAGEOUT))\n+\t\t\tksft_exit_fail_perror(\"madvise(MADV_PAGEOUT)\");\n+\t\tif (check_swap(p, size))\n+\t\t\treturn true;\n+\t\tusleep(50 * 1000);\n+\t}\n+\treturn false;\n+}\n+\n static void *alloc_mapping(int nr)\n {\n \tvoid *p;\n@@ -510,8 +538,8 @@ static bool is_anon(struct mem_ops *ops)\n static void __madvise_collapse(const char *msg, char *p, int nr_hpages,\n \t\t\t       struct mem_ops *ops, bool expect)\n {\n-\tint ret;\n \tstruct thp_settings settings = *thp_current_settings();\n+\tint ret, i;\n \n \tksft_print_msg(\"%s...\", msg);\n \n@@ -524,9 +552,16 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages,\n \t/*\n \t * Prevent khugepaged interference and tests that MADV_COLLAPSE\n \t * ignores /sys/kernel/mm/transparent_hugepage/enabled\n+\t *\n+\t * \"inherit\" rather than \"never\" so that MADV_COLLAPSE on shmem still\n+\t * finds an order to build.\n \t */\n \tsettings.thp_enabled = THP_NEVER;\n \tsettings.shmem_enabled = SHMEM_NEVER;\n+\tfor (i = 0; i \u003c NR_ORDERS; i++) {\n+\t\tsettings.hugepages[i].enabled = THP_INHERIT;\n+\t\tsettings.shmem_hugepages[i].enabled = SHMEM_INHERIT;\n+\t}\n \tthp_push_settings(\u0026settings);\n \n \t/* Clear VM_NOHUGEPAGE */\n@@ -556,8 +591,11 @@ static bool wait_for_scan(const char *msg, char *p, size_t len,\n \t\tint nr_hpages, int collap_order, struct mem_ops *ops)\n {\n \tunsigned long hpage_size = page_size \u003c\u003c collap_order;\n-\tint full_scans;\n-\tint timeout = 6; /* 3 seconds */\n+\tunsigned long bytes = (unsigned long)nr_hpages * hpage_size;\n+\tint timeout, full_scans;\n+\n+\t/* Half-second ticks: three seconds floor, plus a second per 128M */\n+\ttimeout = 6 + 2 * (bytes / (128UL \u003c\u003c 20));\n \n \t/* Sanity check */\n \tif (!ops-\u003echeck_huge(p, len, 0, hpage_size))\n@@ -820,12 +858,10 @@ static void collapse_swapin_single_pte(struct collapse_context *c, struct mem_op\n \tops-\u003efault(p, 0, hpage_pmd_size);\n \n \tksft_print_msg(\"Swapout one page...\");\n-\tif (madvise(p, page_size, MADV_PAGEOUT))\n-\t\tksft_exit_fail_perror(\"madvise(MADV_PAGEOUT)\");\n-\tif (check_swap(p, page_size)) {\n+\tif (swapout_range(p, page_size)) {\n \t\tsuccess(\"OK\");\n \t} else {\n-\t\tfail(\"Fail\");\n+\t\tskip(\"Could not swap out\");\n \t\tgoto out;\n \t}\n \n@@ -846,12 +882,10 @@ static void collapse_max_ptes_swap(struct collapse_context *c, struct mem_ops *o\n \tops-\u003efault(p, 0, hpage_pmd_size);\n \n \tksft_print_msg(\"Swapout %d of %d pages...\", max_ptes_swap + 1, hpage_pmd_nr);\n-\tif (madvise(p, (max_ptes_swap + 1) * page_size, MADV_PAGEOUT))\n-\t\tksft_exit_fail_perror(\"madvise(MADV_PAGEOUT)\");\n-\tif (check_swap(p, (max_ptes_swap + 1) * page_size)) {\n+\tif (swapout_range(p, (max_ptes_swap + 1) * page_size)) {\n \t\tsuccess(\"OK\");\n \t} else {\n-\t\tfail(\"Fail\");\n+\t\tskip(\"Could not swap out\");\n \t\tgoto out;\n \t}\n \n@@ -863,12 +897,10 @@ static void collapse_max_ptes_swap(struct collapse_context *c, struct mem_ops *o\n \t\tops-\u003efault(p, 0, hpage_pmd_size);\n \t\tksft_print_msg(\"Swapout %d of %d pages...\", max_ptes_swap,\n \t\t       hpage_pmd_nr);\n-\t\tif (madvise(p, max_ptes_swap * page_size, MADV_PAGEOUT))\n-\t\t\tksft_exit_fail_perror(\"madvise(MADV_PAGEOUT)\");\n-\t\tif (check_swap(p, max_ptes_swap * page_size)) {\n+\t\tif (swapout_range(p, max_ptes_swap * page_size)) {\n \t\t\tsuccess(\"OK\");\n \t\t} else {\n-\t\t\tfail(\"Fail\");\n+\t\t\tskip(\"Could not swap out\");\n \t\t\tgoto out;\n \t\t}\n \n@@ -935,6 +967,16 @@ static void collapse_compound_extreme(struct collapse_context *c, struct mem_ops\n \tvoid *p;\n \tint i;\n \n+\t/*\n+\t * This needs hpage_pmd_nr PMD-order allocations in a row, which the\n+\t * allocator will not supply if the PMD is very large.\n+\t */\n+\tif (hpage_pmd_size \u003e (32UL \u003c\u003c 20)) {\n+\t\tksft_test_result_skip(\"%s: PMD too large for fault-time THP construction\\n\",\n+\t\t\t\t      __func__);\n+\t\treturn;\n+\t}\n+\n \tp = ops-\u003esetup_area(1);\n \tksft_print_msg(\"Construct PTE page table full of different PTE-mapped compound pages\\n\");\n \tfor (i = 0; i \u003c hpage_pmd_nr; i++) {\n@@ -1125,6 +1167,103 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops\n \tksft_test_result_report(exit_status, \"%s\\n\", __func__);\n }\n \n+/*\n+ * The parent writes to the fork-shared range throughout the child's\n+ * collapse.  CoW must keep the two apart: the child sees the pre-fork\n+ * content, the parent only its own writes.\n+ */\n+static void collapse_fork_cow_race(struct collapse_context *c, struct mem_ops *ops)\n+{\n+\tconst int stride = page_size / sizeof(int);\n+\tint wstatus, child_status, i, n;\n+\tunsigned long shared;\n+\tvolatile int *ip;\n+\tpid_t child;\n+\tint sync[2];\n+\tchar go = 1;\n+\tvoid *p;\n+\n+\t/* At a page per 10 ms, 64 pages spread the writes across the collapse */\n+\tn = 64;\n+\tshared = n * page_size;\n+\n+\tp = ops-\u003esetup_area(1);\n+\t/* Shared prefix, with the pre-fork pattern */\n+\tops-\u003efault(p, 0, shared);\n+\tif (pipe(sync))\n+\t\tksft_exit_fail_perror(\"pipe()\");\n+\n+\t/* A volatile pointer so the stores are not merged or dropped */\n+\tip = p;\n+\n+\tksft_print_msg(\"Fork, collapse in the child while the parent rewrites...\");\n+\tchild = fork();\n+\tif (!child) {\n+\t\tint collapse_status;\n+\n+\t\tclose(sync[0]);\n+\t\t/* Private remainder */\n+\t\tops-\u003efault(p, shared, hpage_pmd_size);\n+\t\t/* Start the parent unsharing, and give it a head start */\n+\t\tif (write(sync[1], \u0026go, 1) != 1)\n+\t\t\t_exit(KSFT_FAIL);\n+\t\tusleep(5000);\n+\t\tc-\u003ecollapse(\"Collapse a range the parent is writing to\",\n+\t\t\t    p, 1, ops, true);\n+\t\tcollapse_status = exit_status;\n+\t\tfor (i = 0; i \u003c n; i++)\n+\t\t\tif (ip[i * stride] != i + 0xdead0000)\n+\t\t\t\tbreak;\n+\t\tif (i == n)\n+\t\t\tsuccess(\"OK\");\n+\t\telse\n+\t\t\tfail(\"Fail: child content\");\n+\t\t/* The content check must not bury a failed collapse */\n+\t\tif (exit_status != KSFT_FAIL)\n+\t\t\texit_status = collapse_status;\n+\t\tops-\u003ecleanup_area(p, hpage_pmd_size);\n+\t\t_exit(exit_status);\n+\t}\n+\n+\tclose(sync[1]);\n+\tif (read(sync[0], \u0026go, 1) != 1)\n+\t\tksft_exit_fail_msg(\"child never reached the collapse\\n\");\n+\n+\t/*\n+\t * Unshare one page at a time: a burst would break CoW on the whole\n+\t * range before the collapse starts, leaving nothing shared to collapse.\n+\t */\n+\ti = 0;\n+\tfor (;;) {\n+\t\tif (i \u003c n)\n+\t\t\tip[i * stride] = i + 0xbeef0000;\n+\t\ti++;\n+\t\tusleep(10 * 1000);\n+\t\tif (waitpid(child, \u0026wstatus, WNOHANG))\n+\t\t\tbreak;\n+\t}\n+\n+\t/* Finish whatever the paced sweep did not reach */\n+\tfor (; i \u003c n; i++)\n+\t\tip[i * stride] = i + 0xbeef0000;\n+\t/* A child that died reading the racing pages is a failure, not a zero */\n+\tchild_status = WIFEXITED(wstatus) ? WEXITSTATUS(wstatus) : KSFT_FAIL;\n+\n+\tksft_print_msg(\"Check the parent sees only its own writes...\");\n+\tfor (i = 0; i \u003c n; i++)\n+\t\tif (ip[i * stride] != i + 0xbeef0000)\n+\t\t\tbreak;\n+\tif (i == n)\n+\t\tsuccess(\"OK\");\n+\telse\n+\t\tfail(\"Fail: parent content\");\n+\tops-\u003ecleanup_area(p, hpage_pmd_size);\n+\t/* The parent's check must not bury the child's verdict */\n+\tif (exit_status != KSFT_FAIL)\n+\t\texit_status = child_status;\n+\tksft_test_result_report(exit_status, \"%s\\n\", __func__);\n+}\n+\n static void madvise_collapse_existing_thps(struct collapse_context *c,\n \t\t\t\t\t   struct mem_ops *ops)\n {\n@@ -1170,6 +1309,200 @@ static void madvise_retracted_page_tables(struct collapse_context *c,\n \tksft_test_result_report(exit_status, \"%s\\n\", __func__);\n }\n \n+/* Smallest order khugepaged will consider for mTHP collapse */\n+#define MIN_MTHP_ORDER 2\n+\n+/* Time budget for one khugepaged pass in the collapse_order_* cases */\n+#define MTHP_PASS_TIMEOUT_S 30\n+\n+static size_t mthp_window_size(void)\n+{\n+\treturn page_size \u003c\u003c collapse_order;\n+}\n+\n+static void mthp_push_target_order(void)\n+{\n+\tstruct thp_settings settings = *thp_current_settings();\n+\tint i;\n+\n+\t/*\n+\t * Only the target order, and only for madvise: the cases fault their\n+\t * region first, so the sources stay order 0 whatever -s asked for.\n+\t */\n+\tsettings.thp_enabled = THP_NEVER;\n+\tfor (i = 0; i \u003c NR_ORDERS; i++)\n+\t\tsettings.hugepages[i].enabled = THP_NEVER;\n+\tsettings.hugepages[collapse_order].enabled = THP_MADVISE;\n+\tthp_push_settings(\u0026settings);\n+}\n+\n+static bool all_windows_at_order(void *p, size_t len)\n+{\n+\treturn is_range_backed_by_order(p, len, collapse_order,\n+\t\t\t\t\tpagemap_fd, kpageflags_fd);\n+}\n+\n+static bool any_window_at_order(void *p, size_t len)\n+{\n+\tsize_t window = mthp_window_size();\n+\tchar *addr = p;\n+\n+\tfor (; len \u003e= window; addr += window, len -= window) {\n+\t\tif (all_windows_at_order(addr, window))\n+\t\t\treturn true;\n+\t}\n+\treturn false;\n+}\n+\n+static void collapse_order_single_window(struct collapse_context *c,\n+\t\t\t\t\t struct mem_ops *ops)\n+{\n+\tsize_t window = mthp_window_size();\n+\tvoid *p;\n+\n+\tmthp_push_target_order();\n+\n+\tp = ops-\u003esetup_area(1);\n+\tops-\u003efault(p, window, 2 * window);\n+\tif (any_window_at_order(p, hpage_pmd_size))\n+\t\tksft_exit_fail_msg(\"Unexpected large folio after fault\\n\");\n+\n+\tif (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))\n+\t\tksft_exit_fail_perror(\"madvise(MADV_HUGEPAGE)\");\n+\tksft_print_msg(\"Collapse one fully populated window...\");\n+\tif (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))\n+\t\tfail(\"Timeout\");\n+\telse if (all_windows_at_order(p + window, window) \u0026\u0026\n+\t\t !any_window_at_order(p, window) \u0026\u0026\n+\t\t !any_window_at_order(p + 2 * window,\n+\t\t\t\t      hpage_pmd_size - 2 * window))\n+\t\tsuccess(\"OK\");\n+\telse\n+\t\tfail(\"Fail\");\n+\n+\tvalidate_memory(p, window, 2 * window);\n+\tops-\u003ecleanup_area(p, hpage_pmd_size);\n+\tthp_pop_settings();\n+\tksft_test_result_report(exit_status, \"%s\\n\", __func__);\n+}\n+\n+static void collapse_order_partial_window(struct collapse_context *c,\n+\t\t\t\t\t  struct mem_ops *ops)\n+{\n+\tvoid *p;\n+\n+\tmthp_push_target_order();\n+\n+\tp = ops-\u003esetup_area(1);\n+\tops-\u003efault(p, 0, page_size);\n+\tif (any_window_at_order(p, hpage_pmd_size))\n+\t\tksft_exit_fail_msg(\"Unexpected large folio after fault\\n\");\n+\n+\tif (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))\n+\t\tksft_exit_fail_perror(\"madvise(MADV_HUGEPAGE)\");\n+\tksft_print_msg(\"Collapse window with single PTE entry present...\");\n+\tif (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))\n+\t\tfail(\"Timeout\");\n+\telse if (all_windows_at_order(p, mthp_window_size()))\n+\t\tsuccess(\"OK\");\n+\telse\n+\t\tfail(\"Fail\");\n+\n+\tvalidate_memory(p, 0, page_size);\n+\tops-\u003ecleanup_area(p, hpage_pmd_size);\n+\tthp_pop_settings();\n+\tksft_test_result_report(exit_status, \"%s\\n\", __func__);\n+}\n+\n+static void collapse_order_max_ptes_none(struct collapse_context *c,\n+\t\t\t\t\t struct mem_ops *ops)\n+{\n+\tstruct thp_settings settings;\n+\tsize_t window = mthp_window_size();\n+\tvoid *p;\n+\n+\tmthp_push_target_order();\n+\tsettings = *thp_current_settings();\n+\tsettings.khugepaged.max_ptes_none = 0;\n+\tthp_push_settings(\u0026settings);\n+\n+\tp = ops-\u003esetup_area(1);\n+\tops-\u003efault(p, 0, 2 * window - page_size);\n+\tif (any_window_at_order(p, hpage_pmd_size))\n+\t\tksft_exit_fail_msg(\"Unexpected large folio after fault\\n\");\n+\n+\tif (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))\n+\t\tksft_exit_fail_perror(\"madvise(MADV_HUGEPAGE)\");\n+\tksft_print_msg(\"Collapse full window, not the one missing a page...\");\n+\tif (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))\n+\t\tfail(\"Timeout\");\n+\telse if (all_windows_at_order(p, window) \u0026\u0026\n+\t\t !any_window_at_order(p + window, window))\n+\t\tsuccess(\"OK\");\n+\telse\n+\t\tfail(\"Fail\");\n+\n+\tvalidate_memory(p, 0, 2 * window - page_size);\n+\tops-\u003ecleanup_area(p, hpage_pmd_size);\n+\tthp_pop_settings();\n+\tthp_pop_settings();\n+\tksft_test_result_report(exit_status, \"%s\\n\", __func__);\n+}\n+\n+static void collapse_order_mixed_sources(struct collapse_context *c,\n+\t\t\t\t\t struct mem_ops *ops)\n+{\n+\tint source_order = anon_order ? anon_order : MIN_MTHP_ORDER;\n+\tstruct thp_settings settings;\n+\tvoid *p;\n+\n+\tif (source_order \u003e= collapse_order ||\n+\t    !(thp_supported_orders() \u0026 (1UL \u003c\u003c source_order))) {\n+\t\tksft_test_result_skip(\"%s: no supported source order below target\\n\",\n+\t\t\t\t      __func__);\n+\t\treturn;\n+\t}\n+\n+\tmthp_push_target_order();\n+\n+\tsettings = *thp_current_settings();\n+\tsettings.hugepages[source_order].enabled = THP_ALWAYS;\n+\tthp_push_settings(\u0026settings);\n+\tp = ops-\u003esetup_area(1);\n+\tops-\u003efault(p, 0, hpage_pmd_size);\n+\tthp_pop_settings();\n+\n+\t/*\n+\t * The allocator can fall back to smaller folios under fragmentation;\n+\t * having nothing to collapse from is not a failure.\n+\t */\n+\tif (!is_range_backed_by_order(p, hpage_pmd_size, source_order,\n+\t\t\t\t      pagemap_fd, kpageflags_fd)) {\n+\t\tksft_print_msg(\"No order-%d sources to collapse...\", source_order);\n+\t\tskip(\"Skip\");\n+\t\tops-\u003ecleanup_area(p, hpage_pmd_size);\n+\t\tthp_pop_settings();\n+\t\tksft_test_result_report(exit_status, \"%s\\n\", __func__);\n+\t\treturn;\n+\t}\n+\n+\tif (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))\n+\t\tksft_exit_fail_perror(\"madvise(MADV_HUGEPAGE)\");\n+\tksft_print_msg(\"Collapse region backed by order-%d sources...\",\n+\t\t       source_order);\n+\tif (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))\n+\t\tfail(\"Timeout\");\n+\telse if (all_windows_at_order(p, hpage_pmd_size))\n+\t\tsuccess(\"OK\");\n+\telse\n+\t\tfail(\"Fail\");\n+\n+\tvalidate_memory(p, 0, hpage_pmd_size);\n+\tops-\u003ecleanup_area(p, hpage_pmd_size);\n+\tthp_pop_settings();\n+\tksft_test_result_report(exit_status, \"%s\\n\", __func__);\n+}\n+\n static void usage(void)\n {\n \tfprintf(stderr, \"\\nUsage: ./khugepaged [OPTIONS] \u003ctest type\u003e [dir]\\n\\n\");\n@@ -1187,11 +1520,14 @@ static void usage(void)\n \tfprintf(stderr,\t\"\\t\\t-s: mTHP size, expressed as page order.\\n\");\n \tfprintf(stderr,\t\"\\t\\t    Defaults to 0. Use this size for anon or shmem allocations.\\n\");\n \tfprintf(stderr,\t\"\\t\\t-c: collapse order for mTHP collapse, expressed as page order.\\n\");\n+\tfprintf(stderr,\t\"\\t\\t    Defaults to every supported order below the PMD.\\n\");\n+\tfprintf(stderr,\t\"\\t\\t    -s, if set, is the source order for the mixed-source case.\\n\");\n \texit(1);\n }\n \n static void parse_test_type(int argc, char **argv)\n {\n+\tbool mthp_context_implied = false;\n \tint opt;\n \tchar *buf;\n \tconst char *token;\n@@ -1203,6 +1539,7 @@ static void parse_test_type(int argc, char **argv)\n \t\t\tbreak;\n \t\tcase 'c':\n \t\t\tcollapse_order = atoi(optarg);\n+\t\t\tcollapse_order_set = true;\n \t\t\tbreak;\n \t\tcase 'h':\n \t\tdefault:\n@@ -1210,12 +1547,25 @@ static void parse_test_type(int argc, char **argv)\n \t\t}\n \t}\n \n+\t/*\n+\t * Both orders end up as array indices and shift counts, so neither\n+\t * can be negative, and a zero collapse order asks for base pages.\n+\t */\n+\tif (anon_order \u003c 0 || anon_order \u003e hpage_pmd_order)\n+\t\tksft_exit_fail_msg(\"-s takes an order in 0..%d, not %d\\n\",\n+\t\t\t\t   hpage_pmd_order, anon_order);\n+\tif (collapse_order_set \u0026\u0026\n+\t    (collapse_order \u003c= 0 || collapse_order \u003e= hpage_pmd_order))\n+\t\tksft_exit_fail_msg(\"-c takes an order in 1..%d, not %d\\n\",\n+\t\t\t\t   hpage_pmd_order - 1, collapse_order);\n+\n \targv += optind;\n \targc -= optind;\n \n \tif (argc == 0) {\n-\t\t/* Backwards compatibility */\n+\t\t/* No arguments: anon under every context */\n \t\tkhugepaged_context =  \u0026__khugepaged_context;\n+\t\tmthp_khugepaged_context =  \u0026__mthp_khugepaged_context;\n \t\tmadvise_context =  \u0026__madvise_context;\n \t\tanon_ops = \u0026__anon_ops;\n \t\treturn;\n@@ -1226,13 +1576,14 @@ static void parse_test_type(int argc, char **argv)\n \n \tif (!strcmp(token, \"all\")) {\n \t\tkhugepaged_context =  \u0026__khugepaged_context;\n+\t\tmthp_khugepaged_context =  \u0026__mthp_khugepaged_context;\n \t\tmadvise_context =  \u0026__madvise_context;\n+\t\t/* The mTHP context has only anon cases; let other mem_types drop it */\n+\t\tmthp_context_implied = true;\n \t} else if (!strcmp(token, \"khugepaged\")) {\n \t\tkhugepaged_context =  \u0026__khugepaged_context;\n \t} else if (!strcmp(token, \"mthp_khugepaged\")) {\n \t\tmthp_khugepaged_context =  \u0026__mthp_khugepaged_context;\n-\t\tif (collapse_order \u003c= 0 || collapse_order \u003e= hpage_pmd_order)\n-\t\t\tusage();\n \t} else if (!strcmp(token, \"madvise\")) {\n \t\tmadvise_context =  \u0026__madvise_context;\n \t} else {\n@@ -1248,20 +1599,20 @@ static void parse_test_type(int argc, char **argv)\n \t\tread_write_file_write_ops =  \u0026__read_write_file_write_ops;\n \t\tanon_ops = \u0026__anon_ops;\n \t\tshmem_ops = \u0026__shmem_ops;\n-\t\tif (mthp_khugepaged_context)\n-\t\t\tusage();\n \t} else if (!strcmp(buf, \"anon\")) {\n \t\tanon_ops = \u0026__anon_ops;\n \t} else if (!strcmp(buf, \"file\")) {\n \t\tread_only_file_ops =  \u0026__read_only_file_ops;\n \t\tread_write_file_read_ops =  \u0026__read_write_file_read_ops;\n \t\tread_write_file_write_ops =  \u0026__read_write_file_write_ops;\n-\t\tif (mthp_khugepaged_context)\n+\t\tif (mthp_khugepaged_context \u0026\u0026 !mthp_context_implied)\n \t\t\tusage();\n+\t\tmthp_khugepaged_context = NULL;\n \t} else if (!strcmp(buf, \"shmem\")) {\n \t\tshmem_ops = \u0026__shmem_ops;\n-\t\tif (mthp_khugepaged_context)\n+\t\tif (mthp_khugepaged_context \u0026\u0026 !mthp_context_implied)\n \t\t\tusage();\n+\t\tmthp_khugepaged_context = NULL;\n \t} else {\n \t\tusage();\n \t}\n@@ -1283,9 +1634,10 @@ struct test_case {\n \tstruct mem_ops *ops;\n \tconst char *desc;\n \ttest_fn fn;\n+\tint order;\t\t/* mTHP contexts: the collapse order */\n };\n \n-#define MAX_TEST_CASES 64\n+#define MAX_TEST_CASES 256\n static struct test_case test_cases[MAX_TEST_CASES];\n static int nr_test_cases;\n \n@@ -1298,6 +1650,7 @@ static int nr_test_cases;\n \t\t\t.ops\t= o,\t\t\t\t\t\\\n \t\t\t.desc\t= #t,\t\t\t\t\t\\\n \t\t\t.fn\t= t,\t\t\t\t\t\\\n+\t\t\t.order\t= collapse_order,\t\t\t\\\n \t\t};\t\t\t\t\t\t\t\\\n \t}\t\t\t\t\t\t\t\t\\\n \t} while (0)\n@@ -1338,8 +1691,66 @@ int main(int argc, char **argv)\n \n \tparse_test_type(argc, argv);\n \n+\tif (mthp_khugepaged_context) {\n+\t\tunsigned long orders = thp_supported_orders();\n+\n+\t\tif (collapse_order_set) {\n+\t\t\tif (!(orders \u0026 (1UL \u003c\u003c collapse_order)))\n+\t\t\t\tksft_exit_skip(\"Order %d is not a supported anon THP order\\n\",\n+\t\t\t\t\t       collapse_order);\n+\t\t\tif (collapse_order \u003c= anon_order)\n+\t\t\t\tksft_exit_skip(\"-c %d needs a source order below it, -s says %d\\n\",\n+\t\t\t\t\t       collapse_order, anon_order);\n+\t\t\tcollapse_orders[nr_collapse_orders++] = collapse_order;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * Every supported order above the source: -s makes the\n+\t\t\t * fault path hand out folios of that order, so a target\n+\t\t\t * at or below it has nothing to collapse.\n+\t\t\t */\n+\t\t\tint first = anon_order + 1;\n+\n+\t\t\tif (first \u003c MIN_MTHP_ORDER)\n+\t\t\t\tfirst = MIN_MTHP_ORDER;\n+\t\t\tfor (int i = first; i \u003c hpage_pmd_order; i++) {\n+\t\t\t\tif (orders \u0026 (1UL \u003c\u003c i))\n+\t\t\t\t\tcollapse_orders[nr_collapse_orders++] = i;\n+\t\t\t}\n+\t\t\tif (!nr_collapse_orders)\n+\t\t\t\tksft_print_msg(\"mTHP cases skipped: no order above the source\\n\");\n+\t\t}\n+\t}\n+\n+\tif (mthp_khugepaged_context) {\n+\t\tpagemap_fd = open(\"/proc/self/pagemap\", O_RDONLY);\n+\t\tif (pagemap_fd \u003c 0)\n+\t\t\tksft_exit_fail_perror(\"open(/proc/self/pagemap)\");\n+\t\tkpageflags_fd = open(\"/proc/kpageflags\", O_RDONLY);\n+\t\tif (kpageflags_fd \u003c 0)\n+\t\t\tksft_exit_fail_perror(\"open(/proc/kpageflags)\");\n+\t}\n+\n \tsetbuf(stdout, NULL);\n \n+\t/*\n+\t * Without a PMD-order page cache folio the kernel refuses these\n+\t * collapses, so there is nothing to test.\n+\t */\n+\tif (!(thp_file_supported_orders() \u0026 (1UL \u003c\u003c hpage_pmd_order))) {\n+\t\tif (shmem_ops) {\n+\t\t\tksft_print_msg(\"no PMD-order page cache folio: skipping shmem\\n\");\n+\t\t\tshmem_ops = NULL;\n+\t\t}\n+\t\tif (read_only_file_ops) {\n+\t\t\tksft_print_msg(\"no PMD-order page cache folio: skipping file\\n\");\n+\t\t\tread_only_file_ops = NULL;\n+\t\t\tread_write_file_read_ops = NULL;\n+\t\t\tread_write_file_write_ops = NULL;\n+\t\t}\n+\t\tif (!anon_ops \u0026\u0026 !shmem_ops \u0026\u0026 !read_only_file_ops)\n+\t\t\tksft_exit_skip(\"No mem_type left to run\\n\");\n+\t}\n+\n \tdefault_settings.khugepaged.max_ptes_none = hpage_pmd_nr - 1;\n \tdefault_settings.khugepaged.max_ptes_swap = hpage_pmd_nr / 8;\n \tdefault_settings.khugepaged.max_ptes_shared = hpage_pmd_nr / 2;\n@@ -1357,7 +1768,17 @@ int main(int argc, char **argv)\n \tTEST(collapse_full, khugepaged_context, read_write_file_read_ops);\n \tTEST(collapse_full, khugepaged_context, read_write_file_write_ops);\n \tTEST(collapse_full, khugepaged_context, shmem_ops);\n-\tTEST(collapse_full, mthp_khugepaged_context, anon_ops);\n+\tfor (int i = 0; i \u003c nr_collapse_orders; i++) {\n+\t\tcollapse_order = collapse_orders[i];\n+\t\tTEST(collapse_full, mthp_khugepaged_context, anon_ops);\n+\t\tTEST(collapse_empty, mthp_khugepaged_context, anon_ops);\n+\t\tTEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops);\n+\t\tTEST(collapse_order_single_window, mthp_khugepaged_context, anon_ops);\n+\t\tTEST(collapse_order_partial_window, mthp_khugepaged_context, anon_ops);\n+\t\tTEST(collapse_order_max_ptes_none, mthp_khugepaged_context, anon_ops);\n+\t\tTEST(collapse_order_mixed_sources, mthp_khugepaged_context, anon_ops);\n+\t}\n+\n \tTEST(collapse_full, madvise_context, anon_ops);\n \tTEST(collapse_full, madvise_context, read_only_file_ops);\n \tTEST(collapse_full, madvise_context, read_write_file_read_ops);\n@@ -1365,11 +1786,8 @@ int main(int argc, char **argv)\n \tTEST(collapse_full, madvise_context, shmem_ops);\n \n \tTEST(collapse_empty, khugepaged_context, anon_ops);\n-\tTEST(collapse_empty, mthp_khugepaged_context, anon_ops);\n \tTEST(collapse_empty, madvise_context, anon_ops);\n \n-\tTEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops);\n-\n \tTEST(collapse_single_pte_entry, khugepaged_context, anon_ops);\n \tTEST(collapse_single_pte_entry, khugepaged_context, read_only_file_ops);\n \tTEST(collapse_single_pte_entry, khugepaged_context, read_write_file_read_ops);\n@@ -1424,6 +1842,9 @@ int main(int argc, char **argv)\n \tTEST(collapse_max_ptes_shared, khugepaged_context, anon_ops);\n \tTEST(collapse_max_ptes_shared, madvise_context, anon_ops);\n \n+\tTEST(collapse_fork_cow_race, khugepaged_context, anon_ops);\n+\tTEST(collapse_fork_cow_race, madvise_context, anon_ops);\n+\n \tTEST(madvise_collapse_existing_thps, madvise_context, anon_ops);\n \tTEST(madvise_collapse_existing_thps, madvise_context, read_only_file_ops);\n \tTEST(madvise_collapse_existing_thps, madvise_context, read_write_file_read_ops);\n@@ -1439,7 +1860,15 @@ int main(int argc, char **argv)\n \tfor (int i = 0; i \u003c nr_test_cases; i++) {\n \t\tstruct test_case *t = \u0026test_cases[i];\n \n-\t\tksft_print_msg(\"\\n# Run test: %s (%s:%s)\\n\", t-\u003edesc, t-\u003ectx-\u003ename, t-\u003eops-\u003ename);\n+\t\tif (t-\u003ectx == \u0026__mthp_khugepaged_context) {\n+\t\t\tcollapse_order = t-\u003eorder;\n+\t\t\tksft_print_msg(\"\\n# Run test: %s (%s:%s, order %d)\\n\",\n+\t\t\t\t       t-\u003edesc, t-\u003ectx-\u003ename, t-\u003eops-\u003ename,\n+\t\t\t\t       t-\u003eorder);\n+\t\t} else {\n+\t\t\tksft_print_msg(\"\\n# Run test: %s (%s:%s)\\n\", t-\u003edesc,\n+\t\t\t\t       t-\u003ectx-\u003ename, t-\u003eops-\u003ename);\n+\t\t}\n \t\tt-\u003efn(t-\u003ectx, t-\u003eops);\n \t}\n \ndiff --git a/tools/testing/selftests/mm/khugepaged_race.c b/tools/testing/selftests/mm/khugepaged_race.c\nnew file mode 100644\nindex 0000000000000..c3478b5123e23\n--- /dev/null\n+++ b/tools/testing/selftests/mm/khugepaged_race.c\n@@ -0,0 +1,533 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Race collapse against faults, GUP pins, fork, mremap and MADV_DONTNEED\n+ * over the same ranges.  A racing page must read as its pattern or as\n+ * zero, never anything else; the kernel's own assertions in dmesg are the\n+ * other half of the check.\n+ */\n+#define _GNU_SOURCE\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003cpthread.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/ioctl.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003csys/time.h\u003e\n+#include \u003csys/wait.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"kselftest.h\"\n+#include \"vm_util.h\"\n+#include \"hugepage_settings.h\"\n+#include \"../../../../mm/gup_test.h\"\n+\n+#ifndef FOLL_WRITE\n+#define FOLL_WRITE 0x01\n+#endif\n+\n+#define BASE_ADDR\t((void *)(1UL \u003c\u003c 30))\n+#define PASS_TIMEOUT_S\t30\n+\n+/*\n+ * PMD-sized areas the racing threads share, plus one for the mremap\n+ * thread.  -a shrinks it where a PMD is 512M.\n+ */\n+#define DEFAULT_SHARED_AREAS\t3\n+static int nr_shared_areas;\n+static int nr_areas;\n+\n+static unsigned long hpage_pmd_size;\n+static unsigned long page_size;\n+/* nr_areas PMD-sized areas; the last one belongs to the mremap thread */\n+static char *region;\n+static char *mremap_area;\n+static char *mremap_scratch;\n+static char *pageout_area;\n+static size_t pageout_size;\n+static int gup_fd = -1;\n+static volatile int stop;\n+static volatile int corrupted;\n+\n+static unsigned int pattern(unsigned long page_idx)\n+{\n+\tunsigned int val = (unsigned int)page_idx * 2654435761U;\n+\n+\treturn val ? val : 1;\t/* never collides with the zero-fill */\n+}\n+\n+/* Zero means never written; anything else must be this page's pattern */\n+static bool page_is_corrupt(unsigned long page_idx, unsigned int *val)\n+{\n+\t*val = *(unsigned int *)(region + page_idx * page_size);\n+\n+\treturn *val \u0026\u0026 *val != pattern(page_idx);\n+}\n+\n+static void check_page(unsigned long page_idx)\n+{\n+\tunsigned int val;\n+\n+\tif (page_is_corrupt(page_idx, \u0026val)) {\n+\t\tcorrupted = 1;\n+\t\tksft_print_msg(\"Corruption at page %lu: %#x != %#x\\n\",\n+\t\t\t       page_idx, val, pattern(page_idx));\n+\t}\n+}\n+\n+static unsigned long shared_pages(void)\n+{\n+\treturn nr_shared_areas * hpage_pmd_size / page_size;\n+}\n+\n+static unsigned long rand_page(unsigned int *seed)\n+{\n+\treturn (unsigned long)rand_r(seed) % shared_pages();\n+}\n+\n+/* Clamp so a range never reaches the mremap thread's area */\n+static unsigned long room_from(unsigned long page_idx, unsigned long want)\n+{\n+\tunsigned long left = shared_pages() - page_idx;\n+\n+\treturn want \u003c left ? want : left;\n+}\n+\n+static void *faulter_fn(void *arg)\n+{\n+\tunsigned int seed = (unsigned long)arg;\n+\n+\twhile (!stop) {\n+\t\tunsigned long page_idx = rand_page(\u0026seed);\n+\n+\t\tif (rand_r(\u0026seed) \u0026 1)\n+\t\t\t*(unsigned int *)(region + page_idx * page_size) =\n+\t\t\t\tpattern(page_idx);\n+\t\telse\n+\t\t\tcheck_page(page_idx);\n+\t}\n+\treturn NULL;\n+}\n+\n+static void *dontneed_fn(void *arg)\n+{\n+\tunsigned int seed = (unsigned long)arg;\n+\n+\twhile (!stop) {\n+\t\tunsigned long page_idx = rand_page(\u0026seed);\n+\t\tunsigned long nr = 1UL \u003c\u003c (rand_r(\u0026seed) % 6);\t/* 1..32 pages */\n+\n+\t\t/*\n+\t\t * Now and then zap a whole PMD-aligned area: only a zap that\n+\t\t * covers the full table frees the table itself (CONFIG_PT_RECLAIM).\n+\t\t */\n+\t\tif (!(rand_r(\u0026seed) % 64)) {\n+\t\t\tunsigned long area = page_idx /\n+\t\t\t\t\t(hpage_pmd_size / page_size);\n+\n+\t\t\tmadvise(region + area * hpage_pmd_size,\n+\t\t\t\thpage_pmd_size, MADV_DONTNEED);\n+\t\t} else {\n+\t\t\tmadvise(region + page_idx * page_size,\n+\t\t\t\troom_from(page_idx, nr) * page_size,\n+\t\t\t\tMADV_DONTNEED);\n+\t\t}\n+\t\tusleep(rand_r(\u0026seed) % 500);\n+\t}\n+\treturn NULL;\n+}\n+\n+static void *pinner_fn(void *arg)\n+{\n+\tunsigned int seed = (unsigned long)arg;\n+\n+\twhile (!stop) {\n+\t\tstruct gup_test gup = {};\n+\t\tunsigned long page_idx = rand_page(\u0026seed);\n+\t\tunsigned long nr = room_from(page_idx, 16);\n+\n+\t\tgup.addr = (unsigned long)(region + page_idx * page_size);\n+\t\tgup.size = nr * page_size;\n+\t\tgup.nr_pages_per_call = nr;\n+\t\tgup.gup_flags = FOLL_WRITE;\n+\t\t/* Racing MADV_DONTNEED makes transient failures expected */\n+\t\tioctl(gup_fd, PIN_FAST_BENCHMARK, \u0026gup);\n+\t\tusleep(rand_r(\u0026seed) % 200);\n+\t}\n+\treturn NULL;\n+}\n+\n+static void *forker_fn(void *arg)\n+{\n+\tunsigned int seed = (unsigned long)arg;\n+\n+\twhile (!stop) {\n+\t\tpid_t pid = fork();\n+\n+\t\tif (pid == 0) {\n+\t\t\tunsigned int val;\n+\t\t\tint bad = 0;\n+\n+\t\t\t/*\n+\t\t\t * No stdio in the child: a thread may hold stdout's\n+\t\t\t * lock across the fork, and printing under it hangs.\n+\t\t\t */\n+\t\t\tfor (int i = 0; i \u003c 16; i++)\n+\t\t\t\tbad |= page_is_corrupt(rand_page(\u0026seed), \u0026val);\n+\t\t\t_exit(bad);\n+\t\t}\n+\t\tif (pid \u003e 0) {\n+\t\t\tint wstatus;\n+\n+\t\t\tif (waitpid(pid, \u0026wstatus, 0) \u003c 0)\n+\t\t\t\tksft_exit_fail_perror(\"waitpid()\");\n+\t\t\t/* A child killed on the read counts too */\n+\t\t\tif (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus))\n+\t\t\t\tcorrupted = 1;\n+\t\t}\n+\t\tusleep(rand_r(\u0026seed) % 2000);\n+\t}\n+\treturn NULL;\n+}\n+\n+static void *mremapper_fn(void *arg)\n+{\n+\tunsigned int seed = (unsigned long)arg;\n+\n+\twhile (!stop) {\n+\t\tvoid *p;\n+\n+\t\tp = mremap(mremap_area, hpage_pmd_size, hpage_pmd_size,\n+\t\t\t   MREMAP_MAYMOVE | MREMAP_FIXED, mremap_scratch);\n+\t\tif (p == MAP_FAILED)\n+\t\t\tksft_exit_fail_perror(\"mremap() away\");\n+\t\tfor (int i = 0; i \u003c 8; i++)\n+\t\t\tmremap_scratch[(rand_r(\u0026seed) %\n+\t\t\t\t(hpage_pmd_size / page_size)) * page_size] = 1;\n+\t\tp = mremap(mremap_scratch, hpage_pmd_size, hpage_pmd_size,\n+\t\t\t   MREMAP_MAYMOVE | MREMAP_FIXED, mremap_area);\n+\t\tif (p == MAP_FAILED)\n+\t\t\tksft_exit_fail_perror(\"mremap() back\");\n+\t\tusleep(rand_r(\u0026seed) % 2000);\n+\t}\n+\treturn NULL;\n+}\n+\n+/*\n+ * Swap traffic and LRU churn on a region nothing else writes, so a page's\n+ * pattern must survive the trip through swap exactly.\n+ */\n+static void *pageout_fn(void *arg)\n+{\n+\tunsigned int seed = (unsigned long)arg;\n+\tunsigned long nr = pageout_size / page_size;\n+\tunsigned long i;\n+\n+\tfor (i = 0; i \u003c nr; i++)\n+\t\t*(unsigned int *)(pageout_area + i * page_size) = pattern(i);\n+\n+\twhile (!stop) {\n+\t\tmadvise(pageout_area, pageout_size, MADV_PAGEOUT);\n+\t\tfor (i = 0; i \u003c nr \u0026\u0026 !stop; i++) {\n+\t\t\tunsigned int val = *(unsigned int *)(pageout_area +\n+\t\t\t\t\t\t\t     i * page_size);\n+\n+\t\t\tif (val != pattern(i)) {\n+\t\t\t\tcorrupted = 1;\n+\t\t\t\tksft_print_msg(\"Pageout corruption at page %lu: %#x != %#x\\n\",\n+\t\t\t\t\t       i, val, pattern(i));\n+\t\t\t}\n+\t\t}\n+\t\tusleep(rand_r(\u0026seed) % 2000);\n+\t}\n+\treturn NULL;\n+}\n+\n+/* Compaction migrates the collapse sources while they are being gathered */\n+static void *compactor_fn(void *arg)\n+{\n+\tunsigned int seed = (unsigned long)arg;\n+\tint fd = open(\"/proc/sys/vm/compact_memory\", O_WRONLY);\n+\n+\tif (fd \u003c 0) {\n+\t\tksft_print_msg(\"No compact_memory; compactor idle\\n\");\n+\t\treturn NULL;\n+\t}\n+\twhile (!stop) {\n+\t\tif (write(fd, \"1\", 1) \u003c 0)\n+\t\t\tbreak;\n+\t\tusleep(10000 + rand_r(\u0026seed) % 100000);\n+\t}\n+\tclose(fd);\n+\treturn NULL;\n+}\n+\n+static bool swap_available(void)\n+{\n+\tchar line[256];\n+\tint lines = 0;\n+\tFILE *fp = fopen(\"/proc/swaps\", \"r\");\n+\n+\tif (!fp)\n+\t\treturn false;\n+\twhile (fgets(line, sizeof(line), fp))\n+\t\tlines++;\n+\tfclose(fp);\n+\treturn lines \u003e 1;\n+}\n+\n+static unsigned long now_ms(void)\n+{\n+\tstruct timeval tv;\n+\n+\tgettimeofday(\u0026tv, NULL);\n+\treturn tv.tv_sec * 1000UL + tv.tv_usec / 1000;\n+}\n+\n+static void usage(void)\n+{\n+\tfprintf(stderr,\n+\t\t\"Usage: khugepaged_race [-d seconds] [-m stepped|free|madvise] [-a areas] [-t mask]\\n\"\n+\t\t\"\\tWithout -m, every mode runs in turn.\\n\"\n+\t\t\"\\t-d: seconds per mode (default 5)\\n\"\n+\t\t\"\\t-a: number of shared PMD-sized playground areas (default 3)\\n\"\n+\t\t\"\\t-t: bitmask of racing threads to start, for bisecting a failure\\n\");\n+\texit(1);\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tstatic const char * const thread_names[] = {\n+\t\t\"faulter\", \"faulter2\", \"dontneed\", \"pinner\", \"forker\",\n+\t\t\"mremapper\", \"pageout\", \"compactor\",\n+\t};\n+\tvoid *(*const thread_fns[])(void *) = {\n+\t\tfaulter_fn, faulter_fn, dontneed_fn, pinner_fn, forker_fn,\n+\t\tmremapper_fn, pageout_fn, compactor_fn,\n+\t};\n+\tenum { T_FAULTER, T_FAULTER2, T_DONTNEED, T_PINNER, T_FORKER,\n+\t       T_MREMAPPER, T_PAGEOUT, T_COMPACTOR };\n+\tconst unsigned long pageout_bit = 1UL \u003c\u003c T_PAGEOUT;\n+\tconst unsigned long compactor_bit = 1UL \u003c\u003c T_COMPACTOR;\n+\tconst int nr_threads = ARRAY_SIZE(thread_names);\n+\tpthread_t threads[ARRAY_SIZE(thread_names)];\n+\tstatic const char * const all_modes[] = { \"stepped\", \"free\", \"madvise\" };\n+\tstatic const bool occupancies[] = { false, true };\t/* strict, holes */\n+\tstatic const bool pressures[] = { false, true };\t/* quiet, under pressure */\n+\tconst int nr_occupancies = ARRAY_SIZE(occupancies);\n+\tconst int nr_pressures = ARRAY_SIZE(pressures);\n+\tconst char *one_mode[1];\n+\tconst char * const *modes = all_modes;\n+\tint nr_modes = ARRAY_SIZE(all_modes);\n+\tconst char *mode_arg = NULL;\n+\tstruct thp_settings settings;\n+\tunsigned long end_ms;\n+\tint duration_s = 5;\n+\tunsigned long thread_mask = ~0UL;\n+\tunsigned long base_mask;\n+\tbool have_swap;\n+\tchar label[64];\n+\tint nr_areas_arg = 0;\n+\tunsigned long i;\n+\tint steps = 0;\n+\tint opt;\n+\n+\twhile ((opt = getopt(argc, argv, \"a:d:m:t:h\")) != -1) {\n+\t\tswitch (opt) {\n+\t\tcase 'a':\n+\t\t\tnr_areas_arg = atoi(optarg);\n+\t\t\tbreak;\n+\t\tcase 'd':\n+\t\t\tduration_s = atoi(optarg);\n+\t\t\tbreak;\n+\t\tcase 'm':\n+\t\t\tmode_arg = optarg;\n+\t\t\tbreak;\n+\t\tcase 't':\n+\t\t\tthread_mask = strtoul(optarg, NULL, 0);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tusage();\n+\t\t}\n+\t}\n+\n+\tif (mode_arg) {\n+\t\tif (strcmp(mode_arg, \"stepped\") \u0026\u0026 strcmp(mode_arg, \"free\") \u0026\u0026\n+\t\t    strcmp(mode_arg, \"madvise\"))\n+\t\t\tusage();\n+\t\tone_mode[0] = mode_arg;\n+\t\tmodes = one_mode;\n+\t\tnr_modes = 1;\n+\t}\n+\n+\tksft_print_header();\n+\tif (!thp_available())\n+\t\tksft_exit_skip(\"Transparent Hugepages not available\\n\");\n+\n+\tpage_size = getpagesize();\n+\thpage_pmd_size = read_pmd_pagesize();\n+\tif (!hpage_pmd_size)\n+\t\tksft_exit_fail_msg(\"Reading PMD pagesize failed\\n\");\n+\n+\tgup_fd = open(\"/sys/kernel/debug/gup_test\", O_RDWR);\n+\tif (gup_fd \u003c 0)\n+\t\tksft_exit_skip(\"/sys/kernel/debug/gup_test requires CONFIG_GUP_TEST and root\\n\");\n+\n+\tnr_shared_areas = nr_areas_arg \u003e 0 ? nr_areas_arg : DEFAULT_SHARED_AREAS;\n+\tnr_areas = nr_shared_areas + 1;\n+\n+\t/*\n+\t * MREMAP_FIXED unmaps whatever is in the way without saying so, so\n+\t * claim the mremap thread's scratch address up front.\n+\t */\n+\tmremap_scratch = (char *)BASE_ADDR + 2 * nr_areas * hpage_pmd_size;\n+\tif (mmap(mremap_scratch, hpage_pmd_size, PROT_NONE,\n+\t\t MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE,\n+\t\t -1, 0) != (void *)mremap_scratch)\n+\t\tksft_exit_fail_perror(\"mmap() mremap scratch\");\n+\n+\tbase_mask = thread_mask;\n+\thave_swap = swap_available();\n+\tif (!have_swap)\n+\t\t/* No swap, no anon reclaim: compaction-only pressure */\n+\t\tksft_print_msg(\"no swap: the pageout thread is not started\\n\");\n+\n+\tksft_set_plan(nr_modes * nr_occupancies * nr_pressures);\n+\n+\tthp_save_settings();\n+\tthp_read_settings(\u0026settings);\n+\n+\t/* Base of the settings stack; the bottom entry is never popped */\n+\tthp_push_settings(\u0026settings);\n+\n+\tfor (int run = 0; run \u003c nr_modes * nr_occupancies * nr_pressures; run++) {\n+\t\tint rem = run % (nr_occupancies * nr_pressures);\n+\t\tconst char *mode = modes[run / (nr_occupancies * nr_pressures)];\n+\t\tbool holes = occupancies[rem / nr_pressures];\n+\t\tbool pressure = pressures[rem % nr_pressures];\n+\n+\t\tsnprintf(label, sizeof(label), \"%s/%s%s\", mode,\n+\t\t\t holes ? \"holes\" : \"strict\", pressure ? \"/pressure\" : \"\");\n+\t\tif (corrupted) {\n+\t\t\t/* Memory is suspect; the rest would prove nothing */\n+\t\t\tksft_test_result_skip(\"%s: skipped after corruption\\n\", label);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tthread_mask = base_mask;\n+\t\tif (!pressure)\n+\t\t\tthread_mask \u0026= ~(pageout_bit | compactor_bit);\n+\t\telse if (!have_swap)\n+\t\t\tthread_mask \u0026= ~pageout_bit;\n+\n+\t\tthp_read_settings(\u0026settings);\n+\t\tsettings.thp_enabled = THP_MADVISE;\n+\t\tsettings.thp_defrag = THP_DEFRAG_ALWAYS;\n+\t\tsettings.shmem_enabled = SHMEM_NEVER;\n+\t\tsettings.khugepaged.defrag = 1;\n+\t\tsettings.khugepaged.scan_sleep_millisecs =\n+\t\t\tstrcmp(mode, \"free\") ? 1000 : 0;\n+\t\tsettings.khugepaged.alloc_sleep_millisecs = 10;\n+\n+\t\t/*\n+\t\t * mTHP collapse honours only 0 or HPAGE_PMD_NR - 1 here.  The two\n+\t\t * ends race different paths: a strict window has every PTE\n+\t\t * present, a hole-heavy one is mostly zero-filled.\n+\t\t */\n+\t\tsettings.khugepaged.max_ptes_none = holes ?\n+\t\t\t(hpage_pmd_size / page_size) - 1 : 0;\n+\t\t/* One wake, one pass: the playground plus the forked children's copies */\n+\t\tsettings.khugepaged.pages_to_scan =\n+\t\t\tnr_areas * (hpage_pmd_size / page_size) * 8;\n+\t\tfor (i = 0; i \u003c NR_ORDERS; i++) {\n+\t\t\tif (thp_supported_orders() \u0026 (1UL \u003c\u003c i))\n+\t\t\t\tsettings.hugepages[i].enabled = THP_INHERIT;\n+\t\t}\n+\t\tthp_push_settings(\u0026settings);\n+\n+\t\tregion = mmap(BASE_ADDR, nr_areas * hpage_pmd_size,\n+\t\t\t      PROT_READ | PROT_WRITE, MAP_ANONYMOUS |\n+\t\t\t      MAP_PRIVATE | MAP_FIXED_NOREPLACE, -1, 0);\n+\t\tif (region != BASE_ADDR)\n+\t\t\tksft_exit_fail_perror(\"mmap() playground\");\n+\t\tmremap_area = region + nr_shared_areas * hpage_pmd_size;\n+\n+\t\tif (thread_mask \u0026 pageout_bit) {\n+\t\t\t/* Enough to drive real reclaim without swamping a small guest */\n+\t\t\tpageout_size = 4 * hpage_pmd_size;\n+\t\t\tif (pageout_size \u003c 16UL \u003c\u003c 20)\n+\t\t\t\tpageout_size = 16UL \u003c\u003c 20;\n+\t\t\tif (pageout_size \u003e 64UL \u003c\u003c 20)\n+\t\t\t\tpageout_size = 64UL \u003c\u003c 20;\n+\t\t\tpageout_area = mmap(NULL, pageout_size,\n+\t\t\t\t\t    PROT_READ | PROT_WRITE,\n+\t\t\t\t\t    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);\n+\t\t\tif (pageout_area == MAP_FAILED)\n+\t\t\t\tksft_exit_fail_perror(\"mmap() pageout area\");\n+\t\t}\n+\n+\t\t/* Populate so the first pass has something to collapse */\n+\t\tfor (i = 0; i \u003c nr_shared_areas * hpage_pmd_size / page_size; i++)\n+\t\t\t*(unsigned int *)(region + i * page_size) = pattern(i);\n+\t\tmemset(mremap_area, 1, hpage_pmd_size);\n+\t\tif (madvise(region, nr_areas * hpage_pmd_size, MADV_HUGEPAGE))\n+\t\t\tksft_exit_fail_perror(\"madvise(MADV_HUGEPAGE)\");\n+\n+\t\tfor (i = 0; i \u003c nr_threads; i++) {\n+\t\t\tif (!(thread_mask \u0026 (1UL \u003c\u003c i))) {\n+\t\t\t\tthreads[i] = 0;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (pthread_create(\u0026threads[i], NULL, thread_fns[i],\n+\t\t\t\t\t   (void *)(i + 1)))\n+\t\t\t\tksft_exit_fail_perror(thread_names[i]);\n+\t\t}\n+\n+\t\tend_ms = now_ms() + duration_s * 1000UL;\n+\t\tif (!strcmp(mode, \"stepped\")) {\n+\t\t\twhile (now_ms() \u003c end_ms \u0026\u0026 !corrupted) {\n+\t\t\t\tif (!khugepaged_full_pass(PASS_TIMEOUT_S))\n+\t\t\t\t\tksft_exit_fail_msg(\"khugepaged pass timed out\\n\");\n+\t\t\t\tsteps++;\n+\t\t\t}\n+\t\t} else if (!strcmp(mode, \"free\")) {\n+\t\t\twhile (now_ms() \u003c end_ms \u0026\u0026 !corrupted)\n+\t\t\t\tusleep(100 * 1000);\n+\t\t} else {\t/* madvise */\n+\t\t\twhile (now_ms() \u003c end_ms \u0026\u0026 !corrupted) {\n+\t\t\t\tfor (i = 0; i \u003c nr_shared_areas; i++) {\n+\t\t\t\t\tmadvise(region + i * hpage_pmd_size,\n+\t\t\t\t\t\thpage_pmd_size, MADV_COLLAPSE);\n+\t\t\t\t}\n+\t\t\t\tmadvise(region, nr_shared_areas * hpage_pmd_size,\n+\t\t\t\t\tMADV_DONTNEED);\n+\t\t\t\tsteps++;\n+\t\t\t}\n+\t\t}\n+\n+\t\tstop = 1;\n+\t\tfor (i = 0; i \u003c nr_threads; i++) {\n+\t\t\tif (threads[i])\n+\t\t\t\tpthread_join(threads[i], NULL);\n+\t\t}\n+\n+\t\tfor (i = 0; i \u003c nr_shared_areas * hpage_pmd_size / page_size; i++)\n+\t\t\tcheck_page(i);\n+\n+\t\tksft_test_result(!corrupted, \"%s: %ds, %d steps, no corruption\\n\",\n+\t\t\t\t label, duration_s, steps);\n+\n+\t\t/* The next mode maps the same fixed address with its own settings */\n+\t\tmunmap(region, nr_areas * hpage_pmd_size);\n+\t\tif (pageout_area) {\n+\t\t\tmunmap(pageout_area, pageout_size);\n+\t\t\tpageout_area = NULL;\n+\t\t}\n+\t\tthp_pop_settings();\n+\t\tstop = 0;\n+\t\tsteps = 0;\n+\t}\n+\n+\tksft_finished();\n+}\ndiff --git a/tools/testing/selftests/mm/khugepaged_sync_check.c b/tools/testing/selftests/mm/khugepaged_sync_check.c\nnew file mode 100644\nindex 0000000000000..1c1b942ac3258\n--- /dev/null\n+++ b/tools/testing/selftests/mm/khugepaged_sync_check.c\n@@ -0,0 +1,179 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Check that khugepaged_full_pass() drives khugepaged in step: one barrier\n+ * over one prepared window must collapse it with exactly one collapse\n+ * attempt attributed to its source pages, step after step.\n+ *\n+ * scan_sleep_millisecs is a minute so that a step which slept instead of\n+ * being woken blows the budget.\n+ */\n+#define _GNU_SOURCE\n+#include \u003cfcntl.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"kselftest.h\"\n+#include \"vm_util.h\"\n+#include \"hugepage_settings.h\"\n+\n+#define BASE_ADDR ((void *)(1UL \u003c\u003c 30))\n+/* Smallest order khugepaged considers */\n+#define TARGET_ORDER 2\n+#define NR_ITERATIONS 5\n+#define PASS_TIMEOUT_S 30\n+\n+static int pagemap_fd;\n+static int kpageflags_fd;\n+static int trace_events_fd = -1;\n+static unsigned long hpage_pmd_size;\n+\n+/*\n+ * The events are system-wide: switch them off however the test ends,\n+ * including from inside a helper that gives up.\n+ */\n+static void trace_events_off(void)\n+{\n+\tif (trace_events_fd \u003e= 0)\n+\t\ttracing_events_enable(trace_events_fd, false);\n+}\n+\n+/* Count the isolate events whose scan_pfn is one of the window's source PFNs */\n+static int count_attributed(unsigned long *pfns, int nr_pfns,\n+\t\t\t    unsigned int order)\n+{\n+\tchar line[1024];\n+\tint count = 0;\n+\tFILE *fp;\n+\n+\tfp = tracing_open_trace();\n+\tif (!fp)\n+\t\tksft_exit_fail_msg(\"Cannot open trace buffer\\n\");\n+\n+\twhile (fgets(line, sizeof(line), fp)) {\n+\t\tunsigned long val;\n+\t\tunsigned int ord;\n+\t\tchar *s, *o;\n+\t\tint i;\n+\n+\t\ts = strstr(line, \"mm_collapse_huge_page_isolate:\");\n+\t\tif (!s)\n+\t\t\tcontinue;\n+\t\tif (sscanf(s, \"mm_collapse_huge_page_isolate: scan_pfn=0x%lx\",\n+\t\t\t   \u0026val) != 1)\n+\t\t\tcontinue;\n+\t\to = strstr(s, \"order=\");\n+\t\tif (!o || sscanf(o, \"order=%u\", \u0026ord) != 1 || ord != order)\n+\t\t\tcontinue;\n+\t\tfor (i = 0; i \u003c nr_pfns; i++) {\n+\t\t\tif (val == pfns[i]) {\n+\t\t\t\tcount++;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\tfclose(fp);\n+\treturn count;\n+}\n+\n+static void one_step(int iteration)\n+{\n+\tconst size_t window = getpagesize() \u003c\u003c TARGET_ORDER;\n+\tconst int nr_pages = 1 \u003c\u003c TARGET_ORDER;\n+\tunsigned long pfns[1 \u003c\u003c TARGET_ORDER];\n+\tbool collapsed, passed;\n+\tint attributed;\n+\tchar *p;\n+\tint i;\n+\n+\tp = mmap(BASE_ADDR, hpage_pmd_size, PROT_READ | PROT_WRITE,\n+\t\t MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE, -1, 0);\n+\tif (p != BASE_ADDR)\n+\t\tksft_exit_fail_perror(\"mmap() window\");\n+\n+\tfor (i = 0; i \u003c nr_pages; i++) {\n+\t\tp[i * getpagesize()] = i + 1;\n+\t\tpfns[i] = pagemap_get_pfn(pagemap_fd, p + i * getpagesize());\n+\t\tif (pfns[i] == -1UL)\n+\t\t\tksft_exit_fail_msg(\"Source page not present\\n\");\n+\t}\n+\n+\t/* Clear before enabling so the buffer holds only this step's events */\n+\tif (tracing_clear_trace())\n+\t\tksft_exit_fail_msg(\"Cannot clear the trace buffer\\n\");\n+\tif (tracing_events_enable(trace_events_fd, true))\n+\t\tksft_exit_fail_msg(\"Cannot enable huge_memory events\\n\");\n+\n+\tif (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))\n+\t\tksft_exit_fail_perror(\"madvise(MADV_HUGEPAGE)\");\n+\tpassed = khugepaged_full_pass(PASS_TIMEOUT_S);\n+\n+\t/* Off before anything that can give up: the events are system-wide */\n+\tif (tracing_events_enable(trace_events_fd, false))\n+\t\tksft_exit_fail_msg(\"Cannot disable huge_memory events\\n\");\n+\tif (!passed)\n+\t\tksft_exit_fail_msg(\"khugepaged did not complete a full pass\\n\");\n+\n+\tcollapsed = is_range_backed_by_order(p, window, TARGET_ORDER,\n+\t\t\t\t\t     pagemap_fd, kpageflags_fd);\n+\tattributed = count_attributed(pfns, nr_pages, TARGET_ORDER);\n+\n+\tksft_test_result(collapsed \u0026\u0026 attributed == 1,\n+\t\t\t \"step %d: window collapsed, %d attributed result(s)\\n\",\n+\t\t\t iteration, attributed);\n+\n+\tmunmap(p, hpage_pmd_size);\n+}\n+\n+int main(void)\n+{\n+\tstruct thp_settings settings;\n+\tint i;\n+\n+\tksft_print_header();\n+\n+\tif (!thp_available())\n+\t\tksft_exit_skip(\"Transparent Hugepages not available\\n\");\n+\tif (!(thp_supported_orders() \u0026 (1UL \u003c\u003c TARGET_ORDER)))\n+\t\tksft_exit_skip(\"Order %d is not a supported anon THP order\\n\",\n+\t\t\t       TARGET_ORDER);\n+\n+\thpage_pmd_size = read_pmd_pagesize();\n+\tif (!hpage_pmd_size)\n+\t\tksft_exit_fail_msg(\"Reading PMD pagesize failed\\n\");\n+\tpagemap_fd = open(\"/proc/self/pagemap\", O_RDONLY);\n+\tif (pagemap_fd \u003c 0)\n+\t\tksft_exit_fail_perror(\"open(/proc/self/pagemap)\");\n+\tkpageflags_fd = open(\"/proc/kpageflags\", O_RDONLY);\n+\tif (kpageflags_fd \u003c 0)\n+\t\tksft_exit_skip(\"open(/proc/kpageflags) requires root\\n\");\n+\ttrace_events_fd = tracing_events_open(\"huge_memory\");\n+\tif (trace_events_fd \u003c 0)\n+\t\tksft_exit_skip(\"huge_memory events require tracefs and root\\n\");\n+\tatexit(trace_events_off);\n+\n+\tksft_set_plan(NR_ITERATIONS);\n+\n+\tthp_save_settings();\n+\tthp_read_settings(\u0026settings);\n+\tsettings.thp_enabled = THP_MADVISE;\n+\tsettings.thp_defrag = THP_DEFRAG_ALWAYS;\n+\tsettings.khugepaged.defrag = 1;\n+\tsettings.khugepaged.scan_sleep_millisecs = 60 * 1000;\n+\tsettings.khugepaged.alloc_sleep_millisecs = 60 * 1000;\n+\tsettings.khugepaged.max_ptes_none = (hpage_pmd_size / getpagesize()) - 1;\n+\t/* One wake must complete one full pass; see khugepaged_full_pass() */\n+\tsettings.khugepaged.pages_to_scan = 1UL \u003c\u003c 24;\n+\tfor (i = 0; i \u003c NR_ORDERS; i++)\n+\t\tsettings.hugepages[i].enabled = THP_NEVER;\n+\tsettings.hugepages[TARGET_ORDER].enabled = THP_INHERIT;\n+\t/* Base of the settings stack; the bottom entry is never popped */\n+\tthp_push_settings(\u0026settings);\n+\n+\tfor (i = 0; i \u003c NR_ITERATIONS; i++)\n+\t\tone_step(i);\n+\n+\tksft_finished();\n+}\ndiff --git a/tools/testing/selftests/mm/migration.c b/tools/testing/selftests/mm/migration.c\nindex f19d53c695764..fd35f8a7b5b83 100644\n--- a/tools/testing/selftests/mm/migration.c\n+++ b/tools/testing/selftests/mm/migration.c\n@@ -20,7 +20,6 @@\n \n #define TWOMEG\t\t(2\u003c\u003c20)\n #define RUNTIME\t\t(20)\n-#define ALIGN(x, a)\t(((x) + (a - 1)) \u0026 (~((a) - 1)))\n \n HUGETLB_SETUP_DEFAULT_PAGES(1)\n \ndiff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh\nindex d09f9f6a384ee..fc61907aa3b2a 100755\n--- a/tools/testing/selftests/mm/run_vmtests.sh\n+++ b/tools/testing/selftests/mm/run_vmtests.sh\n@@ -402,6 +402,12 @@ CATEGORY=\"pfnmap\" run_test ./pfnmap\n # COW tests\n CATEGORY=\"cow\" run_test ./cow\n \n+CATEGORY=\"thp\" run_test ./folio_order_check\n+\n+CATEGORY=\"thp\" run_test ./khugepaged_sync_check\n+\n+CATEGORY=\"thp\" run_test ./khugepaged_race\n+\n CATEGORY=\"thp\" run_test ./khugepaged\n \n CATEGORY=\"thp\" run_test ./khugepaged -s 2\n@@ -410,8 +416,6 @@ CATEGORY=\"thp\" run_test ./khugepaged all:shmem\n \n CATEGORY=\"thp\" run_test ./khugepaged -s 4 all:shmem\n \n-CATEGORY=\"thp\" run_test ./khugepaged -c 4 mthp_khugepaged:anon\n-\n # Try to create XFS if not provided\n if [ -z \"${SPLIT_HUGE_PAGE_TEST_XFS_PATH}\" ]; then\n     if test_selected \"thp\"; then\ndiff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c\nindex 86a6036928261..0adfe7dde7e59 100644\n--- a/tools/testing/selftests/mm/split_huge_page_test.c\n+++ b/tools/testing/selftests/mm/split_huge_page_test.c\n@@ -42,68 +42,6 @@ const char *kpageflags_proc = \"/proc/kpageflags\";\n int pagemap_fd;\n int kpageflags_fd;\n \n-static bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd,\n-\t\tint kpageflags_fd)\n-{\n-\tconst uint64_t folio_head_flags = KPF_THP | KPF_COMPOUND_HEAD;\n-\tconst uint64_t folio_tail_flags = KPF_THP | KPF_COMPOUND_TAIL;\n-\tconst unsigned long nr_pages = 1UL \u003c\u003c order;\n-\tunsigned long pfn_head;\n-\tuint64_t pfn_flags;\n-\tunsigned long pfn;\n-\tunsigned long i;\n-\n-\tpfn = pagemap_get_pfn(pagemap_fd, vaddr);\n-\n-\t/* non present page */\n-\tif (pfn == -1UL)\n-\t\treturn false;\n-\n-\tif (pageflags_get(pfn, kpageflags_fd, \u0026pfn_flags))\n-\t\tgoto fail;\n-\n-\t/* check for order-0 pages */\n-\tif (!order) {\n-\t\tif (pfn_flags \u0026 (folio_head_flags | folio_tail_flags))\n-\t\t\treturn false;\n-\t\treturn true;\n-\t}\n-\n-\t/* non THP folio */\n-\tif (!(pfn_flags \u0026 KPF_THP))\n-\t\treturn false;\n-\n-\tpfn_head = pfn \u0026 ~(nr_pages - 1);\n-\n-\tif (pageflags_get(pfn_head, kpageflags_fd, \u0026pfn_flags))\n-\t\tgoto fail;\n-\n-\t/* head PFN has no compound_head flag set */\n-\tif ((pfn_flags \u0026 folio_head_flags) != folio_head_flags)\n-\t\treturn false;\n-\n-\t/* check all tail PFN flags */\n-\tfor (i = 1; i \u003c nr_pages; i++) {\n-\t\tif (pageflags_get(pfn_head + i, kpageflags_fd, \u0026pfn_flags))\n-\t\t\tgoto fail;\n-\t\tif ((pfn_flags \u0026 folio_tail_flags) != folio_tail_flags)\n-\t\t\treturn false;\n-\t}\n-\n-\t/*\n-\t * check the PFN after this folio, but if its flags cannot be obtained,\n-\t * assume this folio has the expected order\n-\t */\n-\tif (pageflags_get(pfn_head + nr_pages, kpageflags_fd, \u0026pfn_flags))\n-\t\treturn true;\n-\n-\t/* If we find another tail page, then the folio is larger. */\n-\treturn (pfn_flags \u0026 folio_tail_flags) != folio_tail_flags;\n-fail:\n-\tksft_exit_fail_msg(\"Failed to get folio info\\n\");\n-\treturn false;\n-}\n-\n static int check_after_split_folio_orders(char *vaddr_start, size_t len,\n \t\tint pagemap_fd, int kpageflags_fd, int orders[], int nr_orders)\n {\ndiff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c\nindex 4821a35630363..af8324e1e8f2c 100644\n--- a/tools/testing/selftests/mm/vm_util.c\n+++ b/tools/testing/selftests/mm/vm_util.c\n@@ -490,6 +490,153 @@ int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags)\n \treturn 0;\n }\n \n+bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd,\n+\t\t\tint kpageflags_fd)\n+{\n+\tconst uint64_t folio_head_flags = KPF_THP | KPF_COMPOUND_HEAD;\n+\tconst uint64_t folio_tail_flags = KPF_THP | KPF_COMPOUND_TAIL;\n+\tconst unsigned long nr_pages = 1UL \u003c\u003c order;\n+\tunsigned long pfn_head;\n+\tuint64_t pfn_flags;\n+\tunsigned long pfn;\n+\tunsigned long i;\n+\n+\tpfn = pagemap_get_pfn(pagemap_fd, vaddr);\n+\n+\t/* non present page */\n+\tif (pfn == -1UL)\n+\t\treturn false;\n+\n+\tif (pageflags_get(pfn, kpageflags_fd, \u0026pfn_flags))\n+\t\tgoto fail;\n+\n+\t/* check for order-0 pages */\n+\tif (!order) {\n+\t\tif (pfn_flags \u0026 (folio_head_flags | folio_tail_flags))\n+\t\t\treturn false;\n+\t\treturn true;\n+\t}\n+\n+\t/* non THP folio */\n+\tif (!(pfn_flags \u0026 KPF_THP))\n+\t\treturn false;\n+\n+\tpfn_head = pfn \u0026 ~(nr_pages - 1);\n+\n+\tif (pageflags_get(pfn_head, kpageflags_fd, \u0026pfn_flags))\n+\t\tgoto fail;\n+\n+\t/* head PFN has no compound_head flag set */\n+\tif ((pfn_flags \u0026 folio_head_flags) != folio_head_flags)\n+\t\treturn false;\n+\n+\t/* check all tail PFN flags */\n+\tfor (i = 1; i \u003c nr_pages; i++) {\n+\t\tif (pageflags_get(pfn_head + i, kpageflags_fd, \u0026pfn_flags))\n+\t\t\tgoto fail;\n+\t\tif ((pfn_flags \u0026 folio_tail_flags) != folio_tail_flags)\n+\t\t\treturn false;\n+\t}\n+\n+\t/*\n+\t * check the PFN after this folio, but if its flags cannot be obtained,\n+\t * assume this folio has the expected order\n+\t */\n+\tif (pageflags_get(pfn_head + nr_pages, kpageflags_fd, \u0026pfn_flags))\n+\t\treturn true;\n+\n+\t/* If we find another tail page, then the folio is larger. */\n+\treturn (pfn_flags \u0026 folio_tail_flags) != folio_tail_flags;\n+fail:\n+\tksft_exit_fail_msg(\"Failed to get folio info\\n\");\n+\treturn false;\n+}\n+\n+/**\n+ * is_range_backed_by_order() - check that a range is backed by @order folios\n+ * @start: start of the range, a multiple of the folio size\n+ * @len: length of the range in bytes, a multiple of the folio size\n+ * @order: the folio order to check for\n+ * @pagemap_fd: open /proc/\u003cpid\u003e/pagemap of the range's owner\n+ * @kpageflags_fd: open /proc/kpageflags\n+ *\n+ * Every folio-sized, folio-aligned part of the range must map one folio of\n+ * @order, head to tail, with the head at the start of the part.  A part\n+ * backed by several smaller folios fails, and so does a folio mapped off\n+ * its natural alignment.\n+ *\n+ * Returns: true if the whole range is backed that way, false otherwise.\n+ */\n+bool is_range_backed_by_order(char *start, size_t len, int order,\n+\t\t\t      int pagemap_fd, int kpageflags_fd)\n+{\n+\tconst unsigned long nr_pages = 1UL \u003c\u003c order;\n+\tconst size_t folio_size = nr_pages * psize();\n+\tchar *vaddr;\n+\n+\tif ((uintptr_t)start % folio_size || len % folio_size)\n+\t\treturn false;\n+\n+\tfor (vaddr = start; vaddr \u003c start + len; vaddr += folio_size) {\n+\t\tconst unsigned long pfn = pagemap_get_pfn(pagemap_fd, vaddr);\n+\t\tunsigned long i;\n+\n+\t\t/* Not present, or a tail page */\n+\t\tif (pfn == -1UL || pfn % nr_pages)\n+\t\t\treturn false;\n+\n+\t\tfor (i = 1; i \u003c nr_pages; i++) {\n+\t\t\tchar *page = vaddr + i * psize();\n+\n+\t\t\tif (pagemap_get_pfn(pagemap_fd, page) != pfn + i)\n+\t\t\t\treturn false;\n+\t\t}\n+\n+\t\tif (!is_backed_by_folio(vaddr, order, pagemap_fd, kpageflags_fd))\n+\t\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+#define TRACEFS_ROOT \"/sys/kernel/tracing\"\n+\n+/*\n+ * Returns -1 without tracefs or the subsystem.  The events are system-wide:\n+ * whoever switches them on has to switch them off again, on every exit path.\n+ */\n+int tracing_events_open(const char *subsys)\n+{\n+\tchar path[256];\n+\n+\tsnprintf(path, sizeof(path), TRACEFS_ROOT \"/events/%s/enable\",\n+\t\t subsys);\n+\treturn open(path, O_WRONLY);\n+}\n+\n+int tracing_events_enable(int fd, bool enable)\n+{\n+\tif (pwrite(fd, enable ? \"1\" : \"0\", 1, 0) != 1)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+/* Drop what the trace buffer holds so far */\n+int tracing_clear_trace(void)\n+{\n+\tint fd = open(TRACEFS_ROOT \"/trace\", O_WRONLY | O_TRUNC);\n+\n+\tif (fd \u003c 0)\n+\t\treturn -1;\n+\tclose(fd);\n+\treturn 0;\n+}\n+\n+FILE *tracing_open_trace(void)\n+{\n+\treturn fopen(TRACEFS_ROOT \"/trace\", \"r\");\n+}\n+\n /* If `ioctls' non-NULL, the allowed ioctls will be returned into the var */\n int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len,\n \t\t\t      bool miss, bool wp, bool minor, uint64_t *ioctls)\ndiff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h\nindex 9a49af88702e4..5a91b9676ec5d 100644\n--- a/tools/testing/selftests/mm/vm_util.h\n+++ b/tools/testing/selftests/mm/vm_util.h\n@@ -10,6 +10,8 @@\n #include \u003clinux/fs.h\u003e\n \n #define BIT_ULL(nr)                   (1ULL \u003c\u003c (nr))\n+#define ALIGN(x, a)                   (((x) + (a) - 1) \u0026 ~((a) - 1))\n+\n #define PM_SOFT_DIRTY                 BIT_ULL(55)\n #define PM_MMAP_EXCLUSIVE             BIT_ULL(56)\n #define PM_UFFD_WP                    BIT_ULL(57)\n@@ -97,6 +99,10 @@ int64_t allocate_transhuge(void *ptr, int pagemap_fd);\n int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags);\n int gather_folio_orders(char *vaddr_start, size_t len,\n \t\tint pagemap_fd, int kpageflags_fd, int orders[], int nr_orders);\n+bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd,\n+\t\t\tint kpageflags_fd);\n+bool is_range_backed_by_order(char *start, size_t len, int order,\n+\t\t\t      int pagemap_fd, int kpageflags_fd);\n \n int uffd_register(int uffd, void *addr, uint64_t len,\n \t\t  bool miss, bool wp, bool minor);\n@@ -113,6 +119,10 @@ int close_procmap(struct procmap_fd *procmap);\n int write_sysfs(const char *file_path, unsigned long val);\n int read_sysfs(const char *file_path, unsigned long *val);\n bool softdirty_supported(void);\n+int tracing_events_open(const char *subsys);\n+int tracing_events_enable(int fd, bool enable);\n+int tracing_clear_trace(void);\n+FILE *tracing_open_trace(void);\n \n static inline int open_self_procmap(struct procmap_fd *procmap_out)\n {\n"
}

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

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "All modifications in this patch series are strictly confined to user-space selftests under tools/testing/selftests/mm/. No kernel image (vmlinux) or module code is altered, making the changes non-kernel/non-executable from the perspective of kernel fuzzing.",
  "WorthFuzzing": false
}

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 68270a1ab82b67973de67ffb4d354065ad5a8691
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Sep 8 15:15:39 2026 +0000

    syz-cluster: applied patch under review

diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index 2d5366196e309..308bbad73c11a 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -104,6 +104,9 @@ TEST_GEN_FILES += guard-regions
 TEST_GEN_FILES += merge
 TEST_GEN_FILES += rmap
 TEST_GEN_FILES += folio_split_race_test
+TEST_GEN_FILES += folio_order_check
+TEST_GEN_FILES += khugepaged_sync_check
+TEST_GEN_FILES += khugepaged_race
 
 ifneq ($(ARCH),arm64)
 TEST_GEN_FILES += soft-dirty
diff --git a/tools/testing/selftests/mm/folio_order_check.c b/tools/testing/selftests/mm/folio_order_check.c
new file mode 100644
index 0000000000000..fa736c9f701a4
--- /dev/null
+++ b/tools/testing/selftests/mm/folio_order_check.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Self-check for the vm_util folio-order helpers, is_backed_by_folio() and
+ * is_range_backed_by_order(), which the khugepaged mTHP cases use to detect
+ * collapse results.  For every anon THP order the kernel supports, fault
+ * memory in with only that order enabled and require the helpers to report
+ * exactly that order.
+ */
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "kselftest.h"
+#include "vm_util.h"
+#include "hugepage_settings.h"
+
+static int pagemap_fd;
+static int kpageflags_fd;
+
+static char *alloc_aligned(size_t size)
+{
+	size_t len = size * 2;
+	char *p, *aligned;
+
+	p = mmap(NULL, len, PROT_READ | PROT_WRITE,
+		 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+	if (p == MAP_FAILED)
+		ksft_exit_fail_perror("mmap()");
+
+	aligned = (char *)ALIGN((uintptr_t)p, size);
+	if (aligned != p)
+		munmap(p, aligned - p);
+	if (aligned + size != p + len)
+		munmap(aligned + size, p + len - aligned - size);
+
+	return aligned;
+}
+
+static void check_order(int order)
+{
+	struct thp_settings settings = *thp_current_settings();
+	size_t size = psize() << order;
+	bool ok = true;
+	char *p;
+	int i;
+
+	for (i = 0; i < NR_ORDERS; i++)
+		settings.hugepages[i].enabled = THP_NEVER;
+	if (order)
+		settings.hugepages[order].enabled = THP_ALWAYS;
+	thp_push_settings(&settings);
+
+	p = alloc_aligned(size);
+	*p = 1;
+
+	if (!is_range_backed_by_order(p, size, order, pagemap_fd, kpageflags_fd)) {
+		ksft_print_msg("order %d not detected after fault\n", order);
+		ok = false;
+	}
+
+	/* A lower order must be rejected: the folio is larger */
+	if (order && is_range_backed_by_order(p, size, order - 1,
+					      pagemap_fd, kpageflags_fd)) {
+		ksft_print_msg("order %d also reported as order %d\n",
+			       order, order - 1);
+		ok = false;
+	}
+
+	/* A large folio must not pass as order 0 */
+	if (order && is_range_backed_by_order(p, size, 0,
+					      pagemap_fd, kpageflags_fd)) {
+		ksft_print_msg("order %d also reported as order 0\n", order);
+		ok = false;
+	}
+
+	munmap(p, size);
+	thp_pop_settings();
+
+	ksft_test_result(ok, "order %d classified\n", order);
+}
+
+int main(void)
+{
+	struct thp_settings settings;
+	unsigned long orders;
+	int order;
+
+	ksft_print_header();
+
+	if (!thp_available())
+		ksft_exit_skip("Transparent Hugepages not available\n");
+
+	pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+	if (pagemap_fd < 0)
+		ksft_exit_fail_perror("open(/proc/self/pagemap)");
+	kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
+	if (kpageflags_fd < 0)
+		ksft_exit_skip("open(/proc/kpageflags) requires root\n");
+
+	orders = thp_supported_orders();
+	if (!orders)
+		ksft_exit_skip("No supported THP orders\n");
+
+	ksft_set_plan(__builtin_popcountl(orders) + 1);
+
+	thp_save_settings();
+	thp_read_settings(&settings);
+	/* Base of the settings stack; the bottom entry is never popped */
+	thp_push_settings(&settings);
+
+	check_order(0);
+	for (order = 1; order < NR_ORDERS; order++) {
+		if (!(orders & (1UL << order)))
+			continue;
+		check_order(order);
+	}
+
+	ksft_finished();
+}
diff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c
index e2642eca0d02b..df426f9218e71 100644
--- a/tools/testing/selftests/mm/hmm-tests.c
+++ b/tools/testing/selftests/mm/hmm-tests.c
@@ -65,7 +65,6 @@ enum {
 #define HMM_PATH_MAX    64
 #define NTIMES		10
 
-#define ALIGN(x, a) (((x) + (a - 1)) & (~((a) - 1)))
 /* Just the flags we need, copied from mm.h: */
 
 #ifndef FOLL_WRITE
diff --git a/tools/testing/selftests/mm/hugepage_settings.c b/tools/testing/selftests/mm/hugepage_settings.c
index d7917dce3abac..ca73f9ac8e9b6 100644
--- a/tools/testing/selftests/mm/hugepage_settings.c
+++ b/tools/testing/selftests/mm/hugepage_settings.c
@@ -183,6 +183,13 @@ void thp_read_settings(struct thp_settings *settings)
 	}
 }
 
+/* A store to either sleep knob wakes khugepaged, so write only on change */
+static void thp_update_num(const char *name, unsigned long num)
+{
+	if (thp_read_num(name) != num)
+		thp_write_num(name, num);
+}
+
 void thp_write_settings(struct thp_settings *settings)
 {
 	struct khugepaged_settings *khugepaged = &settings->khugepaged;
@@ -198,15 +205,15 @@ void thp_write_settings(struct thp_settings *settings)
 			shmem_enabled_strings[settings->shmem_enabled]);
 	thp_write_num("use_zero_page", settings->use_zero_page);
 
-	thp_write_num("khugepaged/defrag", khugepaged->defrag);
-	thp_write_num("khugepaged/alloc_sleep_millisecs",
-			khugepaged->alloc_sleep_millisecs);
-	thp_write_num("khugepaged/scan_sleep_millisecs",
-			khugepaged->scan_sleep_millisecs);
-	thp_write_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none);
-	thp_write_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap);
-	thp_write_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared);
-	thp_write_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan);
+	thp_update_num("khugepaged/defrag", khugepaged->defrag);
+	thp_update_num("khugepaged/alloc_sleep_millisecs",
+		       khugepaged->alloc_sleep_millisecs);
+	thp_update_num("khugepaged/scan_sleep_millisecs",
+		       khugepaged->scan_sleep_millisecs);
+	thp_update_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none);
+	thp_update_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap);
+	thp_update_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared);
+	thp_update_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan);
 
 	if (dev_queue_read_ahead_path[0])
 		write_num(dev_queue_read_ahead_path, settings->read_ahead_kb);
@@ -230,6 +237,41 @@ void thp_write_settings(struct thp_settings *settings)
 	}
 }
 
+/*
+ * Wait for a full khugepaged scan pass that started after this call: the
+ * pass in progress may already have passed this mm, so full_scans has to
+ * advance twice.
+ *
+ * A store to scan_sleep_millisecs wakes the daemon, but one made while it
+ * is scanning rather than sleeping is lost, so keep storing until the pass
+ * lands.
+ *
+ * One wake is one pass only if pages_to_scan covers every mm on the list.
+ */
+bool khugepaged_full_pass(unsigned int timeout_s)
+{
+	unsigned long deadline_ms = timeout_s * 1000UL;
+	unsigned long elapsed_ms = 0, poll_ms = 10;
+	unsigned long sleep_ms;
+	int pass;
+
+	sleep_ms = thp_read_num("khugepaged/scan_sleep_millisecs");
+	for (pass = 0; pass < 2; pass++) {
+		unsigned long target =
+			thp_read_num("khugepaged/full_scans") + 1;
+
+		while (thp_read_num("khugepaged/full_scans") < target) {
+			if (elapsed_ms >= deadline_ms)
+				return false;
+			thp_write_num("khugepaged/scan_sleep_millisecs",
+				      sleep_ms);
+			usleep(poll_ms * 1000);
+			elapsed_ms += poll_ms;
+		}
+	}
+	return true;
+}
+
 struct thp_settings *thp_current_settings(void)
 {
 	if (!settings_index) {
diff --git a/tools/testing/selftests/mm/hugepage_settings.h b/tools/testing/selftests/mm/hugepage_settings.h
index 726c73c43c05b..2ea169d117962 100644
--- a/tools/testing/selftests/mm/hugepage_settings.h
+++ b/tools/testing/selftests/mm/hugepage_settings.h
@@ -83,10 +83,21 @@ static inline void thp_save_settings(void)
 	hugepage_save_settings(/* thp = */ true, /* hugetlb = */ false);
 }
 
+bool khugepaged_full_pass(unsigned int timeout_s);
+
 void thp_set_read_ahead_path(char *path);
 unsigned long thp_supported_orders(void);
 unsigned long thp_shmem_supported_orders(void);
 
+/*
+ * The per-order shmem_enabled attribute is created for the orders the page
+ * cache can hold, not just for shmem, so it answers for regular files too.
+ */
+static inline unsigned long thp_file_supported_orders(void)
+{
+	return thp_shmem_supported_orders();
+}
+
 bool thp_available(void);
 bool thp_is_enabled(void);
 
diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index f82673f5f6b47..335f946eca61e 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -31,6 +31,11 @@ static unsigned long page_size;
 static int hpage_pmd_nr;
 static int anon_order;
 static int collapse_order;
+static bool collapse_order_set;
+static int collapse_orders[NR_ORDERS];
+static int nr_collapse_orders;
+static int pagemap_fd = -1;
+static int kpageflags_fd = -1;
 
 #define PID_SMAPS "/proc/self/smaps"
 #define TEST_FILE "collapse_test_file"
@@ -220,6 +225,29 @@ static bool check_swap(void *addr, unsigned long size)
 	return swap;
 }
 
+static bool swapout_range(void *p, unsigned long size)
+{
+	int i;
+
+	/* keep khugepaged from collapsing the range and swapping it back in */
+	if (madvise(p, size, MADV_NOHUGEPAGE))
+		ksft_exit_fail_perror("madvise(MADV_NOHUGEPAGE)");
+
+	/*
+	 * Retry several times because MADV_PAGEOUT is best effort.  Sleep
+	 * between the retries to give outstanding writeback a chance to
+	 * finish.
+	 */
+	for (i = 0; i < 40; i++) {
+		if (madvise(p, size, MADV_PAGEOUT))
+			ksft_exit_fail_perror("madvise(MADV_PAGEOUT)");
+		if (check_swap(p, size))
+			return true;
+		usleep(50 * 1000);
+	}
+	return false;
+}
+
 static void *alloc_mapping(int nr)
 {
 	void *p;
@@ -510,8 +538,8 @@ static bool is_anon(struct mem_ops *ops)
 static void __madvise_collapse(const char *msg, char *p, int nr_hpages,
 			       struct mem_ops *ops, bool expect)
 {
-	int ret;
 	struct thp_settings settings = *thp_current_settings();
+	int ret, i;
 
 	ksft_print_msg("%s...", msg);
 
@@ -524,9 +552,16 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages,
 	/*
 	 * Prevent khugepaged interference and tests that MADV_COLLAPSE
 	 * ignores /sys/kernel/mm/transparent_hugepage/enabled
+	 *
+	 * "inherit" rather than "never" so that MADV_COLLAPSE on shmem still
+	 * finds an order to build.
 	 */
 	settings.thp_enabled = THP_NEVER;
 	settings.shmem_enabled = SHMEM_NEVER;
+	for (i = 0; i < NR_ORDERS; i++) {
+		settings.hugepages[i].enabled = THP_INHERIT;
+		settings.shmem_hugepages[i].enabled = SHMEM_INHERIT;
+	}
 	thp_push_settings(&settings);
 
 	/* Clear VM_NOHUGEPAGE */
@@ -556,8 +591,11 @@ static bool wait_for_scan(const char *msg, char *p, size_t len,
 		int nr_hpages, int collap_order, struct mem_ops *ops)
 {
 	unsigned long hpage_size = page_size << collap_order;
-	int full_scans;
-	int timeout = 6; /* 3 seconds */
+	unsigned long bytes = (unsigned long)nr_hpages * hpage_size;
+	int timeout, full_scans;
+
+	/* Half-second ticks: three seconds floor, plus a second per 128M */
+	timeout = 6 + 2 * (bytes / (128UL << 20));
 
 	/* Sanity check */
 	if (!ops->check_huge(p, len, 0, hpage_size))
@@ -820,12 +858,10 @@ static void collapse_swapin_single_pte(struct collapse_context *c, struct mem_op
 	ops->fault(p, 0, hpage_pmd_size);
 
 	ksft_print_msg("Swapout one page...");
-	if (madvise(p, page_size, MADV_PAGEOUT))
-		ksft_exit_fail_perror("madvise(MADV_PAGEOUT)");
-	if (check_swap(p, page_size)) {
+	if (swapout_range(p, page_size)) {
 		success("OK");
 	} else {
-		fail("Fail");
+		skip("Could not swap out");
 		goto out;
 	}
 
@@ -846,12 +882,10 @@ static void collapse_max_ptes_swap(struct collapse_context *c, struct mem_ops *o
 	ops->fault(p, 0, hpage_pmd_size);
 
 	ksft_print_msg("Swapout %d of %d pages...", max_ptes_swap + 1, hpage_pmd_nr);
-	if (madvise(p, (max_ptes_swap + 1) * page_size, MADV_PAGEOUT))
-		ksft_exit_fail_perror("madvise(MADV_PAGEOUT)");
-	if (check_swap(p, (max_ptes_swap + 1) * page_size)) {
+	if (swapout_range(p, (max_ptes_swap + 1) * page_size)) {
 		success("OK");
 	} else {
-		fail("Fail");
+		skip("Could not swap out");
 		goto out;
 	}
 
@@ -863,12 +897,10 @@ static void collapse_max_ptes_swap(struct collapse_context *c, struct mem_ops *o
 		ops->fault(p, 0, hpage_pmd_size);
 		ksft_print_msg("Swapout %d of %d pages...", max_ptes_swap,
 		       hpage_pmd_nr);
-		if (madvise(p, max_ptes_swap * page_size, MADV_PAGEOUT))
-			ksft_exit_fail_perror("madvise(MADV_PAGEOUT)");
-		if (check_swap(p, max_ptes_swap * page_size)) {
+		if (swapout_range(p, max_ptes_swap * page_size)) {
 			success("OK");
 		} else {
-			fail("Fail");
+			skip("Could not swap out");
 			goto out;
 		}
 
@@ -935,6 +967,16 @@ static void collapse_compound_extreme(struct collapse_context *c, struct mem_ops
 	void *p;
 	int i;
 
+	/*
+	 * This needs hpage_pmd_nr PMD-order allocations in a row, which the
+	 * allocator will not supply if the PMD is very large.
+	 */
+	if (hpage_pmd_size > (32UL << 20)) {
+		ksft_test_result_skip("%s: PMD too large for fault-time THP construction\n",
+				      __func__);
+		return;
+	}
+
 	p = ops->setup_area(1);
 	ksft_print_msg("Construct PTE page table full of different PTE-mapped compound pages\n");
 	for (i = 0; i < hpage_pmd_nr; i++) {
@@ -1125,6 +1167,103 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops
 	ksft_test_result_report(exit_status, "%s\n", __func__);
 }
 
+/*
+ * The parent writes to the fork-shared range throughout the child's
+ * collapse.  CoW must keep the two apart: the child sees the pre-fork
+ * content, the parent only its own writes.
+ */
+static void collapse_fork_cow_race(struct collapse_context *c, struct mem_ops *ops)
+{
+	const int stride = page_size / sizeof(int);
+	int wstatus, child_status, i, n;
+	unsigned long shared;
+	volatile int *ip;
+	pid_t child;
+	int sync[2];
+	char go = 1;
+	void *p;
+
+	/* At a page per 10 ms, 64 pages spread the writes across the collapse */
+	n = 64;
+	shared = n * page_size;
+
+	p = ops->setup_area(1);
+	/* Shared prefix, with the pre-fork pattern */
+	ops->fault(p, 0, shared);
+	if (pipe(sync))
+		ksft_exit_fail_perror("pipe()");
+
+	/* A volatile pointer so the stores are not merged or dropped */
+	ip = p;
+
+	ksft_print_msg("Fork, collapse in the child while the parent rewrites...");
+	child = fork();
+	if (!child) {
+		int collapse_status;
+
+		close(sync[0]);
+		/* Private remainder */
+		ops->fault(p, shared, hpage_pmd_size);
+		/* Start the parent unsharing, and give it a head start */
+		if (write(sync[1], &go, 1) != 1)
+			_exit(KSFT_FAIL);
+		usleep(5000);
+		c->collapse("Collapse a range the parent is writing to",
+			    p, 1, ops, true);
+		collapse_status = exit_status;
+		for (i = 0; i < n; i++)
+			if (ip[i * stride] != i + 0xdead0000)
+				break;
+		if (i == n)
+			success("OK");
+		else
+			fail("Fail: child content");
+		/* The content check must not bury a failed collapse */
+		if (exit_status != KSFT_FAIL)
+			exit_status = collapse_status;
+		ops->cleanup_area(p, hpage_pmd_size);
+		_exit(exit_status);
+	}
+
+	close(sync[1]);
+	if (read(sync[0], &go, 1) != 1)
+		ksft_exit_fail_msg("child never reached the collapse\n");
+
+	/*
+	 * Unshare one page at a time: a burst would break CoW on the whole
+	 * range before the collapse starts, leaving nothing shared to collapse.
+	 */
+	i = 0;
+	for (;;) {
+		if (i < n)
+			ip[i * stride] = i + 0xbeef0000;
+		i++;
+		usleep(10 * 1000);
+		if (waitpid(child, &wstatus, WNOHANG))
+			break;
+	}
+
+	/* Finish whatever the paced sweep did not reach */
+	for (; i < n; i++)
+		ip[i * stride] = i + 0xbeef0000;
+	/* A child that died reading the racing pages is a failure, not a zero */
+	child_status = WIFEXITED(wstatus) ? WEXITSTATUS(wstatus) : KSFT_FAIL;
+
+	ksft_print_msg("Check the parent sees only its own writes...");
+	for (i = 0; i < n; i++)
+		if (ip[i * stride] != i + 0xbeef0000)
+			break;
+	if (i == n)
+		success("OK");
+	else
+		fail("Fail: parent content");
+	ops->cleanup_area(p, hpage_pmd_size);
+	/* The parent's check must not bury the child's verdict */
+	if (exit_status != KSFT_FAIL)
+		exit_status = child_status;
+	ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
 static void madvise_collapse_existing_thps(struct collapse_context *c,
 					   struct mem_ops *ops)
 {
@@ -1170,6 +1309,200 @@ static void madvise_retracted_page_tables(struct collapse_context *c,
 	ksft_test_result_report(exit_status, "%s\n", __func__);
 }
 
+/* Smallest order khugepaged will consider for mTHP collapse */
+#define MIN_MTHP_ORDER 2
+
+/* Time budget for one khugepaged pass in the collapse_order_* cases */
+#define MTHP_PASS_TIMEOUT_S 30
+
+static size_t mthp_window_size(void)
+{
+	return page_size << collapse_order;
+}
+
+static void mthp_push_target_order(void)
+{
+	struct thp_settings settings = *thp_current_settings();
+	int i;
+
+	/*
+	 * Only the target order, and only for madvise: the cases fault their
+	 * region first, so the sources stay order 0 whatever -s asked for.
+	 */
+	settings.thp_enabled = THP_NEVER;
+	for (i = 0; i < NR_ORDERS; i++)
+		settings.hugepages[i].enabled = THP_NEVER;
+	settings.hugepages[collapse_order].enabled = THP_MADVISE;
+	thp_push_settings(&settings);
+}
+
+static bool all_windows_at_order(void *p, size_t len)
+{
+	return is_range_backed_by_order(p, len, collapse_order,
+					pagemap_fd, kpageflags_fd);
+}
+
+static bool any_window_at_order(void *p, size_t len)
+{
+	size_t window = mthp_window_size();
+	char *addr = p;
+
+	for (; len >= window; addr += window, len -= window) {
+		if (all_windows_at_order(addr, window))
+			return true;
+	}
+	return false;
+}
+
+static void collapse_order_single_window(struct collapse_context *c,
+					 struct mem_ops *ops)
+{
+	size_t window = mthp_window_size();
+	void *p;
+
+	mthp_push_target_order();
+
+	p = ops->setup_area(1);
+	ops->fault(p, window, 2 * window);
+	if (any_window_at_order(p, hpage_pmd_size))
+		ksft_exit_fail_msg("Unexpected large folio after fault\n");
+
+	if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
+		ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)");
+	ksft_print_msg("Collapse one fully populated window...");
+	if (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))
+		fail("Timeout");
+	else if (all_windows_at_order(p + window, window) &&
+		 !any_window_at_order(p, window) &&
+		 !any_window_at_order(p + 2 * window,
+				      hpage_pmd_size - 2 * window))
+		success("OK");
+	else
+		fail("Fail");
+
+	validate_memory(p, window, 2 * window);
+	ops->cleanup_area(p, hpage_pmd_size);
+	thp_pop_settings();
+	ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
+static void collapse_order_partial_window(struct collapse_context *c,
+					  struct mem_ops *ops)
+{
+	void *p;
+
+	mthp_push_target_order();
+
+	p = ops->setup_area(1);
+	ops->fault(p, 0, page_size);
+	if (any_window_at_order(p, hpage_pmd_size))
+		ksft_exit_fail_msg("Unexpected large folio after fault\n");
+
+	if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
+		ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)");
+	ksft_print_msg("Collapse window with single PTE entry present...");
+	if (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))
+		fail("Timeout");
+	else if (all_windows_at_order(p, mthp_window_size()))
+		success("OK");
+	else
+		fail("Fail");
+
+	validate_memory(p, 0, page_size);
+	ops->cleanup_area(p, hpage_pmd_size);
+	thp_pop_settings();
+	ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
+static void collapse_order_max_ptes_none(struct collapse_context *c,
+					 struct mem_ops *ops)
+{
+	struct thp_settings settings;
+	size_t window = mthp_window_size();
+	void *p;
+
+	mthp_push_target_order();
+	settings = *thp_current_settings();
+	settings.khugepaged.max_ptes_none = 0;
+	thp_push_settings(&settings);
+
+	p = ops->setup_area(1);
+	ops->fault(p, 0, 2 * window - page_size);
+	if (any_window_at_order(p, hpage_pmd_size))
+		ksft_exit_fail_msg("Unexpected large folio after fault\n");
+
+	if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
+		ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)");
+	ksft_print_msg("Collapse full window, not the one missing a page...");
+	if (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))
+		fail("Timeout");
+	else if (all_windows_at_order(p, window) &&
+		 !any_window_at_order(p + window, window))
+		success("OK");
+	else
+		fail("Fail");
+
+	validate_memory(p, 0, 2 * window - page_size);
+	ops->cleanup_area(p, hpage_pmd_size);
+	thp_pop_settings();
+	thp_pop_settings();
+	ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
+static void collapse_order_mixed_sources(struct collapse_context *c,
+					 struct mem_ops *ops)
+{
+	int source_order = anon_order ? anon_order : MIN_MTHP_ORDER;
+	struct thp_settings settings;
+	void *p;
+
+	if (source_order >= collapse_order ||
+	    !(thp_supported_orders() & (1UL << source_order))) {
+		ksft_test_result_skip("%s: no supported source order below target\n",
+				      __func__);
+		return;
+	}
+
+	mthp_push_target_order();
+
+	settings = *thp_current_settings();
+	settings.hugepages[source_order].enabled = THP_ALWAYS;
+	thp_push_settings(&settings);
+	p = ops->setup_area(1);
+	ops->fault(p, 0, hpage_pmd_size);
+	thp_pop_settings();
+
+	/*
+	 * The allocator can fall back to smaller folios under fragmentation;
+	 * having nothing to collapse from is not a failure.
+	 */
+	if (!is_range_backed_by_order(p, hpage_pmd_size, source_order,
+				      pagemap_fd, kpageflags_fd)) {
+		ksft_print_msg("No order-%d sources to collapse...", source_order);
+		skip("Skip");
+		ops->cleanup_area(p, hpage_pmd_size);
+		thp_pop_settings();
+		ksft_test_result_report(exit_status, "%s\n", __func__);
+		return;
+	}
+
+	if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
+		ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)");
+	ksft_print_msg("Collapse region backed by order-%d sources...",
+		       source_order);
+	if (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))
+		fail("Timeout");
+	else if (all_windows_at_order(p, hpage_pmd_size))
+		success("OK");
+	else
+		fail("Fail");
+
+	validate_memory(p, 0, hpage_pmd_size);
+	ops->cleanup_area(p, hpage_pmd_size);
+	thp_pop_settings();
+	ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
 static void usage(void)
 {
 	fprintf(stderr, "\nUsage: ./khugepaged [OPTIONS] <test type> [dir]\n\n");
@@ -1187,11 +1520,14 @@ static void usage(void)
 	fprintf(stderr,	"\t\t-s: mTHP size, expressed as page order.\n");
 	fprintf(stderr,	"\t\t    Defaults to 0. Use this size for anon or shmem allocations.\n");
 	fprintf(stderr,	"\t\t-c: collapse order for mTHP collapse, expressed as page order.\n");
+	fprintf(stderr,	"\t\t    Defaults to every supported order below the PMD.\n");
+	fprintf(stderr,	"\t\t    -s, if set, is the source order for the mixed-source case.\n");
 	exit(1);
 }
 
 static void parse_test_type(int argc, char **argv)
 {
+	bool mthp_context_implied = false;
 	int opt;
 	char *buf;
 	const char *token;
@@ -1203,6 +1539,7 @@ static void parse_test_type(int argc, char **argv)
 			break;
 		case 'c':
 			collapse_order = atoi(optarg);
+			collapse_order_set = true;
 			break;
 		case 'h':
 		default:
@@ -1210,12 +1547,25 @@ static void parse_test_type(int argc, char **argv)
 		}
 	}
 
+	/*
+	 * Both orders end up as array indices and shift counts, so neither
+	 * can be negative, and a zero collapse order asks for base pages.
+	 */
+	if (anon_order < 0 || anon_order > hpage_pmd_order)
+		ksft_exit_fail_msg("-s takes an order in 0..%d, not %d\n",
+				   hpage_pmd_order, anon_order);
+	if (collapse_order_set &&
+	    (collapse_order <= 0 || collapse_order >= hpage_pmd_order))
+		ksft_exit_fail_msg("-c takes an order in 1..%d, not %d\n",
+				   hpage_pmd_order - 1, collapse_order);
+
 	argv += optind;
 	argc -= optind;
 
 	if (argc == 0) {
-		/* Backwards compatibility */
+		/* No arguments: anon under every context */
 		khugepaged_context =  &__khugepaged_context;
+		mthp_khugepaged_context =  &__mthp_khugepaged_context;
 		madvise_context =  &__madvise_context;
 		anon_ops = &__anon_ops;
 		return;
@@ -1226,13 +1576,14 @@ static void parse_test_type(int argc, char **argv)
 
 	if (!strcmp(token, "all")) {
 		khugepaged_context =  &__khugepaged_context;
+		mthp_khugepaged_context =  &__mthp_khugepaged_context;
 		madvise_context =  &__madvise_context;
+		/* The mTHP context has only anon cases; let other mem_types drop it */
+		mthp_context_implied = true;
 	} else if (!strcmp(token, "khugepaged")) {
 		khugepaged_context =  &__khugepaged_context;
 	} else if (!strcmp(token, "mthp_khugepaged")) {
 		mthp_khugepaged_context =  &__mthp_khugepaged_context;
-		if (collapse_order <= 0 || collapse_order >= hpage_pmd_order)
-			usage();
 	} else if (!strcmp(token, "madvise")) {
 		madvise_context =  &__madvise_context;
 	} else {
@@ -1248,20 +1599,20 @@ static void parse_test_type(int argc, char **argv)
 		read_write_file_write_ops =  &__read_write_file_write_ops;
 		anon_ops = &__anon_ops;
 		shmem_ops = &__shmem_ops;
-		if (mthp_khugepaged_context)
-			usage();
 	} else if (!strcmp(buf, "anon")) {
 		anon_ops = &__anon_ops;
 	} else if (!strcmp(buf, "file")) {
 		read_only_file_ops =  &__read_only_file_ops;
 		read_write_file_read_ops =  &__read_write_file_read_ops;
 		read_write_file_write_ops =  &__read_write_file_write_ops;
-		if (mthp_khugepaged_context)
+		if (mthp_khugepaged_context && !mthp_context_implied)
 			usage();
+		mthp_khugepaged_context = NULL;
 	} else if (!strcmp(buf, "shmem")) {
 		shmem_ops = &__shmem_ops;
-		if (mthp_khugepaged_context)
+		if (mthp_khugepaged_context && !mthp_context_implied)
 			usage();
+		mthp_khugepaged_context = NULL;
 	} else {
 		usage();
 	}
@@ -1283,9 +1634,10 @@ struct test_case {
 	struct mem_ops *ops;
 	const char *desc;
 	test_fn fn;
+	int order;		/* mTHP contexts: the collapse order */
 };
 
-#define MAX_TEST_CASES 64
+#define MAX_TEST_CASES 256
 static struct test_case test_cases[MAX_TEST_CASES];
 static int nr_test_cases;
 
@@ -1298,6 +1650,7 @@ static int nr_test_cases;
 			.ops	= o,					\
 			.desc	= #t,					\
 			.fn	= t,					\
+			.order	= collapse_order,			\
 		};							\
 	}								\
 	} while (0)
@@ -1338,8 +1691,66 @@ int main(int argc, char **argv)
 
 	parse_test_type(argc, argv);
 
+	if (mthp_khugepaged_context) {
+		unsigned long orders = thp_supported_orders();
+
+		if (collapse_order_set) {
+			if (!(orders & (1UL << collapse_order)))
+				ksft_exit_skip("Order %d is not a supported anon THP order\n",
+					       collapse_order);
+			if (collapse_order <= anon_order)
+				ksft_exit_skip("-c %d needs a source order below it, -s says %d\n",
+					       collapse_order, anon_order);
+			collapse_orders[nr_collapse_orders++] = collapse_order;
+		} else {
+			/*
+			 * Every supported order above the source: -s makes the
+			 * fault path hand out folios of that order, so a target
+			 * at or below it has nothing to collapse.
+			 */
+			int first = anon_order + 1;
+
+			if (first < MIN_MTHP_ORDER)
+				first = MIN_MTHP_ORDER;
+			for (int i = first; i < hpage_pmd_order; i++) {
+				if (orders & (1UL << i))
+					collapse_orders[nr_collapse_orders++] = i;
+			}
+			if (!nr_collapse_orders)
+				ksft_print_msg("mTHP cases skipped: no order above the source\n");
+		}
+	}
+
+	if (mthp_khugepaged_context) {
+		pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+		if (pagemap_fd < 0)
+			ksft_exit_fail_perror("open(/proc/self/pagemap)");
+		kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
+		if (kpageflags_fd < 0)
+			ksft_exit_fail_perror("open(/proc/kpageflags)");
+	}
+
 	setbuf(stdout, NULL);
 
+	/*
+	 * Without a PMD-order page cache folio the kernel refuses these
+	 * collapses, so there is nothing to test.
+	 */
+	if (!(thp_file_supported_orders() & (1UL << hpage_pmd_order))) {
+		if (shmem_ops) {
+			ksft_print_msg("no PMD-order page cache folio: skipping shmem\n");
+			shmem_ops = NULL;
+		}
+		if (read_only_file_ops) {
+			ksft_print_msg("no PMD-order page cache folio: skipping file\n");
+			read_only_file_ops = NULL;
+			read_write_file_read_ops = NULL;
+			read_write_file_write_ops = NULL;
+		}
+		if (!anon_ops && !shmem_ops && !read_only_file_ops)
+			ksft_exit_skip("No mem_type left to run\n");
+	}
+
 	default_settings.khugepaged.max_ptes_none = hpage_pmd_nr - 1;
 	default_settings.khugepaged.max_ptes_swap = hpage_pmd_nr / 8;
 	default_settings.khugepaged.max_ptes_shared = hpage_pmd_nr / 2;
@@ -1357,7 +1768,17 @@ int main(int argc, char **argv)
 	TEST(collapse_full, khugepaged_context, read_write_file_read_ops);
 	TEST(collapse_full, khugepaged_context, read_write_file_write_ops);
 	TEST(collapse_full, khugepaged_context, shmem_ops);
-	TEST(collapse_full, mthp_khugepaged_context, anon_ops);
+	for (int i = 0; i < nr_collapse_orders; i++) {
+		collapse_order = collapse_orders[i];
+		TEST(collapse_full, mthp_khugepaged_context, anon_ops);
+		TEST(collapse_empty, mthp_khugepaged_context, anon_ops);
+		TEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops);
+		TEST(collapse_order_single_window, mthp_khugepaged_context, anon_ops);
+		TEST(collapse_order_partial_window, mthp_khugepaged_context, anon_ops);
+		TEST(collapse_order_max_ptes_none, mthp_khugepaged_context, anon_ops);
+		TEST(collapse_order_mixed_sources, mthp_khugepaged_context, anon_ops);
+	}
+
 	TEST(collapse_full, madvise_context, anon_ops);
 	TEST(collapse_full, madvise_context, read_only_file_ops);
 	TEST(collapse_full, madvise_context, read_write_file_read_ops);
@@ -1365,11 +1786,8 @@ int main(int argc, char **argv)
 	TEST(collapse_full, madvise_context, shmem_ops);
 
 	TEST(collapse_empty, khugepaged_context, anon_ops);
-	TEST(collapse_empty, mthp_khugepaged_context, anon_ops);
 	TEST(collapse_empty, madvise_context, anon_ops);
 
-	TEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops);
-
 	TEST(collapse_single_pte_entry, khugepaged_context, anon_ops);
 	TEST(collapse_single_pte_entry, khugepaged_context, read_only_file_ops);
 	TEST(collapse_single_pte_entry, khugepaged_context, read_write_file_read_ops);
@@ -1424,6 +1842,9 @@ int main(int argc, char **argv)
 	TEST(collapse_max_ptes_shared, khugepaged_context, anon_ops);
 	TEST(collapse_max_ptes_shared, madvise_context, anon_ops);
 
+	TEST(collapse_fork_cow_race, khugepaged_context, anon_ops);
+	TEST(collapse_fork_cow_race, madvise_context, anon_ops);
+
 	TEST(madvise_collapse_existing_thps, madvise_context, anon_ops);
 	TEST(madvise_collapse_existing_thps, madvise_context, read_only_file_ops);
 	TEST(madvise_collapse_existing_thps, madvise_context, read_write_file_read_ops);
@@ -1439,7 +1860,15 @@ int main(int argc, char **argv)
 	for (int i = 0; i < nr_test_cases; i++) {
 		struct test_case *t = &test_cases[i];
 
-		ksft_print_msg("\n# Run test: %s (%s:%s)\n", t->desc, t->ctx->name, t->ops->name);
+		if (t->ctx == &__mthp_khugepaged_context) {
+			collapse_order = t->order;
+			ksft_print_msg("\n# Run test: %s (%s:%s, order %d)\n",
+				       t->desc, t->ctx->name, t->ops->name,
+				       t->order);
+		} else {
+			ksft_print_msg("\n# Run test: %s (%s:%s)\n", t->desc,
+				       t->ctx->name, t->ops->name);
+		}
 		t->fn(t->ctx, t->ops);
 	}
 
diff --git a/tools/testing/selftests/mm/khugepaged_race.c b/tools/testing/selftests/mm/khugepaged_race.c
new file mode 100644
index 0000000000000..c3478b5123e23
--- /dev/null
+++ b/tools/testing/selftests/mm/khugepaged_race.c
@@ -0,0 +1,533 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Race collapse against faults, GUP pins, fork, mremap and MADV_DONTNEED
+ * over the same ranges.  A racing page must read as its pattern or as
+ * zero, never anything else; the kernel's own assertions in dmesg are the
+ * other half of the check.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "kselftest.h"
+#include "vm_util.h"
+#include "hugepage_settings.h"
+#include "../../../../mm/gup_test.h"
+
+#ifndef FOLL_WRITE
+#define FOLL_WRITE 0x01
+#endif
+
+#define BASE_ADDR	((void *)(1UL << 30))
+#define PASS_TIMEOUT_S	30
+
+/*
+ * PMD-sized areas the racing threads share, plus one for the mremap
+ * thread.  -a shrinks it where a PMD is 512M.
+ */
+#define DEFAULT_SHARED_AREAS	3
+static int nr_shared_areas;
+static int nr_areas;
+
+static unsigned long hpage_pmd_size;
+static unsigned long page_size;
+/* nr_areas PMD-sized areas; the last one belongs to the mremap thread */
+static char *region;
+static char *mremap_area;
+static char *mremap_scratch;
+static char *pageout_area;
+static size_t pageout_size;
+static int gup_fd = -1;
+static volatile int stop;
+static volatile int corrupted;
+
+static unsigned int pattern(unsigned long page_idx)
+{
+	unsigned int val = (unsigned int)page_idx * 2654435761U;
+
+	return val ? val : 1;	/* never collides with the zero-fill */
+}
+
+/* Zero means never written; anything else must be this page's pattern */
+static bool page_is_corrupt(unsigned long page_idx, unsigned int *val)
+{
+	*val = *(unsigned int *)(region + page_idx * page_size);
+
+	return *val && *val != pattern(page_idx);
+}
+
+static void check_page(unsigned long page_idx)
+{
+	unsigned int val;
+
+	if (page_is_corrupt(page_idx, &val)) {
+		corrupted = 1;
+		ksft_print_msg("Corruption at page %lu: %#x != %#x\n",
+			       page_idx, val, pattern(page_idx));
+	}
+}
+
+static unsigned long shared_pages(void)
+{
+	return nr_shared_areas * hpage_pmd_size / page_size;
+}
+
+static unsigned long rand_page(unsigned int *seed)
+{
+	return (unsigned long)rand_r(seed) % shared_pages();
+}
+
+/* Clamp so a range never reaches the mremap thread's area */
+static unsigned long room_from(unsigned long page_idx, unsigned long want)
+{
+	unsigned long left = shared_pages() - page_idx;
+
+	return want < left ? want : left;
+}
+
+static void *faulter_fn(void *arg)
+{
+	unsigned int seed = (unsigned long)arg;
+
+	while (!stop) {
+		unsigned long page_idx = rand_page(&seed);
+
+		if (rand_r(&seed) & 1)
+			*(unsigned int *)(region + page_idx * page_size) =
+				pattern(page_idx);
+		else
+			check_page(page_idx);
+	}
+	return NULL;
+}
+
+static void *dontneed_fn(void *arg)
+{
+	unsigned int seed = (unsigned long)arg;
+
+	while (!stop) {
+		unsigned long page_idx = rand_page(&seed);
+		unsigned long nr = 1UL << (rand_r(&seed) % 6);	/* 1..32 pages */
+
+		/*
+		 * Now and then zap a whole PMD-aligned area: only a zap that
+		 * covers the full table frees the table itself (CONFIG_PT_RECLAIM).
+		 */
+		if (!(rand_r(&seed) % 64)) {
+			unsigned long area = page_idx /
+					(hpage_pmd_size / page_size);
+
+			madvise(region + area * hpage_pmd_size,
+				hpage_pmd_size, MADV_DONTNEED);
+		} else {
+			madvise(region + page_idx * page_size,
+				room_from(page_idx, nr) * page_size,
+				MADV_DONTNEED);
+		}
+		usleep(rand_r(&seed) % 500);
+	}
+	return NULL;
+}
+
+static void *pinner_fn(void *arg)
+{
+	unsigned int seed = (unsigned long)arg;
+
+	while (!stop) {
+		struct gup_test gup = {};
+		unsigned long page_idx = rand_page(&seed);
+		unsigned long nr = room_from(page_idx, 16);
+
+		gup.addr = (unsigned long)(region + page_idx * page_size);
+		gup.size = nr * page_size;
+		gup.nr_pages_per_call = nr;
+		gup.gup_flags = FOLL_WRITE;
+		/* Racing MADV_DONTNEED makes transient failures expected */
+		ioctl(gup_fd, PIN_FAST_BENCHMARK, &gup);
+		usleep(rand_r(&seed) % 200);
+	}
+	return NULL;
+}
+
+static void *forker_fn(void *arg)
+{
+	unsigned int seed = (unsigned long)arg;
+
+	while (!stop) {
+		pid_t pid = fork();
+
+		if (pid == 0) {
+			unsigned int val;
+			int bad = 0;
+
+			/*
+			 * No stdio in the child: a thread may hold stdout's
+			 * lock across the fork, and printing under it hangs.
+			 */
+			for (int i = 0; i < 16; i++)
+				bad |= page_is_corrupt(rand_page(&seed), &val);
+			_exit(bad);
+		}
+		if (pid > 0) {
+			int wstatus;
+
+			if (waitpid(pid, &wstatus, 0) < 0)
+				ksft_exit_fail_perror("waitpid()");
+			/* A child killed on the read counts too */
+			if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus))
+				corrupted = 1;
+		}
+		usleep(rand_r(&seed) % 2000);
+	}
+	return NULL;
+}
+
+static void *mremapper_fn(void *arg)
+{
+	unsigned int seed = (unsigned long)arg;
+
+	while (!stop) {
+		void *p;
+
+		p = mremap(mremap_area, hpage_pmd_size, hpage_pmd_size,
+			   MREMAP_MAYMOVE | MREMAP_FIXED, mremap_scratch);
+		if (p == MAP_FAILED)
+			ksft_exit_fail_perror("mremap() away");
+		for (int i = 0; i < 8; i++)
+			mremap_scratch[(rand_r(&seed) %
+				(hpage_pmd_size / page_size)) * page_size] = 1;
+		p = mremap(mremap_scratch, hpage_pmd_size, hpage_pmd_size,
+			   MREMAP_MAYMOVE | MREMAP_FIXED, mremap_area);
+		if (p == MAP_FAILED)
+			ksft_exit_fail_perror("mremap() back");
+		usleep(rand_r(&seed) % 2000);
+	}
+	return NULL;
+}
+
+/*
+ * Swap traffic and LRU churn on a region nothing else writes, so a page's
+ * pattern must survive the trip through swap exactly.
+ */
+static void *pageout_fn(void *arg)
+{
+	unsigned int seed = (unsigned long)arg;
+	unsigned long nr = pageout_size / page_size;
+	unsigned long i;
+
+	for (i = 0; i < nr; i++)
+		*(unsigned int *)(pageout_area + i * page_size) = pattern(i);
+
+	while (!stop) {
+		madvise(pageout_area, pageout_size, MADV_PAGEOUT);
+		for (i = 0; i < nr && !stop; i++) {
+			unsigned int val = *(unsigned int *)(pageout_area +
+							     i * page_size);
+
+			if (val != pattern(i)) {
+				corrupted = 1;
+				ksft_print_msg("Pageout corruption at page %lu: %#x != %#x\n",
+					       i, val, pattern(i));
+			}
+		}
+		usleep(rand_r(&seed) % 2000);
+	}
+	return NULL;
+}
+
+/* Compaction migrates the collapse sources while they are being gathered */
+static void *compactor_fn(void *arg)
+{
+	unsigned int seed = (unsigned long)arg;
+	int fd = open("/proc/sys/vm/compact_memory", O_WRONLY);
+
+	if (fd < 0) {
+		ksft_print_msg("No compact_memory; compactor idle\n");
+		return NULL;
+	}
+	while (!stop) {
+		if (write(fd, "1", 1) < 0)
+			break;
+		usleep(10000 + rand_r(&seed) % 100000);
+	}
+	close(fd);
+	return NULL;
+}
+
+static bool swap_available(void)
+{
+	char line[256];
+	int lines = 0;
+	FILE *fp = fopen("/proc/swaps", "r");
+
+	if (!fp)
+		return false;
+	while (fgets(line, sizeof(line), fp))
+		lines++;
+	fclose(fp);
+	return lines > 1;
+}
+
+static unsigned long now_ms(void)
+{
+	struct timeval tv;
+
+	gettimeofday(&tv, NULL);
+	return tv.tv_sec * 1000UL + tv.tv_usec / 1000;
+}
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: khugepaged_race [-d seconds] [-m stepped|free|madvise] [-a areas] [-t mask]\n"
+		"\tWithout -m, every mode runs in turn.\n"
+		"\t-d: seconds per mode (default 5)\n"
+		"\t-a: number of shared PMD-sized playground areas (default 3)\n"
+		"\t-t: bitmask of racing threads to start, for bisecting a failure\n");
+	exit(1);
+}
+
+int main(int argc, char **argv)
+{
+	static const char * const thread_names[] = {
+		"faulter", "faulter2", "dontneed", "pinner", "forker",
+		"mremapper", "pageout", "compactor",
+	};
+	void *(*const thread_fns[])(void *) = {
+		faulter_fn, faulter_fn, dontneed_fn, pinner_fn, forker_fn,
+		mremapper_fn, pageout_fn, compactor_fn,
+	};
+	enum { T_FAULTER, T_FAULTER2, T_DONTNEED, T_PINNER, T_FORKER,
+	       T_MREMAPPER, T_PAGEOUT, T_COMPACTOR };
+	const unsigned long pageout_bit = 1UL << T_PAGEOUT;
+	const unsigned long compactor_bit = 1UL << T_COMPACTOR;
+	const int nr_threads = ARRAY_SIZE(thread_names);
+	pthread_t threads[ARRAY_SIZE(thread_names)];
+	static const char * const all_modes[] = { "stepped", "free", "madvise" };
+	static const bool occupancies[] = { false, true };	/* strict, holes */
+	static const bool pressures[] = { false, true };	/* quiet, under pressure */
+	const int nr_occupancies = ARRAY_SIZE(occupancies);
+	const int nr_pressures = ARRAY_SIZE(pressures);
+	const char *one_mode[1];
+	const char * const *modes = all_modes;
+	int nr_modes = ARRAY_SIZE(all_modes);
+	const char *mode_arg = NULL;
+	struct thp_settings settings;
+	unsigned long end_ms;
+	int duration_s = 5;
+	unsigned long thread_mask = ~0UL;
+	unsigned long base_mask;
+	bool have_swap;
+	char label[64];
+	int nr_areas_arg = 0;
+	unsigned long i;
+	int steps = 0;
+	int opt;
+
+	while ((opt = getopt(argc, argv, "a:d:m:t:h")) != -1) {
+		switch (opt) {
+		case 'a':
+			nr_areas_arg = atoi(optarg);
+			break;
+		case 'd':
+			duration_s = atoi(optarg);
+			break;
+		case 'm':
+			mode_arg = optarg;
+			break;
+		case 't':
+			thread_mask = strtoul(optarg, NULL, 0);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	if (mode_arg) {
+		if (strcmp(mode_arg, "stepped") && strcmp(mode_arg, "free") &&
+		    strcmp(mode_arg, "madvise"))
+			usage();
+		one_mode[0] = mode_arg;
+		modes = one_mode;
+		nr_modes = 1;
+	}
+
+	ksft_print_header();
+	if (!thp_available())
+		ksft_exit_skip("Transparent Hugepages not available\n");
+
+	page_size = getpagesize();
+	hpage_pmd_size = read_pmd_pagesize();
+	if (!hpage_pmd_size)
+		ksft_exit_fail_msg("Reading PMD pagesize failed\n");
+
+	gup_fd = open("/sys/kernel/debug/gup_test", O_RDWR);
+	if (gup_fd < 0)
+		ksft_exit_skip("/sys/kernel/debug/gup_test requires CONFIG_GUP_TEST and root\n");
+
+	nr_shared_areas = nr_areas_arg > 0 ? nr_areas_arg : DEFAULT_SHARED_AREAS;
+	nr_areas = nr_shared_areas + 1;
+
+	/*
+	 * MREMAP_FIXED unmaps whatever is in the way without saying so, so
+	 * claim the mremap thread's scratch address up front.
+	 */
+	mremap_scratch = (char *)BASE_ADDR + 2 * nr_areas * hpage_pmd_size;
+	if (mmap(mremap_scratch, hpage_pmd_size, PROT_NONE,
+		 MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE,
+		 -1, 0) != (void *)mremap_scratch)
+		ksft_exit_fail_perror("mmap() mremap scratch");
+
+	base_mask = thread_mask;
+	have_swap = swap_available();
+	if (!have_swap)
+		/* No swap, no anon reclaim: compaction-only pressure */
+		ksft_print_msg("no swap: the pageout thread is not started\n");
+
+	ksft_set_plan(nr_modes * nr_occupancies * nr_pressures);
+
+	thp_save_settings();
+	thp_read_settings(&settings);
+
+	/* Base of the settings stack; the bottom entry is never popped */
+	thp_push_settings(&settings);
+
+	for (int run = 0; run < nr_modes * nr_occupancies * nr_pressures; run++) {
+		int rem = run % (nr_occupancies * nr_pressures);
+		const char *mode = modes[run / (nr_occupancies * nr_pressures)];
+		bool holes = occupancies[rem / nr_pressures];
+		bool pressure = pressures[rem % nr_pressures];
+
+		snprintf(label, sizeof(label), "%s/%s%s", mode,
+			 holes ? "holes" : "strict", pressure ? "/pressure" : "");
+		if (corrupted) {
+			/* Memory is suspect; the rest would prove nothing */
+			ksft_test_result_skip("%s: skipped after corruption\n", label);
+			continue;
+		}
+
+		thread_mask = base_mask;
+		if (!pressure)
+			thread_mask &= ~(pageout_bit | compactor_bit);
+		else if (!have_swap)
+			thread_mask &= ~pageout_bit;
+
+		thp_read_settings(&settings);
+		settings.thp_enabled = THP_MADVISE;
+		settings.thp_defrag = THP_DEFRAG_ALWAYS;
+		settings.shmem_enabled = SHMEM_NEVER;
+		settings.khugepaged.defrag = 1;
+		settings.khugepaged.scan_sleep_millisecs =
+			strcmp(mode, "free") ? 1000 : 0;
+		settings.khugepaged.alloc_sleep_millisecs = 10;
+
+		/*
+		 * mTHP collapse honours only 0 or HPAGE_PMD_NR - 1 here.  The two
+		 * ends race different paths: a strict window has every PTE
+		 * present, a hole-heavy one is mostly zero-filled.
+		 */
+		settings.khugepaged.max_ptes_none = holes ?
+			(hpage_pmd_size / page_size) - 1 : 0;
+		/* One wake, one pass: the playground plus the forked children's copies */
+		settings.khugepaged.pages_to_scan =
+			nr_areas * (hpage_pmd_size / page_size) * 8;
+		for (i = 0; i < NR_ORDERS; i++) {
+			if (thp_supported_orders() & (1UL << i))
+				settings.hugepages[i].enabled = THP_INHERIT;
+		}
+		thp_push_settings(&settings);
+
+		region = mmap(BASE_ADDR, nr_areas * hpage_pmd_size,
+			      PROT_READ | PROT_WRITE, MAP_ANONYMOUS |
+			      MAP_PRIVATE | MAP_FIXED_NOREPLACE, -1, 0);
+		if (region != BASE_ADDR)
+			ksft_exit_fail_perror("mmap() playground");
+		mremap_area = region + nr_shared_areas * hpage_pmd_size;
+
+		if (thread_mask & pageout_bit) {
+			/* Enough to drive real reclaim without swamping a small guest */
+			pageout_size = 4 * hpage_pmd_size;
+			if (pageout_size < 16UL << 20)
+				pageout_size = 16UL << 20;
+			if (pageout_size > 64UL << 20)
+				pageout_size = 64UL << 20;
+			pageout_area = mmap(NULL, pageout_size,
+					    PROT_READ | PROT_WRITE,
+					    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+			if (pageout_area == MAP_FAILED)
+				ksft_exit_fail_perror("mmap() pageout area");
+		}
+
+		/* Populate so the first pass has something to collapse */
+		for (i = 0; i < nr_shared_areas * hpage_pmd_size / page_size; i++)
+			*(unsigned int *)(region + i * page_size) = pattern(i);
+		memset(mremap_area, 1, hpage_pmd_size);
+		if (madvise(region, nr_areas * hpage_pmd_size, MADV_HUGEPAGE))
+			ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)");
+
+		for (i = 0; i < nr_threads; i++) {
+			if (!(thread_mask & (1UL << i))) {
+				threads[i] = 0;
+				continue;
+			}
+			if (pthread_create(&threads[i], NULL, thread_fns[i],
+					   (void *)(i + 1)))
+				ksft_exit_fail_perror(thread_names[i]);
+		}
+
+		end_ms = now_ms() + duration_s * 1000UL;
+		if (!strcmp(mode, "stepped")) {
+			while (now_ms() < end_ms && !corrupted) {
+				if (!khugepaged_full_pass(PASS_TIMEOUT_S))
+					ksft_exit_fail_msg("khugepaged pass timed out\n");
+				steps++;
+			}
+		} else if (!strcmp(mode, "free")) {
+			while (now_ms() < end_ms && !corrupted)
+				usleep(100 * 1000);
+		} else {	/* madvise */
+			while (now_ms() < end_ms && !corrupted) {
+				for (i = 0; i < nr_shared_areas; i++) {
+					madvise(region + i * hpage_pmd_size,
+						hpage_pmd_size, MADV_COLLAPSE);
+				}
+				madvise(region, nr_shared_areas * hpage_pmd_size,
+					MADV_DONTNEED);
+				steps++;
+			}
+		}
+
+		stop = 1;
+		for (i = 0; i < nr_threads; i++) {
+			if (threads[i])
+				pthread_join(threads[i], NULL);
+		}
+
+		for (i = 0; i < nr_shared_areas * hpage_pmd_size / page_size; i++)
+			check_page(i);
+
+		ksft_test_result(!corrupted, "%s: %ds, %d steps, no corruption\n",
+				 label, duration_s, steps);
+
+		/* The next mode maps the same fixed address with its own settings */
+		munmap(region, nr_areas * hpage_pmd_size);
+		if (pageout_area) {
+			munmap(pageout_area, pageout_size);
+			pageout_area = NULL;
+		}
+		thp_pop_settings();
+		stop = 0;
+		steps = 0;
+	}
+
+	ksft_finished();
+}
diff --git a/tools/testing/selftests/mm/khugepaged_sync_check.c b/tools/testing/selftests/mm/khugepaged_sync_check.c
new file mode 100644
index 0000000000000..1c1b942ac3258
--- /dev/null
+++ b/tools/testing/selftests/mm/khugepaged_sync_check.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Check that khugepaged_full_pass() drives khugepaged in step: one barrier
+ * over one prepared window must collapse it with exactly one collapse
+ * attempt attributed to its source pages, step after step.
+ *
+ * scan_sleep_millisecs is a minute so that a step which slept instead of
+ * being woken blows the budget.
+ */
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "kselftest.h"
+#include "vm_util.h"
+#include "hugepage_settings.h"
+
+#define BASE_ADDR ((void *)(1UL << 30))
+/* Smallest order khugepaged considers */
+#define TARGET_ORDER 2
+#define NR_ITERATIONS 5
+#define PASS_TIMEOUT_S 30
+
+static int pagemap_fd;
+static int kpageflags_fd;
+static int trace_events_fd = -1;
+static unsigned long hpage_pmd_size;
+
+/*
+ * The events are system-wide: switch them off however the test ends,
+ * including from inside a helper that gives up.
+ */
+static void trace_events_off(void)
+{
+	if (trace_events_fd >= 0)
+		tracing_events_enable(trace_events_fd, false);
+}
+
+/* Count the isolate events whose scan_pfn is one of the window's source PFNs */
+static int count_attributed(unsigned long *pfns, int nr_pfns,
+			    unsigned int order)
+{
+	char line[1024];
+	int count = 0;
+	FILE *fp;
+
+	fp = tracing_open_trace();
+	if (!fp)
+		ksft_exit_fail_msg("Cannot open trace buffer\n");
+
+	while (fgets(line, sizeof(line), fp)) {
+		unsigned long val;
+		unsigned int ord;
+		char *s, *o;
+		int i;
+
+		s = strstr(line, "mm_collapse_huge_page_isolate:");
+		if (!s)
+			continue;
+		if (sscanf(s, "mm_collapse_huge_page_isolate: scan_pfn=0x%lx",
+			   &val) != 1)
+			continue;
+		o = strstr(s, "order=");
+		if (!o || sscanf(o, "order=%u", &ord) != 1 || ord != order)
+			continue;
+		for (i = 0; i < nr_pfns; i++) {
+			if (val == pfns[i]) {
+				count++;
+				break;
+			}
+		}
+	}
+	fclose(fp);
+	return count;
+}
+
+static void one_step(int iteration)
+{
+	const size_t window = getpagesize() << TARGET_ORDER;
+	const int nr_pages = 1 << TARGET_ORDER;
+	unsigned long pfns[1 << TARGET_ORDER];
+	bool collapsed, passed;
+	int attributed;
+	char *p;
+	int i;
+
+	p = mmap(BASE_ADDR, hpage_pmd_size, PROT_READ | PROT_WRITE,
+		 MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE, -1, 0);
+	if (p != BASE_ADDR)
+		ksft_exit_fail_perror("mmap() window");
+
+	for (i = 0; i < nr_pages; i++) {
+		p[i * getpagesize()] = i + 1;
+		pfns[i] = pagemap_get_pfn(pagemap_fd, p + i * getpagesize());
+		if (pfns[i] == -1UL)
+			ksft_exit_fail_msg("Source page not present\n");
+	}
+
+	/* Clear before enabling so the buffer holds only this step's events */
+	if (tracing_clear_trace())
+		ksft_exit_fail_msg("Cannot clear the trace buffer\n");
+	if (tracing_events_enable(trace_events_fd, true))
+		ksft_exit_fail_msg("Cannot enable huge_memory events\n");
+
+	if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
+		ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)");
+	passed = khugepaged_full_pass(PASS_TIMEOUT_S);
+
+	/* Off before anything that can give up: the events are system-wide */
+	if (tracing_events_enable(trace_events_fd, false))
+		ksft_exit_fail_msg("Cannot disable huge_memory events\n");
+	if (!passed)
+		ksft_exit_fail_msg("khugepaged did not complete a full pass\n");
+
+	collapsed = is_range_backed_by_order(p, window, TARGET_ORDER,
+					     pagemap_fd, kpageflags_fd);
+	attributed = count_attributed(pfns, nr_pages, TARGET_ORDER);
+
+	ksft_test_result(collapsed && attributed == 1,
+			 "step %d: window collapsed, %d attributed result(s)\n",
+			 iteration, attributed);
+
+	munmap(p, hpage_pmd_size);
+}
+
+int main(void)
+{
+	struct thp_settings settings;
+	int i;
+
+	ksft_print_header();
+
+	if (!thp_available())
+		ksft_exit_skip("Transparent Hugepages not available\n");
+	if (!(thp_supported_orders() & (1UL << TARGET_ORDER)))
+		ksft_exit_skip("Order %d is not a supported anon THP order\n",
+			       TARGET_ORDER);
+
+	hpage_pmd_size = read_pmd_pagesize();
+	if (!hpage_pmd_size)
+		ksft_exit_fail_msg("Reading PMD pagesize failed\n");
+	pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+	if (pagemap_fd < 0)
+		ksft_exit_fail_perror("open(/proc/self/pagemap)");
+	kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
+	if (kpageflags_fd < 0)
+		ksft_exit_skip("open(/proc/kpageflags) requires root\n");
+	trace_events_fd = tracing_events_open("huge_memory");
+	if (trace_events_fd < 0)
+		ksft_exit_skip("huge_memory events require tracefs and root\n");
+	atexit(trace_events_off);
+
+	ksft_set_plan(NR_ITERATIONS);
+
+	thp_save_settings();
+	thp_read_settings(&settings);
+	settings.thp_enabled = THP_MADVISE;
+	settings.thp_defrag = THP_DEFRAG_ALWAYS;
+	settings.khugepaged.defrag = 1;
+	settings.khugepaged.scan_sleep_millisecs = 60 * 1000;
+	settings.khugepaged.alloc_sleep_millisecs = 60 * 1000;
+	settings.khugepaged.max_ptes_none = (hpage_pmd_size / getpagesize()) - 1;
+	/* One wake must complete one full pass; see khugepaged_full_pass() */
+	settings.khugepaged.pages_to_scan = 1UL << 24;
+	for (i = 0; i < NR_ORDERS; i++)
+		settings.hugepages[i].enabled = THP_NEVER;
+	settings.hugepages[TARGET_ORDER].enabled = THP_INHERIT;
+	/* Base of the settings stack; the bottom entry is never popped */
+	thp_push_settings(&settings);
+
+	for (i = 0; i < NR_ITERATIONS; i++)
+		one_step(i);
+
+	ksft_finished();
+}
diff --git a/tools/testing/selftests/mm/migration.c b/tools/testing/selftests/mm/migration.c
index f19d53c695764..fd35f8a7b5b83 100644
--- a/tools/testing/selftests/mm/migration.c
+++ b/tools/testing/selftests/mm/migration.c
@@ -20,7 +20,6 @@
 
 #define TWOMEG		(2<<20)
 #define RUNTIME		(20)
-#define ALIGN(x, a)	(((x) + (a - 1)) & (~((a) - 1)))
 
 HUGETLB_SETUP_DEFAULT_PAGES(1)
 
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index d09f9f6a384ee..fc61907aa3b2a 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -402,6 +402,12 @@ CATEGORY="pfnmap" run_test ./pfnmap
 # COW tests
 CATEGORY="cow" run_test ./cow
 
+CATEGORY="thp" run_test ./folio_order_check
+
+CATEGORY="thp" run_test ./khugepaged_sync_check
+
+CATEGORY="thp" run_test ./khugepaged_race
+
 CATEGORY="thp" run_test ./khugepaged
 
 CATEGORY="thp" run_test ./khugepaged -s 2
@@ -410,8 +416,6 @@ CATEGORY="thp" run_test ./khugepaged all:shmem
 
 CATEGORY="thp" run_test ./khugepaged -s 4 all:shmem
 
-CATEGORY="thp" run_test ./khugepaged -c 4 mthp_khugepaged:anon
-
 # Try to create XFS if not provided
 if [ -z "${SPLIT_HUGE_PAGE_TEST_XFS_PATH}" ]; then
     if test_selected "thp"; then
diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 86a6036928261..0adfe7dde7e59 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -42,68 +42,6 @@ const char *kpageflags_proc = "/proc/kpageflags";
 int pagemap_fd;
 int kpageflags_fd;
 
-static bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd,
-		int kpageflags_fd)
-{
-	const uint64_t folio_head_flags = KPF_THP | KPF_COMPOUND_HEAD;
-	const uint64_t folio_tail_flags = KPF_THP | KPF_COMPOUND_TAIL;
-	const unsigned long nr_pages = 1UL << order;
-	unsigned long pfn_head;
-	uint64_t pfn_flags;
-	unsigned long pfn;
-	unsigned long i;
-
-	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
-
-	/* non present page */
-	if (pfn == -1UL)
-		return false;
-
-	if (pageflags_get(pfn, kpageflags_fd, &pfn_flags))
-		goto fail;
-
-	/* check for order-0 pages */
-	if (!order) {
-		if (pfn_flags & (folio_head_flags | folio_tail_flags))
-			return false;
-		return true;
-	}
-
-	/* non THP folio */
-	if (!(pfn_flags & KPF_THP))
-		return false;
-
-	pfn_head = pfn & ~(nr_pages - 1);
-
-	if (pageflags_get(pfn_head, kpageflags_fd, &pfn_flags))
-		goto fail;
-
-	/* head PFN has no compound_head flag set */
-	if ((pfn_flags & folio_head_flags) != folio_head_flags)
-		return false;
-
-	/* check all tail PFN flags */
-	for (i = 1; i < nr_pages; i++) {
-		if (pageflags_get(pfn_head + i, kpageflags_fd, &pfn_flags))
-			goto fail;
-		if ((pfn_flags & folio_tail_flags) != folio_tail_flags)
-			return false;
-	}
-
-	/*
-	 * check the PFN after this folio, but if its flags cannot be obtained,
-	 * assume this folio has the expected order
-	 */
-	if (pageflags_get(pfn_head + nr_pages, kpageflags_fd, &pfn_flags))
-		return true;
-
-	/* If we find another tail page, then the folio is larger. */
-	return (pfn_flags & folio_tail_flags) != folio_tail_flags;
-fail:
-	ksft_exit_fail_msg("Failed to get folio info\n");
-	return false;
-}
-
 static int check_after_split_folio_orders(char *vaddr_start, size_t len,
 		int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders)
 {
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 4821a35630363..af8324e1e8f2c 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -490,6 +490,153 @@ int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags)
 	return 0;
 }
 
+bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd,
+			int kpageflags_fd)
+{
+	const uint64_t folio_head_flags = KPF_THP | KPF_COMPOUND_HEAD;
+	const uint64_t folio_tail_flags = KPF_THP | KPF_COMPOUND_TAIL;
+	const unsigned long nr_pages = 1UL << order;
+	unsigned long pfn_head;
+	uint64_t pfn_flags;
+	unsigned long pfn;
+	unsigned long i;
+
+	pfn = pagemap_get_pfn(pagemap_fd, vaddr);
+
+	/* non present page */
+	if (pfn == -1UL)
+		return false;
+
+	if (pageflags_get(pfn, kpageflags_fd, &pfn_flags))
+		goto fail;
+
+	/* check for order-0 pages */
+	if (!order) {
+		if (pfn_flags & (folio_head_flags | folio_tail_flags))
+			return false;
+		return true;
+	}
+
+	/* non THP folio */
+	if (!(pfn_flags & KPF_THP))
+		return false;
+
+	pfn_head = pfn & ~(nr_pages - 1);
+
+	if (pageflags_get(pfn_head, kpageflags_fd, &pfn_flags))
+		goto fail;
+
+	/* head PFN has no compound_head flag set */
+	if ((pfn_flags & folio_head_flags) != folio_head_flags)
+		return false;
+
+	/* check all tail PFN flags */
+	for (i = 1; i < nr_pages; i++) {
+		if (pageflags_get(pfn_head + i, kpageflags_fd, &pfn_flags))
+			goto fail;
+		if ((pfn_flags & folio_tail_flags) != folio_tail_flags)
+			return false;
+	}
+
+	/*
+	 * check the PFN after this folio, but if its flags cannot be obtained,
+	 * assume this folio has the expected order
+	 */
+	if (pageflags_get(pfn_head + nr_pages, kpageflags_fd, &pfn_flags))
+		return true;
+
+	/* If we find another tail page, then the folio is larger. */
+	return (pfn_flags & folio_tail_flags) != folio_tail_flags;
+fail:
+	ksft_exit_fail_msg("Failed to get folio info\n");
+	return false;
+}
+
+/**
+ * is_range_backed_by_order() - check that a range is backed by @order folios
+ * @start: start of the range, a multiple of the folio size
+ * @len: length of the range in bytes, a multiple of the folio size
+ * @order: the folio order to check for
+ * @pagemap_fd: open /proc/<pid>/pagemap of the range's owner
+ * @kpageflags_fd: open /proc/kpageflags
+ *
+ * Every folio-sized, folio-aligned part of the range must map one folio of
+ * @order, head to tail, with the head at the start of the part.  A part
+ * backed by several smaller folios fails, and so does a folio mapped off
+ * its natural alignment.
+ *
+ * Returns: true if the whole range is backed that way, false otherwise.
+ */
+bool is_range_backed_by_order(char *start, size_t len, int order,
+			      int pagemap_fd, int kpageflags_fd)
+{
+	const unsigned long nr_pages = 1UL << order;
+	const size_t folio_size = nr_pages * psize();
+	char *vaddr;
+
+	if ((uintptr_t)start % folio_size || len % folio_size)
+		return false;
+
+	for (vaddr = start; vaddr < start + len; vaddr += folio_size) {
+		const unsigned long pfn = pagemap_get_pfn(pagemap_fd, vaddr);
+		unsigned long i;
+
+		/* Not present, or a tail page */
+		if (pfn == -1UL || pfn % nr_pages)
+			return false;
+
+		for (i = 1; i < nr_pages; i++) {
+			char *page = vaddr + i * psize();
+
+			if (pagemap_get_pfn(pagemap_fd, page) != pfn + i)
+				return false;
+		}
+
+		if (!is_backed_by_folio(vaddr, order, pagemap_fd, kpageflags_fd))
+			return false;
+	}
+
+	return true;
+}
+
+#define TRACEFS_ROOT "/sys/kernel/tracing"
+
+/*
+ * Returns -1 without tracefs or the subsystem.  The events are system-wide:
+ * whoever switches them on has to switch them off again, on every exit path.
+ */
+int tracing_events_open(const char *subsys)
+{
+	char path[256];
+
+	snprintf(path, sizeof(path), TRACEFS_ROOT "/events/%s/enable",
+		 subsys);
+	return open(path, O_WRONLY);
+}
+
+int tracing_events_enable(int fd, bool enable)
+{
+	if (pwrite(fd, enable ? "1" : "0", 1, 0) != 1)
+		return -1;
+	return 0;
+}
+
+/* Drop what the trace buffer holds so far */
+int tracing_clear_trace(void)
+{
+	int fd = open(TRACEFS_ROOT "/trace", O_WRONLY | O_TRUNC);
+
+	if (fd < 0)
+		return -1;
+	close(fd);
+	return 0;
+}
+
+FILE *tracing_open_trace(void)
+{
+	return fopen(TRACEFS_ROOT "/trace", "r");
+}
+
 /* If `ioctls' non-NULL, the allowed ioctls will be returned into the var */
 int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len,
 			      bool miss, bool wp, bool minor, uint64_t *ioctls)
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index 9a49af88702e4..5a91b9676ec5d 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -10,6 +10,8 @@
 #include <linux/fs.h>
 
 #define BIT_ULL(nr)                   (1ULL << (nr))
+#define ALIGN(x, a)                   (((x) + (a) - 1) & ~((a) - 1))
+
 #define PM_SOFT_DIRTY                 BIT_ULL(55)
 #define PM_MMAP_EXCLUSIVE             BIT_ULL(56)
 #define PM_UFFD_WP                    BIT_ULL(57)
@@ -97,6 +99,10 @@ int64_t allocate_transhuge(void *ptr, int pagemap_fd);
 int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags);
 int gather_folio_orders(char *vaddr_start, size_t len,
 		int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders);
+bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd,
+			int kpageflags_fd);
+bool is_range_backed_by_order(char *start, size_t len, int order,
+			      int pagemap_fd, int kpageflags_fd);
 
 int uffd_register(int uffd, void *addr, uint64_t len,
 		  bool miss, bool wp, bool minor);
@@ -113,6 +119,10 @@ int close_procmap(struct procmap_fd *procmap);
 int write_sysfs(const char *file_path, unsigned long val);
 int read_sysfs(const char *file_path, unsigned long *val);
 bool softdirty_supported(void);
+int tracing_events_open(const char *subsys);
+int tracing_events_enable(int fd, bool enable);
+int tracing_clear_trace(void);
+FILE *tracing_open_trace(void);
 
 static inline int open_self_procmap(struct procmap_fd *procmap_out)
 {

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

Args:
null

Results:
null

Tokens:
							input: 27904
							output: 90
							thoughts: 1040
						

Thoughts:
**Analysis of the Patch for Fuzzing Eligibility**

Okay, let's break this down. First, I need to look at the diff and quickly identify which files have been modified. It's all under `tools/testing/selftests/mm/`.  Right, that means these are self-tests related to memory management. Given my expertise, I already know that this section contains tests for transparent hugepages (THP/khugepaged).

Now, the critical question is whether this warrants fuzzing. I have to apply that negative criteria check. Specifically, the rule I need to look at is: *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.*

Bingo. Every single file listed—`Makefile`, the `.c` files, and the `.h` files—resides precisely within that `tools/testing/selftests/mm/` directory. These are user-space tests, and the modified files will *not* affect the kernel image or modules. I've double-checked the diff and this is the entire set of changes.

Therefore, according to the "Negative Criteria" outlined, the `WorthFuzzing` parameter is `false`.

Given this, I have to formulate the `set-results` call:

*   `WorthFuzzing`: `false`
*   `Reasoning`: "Modified files are located in `tools/testing/selftests/mm/`, which consists of user-space tests and helper functions that do not affect the compiled kernel image or kernel modules."
*   `FocusSymbols`: \[ ]
*   `EnableConfigs`: \[ ]

I'm ready to call `set-results`. This is a straightforward case.



4/2 2026/09/08 15:15 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "All modifications in this patch series are strictly confined to user-space selftests under tools/testing/selftests/mm/. No kernel image (vmlinux) or module code is altered, making the changes non-kernel/non-executable from the perspective of kernel fuzzing.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "All modifications in this patch series are strictly confined to user-space selftests under tools/testing/selftests/mm/. No kernel image (vmlinux) or module code is altered, making the changes non-kernel/non-executable from the perspective of kernel fuzzing.",
  "WorthFuzzing": false
}

5/1 2026/09/08 15:15 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

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