From: "Kiryl Shutsemau (Meta)" <[email protected]>

The upcoming khugepaged mTHP tests detect collapse results with the
vm_util folio-order helpers instead of smaps AnonHugePages, which only
sees PMD mappings. Before any collapse test trusts those helpers, make
sure they agree with the kernel about what backs a mapping.

For every anon THP order the kernel supports, fault memory in with
only that order enabled and require the helpers to classify the
backing as exactly that order: not a neighbouring order, and 4K-backed
memory as order 0.

Runs in the thp category of run_vmtests.sh. Verified on x86-64 4K
(orders 0, 2-9) and arm64 64K (orders 0, 2-13).

Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]>
---
 tools/testing/selftests/mm/.gitignore         |   1 +
 tools/testing/selftests/mm/Makefile           |   1 +
 .../testing/selftests/mm/folio_order_check.c  | 138 ++++++++++++++++++
 tools/testing/selftests/mm/run_vmtests.sh     |   2 +
 4 files changed, 142 insertions(+)
 create mode 100644 tools/testing/selftests/mm/folio_order_check.c

diff --git a/tools/testing/selftests/mm/.gitignore 
b/tools/testing/selftests/mm/.gitignore
index 9ccd9e1447e6..54eefd8f97bc 100644
--- a/tools/testing/selftests/mm/.gitignore
+++ b/tools/testing/selftests/mm/.gitignore
@@ -66,3 +66,4 @@ merge
 prctl_thp_disable
 rmap
 folio_split_race_test
+folio_order_check
diff --git a/tools/testing/selftests/mm/Makefile 
b/tools/testing/selftests/mm/Makefile
index 25d10ced3b3b..33917c76b871 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -104,6 +104,7 @@ 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
 
 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 000000000000..f70d766ff3eb
--- /dev/null
+++ b/tools/testing/selftests/mm/folio_order_check.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Self-check for the vm_util folio-order detection helpers,
+ * is_backed_by_folio() and is_range_backed_by_folio_orders().
+ *
+ * For every anon THP order the kernel supports, fault memory in with only
+ * that order enabled and verify the helpers report exactly that order:
+ * not a neighbouring order, and plain 4K memory as order 0. The helpers
+ * are what the khugepaged mTHP tests use to detect collapse results, so
+ * they must agree with the kernel's own idea of the backing before any
+ * collapse test relies on them.
+ */
+#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;
+
+/* mmap an anon VMA of exactly @size bytes at a @size-aligned address. */
+static char *alloc_aligned(size_t size)
+{
+       size_t len = size * 2;
+       uintptr_t aligned;
+       char *p;
+
+       p = mmap(NULL, len, PROT_READ | PROT_WRITE,
+                MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+       if (p == MAP_FAILED)
+               ksft_exit_fail_perror("mmap()");
+
+       aligned = ((uintptr_t)p + size - 1) & ~(size - 1);
+       if (aligned != (uintptr_t)p)
+               munmap(p, aligned - (uintptr_t)p);
+       if (aligned + size != (uintptr_t)p + len)
+               munmap((char *)aligned + size,
+                      (uintptr_t)p + len - aligned - size);
+
+       return (char *)aligned;
+}
+
+/*
+ * Enable only @order (order 0: nothing), fault one aligned window in and
+ * check the helpers see exactly @order.
+ */
+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_folio_orders(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_folio_orders(p, size, order - 1,
+                                                    pagemap_fd,
+                                                    kpageflags_fd)) {
+               ksft_print_msg("order %d also reported as order %d\n",
+                              order, order - 1);
+               ok = false;
+       }
+
+       /* Order 0 pages must not look like any large folio, and vice versa. */
+       if (order && is_range_backed_by_folio_orders(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);
+       }
+
+       thp_restore_settings();
+
+       ksft_finished();
+}
diff --git a/tools/testing/selftests/mm/run_vmtests.sh 
b/tools/testing/selftests/mm/run_vmtests.sh
index 687d115e3bd8..8bf898b71350 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -402,6 +402,8 @@ CATEGORY="pfnmap" run_test ./pfnmap
 # COW tests
 CATEGORY="cow" run_test ./cow
 
+CATEGORY="thp" run_test ./folio_order_check
+
 CATEGORY="thp" run_test ./khugepaged
 
 CATEGORY="thp" run_test ./khugepaged -s 2
-- 
2.54.0


Reply via email to