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).

While here: ALIGN() moves into vm_util.h, where the next test that needs a
round-up can find it, and hmm-tests.c drops its own copy.

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

diff --git a/tools/testing/selftests/mm/Makefile 
b/tools/testing/selftests/mm/Makefile
index 2d5366196e30..2093fcf6e915 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..93030a42c3cc
--- /dev/null
+++ b/tools/testing/selftests/mm/folio_order_check.c
@@ -0,0 +1,137 @@
+// 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 = ALIGN((uintptr_t)p, size);
+       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);
+       }
+
+
+       ksft_finished();
+}
diff --git a/tools/testing/selftests/mm/hmm-tests.c 
b/tools/testing/selftests/mm/hmm-tests.c
index e2642eca0d02..df426f9218e7 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/run_vmtests.sh 
b/tools/testing/selftests/mm/run_vmtests.sh
index d09f9f6a384e..2652a7920b80 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
diff --git a/tools/testing/selftests/mm/vm_util.h 
b/tools/testing/selftests/mm/vm_util.h
index 39dfb18dc10c..ce05bce4670d 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)
-- 
2.54.0


Reply via email to