AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/16 20:29 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space tools (tools/mm/gup_bench.c, tools/testing/selftests/mm/), Documentation, and MAINTAINERS. It does not make any functional changes to the core kernel logic, drivers, or architectures.",
  "WorthFuzzing": false
}

1/1 2026/07/16 20:29 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit b0a23e9979eedf4a6fb58ea1b376ca527572c4fc\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Jul 16 20:29:07 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/core-api/pin_user_pages.rst b/Documentation/core-api/pin_user_pages.rst\nindex c16ca163b55e3c..ea722adf22ccac 100644\n--- a/Documentation/core-api/pin_user_pages.rst\n+++ b/Documentation/core-api/pin_user_pages.rst\n@@ -230,10 +230,16 @@ This file::\n \n  tools/testing/selftests/mm/gup_test.c\n \n-has the following new calls to exercise the new pin*() wrapper functions:\n+contains the following test cases to exercise pin_user_pages*():\n \n-* PIN_FAST_BENCHMARK (./gup_test -a)\n-* PIN_BASIC_TEST (./gup_test -b)\n+* pin_user_pages via PIN_BASIC_TEST\n+* pin_user_pages_fast via PIN_FAST_BENCHMARK\n+* pin_user_pages_longterm via PIN_LONGTERM_BENCHMARK\n+\n+Run with::\n+\n+  make -C tools/testing/selftests/mm\n+  ./tools/testing/selftests/mm/gup_test\n \n You can monitor how many total dma-pinned pages have been acquired and released\n since the system was booted, via two new /proc/vmstat entries: ::\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 806bd2d80d1535..7d121dee9f0f2c 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -17024,6 +17024,7 @@ T:\tgit git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm\n F:\tmm/gup.c\n F:\tmm/gup_test.c\n F:\tmm/gup_test.h\n+F:\ttools/mm/gup_bench.c\n F:\ttools/testing/selftests/mm/gup_longterm.c\n F:\ttools/testing/selftests/mm/gup_test.c\n \n@@ -17128,6 +17129,7 @@ F:\tmm/memory-tiers.c\n F:\tmm/page_idle.c\n F:\tmm/pgalloc-track.h\n F:\tmm/process_vm_access.c\n+F:\ttools/lib/mm/\n F:\ttools/testing/selftests/mm/\n \n MEMORY MANAGEMENT - NUMA MEMBLOCKS AND NUMA EMULATION\ndiff --git a/tools/lib/mm/file_utils.c b/tools/lib/mm/file_utils.c\nnew file mode 100644\nindex 00000000000000..792f4b4db6252e\n--- /dev/null\n+++ b/tools/lib/mm/file_utils.c\n@@ -0,0 +1,119 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"file_utils.h\"\n+\n+int read_file(const char *path, char *buf, size_t buflen)\n+{\n+\tint fd;\n+\tssize_t numread;\n+\n+\tfd = open(path, O_RDONLY);\n+\tif (fd == -1) {\n+\t\tint err = errno;\n+\n+\t\tprintf(\"# %s: %s (%d)\\n\", path, strerror(err), err);\n+\t\treturn -err;\n+\t}\n+\n+\tnumread = read(fd, buf, buflen - 1);\n+\tif (numread \u003c 1) {\n+\t\tint err = numread ? errno : ENODATA;\n+\n+\t\tclose(fd);\n+\t\tprintf(\"# %s: %s (%d)\\n\", path, strerror(err), err);\n+\t\treturn -err;\n+\t}\n+\n+\tbuf[numread] = '\\0';\n+\tclose(fd);\n+\n+\treturn (int)numread;\n+}\n+\n+int write_file(const char *path, const char *buf, size_t buflen)\n+{\n+\tint fd, saved_errno;\n+\tssize_t numwritten;\n+\n+\tif (buflen \u003c 2) {\n+\t\tprintf(\"# %s: %s (%d)\\n\", path, strerror(EINVAL), EINVAL);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tfd = open(path, O_WRONLY);\n+\tif (fd == -1) {\n+\t\tint err = errno;\n+\n+\t\tprintf(\"# %s: %s (%d)\\n\", path, strerror(err), err);\n+\t\treturn -err;\n+\t}\n+\n+\tnumwritten = write(fd, buf, buflen - 1);\n+\tsaved_errno = errno;\n+\tclose(fd);\n+\tif (numwritten \u003c 0) {\n+\t\tprintf(\"# %s: %s (%d)\\n\", path, strerror(saved_errno),\n+\t\t       saved_errno);\n+\t\treturn -saved_errno;\n+\t}\n+\tif (numwritten != (ssize_t)(buflen - 1)) {\n+\t\tprintf(\"# %s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\\n\",\n+\t\t       path, (int)(buflen - 1), buf, buflen - 1, numwritten);\n+\t\treturn -EIO;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int read_num(const char *path, unsigned long *num)\n+{\n+\tunsigned long val;\n+\tint err, ret;\n+\tchar buf[21];\n+\tchar *end;\n+\n+\tif (!num)\n+\t\treturn -EINVAL;\n+\n+\tret = read_file(path, buf, sizeof(buf));\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\terrno = 0;\n+\tval = strtoul(buf, \u0026end, 10);\n+\tif (errno) {\n+\t\terr = errno;\n+\t\tprintf(\"# %s: %s (%d)\\n\", path, strerror(err), err);\n+\t\treturn -err;\n+\t}\n+\n+\tif (end == buf || buf[0] == '-') {\n+\t\tprintf(\"# %s: invalid numeric value\\n\", path);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (*end == '\\n')\n+\t\tend++;\n+\n+\tif (*end != '\\0') {\n+\t\tprintf(\"# %s: invalid numeric value\\n\", path);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t*num = val;\n+\treturn 0;\n+}\n+\n+int write_num(const char *path, unsigned long num)\n+{\n+\tchar buf[21];\n+\n+\tsnprintf(buf, sizeof(buf), \"%lu\", num);\n+\treturn write_file(path, buf, strlen(buf) + 1);\n+}\ndiff --git a/tools/lib/mm/file_utils.h b/tools/lib/mm/file_utils.h\nnew file mode 100644\nindex 00000000000000..38576790a34244\n--- /dev/null\n+++ b/tools/lib/mm/file_utils.h\n@@ -0,0 +1,12 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#ifndef __MM_FILE_UTILS_H__\n+#define __MM_FILE_UTILS_H__\n+\n+#include \u003cstddef.h\u003e\n+\n+int read_file(const char *path, char *buf, size_t buflen);\n+int write_file(const char *path, const char *buf, size_t buflen);\n+int read_num(const char *path, unsigned long *num);\n+int write_num(const char *path, unsigned long num);\n+\n+#endif\ndiff --git a/tools/testing/selftests/mm/hugepage_settings.c b/tools/lib/mm/hugepage_settings.c\nsimilarity index 91%\nrename from tools/testing/selftests/mm/hugepage_settings.c\nrename to tools/lib/mm/hugepage_settings.c\nindex 2eab2110ac6a46..8d9e44727edbf5 100644\n--- a/tools/testing/selftests/mm/hugepage_settings.c\n+++ b/tools/lib/mm/hugepage_settings.c\n@@ -9,11 +9,16 @@\n #include \u003cstring.h\u003e\n #include \u003cunistd.h\u003e\n \n-#include \"vm_util.h\"\n+#include \"file_utils.h\"\n #include \"hugepage_settings.h\"\n \n #define THP_SYSFS \"/sys/kernel/mm/transparent_hugepage/\"\n #define MAX_SETTINGS_DEPTH 4\n+\n+#ifndef ARRAY_SIZE\n+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))\n+#endif\n+\n static struct thp_settings settings_stack[MAX_SETTINGS_DEPTH];\n static int settings_index;\n static struct thp_settings saved_settings;\n@@ -61,10 +66,9 @@ int thp_read_string(const char *name, const char * const strings[])\n \t\texit(EXIT_FAILURE);\n \t}\n \n-\tif (!read_file(path, buf, sizeof(buf))) {\n-\t\tperror(path);\n+\tret = read_file(path, buf, sizeof(buf));\n+\tif (ret \u003c 0)\n \t\texit(EXIT_FAILURE);\n-\t}\n \n \tc = strchr(buf, '[');\n \tif (!c) {\n@@ -103,12 +107,15 @@ void thp_write_string(const char *name, const char *val)\n \t\tprintf(\"%s: Pathname is too long\\n\", __func__);\n \t\texit(EXIT_FAILURE);\n \t}\n-\twrite_file(path, val, strlen(val) + 1);\n+\tret = write_file(path, val, strlen(val) + 1);\n+\tif (ret \u003c 0)\n+\t\texit(EXIT_FAILURE);\n }\n \n unsigned long thp_read_num(const char *name)\n {\n \tchar path[PATH_MAX];\n+\tunsigned long num;\n \tint ret;\n \n \tret = snprintf(path, PATH_MAX, THP_SYSFS \"%s\", name);\n@@ -116,7 +123,11 @@ unsigned long thp_read_num(const char *name)\n \t\tprintf(\"%s: Pathname is too long\\n\", __func__);\n \t\texit(EXIT_FAILURE);\n \t}\n-\treturn read_num(path);\n+\tret = read_num(path, \u0026num);\n+\tif (ret \u003c 0)\n+\t\texit(EXIT_FAILURE);\n+\n+\treturn num;\n }\n \n void thp_write_num(const char *name, unsigned long num)\n@@ -129,7 +140,9 @@ void thp_write_num(const char *name, unsigned long num)\n \t\tprintf(\"%s: Pathname is too long\\n\", __func__);\n \t\texit(EXIT_FAILURE);\n \t}\n-\twrite_num(path, num);\n+\tret = write_num(path, num);\n+\tif (ret \u003c 0)\n+\t\texit(EXIT_FAILURE);\n }\n \n void thp_read_settings(struct thp_settings *settings)\n@@ -157,8 +170,13 @@ void thp_read_settings(struct thp_settings *settings)\n \t\t.max_ptes_shared = thp_read_num(\"khugepaged/max_ptes_shared\"),\n \t\t.pages_to_scan = thp_read_num(\"khugepaged/pages_to_scan\"),\n \t};\n-\tif (dev_queue_read_ahead_path[0])\n-\t\tsettings-\u003eread_ahead_kb = read_num(dev_queue_read_ahead_path);\n+\tif (dev_queue_read_ahead_path[0]) {\n+\t\tint ret = read_num(dev_queue_read_ahead_path,\n+\t\t\t\t   \u0026settings-\u003eread_ahead_kb);\n+\n+\t\tif (ret \u003c 0)\n+\t\t\texit(EXIT_FAILURE);\n+\t}\n \n \tfor (i = 0; i \u003c NR_ORDERS; i++) {\n \t\tif (!((1 \u003c\u003c i) \u0026 orders)) {\n@@ -208,8 +226,13 @@ void thp_write_settings(struct thp_settings *settings)\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 \n-\tif (dev_queue_read_ahead_path[0])\n-\t\twrite_num(dev_queue_read_ahead_path, settings-\u003eread_ahead_kb);\n+\tif (dev_queue_read_ahead_path[0]) {\n+\t\tint ret = write_num(dev_queue_read_ahead_path,\n+\t\t\t\t    settings-\u003eread_ahead_kb);\n+\n+\t\tif (ret \u003c 0)\n+\t\t\texit(EXIT_FAILURE);\n+\t}\n \n \tfor (i = 0; i \u003c NR_ORDERS; i++) {\n \t\tif (!((1 \u003c\u003c i) \u0026 orders))\n@@ -306,8 +329,11 @@ static unsigned long __thp_supported_orders(bool is_shmem)\n \t\t\texit(EXIT_FAILURE);\n \t\t}\n \n+\t\tif (access(path, F_OK))\n+\t\t\tcontinue;\n+\n \t\tret = read_file(path, buf, sizeof(buf));\n-\t\tif (ret)\n+\t\tif (ret \u003e 0)\n \t\t\torders |= 1UL \u003c\u003c i;\n \t}\n \n@@ -382,8 +408,7 @@ int detect_hugetlb_page_sizes(unsigned long sizes[], int max)\n \t\tif (sscanf(entry-\u003ed_name, \"hugepages-%zukB\", \u0026kb) != 1)\n \t\t\tcontinue;\n \t\tsizes[count++] = kb * 1024;\n-\t\tksft_print_msg(\"[INFO] detected hugetlb page size: %zu KiB\\n\",\n-\t\t\t       kb);\n+\t\tprintf(\"# [INFO] detected hugetlb page size: %zu KiB\\n\", kb);\n \t}\n \tclosedir(dir);\n \treturn count;\n@@ -425,28 +450,43 @@ static void hugetlb_sysfs_path(char *buf, size_t buflen,\n unsigned long hugetlb_nr_pages(unsigned long size)\n {\n \tchar path[PATH_MAX];\n+\tunsigned long nr;\n+\tint ret;\n \n \thugetlb_sysfs_path(path, sizeof(path), size, \"nr_hugepages\");\n \n-\treturn read_num(path);\n+\tret = read_num(path, \u0026nr);\n+\tif (ret \u003c 0)\n+\t\texit(EXIT_FAILURE);\n+\n+\treturn nr;\n }\n \n void hugetlb_set_nr_pages(unsigned long size, unsigned long nr)\n {\n \tchar path[PATH_MAX];\n+\tint ret;\n \n \thugetlb_sysfs_path(path, sizeof(path), size, \"nr_hugepages\");\n \n-\twrite_num(path, nr);\n+\tret = write_num(path, nr);\n+\tif (ret \u003c 0)\n+\t\texit(EXIT_FAILURE);\n }\n \n unsigned long hugetlb_free_pages(unsigned long size)\n {\n \tchar path[PATH_MAX];\n+\tunsigned long nr;\n+\tint ret;\n \n \thugetlb_sysfs_path(path, sizeof(path), size, \"free_hugepages\");\n \n-\treturn read_num(path);\n+\tret = read_num(path, \u0026nr);\n+\tif (ret \u003c 0)\n+\t\texit(EXIT_FAILURE);\n+\n+\treturn nr;\n }\n \n static bool __hugetlb_setup(unsigned long size, unsigned long nr)\n@@ -502,7 +542,8 @@ unsigned long hugetlb_setup(unsigned long nr, unsigned long sizes[],\n \t\treturn 0;\n \n \tif (nr_enabled \u003e max) {\n-\t\tksft_print_msg(\"detected %d huge page sizes, will only test %d\\n\", nr_enabled, max);\n+\t\tprintf(\"# detected %d huge page sizes, will only test %d\\n\",\n+\t\t       nr_enabled, max);\n \t\tnr_enabled = max;\n \t}\n \n@@ -574,8 +615,10 @@ static void hugepage_restore_settings_atexit(void)\n \n static void hugepage_restore_settings_sighandler(int sig)\n {\n+\t(void)sig;\n+\n \t/* exit() will invoke the hugepage_restore_settings_atexit handler. */\n-\texit(KSFT_FAIL);\n+\texit(EXIT_FAILURE);\n }\n \n void hugepage_save_settings(bool thp, bool hugetlb)\ndiff --git a/tools/testing/selftests/mm/hugepage_settings.h b/tools/lib/mm/hugepage_settings.h\nsimilarity index 100%\nrename from tools/testing/selftests/mm/hugepage_settings.h\nrename to tools/lib/mm/hugepage_settings.h\ndiff --git a/tools/mm/.gitignore b/tools/mm/.gitignore\nindex 922879f93fc849..154d740be02e82 100644\n--- a/tools/mm/.gitignore\n+++ b/tools/mm/.gitignore\n@@ -2,3 +2,5 @@\n slabinfo\n page-types\n page_owner_sort\n+thp_swap_allocator_test\n+gup_bench\ndiff --git a/tools/mm/Makefile b/tools/mm/Makefile\nindex f5725b5c23aa26..d82cc8c43ee02d 100644\n--- a/tools/mm/Makefile\n+++ b/tools/mm/Makefile\n@@ -3,13 +3,14 @@\n #\n include ../scripts/Makefile.include\n \n-BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test\n+BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test gup_bench\n INSTALL_TARGETS = $(BUILD_TARGETS) thpmaps\n \n LIB_DIR = ../lib/api\n LIBS = $(LIB_DIR)/libapi.a\n+GUP_BENCH_OBJS = gup_bench.c ../lib/mm/hugepage_settings.c ../lib/mm/file_utils.c\n \n-CFLAGS += -Wall -Wextra -I../lib/ -pthread\n+CFLAGS += -Wall -Wextra -I../lib/ -I../.. -pthread\n LDFLAGS += $(LIBS) -pthread\n \n all: $(BUILD_TARGETS)\n@@ -22,8 +23,11 @@ $(LIBS):\n %: %.c\n \t$(CC) $(CFLAGS) -o $@ $\u003c $(LDFLAGS)\n \n+gup_bench: $(GUP_BENCH_OBJS) $(LIBS)\n+\t$(CC) $(CFLAGS) -o $@ $(GUP_BENCH_OBJS) $(LDFLAGS)\n+\n clean:\n-\t$(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test\n+\t$(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test gup_bench\n \tmake -C $(LIB_DIR) clean\n \n sbindir ?= /usr/sbin\ndiff --git a/tools/mm/gup_bench.c b/tools/mm/gup_bench.c\nnew file mode 100644\nindex 00000000000000..497b8f3f6ac4b4\n--- /dev/null\n+++ b/tools/mm/gup_bench.c\n@@ -0,0 +1,482 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Microbenchmark for get_user_pages (GUP) kernel interfaces.\n+ *\n+ * Exercises GUP_FAST_BENCHMARK, PIN_FAST_BENCHMARK, and\n+ * PIN_LONGTERM_BENCHMARK via the CONFIG_GUP_TEST debugfs interface.\n+ *\n+ * Example use:\n+ *   # Run the full matrix (all commands, access modes, page counts):\n+ *   ./gup_bench\n+ *\n+ *   # Single run: pin_user_pages_fast, 512 pages, write access, hugetlb:\n+ *   ./gup_bench -a -n 512 -w -H\n+ *\n+ * Requires CONFIG_GUP_TEST=y and debugfs mounted at /sys/kernel/debug.\n+ * Must be run as root.\n+ */\n+\n+#define __SANE_USERSPACE_TYPES__ // Use ll64\n+#include \u003cfcntl.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003cdirent.h\u003e\n+#include \u003csys/ioctl.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003csys/types.h\u003e\n+#include \u003cpthread.h\u003e\n+#include \u003cstdbool.h\u003e\n+#include \u003cstdatomic.h\u003e\n+#include \u003cstdint.h\u003e\n+#include \u003climits.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003cmm/gup_test.h\u003e\n+#include \u003cmm/hugepage_settings.h\u003e\n+\n+#define MB (1UL \u003c\u003c 20)\n+\n+#ifndef ARRAY_SIZE\n+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))\n+#endif\n+\n+/* Just the flags we need, copied from the kernel internals. */\n+#define FOLL_WRITE\t0x01\t/* check pte is writable */\n+\n+#define GUP_TEST_FILE \"/sys/kernel/debug/gup_test\"\n+\n+static unsigned int psize(void)\n+{\n+\tstatic unsigned int __page_size;\n+\n+\tif (!__page_size)\n+\t\t__page_size = sysconf(_SC_PAGESIZE);\n+\treturn __page_size;\n+}\n+\n+static unsigned long cmd;\n+static const char *bench_label;\n+static int gup_fd, repeats = 1;\n+static unsigned long size = 128 * MB;\n+static atomic_int bench_error;\n+/* Serialize prints */\n+static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;\n+\n+static const unsigned long bench_cmds[] = {\n+\tGUP_FAST_BENCHMARK,\n+\tPIN_FAST_BENCHMARK,\n+\tPIN_LONGTERM_BENCHMARK,\n+};\n+\n+static const int bench_thp_modes[] = { 1, 0 };\t/* on, off */\n+static const int bench_nr_pages_list[] = { 1, 512, 123, -1 };\n+\n+static const char *cmd_to_str(unsigned long cmd)\n+{\n+\tswitch (cmd) {\n+\tcase GUP_FAST_BENCHMARK:\n+\t\treturn \"GUP_FAST_BENCHMARK\";\n+\tcase PIN_FAST_BENCHMARK:\n+\t\treturn \"PIN_FAST_BENCHMARK\";\n+\tcase PIN_LONGTERM_BENCHMARK:\n+\t\treturn \"PIN_LONGTERM_BENCHMARK\";\n+\t}\n+\treturn \"Unknown command\";\n+}\n+\n+static long parse_long_arg_base(const char *arg, const char *name, int base)\n+{\n+\tchar *end;\n+\tlong val;\n+\n+\terrno = 0;\n+\tval = strtol(arg, \u0026end, base);\n+\tif (errno || end == arg || *end != '\\0') {\n+\t\tfprintf(stderr, \"Invalid %s '%s'\\n\", name, arg);\n+\t\texit(1);\n+\t}\n+\treturn val;\n+}\n+\n+static long parse_long_arg(const char *arg, const char *name)\n+{\n+\treturn parse_long_arg_base(arg, name, 10);\n+}\n+\n+static long parse_positive_long_arg(const char *arg, const char *name)\n+{\n+\tlong val = parse_long_arg(arg, name);\n+\n+\tif (val \u003c 1) {\n+\t\tfprintf(stderr, \"Invalid %s '%s'\\n\", name, arg);\n+\t\texit(1);\n+\t}\n+\n+\treturn val;\n+}\n+\n+struct bench_run {\n+\tunsigned long cmd;\n+\tint thp;\t\t/* -1: default, 0: off, 1: on */\n+\tbool hugetlb;\n+\tbool write;\t\t/* whether to set FOLL_WRITE */\n+\tbool shared;\n+\tint nr_pages;\t\t/* -1 means all pages (size / psize()) */\n+\tunsigned long size;\n+\tchar *file;\n+\tint nthreads;\n+\tunsigned int gup_flags;\n+};\n+\n+void *gup_thread(void *data)\n+{\n+\tstruct gup_test gup = *(struct gup_test *)data;\n+\tint i, status;\n+\n+\tfor (i = 0; i \u003c repeats; i++) {\n+\t\tgup.size = size;\n+\t\tstatus = ioctl(gup_fd, cmd, \u0026gup);\n+\t\tif (status) {\n+\t\t\tbench_error = 1;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tpthread_mutex_lock(\u0026print_mutex);\n+\t\tprintf(\"%s time: get:%lld put:%lld us\",\n+\t\t       bench_label, gup.get_delta_usec,\n+\t\t       gup.put_delta_usec);\n+\t\tif (gup.size != size)\n+\t\t\tprintf(\", truncated (size: %lld)\", gup.size);\n+\t\tprintf(\"\\n\");\n+\t\tpthread_mutex_unlock(\u0026print_mutex);\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static int run_bench(struct bench_run *run)\n+{\n+\tstruct gup_test gup = { 0 };\n+\tint zero_fd, i, ret, started_threads = 0;\n+\tunsigned long nr_pages_per_call;\n+\tint flags = MAP_PRIVATE;\n+\tpthread_t *tid;\n+\tchar label[128];\n+\tchar *p;\n+\tbool restore_hugetlb = false;\n+\n+\t/* Set globals consumed by gup_thread */\n+\tcmd = run-\u003ecmd;\n+\tsize = run-\u003esize;\n+\tbench_error = 0;\n+\n+\tif (run-\u003ehugetlb) {\n+\t\tunsigned long hp_size = default_huge_page_size();\n+\n+\t\tif (!hp_size) {\n+\t\t\tfprintf(stderr, \"Could not determine huge page size\\n\");\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tif (size \u003e ULONG_MAX - (hp_size - 1)) {\n+\t\t\tfprintf(stderr, \"HugeTLB mapping size is too large\\n\");\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tsize = (size + hp_size - 1) \u0026 ~(hp_size - 1);\n+\t\tif (!hugetlb_setup_default(size / hp_size)) {\n+\t\t\tfprintf(stderr, \"Not enough huge pages\\n\");\n+\t\t\thugetlb_restore_settings();\n+\t\t\treturn 1;\n+\t\t}\n+\t\trestore_hugetlb = true;\n+\t\tflags |= (MAP_HUGETLB | MAP_ANONYMOUS);\n+\t}\n+\n+\tif (run-\u003eshared) {\n+\t\tflags \u0026= ~MAP_PRIVATE;\n+\t\tflags |= MAP_SHARED;\n+\t}\n+\n+\tnr_pages_per_call = run-\u003enr_pages \u003c 0 ? size / psize() :\n+\t\t(unsigned long)run-\u003enr_pages;\n+\n+\tif (nr_pages_per_call \u003e UINT_MAX) {\n+\t\tfprintf(stderr, \"Page count is too large\\n\");\n+\t\tif (restore_hugetlb)\n+\t\t\thugetlb_restore_settings();\n+\t\treturn 1;\n+\t}\n+\tgup.nr_pages_per_call = nr_pages_per_call;\n+\n+\tgup.gup_flags = run-\u003egup_flags;\n+\tif (run-\u003ewrite)\n+\t\tgup.gup_flags |= FOLL_WRITE;\n+\n+\tsnprintf(label, sizeof(label), \"%s (nr_pages=%-4u %s %s %s %s)\",\n+\t\t cmd_to_str(run-\u003ecmd),\n+\t\t gup.nr_pages_per_call,\n+\t\t run-\u003ewrite  ? \"write\"   : \"read\",\n+\t\t run-\u003eshared ? \"shared\"  : \"private\",\n+\t\t run-\u003ehugetlb ? \"hugetlb=on\" : \"hugetlb=off\",\n+\t\t run-\u003ehugetlb ? \"thp=off\" :\n+\t\t (run-\u003ethp == 1 ? \"thp=on\" :\n+\t\t (run-\u003ethp == 0 ? \"thp=off\" : \"thp=default\")));\n+\tbench_label = label;\n+\n+\tzero_fd = open(run-\u003efile, O_RDWR);\n+\tif (zero_fd \u003c 0) {\n+\t\tfprintf(stderr, \"Unable to open %s: %s\\n\", run-\u003efile, strerror(errno));\n+\t\tif (restore_hugetlb)\n+\t\t\thugetlb_restore_settings();\n+\t\treturn 1;\n+\t}\n+\n+\tp = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, zero_fd, 0);\n+\tclose(zero_fd);\n+\tif (p == MAP_FAILED) {\n+\t\tfprintf(stderr, \"mmap: %s\\n\", strerror(errno));\n+\t\tif (restore_hugetlb)\n+\t\t\thugetlb_restore_settings();\n+\t\treturn 1;\n+\t}\n+\tgup.addr = (unsigned long)p;\n+\n+\tif (run-\u003ethp == 1)\n+\t\tmadvise(p, size, MADV_HUGEPAGE);\n+\telse if (run-\u003ethp == 0)\n+\t\tmadvise(p, size, MADV_NOHUGEPAGE);\n+\n+\t/* Fault them in here, from user space. */\n+\tfor (; (unsigned long)p \u003c gup.addr + size; p += psize())\n+\t\tp[0] = 0;\n+\n+\ttid = malloc(sizeof(pthread_t) * run-\u003enthreads);\n+\tif (!tid) {\n+\t\tfprintf(stderr, \"Failed to allocate %d threads: %s\\n\",\n+\t\t\trun-\u003enthreads, strerror(errno));\n+\t\tmunmap((void *)gup.addr, size);\n+\t\tif (restore_hugetlb)\n+\t\t\thugetlb_restore_settings();\n+\t\treturn 1;\n+\t}\n+\n+\tfor (i = 0; i \u003c run-\u003enthreads; i++) {\n+\t\tret = pthread_create(\u0026tid[i], NULL, gup_thread, \u0026gup);\n+\t\tif (ret) {\n+\t\t\tfprintf(stderr, \"pthread_create failed: %s\\n\", strerror(ret));\n+\t\t\tbench_error = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t\tstarted_threads++;\n+\t}\n+\tfor (i = 0; i \u003c started_threads; i++) {\n+\t\tret = pthread_join(tid[i], NULL);\n+\t\tif (ret) {\n+\t\t\tfprintf(stderr, \"pthread_join failed: %s\\n\", strerror(ret));\n+\t\t\tbench_error = 1;\n+\t\t}\n+\t}\n+\n+\tfree(tid);\n+\tmunmap((void *)gup.addr, size);\n+\tif (restore_hugetlb)\n+\t\thugetlb_restore_settings();\n+\n+\treturn bench_error ? 1 : 0;\n+}\n+\n+static int run_matrix(void)\n+{\n+\tunsigned int c, t, w, s, n;\n+\tint ret = 0;\n+\n+\tfor (c = 0; c \u003c ARRAY_SIZE(bench_cmds); c++) {\n+\t\tfor (w = 0; w \u003c= 1; w++) {\n+\t\t\tfor (s = 0; s \u003c= 1; s++) {\n+\t\t\t\tfor (t = 0; t \u003c ARRAY_SIZE(bench_thp_modes); t++) {\n+\t\t\t\t\tfor (n = 0; n \u003c ARRAY_SIZE(bench_nr_pages_list); n++) {\n+\t\t\t\t\t\tstruct bench_run run = {\n+\t\t\t\t\t\t\t.cmd\t  = bench_cmds[c],\n+\t\t\t\t\t\t\t.thp\t  = bench_thp_modes[t],\n+\t\t\t\t\t\t\t.hugetlb  = false,\n+\t\t\t\t\t\t\t.write\t  = w,\n+\t\t\t\t\t\t\t.shared\t  = s,\n+\t\t\t\t\t\t\t.nr_pages = bench_nr_pages_list[n],\n+\t\t\t\t\t\t\t.size\t  = 128 * MB,\n+\t\t\t\t\t\t\t.file\t  = \"/dev/zero\",\n+\t\t\t\t\t\t\t.nthreads = 1,\n+\t\t\t\t\t\t};\n+\t\t\t\t\t\tret |= run_bench(\u0026run);\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\t/* hugetlb: 256M to match run_gup_matrix() in run_vmtests.sh */\n+\t\t\t\tfor (n = 0; n \u003c ARRAY_SIZE(bench_nr_pages_list); n++) {\n+\t\t\t\t\tstruct bench_run run = {\n+\t\t\t\t\t\t.cmd\t  = bench_cmds[c],\n+\t\t\t\t\t\t.thp\t  = -1,\n+\t\t\t\t\t\t.hugetlb  = true,\n+\t\t\t\t\t\t.write\t  = w,\n+\t\t\t\t\t\t.shared\t  = s,\n+\t\t\t\t\t\t.nr_pages = bench_nr_pages_list[n],\n+\t\t\t\t\t\t.size\t  = 256 * MB,\n+\t\t\t\t\t\t.file\t  = \"/dev/zero\",\n+\t\t\t\t\t\t.nthreads = 1,\n+\t\t\t\t\t};\n+\t\t\t\t\tret |= run_bench(\u0026run);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\treturn ret;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tstruct bench_run run = {\n+\t\t.cmd\t  = GUP_FAST_BENCHMARK,\n+\t\t.thp\t  = -1,\n+\t\t.hugetlb  = false,\n+\t\t.write\t  = true,\n+\t\t.shared\t  = false,\n+\t\t.nr_pages = 1,\n+\t\t.size\t  = 128 * MB,\n+\t\t.file\t  = \"/dev/zero\",\n+\t\t.nthreads = 1,\n+\t};\n+\tint opt, result;\n+\n+\twhile ((opt = getopt(argc, argv, \"m:r:n:F:f:aj:tTLuwWSH\")) != -1) {\n+\t\tswitch (opt) {\n+\t\t/* Command selection */\n+\t\tcase 'u':\n+\t\t\trun.cmd = GUP_FAST_BENCHMARK;\n+\t\t\tbreak;\n+\t\tcase 'a':\n+\t\t\trun.cmd = PIN_FAST_BENCHMARK;\n+\t\t\tbreak;\n+\t\tcase 'L':\n+\t\t\trun.cmd = PIN_LONGTERM_BENCHMARK;\n+\t\t\tbreak;\n+\n+\t\t/* Memory type */\n+\t\tcase 'H':\n+\t\t\trun.hugetlb = true;\n+\t\t\tbreak;\n+\t\tcase 't':\n+\t\t\trun.thp = 1;\n+\t\t\tbreak;\n+\t\tcase 'T':\n+\t\t\trun.thp = 0;\n+\t\t\tbreak;\n+\n+\t\t/* Access mode */\n+\t\tcase 'w':\n+\t\t\trun.write = true;\n+\t\t\tbreak;\n+\t\tcase 'W':\n+\t\t\trun.write = false;\n+\t\t\tbreak;\n+\t\tcase 'S':\n+\t\t\trun.shared = true;\n+\t\t\tbreak;\n+\n+\t\t/* Mapping */\n+\t\tcase 'f':\n+\t\t\trun.file = optarg;\n+\t\t\tbreak;\n+\n+\t\t/* Sizing and iteration */\n+\t\tcase 'm':\n+\t\t\trun.size = parse_positive_long_arg(optarg, \"size\");\n+\t\t\tif (run.size \u003e ULONG_MAX / MB) {\n+\t\t\t\tfprintf(stderr, \"Invalid size '%s'\\n\", optarg);\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t\trun.size *= MB;\n+\t\t\tbreak;\n+\t\tcase 'n': {\n+\t\t\tlong val;\n+\n+\t\t\tval = parse_long_arg(optarg, \"page count\");\n+\t\t\tif (val != -1 \u0026\u0026 (val \u003c 1 || val \u003e INT_MAX)) {\n+\t\t\t\tfprintf(stderr, \"Invalid page count '%s'\\n\", optarg);\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t\trun.nr_pages = val;\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase 'r': {\n+\t\t\tlong val;\n+\n+\t\t\tval = parse_positive_long_arg(optarg, \"repeat count\");\n+\t\t\tif (val \u003e INT_MAX) {\n+\t\t\t\tfprintf(stderr, \"Invalid repeat count '%s'\\n\", optarg);\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t\trepeats = val;\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase 'j': {\n+\t\t\tlong val;\n+\n+\t\t\tval = parse_positive_long_arg(optarg, \"thread count\");\n+\t\t\tif (val \u003e INT_MAX ||\n+\t\t\t    (size_t)val \u003e SIZE_MAX / sizeof(pthread_t)) {\n+\t\t\t\tfprintf(stderr, \"Invalid thread count '%s'\\n\", optarg);\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t\trun.nthreads = val;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* Advanced */\n+\t\tcase 'F': {\n+\t\t\tlong val;\n+\n+\t\t\tval = parse_long_arg_base(optarg, \"GUP flags\", 0);\n+\t\t\tif (val \u003c 0 || val \u003e UINT_MAX) {\n+\t\t\t\tfprintf(stderr, \"Invalid GUP flags '%s'\\n\", optarg);\n+\t\t\t\texit(1);\n+\t\t\t}\n+\n+\t\t\trun.gup_flags = val;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tdefault:\n+\t\t\tfprintf(stderr, \"Wrong argument\\n\");\n+\t\t\texit(1);\n+\t\t}\n+\t}\n+\n+\tif (optind != argc) {\n+\t\tfprintf(stderr, \"Unexpected argument '%s'\\n\", argv[optind]);\n+\t\texit(1);\n+\t}\n+\n+\tgup_fd = open(GUP_TEST_FILE, O_RDWR);\n+\tif (gup_fd == -1) {\n+\t\tif (errno == EACCES) {\n+\t\t\tfprintf(stderr, \"Please run as root\\n\");\n+\t\t} else if (errno == ENOENT) {\n+\t\t\tDIR *debugfs = opendir(\"/sys/kernel/debug\");\n+\n+\t\t\tif (!debugfs) {\n+\t\t\t\tfprintf(stderr, \"Mount debugfs at /sys/kernel/debug\\n\");\n+\t\t\t} else {\n+\t\t\t\tclosedir(debugfs);\n+\t\t\t\tfprintf(stderr, \"Check CONFIG_GUP_TEST in kernel config\\n\");\n+\t\t\t}\n+\t\t} else {\n+\t\t\tfprintf(stderr, \"Failed to open %s: %s\\n\", GUP_TEST_FILE,\n+\t\t\t\tstrerror(errno));\n+\t\t}\n+\t\texit(1);\n+\t}\n+\n+\tresult = (argc == 1) ? run_matrix() : run_bench(\u0026run);\n+\tclose(gup_fd);\n+\treturn result;\n+}\ndiff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile\nindex e6df968f0971c8..e77c4cc41b4703 100644\n--- a/tools/testing/selftests/mm/Makefile\n+++ b/tools/testing/selftests/mm/Makefile\n@@ -37,7 +37,8 @@ endif\n # LDLIBS.\n MAKEFLAGS += --no-builtin-rules\n \n-CFLAGS = -Wall -O2 -I $(top_srcdir) $(EXTRA_CFLAGS) $(KHDR_INCLUDES) $(TOOLS_INCLUDES)\n+CFLAGS = -Wall -O2 -I $(top_srcdir) -I $(top_srcdir)/tools/lib\n+CFLAGS += $(EXTRA_CFLAGS) $(KHDR_INCLUDES) $(TOOLS_INCLUDES)\n CFLAGS += -Wunreachable-code\n LDLIBS = -lrt -lpthread -lm\n \n@@ -187,8 +188,10 @@ TEST_FILES += write_hugetlb_memory.sh\n \n include ../lib.mk\n \n-$(TEST_GEN_PROGS): vm_util.c hugepage_settings.c\n-$(TEST_GEN_FILES): vm_util.c hugepage_settings.c\n+$(TEST_GEN_PROGS): vm_util.c $(top_srcdir)/tools/lib/mm/hugepage_settings.c \\\n+\t\t   $(top_srcdir)/tools/lib/mm/file_utils.c\n+$(TEST_GEN_FILES): vm_util.c $(top_srcdir)/tools/lib/mm/hugepage_settings.c \\\n+\t\t   $(top_srcdir)/tools/lib/mm/file_utils.c\n \n $(OUTPUT)/uffd-stress: uffd-common.c\n $(OUTPUT)/uffd-unit-tests: uffd-common.c\n@@ -217,7 +220,7 @@ $(BINARIES_32): CFLAGS += -m32 -mxsave\n $(BINARIES_32): LDLIBS += -lrt -ldl -lm\n $(BINARIES_32): $(OUTPUT)/%_32: %.c\n \t$(call msg,CC,,$@)\n-\t$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(notdir $^) $(LDLIBS) -o $@\n+\t$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $^ $(LDLIBS) -o $@\n $(foreach t,$(VMTARGETS),$(eval $(call gen-target-rule-32,$(t))))\n endif\n \n@@ -226,7 +229,7 @@ $(BINARIES_64): CFLAGS += -m64 -mxsave\n $(BINARIES_64): LDLIBS += -lrt -ldl\n $(BINARIES_64): $(OUTPUT)/%_64: %.c\n \t$(call msg,CC,,$@)\n-\t$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(notdir $^) $(LDLIBS) -o $@\n+\t$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $^ $(LDLIBS) -o $@\n $(foreach t,$(VMTARGETS),$(eval $(call gen-target-rule-64,$(t))))\n endif\n \ndiff --git a/tools/testing/selftests/mm/compaction_test.c b/tools/testing/selftests/mm/compaction_test.c\nindex 5b582588e015d4..b2eaa490e7c253 100644\n--- a/tools/testing/selftests/mm/compaction_test.c\n+++ b/tools/testing/selftests/mm/compaction_test.c\n@@ -15,9 +15,9 @@\n #include \u003cerrno.h\u003e\n #include \u003cunistd.h\u003e\n #include \u003cstring.h\u003e\n+#include \u003cmm/hugepage_settings.h\u003e\n \n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n #define MAP_SIZE_MB\t100\n #define MAP_SIZE\t(MAP_SIZE_MB * 1024 * 1024)\ndiff --git a/tools/testing/selftests/mm/cow.c b/tools/testing/selftests/mm/cow.c\nindex 0c627ea89ff7b2..3c3d129c91b29d 100644\n--- a/tools/testing/selftests/mm/cow.c\n+++ b/tools/testing/selftests/mm/cow.c\n@@ -29,7 +29,6 @@\n #include \"../../../../mm/gup_test.h\"\n #include \"kselftest.h\"\n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n static size_t pagesize;\n static int pagemap_fd;\ndiff --git a/tools/testing/selftests/mm/folio_split_race_test.c b/tools/testing/selftests/mm/folio_split_race_test.c\nindex 6329e37fff4c94..496b74b37476b3 100644\n--- a/tools/testing/selftests/mm/folio_split_race_test.c\n+++ b/tools/testing/selftests/mm/folio_split_race_test.c\n@@ -25,7 +25,6 @@\n #include \u003cunistd.h\u003e\n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n uint64_t page_size;\n uint64_t pmd_pagesize;\ndiff --git a/tools/testing/selftests/mm/guard-regions.c b/tools/testing/selftests/mm/guard-regions.c\nindex b21df3040b1c7c..ed967b3c7f3704 100644\n--- a/tools/testing/selftests/mm/guard-regions.c\n+++ b/tools/testing/selftests/mm/guard-regions.c\n@@ -21,7 +21,6 @@\n #include \u003csys/uio.h\u003e\n #include \u003cunistd.h\u003e\n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n #include \"../pidfd/pidfd.h\"\n \ndiff --git a/tools/testing/selftests/mm/gup_longterm.c b/tools/testing/selftests/mm/gup_longterm.c\nindex eb8963e9d98f06..68c840bae08208 100644\n--- a/tools/testing/selftests/mm/gup_longterm.c\n+++ b/tools/testing/selftests/mm/gup_longterm.c\n@@ -29,7 +29,6 @@\n #include \"../../../../mm/gup_test.h\"\n #include \"kselftest.h\"\n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n static size_t pagesize;\n static int nr_hugetlbsizes;\ndiff --git a/tools/testing/selftests/mm/gup_test.c b/tools/testing/selftests/mm/gup_test.c\nindex 3f841a96f87068..d9caa9fb8b7856 100644\n--- a/tools/testing/selftests/mm/gup_test.c\n+++ b/tools/testing/selftests/mm/gup_test.c\n@@ -1,275 +1,443 @@\n #define __SANE_USERSPACE_TYPES__ // Use ll64\n #include \u003cfcntl.h\u003e\n #include \u003cerrno.h\u003e\n+#include \u003cstdbool.h\u003e\n #include \u003cstdio.h\u003e\n #include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n #include \u003cunistd.h\u003e\n #include \u003cdirent.h\u003e\n #include \u003csys/ioctl.h\u003e\n #include \u003csys/mman.h\u003e\n #include \u003csys/stat.h\u003e\n #include \u003csys/types.h\u003e\n-#include \u003cpthread.h\u003e\n-#include \u003cassert.h\u003e\n #include \u003cmm/gup_test.h\u003e\n #include \"kselftest.h\"\n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n+#include \"kselftest_harness.h\"\n \n #define MB (1UL \u003c\u003c 20)\n \n+#ifndef ARRAY_SIZE\n+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))\n+#endif\n+\n /* Just the flags we need, copied from the kernel internals. */\n #define FOLL_WRITE\t0x01\t/* check pte is writable */\n \n+/* Page counts exercising single, THP-batch, partial, and full-mapping GUP. */\n+static const int nr_pages_list[] = { 1, 512, 123, -1 };\n+\n #define GUP_TEST_FILE \"/sys/kernel/debug/gup_test\"\n \n-static unsigned long cmd = GUP_FAST_BENCHMARK;\n-static int gup_fd, repeats = 1;\n-static unsigned long size = 128 * MB;\n-/* Serialize prints */\n-static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;\n+FIXTURE(gup_test)\n+{\n+\tint gup_fd;\n+\tchar *addr;\n+\tunsigned long size;\n+};\n+\n+FIXTURE_VARIANT(gup_test)\n+{\n+\tbool thp;\n+\tbool hugetlb;\n+\tbool write;\n+\tbool shared;\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, private_write)\n+{\n+\t.thp = false,\n+\t.hugetlb = false,\n+\t.write = true,\n+\t.shared = false,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, private_readonly)\n+{\n+\t.thp = false,\n+\t.hugetlb = false,\n+\t.write = false,\n+\t.shared = false,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, private_write_thp)\n+{\n+\t.thp = true,\n+\t.hugetlb = false,\n+\t.write = true,\n+\t.shared = false,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, private_readonly_thp)\n+{\n+\t.thp = true,\n+\t.hugetlb = false,\n+\t.write = false,\n+\t.shared = false,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, private_write_hugetlb)\n+{\n+\t.thp = false,\n+\t.hugetlb = true,\n+\t.write = true,\n+\t.shared = false,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, private_readonly_hugetlb)\n+{\n+\t.thp = false,\n+\t.hugetlb = true,\n+\t.write = false,\n+\t.shared = false,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, shared_write)\n+{\n+\t.thp = false,\n+\t.hugetlb = false,\n+\t.write = true,\n+\t.shared = true,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, shared_readonly)\n+{\n+\t.thp = false,\n+\t.hugetlb = false,\n+\t.write = false,\n+\t.shared = true,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, shared_write_thp)\n+{\n+\t.thp = true,\n+\t.hugetlb = false,\n+\t.write = true,\n+\t.shared = true,\n+};\n \n-static char *cmd_to_str(unsigned long cmd)\n+FIXTURE_VARIANT_ADD(gup_test, shared_readonly_thp)\n {\n-\tswitch (cmd) {\n-\tcase GUP_FAST_BENCHMARK:\n-\t\treturn \"GUP_FAST_BENCHMARK\";\n-\tcase PIN_FAST_BENCHMARK:\n-\t\treturn \"PIN_FAST_BENCHMARK\";\n-\tcase PIN_LONGTERM_BENCHMARK:\n-\t\treturn \"PIN_LONGTERM_BENCHMARK\";\n-\tcase GUP_BASIC_TEST:\n-\t\treturn \"GUP_BASIC_TEST\";\n-\tcase PIN_BASIC_TEST:\n-\t\treturn \"PIN_BASIC_TEST\";\n-\tcase DUMP_USER_PAGES_TEST:\n-\t\treturn \"DUMP_USER_PAGES_TEST\";\n+\t.thp = true,\n+\t.hugetlb = false,\n+\t.write = false,\n+\t.shared = true,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, shared_write_hugetlb)\n+{\n+\t.thp = false,\n+\t.hugetlb = true,\n+\t.write = true,\n+\t.shared = true,\n+};\n+\n+FIXTURE_VARIANT_ADD(gup_test, shared_readonly_hugetlb)\n+{\n+\t.thp = false,\n+\t.hugetlb = true,\n+\t.write = false,\n+\t.shared = true,\n+};\n+\n+FIXTURE_SETUP(gup_test)\n+{\n+\tint mmap_flags = MAP_PRIVATE;\n+\tint zero_fd;\n+\tchar *p;\n+\n+\t/* zero_fd has to be \u003e= 0. Already checked in main() */\n+\tzero_fd = open(\"/dev/zero\", O_RDWR);\n+\tASSERT_GE(zero_fd, 0);\n+\n+\t/* gup_fd has to be \u003e= 0. Already checked in main() */\n+\tself-\u003egup_fd = open(GUP_TEST_FILE, O_RDWR);\n+\tASSERT_GE(self-\u003egup_fd, 0);\n+\n+\tself-\u003esize = variant-\u003ehugetlb ? 256 * MB : 128 * MB;\n+\n+\t/* Check for hugetlb */\n+\tif (variant-\u003ehugetlb) {\n+\t\tunsigned long hp_size = default_huge_page_size();\n+\n+\t\tif (!hp_size) {\n+\t\t\tclose(zero_fd);\n+\t\t\tclose(self-\u003egup_fd);\n+\t\t\tSKIP(return, \"HugeTLB not available\\n\");\n+\t\t}\n+\n+\t\tself-\u003esize = (self-\u003esize + hp_size - 1) \u0026 ~(hp_size - 1);\n+\t\tif (!hugetlb_setup_default(self-\u003esize / hp_size)) {\n+\t\t\thugetlb_restore_settings();\n+\t\t\tclose(zero_fd);\n+\t\t\tclose(self-\u003egup_fd);\n+\t\t\tSKIP(return, \"Not enough huge pages\\n\");\n+\t\t}\n+\n+\t\tmmap_flags |= (MAP_HUGETLB | MAP_ANONYMOUS);\n+\t}\n+\n+\tif (variant-\u003eshared)\n+\t\tmmap_flags = (mmap_flags \u0026 ~MAP_PRIVATE) | MAP_SHARED;\n+\n+\tself-\u003eaddr = mmap(NULL, self-\u003esize, PROT_READ | PROT_WRITE,\n+\t\t\t  mmap_flags, zero_fd, 0);\n+\n+\tclose(zero_fd);\n+\n+\tASSERT_NE(self-\u003eaddr, MAP_FAILED) {\n+\t\tint err = errno;\n+\n+\t\tclose(self-\u003egup_fd);\n+\t\tif (variant-\u003ehugetlb)\n+\t\t\thugetlb_restore_settings();\n+\t\tTH_LOG(\"mmap failed: %s\", strerror(err));\n \t}\n-\treturn \"Unknown command\";\n+\n+\tif (variant-\u003ethp)\n+\t\tmadvise(self-\u003eaddr, self-\u003esize, MADV_HUGEPAGE);\n+\telse\n+\t\tmadvise(self-\u003eaddr, self-\u003esize, MADV_NOHUGEPAGE);\n+\n+\tfor (p = self-\u003eaddr; (unsigned long)p \u003c (unsigned long)self-\u003eaddr\n+\t\t\t+ self-\u003esize; p += psize())\n+\t\tp[0] = 0;\n }\n \n-void *gup_thread(void *data)\n+FIXTURE_TEARDOWN(gup_test)\n {\n-\tstruct gup_test gup = *(struct gup_test *)data;\n-\tint i, status;\n-\n-\t/* Only report timing information on the *_BENCHMARK commands: */\n-\tif ((cmd == PIN_FAST_BENCHMARK) || (cmd == GUP_FAST_BENCHMARK) ||\n-\t     (cmd == PIN_LONGTERM_BENCHMARK)) {\n-\t\tfor (i = 0; i \u003c repeats; i++) {\n-\t\t\tgup.size = size;\n-\t\t\tstatus = ioctl(gup_fd, cmd, \u0026gup);\n-\t\t\tif (status)\n-\t\t\t\tbreak;\n-\n-\t\t\tpthread_mutex_lock(\u0026print_mutex);\n-\t\t\tksft_print_msg(\"%s: Time: get:%lld put:%lld us\",\n-\t\t\t\t       cmd_to_str(cmd), gup.get_delta_usec,\n-\t\t\t\t       gup.put_delta_usec);\n-\t\t\tif (gup.size != size)\n-\t\t\t\tksft_print_msg(\", truncated (size: %lld)\", gup.size);\n-\t\t\tksft_print_msg(\"\\n\");\n-\t\t\tpthread_mutex_unlock(\u0026print_mutex);\n-\t\t}\n-\t} else {\n-\t\tgup.size = size;\n-\t\tstatus = ioctl(gup_fd, cmd, \u0026gup);\n-\t\tif (status)\n-\t\t\tgoto return_;\n-\n-\t\tpthread_mutex_lock(\u0026print_mutex);\n-\t\tksft_print_msg(\"%s: done\\n\", cmd_to_str(cmd));\n-\t\tif (gup.size != size)\n-\t\t\tksft_print_msg(\"Truncated (size: %lld)\\n\", gup.size);\n-\t\tpthread_mutex_unlock(\u0026print_mutex);\n+\tmunmap(self-\u003eaddr, self-\u003esize);\n+\tclose(self-\u003egup_fd);\n+\n+\tif (variant-\u003ehugetlb)\n+\t\thugetlb_restore_settings();\n+}\n+\n+TEST_F(gup_test, get_user_pages)\n+{\n+\t/* Tests the get_user_pages path */\n+\tint i;\n+\n+\tfor (i = 0; i \u003c (int)ARRAY_SIZE(nr_pages_list); i++) {\n+\t\tstruct gup_test gup = { 0 };\n+\n+\t\tgup.addr = (unsigned long)self-\u003eaddr;\n+\t\tgup.size = self-\u003esize;\n+\t\tgup.nr_pages_per_call = nr_pages_list[i] \u003c 0 ?\n+\t\t\tself-\u003esize / psize() : nr_pages_list[i];\n+\n+\t\tif (variant-\u003ewrite)\n+\t\t\tgup.gup_flags |= FOLL_WRITE;\n+\n+\t\tTH_LOG(\"nr_pages_per_call=%u\", gup.nr_pages_per_call);\n+\t\tASSERT_EQ(ioctl(self-\u003egup_fd, GUP_BASIC_TEST, \u0026gup), 0);\n+\t}\n+}\n+\n+TEST_F(gup_test, pin_user_pages)\n+{\n+\t/* Tests the pin_user_pages path */\n+\tint i;\n+\n+\tfor (i = 0; i \u003c (int)ARRAY_SIZE(nr_pages_list); i++) {\n+\t\tstruct gup_test gup = { 0 };\n+\n+\t\tgup.addr = (unsigned long)self-\u003eaddr;\n+\t\tgup.size = self-\u003esize;\n+\t\tgup.nr_pages_per_call = nr_pages_list[i] \u003c 0 ?\n+\t\t\tself-\u003esize / psize() : nr_pages_list[i];\n+\n+\t\tif (variant-\u003ewrite)\n+\t\t\tgup.gup_flags |= FOLL_WRITE;\n+\n+\t\tTH_LOG(\"nr_pages_per_call=%u\", gup.nr_pages_per_call);\n+\t\tASSERT_EQ(ioctl(self-\u003egup_fd, PIN_BASIC_TEST, \u0026gup), 0);\n \t}\n+}\n \n-return_:\n-\tksft_test_result(!status, \"ioctl status %d\\n\", status);\n-\treturn NULL;\n+TEST_F(gup_test, dump_user_pages_with_get)\n+{\n+\t/* Tests DUMP_USER_PAGES_TEST using get_user_pages */\n+\tint i;\n+\n+\tfor (i = 0; i \u003c (int)ARRAY_SIZE(nr_pages_list); i++) {\n+\t\tstruct gup_test gup = { 0 };\n+\n+\t\tgup.addr = (unsigned long)self-\u003eaddr;\n+\t\tgup.size = self-\u003esize;\n+\t\tgup.nr_pages_per_call = nr_pages_list[i] \u003c 0 ?\n+\t\t\tself-\u003esize / psize() : nr_pages_list[i];\n+\n+\t\tif (variant-\u003ewrite)\n+\t\t\tgup.gup_flags |= FOLL_WRITE;\n+\n+\t\tgup.which_pages[0] = 1;\n+\n+\t\tTH_LOG(\"nr_pages_per_call=%u\", gup.nr_pages_per_call);\n+\t\tASSERT_EQ(ioctl(self-\u003egup_fd, DUMP_USER_PAGES_TEST, \u0026gup), 0);\n+\t}\n }\n \n-int main(int argc, char **argv)\n+TEST_F(gup_test, dump_user_pages_with_pin)\n {\n-\tstruct gup_test gup = { 0 };\n-\tint filed, i, opt, nr_pages = 1, thp = -1, write = 1, nthreads = 1, ret;\n-\tint flags = MAP_PRIVATE;\n-\tchar *file = \"/dev/zero\";\n-\tbool hugetlb = false;\n-\tpthread_t *tid;\n-\tchar *p;\n+\t/* Tests DUMP_USER_PAGES_TEST using pin_user_pages */\n+\tint i;\n \n-\twhile ((opt = getopt(argc, argv, \"m:r:n:F:f:abcj:tTLUuwWSHpz\")) != -1) {\n-\t\tswitch (opt) {\n-\t\tcase 'a':\n-\t\t\tcmd = PIN_FAST_BENCHMARK;\n-\t\t\tbreak;\n-\t\tcase 'b':\n-\t\t\tcmd = PIN_BASIC_TEST;\n-\t\t\tbreak;\n-\t\tcase 'L':\n-\t\t\tcmd = PIN_LONGTERM_BENCHMARK;\n-\t\t\tbreak;\n-\t\tcase 'c':\n-\t\t\tcmd = DUMP_USER_PAGES_TEST;\n-\t\t\t/*\n-\t\t\t * Dump page 0 (index 1). May be overridden later, by\n-\t\t\t * user's non-option arguments.\n-\t\t\t *\n-\t\t\t * .which_pages is zero-based, so that zero can mean \"do\n-\t\t\t * nothing\".\n-\t\t\t */\n-\t\t\tgup.which_pages[0] = 1;\n-\t\t\tbreak;\n-\t\tcase 'p':\n-\t\t\t/* works only with DUMP_USER_PAGES_TEST */\n-\t\t\tgup.test_flags |= GUP_TEST_FLAG_DUMP_PAGES_USE_PIN;\n-\t\t\tbreak;\n-\t\tcase 'F':\n-\t\t\t/* strtol, so you can pass flags in hex form */\n-\t\t\tgup.gup_flags = strtol(optarg, 0, 0);\n-\t\t\tbreak;\n-\t\tcase 'j':\n-\t\t\tnthreads = atoi(optarg);\n-\t\t\tbreak;\n-\t\tcase 'm':\n-\t\t\tsize = atoi(optarg) * MB;\n-\t\t\tbreak;\n-\t\tcase 'r':\n-\t\t\trepeats = atoi(optarg);\n-\t\t\tbreak;\n-\t\tcase 'n':\n-\t\t\tnr_pages = atoi(optarg);\n-\t\t\tif (nr_pages \u003c 0)\n-\t\t\t\tnr_pages = size / psize();\n-\t\t\tbreak;\n-\t\tcase 't':\n-\t\t\tthp = 1;\n-\t\t\tbreak;\n-\t\tcase 'T':\n-\t\t\tthp = 0;\n-\t\t\tbreak;\n-\t\tcase 'U':\n-\t\t\tcmd = GUP_BASIC_TEST;\n-\t\t\tbreak;\n-\t\tcase 'u':\n-\t\t\tcmd = GUP_FAST_BENCHMARK;\n-\t\t\tbreak;\n-\t\tcase 'w':\n-\t\t\twrite = 1;\n-\t\t\tbreak;\n-\t\tcase 'W':\n-\t\t\twrite = 0;\n-\t\t\tbreak;\n-\t\tcase 'f':\n-\t\t\tfile = optarg;\n-\t\t\tbreak;\n-\t\tcase 'S':\n-\t\t\tflags \u0026= ~MAP_PRIVATE;\n-\t\t\tflags |= MAP_SHARED;\n-\t\t\tbreak;\n-\t\tcase 'H':\n-\t\t\tflags |= (MAP_HUGETLB | MAP_ANONYMOUS);\n-\t\t\thugetlb = true;\n-\t\t\tbreak;\n-\t\tdefault:\n-\t\t\tksft_exit_fail_msg(\"Wrong argument\\n\");\n-\t\t}\n+\tfor (i = 0; i \u003c (int)ARRAY_SIZE(nr_pages_list); i++) {\n+\t\tstruct gup_test gup = { 0 };\n+\n+\t\tgup.addr = (unsigned long)self-\u003eaddr;\n+\t\tgup.size = self-\u003esize;\n+\t\tgup.nr_pages_per_call = nr_pages_list[i] \u003c 0 ?\n+\t\t\tself-\u003esize / psize() : nr_pages_list[i];\n+\n+\t\tif (variant-\u003ewrite)\n+\t\t\tgup.gup_flags |= FOLL_WRITE;\n+\n+\t\tgup.which_pages[0] = 1;\n+\t\tgup.test_flags |= GUP_TEST_FLAG_DUMP_PAGES_USE_PIN;\n+\n+\t\tTH_LOG(\"nr_pages_per_call=%u\", gup.nr_pages_per_call);\n+\t\tASSERT_EQ(ioctl(self-\u003egup_fd, DUMP_USER_PAGES_TEST, \u0026gup), 0);\n \t}\n+}\n \n-\tif (optind \u003c argc) {\n-\t\tint extra_arg_count = 0;\n-\t\t/*\n-\t\t * For example:\n-\t\t *\n-\t\t *   ./gup_test -c 0 1 0x1001\n-\t\t *\n-\t\t * ...to dump pages 0, 1, and 4097\n-\t\t */\n-\n-\t\twhile ((optind \u003c argc) \u0026\u0026\n-\t\t       (extra_arg_count \u003c GUP_TEST_MAX_PAGES_TO_DUMP)) {\n-\t\t\t/*\n-\t\t\t * Do the 1-based indexing here, so that the user can\n-\t\t\t * use normal 0-based indexing on the command line.\n-\t\t\t */\n-\t\t\tlong page_index = strtol(argv[optind], 0, 0) + 1;\n-\n-\t\t\tgup.which_pages[extra_arg_count] = page_index;\n-\t\t\textra_arg_count++;\n-\t\t\toptind++;\n-\t\t}\n+TEST_F(gup_test, get_user_pages_fast)\n+{\n+\t/* Tests the lockless get_user_pages_fast() path */\n+\tint i;\n+\n+\tfor (i = 0; i \u003c (int)ARRAY_SIZE(nr_pages_list); i++) {\n+\t\tstruct gup_test gup = { 0 };\n+\n+\t\tgup.addr = (unsigned long)self-\u003eaddr;\n+\t\tgup.size = self-\u003esize;\n+\t\tgup.nr_pages_per_call = nr_pages_list[i] \u003c 0 ?\n+\t\t\tself-\u003esize / psize() : nr_pages_list[i];\n+\n+\t\tif (variant-\u003ewrite)\n+\t\t\tgup.gup_flags |= FOLL_WRITE;\n+\n+\t\tTH_LOG(\"nr_pages_per_call=%u\", gup.nr_pages_per_call);\n+\t\tASSERT_EQ(ioctl(self-\u003egup_fd, GUP_FAST_BENCHMARK, \u0026gup), 0);\n \t}\n+}\n+\n+TEST_F(gup_test, pin_user_pages_fast)\n+{\n+\t/* Tests the lockless pin_user_pages_fast() path */\n+\tint i;\n \n-\tksft_print_header();\n+\tfor (i = 0; i \u003c (int)ARRAY_SIZE(nr_pages_list); i++) {\n+\t\tstruct gup_test gup = { 0 };\n \n-\tif (hugetlb) {\n-\t\tunsigned long hp_size = default_huge_page_size();\n+\t\tgup.addr = (unsigned long)self-\u003eaddr;\n+\t\tgup.size = self-\u003esize;\n+\t\tgup.nr_pages_per_call = nr_pages_list[i] \u003c 0 ?\n+\t\t\tself-\u003esize / psize() : nr_pages_list[i];\n \n-\t\tif (!hp_size)\n-\t\t\tksft_exit_skip(\"HugeTLB is unavailable\\n\");\n+\t\tif (variant-\u003ewrite)\n+\t\t\tgup.gup_flags |= FOLL_WRITE;\n \n-\t\tsize = (size + hp_size - 1) \u0026 ~(hp_size - 1);\n-\t\tif (!hugetlb_setup_default(size / hp_size))\n-\t\t\tksft_exit_skip(\"Not enough huge pages\\n\");\n+\t\tTH_LOG(\"nr_pages_per_call=%u\", gup.nr_pages_per_call);\n+\t\tASSERT_EQ(ioctl(self-\u003egup_fd, PIN_FAST_BENCHMARK, \u0026gup), 0);\n \t}\n+}\n \n-\tksft_set_plan(nthreads);\n+TEST_F(gup_test, pin_user_pages_longterm)\n+{\n+\t/* Tests pin_user_pages() with FOLL_LONGTERM */\n+\tint i;\n \n-\tfiled = open(file, O_RDWR|O_CREAT, 0664);\n-\tif (filed \u003c 0)\n-\t\tksft_exit_fail_msg(\"Unable to open %s: %s\\n\", file, strerror(errno));\n+\tfor (i = 0; i \u003c (int)ARRAY_SIZE(nr_pages_list); i++) {\n+\t\tstruct gup_test gup = { 0 };\n \n-\tgup.nr_pages_per_call = nr_pages;\n-\tif (write)\n-\t\tgup.gup_flags |= FOLL_WRITE;\n+\t\tgup.addr = (unsigned long)self-\u003eaddr;\n+\t\tgup.size = self-\u003esize;\n+\t\tgup.nr_pages_per_call = nr_pages_list[i] \u003c 0 ?\n+\t\t\tself-\u003esize / psize() : nr_pages_list[i];\n \n-\tgup_fd = open(GUP_TEST_FILE, O_RDWR);\n-\tif (gup_fd == -1) {\n-\t\tswitch (errno) {\n-\t\tcase EACCES:\n-\t\t\tif (getuid())\n-\t\t\t\tksft_print_msg(\"Please run this test as root\\n\");\n-\t\t\tbreak;\n-\t\tcase ENOENT:\n-\t\t\tif (opendir(\"/sys/kernel/debug\") == NULL)\n-\t\t\t\tksft_print_msg(\"mount debugfs at /sys/kernel/debug\\n\");\n-\t\t\tksft_print_msg(\"check if CONFIG_GUP_TEST is enabled in kernel config\\n\");\n-\t\t\tbreak;\n-\t\tdefault:\n-\t\t\tksft_print_msg(\"failed to open %s: %s\\n\", GUP_TEST_FILE, strerror(errno));\n-\t\t\tbreak;\n-\t\t}\n-\t\tksft_test_result_skip(\"Please run this test as root\\n\");\n-\t\tksft_exit_pass();\n+\t\tif (variant-\u003ewrite)\n+\t\t\tgup.gup_flags |= FOLL_WRITE;\n+\n+\t\tTH_LOG(\"nr_pages_per_call=%u\", gup.nr_pages_per_call);\n+\t\tASSERT_EQ(ioctl(self-\u003egup_fd, PIN_LONGTERM_BENCHMARK, \u0026gup), 0);\n \t}\n+}\n+\n+TEST(dump_user_pages_sparse_indices)\n+{\n+\t/* Tests sparse multi-index which_pages[] inputs. */\n+\tstruct gup_test gup = { 0 };\n+\tunsigned long size = 128 * MB;\n+\tint zero_fd, gup_fd;\n+\tchar *addr, *p;\n+\n+\tzero_fd = open(\"/dev/zero\", O_RDWR);\n+\tASSERT_GE(zero_fd, 0);\n \n-\tp = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, filed, 0);\n-\tif (p == MAP_FAILED)\n-\t\tksft_exit_fail_msg(\"mmap: %s\\n\", strerror(errno));\n-\tgup.addr = (unsigned long)p;\n+\tgup_fd = open(GUP_TEST_FILE, O_RDWR);\n+\tASSERT_GE(gup_fd, 0);\n+\n+\taddr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, zero_fd, 0);\n+\tclose(zero_fd);\n+\tASSERT_NE(addr, MAP_FAILED);\n \n-\tif (thp == 1)\n-\t\tmadvise(p, size, MADV_HUGEPAGE);\n-\telse if (thp == 0)\n-\t\tmadvise(p, size, MADV_NOHUGEPAGE);\n+\tmadvise(addr, size, MADV_HUGEPAGE);\n \n-\t/* Fault them in here, from user space. */\n-\tfor (; (unsigned long)p \u003c gup.addr + size; p += psize())\n+\tfor (p = addr; (unsigned long)p \u003c (unsigned long)addr + size;\n+\t     p += psize())\n \t\tp[0] = 0;\n \n-\ttid = malloc(sizeof(pthread_t) * nthreads);\n-\tassert(tid);\n-\tfor (i = 0; i \u003c nthreads; i++) {\n-\t\tret = pthread_create(\u0026tid[i], NULL, gup_thread, \u0026gup);\n-\t\tassert(ret == 0);\n-\t}\n-\tfor (i = 0; i \u003c nthreads; i++) {\n-\t\tret = pthread_join(tid[i], NULL);\n-\t\tassert(ret == 0);\n+\tgup.addr = (unsigned long)addr;\n+\tgup.size = size;\n+\tgup.nr_pages_per_call = size / psize();\n+\tgup.gup_flags = FOLL_WRITE;\n+\tgup.which_pages[0] = 1;\n+\tgup.which_pages[1] = 20;\n+\tgup.which_pages[2] = 0x1001;\n+\n+\t/*\n+\t * Preserve the old \"./gup_test -ct -F 0x1 0 19 0x1000\" sparse dump\n+\t * coverage after removing command-line parsing from this binary.\n+\t */\n+\tASSERT_EQ(ioctl(gup_fd, DUMP_USER_PAGES_TEST, \u0026gup), 0);\n+\n+\tmunmap(addr, size);\n+\tclose(gup_fd);\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tint fd;\n+\tchar *file = \"/dev/zero\";\n+\n+\tfd = open(file, O_RDWR);\n+\tif (fd \u003c 0) {\n+\t\tksft_print_header();\n+\t\tksft_exit_fail_msg(\"Unable to open %s: %s\\n\", file, strerror(errno));\n \t}\n+\tclose(fd);\n \n-\tfree(tid);\n+\tfd = open(GUP_TEST_FILE, O_RDWR);\n+\tif (fd == -1) {\n+\t\tksft_print_header();\n+\t\tif (errno == EACCES)\n+\t\t\tksft_exit_skip(\"Please run this test as root\\n\");\n+\t\tif (errno == ENOENT) {\n+\t\t\tDIR *debugfs = opendir(\"/sys/kernel/debug\");\n+\n+\t\t\tif (!debugfs) {\n+\t\t\t\tksft_exit_skip(\"Mount debugfs at /sys/kernel/debug\\n\");\n+\t\t\t} else {\n+\t\t\t\tclosedir(debugfs);\n+\t\t\t\tksft_exit_skip(\"Check CONFIG_GUP_TEST in kernel config\\n\");\n+\t\t\t}\n+\t\t}\n+\t\tksft_exit_skip(\"failed to open %s: %s\\n\", GUP_TEST_FILE, strerror(errno));\n+\t}\n+\tclose(fd);\n \n-\tksft_exit_pass();\n+\treturn test_harness_run(argc, argv);\n }\ndiff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c\nindex 2f2b9879d10051..e1bb031019f2ac 100644\n--- a/tools/testing/selftests/mm/hmm-tests.c\n+++ b/tools/testing/selftests/mm/hmm-tests.c\n@@ -10,9 +10,6 @@\n  * bugs.\n  */\n \n-#include \"kselftest_harness.h\"\n-#include \"hugepage_settings.h\"\n-\n #include \u003cerrno.h\u003e\n #include \u003cfcntl.h\u003e\n #include \u003cstdio.h\u003e\n@@ -29,6 +26,9 @@\n #include \u003csys/mman.h\u003e\n #include \u003csys/ioctl.h\u003e\n #include \u003csys/time.h\u003e\n+#include \u003cmm/hugepage_settings.h\u003e\n+\n+#include \"kselftest_harness.h\"\n \n /*\n  * This is a private UAPI to the kernel test module so it isn't exported\ndiff --git a/tools/testing/selftests/mm/hugetlb-madvise.c b/tools/testing/selftests/mm/hugetlb-madvise.c\nindex 555b4b3d14307e..57cf790ca478d4 100644\n--- a/tools/testing/selftests/mm/hugetlb-madvise.c\n+++ b/tools/testing/selftests/mm/hugetlb-madvise.c\n@@ -14,7 +14,6 @@\n #include \u003cfcntl.h\u003e\n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n #define MIN_FREE_PAGES\t20\n #define NR_HUGE_PAGES\t10\t/* common number of pages to map/allocate */\ndiff --git a/tools/testing/selftests/mm/hugetlb-mmap.c b/tools/testing/selftests/mm/hugetlb-mmap.c\nindex 0f2aad1b7dbd6f..a458becf58327f 100644\n--- a/tools/testing/selftests/mm/hugetlb-mmap.c\n+++ b/tools/testing/selftests/mm/hugetlb-mmap.c\n@@ -18,7 +18,6 @@\n #include \u003clinux/memfd.h\u003e\n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n #define LENGTH (256UL*1024*1024)\n #define PROTECTION (PROT_READ | PROT_WRITE)\ndiff --git a/tools/testing/selftests/mm/hugetlb-mremap.c b/tools/testing/selftests/mm/hugetlb-mremap.c\nindex ed3d92e862d876..9b724af66e9388 100644\n--- a/tools/testing/selftests/mm/hugetlb-mremap.c\n+++ b/tools/testing/selftests/mm/hugetlb-mremap.c\n@@ -26,7 +26,6 @@\n #include \u003cstdbool.h\u003e\n #include \"kselftest.h\"\n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n #define DEFAULT_LENGTH_MB 10UL\n #define MB_TO_BYTES(x) (x * 1024 * 1024)\ndiff --git a/tools/testing/selftests/mm/hugetlb-shm.c b/tools/testing/selftests/mm/hugetlb-shm.c\nindex 3ff7f062b7eb45..f4514da49e1df7 100644\n--- a/tools/testing/selftests/mm/hugetlb-shm.c\n+++ b/tools/testing/selftests/mm/hugetlb-shm.c\n@@ -29,7 +29,6 @@\n #include \u003csys/mman.h\u003e\n \n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n #define LENGTH (256UL*1024*1024)\n \ndiff --git a/tools/testing/selftests/mm/hugetlb-soft-offline.c b/tools/testing/selftests/mm/hugetlb-soft-offline.c\nindex bc202e4ed2bda7..20864e7d4e0c72 100644\n--- a/tools/testing/selftests/mm/hugetlb-soft-offline.c\n+++ b/tools/testing/selftests/mm/hugetlb-soft-offline.c\n@@ -21,9 +21,9 @@\n #include \u003csys/mman.h\u003e\n #include \u003csys/statfs.h\u003e\n #include \u003csys/types.h\u003e\n+#include \u003cmm/hugepage_settings.h\u003e\n \n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n #ifndef MADV_SOFT_OFFLINE\n #define MADV_SOFT_OFFLINE 101\ndiff --git a/tools/testing/selftests/mm/hugetlb-vmemmap.c b/tools/testing/selftests/mm/hugetlb-vmemmap.c\nindex 507df78a158d8d..c46a656c25a04e 100644\n--- a/tools/testing/selftests/mm/hugetlb-vmemmap.c\n+++ b/tools/testing/selftests/mm/hugetlb-vmemmap.c\n@@ -11,7 +11,6 @@\n #include \u003csys/mman.h\u003e\n #include \u003cfcntl.h\u003e\n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n #define PAGE_COMPOUND_HEAD\t(1UL \u003c\u003c 15)\n #define PAGE_COMPOUND_TAIL\t(1UL \u003c\u003c 16)\ndiff --git a/tools/testing/selftests/mm/hugetlb_dio.c b/tools/testing/selftests/mm/hugetlb_dio.c\nindex fb4600570e1319..9495974eccbea5 100644\n--- a/tools/testing/selftests/mm/hugetlb_dio.c\n+++ b/tools/testing/selftests/mm/hugetlb_dio.c\n@@ -20,7 +20,6 @@\n #include \u003csys/syscall.h\u003e\n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n #ifndef STATX_DIOALIGN\n #define STATX_DIOALIGN\t\t0x00002000U\ndiff --git a/tools/testing/selftests/mm/hugetlb_fault_after_madv.c b/tools/testing/selftests/mm/hugetlb_fault_after_madv.c\nindex 2dc158054f666b..56c5a8533e9d90 100644\n--- a/tools/testing/selftests/mm/hugetlb_fault_after_madv.c\n+++ b/tools/testing/selftests/mm/hugetlb_fault_after_madv.c\n@@ -10,7 +10,6 @@\n \n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n #define INLOOP_ITER 100\n \ndiff --git a/tools/testing/selftests/mm/hugetlb_madv_vs_map.c b/tools/testing/selftests/mm/hugetlb_madv_vs_map.c\nindex f94549efcc6ff3..2532a42b98dff1 100644\n--- a/tools/testing/selftests/mm/hugetlb_madv_vs_map.c\n+++ b/tools/testing/selftests/mm/hugetlb_madv_vs_map.c\n@@ -25,7 +25,6 @@\n #include \u003cunistd.h\u003e\n \n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n #define INLOOP_ITER 100\n \ndiff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c\nindex 10e8dedcb087dc..2382973b2f552c 100644\n--- a/tools/testing/selftests/mm/khugepaged.c\n+++ b/tools/testing/selftests/mm/khugepaged.c\n@@ -22,7 +22,6 @@\n #include \"linux/magic.h\"\n \n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n #define BASE_ADDR ((void *)(1UL \u003c\u003c 30))\n static unsigned long hpage_pmd_size;\n@@ -119,6 +118,7 @@ static void get_finfo(const char *dir)\n \tchar buf[1 \u003c\u003c 10];\n \tchar path[PATH_MAX];\n \tchar *str, *end;\n+\tint ret;\n \n \tfinfo.dir = dir;\n \tstat(finfo.dir, \u0026path_stat);\n@@ -138,8 +138,9 @@ static void get_finfo(const char *dir)\n \t\t     major(path_stat.st_dev), minor(path_stat.st_dev))\n \t    \u003e= sizeof(path))\n \t\tksft_exit_fail_msg(\"%s: Pathname is too long\\n\", __func__);\n-\tif (read_file(path, buf, sizeof(buf)) \u003c 0)\n-\t\tksft_exit_fail_perror(\"read_file(read_num)\");\n+\tret = read_file(path, buf, sizeof(buf));\n+\tif (ret \u003c 0)\n+\t\tksft_exit_fail();\n \tif (strstr(buf, \"DEVTYPE=disk\")) {\n \t\t/* Found it */\n \t\tif (snprintf(finfo.dev_queue_read_ahead_path,\n@@ -319,7 +320,7 @@ static void *file_setup_area_common(int nr_hpages, enum file_setup_ops setup)\n {\n \tconst int open_opt = setup == FILE_SETUP_READ_ONLY_FS ? O_RDONLY : O_RDWR;\n \tconst int mmap_prot = setup == FILE_SETUP_READ_ONLY_FS ? PROT_READ : (PROT_READ | PROT_WRITE);\n-\tint fd;\n+\tint fd, ret;\n \tvoid *p;\n \tunsigned long size;\n \n@@ -363,7 +364,10 @@ static void *file_setup_area_common(int nr_hpages, enum file_setup_ops setup)\n \t\tksft_exit_fail_perror(\"mmap()\");\n \n \t/* Drop page cache */\n-\twrite_file(\"/proc/sys/vm/drop_caches\", \"3\", 2);\n+\tret = write_file(\"/proc/sys/vm/drop_caches\", \"3\", 2);\n+\tif (ret)\n+\t\tksft_exit_fail();\n+\n \tsuccess(\"OK\");\n \treturn p;\n }\ndiff --git a/tools/testing/selftests/mm/ksm_tests.c b/tools/testing/selftests/mm/ksm_tests.c\nindex a050f4840cfa3a..cc37492cf32c3d 100644\n--- a/tools/testing/selftests/mm/ksm_tests.c\n+++ b/tools/testing/selftests/mm/ksm_tests.c\n@@ -15,7 +15,6 @@\n #include \"kselftest.h\"\n #include \u003cinclude/vdso/time64.h\u003e\n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n #define KSM_SYSFS_PATH \"/sys/kernel/mm/ksm/\"\n #define KSM_FP(s) (KSM_SYSFS_PATH s)\ndiff --git a/tools/testing/selftests/mm/migration.c b/tools/testing/selftests/mm/migration.c\nindex 29f7492453d439..4fe97c03326124 100644\n--- a/tools/testing/selftests/mm/migration.c\n+++ b/tools/testing/selftests/mm/migration.c\n@@ -4,9 +4,6 @@\n  * paths in the kernel.\n  */\n \n-#include \"kselftest_harness.h\"\n-#include \"hugepage_settings.h\"\n-\n #include \u003cstrings.h\u003e\n #include \u003cpthread.h\u003e\n #include \u003cnuma.h\u003e\n@@ -16,6 +13,8 @@\n #include \u003csys/types.h\u003e\n #include \u003csignal.h\u003e\n #include \u003ctime.h\u003e\n+\n+#include \"kselftest_harness.h\"\n #include \"vm_util.h\"\n \n #define TWOMEG\t\t(2\u003c\u003c20)\ndiff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c\nindex 6f8971d5b3ce43..804372803080cc 100644\n--- a/tools/testing/selftests/mm/pagemap_ioctl.c\n+++ b/tools/testing/selftests/mm/pagemap_ioctl.c\n@@ -23,7 +23,6 @@\n \n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n #define PAGEMAP_BITS_ALL\t\t(PAGE_IS_WPALLOWED | PAGE_IS_WRITTEN |\t\\\n \t\t\t\t\t PAGE_IS_FILE | PAGE_IS_PRESENT |\t\\\ndiff --git a/tools/testing/selftests/mm/prctl_thp_disable.c b/tools/testing/selftests/mm/prctl_thp_disable.c\nindex d8d9d1de57b8b0..a4f8451791cbdb 100644\n--- a/tools/testing/selftests/mm/prctl_thp_disable.c\n+++ b/tools/testing/selftests/mm/prctl_thp_disable.c\n@@ -14,7 +14,6 @@\n #include \u003csys/wait.h\u003e\n \n #include \"kselftest_harness.h\"\n-#include \"hugepage_settings.h\"\n #include \"vm_util.h\"\n \n #ifndef PR_THP_DISABLE_EXCEPT_ADVISED\ndiff --git a/tools/testing/selftests/mm/protection_keys.c b/tools/testing/selftests/mm/protection_keys.c\nindex 9a6d954ee37126..5ba2033e8a09cd 100644\n--- a/tools/testing/selftests/mm/protection_keys.c\n+++ b/tools/testing/selftests/mm/protection_keys.c\n@@ -45,8 +45,8 @@\n #include \u003cunistd.h\u003e\n #include \u003csys/ptrace.h\u003e\n #include \u003csetjmp.h\u003e\n+#include \u003cmm/hugepage_settings.h\u003e\n \n-#include \"hugepage_settings.h\"\n #include \"pkey-helpers.h\"\n \n int iteration_nr = 1;\ndiff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh\nindex 8c296dedf0474d..56df0dcc9640c0 100755\n--- a/tools/testing/selftests/mm/run_vmtests.sh\n+++ b/tools/testing/selftests/mm/run_vmtests.sh\n@@ -130,30 +130,6 @@ test_selected() {\n \tfi\n }\n \n-run_gup_matrix() {\n-    # -t: thp=on, -T: thp=off, -H: hugetlb=on\n-    local hugetlb_mb=256\n-\n-    for huge in -t -T \"-H -m $hugetlb_mb\"; do\n-        # -u: gup-fast, -U: gup-basic, -a: pin-fast, -b: pin-basic, -L: pin-longterm\n-        for test_cmd in -u -U -a -b -L; do\n-            # -w: write=1, -W: write=0\n-            for write in -w -W; do\n-                # -S: shared\n-                for share in -S \" \"; do\n-                    # -n: How many pages to fetch together?  512 is special\n-                    # because it's default thp size (or 2M on x86), 123 to\n-                    # just test partial gup when hit a huge in whatever form\n-                    for num in \"-n 1\" \"-n 512\" \"-n 123\" \"-n -1\"; do\n-                        CATEGORY=\"gup_test\" run_test ./gup_test \\\n-                                $huge $test_cmd $write $share $num\n-                    done\n-                done\n-            done\n-        done\n-    done\n-}\n-\n # filter 64bit architectures\n ARCH64STR=\"arm64 mips64 parisc64 ppc64 ppc64le riscv64 s390x sparc64 x86_64\"\n if [ -z \"$ARCH\" ]; then\n@@ -276,18 +252,7 @@ fi\n \n CATEGORY=\"mmap\" run_test ./map_fixed_noreplace\n \n-if $RUN_ALL; then\n-    run_gup_matrix\n-else\n-    # get_user_pages_fast() benchmark\n-    CATEGORY=\"gup_test\" run_test ./gup_test -u -n 1\n-    CATEGORY=\"gup_test\" run_test ./gup_test -u -n -1\n-    # pin_user_pages_fast() benchmark\n-    CATEGORY=\"gup_test\" run_test ./gup_test -a -n 1\n-    CATEGORY=\"gup_test\" run_test ./gup_test -a -n -1\n-fi\n-# Dump pages 0, 19, and 4096, using pin_user_pages:\n-CATEGORY=\"gup_test\" run_test ./gup_test -ct -F 0x1 0 19 0x1000\n+CATEGORY=\"gup_test\" run_test ./gup_test\n CATEGORY=\"gup_test\" run_test ./gup_longterm\n \n CATEGORY=\"userfaultfd\" run_test ./uffd-unit-tests\ndiff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c\nindex fb1864a68e1c6b..6c22ac9e93db06 100644\n--- a/tools/testing/selftests/mm/soft-dirty.c\n+++ b/tools/testing/selftests/mm/soft-dirty.c\n@@ -9,7 +9,6 @@\n \n #include \"kselftest.h\"\n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n #define PAGEMAP_FILE_PATH \"/proc/self/pagemap\"\n #define TEST_ITERATIONS 10000\ndiff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c\nindex 32b991472f744f..e744c9434e7d05 100644\n--- a/tools/testing/selftests/mm/split_huge_page_test.c\n+++ b/tools/testing/selftests/mm/split_huge_page_test.c\n@@ -21,7 +21,6 @@\n #include \u003ctime.h\u003e\n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n uint64_t pagesize;\n unsigned int pageshift;\n@@ -269,7 +268,9 @@ static void write_debugfs(const char *fmt, ...)\n \tif (ret \u003e= INPUT_MAX)\n \t\tksft_exit_fail_msg(\"%s: Debugfs input is too long\\n\", __func__);\n \n-\twrite_file(SPLIT_DEBUGFS, input, ret + 1);\n+\tret = write_file(SPLIT_DEBUGFS, input, ret + 1);\n+\tif (ret)\n+\t\tksft_exit_fail();\n }\n \n static char *allocate_zero_filled_hugepage(size_t len)\ndiff --git a/tools/testing/selftests/mm/thuge-gen.c b/tools/testing/selftests/mm/thuge-gen.c\nindex 22b9c2f1c35d23..0da15d530a1fc8 100644\n--- a/tools/testing/selftests/mm/thuge-gen.c\n+++ b/tools/testing/selftests/mm/thuge-gen.c\n@@ -14,7 +14,6 @@\n #include \u003cstring.h\u003e\n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n #if !defined(MAP_HUGETLB)\n #define MAP_HUGETLB\t0x40000\ndiff --git a/tools/testing/selftests/mm/transhuge-stress.c b/tools/testing/selftests/mm/transhuge-stress.c\nindex 8eb0c5630e7e31..96f72898ebe0a5 100644\n--- a/tools/testing/selftests/mm/transhuge-stress.c\n+++ b/tools/testing/selftests/mm/transhuge-stress.c\n@@ -17,7 +17,6 @@\n #include \u003csys/mman.h\u003e\n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n int backing_fd = -1;\n int mmap_flags = MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE;\ndiff --git a/tools/testing/selftests/mm/uffd-common.h b/tools/testing/selftests/mm/uffd-common.h\nindex 92a21b97f745af..0723843a7626b1 100644\n--- a/tools/testing/selftests/mm/uffd-common.h\n+++ b/tools/testing/selftests/mm/uffd-common.h\n@@ -37,7 +37,6 @@\n \n #include \"kselftest.h\"\n #include \"vm_util.h\"\n-#include \"hugepage_settings.h\"\n \n #define UFFD_FLAGS\t(O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY)\n \ndiff --git a/tools/testing/selftests/mm/uffd-wp-mremap.c b/tools/testing/selftests/mm/uffd-wp-mremap.c\nindex c973d6722720c3..eb4b2433b00e61 100644\n--- a/tools/testing/selftests/mm/uffd-wp-mremap.c\n+++ b/tools/testing/selftests/mm/uffd-wp-mremap.c\n@@ -7,8 +7,8 @@\n #include \u003cassert.h\u003e\n #include \u003clinux/mman.h\u003e\n #include \u003csys/mman.h\u003e\n+#include \u003cmm/hugepage_settings.h\u003e\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n #include \"uffd-common.h\"\n \n static int pagemap_fd;\ndiff --git a/tools/testing/selftests/mm/va_high_addr_switch.c b/tools/testing/selftests/mm/va_high_addr_switch.c\nindex e24d7ba00b4417..5a354a664d1f7d 100644\n--- a/tools/testing/selftests/mm/va_high_addr_switch.c\n+++ b/tools/testing/selftests/mm/va_high_addr_switch.c\n@@ -11,7 +11,6 @@\n \n #include \"vm_util.h\"\n #include \"kselftest.h\"\n-#include \"hugepage_settings.h\"\n \n /*\n  * The hint addr value is used to allocate addresses\ndiff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c\nindex 311fc5b4513eb4..f276eee576804a 100644\n--- a/tools/testing/selftests/mm/vm_util.c\n+++ b/tools/testing/selftests/mm/vm_util.c\n@@ -698,93 +698,39 @@ int unpoison_memory(unsigned long pfn)\n \treturn ret \u003e 0 ? 0 : -errno;\n }\n \n-int read_file(const char *path, char *buf, size_t buflen)\n-{\n-\tint fd;\n-\tssize_t numread;\n-\n-\tfd = open(path, O_RDONLY);\n-\tif (fd == -1)\n-\t\treturn 0;\n-\n-\tnumread = read(fd, buf, buflen - 1);\n-\tif (numread \u003c 1) {\n-\t\tclose(fd);\n-\t\treturn 0;\n-\t}\n-\n-\tbuf[numread] = '\\0';\n-\tclose(fd);\n-\n-\treturn (unsigned int) numread;\n-}\n-\n-void write_file(const char *path, const char *buf, size_t buflen)\n-{\n-\tint fd, saved_errno;\n-\tssize_t numwritten;\n-\n-\tif (buflen \u003c 2)\n-\t\tksft_exit_fail_msg(\"Incorrect buffer len: %zu\\n\", buflen);\n-\n-\tfd = open(path, O_WRONLY);\n-\tif (fd == -1)\n-\t\tksft_exit_fail_msg(\"%s open failed: %s\\n\", path, strerror(errno));\n-\n-\tnumwritten = write(fd, buf, buflen - 1);\n-\tsaved_errno = errno;\n-\tclose(fd);\n-\terrno = saved_errno;\n-\tif (numwritten \u003c 0)\n-\t\tksft_exit_fail_msg(\"%s write(%.*s) failed: %s\\n\", path, (int)(buflen - 1),\n-\t\t\t\tbuf, strerror(errno));\n-\tif (numwritten != buflen - 1)\n-\t\tksft_exit_fail_msg(\"%s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\\n\",\n-\t\t\t\tpath, (int)(buflen - 1), buf, buflen - 1, numwritten);\n-}\n-\n-unsigned long read_num(const char *path)\n-{\n-\tchar buf[21];\n-\n-\tif (read_file(path, buf, sizeof(buf)) \u003c 0)\n-\t\tksft_exit_fail_perror(\"read_file()\");\n-\n-\treturn strtoul(buf, NULL, 10);\n-}\n-\n-void write_num(const char *path, unsigned long num)\n-{\n-\tchar buf[21];\n-\n-\tsprintf(buf, \"%lu\", num);\n-\twrite_file(path, buf, strlen(buf) + 1);\n-}\n-\n static unsigned long shmall, shmmax;\n \n void __shm_limits_restore(void)\n {\n-\tif (shmmax)\n-\t\twrite_num(\"/proc/sys/kernel/shmmax\", shmmax);\n-\tif (shmall)\n-\t\twrite_num(\"/proc/sys/kernel/shmall\", shmall);\n+\tif (shmmax \u0026\u0026 write_num(\"/proc/sys/kernel/shmmax\", shmmax))\n+\t\tksft_exit_fail();\n+\tif (shmall \u0026\u0026 write_num(\"/proc/sys/kernel/shmall\", shmall))\n+\t\tksft_exit_fail();\n }\n \n void shm_limits_prepare(unsigned long length)\n {\n \tunsigned long nr = length / psize();\n \tunsigned long val;\n+\tint ret;\n \n-\tval = read_num(\"/proc/sys/kernel/shmmax\");\n+\tret = read_num(\"/proc/sys/kernel/shmmax\", \u0026val);\n+\tif (ret)\n+\t\tksft_exit_fail();\n \tif (val \u003c length) {\n-\t\twrite_num(\"/proc/sys/kernel/shmmax\", length);\n+\t\tret = write_num(\"/proc/sys/kernel/shmmax\", length);\n+\t\tif (ret)\n+\t\t\tksft_exit_fail();\n \t\tshmmax = val;\n \t}\n \n-\tval = read_num(\"/proc/sys/kernel/shmall\");\n+\tret = read_num(\"/proc/sys/kernel/shmall\", \u0026val);\n+\tif (ret)\n+\t\tksft_exit_fail();\n \tif (val \u003c nr) {\n-\t\twrite_num(\"/proc/sys/kernel/shmall\", nr);\n+\t\tret = write_num(\"/proc/sys/kernel/shmall\", nr);\n+\t\tif (ret)\n+\t\t\tksft_exit_fail();\n \t\tshmall = val;\n \t}\n }\ndiff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h\nindex ea8fc8fdf0eb0b..aa856f0d32d64e 100644\n--- a/tools/testing/selftests/mm/vm_util.h\n+++ b/tools/testing/selftests/mm/vm_util.h\n@@ -8,6 +8,8 @@\n #include \u003cunistd.h\u003e /* _SC_PAGESIZE */\n #include \"kselftest.h\"\n #include \u003clinux/fs.h\u003e\n+#include \u003cmm/file_utils.h\u003e\n+#include \u003cmm/hugepage_settings.h\u003e\n \n #define BIT_ULL(nr)                   (1ULL \u003c\u003c (nr))\n #define PM_SOFT_DIRTY                 BIT_ULL(55)\n@@ -164,11 +166,6 @@ int unpoison_memory(unsigned long pfn);\n #define PAGEMAP_PRESENT(ent)\t(((ent) \u0026 (1ull \u003c\u003c 63)) != 0)\n #define PAGEMAP_PFN(ent)\t((ent) \u0026 ((1ull \u003c\u003c 55) - 1))\n \n-void write_file(const char *path, const char *buf, size_t buflen);\n-int read_file(const char *path, char *buf, size_t buflen);\n-unsigned long read_num(const char *path);\n-void write_num(const char *path, unsigned long num);\n-\n void shm_limits_prepare(unsigned long length);\n void __shm_limits_restore(void);\n \n"
}

2/1 2026/07/16 20:29 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space tools (tools/mm/gup_bench.c, tools/testing/selftests/mm/), Documentation, and MAINTAINERS. It does not make any functional changes to the core kernel logic, drivers, or architectures.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a '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:
For your convenience, here is the diff of the changes:
commit b0a23e9979eedf4a6fb58ea1b376ca527572c4fc
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Jul 16 20:29:07 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/core-api/pin_user_pages.rst b/Documentation/core-api/pin_user_pages.rst
index c16ca163b55e3c..ea722adf22ccac 100644
--- a/Documentation/core-api/pin_user_pages.rst
+++ b/Documentation/core-api/pin_user_pages.rst
@@ -230,10 +230,16 @@ This file::
 
  tools/testing/selftests/mm/gup_test.c
 
-has the following new calls to exercise the new pin*() wrapper functions:
+contains the following test cases to exercise pin_user_pages*():
 
-* PIN_FAST_BENCHMARK (./gup_test -a)
-* PIN_BASIC_TEST (./gup_test -b)
+* pin_user_pages via PIN_BASIC_TEST
+* pin_user_pages_fast via PIN_FAST_BENCHMARK
+* pin_user_pages_longterm via PIN_LONGTERM_BENCHMARK
+
+Run with::
+
+  make -C tools/testing/selftests/mm
+  ./tools/testing/selftests/mm/gup_test
 
 You can monitor how many total dma-pinned pages have been acquired and released
 since the system was booted, via two new /proc/vmstat entries: ::
diff --git a/MAINTAINERS b/MAINTAINERS
index 806bd2d80d1535..7d121dee9f0f2c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17024,6 +17024,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
 F:	mm/gup.c
 F:	mm/gup_test.c
 F:	mm/gup_test.h
+F:	tools/mm/gup_bench.c
 F:	tools/testing/selftests/mm/gup_longterm.c
 F:	tools/testing/selftests/mm/gup_test.c
 
@@ -17128,6 +17129,7 @@ F:	mm/memory-tiers.c
 F:	mm/page_idle.c
 F:	mm/pgalloc-track.h
 F:	mm/process_vm_access.c
+F:	tools/lib/mm/
 F:	tools/testing/selftests/mm/
 
 MEMORY MANAGEMENT - NUMA MEMBLOCKS AND NUMA EMULATION
diff --git a/tools/lib/mm/file_utils.c b/tools/lib/mm/file_utils.c
new file mode 100644
index 00000000000000..792f4b4db6252e
--- /dev/null
+++ b/tools/lib/mm/file_utils.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "file_utils.h"
+
+int read_file(const char *path, char *buf, size_t buflen)
+{
+	int fd;
+	ssize_t numread;
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		int err = errno;
+
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
+
+	numread = read(fd, buf, buflen - 1);
+	if (numread < 1) {
+		int err = numread ? errno : ENODATA;
+
+		close(fd);
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
+
+	buf[numread] = '\0';
+	close(fd);
+
+	return (int)numread;
+}
+
+int write_file(const char *path, const char *buf, size_t buflen)
+{
+	int fd, saved_errno;
+	ssize_t numwritten;
+
+	if (buflen < 2) {
+		printf("# %s: %s (%d)\n", path, strerror(EINVAL), EINVAL);
+		return -EINVAL;
+	}
+
+	fd = open(path, O_WRONLY);
+	if (fd == -1) {
+		int err = errno;
+
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
+
+	numwritten = write(fd, buf, buflen - 1);
+	saved_errno = errno;
+	close(fd);
+	if (numwritten < 0) {
+		printf("# %s: %s (%d)\n", path, strerror(saved_errno),
+		       saved_errno);
+		return -saved_errno;
+	}
+	if (numwritten != (ssize_t)(buflen - 1)) {
+		printf("# %s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\n",
+		       path, (int)(buflen - 1), buf, buflen - 1, numwritten);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int read_num(const char *path, unsigned long *num)
+{
+	unsigned long val;
+	int err, ret;
+	char buf[21];
+	char *end;
+
+	if (!num)
+		return -EINVAL;
+
+	ret = read_file(path, buf, sizeof(buf));
+	if (ret < 0)
+		return ret;
+
+	errno = 0;
+	val = strtoul(buf, &end, 10);
+	if (errno) {
+		err = errno;
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
+
+	if (end == buf || buf[0] == '-') {
+		printf("# %s: invalid numeric value\n", path);
+		return -EINVAL;
+	}
+
+	if (*end == '\n')
+		end++;
+
+	if (*end != '\0') {
+		printf("# %s: invalid numeric value\n", path);
+		return -EINVAL;
+	}
+
+	*num = val;
+	return 0;
+}
+
+int write_num(const char *path, unsigned long num)
+{
+	char buf[21];
+
+	snprintf(buf, sizeof(buf), "%lu", num);
+	return write_file(path, buf, strlen(buf) + 1);
+}
diff --git a/tools/lib/mm/file_utils.h b/tools/lib/mm/file_utils.h
new file mode 100644
index 00000000000000..38576790a34244
--- /dev/null
+++ b/tools/lib/mm/file_utils.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __MM_FILE_UTILS_H__
+#define __MM_FILE_UTILS_H__
+
+#include <stddef.h>
+
+int read_file(const char *path, char *buf, size_t buflen);
+int write_file(const char *path, const char *buf, size_t buflen);
+int read_num(const char *path, unsigned long *num);
+int write_num(const char *path, unsigned long num);
+
+#endif
diff --git a/tools/testing/selftests/mm/hugepage_settings.c b/tools/lib/mm/hugepage_settings.c
similarity index 91%
rename from tools/testing/selftests/mm/hugepage_settings.c
rename to tools/lib/mm/hugepage_settings.c
index 2eab2110ac6a46..8d9e44727edbf5 100644
--- a/tools/testing/selftests/mm/hugepage_settings.c
+++ b/tools/lib/mm/hugepage_settings.c
@@ -9,11 +9,16 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "vm_util.h"
+#include "file_utils.h"
 #include "hugepage_settings.h"
 
 #define THP_SYSFS "/sys/kernel/mm/transparent_hugepage/"
 #define MAX_SETTINGS_DEPTH 4
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
 static struct thp_settings settings_stack[MAX_SETTINGS_DEPTH];
 static int settings_index;
 static struct thp_settings saved_settings;
@@ -61,10 +66,9 @@ int thp_read_string(const char *name, const char * const strings[])
 		exit(EXIT_FAILURE);
 	}
 
-	if (!read_file(path, buf, sizeof(buf))) {
-		perror(path);
+	ret = read_file(path, buf, sizeof(buf));
+	if (ret < 0)
 		exit(EXIT_FAILURE);
-	}
 
 	c = strchr(buf, '[');
 	if (!c) {
@@ -103,12 +107,15 @@ void thp_write_string(const char *name, const char *val)
 		printf("%s: Pathname is too long\n", __func__);
 		exit(EXIT_FAILURE);
 	}
-	write_file(path, val, strlen(val) + 1);
+	ret = write_file(path, val, strlen(val) + 1);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
 }
 
 unsigned long thp_read_num(const char *name)
 {
 	char path[PATH_MAX];
+	unsigned long num;
 	int ret;
 
 	ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
@@ -116,7 +123,11 @@ unsigned long thp_read_num(const char *name)
 		printf("%s: Pathname is too long\n", __func__);
 		exit(EXIT_FAILURE);
 	}
-	return read_num(path);
+	ret = read_num(path, &num);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	return num;
 }
 
 void thp_write_num(const char *name, unsigned long num)
@@ -129,7 +140,9 @@ void thp_write_num(const char *name, unsigned long num)
 		printf("%s: Pathname is too long\n", __func__);
 		exit(EXIT_FAILURE);
 	}
-	write_num(path, num);
+	ret = write_num(path, num);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
 }
 
 void thp_read_settings(struct thp_settings *settings)
@@ -157,8 +170,13 @@ void thp_read_settings(struct thp_settings *settings)
 		.max_ptes_shared = thp_read_num("khugepaged/max_ptes_shared"),
 		.pages_to_scan = thp_read_num("khugepaged/pages_to_scan"),
 	};
-	if (dev_queue_read_ahead_path[0])
-		settings->read_ahead_kb = read_num(dev_queue_read_ahead_path);
+	if (dev_queue_read_ahead_path[0]) {
+		int ret = read_num(dev_queue_read_ahead_path,
+				   &settings->read_ahead_kb);
+
+		if (ret < 0)
+			exit(EXIT_FAILURE);
+	}
 
 	for (i = 0; i < NR_ORDERS; i++) {
 		if (!((1 << i) & orders)) {
@@ -208,8 +226,13 @@ void thp_write_settings(struct thp_settings *settings)
 	thp_write_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared);
 	thp_write_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);
+	if (dev_queue_read_ahead_path[0]) {
+		int ret = write_num(dev_queue_read_ahead_path,
+				    settings->read_ahead_kb);
+
+		if (ret < 0)
+			exit(EXIT_FAILURE);
+	}
 
 	for (i = 0; i < NR_ORDERS; i++) {
 		if (!((1 << i) & orders))
@@ -306,8 +329,11 @@ static unsigned long __thp_supported_orders(bool is_shmem)
 			exit(EXIT_FAILURE);
 		}
 
+		if (access(path, F_OK))
+			continue;
+
 		ret = read_file(path, buf, sizeof(buf));
-		if (ret)
+		if (ret > 0)
 			orders |= 1UL << i;
 	}
 
@@ -382,8 +408,7 @@ int detect_hugetlb_page_sizes(unsigned long sizes[], int max)
 		if (sscanf(entry->d_name, "hugepages-%zukB", &kb) != 1)
 			continue;
 		sizes[count++] = kb * 1024;
-		ksft_print_msg("[INFO] detected hugetlb page size: %zu KiB\n",
-			       kb);
+		printf("# [INFO] detected hugetlb page size: %zu KiB\n", kb);
 	}
 	closedir(dir);
 	return count;
@@ -425,28 +450,43 @@ static void hugetlb_sysfs_path(char *buf, size_t buflen,
 unsigned long hugetlb_nr_pages(unsigned long size)
 {
 	char path[PATH_MAX];
+	unsigned long nr;
+	int ret;
 
 	hugetlb_sysfs_path(path, sizeof(path), size, "nr_hugepages");
 
-	return read_num(path);
+	ret = read_num(path, &nr);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	return nr;
 }
 
 void hugetlb_set_nr_pages(unsigned long size, unsigned long nr)
 {
 	char path[PATH_MAX];
+	int ret;
 
 	hugetlb_sysfs_path(path, sizeof(path), size, "nr_hugepages");
 
-	write_num(path, nr);
+	ret = write_num(path, nr);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
 }
 
 unsigned long hugetlb_free_pages(unsigned long size)
 {
 	char path[PATH_MAX];
+	unsigned long nr;
+	int ret;
 
 	hugetlb_sysfs_path(path, sizeof(path), size, "free_hugepages");
 
-	return read_num(path);
+	ret = read_num(path, &nr);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	return nr;
 }
 
 static bool __hugetlb_setup(unsigned long size, unsigned long nr)
@@ -502,7 +542,8 @@ unsigned long hugetlb_setup(unsigned long nr, unsigned long sizes[],
 		return 0;
 
 	if (nr_enabled > max) {
-		ksft_print_msg("detected %d huge page sizes, will only test %d\n", nr_enabled, max);
+		printf("# detected %d huge page sizes, will only test %d\n",
+		       nr_enabled, max);
 		nr_enabled = max;
 	}
 
@@ -574,8 +615,10 @@ static void hugepage_restore_settings_atexit(void)
 
 static void hugepage_restore_settings_sighandler(int sig)
 {
+	(void)sig;
+
 	/* exit() will invoke the hugepage_restore_settings_atexit handler. */
-	exit(KSFT_FAIL);
+	exit(EXIT_FAILURE);
 }
 
 void hugepage_save_settings(bool thp, bool hugetlb)
diff --git a/tools/testing/selftests/mm/hugepage_settings.h b/tools/lib/mm/hugepage_settings.h
similarity index 100%
rename from tools/testing/selftests/mm/hugepage_settings.h
rename to tools/lib/mm/hugepage_settings.h
diff --git a/tools/mm/.gitignore b/tools/mm/.gitignore
index 922879f93fc849..154d740be02e82 100644
--- a/tools/mm/.gitignore
+++ b/tools/mm/.gitignore
@@ -2,3 +2,5 @@
 slabinfo
 page-types
 page_owner_sort
+thp_swap_allocator_test
+gup_bench
diff --git a/tools/mm/Makefile b/tools/mm/Makefile
index f5725b5c23aa26..d82cc8c43ee02d 100644
--- a/tools/mm/Makefile
+++ b/tools/mm/Makefile
@@ -3,13 +3,14 @@
 #
 include ../scripts/Makefile.include
 
-BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test
+BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test gup_bench
 INSTALL_TARGETS = $(BUILD_TARGETS) thpmaps
 
 LIB_DIR = ../lib/api
 LIBS = $(LIB_DIR)/libapi.a
+GUP_BENCH_OBJS = gup_bench.c ../lib/mm/hugepage_settings.c ../lib/mm/file_utils.c
 
-CFLAGS += -Wall -Wextra -I../lib/ -pthread
+CFLAGS += -Wall -Wextra -I../lib/ -I../.. -pthread
 LDFLAGS += $(LIBS) -pthread
 
 all: $(BUILD_TARGETS)
@@ -22,8 +23,11 @@ $(LIBS):
 %: %.c
 	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
 
+gup_bench: $(GUP_BENCH_OBJS) $(LIBS)
+	$(CC) $(CFLAGS) -o $@ $(GUP_BENCH_OBJS) $(LDFLAGS)
+
 clean:
-	$(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test
+	$(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test gup_bench
 	make -C $(LIB_DIR) clean
 
 sbindir ?= /usr/sbin
diff --git a/tools/mm/gup_bench.c b/tools/mm/gup_bench.c
new file mode 100644
index 00000000000000..497b8f3f6ac4b4
--- /dev/null
+++ b/tools/mm/gup_bench.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Microbenchmark for get_user_pages (GUP) kernel interfaces.
+ *
+ * Exercises GUP_FAST_BENCHMARK, PIN_FAST_BENCHMARK, and
+ * PIN_LONGTERM_BENCHMARK via the CONFIG_GUP_TEST debugfs interface.
+ *
+ * Example use:
+ *   # Run the full matrix (all commands, access modes, page counts):
+ *   ./gup_bench
+ *
+ *   # Single run: pin_user_pages_fast, 512 pages, write access, hugetlb:
+ *   ./gup_bench -a -n 512 -w -H
+ *
+ * Requires CONFIG_GUP_TEST=y and debugfs mounted at /sys/kernel/debug.
+ * Must be run as root.
+ */
+
+#define __SANE_USERSPACE_TYPES__ // Use ll64
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+#include <stdint.h>
+#include <limits.h>
+#include <string.h>
+#include <mm/gup_test.h>
+#include <mm/hugepage_settings.h>
+
+#define MB (1UL << 20)
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
+/* Just the flags we need, copied from the kernel internals. */
+#define FOLL_WRITE	0x01	/* check pte is writable */
+
+#define GUP_TEST_FILE "/sys/kernel/debug/gup_test"
+
+static unsigned int psize(void)
+{
+	static unsigned int __page_size;
+
+	if (!__page_size)
+		__page_size = sysconf(_SC_PAGESIZE);
+	return __page_size;
+}
+
+static unsigned long cmd;
+static const char *bench_label;
+static int gup_fd, repeats = 1;
+static unsigned long size = 128 * MB;
+static atomic_int bench_error;
+/* Serialize prints */
+static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static const unsigned long bench_cmds[] = {
+	GUP_FAST_BENCHMARK,
+	PIN_FAST_BENCHMARK,
+	PIN_LONGTERM_BENCHMARK,
+};
+
+static const int bench_thp_modes[] = { 1, 0 };	/* on, off */
+static const int bench_nr_pages_list[] = { 1, 512, 123, -1 };
+
+static const char *cmd_to_str(unsigned long cmd)
+{
+	switch (cmd) {
+	case GUP_FAST_BENCHMARK:
+		return "GUP_FAST_BENCHMARK";
+	case PIN_FAST_BENCHMARK:
+		return "PIN_FAST_BENCHMARK";
+	case PIN_LONGTERM_BENCHMARK:
+		return "PIN_LONGTERM_BENCHMARK";
+	}
+	return "Unknown command";
+}
+
+static long parse_long_arg_base(const char *arg, const char *name, int base)
+{
+	char *end;
+	long val;
+
+	errno = 0;
+	val = strtol(arg, &end, base);
+	if (errno || end == arg || *end != '\0') {
+		fprintf(stderr, "Invalid %s '%s'\n", name, arg);
+		exit(1);
+	}
+	return val;
+}
+
+static long parse_long_arg(const char *arg, const char *name)
+{
+	return parse_long_arg_base(arg, name, 10);
+}
+
+static long parse_positive_long_arg(const char *arg, const char *name)
+{
+	long val = parse_long_arg(arg, name);
+
+	if (val < 1) {
+		fprintf(stderr, "Invalid %s '%s'\n", name, arg);
+		exit(1);
+	}
+
+	return val;
+}
+
+struct bench_run {
+	unsigned long cmd;
+	int thp;		/* -1: default, 0: off, 1: on */
+	bool hugetlb;
+	bool write;		/* whether to set FOLL_WRITE */
+	bool shared;
+	int nr_pages;		/* -1 means all pages (size / psize()) */
+	unsigned long size;
+	char *file;
+	int nthreads;
+	unsigned int gup_flags;
+};
+
+void *gup_thread(void *data)
+{
+	struct gup_test gup = *(struct gup_test *)data;
+	int i, status;
+
+	for (i = 0; i < repeats; i++) {
+		gup.size = size;
+		status = ioctl(gup_fd, cmd, &gup);
+		if (status) {
+			bench_error = 1;
+			break;
+		}
+
+		pthread_mutex_lock(&print_mutex);
+		printf("%s time: get:%lld put:%lld us",
+		       bench_label, gup.get_delta_usec,
+		       gup.put_delta_usec);
+		if (gup.size != size)
+			printf(", truncated (size: %lld)", gup.size);
+		printf("\n");
+		pthread_mutex_unlock(&print_mutex);
+	}
+
+	return NULL;
+}
+
+static int run_bench(struct bench_run *run)
+{
+	struct gup_test gup = { 0 };
+	int zero_fd, i, ret, started_threads = 0;
+	unsigned long nr_pages_per_call;
+	int flags = MAP_PRIVATE;
+	pthread_t *tid;
+	char label[128];
+	char *p;
+	bool restore_hugetlb = false;
+
+	/* Set globals consumed by gup_thread */
+	cmd = run->cmd;
+	size = run->size;
+	bench_error = 0;
+
+	if (run->hugetlb) {
+		unsigned long hp_size = default_huge_page_size();
+
+		if (!hp_size) {
+			fprintf(stderr, "Could not determine huge page size\n");
+			return 1;
+		}
+
+		if (size > ULONG_MAX - (hp_size - 1)) {
+			fprintf(stderr, "HugeTLB mapping size is too large\n");
+			return 1;
+		}
+
+		size = (size + hp_size - 1) & ~(hp_size - 1);
+		if (!hugetlb_setup_default(size / hp_size)) {
+			fprintf(stderr, "Not enough huge pages\n");
+			hugetlb_restore_settings();
+			return 1;
+		}
+		restore_hugetlb = true;
+		flags |= (MAP_HUGETLB | MAP_ANONYMOUS);
+	}
+
+	if (run->shared) {
+		flags &= ~MAP_PRIVATE;
+		flags |= MAP_SHARED;
+	}
+
+	nr_pages_per_call = run->nr_pages < 0 ? size / psize() :
+		(unsigned long)run->nr_pages;
+
+	if (nr_pages_per_call > UINT_MAX) {
+		fprintf(stderr, "Page count is too large\n");
+		if (restore_hugetlb)
+			hugetlb_restore_settings();
+		return 1;
+	}
+	gup.nr_pages_per_call = nr_pages_per_call;
+
+	gup.gup_flags = run->gup_flags;
+	if (run->write)
+		gup.gup_flags |= FOLL_WRITE;
+
+	snprintf(label, sizeof(label), "%s (nr_pages=%-4u %s %s %s %s)",
+		 cmd_to_str(run->cmd),
+		 gup.nr_pages_per_call,
+		 run->write  ? "write"   : "read",
+		 run->shared ? "shared"  : "private",
+		 run->hugetlb ? "hugetlb=on" : "hugetlb=off",
+		 run->hugetlb ? "thp=off" :
+		 (run->thp == 1 ? "thp=on" :
+		 (run->thp == 0 ? "thp=off" : "thp=default")));
+	bench_label = label;
+
+	zero_fd = open(run->file, O_RDWR);
+	if (zero_fd < 0) {
+		fprintf(stderr, "Unable to open %s: %s\n", run->file, strerror(errno));
+		if (restore_hugetlb)
+			hugetlb_restore_settings();
+		return 1;
+	}
+
+	p = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, zero_fd, 0);
+	close(zero_fd);
+	if (p == MAP_FAILED) {
+		fprintf(stderr, "mmap: %s\n", strerror(errno));
+		if (restore_hugetlb)
+			hugetlb_restore_settings();
+		return 1;
+	}
+	gup.addr = (unsigned long)p;
+
+	if (run->thp == 1)
+		madvise(p, size, MADV_HUGEPAGE);
+	else if (run->thp == 0)
+		madvise(p, size, MADV_NOHUGEPAGE);
+
+	/* Fault them in here, from user space. */
+	for (; (unsigned long)p < gup.addr + size; p += psize())
+		p[0] = 0;
+
+	tid = malloc(sizeof(pthread_t) * run->nthreads);
+	if (!tid) {
+		fprintf(stderr, "Failed to allocate %d threads: %s\n",
+			run->nthreads, strerror(errno));
+		munmap((void *)gup.addr, size);
+		if (restore_hugetlb)
+			hugetlb_restore_settings();
+		return 1;
+	}
+
+	for (i = 0; i < run->nthreads; i++) {
+		ret = pthread_create(&tid[i], NULL, gup_thread, &gup);
+		if (ret) {
+			fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
+			bench_error = 1;
+			break;
+		}
+		started_threads++;
+	}
+	for (i = 0; i < started_threads; i++) {
+		ret = pthread_join(tid[i], NULL);
+		if (ret) {
+			fprintf(stderr, "pthread_join failed: %s\n", strerror(ret));
+			bench_error = 1;
+		}
+	}
+
+	free(tid);
+	munmap((void *)gup.addr, size);
+	if (restore_hugetlb)
+		hugetlb_restore_settings();
+
+	return bench_error ? 1 : 0;
+}
+
+static int run_matrix(void)
+{
+	unsigned int c, t, w, s, n;
+	int ret = 0;
+
+	for (c = 0; c < ARRAY_SIZE(bench_cmds); c++) {
+		for (w = 0; w <= 1; w++) {
+			for (s = 0; s <= 1; s++) {
+				for (t = 0; t < ARRAY_SIZE(bench_thp_modes); t++) {
+					for (n = 0; n < ARRAY_SIZE(bench_nr_pages_list); n++) {
+						struct bench_run run = {
+							.cmd	  = bench_cmds[c],
+							.thp	  = bench_thp_modes[t],
+							.hugetlb  = false,
+							.write	  = w,
+							.shared	  = s,
+							.nr_pages = bench_nr_pages_list[n],
+							.size	  = 128 * MB,
+							.file	  = "/dev/zero",
+							.nthreads = 1,
+						};
+						ret |= run_bench(&run);
+					}
+				}
+				/* hugetlb: 256M to match run_gup_matrix() in run_vmtests.sh */
+				for (n = 0; n < ARRAY_SIZE(bench_nr_pages_list); n++) {
+					struct bench_run run = {
+						.cmd	  = bench_cmds[c],
+						.thp	  = -1,
+						.hugetlb  = true,
+						.write	  = w,
+						.shared	  = s,
+						.nr_pages = bench_nr_pages_list[n],
+						.size	  = 256 * MB,
+						.file	  = "/dev/zero",
+						.nthreads = 1,
+					};
+					ret |= run_bench(&run);
+				}
+			}
+		}
+	}
+	return ret;
+}
+
+int main(int argc, char **argv)
+{
+	struct bench_run run = {
+		.cmd	  = GUP_FAST_BENCHMARK,
+		.thp	  = -1,
+		.hugetlb  = false,
+		.write	  = true,
+		.shared	  = false,
+		.nr_pages = 1,
+		.size	  = 128 * MB,
+		.file	  = "/dev/zero",
+		.nthreads = 1,
+	};
+	int opt, result;
+
+	while ((opt = getopt(argc, argv, "m:r:n:F:f:aj:tTLuwWSH")) != -1) {
+		switch (opt) {
+		/* Command selection */
+		case 'u':
+			run.cmd = GUP_FAST_BENCHMARK;
+			break;
+		case 'a':
+			run.cmd = PIN_FAST_BENCHMARK;
+			break;
+		case 'L':
+			run.cmd = PIN_LONGTERM_BENCHMARK;
+			break;
+
+		/* Memory type */
+		case 'H':
+			run.hugetlb = true;
+			break;
+		case 't':
+			run.thp = 1;
+			break;
+		case 'T':
+			run.thp = 0;
+			break;
+
+		/* Access mode */
+		case 'w':
+			run.write = true;
+			break;
+		case 'W':
+			run.write = false;
+			break;
+		case 'S':
+			run.shared = true;
+			break;
+
+		/* Mapping */
+		case 'f':
+			run.file = optarg;
+			break;
+
+		/* Sizing and iteration */
+		case 'm':
+			run.size = parse_positive_long_arg(optarg, "size");
+			if (run.size > ULONG_MAX / MB) {
+				fprintf(stderr, "Invalid size '%s'\n", optarg);
+				exit(1);
+			}
+			run.size *= MB;
+			break;
+		case 'n': {
+			long val;
+
+			val = parse_long_arg(optarg, "page count");
+			if (val != -1 && (val < 1 || val > INT_MAX)) {
+				fprintf(stderr, "Invalid page count '%s'\n", optarg);
+				exit(1);
+			}
+			run.nr_pages = val;
+			break;
+		}
+		case 'r': {
+			long val;
+
+			val = parse_positive_long_arg(optarg, "repeat count");
+			if (val > INT_MAX) {
+				fprintf(stderr, "Invalid repeat count '%s'\n", optarg);
+				exit(1);
+			}
+			repeats = val;
+			break;
+		}
+		case 'j': {
+			long val;
+
+			val = parse_positive_long_arg(optarg, "thread count");
+			if (val > INT_MAX ||
+			    (size_t)val > SIZE_MAX / sizeof(pthread_t)) {
+				fprintf(stderr, "Invalid thread count '%s'\n", optarg);
+				exit(1);
+			}
+			run.nthreads = val;
+			break;
+		}
+
+		/* Advanced */
+		case 'F': {
+			long val;
+
+			val = parse_long_arg_base(optarg, "GUP flags", 0);
+			if (val < 0 || val > UINT_MAX) {
+				fprintf(stderr, "Invalid GUP flags '%s'\n", optarg);
+				exit(1);
+			}
+
+			run.gup_flags = val;
+			break;
+		}
+
+		default:
+			fprintf(stderr, "Wrong argument\n");
+			exit(1);
+		}
+	}
+
+	if (optind != argc) {
+		fprintf(stderr, "Unexpected argument '%s'\n", argv[optind]);
+		exit(1);
+	}
+
+	gup_fd = open(GUP_TEST_FILE, O_RDWR);
+	if (gup_fd == -1) {
+		if (errno == EACCES) {
+			fprintf(stderr, "Please run as root\n");
+		} else if (errno == ENOENT) {
+			DIR *debugfs = opendir("/sys/kernel/debug");
+
+			if (!debugfs) {
+				fprintf(stderr, "Mount debugfs at /sys/kernel/debug\n");
+			} else {
+				closedir(debugfs);
+				fprintf(stderr, "Check CONFIG_GUP_TEST in kernel config\n");
+			}
+		} else {
+			fprintf(stderr, "Failed to open %s: %s\n", GUP_TEST_FILE,
+				strerror(errno));
+		}
+		exit(1);
+	}
+
+	result = (argc == 1) ? run_matrix() : run_bench(&run);
+	close(gup_fd);
+	return result;
+}
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index e6df968f0971c8..e77c4cc41b4703 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -37,7 +37,8 @@ endif
 # LDLIBS.
 MAKEFLAGS += --no-builtin-rules
 
-CFLAGS = -Wall -O2 -I $(top_srcdir) $(EXTRA_CFLAGS) $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
+CFLAGS = -Wall -O2 -I $(top_srcdir) -I $(top_srcdir)/tools/lib
+CFLAGS += $(EXTRA_CFLAGS) $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
 CFLAGS += -Wunreachable-code
 LDLIBS = -lrt -lpthread -lm
 
@@ -187,8 +188,10 @@ TEST_FILES += write_hugetlb_memory.sh
 
 include ../lib.mk
 
-$(TEST_GEN_PROGS): vm_util.c hugepage_settings.c
-$(TEST_GEN_FILES): vm_util.c hugepage_settings.c
+$(TEST_GEN_PROGS): vm_util.c $(top_srcdir)/tools/lib/mm/hugepage_settings.c \
+		   $(top_srcdir)/tools/lib/mm/file_utils.c
+$(TEST_GEN_FILES): vm_util.c $(top_srcdir)/tools/lib/mm/hugepage_settings.c \
+		   $(top_srcdir)/tools/lib/mm/file_utils.c
 
 $(OUTPUT)/uffd-stress: uffd-common.c
 $(OUTPUT)/uffd-unit-tests: uffd-common.c
@@ -217,7 +220,7 @@ $(BINARIES_32): CFLAGS += -m32 -mxsave
 $(BINARIES_32): LDLIBS += -lrt -ldl -lm
 $(BINARIES_32): $(OUTPUT)/%_32: %.c
 	$(call msg,CC,,$@)
-	$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(notdir $^) $(LDLIBS) -o $@
+	$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $^ $(LDLIBS) -o $@
 $(foreach t,$(VMTARGETS),$(eval $(call gen-target-rule-32,$(t))))
 endif
 
@@ -226,7 +229,7 @@ $(BINARIES_64): CFLAGS += -m64 -mxsave
 $(BINARIES_64): LDLIBS += -lrt -ldl
 $(BINARIES_64): $(OUTPUT)/%_64: %.c
 	$(call msg,CC,,$@)
-	$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(notdir $^) $(LDLIBS) -o $@
+	$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $^ $(LDLIBS) -o $@
 $(foreach t,$(VMTARGETS),$(eval $(call gen-target-rule-64,$(t))))
 endif
 
diff --git a/tools/testing/selftests/mm/compaction_test.c b/tools/testing/selftests/mm/compaction_test.c
index 5b582588e015d4..b2eaa490e7c253 100644
--- a/tools/testing/selftests/mm/compaction_test.c
+++ b/tools/testing/selftests/mm/compaction_test.c
@@ -15,9 +15,9 @@
 #include <errno.h>
 #include <unistd.h>
 #include <string.h>
+#include <mm/hugepage_settings.h>
 
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define MAP_SIZE_MB	100
 #define MAP_SIZE	(MAP_SIZE_MB * 1024 * 1024)
diff --git a/tools/testing/selftests/mm/cow.c b/tools/testing/selftests/mm/cow.c
index 0c627ea89ff7b2..3c3d129c91b29d 100644
--- a/tools/testing/selftests/mm/cow.c
+++ b/tools/testing/selftests/mm/cow.c
@@ -29,7 +29,6 @@
 #include "../../../../mm/gup_test.h"
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 static size_t pagesize;
 static int pagemap_fd;
diff --git a/tools/testing/selftests/mm/folio_split_race_test.c b/tools/testing/selftests/mm/folio_split_race_test.c
index 6329e37fff4c94..496b74b37476b3 100644
--- a/tools/testing/selftests/mm/folio_split_race_test.c
+++ b/tools/testing/selftests/mm/folio_split_race_test.c
@@ -25,7 +25,6 @@
 #include <unistd.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 uint64_t page_size;
 uint64_t pmd_pagesize;
diff --git a/tools/testing/selftests/mm/guard-regions.c b/tools/testing/selftests/mm/guard-regions.c
index b21df3040b1c7c..ed967b3c7f3704 100644
--- a/tools/testing/selftests/mm/guard-regions.c
+++ b/tools/testing/selftests/mm/guard-regions.c
@@ -21,7 +21,6 @@
 #include <sys/uio.h>
 #include <unistd.h>
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #include "../pidfd/pidfd.h"
 
diff --git a/tools/testing/selftests/mm/gup_longterm.c b/tools/testing/selftests/mm/gup_longterm.c
index eb8963e9d98f06..68c840bae08208 100644
--- a/tools/testing/selftests/mm/gup_longterm.c
+++ b/tools/testing/selftests/mm/gup_longterm.c
@@ -29,7 +29,6 @@
 #include "../../../../mm/gup_test.h"
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 static size_t pagesize;
 static int nr_hugetlbsizes;
diff --git a/tools/testing/selftests/mm/gup_test.c b/tools/testing/selftests/mm/gup_test.c
index 3f841a96f87068..d9caa9fb8b7856 100644
--- a/tools/testing/selftests/mm/gup_test.c
+++ b/tools/testing/selftests/mm/gup_test.c
@@ -1,275 +1,443 @@
 #define __SANE_USERSPACE_TYPES__ // Use ll64
 #include <fcntl.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <pthread.h>
-#include <assert.h>
 #include <mm/gup_test.h>
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
+#include "kselftest_harness.h"
 
 #define MB (1UL << 20)
 
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
 /* Just the flags we need, copied from the kernel internals. */
 #define FOLL_WRITE	0x01	/* check pte is writable */
 
+/* Page counts exercising single, THP-batch, partial, and full-mapping GUP. */
+static const int nr_pages_list[] = { 1, 512, 123, -1 };
+
 #define GUP_TEST_FILE "/sys/kernel/debug/gup_test"
 
-static unsigned long cmd = GUP_FAST_BENCHMARK;
-static int gup_fd, repeats = 1;
-static unsigned long size = 128 * MB;
-/* Serialize prints */
-static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;
+FIXTURE(gup_test)
+{
+	int gup_fd;
+	char *addr;
+	unsigned long size;
+};
+
+FIXTURE_VARIANT(gup_test)
+{
+	bool thp;
+	bool hugetlb;
+	bool write;
+	bool shared;
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_write)
+{
+	.thp = false,
+	.hugetlb = false,
+	.write = true,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_readonly)
+{
+	.thp = false,
+	.hugetlb = false,
+	.write = false,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_write_thp)
+{
+	.thp = true,
+	.hugetlb = false,
+	.write = true,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_readonly_thp)
+{
+	.thp = true,
+	.hugetlb = false,
+	.write = false,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_write_hugetlb)
+{
+	.thp = false,
+	.hugetlb = true,
+	.write = true,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_readonly_hugetlb)
+{
+	.thp = false,
+	.hugetlb = true,
+	.write = false,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_write)
+{
+	.thp = false,
+	.hugetlb = false,
+	.write = true,
+	.shared = true,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_readonly)
+{
+	.thp = false,
+	.hugetlb = false,
+	.write = false,
+	.shared = true,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_write_thp)
+{
+	.thp = true,
+	.hugetlb = false,
+	.write = true,
+	.shared = true,
+};
 
-static char *cmd_to_str(unsigned long cmd)
+FIXTURE_VARIANT_ADD(gup_test, shared_readonly_thp)
 {
-	switch (cmd) {
-	case GUP_FAST_BENCHMARK:
-		return "GUP_FAST_BENCHMARK";
-	case PIN_FAST_BENCHMARK:
-		return "PIN_FAST_BENCHMARK";
-	case PIN_LONGTERM_BENCHMARK:
-		return "PIN_LONGTERM_BENCHMARK";
-	case GUP_BASIC_TEST:
-		return "GUP_BASIC_TEST";
-	case PIN_BASIC_TEST:
-		return "PIN_BASIC_TEST";
-	case DUMP_USER_PAGES_TEST:
-		return "DUMP_USER_PAGES_TEST";
+	.thp = true,
+	.hugetlb = false,
+	.write = false,
+	.shared = true,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_write_hugetlb)
+{
+	.thp = false,
+	.hugetlb = true,
+	.write = true,
+	.shared = true,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_readonly_hugetlb)
+{
+	.thp = false,
+	.hugetlb = true,
+	.write = false,
+	.shared = true,
+};
+
+FIXTURE_SETUP(gup_test)
+{
+	int mmap_flags = MAP_PRIVATE;
+	int zero_fd;
+	char *p;
+
+	/* zero_fd has to be >= 0. Already checked in main() */
+	zero_fd = open("/dev/zero", O_RDWR);
+	ASSERT_GE(zero_fd, 0);
+
+	/* gup_fd has to be >= 0. Already checked in main() */
+	self->gup_fd = open(GUP_TEST_FILE, O_RDWR);
+	ASSERT_GE(self->gup_fd, 0);
+
+	self->size = variant->hugetlb ? 256 * MB : 128 * MB;
+
+	/* Check for hugetlb */
+	if (variant->hugetlb) {
+		unsigned long hp_size = default_huge_page_size();
+
+		if (!hp_size) {
+			close(zero_fd);
+			close(self->gup_fd);
+			SKIP(return, "HugeTLB not available\n");
+		}
+
+		self->size = (self->size + hp_size - 1) & ~(hp_size - 1);
+		if (!hugetlb_setup_default(self->size / hp_size)) {
+			hugetlb_restore_settings();
+			close(zero_fd);
+			close(self->gup_fd);
+			SKIP(return, "Not enough huge pages\n");
+		}
+
+		mmap_flags |= (MAP_HUGETLB | MAP_ANONYMOUS);
+	}
+
+	if (variant->shared)
+		mmap_flags = (mmap_flags & ~MAP_PRIVATE) | MAP_SHARED;
+
+	self->addr = mmap(NULL, self->size, PROT_READ | PROT_WRITE,
+			  mmap_flags, zero_fd, 0);
+
+	close(zero_fd);
+
+	ASSERT_NE(self->addr, MAP_FAILED) {
+		int err = errno;
+
+		close(self->gup_fd);
+		if (variant->hugetlb)
+			hugetlb_restore_settings();
+		TH_LOG("mmap failed: %s", strerror(err));
 	}
-	return "Unknown command";
+
+	if (variant->thp)
+		madvise(self->addr, self->size, MADV_HUGEPAGE);
+	else
+		madvise(self->addr, self->size, MADV_NOHUGEPAGE);
+
+	for (p = self->addr; (unsigned long)p < (unsigned long)self->addr
+			+ self->size; p += psize())
+		p[0] = 0;
 }
 
-void *gup_thread(void *data)
+FIXTURE_TEARDOWN(gup_test)
 {
-	struct gup_test gup = *(struct gup_test *)data;
-	int i, status;
-
-	/* Only report timing information on the *_BENCHMARK commands: */
-	if ((cmd == PIN_FAST_BENCHMARK) || (cmd == GUP_FAST_BENCHMARK) ||
-	     (cmd == PIN_LONGTERM_BENCHMARK)) {
-		for (i = 0; i < repeats; i++) {
-			gup.size = size;
-			status = ioctl(gup_fd, cmd, &gup);
-			if (status)
-				break;
-
-			pthread_mutex_lock(&print_mutex);
-			ksft_print_msg("%s: Time: get:%lld put:%lld us",
-				       cmd_to_str(cmd), gup.get_delta_usec,
-				       gup.put_delta_usec);
-			if (gup.size != size)
-				ksft_print_msg(", truncated (size: %lld)", gup.size);
-			ksft_print_msg("\n");
-			pthread_mutex_unlock(&print_mutex);
-		}
-	} else {
-		gup.size = size;
-		status = ioctl(gup_fd, cmd, &gup);
-		if (status)
-			goto return_;
-
-		pthread_mutex_lock(&print_mutex);
-		ksft_print_msg("%s: done\n", cmd_to_str(cmd));
-		if (gup.size != size)
-			ksft_print_msg("Truncated (size: %lld)\n", gup.size);
-		pthread_mutex_unlock(&print_mutex);
+	munmap(self->addr, self->size);
+	close(self->gup_fd);
+
+	if (variant->hugetlb)
+		hugetlb_restore_settings();
+}
+
+TEST_F(gup_test, get_user_pages)
+{
+	/* Tests the get_user_pages path */
+	int i;
+
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, GUP_BASIC_TEST, &gup), 0);
+	}
+}
+
+TEST_F(gup_test, pin_user_pages)
+{
+	/* Tests the pin_user_pages path */
+	int i;
+
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, PIN_BASIC_TEST, &gup), 0);
 	}
+}
 
-return_:
-	ksft_test_result(!status, "ioctl status %d\n", status);
-	return NULL;
+TEST_F(gup_test, dump_user_pages_with_get)
+{
+	/* Tests DUMP_USER_PAGES_TEST using get_user_pages */
+	int i;
+
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		gup.which_pages[0] = 1;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, DUMP_USER_PAGES_TEST, &gup), 0);
+	}
 }
 
-int main(int argc, char **argv)
+TEST_F(gup_test, dump_user_pages_with_pin)
 {
-	struct gup_test gup = { 0 };
-	int filed, i, opt, nr_pages = 1, thp = -1, write = 1, nthreads = 1, ret;
-	int flags = MAP_PRIVATE;
-	char *file = "/dev/zero";
-	bool hugetlb = false;
-	pthread_t *tid;
-	char *p;
+	/* Tests DUMP_USER_PAGES_TEST using pin_user_pages */
+	int i;
 
-	while ((opt = getopt(argc, argv, "m:r:n:F:f:abcj:tTLUuwWSHpz")) != -1) {
-		switch (opt) {
-		case 'a':
-			cmd = PIN_FAST_BENCHMARK;
-			break;
-		case 'b':
-			cmd = PIN_BASIC_TEST;
-			break;
-		case 'L':
-			cmd = PIN_LONGTERM_BENCHMARK;
-			break;
-		case 'c':
-			cmd = DUMP_USER_PAGES_TEST;
-			/*
-			 * Dump page 0 (index 1). May be overridden later, by
-			 * user's non-option arguments.
-			 *
-			 * .which_pages is zero-based, so that zero can mean "do
-			 * nothing".
-			 */
-			gup.which_pages[0] = 1;
-			break;
-		case 'p':
-			/* works only with DUMP_USER_PAGES_TEST */
-			gup.test_flags |= GUP_TEST_FLAG_DUMP_PAGES_USE_PIN;
-			break;
-		case 'F':
-			/* strtol, so you can pass flags in hex form */
-			gup.gup_flags = strtol(optarg, 0, 0);
-			break;
-		case 'j':
-			nthreads = atoi(optarg);
-			break;
-		case 'm':
-			size = atoi(optarg) * MB;
-			break;
-		case 'r':
-			repeats = atoi(optarg);
-			break;
-		case 'n':
-			nr_pages = atoi(optarg);
-			if (nr_pages < 0)
-				nr_pages = size / psize();
-			break;
-		case 't':
-			thp = 1;
-			break;
-		case 'T':
-			thp = 0;
-			break;
-		case 'U':
-			cmd = GUP_BASIC_TEST;
-			break;
-		case 'u':
-			cmd = GUP_FAST_BENCHMARK;
-			break;
-		case 'w':
-			write = 1;
-			break;
-		case 'W':
-			write = 0;
-			break;
-		case 'f':
-			file = optarg;
-			break;
-		case 'S':
-			flags &= ~MAP_PRIVATE;
-			flags |= MAP_SHARED;
-			break;
-		case 'H':
-			flags |= (MAP_HUGETLB | MAP_ANONYMOUS);
-			hugetlb = true;
-			break;
-		default:
-			ksft_exit_fail_msg("Wrong argument\n");
-		}
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		gup.which_pages[0] = 1;
+		gup.test_flags |= GUP_TEST_FLAG_DUMP_PAGES_USE_PIN;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, DUMP_USER_PAGES_TEST, &gup), 0);
 	}
+}
 
-	if (optind < argc) {
-		int extra_arg_count = 0;
-		/*
-		 * For example:
-		 *
-		 *   ./gup_test -c 0 1 0x1001
-		 *
-		 * ...to dump pages 0, 1, and 4097
-		 */
-
-		while ((optind < argc) &&
-		       (extra_arg_count < GUP_TEST_MAX_PAGES_TO_DUMP)) {
-			/*
-			 * Do the 1-based indexing here, so that the user can
-			 * use normal 0-based indexing on the command line.
-			 */
-			long page_index = strtol(argv[optind], 0, 0) + 1;
-
-			gup.which_pages[extra_arg_count] = page_index;
-			extra_arg_count++;
-			optind++;
-		}
+TEST_F(gup_test, get_user_pages_fast)
+{
+	/* Tests the lockless get_user_pages_fast() path */
+	int i;
+
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, GUP_FAST_BENCHMARK, &gup), 0);
 	}
+}
+
+TEST_F(gup_test, pin_user_pages_fast)
+{
+	/* Tests the lockless pin_user_pages_fast() path */
+	int i;
 
-	ksft_print_header();
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
 
-	if (hugetlb) {
-		unsigned long hp_size = default_huge_page_size();
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
 
-		if (!hp_size)
-			ksft_exit_skip("HugeTLB is unavailable\n");
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
 
-		size = (size + hp_size - 1) & ~(hp_size - 1);
-		if (!hugetlb_setup_default(size / hp_size))
-			ksft_exit_skip("Not enough huge pages\n");
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, PIN_FAST_BENCHMARK, &gup), 0);
 	}
+}
 
-	ksft_set_plan(nthreads);
+TEST_F(gup_test, pin_user_pages_longterm)
+{
+	/* Tests pin_user_pages() with FOLL_LONGTERM */
+	int i;
 
-	filed = open(file, O_RDWR|O_CREAT, 0664);
-	if (filed < 0)
-		ksft_exit_fail_msg("Unable to open %s: %s\n", file, strerror(errno));
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
 
-	gup.nr_pages_per_call = nr_pages;
-	if (write)
-		gup.gup_flags |= FOLL_WRITE;
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
 
-	gup_fd = open(GUP_TEST_FILE, O_RDWR);
-	if (gup_fd == -1) {
-		switch (errno) {
-		case EACCES:
-			if (getuid())
-				ksft_print_msg("Please run this test as root\n");
-			break;
-		case ENOENT:
-			if (opendir("/sys/kernel/debug") == NULL)
-				ksft_print_msg("mount debugfs at /sys/kernel/debug\n");
-			ksft_print_msg("check if CONFIG_GUP_TEST is enabled in kernel config\n");
-			break;
-		default:
-			ksft_print_msg("failed to open %s: %s\n", GUP_TEST_FILE, strerror(errno));
-			break;
-		}
-		ksft_test_result_skip("Please run this test as root\n");
-		ksft_exit_pass();
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, PIN_LONGTERM_BENCHMARK, &gup), 0);
 	}
+}
+
+TEST(dump_user_pages_sparse_indices)
+{
+	/* Tests sparse multi-index which_pages[] inputs. */
+	struct gup_test gup = { 0 };
+	unsigned long size = 128 * MB;
+	int zero_fd, gup_fd;
+	char *addr, *p;
+
+	zero_fd = open("/dev/zero", O_RDWR);
+	ASSERT_GE(zero_fd, 0);
 
-	p = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, filed, 0);
-	if (p == MAP_FAILED)
-		ksft_exit_fail_msg("mmap: %s\n", strerror(errno));
-	gup.addr = (unsigned long)p;
+	gup_fd = open(GUP_TEST_FILE, O_RDWR);
+	ASSERT_GE(gup_fd, 0);
+
+	addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, zero_fd, 0);
+	close(zero_fd);
+	ASSERT_NE(addr, MAP_FAILED);
 
-	if (thp == 1)
-		madvise(p, size, MADV_HUGEPAGE);
-	else if (thp == 0)
-		madvise(p, size, MADV_NOHUGEPAGE);
+	madvise(addr, size, MADV_HUGEPAGE);
 
-	/* Fault them in here, from user space. */
-	for (; (unsigned long)p < gup.addr + size; p += psize())
+	for (p = addr; (unsigned long)p < (unsigned long)addr + size;
+	     p += psize())
 		p[0] = 0;
 
-	tid = malloc(sizeof(pthread_t) * nthreads);
-	assert(tid);
-	for (i = 0; i < nthreads; i++) {
-		ret = pthread_create(&tid[i], NULL, gup_thread, &gup);
-		assert(ret == 0);
-	}
-	for (i = 0; i < nthreads; i++) {
-		ret = pthread_join(tid[i], NULL);
-		assert(ret == 0);
+	gup.addr = (unsigned long)addr;
+	gup.size = size;
+	gup.nr_pages_per_call = size / psize();
+	gup.gup_flags = FOLL_WRITE;
+	gup.which_pages[0] = 1;
+	gup.which_pages[1] = 20;
+	gup.which_pages[2] = 0x1001;
+
+	/*
+	 * Preserve the old "./gup_test -ct -F 0x1 0 19 0x1000" sparse dump
+	 * coverage after removing command-line parsing from this binary.
+	 */
+	ASSERT_EQ(ioctl(gup_fd, DUMP_USER_PAGES_TEST, &gup), 0);
+
+	munmap(addr, size);
+	close(gup_fd);
+}
+
+int main(int argc, char **argv)
+{
+	int fd;
+	char *file = "/dev/zero";
+
+	fd = open(file, O_RDWR);
+	if (fd < 0) {
+		ksft_print_header();
+		ksft_exit_fail_msg("Unable to open %s: %s\n", file, strerror(errno));
 	}
+	close(fd);
 
-	free(tid);
+	fd = open(GUP_TEST_FILE, O_RDWR);
+	if (fd == -1) {
+		ksft_print_header();
+		if (errno == EACCES)
+			ksft_exit_skip("Please run this test as root\n");
+		if (errno == ENOENT) {
+			DIR *debugfs = opendir("/sys/kernel/debug");
+
+			if (!debugfs) {
+				ksft_exit_skip("Mount debugfs at /sys/kernel/debug\n");
+			} else {
+				closedir(debugfs);
+				ksft_exit_skip("Check CONFIG_GUP_TEST in kernel config\n");
+			}
+		}
+		ksft_exit_skip("failed to open %s: %s\n", GUP_TEST_FILE, strerror(errno));
+	}
+	close(fd);
 
-	ksft_exit_pass();
+	return test_harness_run(argc, argv);
 }
diff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c
index 2f2b9879d10051..e1bb031019f2ac 100644
--- a/tools/testing/selftests/mm/hmm-tests.c
+++ b/tools/testing/selftests/mm/hmm-tests.c
@@ -10,9 +10,6 @@
  * bugs.
  */
 
-#include "kselftest_harness.h"
-#include "hugepage_settings.h"
-
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
@@ -29,6 +26,9 @@
 #include <sys/mman.h>
 #include <sys/ioctl.h>
 #include <sys/time.h>
+#include <mm/hugepage_settings.h>
+
+#include "kselftest_harness.h"
 
 /*
  * This is a private UAPI to the kernel test module so it isn't exported
diff --git a/tools/testing/selftests/mm/hugetlb-madvise.c b/tools/testing/selftests/mm/hugetlb-madvise.c
index 555b4b3d14307e..57cf790ca478d4 100644
--- a/tools/testing/selftests/mm/hugetlb-madvise.c
+++ b/tools/testing/selftests/mm/hugetlb-madvise.c
@@ -14,7 +14,6 @@
 #include <fcntl.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define MIN_FREE_PAGES	20
 #define NR_HUGE_PAGES	10	/* common number of pages to map/allocate */
diff --git a/tools/testing/selftests/mm/hugetlb-mmap.c b/tools/testing/selftests/mm/hugetlb-mmap.c
index 0f2aad1b7dbd6f..a458becf58327f 100644
--- a/tools/testing/selftests/mm/hugetlb-mmap.c
+++ b/tools/testing/selftests/mm/hugetlb-mmap.c
@@ -18,7 +18,6 @@
 #include <linux/memfd.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define LENGTH (256UL*1024*1024)
 #define PROTECTION (PROT_READ | PROT_WRITE)
diff --git a/tools/testing/selftests/mm/hugetlb-mremap.c b/tools/testing/selftests/mm/hugetlb-mremap.c
index ed3d92e862d876..9b724af66e9388 100644
--- a/tools/testing/selftests/mm/hugetlb-mremap.c
+++ b/tools/testing/selftests/mm/hugetlb-mremap.c
@@ -26,7 +26,6 @@
 #include <stdbool.h>
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define DEFAULT_LENGTH_MB 10UL
 #define MB_TO_BYTES(x) (x * 1024 * 1024)
diff --git a/tools/testing/selftests/mm/hugetlb-shm.c b/tools/testing/selftests/mm/hugetlb-shm.c
index 3ff7f062b7eb45..f4514da49e1df7 100644
--- a/tools/testing/selftests/mm/hugetlb-shm.c
+++ b/tools/testing/selftests/mm/hugetlb-shm.c
@@ -29,7 +29,6 @@
 #include <sys/mman.h>
 
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define LENGTH (256UL*1024*1024)
 
diff --git a/tools/testing/selftests/mm/hugetlb-soft-offline.c b/tools/testing/selftests/mm/hugetlb-soft-offline.c
index bc202e4ed2bda7..20864e7d4e0c72 100644
--- a/tools/testing/selftests/mm/hugetlb-soft-offline.c
+++ b/tools/testing/selftests/mm/hugetlb-soft-offline.c
@@ -21,9 +21,9 @@
 #include <sys/mman.h>
 #include <sys/statfs.h>
 #include <sys/types.h>
+#include <mm/hugepage_settings.h>
 
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #ifndef MADV_SOFT_OFFLINE
 #define MADV_SOFT_OFFLINE 101
diff --git a/tools/testing/selftests/mm/hugetlb-vmemmap.c b/tools/testing/selftests/mm/hugetlb-vmemmap.c
index 507df78a158d8d..c46a656c25a04e 100644
--- a/tools/testing/selftests/mm/hugetlb-vmemmap.c
+++ b/tools/testing/selftests/mm/hugetlb-vmemmap.c
@@ -11,7 +11,6 @@
 #include <sys/mman.h>
 #include <fcntl.h>
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define PAGE_COMPOUND_HEAD	(1UL << 15)
 #define PAGE_COMPOUND_TAIL	(1UL << 16)
diff --git a/tools/testing/selftests/mm/hugetlb_dio.c b/tools/testing/selftests/mm/hugetlb_dio.c
index fb4600570e1319..9495974eccbea5 100644
--- a/tools/testing/selftests/mm/hugetlb_dio.c
+++ b/tools/testing/selftests/mm/hugetlb_dio.c
@@ -20,7 +20,6 @@
 #include <sys/syscall.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #ifndef STATX_DIOALIGN
 #define STATX_DIOALIGN		0x00002000U
diff --git a/tools/testing/selftests/mm/hugetlb_fault_after_madv.c b/tools/testing/selftests/mm/hugetlb_fault_after_madv.c
index 2dc158054f666b..56c5a8533e9d90 100644
--- a/tools/testing/selftests/mm/hugetlb_fault_after_madv.c
+++ b/tools/testing/selftests/mm/hugetlb_fault_after_madv.c
@@ -10,7 +10,6 @@
 
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define INLOOP_ITER 100
 
diff --git a/tools/testing/selftests/mm/hugetlb_madv_vs_map.c b/tools/testing/selftests/mm/hugetlb_madv_vs_map.c
index f94549efcc6ff3..2532a42b98dff1 100644
--- a/tools/testing/selftests/mm/hugetlb_madv_vs_map.c
+++ b/tools/testing/selftests/mm/hugetlb_madv_vs_map.c
@@ -25,7 +25,6 @@
 #include <unistd.h>
 
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define INLOOP_ITER 100
 
diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index 10e8dedcb087dc..2382973b2f552c 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -22,7 +22,6 @@
 #include "linux/magic.h"
 
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define BASE_ADDR ((void *)(1UL << 30))
 static unsigned long hpage_pmd_size;
@@ -119,6 +118,7 @@ static void get_finfo(const char *dir)
 	char buf[1 << 10];
 	char path[PATH_MAX];
 	char *str, *end;
+	int ret;
 
 	finfo.dir = dir;
 	stat(finfo.dir, &path_stat);
@@ -138,8 +138,9 @@ static void get_finfo(const char *dir)
 		     major(path_stat.st_dev), minor(path_stat.st_dev))
 	    >= sizeof(path))
 		ksft_exit_fail_msg("%s: Pathname is too long\n", __func__);
-	if (read_file(path, buf, sizeof(buf)) < 0)
-		ksft_exit_fail_perror("read_file(read_num)");
+	ret = read_file(path, buf, sizeof(buf));
+	if (ret < 0)
+		ksft_exit_fail();
 	if (strstr(buf, "DEVTYPE=disk")) {
 		/* Found it */
 		if (snprintf(finfo.dev_queue_read_ahead_path,
@@ -319,7 +320,7 @@ static void *file_setup_area_common(int nr_hpages, enum file_setup_ops setup)
 {
 	const int open_opt = setup == FILE_SETUP_READ_ONLY_FS ? O_RDONLY : O_RDWR;
 	const int mmap_prot = setup == FILE_SETUP_READ_ONLY_FS ? PROT_READ : (PROT_READ | PROT_WRITE);
-	int fd;
+	int fd, ret;
 	void *p;
 	unsigned long size;
 
@@ -363,7 +364,10 @@ static void *file_setup_area_common(int nr_hpages, enum file_setup_ops setup)
 		ksft_exit_fail_perror("mmap()");
 
 	/* Drop page cache */
-	write_file("/proc/sys/vm/drop_caches", "3", 2);
+	ret = write_file("/proc/sys/vm/drop_caches", "3", 2);
+	if (ret)
+		ksft_exit_fail();
+
 	success("OK");
 	return p;
 }
diff --git a/tools/testing/selftests/mm/ksm_tests.c b/tools/testing/selftests/mm/ksm_tests.c
index a050f4840cfa3a..cc37492cf32c3d 100644
--- a/tools/testing/selftests/mm/ksm_tests.c
+++ b/tools/testing/selftests/mm/ksm_tests.c
@@ -15,7 +15,6 @@
 #include "kselftest.h"
 #include <include/vdso/time64.h>
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define KSM_SYSFS_PATH "/sys/kernel/mm/ksm/"
 #define KSM_FP(s) (KSM_SYSFS_PATH s)
diff --git a/tools/testing/selftests/mm/migration.c b/tools/testing/selftests/mm/migration.c
index 29f7492453d439..4fe97c03326124 100644
--- a/tools/testing/selftests/mm/migration.c
+++ b/tools/testing/selftests/mm/migration.c
@@ -4,9 +4,6 @@
  * paths in the kernel.
  */
 
-#include "kselftest_harness.h"
-#include "hugepage_settings.h"
-
 #include <strings.h>
 #include <pthread.h>
 #include <numa.h>
@@ -16,6 +13,8 @@
 #include <sys/types.h>
 #include <signal.h>
 #include <time.h>
+
+#include "kselftest_harness.h"
 #include "vm_util.h"
 
 #define TWOMEG		(2<<20)
diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c
index 6f8971d5b3ce43..804372803080cc 100644
--- a/tools/testing/selftests/mm/pagemap_ioctl.c
+++ b/tools/testing/selftests/mm/pagemap_ioctl.c
@@ -23,7 +23,6 @@
 
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define PAGEMAP_BITS_ALL		(PAGE_IS_WPALLOWED | PAGE_IS_WRITTEN |	\
 					 PAGE_IS_FILE | PAGE_IS_PRESENT |	\
diff --git a/tools/testing/selftests/mm/prctl_thp_disable.c b/tools/testing/selftests/mm/prctl_thp_disable.c
index d8d9d1de57b8b0..a4f8451791cbdb 100644
--- a/tools/testing/selftests/mm/prctl_thp_disable.c
+++ b/tools/testing/selftests/mm/prctl_thp_disable.c
@@ -14,7 +14,6 @@
 #include <sys/wait.h>
 
 #include "kselftest_harness.h"
-#include "hugepage_settings.h"
 #include "vm_util.h"
 
 #ifndef PR_THP_DISABLE_EXCEPT_ADVISED
diff --git a/tools/testing/selftests/mm/protection_keys.c b/tools/testing/selftests/mm/protection_keys.c
index 9a6d954ee37126..5ba2033e8a09cd 100644
--- a/tools/testing/selftests/mm/protection_keys.c
+++ b/tools/testing/selftests/mm/protection_keys.c
@@ -45,8 +45,8 @@
 #include <unistd.h>
 #include <sys/ptrace.h>
 #include <setjmp.h>
+#include <mm/hugepage_settings.h>
 
-#include "hugepage_settings.h"
 #include "pkey-helpers.h"
 
 int iteration_nr = 1;
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 8c296dedf0474d..56df0dcc9640c0 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -130,30 +130,6 @@ test_selected() {
 	fi
 }
 
-run_gup_matrix() {
-    # -t: thp=on, -T: thp=off, -H: hugetlb=on
-    local hugetlb_mb=256
-
-    for huge in -t -T "-H -m $hugetlb_mb"; do
-        # -u: gup-fast, -U: gup-basic, -a: pin-fast, -b: pin-basic, -L: pin-longterm
-        for test_cmd in -u -U -a -b -L; do
-            # -w: write=1, -W: write=0
-            for write in -w -W; do
-                # -S: shared
-                for share in -S " "; do
-                    # -n: How many pages to fetch together?  512 is special
-                    # because it's default thp size (or 2M on x86), 123 to
-                    # just test partial gup when hit a huge in whatever form
-                    for num in "-n 1" "-n 512" "-n 123" "-n -1"; do
-                        CATEGORY="gup_test" run_test ./gup_test \
-                                $huge $test_cmd $write $share $num
-                    done
-                done
-            done
-        done
-    done
-}
-
 # filter 64bit architectures
 ARCH64STR="arm64 mips64 parisc64 ppc64 ppc64le riscv64 s390x sparc64 x86_64"
 if [ -z "$ARCH" ]; then
@@ -276,18 +252,7 @@ fi
 
 CATEGORY="mmap" run_test ./map_fixed_noreplace
 
-if $RUN_ALL; then
-    run_gup_matrix
-else
-    # get_user_pages_fast() benchmark
-    CATEGORY="gup_test" run_test ./gup_test -u -n 1
-    CATEGORY="gup_test" run_test ./gup_test -u -n -1
-    # pin_user_pages_fast() benchmark
-    CATEGORY="gup_test" run_test ./gup_test -a -n 1
-    CATEGORY="gup_test" run_test ./gup_test -a -n -1
-fi
-# Dump pages 0, 19, and 4096, using pin_user_pages:
-CATEGORY="gup_test" run_test ./gup_test -ct -F 0x1 0 19 0x1000
+CATEGORY="gup_test" run_test ./gup_test
 CATEGORY="gup_test" run_test ./gup_longterm
 
 CATEGORY="userfaultfd" run_test ./uffd-unit-tests
diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c
index fb1864a68e1c6b..6c22ac9e93db06 100644
--- a/tools/testing/selftests/mm/soft-dirty.c
+++ b/tools/testing/selftests/mm/soft-dirty.c
@@ -9,7 +9,6 @@
 
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define PAGEMAP_FILE_PATH "/proc/self/pagemap"
 #define TEST_ITERATIONS 10000
diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 32b991472f744f..e744c9434e7d05 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -21,7 +21,6 @@
 #include <time.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 uint64_t pagesize;
 unsigned int pageshift;
@@ -269,7 +268,9 @@ static void write_debugfs(const char *fmt, ...)
 	if (ret >= INPUT_MAX)
 		ksft_exit_fail_msg("%s: Debugfs input is too long\n", __func__);
 
-	write_file(SPLIT_DEBUGFS, input, ret + 1);
+	ret = write_file(SPLIT_DEBUGFS, input, ret + 1);
+	if (ret)
+		ksft_exit_fail();
 }
 
 static char *allocate_zero_filled_hugepage(size_t len)
diff --git a/tools/testing/selftests/mm/thuge-gen.c b/tools/testing/selftests/mm/thuge-gen.c
index 22b9c2f1c35d23..0da15d530a1fc8 100644
--- a/tools/testing/selftests/mm/thuge-gen.c
+++ b/tools/testing/selftests/mm/thuge-gen.c
@@ -14,7 +14,6 @@
 #include <string.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #if !defined(MAP_HUGETLB)
 #define MAP_HUGETLB	0x40000
diff --git a/tools/testing/selftests/mm/transhuge-stress.c b/tools/testing/selftests/mm/transhuge-stress.c
index 8eb0c5630e7e31..96f72898ebe0a5 100644
--- a/tools/testing/selftests/mm/transhuge-stress.c
+++ b/tools/testing/selftests/mm/transhuge-stress.c
@@ -17,7 +17,6 @@
 #include <sys/mman.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 int backing_fd = -1;
 int mmap_flags = MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE;
diff --git a/tools/testing/selftests/mm/uffd-common.h b/tools/testing/selftests/mm/uffd-common.h
index 92a21b97f745af..0723843a7626b1 100644
--- a/tools/testing/selftests/mm/uffd-common.h
+++ b/tools/testing/selftests/mm/uffd-common.h
@@ -37,7 +37,6 @@
 
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define UFFD_FLAGS	(O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY)
 
diff --git a/tools/testing/selftests/mm/uffd-wp-mremap.c b/tools/testing/selftests/mm/uffd-wp-mremap.c
index c973d6722720c3..eb4b2433b00e61 100644
--- a/tools/testing/selftests/mm/uffd-wp-mremap.c
+++ b/tools/testing/selftests/mm/uffd-wp-mremap.c
@@ -7,8 +7,8 @@
 #include <assert.h>
 #include <linux/mman.h>
 #include <sys/mman.h>
+#include <mm/hugepage_settings.h>
 #include "kselftest.h"
-#include "hugepage_settings.h"
 #include "uffd-common.h"
 
 static int pagemap_fd;
diff --git a/tools/testing/selftests/mm/va_high_addr_switch.c b/tools/testing/selftests/mm/va_high_addr_switch.c
index e24d7ba00b4417..5a354a664d1f7d 100644
--- a/tools/testing/selftests/mm/va_high_addr_switch.c
+++ b/tools/testing/selftests/mm/va_high_addr_switch.c
@@ -11,7 +11,6 @@
 
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 /*
  * The hint addr value is used to allocate addresses
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 311fc5b4513eb4..f276eee576804a 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -698,93 +698,39 @@ int unpoison_memory(unsigned long pfn)
 	return ret > 0 ? 0 : -errno;
 }
 
-int read_file(const char *path, char *buf, size_t buflen)
-{
-	int fd;
-	ssize_t numread;
-
-	fd = open(path, O_RDONLY);
-	if (fd == -1)
-		return 0;
-
-	numread = read(fd, buf, buflen - 1);
-	if (numread < 1) {
-		close(fd);
-		return 0;
-	}
-
-	buf[numread] = '\0';
-	close(fd);
-
-	return (unsigned int) numread;
-}
-
-void write_file(const char *path, const char *buf, size_t buflen)
-{
-	int fd, saved_errno;
-	ssize_t numwritten;
-
-	if (buflen < 2)
-		ksft_exit_fail_msg("Incorrect buffer len: %zu\n", buflen);
-
-	fd = open(path, O_WRONLY);
-	if (fd == -1)
-		ksft_exit_fail_msg("%s open failed: %s\n", path, strerror(errno));
-
-	numwritten = write(fd, buf, buflen - 1);
-	saved_errno = errno;
-	close(fd);
-	errno = saved_errno;
-	if (numwritten < 0)
-		ksft_exit_fail_msg("%s write(%.*s) failed: %s\n", path, (int)(buflen - 1),
-				buf, strerror(errno));
-	if (numwritten != buflen - 1)
-		ksft_exit_fail_msg("%s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\n",
-				path, (int)(buflen - 1), buf, buflen - 1, numwritten);
-}
-
-unsigned long read_num(const char *path)
-{
-	char buf[21];
-
-	if (read_file(path, buf, sizeof(buf)) < 0)
-		ksft_exit_fail_perror("read_file()");
-
-	return strtoul(buf, NULL, 10);
-}
-
-void write_num(const char *path, unsigned long num)
-{
-	char buf[21];
-
-	sprintf(buf, "%lu", num);
-	write_file(path, buf, strlen(buf) + 1);
-}
-
 static unsigned long shmall, shmmax;
 
 void __shm_limits_restore(void)
 {
-	if (shmmax)
-		write_num("/proc/sys/kernel/shmmax", shmmax);
-	if (shmall)
-		write_num("/proc/sys/kernel/shmall", shmall);
+	if (shmmax && write_num("/proc/sys/kernel/shmmax", shmmax))
+		ksft_exit_fail();
+	if (shmall && write_num("/proc/sys/kernel/shmall", shmall))
+		ksft_exit_fail();
 }
 
 void shm_limits_prepare(unsigned long length)
 {
 	unsigned long nr = length / psize();
 	unsigned long val;
+	int ret;
 
-	val = read_num("/proc/sys/kernel/shmmax");
+	ret = read_num("/proc/sys/kernel/shmmax", &val);
+	if (ret)
+		ksft_exit_fail();
 	if (val < length) {
-		write_num("/proc/sys/kernel/shmmax", length);
+		ret = write_num("/proc/sys/kernel/shmmax", length);
+		if (ret)
+			ksft_exit_fail();
 		shmmax = val;
 	}
 
-	val = read_num("/proc/sys/kernel/shmall");
+	ret = read_num("/proc/sys/kernel/shmall", &val);
+	if (ret)
+		ksft_exit_fail();
 	if (val < nr) {
-		write_num("/proc/sys/kernel/shmall", nr);
+		ret = write_num("/proc/sys/kernel/shmall", nr);
+		if (ret)
+			ksft_exit_fail();
 		shmall = val;
 	}
 }
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index ea8fc8fdf0eb0b..aa856f0d32d64e 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -8,6 +8,8 @@
 #include <unistd.h> /* _SC_PAGESIZE */
 #include "kselftest.h"
 #include <linux/fs.h>
+#include <mm/file_utils.h>
+#include <mm/hugepage_settings.h>
 
 #define BIT_ULL(nr)                   (1ULL << (nr))
 #define PM_SOFT_DIRTY                 BIT_ULL(55)
@@ -164,11 +166,6 @@ int unpoison_memory(unsigned long pfn);
 #define PAGEMAP_PRESENT(ent)	(((ent) & (1ull << 63)) != 0)
 #define PAGEMAP_PFN(ent)	((ent) & ((1ull << 55) - 1))
 
-void write_file(const char *path, const char *buf, size_t buflen);
-int read_file(const char *path, char *buf, size_t buflen);
-unsigned long read_num(const char *path);
-void write_num(const char *path, unsigned long num);
-
 void shm_limits_prepare(unsigned long length);
 void __shm_limits_restore(void);
 

3/2 2026/07/16 20:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29032
							output: 86
							thoughts: 985
						

Thoughts:
**Analyzing the Patch Series: A Focused Review**

Okay, so I'm looking at this patch series, and my initial assessment is that it seems pretty contained, which is good. The primary focus is on refactoring and adding some new tooling, specifically around the `gup_test.c` code. My understanding is that the core of the change involves splitting out utility functions from `gup_test.c` into `file_utils.c` and `hugepage_settings.c`. Makes sense; that should improve maintainability and potentially reusability of these functions. A new tool, `gup_bench.c`, has been introduced, which I assume is for benchmarking the GUP (Get User Pages) operations. I'll need to dig into that one in more detail to understand what metrics they're looking at.

From the file list, I can see the scope is fairly narrow: mostly within the `tools/`, `Documentation/`, and `MAINTAINERS` directories. This is what I expected. The diff confirms my initial understanding: there are no changes to any core kernel logic, no drivers, no architecture-specific code. Everything seems to be user-space tools and tests. This is further backed up by the file paths which I will use to assess changes.

Specifically, I'm noting the changes to the `Documentation/core-api/pin_user_pages.rst` file which likely means the documentation around the `pin_user_pages` API has been updated to reflect the changes. I also want to assess the `MAINTAINERS` file to see if any maintainership changes have been made.

Given the scope of the change, and the fact that there are no actual functional changes to the kernel itself, I believe that `WorthFuzzing` should be set to `false` for this patch. It's essentially a refactor and a new tool; there's nothing here that's directly impacting core kernel functionality that would make it a priority target for fuzzing. However, that does not mean that the code is free of bugs, so I will have a more detailed assessment of the code.



4/2 2026/07/16 20:29 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space tools (tools/mm/gup_bench.c, tools/testing/selftests/mm/), Documentation, and MAINTAINERS. It does not make any functional changes to the core kernel logic, drivers, or architectures.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space tools (tools/mm/gup_bench.c, tools/testing/selftests/mm/), Documentation, and MAINTAINERS. It does not make any functional changes to the core kernel logic, drivers, or architectures.",
  "WorthFuzzing": false
}

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