summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-08-21 13:40:46 +0100
committerMark Brown <broonie@kernel.org>2026-08-21 13:40:46 +0100
commit2258662943eecc627259dbe5df2c93a09058b36b (patch)
treec403d149374dcc68e187303dc6086c79534cff26 /tools
parent25f3c66839f7020a703af0c9becc64cae0269e89 (diff)
parentefecab401cb15fd3bb9bc05990609acb6b267ff2 (diff)
downloadlinux-next-2258662943eecc627259dbe5df2c93a09058b36b.tar.gz
linux-next-2258662943eecc627259dbe5df2c93a09058b36b.zip
Merge branch 'mm-unstable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Diffstat (limited to 'tools')
-rw-r--r--tools/testing/radix-tree/maple.c4
-rw-r--r--tools/testing/selftests/Makefile1
-rw-r--r--tools/testing/selftests/alloc_tag/Makefile8
-rw-r--r--tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c548
-rw-r--r--tools/testing/selftests/cgroup/lib/include/cgroup_util.h1
-rw-r--r--tools/testing/selftests/cgroup/test_zswap.c13
-rw-r--r--tools/testing/selftests/mm/.gitignore76
-rw-r--r--tools/testing/selftests/mm/Makefile7
-rwxr-xr-xtools/testing/selftests/mm/check_config.sh2
-rw-r--r--tools/testing/selftests/mm/compaction_test.c38
-rw-r--r--tools/testing/selftests/mm/cow.c11
-rw-r--r--tools/testing/selftests/mm/folio_split_race_test.c2
-rw-r--r--tools/testing/selftests/mm/guard-regions.c10
-rw-r--r--tools/testing/selftests/mm/gup_longterm.c2
-rw-r--r--tools/testing/selftests/mm/khugepaged.c204
-rwxr-xr-xtools/testing/selftests/mm/ksft_kmemleak_confirm.sh130
-rw-r--r--tools/testing/selftests/mm/memory-failure.c14
-rw-r--r--tools/testing/selftests/mm/merge.c57
-rw-r--r--tools/testing/selftests/mm/migration.c37
-rw-r--r--tools/testing/selftests/mm/mseal_test.c3
-rw-r--r--tools/testing/selftests/mm/pagemap_ioctl.c8
-rw-r--r--tools/testing/selftests/mm/prctl_thp_disable.c2
-rwxr-xr-xtools/testing/selftests/mm/run_vmtests.sh2
-rw-r--r--tools/testing/selftests/mm/soft-dirty.c5
-rw-r--r--tools/testing/selftests/mm/split_huge_page_test.c139
-rw-r--r--tools/testing/selftests/mm/uffd-common.c4
-rw-r--r--tools/testing/selftests/mm/vm_util.c207
-rw-r--r--tools/testing/selftests/mm/vm_util.h8
-rw-r--r--tools/testing/selftests/proc/proc-maps-race.c3
-rw-r--r--tools/testing/vma/include/dup.h64
-rw-r--r--tools/testing/vma/shared.c3
-rw-r--r--tools/testing/vma/tests/merge.c49
-rw-r--r--tools/testing/vma/tests/vma.c50
-rw-r--r--tools/testing/vma/vma_internal.h1
34 files changed, 1371 insertions, 342 deletions
diff --git a/tools/testing/radix-tree/maple.c b/tools/testing/radix-tree/maple.c
index 0607913a3022..d967e76a3c06 100644
--- a/tools/testing/radix-tree/maple.c
+++ b/tools/testing/radix-tree/maple.c
@@ -35234,7 +35234,7 @@ static noinline void __init check_prealloc(struct maple_tree *mt)
mt_set_non_kernel(1);
/* Spanning store */
mas_set_range(&mas, 1, 100);
- MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_KERNEL & GFP_NOWAIT) == 0);
+ MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_NOWAIT) == 0);
allocated = mas_allocated(&mas);
height = mas_mt_height(&mas);
MT_BUG_ON(mt, allocated != 0);
@@ -35257,7 +35257,7 @@ static noinline void __init check_prealloc(struct maple_tree *mt)
MT_BUG_ON(mt, mas_allocated(&mas) != 0);
mas_set_range(&mas, 0, 200);
mt_set_non_kernel(1);
- MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_KERNEL & GFP_NOWAIT) == 0);
+ MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_NOWAIT) == 0);
allocated = mas_allocated(&mas);
height = mas_mt_height(&mas);
MT_BUG_ON(mt, allocated != 0);
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index c62642302c84..2d960626750e 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
TARGETS += acct
+TARGETS += alloc_tag
TARGETS += alsa
TARGETS += amd-pstate
TARGETS += arm64
diff --git a/tools/testing/selftests/alloc_tag/Makefile b/tools/testing/selftests/alloc_tag/Makefile
new file mode 100644
index 000000000000..c4637f69e9c2
--- /dev/null
+++ b/tools/testing/selftests/alloc_tag/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_GEN_PROGS := allocinfo_ioctl_test
+
+CFLAGS += -Wall
+CFLAGS += $(KHDR_INCLUDES)
+
+include ../lib.mk
diff --git a/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
new file mode 100644
index 000000000000..74fd64b2370c
--- /dev/null
+++ b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
@@ -0,0 +1,548 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/* kselftest for allocinfo ioctl
+ * allocinfo ioctl retrieves allocinfo data through ioctl
+ * Copyright (C) 2026 Google, Inc.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+#include <linux/alloc_tag.h>
+#include "../kselftest.h"
+
+#define MAX_LINE_LEN 512
+#define ALLOCINFO_PROC "/proc/allocinfo"
+
+enum ioctl_ret {
+ IOCTL_SUCCESS = 0,
+ IOCTL_FAILURE = 1,
+ IOCTL_INVALID_DATA = 2,
+};
+
+#define VEC_MAX_ENTRIES 32
+
+struct allocinfo_tag_data_vec {
+ struct allocinfo_tag_data tag[VEC_MAX_ENTRIES];
+ __u64 count;
+};
+
+static inline int __allocinfo_get_content_id(int dev_fd, struct allocinfo_content_id *params)
+{
+ return ioctl(dev_fd, ALLOCINFO_IOC_CONTENT_ID, params);
+}
+
+static inline int __allocinfo_get_at(int dev_fd, struct allocinfo_get_at *params)
+{
+ return ioctl(dev_fd, ALLOCINFO_IOC_GET_AT, params);
+}
+
+static inline int __allocinfo_get_next(int dev_fd, struct allocinfo_tag_data *params)
+{
+ return ioctl(dev_fd, ALLOCINFO_IOC_GET_NEXT, params);
+}
+
+static bool match_entry(const struct allocinfo_tag_data *procfs_entry,
+ const struct allocinfo_tag_data *tag_data,
+ bool match_bytes, bool match_calls, bool match_lineno,
+ bool match_function, bool match_filename)
+{
+ if (match_bytes && tag_data->counter.bytes != procfs_entry->counter.bytes) {
+ ksft_print_msg("size retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+
+ if (match_calls && tag_data->counter.calls != procfs_entry->counter.calls) {
+ ksft_print_msg("call count retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+
+ if (match_lineno && tag_data->tag.lineno != procfs_entry->tag.lineno) {
+ ksft_print_msg("lineno retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+
+ if (match_function &&
+ strncmp(tag_data->tag.function, procfs_entry->tag.function, ALLOCINFO_STR_SIZE)) {
+ ksft_print_msg("function retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+
+ if (match_filename &&
+ strncmp(tag_data->tag.filename, procfs_entry->tag.filename, ALLOCINFO_STR_SIZE)) {
+ ksft_print_msg("filename retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+ return true;
+}
+
+static bool match_entries(const struct allocinfo_tag_data_vec *procfs_entries,
+ const struct allocinfo_tag_data_vec *tags,
+ bool match_bytes, bool match_calls, bool match_lineno,
+ bool match_function, bool match_filename)
+{
+ __u64 i;
+
+ if (procfs_entries->count != tags->count) {
+ ksft_print_msg("Entry count mismatch. ioctl entries: %llu, proc entries: %llu\n",
+ tags->count, procfs_entries->count);
+ return false;
+ }
+ for (i = 0; i < procfs_entries->count; i++) {
+ if (!match_entry(&procfs_entries->tag[i], &tags->tag[i],
+ match_bytes, match_calls, match_lineno,
+ match_function, match_filename)) {
+ ksft_print_msg("%lluth entry does not match.\n", i);
+ return false;
+ }
+ }
+ return true;
+}
+
+static const char *allocinfo_str(const char *str)
+{
+ size_t len = strlen(str);
+
+ if (len >= ALLOCINFO_STR_SIZE)
+ str += (len - ALLOCINFO_STR_SIZE) + 1;
+ return str;
+}
+
+static void allocinfo_copy_str(char *dest, const char *src)
+{
+ strncpy(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE - 1);
+ dest[ALLOCINFO_STR_SIZE - 1] = '\0';
+}
+
+static int get_filtered_procfs_entries(struct allocinfo_tag_data_vec *procfs_entries,
+ const struct allocinfo_filter *filter)
+{
+ FILE *fp = fopen(ALLOCINFO_PROC, "r");
+ char line[MAX_LINE_LEN];
+ int matches;
+ struct allocinfo_tag_data procfs_entry;
+
+ if (!fp) {
+ ksft_print_msg("Failed to open " ALLOCINFO_PROC " for reading\n");
+ return 1;
+ }
+ memset(procfs_entries, 0, sizeof(*procfs_entries));
+ while (fgets(line, sizeof(line), fp) && procfs_entries->count < VEC_MAX_ENTRIES) {
+ char filename[MAX_LINE_LEN];
+ char function[MAX_LINE_LEN];
+
+ memset(&procfs_entry, 0, sizeof(procfs_entry));
+ matches = sscanf(line, "%llu %llu %[^:]:%llu func:%s",
+ &procfs_entry.counter.bytes,
+ &procfs_entry.counter.calls,
+ filename,
+ &procfs_entry.tag.lineno,
+ function);
+
+ if (matches != 5)
+ continue;
+
+ allocinfo_copy_str(procfs_entry.tag.filename, filename);
+ allocinfo_copy_str(procfs_entry.tag.function, function);
+
+ if (filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) {
+ if (strncmp(procfs_entry.tag.filename,
+ filter->fields.filename, ALLOCINFO_STR_SIZE))
+ continue;
+ }
+ if (filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) {
+ if (strncmp(procfs_entry.tag.function,
+ filter->fields.function, ALLOCINFO_STR_SIZE))
+ continue;
+ }
+ if (filter->mask & ALLOCINFO_FILTER_MASK_LINENO) {
+ if (procfs_entry.tag.lineno != filter->fields.lineno)
+ continue;
+ }
+ if (filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) {
+ if (procfs_entry.counter.bytes < filter->min_size)
+ continue;
+ }
+ if (filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) {
+ if (procfs_entry.counter.bytes > filter->max_size)
+ continue;
+ }
+
+ memcpy(&procfs_entries->tag[procfs_entries->count++], &procfs_entry,
+ sizeof(procfs_entry));
+ }
+ fclose(fp);
+ return 0;
+}
+
+static enum ioctl_ret get_filtered_ioctl_entries(struct allocinfo_tag_data_vec *tags,
+ const struct allocinfo_filter *filter,
+ __u64 start_pos)
+{
+ int fd = open(ALLOCINFO_PROC, O_RDONLY);
+
+ if (fd < 0) {
+ ksft_print_msg("Failed to open " ALLOCINFO_PROC " for IOCTL\n");
+ return IOCTL_FAILURE;
+ }
+
+ struct allocinfo_content_id start_cont_id, end_cont_id;
+ struct allocinfo_get_at get_at_params;
+ const int max_retries = 10;
+ int retry_count = 0;
+ int status;
+
+ /*
+ * __allocinfo_get_content_id may return different values if a kernel module was loaded
+ * between the two calls. If that happens, the data gathered cannot be considered consistent
+ * and hence needs to be fetched again to avoid flakiness.
+ */
+ do {
+ if (__allocinfo_get_content_id(fd, &start_cont_id)) {
+ ksft_print_msg("allocinfo_get_content_id failed\n");
+ status = IOCTL_FAILURE;
+ break;
+ }
+
+ memset(tags, 0, sizeof(*tags));
+ memset(&get_at_params, 0, sizeof(get_at_params));
+ memcpy(&get_at_params.filter, filter, sizeof(*filter));
+ get_at_params.pos = start_pos;
+ if (__allocinfo_get_at(fd, &get_at_params)) {
+ ksft_print_msg("allocinfo_get_at failed\n");
+ status = IOCTL_FAILURE;
+ break;
+ }
+ memcpy(&tags->tag[tags->count++], &get_at_params.data, sizeof(get_at_params.data));
+
+ while (tags->count < VEC_MAX_ENTRIES &&
+ __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0)
+ tags->count++;
+
+ if (__allocinfo_get_content_id(fd, &end_cont_id)) {
+ ksft_print_msg("allocinfo_get_content_id failed\n");
+ status = IOCTL_FAILURE;
+ break;
+ }
+
+ if (start_cont_id.id == end_cont_id.id) {
+ status = IOCTL_SUCCESS;
+ } else {
+ ksft_print_msg("allocinfo_get_content_id mismatch, retrying...\n");
+ status = IOCTL_INVALID_DATA;
+ }
+ } while (status == IOCTL_INVALID_DATA && retry_count++ < max_retries);
+
+ close(fd);
+ return status;
+}
+
+static int run_filter_test(const struct allocinfo_filter *filter)
+{
+ struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
+ struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
+ int ioctl_status;
+ int ret = KSFT_PASS;
+
+ if (!tags || !procfs_entries) {
+ ksft_print_msg("Memory allocation failed.\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ if (get_filtered_procfs_entries(procfs_entries, filter)) {
+ ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+
+ if (procfs_entries->count == 0) {
+ ksft_print_msg("No entries found in " ALLOCINFO_PROC ", skipping test\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+
+ ioctl_status = get_filtered_ioctl_entries(tags, filter, 0);
+ if (ioctl_status == IOCTL_INVALID_DATA) {
+ ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+ if (ioctl_status == IOCTL_FAILURE) {
+ ksft_print_msg("Error retrieving IOCTL entries.\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ if (!match_entries(procfs_entries, tags, false, false, true, true, true))
+ ret = KSFT_FAIL;
+
+exit:
+ free(tags);
+ free(procfs_entries);
+ return ret;
+}
+
+static int test_filename_filter(void)
+{
+ struct allocinfo_filter filter;
+ const char *target_filename = "mm/memory.c";
+
+ memset(&filter, 0, sizeof(filter));
+ filter.mask |= ALLOCINFO_FILTER_MASK_FILENAME;
+ strncpy(filter.fields.filename, target_filename, ALLOCINFO_STR_SIZE);
+
+ return run_filter_test(&filter);
+}
+
+static int test_function_filter(void)
+{
+ struct allocinfo_filter filter;
+ const char *target_function = "dup_mm";
+
+ memset(&filter, 0, sizeof(filter));
+ filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
+ strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE);
+
+ return run_filter_test(&filter);
+}
+
+static int test_size_filter(void)
+{
+ int fd;
+ struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
+ struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
+ struct allocinfo_filter filter;
+ int ret = KSFT_PASS;
+ __u64 target_size, i, pos;
+ struct allocinfo_tag_data *found_tag = NULL;
+ const char *target_function = "do_init_module";
+ struct allocinfo_content_id start_cont_id, end_cont_id;
+ int retry = 0;
+ const int max_retries = 10;
+
+ if (!tags || !procfs_entries) {
+ ksft_print_msg("Memory allocation failed.\n");
+ ret = KSFT_FAIL;
+ goto freemem;
+ }
+
+ fd = open(ALLOCINFO_PROC, O_RDONLY);
+ if (fd < 0) {
+ ksft_print_msg("Failed to open " ALLOCINFO_PROC ": %s\n", strerror(errno));
+ ret = KSFT_SKIP;
+ goto freemem;
+ }
+
+ do {
+ found_tag = NULL;
+ pos = 0;
+
+ if (__allocinfo_get_content_id(fd, &start_cont_id)) {
+ ksft_print_msg("allocinfo_get_content_id failed\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ memset(&filter, 0, sizeof(filter));
+ filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
+ strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE);
+
+ if (get_filtered_procfs_entries(procfs_entries, &filter)) {
+ ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+
+ if (procfs_entries->count == 0) {
+ ksft_print_msg("Function %s not found in procfs\n", target_function);
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+
+ target_size = procfs_entries->tag[0].counter.bytes;
+
+ memset(&filter, 0, sizeof(filter));
+ filter.mask |= ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE;
+ filter.min_size = target_size;
+ filter.max_size = target_size;
+
+ while (1) {
+ struct allocinfo_get_at get_at_params;
+
+ memset(&get_at_params, 0, sizeof(get_at_params));
+ memcpy(&get_at_params.filter, &filter, sizeof(filter));
+ get_at_params.pos = pos;
+
+ if (__allocinfo_get_at(fd, &get_at_params))
+ break;
+
+ tags->count = 0;
+ memcpy(&tags->tag[tags->count++], &get_at_params.data,
+ sizeof(get_at_params.data));
+
+ while (tags->count < VEC_MAX_ENTRIES &&
+ __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0)
+ tags->count++;
+
+ for (i = 0; i < tags->count; i++) {
+ if (strcmp(tags->tag[i].tag.function, target_function) == 0) {
+ found_tag = &tags->tag[i];
+ break;
+ }
+ }
+
+ if (found_tag || tags->count < VEC_MAX_ENTRIES)
+ break;
+
+ pos += tags->count;
+ }
+
+ if (__allocinfo_get_content_id(fd, &end_cont_id)) {
+ ksft_print_msg("allocinfo_get_content_id failed\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ if (start_cont_id.id == end_cont_id.id)
+ break;
+
+ ksft_print_msg("Module load detected during size verification, retrying...\n");
+ } while (retry++ < max_retries);
+
+ if (start_cont_id.id == end_cont_id.id && !found_tag) {
+ ksft_print_msg("Entry with function %s not found in IOCTL results\n",
+ target_function);
+ ret = KSFT_FAIL;
+ } else if (start_cont_id.id != end_cont_id.id) {
+ ksft_print_msg("Failed to match content_ids for procfs and IOCTL, skipping...\n");
+ ret = KSFT_SKIP;
+ } else if (found_tag && found_tag->counter.bytes != target_size) {
+ ksft_print_msg("IOCTL entry size %llu does not match target size %llu\n",
+ found_tag->counter.bytes, target_size);
+ ret = KSFT_FAIL;
+ }
+
+exit:
+ close(fd);
+freemem:
+ free(tags);
+ free(procfs_entries);
+ return ret;
+}
+
+static int test_lineno_filter(void)
+{
+ struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
+ struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
+ struct allocinfo_filter filter;
+ enum ioctl_ret ioctl_status;
+ int ret = KSFT_PASS;
+ __u64 target_lineno, i;
+ struct allocinfo_tag_data *target_tag;
+ bool found = false;
+
+ if (!tags || !procfs_entries) {
+ ksft_print_msg("Memory allocation failed.\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ memset(&filter, 0, sizeof(filter));
+
+ if (get_filtered_procfs_entries(procfs_entries, &filter)) {
+ ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+ if (procfs_entries->count == 0) {
+ ksft_print_msg("Could not retrieve procfs entries\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+ /*
+ * We depend on the procfs results to determine the line number for the filter before
+ * making the ioctl query. Hence, we cannot reuse run_filter_test here.
+ */
+ target_tag = &procfs_entries->tag[0];
+ target_lineno = target_tag->tag.lineno;
+
+ filter.mask |= ALLOCINFO_FILTER_MASK_LINENO;
+ filter.fields.lineno = target_lineno;
+
+ ioctl_status = get_filtered_ioctl_entries(tags, &filter, 0);
+ if (ioctl_status == IOCTL_INVALID_DATA) {
+ ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+ if (ioctl_status == IOCTL_FAILURE) {
+ ksft_print_msg("Error retrieving IOCTL entries.\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ for (i = 0; i < tags->count; i++) {
+ if (tags->tag[i].tag.lineno != target_lineno) {
+ ksft_print_msg("IOCTL entry %llu has incorrect lineno %llu.\n",
+ i, tags->tag[i].tag.lineno);
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ if (strncmp(tags->tag[i].tag.function, target_tag->tag.function,
+ ALLOCINFO_STR_SIZE) == 0 &&
+ strncmp(tags->tag[i].tag.filename, target_tag->tag.filename,
+ ALLOCINFO_STR_SIZE) == 0)
+ found = true;
+ }
+
+ if (!found) {
+ ksft_print_msg("Original procfs entry not found in IOCTL lineno filter results.\n");
+ ret = KSFT_FAIL;
+ }
+
+exit:
+ free(tags);
+ free(procfs_entries);
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+
+ ksft_set_plan(4);
+
+ ret = test_filename_filter();
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("Skipping test_filename_filter\n");
+ else
+ ksft_test_result(ret == KSFT_PASS, "test_filename_filter\n");
+
+ ret = test_function_filter();
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("Skipping test_function_filter\n");
+ else
+ ksft_test_result(ret == KSFT_PASS, "test_function_filter\n");
+
+ ret = test_size_filter();
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("Skipping test_size_filter\n");
+ else
+ ksft_test_result(ret == KSFT_PASS, "test_size_filter\n");
+
+ ret = test_lineno_filter();
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("Skipping test_lineno_filter\n");
+ else
+ ksft_test_result(ret == KSFT_PASS, "test_lineno_filter\n");
+
+ ksft_finished();
+}
diff --git a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h
index 8ebb2b4d4ec0..5d39c709ac7a 100644
--- a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h
+++ b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h
@@ -7,6 +7,7 @@
#endif
#define MB(x) (x << 20)
+#define GB(x) ((unsigned long long)(x) << 30)
#define NSEC_PER_USEC 1000L
#define USEC_PER_SEC 1000000L
diff --git a/tools/testing/selftests/cgroup/test_zswap.c b/tools/testing/selftests/cgroup/test_zswap.c
index 49b36ee79160..f7b4c4370db6 100644
--- a/tools/testing/selftests/cgroup/test_zswap.c
+++ b/tools/testing/selftests/cgroup/test_zswap.c
@@ -20,6 +20,7 @@ static int page_size;
#define PATH_ZSWAP "/sys/module/zswap"
#define PATH_ZSWAP_ENABLED "/sys/module/zswap/parameters/enabled"
+#define PATH_ZSWAP_STORED_PAGES "/sys/kernel/debug/zswap/stored_pages"
static int read_int(const char *path, size_t *value)
{
@@ -55,7 +56,7 @@ static int read_min_free_kb(size_t *value)
static int get_zswap_stored_pages(size_t *value)
{
- return read_int("/sys/kernel/debug/zswap/stored_pages", value);
+ return read_int(PATH_ZSWAP_STORED_PAGES, value);
}
static long get_cg_wb_count(const char *cg)
@@ -570,8 +571,16 @@ static int test_no_kmem_bypass(const char *root)
/* Read sys info and compute test values accordingly */
if (sysinfo(&sys_info) != 0)
return KSFT_FAIL;
- if (sys_info.totalram > 5000000000)
+ if (sys_info.totalram > GB(4)) {
+ ksft_print_msg(
+ "requires less than 4GB total ram, sys_info.totalram: %.1fGB\n",
+ (double)sys_info.totalram / GB(1));
return KSFT_SKIP;
+ }
+ if (access(PATH_ZSWAP_STORED_PAGES, R_OK)) {
+ ksft_print_msg("debugfs not mounted at /sys/kernel/debug\n");
+ return KSFT_SKIP;
+ }
values = mmap(0, sizeof(struct no_kmem_bypass_child_args), PROT_READ |
PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (values == MAP_FAILED)
diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore
index 9ccd9e1447e6..fcd892ed21e3 100644
--- a/tools/testing/selftests/mm/.gitignore
+++ b/tools/testing/selftests/mm/.gitignore
@@ -1,68 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
-cow
-hugepage-mmap
-hugepage-mremap
-hugepage-shm
-hugepage-vmemmap
-hugetlb-mmap
-hugetlb-mremap
-hugetlb-shm
-hugetlb-vmemmap
-hugetlb-madvise
-hugetlb-read-hwpoison
-hugetlb-soft-offline
-khugepaged
-map_hugetlb
-map_populate
-thuge-gen
-compaction_test
-memory-failure
-migration
-mlock2-tests
-mrelease_test
-mremap_dontunmap
-mremap_test
-on-fault-limit
-transhuge-stress
-pagemap_ioctl
-pfnmap
-process_madv
-*.tmp*
-protection_keys
-protection_keys_32
-protection_keys_64
-madv_populate
-uffd-stress
-uffd-unit-tests
-uffd-wp-mremap
-mlock-intersect-test
-mlock-random-test
-virtual_address_range
-gup_test
-va_128TBswitch
-map_fixed_noreplace
-write_to_hugetlbfs
-hmm-tests
-memfd_secret
-soft-dirty
-split_huge_page_test
-ksm_tests
-local_config.h
-local_config.mk
-ksm_functional_tests
-mdwe_test
-gup_longterm
-mkdirty
-va_high_addr_switch
-hugetlb_fault_after_madv
-hugetlb_madv_vs_map
-mseal_test
-droppable
-hugetlb_dio
-pkey_sighandler_tests_32
-pkey_sighandler_tests_64
-guard-regions
-merge
-prctl_thp_disable
-rmap
-folio_split_race_test
+*
+!/**/
+!*.c
+!*.h
+!*.sh
+!.gitignore
+!Makefile
+!config
+!settings
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index 277a141d662e..2d5366196e30 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -5,7 +5,7 @@
# script so kunit knows to run it, and add it to the list below.
# If you do not YOUR TESTS WILL NOT RUN IN THE CI.
-LOCAL_HDRS += $(selfdir)/mm/local_config.h $(top_srcdir)/mm/gup_test.h
+LOCAL_HDRS += $(selfdir)/mm/local_config.h_gen $(top_srcdir)/mm/gup_test.h
LOCAL_HDRS += $(selfdir)/mm/mseal_helpers.h
include local_config.mk
@@ -149,6 +149,7 @@ TEST_PROGS += ksft_gup_test.sh
TEST_PROGS += ksft_hmm.sh
TEST_PROGS += ksft_hugetlb.sh
TEST_PROGS += ksft_hugevm.sh
+TEST_PROGS += ksft_kmemleak_confirm.sh
TEST_PROGS += ksft_kmemleak_dedup.sh
TEST_PROGS += ksft_ksm.sh
TEST_PROGS += ksft_ksm_numa.sh
@@ -261,11 +262,11 @@ $(OUTPUT)/migration: LDLIBS += -lnuma
$(OUTPUT)/rmap: LDLIBS += -lnuma
-local_config.mk local_config.h: check_config.sh
+local_config.mk local_config.h_gen: check_config.sh
$(call msg,CHK,config,$@)
$(Q)CC="$(CC)" CFLAGS="$(CFLAGS)" ./check_config.sh
-EXTRA_CLEAN += local_config.mk local_config.h
+EXTRA_CLEAN += local_config.mk local_config.h_gen
ifeq ($(IOURING_EXTRA_LIBS),)
all: warn_missing_liburing
diff --git a/tools/testing/selftests/mm/check_config.sh b/tools/testing/selftests/mm/check_config.sh
index 32beaefe279e..1c603261e93d 100755
--- a/tools/testing/selftests/mm/check_config.sh
+++ b/tools/testing/selftests/mm/check_config.sh
@@ -4,7 +4,7 @@
# Probe for libraries and create header files to record the results. Both C
# header files and Makefile include fragments are created.
-OUTPUT_H_FILE=local_config.h
+OUTPUT_H_FILE=local_config.h_gen
OUTPUT_MKFILE=local_config.mk
tmpname=$(mktemp)
diff --git a/tools/testing/selftests/mm/compaction_test.c b/tools/testing/selftests/mm/compaction_test.c
index 5b582588e015..30d4ace7155a 100644
--- a/tools/testing/selftests/mm/compaction_test.c
+++ b/tools/testing/selftests/mm/compaction_test.c
@@ -29,30 +29,34 @@ struct map_list {
int read_memory_info(unsigned long *memfree, unsigned long *hugepagesize)
{
- char buffer[256] = {0};
- char *cmd = "cat /proc/meminfo | grep -i memfree | grep -o '[0-9]*'";
- FILE *cmdfile = popen(cmd, "r");
+ char buffer[256];
+ int found = 0;
+ FILE *file;
+ int ret = -1;
- if (!(fgets(buffer, sizeof(buffer), cmdfile))) {
- ksft_print_msg("Failed to read meminfo: %s\n", strerror(errno));
+ file = fopen("/proc/meminfo", "r");
+ if (!file) {
+ ksft_print_msg("Failed to open /proc/meminfo: %s\n",
+ strerror(errno));
return -1;
}
- pclose(cmdfile);
-
- *memfree = atoll(buffer);
- cmd = "cat /proc/meminfo | grep -i hugepagesize | grep -o '[0-9]*'";
- cmdfile = popen(cmd, "r");
-
- if (!(fgets(buffer, sizeof(buffer), cmdfile))) {
- ksft_print_msg("Failed to read meminfo: %s\n", strerror(errno));
- return -1;
+ while (fgets(buffer, sizeof(buffer), file) && found != 2) {
+ if (sscanf(buffer, "MemFree: %lu kB", memfree) == 1 ||
+ sscanf(buffer, "Hugepagesize: %lu kB", hugepagesize) == 1)
+ found++;
}
- pclose(cmdfile);
- *hugepagesize = atoll(buffer);
+ if (ferror(file))
+ ksft_print_msg("Failed to read /proc/meminfo: %s\n",
+ strerror(errno));
+ else if (found != 2)
+ ksft_print_msg("Failed to parse /proc/meminfo\n");
+ else
+ ret = 0;
- return 0;
+ fclose(file);
+ return ret;
}
int prereq(void)
diff --git a/tools/testing/selftests/mm/cow.c b/tools/testing/selftests/mm/cow.c
index 0c627ea89ff7..8aa5249d9bef 100644
--- a/tools/testing/selftests/mm/cow.c
+++ b/tools/testing/selftests/mm/cow.c
@@ -21,7 +21,7 @@
#include <sys/wait.h>
#include <linux/memfd.h>
-#include "local_config.h"
+#include "local_config.h_gen"
#ifdef LOCAL_CONFIG_HAVE_LIBURING
#include <liburing.h>
#endif /* LOCAL_CONFIG_HAVE_LIBURING */
@@ -1718,8 +1718,13 @@ static void run_with_tmpfile(non_anon_test_fn fn, const char *desc)
/* File consists of a single page filled with zeroes. */
if (fallocate(fd, 0, 0, pagesize)) {
- ksft_perror("fallocate() failed");
- log_test_result(KSFT_FAIL);
+ if (errno == EOPNOTSUPP) {
+ ksft_print_msg("fallocate() not supported by filesystem\n");
+ log_test_result(KSFT_SKIP);
+ } else {
+ ksft_perror("fallocate() failed");
+ log_test_result(KSFT_FAIL);
+ }
goto close;
}
diff --git a/tools/testing/selftests/mm/folio_split_race_test.c b/tools/testing/selftests/mm/folio_split_race_test.c
index 6329e37fff4c..45b84f7b364e 100644
--- a/tools/testing/selftests/mm/folio_split_race_test.c
+++ b/tools/testing/selftests/mm/folio_split_race_test.c
@@ -182,7 +182,7 @@ static uint64_t run_iteration(void)
for (i = 0; i < TOTAL_PAGES; i++)
fill_page(mmap_base, i);
- if (!check_huge_shmem(mmap_base, NR_PMD_PAGE, pmd_pagesize))
+ if (!check_huge_shmem(mmap_base, FILE_SIZE, NR_PMD_PAGE, pmd_pagesize))
ksft_exit_fail_msg("No shmem THP is allocated\n");
if (pthread_barrier_init(&ctl.barrier, NULL, NUM_READER_THREADS + 1) != 0)
diff --git a/tools/testing/selftests/mm/guard-regions.c b/tools/testing/selftests/mm/guard-regions.c
index b21df3040b1c..5c8ec3ca75d7 100644
--- a/tools/testing/selftests/mm/guard-regions.c
+++ b/tools/testing/selftests/mm/guard-regions.c
@@ -1912,7 +1912,7 @@ TEST_F(guard_regions, hole_punch)
{
const unsigned long page_size = self->page_size;
char *ptr;
- int i;
+ int i, ret;
if (variant->backing == ANON_BACKED)
SKIP(return, "Truncation test specific to file-backed");
@@ -1944,8 +1944,12 @@ TEST_F(guard_regions, hole_punch)
}
/* Now hole punch the guarded region. */
- ASSERT_EQ(madvise(&ptr[3 * page_size], 4 * page_size,
- MADV_REMOVE), 0);
+ ret = madvise(&ptr[3 * page_size], 4 * page_size, MADV_REMOVE);
+ if (ret == -1 && errno == EOPNOTSUPP) {
+ ASSERT_EQ(munmap(ptr, 10 * page_size), 0);
+ SKIP(return, "MADV_REMOVE not supported by filesystem");
+ }
+ ASSERT_EQ(ret, 0);
/* Ensure guard regions remain. */
for (i = 0; i < 10; i++) {
diff --git a/tools/testing/selftests/mm/gup_longterm.c b/tools/testing/selftests/mm/gup_longterm.c
index c03b4f8910c0..510de93be681 100644
--- a/tools/testing/selftests/mm/gup_longterm.c
+++ b/tools/testing/selftests/mm/gup_longterm.c
@@ -21,7 +21,7 @@
#include <linux/magic.h>
#include <linux/memfd.h>
-#include "local_config.h"
+#include "local_config.h_gen"
#ifdef LOCAL_CONFIG_HAVE_LIBURING
#include <liburing.h>
#endif /* LOCAL_CONFIG_HAVE_LIBURING */
diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index 10e8dedcb087..1d2d6bd72fd2 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -26,9 +26,11 @@
#define BASE_ADDR ((void *)(1UL << 30))
static unsigned long hpage_pmd_size;
+static int hpage_pmd_order;
static unsigned long page_size;
static int hpage_pmd_nr;
static int anon_order;
+static int collapse_order;
#define PID_SMAPS "/proc/self/smaps"
#define TEST_FILE "collapse_test_file"
@@ -51,7 +53,7 @@ struct mem_ops {
void *(*setup_area)(int nr_hpages);
void (*cleanup_area)(void *p, unsigned long size);
void (*fault)(void *p, unsigned long start, unsigned long end);
- bool (*check_huge)(void *addr, int nr_hpages);
+ bool (*check_huge)(void *addr, size_t len, int nr_hpages, unsigned long hpage_size);
const char *name;
};
@@ -69,6 +71,7 @@ struct collapse_context {
};
static struct collapse_context *khugepaged_context;
+static struct collapse_context *mthp_khugepaged_context;
static struct collapse_context *madvise_context;
struct file_info {
@@ -121,7 +124,8 @@ static void get_finfo(const char *dir)
char *str, *end;
finfo.dir = dir;
- stat(finfo.dir, &path_stat);
+ if (stat(finfo.dir, &path_stat))
+ ksft_exit_fail_perror("stat()");
if (!S_ISDIR(path_stat.st_mode))
ksft_exit_fail_msg("%s: Not a directory (%s)\n", __func__, finfo.dir);
if (snprintf(finfo.path, sizeof(finfo.path), "%s/" TEST_FILE,
@@ -138,8 +142,8 @@ 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)");
+ if (!read_file(path, buf, sizeof(buf)))
+ ksft_exit_fail_perror("read_file(uevent)");
if (strstr(buf, "DEVTYPE=disk")) {
/* Found it */
if (snprintf(finfo.dev_queue_read_ahead_path,
@@ -276,7 +280,7 @@ static void *alloc_hpage(struct mem_ops *ops)
ksft_print_msg("Allocate huge page...");
if (madvise_collapse_retry(p, hpage_pmd_size))
ksft_exit_fail_perror("madvise(MADV_COLLAPSE)");
- if (!ops->check_huge(p, 1))
+ if (!ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
ksft_exit_fail_perror("madvise(MADV_COLLAPSE)");
if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)");
@@ -310,9 +314,10 @@ static void anon_fault(void *p, unsigned long start, unsigned long end)
fill_memory(p, start, end);
}
-static bool anon_check_huge(void *addr, int nr_hpages)
+static bool anon_check_huge(void *addr, size_t len, int nr_hpages,
+ unsigned long hpage_size)
{
- return check_huge_anon(addr, nr_hpages, hpage_pmd_size);
+ return check_huge_anon(addr, len, nr_hpages, hpage_size);
}
static void *file_setup_area_common(int nr_hpages, enum file_setup_ops setup)
@@ -412,13 +417,14 @@ static void file_fault_write(void *p, unsigned long start, unsigned long end)
ksft_exit_fail_perror("madvise(MADV_POPULATE_WRITE)");
}
-static bool file_check_huge(void *addr, int nr_hpages)
+static bool file_check_huge(void *addr, size_t len, int nr_hpages,
+ unsigned long hpage_size)
{
switch (finfo.type) {
case VMA_FILE:
- return check_huge_file(addr, nr_hpages, hpage_pmd_size);
+ return check_huge_file(addr, len, nr_hpages, hpage_size);
case VMA_SHMEM:
- return check_huge_shmem(addr, nr_hpages, hpage_pmd_size);
+ return check_huge_shmem(addr, len, nr_hpages, hpage_size);
default:
exit(EXIT_FAILURE);
return false;
@@ -448,9 +454,10 @@ static void shmem_cleanup_area(void *p, unsigned long size)
close(finfo.fd);
}
-static bool shmem_check_huge(void *addr, int nr_hpages)
+static bool shmem_check_huge(void *addr, size_t len, int nr_hpages,
+ unsigned long hpage_size)
{
- return check_huge_shmem(addr, nr_hpages, hpage_pmd_size);
+ return check_huge_shmem(addr, len, nr_hpages, hpage_size);
}
static struct mem_ops __anon_ops = {
@@ -533,7 +540,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages,
ret = madvise_collapse_retry(p, nr_hpages * hpage_pmd_size);
if (((bool)ret) == expect)
fail("Fail: Bad return value");
- else if (!ops->check_huge(p, expect ? nr_hpages : 0))
+ else if (!ops->check_huge(p, nr_hpages * hpage_pmd_size, expect ? nr_hpages : 0, hpage_pmd_size))
fail("Fail: check_huge()");
else
success("OK");
@@ -545,30 +552,31 @@ static void madvise_collapse(const char *msg, char *p, int nr_hpages,
struct mem_ops *ops, bool expect)
{
/* Sanity check */
- if (!ops->check_huge(p, 0))
+ if (!ops->check_huge(p, nr_hpages * hpage_pmd_size, 0, hpage_pmd_size))
ksft_exit_fail_msg("Unexpected huge page\n");
__madvise_collapse(msg, p, nr_hpages, ops, expect);
}
#define TICK 500000
-static bool wait_for_scan(const char *msg, char *p, int nr_hpages,
- struct mem_ops *ops)
+static bool wait_for_scan(const char *msg, char *p, size_t len,
+ int nr_hpages, int collap_order, struct mem_ops *ops)
{
+ unsigned long hpage_size = page_size << collap_order;
int full_scans;
int timeout = 6; /* 3 seconds */
/* Sanity check */
- if (!ops->check_huge(p, 0))
+ if (!ops->check_huge(p, len, 0, hpage_size))
ksft_exit_fail_msg("Unexpected huge page\n");
- madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE);
+ madvise(p, len, MADV_HUGEPAGE);
/* Wait until the second full_scan completed */
full_scans = thp_read_num("khugepaged/full_scans") + 2;
ksft_print_msg("%s...", msg);
while (timeout--) {
- if (ops->check_huge(p, nr_hpages))
+ if (ops->check_huge(p, len, nr_hpages, hpage_size))
break;
if (thp_read_num("khugepaged/full_scans") >= full_scans)
break;
@@ -582,6 +590,8 @@ static bool wait_for_scan(const char *msg, char *p, int nr_hpages,
static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,
struct mem_ops *ops, bool expect)
{
+ size_t len = nr_hpages * hpage_pmd_size;
+
/*
* read&write file collapse fails since khugepaged does not flush
* the target dirty folios
@@ -589,7 +599,7 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,
if (!is_tmpfs(ops) && ops == &__read_write_file_write_ops)
expect = false;
- if (wait_for_scan(msg, p, nr_hpages, ops)) {
+ if (wait_for_scan(msg, p, len, nr_hpages, hpage_pmd_order, ops)) {
if (expect)
fail("Timeout");
else
@@ -605,10 +615,54 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,
if (ops != &__anon_ops)
ops->fault(p, 0, nr_hpages * hpage_pmd_size);
- if (ops->check_huge(p, expect ? nr_hpages : 0))
+ if (ops->check_huge(p, len, expect ? nr_hpages : 0, hpage_pmd_size))
+ success("OK");
+ else
+ fail("Fail");
+}
+
+static void mthp_khugepaged_collapse(const char *msg, char *p, int nr_hpages,
+ struct mem_ops *ops, bool expect)
+{
+ unsigned long hpage_size = page_size << collapse_order;
+ struct thp_settings settings = *thp_current_settings();
+ /* mTHP collpase only allocates PMD sized memory */
+ size_t len = hpage_pmd_size;
+
+ /* Set mTHP setting for mTHP collapse */
+ if (ops == &__anon_ops) {
+ settings.thp_enabled = THP_NEVER;
+ settings.hugepages[collapse_order].enabled = THP_MADVISE;
+ }
+
+ thp_push_settings(&settings);
+
+ if (wait_for_scan(msg, p, len, nr_hpages, collapse_order, ops)) {
+ if (expect)
+ fail("Timeout");
+ else
+ success("OK");
+
+ /* Restore THP settings for mTHP collapse. */
+ thp_pop_settings();
+ return;
+ }
+
+ /*
+ * For file and shmem memory, khugepaged only retracts pte entries after
+ * putting the new hugepage in the page cache. The hugepage must be
+ * subsequently refaulted to install the pmd mapping for the mm.
+ */
+ if (ops != &__anon_ops)
+ ops->fault(p, 0, nr_hpages * hpage_size);
+
+ if (ops->check_huge(p, len, expect ? nr_hpages : 0, hpage_size))
success("OK");
else
fail("Fail");
+
+ /* Restore THP settings for mTHP collapse. */
+ thp_pop_settings();
}
static struct collapse_context __khugepaged_context = {
@@ -617,6 +671,12 @@ static struct collapse_context __khugepaged_context = {
.name = "khugepaged",
};
+static struct collapse_context __mthp_khugepaged_context = {
+ .collapse = &mthp_khugepaged_collapse,
+ .enforce_pte_scan_limits = true,
+ .name = "mthp_khugepaged",
+};
+
static struct collapse_context __madvise_context = {
.collapse = &madvise_collapse,
.enforce_pte_scan_limits = false,
@@ -634,7 +694,7 @@ static void alloc_at_fault(void)
p = alloc_mapping(1);
*p = 1;
ksft_print_msg("Allocate huge page on fault...");
- if (check_huge_anon(p, 1, hpage_pmd_size))
+ if (check_huge_anon(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -643,7 +703,7 @@ static void alloc_at_fault(void)
madvise(p, page_size, MADV_DONTNEED);
ksft_print_msg("Split huge PMD on MADV_DONTNEED...");
- if (check_huge_anon(p, 0, hpage_pmd_size))
+ if (check_huge_anon(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -655,10 +715,17 @@ static void alloc_at_fault(void)
static void collapse_full(struct collapse_context *c, struct mem_ops *ops)
{
void *p;
- int nr_hpages = 4;
+ int nr_pmds = 4, nr_hpages = 4;
unsigned long size = nr_hpages * hpage_pmd_size;
- p = ops->setup_area(nr_hpages);
+ /* Only try 1 PMD sized range for mTHP collapse. */
+ if (c == &__mthp_khugepaged_context) {
+ nr_pmds = 1;
+ nr_hpages = 1 << (hpage_pmd_order - collapse_order);
+ size = hpage_pmd_size;
+ }
+
+ p = ops->setup_area(nr_pmds);
ops->fault(p, 0, size);
c->collapse("Collapse multiple fully populated PTE table", p, nr_hpages,
ops, true);
@@ -670,10 +737,31 @@ static void collapse_full(struct collapse_context *c, struct mem_ops *ops)
static void collapse_empty(struct collapse_context *c, struct mem_ops *ops)
{
+ int nr_hpages = 1;
+ void *p;
+
+ if (c == &__mthp_khugepaged_context)
+ nr_hpages = 1 << (hpage_pmd_order - collapse_order);
+
+ p = ops->setup_area(1);
+ c->collapse("Do not collapse empty PTE table", p, nr_hpages, ops, false);
+ ops->cleanup_area(p, hpage_pmd_size);
+ ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
+static void collapse_single_mthp(struct collapse_context *c, struct mem_ops *ops)
+{
+ unsigned long hpage_size = page_size << collapse_order;
void *p;
p = ops->setup_area(1);
- c->collapse("Do not collapse empty PTE table", p, 1, ops, false);
+ /*
+ * Only fault collapse_order sized ranges, and only check 1
+ * collapse_order sized huge page.
+ */
+ ops->fault(p, 0, hpage_size);
+ c->collapse("Collapse PTE table with half PTE entries present",
+ p, 1, ops, true);
ops->cleanup_area(p, hpage_pmd_size);
ksft_test_result_report(exit_status, "%s\n", __func__);
}
@@ -815,7 +903,7 @@ static void collapse_single_pte_entry_compound(struct collapse_context *c, struc
madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE);
ksft_print_msg("Split huge page leaving single PTE mapping compound page...");
madvise(p + page_size, hpage_pmd_size - page_size, MADV_DONTNEED);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -836,7 +924,7 @@ static void collapse_full_of_compound(struct collapse_context *c, struct mem_ops
ksft_print_msg("Split huge page leaving single PTE page table full of compound pages...");
madvise(p, page_size, MADV_NOHUGEPAGE);
madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -858,7 +946,7 @@ static void collapse_compound_extreme(struct collapse_context *c, struct mem_ops
for (i = 0; i < hpage_pmd_nr; i++) {
madvise(BASE_ADDR, hpage_pmd_size, MADV_HUGEPAGE);
ops->fault(BASE_ADDR, 0, hpage_pmd_size);
- if (!ops->check_huge(BASE_ADDR, 1))
+ if (!ops->check_huge(BASE_ADDR, hpage_pmd_size, 1, hpage_pmd_size))
ksft_exit_fail_msg("Failed to allocate huge page\n");
madvise(BASE_ADDR, hpage_pmd_size, MADV_NOHUGEPAGE);
@@ -881,7 +969,7 @@ static void collapse_compound_extreme(struct collapse_context *c, struct mem_ops
ops->cleanup_area(BASE_ADDR, hpage_pmd_size);
ops->fault(p, 0, hpage_pmd_size);
- if (!ops->check_huge(p, 1))
+ if (!ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -903,7 +991,7 @@ static void collapse_fork(struct collapse_context *c, struct mem_ops *ops)
ksft_print_msg("Allocate small page...");
ops->fault(p, 0, page_size);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -911,7 +999,7 @@ static void collapse_fork(struct collapse_context *c, struct mem_ops *ops)
ksft_print_msg("Share small page over fork()...");
if (!fork()) {
/* Do not touch settings on child exit */
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -929,7 +1017,7 @@ static void collapse_fork(struct collapse_context *c, struct mem_ops *ops)
exit_status = WEXITSTATUS(wstatus);
ksft_print_msg("Check if parent still has small page...");
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -947,7 +1035,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o
ksft_print_msg("Share huge page over fork()...");
if (!fork()) {
/* Do not touch settings on child exit */
- if (ops->check_huge(p, 1))
+ if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -955,7 +1043,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o
ksft_print_msg("Split huge page PMD in child process...");
madvise(p, page_size, MADV_NOHUGEPAGE);
madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -976,7 +1064,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o
exit_status = WEXITSTATUS(wstatus);
ksft_print_msg("Check if parent still has huge page...");
- if (ops->check_huge(p, 1))
+ if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -995,7 +1083,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops
ksft_print_msg("Share huge page over fork()...");
if (!fork()) {
/* Do not touch settings on child exit */
- if (ops->check_huge(p, 1))
+ if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -1003,7 +1091,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops
ksft_print_msg("Trigger CoW on page %d of %d...",
hpage_pmd_nr - max_ptes_shared - 1, hpage_pmd_nr);
ops->fault(p, 0, (hpage_pmd_nr - max_ptes_shared - 1) * page_size);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -1016,7 +1104,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops
hpage_pmd_nr - max_ptes_shared, hpage_pmd_nr);
ops->fault(p, 0, (hpage_pmd_nr - max_ptes_shared) *
page_size);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -1034,7 +1122,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops
exit_status = WEXITSTATUS(wstatus);
ksft_print_msg("Check if parent still has huge page...");
- if (ops->check_huge(p, 1))
+ if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -1075,8 +1163,8 @@ static void madvise_retracted_page_tables(struct collapse_context *c,
ops->fault(p, 0, size);
/* Let khugepaged collapse and leave pmd cleared */
- if (wait_for_scan("Collapse and leave PMD cleared", p, nr_hpages,
- ops)) {
+ if (wait_for_scan("Collapse and leave PMD cleared", p, size, nr_hpages,
+ hpage_pmd_order, ops)) {
fail("Timeout");
return;
}
@@ -1092,17 +1180,19 @@ static void usage(void)
{
fprintf(stderr, "\nUsage: ./khugepaged [OPTIONS] <test type> [dir]\n\n");
fprintf(stderr, "\t<test type>\t: <context>:<mem_type>\n");
- fprintf(stderr, "\t<context>\t: [all|khugepaged|madvise]\n");
+ fprintf(stderr, "\t<context>\t: [all|khugepaged|mthp_khugepaged|madvise]\n");
fprintf(stderr, "\t<mem_type>\t: [all|anon|file|shmem]\n");
fprintf(stderr, "\n\t\"file,all\" mem_type requires [dir] argument\n");
fprintf(stderr, "\n\t\"file,all\" mem_type requires a file system\n");
fprintf(stderr, "\twith PMD-sized large folio support\n");
fprintf(stderr, "\n\tif [dir] is a (sub)directory of a tmpfs mount, tmpfs must be\n");
fprintf(stderr, "\tmounted with huge=advise option for khugepaged tests to work\n");
+ fprintf(stderr, "\n\tmthp_khugepaged only supports anon mem_type now.\n");
fprintf(stderr, "\n\tSupported Options:\n");
fprintf(stderr, "\t\t-h: This help message.\n");
fprintf(stderr, "\t\t-s: mTHP size, expressed as page order.\n");
fprintf(stderr, "\t\t Defaults to 0. Use this size for anon or shmem allocations.\n");
+ fprintf(stderr, "\t\t-c: collapse order for mTHP collapse, expressed as page order.\n");
exit(1);
}
@@ -1112,11 +1202,14 @@ static void parse_test_type(int argc, char **argv)
char *buf;
const char *token;
- while ((opt = getopt(argc, argv, "s:h")) != -1) {
+ while ((opt = getopt(argc, argv, "s:c:h")) != -1) {
switch (opt) {
case 's':
anon_order = atoi(optarg);
break;
+ case 'c':
+ collapse_order = atoi(optarg);
+ break;
case 'h':
default:
usage();
@@ -1142,6 +1235,10 @@ static void parse_test_type(int argc, char **argv)
madvise_context = &__madvise_context;
} else if (!strcmp(token, "khugepaged")) {
khugepaged_context = &__khugepaged_context;
+ } else if (!strcmp(token, "mthp_khugepaged")) {
+ mthp_khugepaged_context = &__mthp_khugepaged_context;
+ if (collapse_order <= 0 || collapse_order >= hpage_pmd_order)
+ usage();
} else if (!strcmp(token, "madvise")) {
madvise_context = &__madvise_context;
} else {
@@ -1157,14 +1254,20 @@ static void parse_test_type(int argc, char **argv)
read_write_file_write_ops = &__read_write_file_write_ops;
anon_ops = &__anon_ops;
shmem_ops = &__shmem_ops;
+ if (mthp_khugepaged_context)
+ usage();
} else if (!strcmp(buf, "anon")) {
anon_ops = &__anon_ops;
} else if (!strcmp(buf, "file")) {
read_only_file_ops = &__read_only_file_ops;
read_write_file_read_ops = &__read_write_file_read_ops;
read_write_file_write_ops = &__read_write_file_write_ops;
+ if (mthp_khugepaged_context)
+ usage();
} else if (!strcmp(buf, "shmem")) {
shmem_ops = &__shmem_ops;
+ if (mthp_khugepaged_context)
+ usage();
} else {
usage();
}
@@ -1207,7 +1310,6 @@ static int nr_test_cases;
int main(int argc, char **argv)
{
- int hpage_pmd_order;
struct thp_settings default_settings = {
.thp_enabled = THP_MADVISE,
.thp_defrag = THP_DEFRAG_ALWAYS,
@@ -1233,10 +1335,6 @@ int main(int argc, char **argv)
if (!thp_is_enabled())
ksft_exit_skip("Transparent Hugepages not available\n");
- parse_test_type(argc, argv);
-
- setbuf(stdout, NULL);
-
page_size = getpagesize();
hpage_pmd_size = read_pmd_pagesize();
if (!hpage_pmd_size)
@@ -1244,6 +1342,10 @@ int main(int argc, char **argv)
hpage_pmd_nr = hpage_pmd_size / page_size;
hpage_pmd_order = __builtin_ctz(hpage_pmd_nr);
+ parse_test_type(argc, argv);
+
+ setbuf(stdout, NULL);
+
default_settings.khugepaged.max_ptes_none = hpage_pmd_nr - 1;
default_settings.khugepaged.max_ptes_swap = hpage_pmd_nr / 8;
default_settings.khugepaged.max_ptes_shared = hpage_pmd_nr / 2;
@@ -1261,6 +1363,7 @@ int main(int argc, char **argv)
TEST(collapse_full, khugepaged_context, read_write_file_read_ops);
TEST(collapse_full, khugepaged_context, read_write_file_write_ops);
TEST(collapse_full, khugepaged_context, shmem_ops);
+ TEST(collapse_full, mthp_khugepaged_context, anon_ops);
TEST(collapse_full, madvise_context, anon_ops);
TEST(collapse_full, madvise_context, read_only_file_ops);
TEST(collapse_full, madvise_context, read_write_file_read_ops);
@@ -1268,8 +1371,11 @@ int main(int argc, char **argv)
TEST(collapse_full, madvise_context, shmem_ops);
TEST(collapse_empty, khugepaged_context, anon_ops);
+ TEST(collapse_empty, mthp_khugepaged_context, anon_ops);
TEST(collapse_empty, madvise_context, anon_ops);
+ TEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops);
+
TEST(collapse_single_pte_entry, khugepaged_context, anon_ops);
TEST(collapse_single_pte_entry, khugepaged_context, read_only_file_ops);
TEST(collapse_single_pte_entry, khugepaged_context, read_write_file_read_ops);
diff --git a/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh b/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh
new file mode 100755
index 000000000000..72ded5e6794c
--- /dev/null
+++ b/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh
@@ -0,0 +1,130 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Functional test for kmemleak's N-consecutive-scan leak confirmation
+# (the min_unref_scans module parameter).
+#
+# kmemleak only reports an object once it has stayed unreferenced for
+# min_unref_scans consecutive scans. A threshold of 1 reports on the first
+# scan (historical behaviour); higher values filter transient false
+# positives where a live object's only reference is briefly invisible to a
+# single scan (e.g. an RCU tree update in flight while the scan runs). The
+# test loads samples/kmemleak's helper module to create orphan allocations
+# and, counting only those orphans (matched by their [kmemleak_test]
+# backtrace so unrelated leaks already present on the system are ignored),
+# checks that:
+# - a freshly allocated object is greyed on its first scan (its checksum
+# settles then), so nothing can be reported before that priming scan;
+# each case below primes once first,
+# - at min_unref_scans=1 one scan after priming reports the orphans,
+# - raising the threshold to 2 needs two scans after priming: one is not
+# enough, the second reports,
+# - the parameter reads back what was written.
+#
+# The "one post-prime scan is not enough at min_unref_scans=2" check is the
+# core regression test: raising min_unref_scans must push the report
+# strictly later. Like ksft_kmemleak_dedup.sh, if the module yields no
+# detectable orphan at all in the running environment the test skips rather
+# than failing.
+#
+# Author: Breno Leitao <leitao@debian.org>
+
+# KTAP output helpers (ktap_skip_all, ktap_exit_fail_msg, ktap_test_pass, ...).
+DIR="$(dirname "$(readlink -f "$0")")"
+# shellcheck source=../kselftest/ktap_helpers.sh
+source "${DIR}"/../kselftest/ktap_helpers.sh
+
+KMEMLEAK=/sys/kernel/debug/kmemleak
+PARAM=/sys/module/kmemleak/parameters/min_unref_scans
+MODULE=kmemleak-test
+AGE=6 # seconds; must exceed kmemleak's 5s minimum object age
+
+ktap_print_header
+
+[ "$(id -u)" -eq 0 ] || { ktap_skip_all "must run as root"; exit "$KSFT_SKIP"; }
+[ -r "$KMEMLEAK" ] ||
+ { ktap_skip_all "no kmemleak debugfs (CONFIG_DEBUG_KMEMLEAK)"; exit "$KSFT_SKIP"; }
+[ -w "$PARAM" ] ||
+ { ktap_skip_all "min_unref_scans module parameter not present"; exit "$KSFT_SKIP"; }
+modinfo "$MODULE" >/dev/null 2>&1 ||
+ { ktap_skip_all "$MODULE not built (CONFIG_SAMPLE_KMEMLEAK)"; exit "$KSFT_SKIP"; }
+
+# kmemleak can be present but disabled at runtime (kmemleak=off boot arg,
+# or it self-disabled after an internal error); a "scan" then returns
+# EPERM. Probe once and skip if so.
+echo scan > "$KMEMLEAK" 2>/dev/null ||
+ { ktap_skip_all "kmemleak is disabled (check dmesg or kmemleak= boot arg)"; exit "$KSFT_SKIP"; }
+
+prev=$(cat "$PARAM")
+# shellcheck disable=SC2317 # invoked indirectly via trap
+cleanup() {
+ echo "$prev" > "$PARAM" 2>/dev/null # restore the parameter
+ echo scan=on > "$KMEMLEAK" 2>/dev/null # re-enable auto scan
+ rmmod "$MODULE" 2>/dev/null
+ echo clear > "$KMEMLEAK" 2>/dev/null
+}
+trap cleanup EXIT
+
+# Stop the automatic scan thread: only our manual scans should advance an
+# object's consecutive-unreferenced run. An auto scan landing between two
+# manual scans would change the result and make the test flaky.
+echo scan=off > "$KMEMLEAK" 2>/dev/null
+
+# Create a fresh, aged set of orphan objects from the helper module's init
+# path (its kmalloc/vmalloc/percpu allocations are dropped right away).
+# Pre-existing reported leaks are greyed first ("clear") so only our
+# orphans are counted. The module is left loaded on purpose: once it is
+# unloaded its symbols are gone, so the orphan backtraces no longer resolve
+# to [kmemleak_test] and could not be matched below.
+gen_orphans() {
+ rmmod "$MODULE" 2>/dev/null
+ echo clear > "$KMEMLEAK"
+ modprobe "$MODULE" ||
+ { ktap_skip_all "failed to load $MODULE"; exit "$KSFT_SKIP"; }
+ sleep "$AGE"
+}
+
+scan() { echo scan > "$KMEMLEAK"; }
+
+# Number of helper-module orphans currently reported by kmemleak. Matching
+# the module's own backtrace ([kmemleak_test]) keeps the count immune to
+# unrelated leaks on the running system. kmemleak only lists an object here
+# once it has been reported, so this reflects the confirmation gating.
+count_orphans() {
+ c=$(grep -c '\[kmemleak_test\]' "$KMEMLEAK" 2>/dev/null)
+ echo "${c:-0}"
+}
+
+# 0) the parameter reads back what was written.
+echo 3 > "$PARAM"
+[ "$(cat "$PARAM")" = "3" ] || ktap_exit_fail_msg "min_unref_scans did not read back as 3"
+
+# Priming scan: kmemleak greys a freshly allocated object on its first scan
+# (its checksum settles then), so nothing can be reported until a second
+# scan. Every case below runs this priming scan before counting.
+prime() { scan; }
+
+# 1) min_unref_scans=1: one scan after priming reports the orphans. This
+# also establishes that the helper produces detectable orphans here.
+echo 1 > "$PARAM"
+gen_orphans
+prime
+scan
+first=$(count_orphans)
+[ "$first" -gt 0 ] ||
+ { ktap_skip_all "$MODULE produced no detectable orphans (cannot test min_unref_scans)"; exit "$KSFT_SKIP"; }
+
+# 2) min_unref_scans=2: after priming, one scan is not enough (still
+# gated), the second reports. The gated-scan-zero check is the core
+# regression.
+echo 2 > "$PARAM"
+gen_orphans
+prime
+scan; s1=$(count_orphans)
+scan; s2=$(count_orphans)
+[ "$s1" -eq 0 ] || ktap_exit_fail_msg "min_unref_scans=2: $s1 orphan(s) after 1 post-prime scan (must be 0)"
+[ "$s2" -gt 0 ] || ktap_exit_fail_msg "min_unref_scans=2: no report after 2 post-prime scans (false negative)"
+
+ktap_set_plan 1
+ktap_test_pass "min_unref_scans=1 reported $first orphan(s) one scan after priming; =2 held them one scan longer ($s1 after one scan, $s2 after two); param read-back ok"
+ktap_finished
diff --git a/tools/testing/selftests/mm/memory-failure.c b/tools/testing/selftests/mm/memory-failure.c
index 1a5a32e22cce..f3cb578b1609 100644
--- a/tools/testing/selftests/mm/memory-failure.c
+++ b/tools/testing/selftests/mm/memory-failure.c
@@ -287,8 +287,10 @@ TEST_F(memory_failure, clean_pagecache)
if (fd < 0)
SKIP(return, "failed to open test file.\n");
fs_type = get_fs_type(fd);
- if (!fs_type || fs_type == TMPFS_MAGIC)
+ if (!fs_type || fs_type == TMPFS_MAGIC) {
+ close(fd);
SKIP(return, "unsupported filesystem :%x\n", fs_type);
+ }
addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
@@ -327,8 +329,16 @@ TEST_F(memory_failure, dirty_pagecache)
if (fd < 0)
SKIP(return, "failed to open test file.\n");
fs_type = get_fs_type(fd);
- if (!fs_type || fs_type == TMPFS_MAGIC)
+ /*
+ * MADV_HARD poisoning of dirty page-cache data records an expected
+ * -EIO in the file mapping. NFS reports this error on close(), so
+ * skip this variant.
+ */
+ if (!fs_type || fs_type == TMPFS_MAGIC ||
+ (fs_type == NFS_SUPER_MAGIC && variant->type == MADV_HARD)) {
+ close(fd);
SKIP(return, "unsupported filesystem :%x\n", fs_type);
+ }
addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
diff --git a/tools/testing/selftests/mm/merge.c b/tools/testing/selftests/mm/merge.c
index 519e5ac02db7..52b8727b6628 100644
--- a/tools/testing/selftests/mm/merge.c
+++ b/tools/testing/selftests/mm/merge.c
@@ -1305,6 +1305,63 @@ TEST_F(merge, merge_vmas_with_mseal)
ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 2 * page_size);
}
+TEST_F(merge, anon_and_page_offset_mismatch_memfd)
+{
+ struct procmap_fd *procmap = &self->procmap;
+ unsigned int page_size = self->page_size;
+ char *carveout = self->carveout;
+ char *ptr, *ptr2;
+ int fd;
+
+ /* Create a 10 page memfd descriptor. */
+ fd = memfd_create("anon_page_offset_test", MFD_CLOEXEC);
+ ASSERT_NE(fd, -1);
+ ASSERT_EQ(ftruncate(fd, 10 * page_size), 0);
+
+ /* Map a region using the memfd at page offset 0. */
+ ptr = mmap(carveout, 5 * page_size, PROT_READ | PROT_WRITE,
+ MAP_FIXED | MAP_PRIVATE, fd, 0);
+ ASSERT_NE(ptr, MAP_FAILED);
+
+ /*
+ * Map another separately and trigger a CoW fault at page offset 5:
+ *
+ * |-----------| |---------|
+ * | unfaulted | | faulted |
+ * |-----------| |---------|
+ */
+ ptr2 = mmap(&carveout[10 * page_size], 5 * page_size,
+ PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE,
+ fd, 5 * page_size);
+ ASSERT_NE(ptr2, MAP_FAILED);
+ ptr2[0] = 'x';
+
+ /*
+ * Now move it in place:
+ *
+ * |----------|
+ * | |
+ * v |
+ * |-----------| |---------|
+ * | unfaulted | | faulted |
+ * |-----------| |---------|
+ *
+ * Because the anonymous page offset of the faulted region is now
+ * &carveout[10 * page_size], despite the two regions being mergeable
+ * due to file page offset, they are NOT mergeable due to anonymous
+ * page offset.
+ */
+ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size,
+ MREMAP_MAYMOVE | MREMAP_FIXED,
+ &carveout[5 * page_size]);
+ ASSERT_NE(ptr2, MAP_FAILED);
+
+ /* Assert that they did not merge. */
+ ASSERT_TRUE(find_vma_procmap(procmap, ptr));
+ ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
+ ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 5 * page_size);
+}
+
TEST_F(merge_with_fork, mremap_faulted_to_unfaulted_prev)
{
struct procmap_fd *procmap = &self->procmap;
diff --git a/tools/testing/selftests/mm/migration.c b/tools/testing/selftests/mm/migration.c
index 29f7492453d4..f19d53c69576 100644
--- a/tools/testing/selftests/mm/migration.c
+++ b/tools/testing/selftests/mm/migration.c
@@ -7,7 +7,7 @@
#include "kselftest_harness.h"
#include "hugepage_settings.h"
-#include <strings.h>
+#include <string.h>
#include <pthread.h>
#include <numa.h>
#include <numaif.h>
@@ -20,7 +20,6 @@
#define TWOMEG (2<<20)
#define RUNTIME (20)
-#define MAX_RETRIES 100
#define ALIGN(x, a) (((x) + (a - 1)) & (~((a) - 1)))
HUGETLB_SETUP_DEFAULT_PAGES(1)
@@ -110,7 +109,7 @@ int migrate(uint64_t *ptr, int n1, int n2)
int ret, tmp;
int status = 0;
struct timespec ts1, ts2;
- int failures = 0;
+ int success = 0;
if (clock_gettime(CLOCK_MONOTONIC, &ts1))
return -1;
@@ -119,29 +118,33 @@ int migrate(uint64_t *ptr, int n1, int n2)
if (clock_gettime(CLOCK_MONOTONIC, &ts2))
return -1;
- if (ts2.tv_sec - ts1.tv_sec >= RUNTIME)
- return 0;
+ if (ts2.tv_sec - ts1.tv_sec >= RUNTIME) {
+ /* Reaching both targets verifies a cross-node move. */
+ if (success >= 2)
+ return 0;
+ else
+ return -2;
+ }
ret = move_pages(0, 1, (void **) &ptr, &n2, &status,
MPOL_MF_MOVE_ALL);
- if (ret) {
- if (ret > 0) {
- /* Migration is best effort; try again */
- if (++failures < MAX_RETRIES)
- continue;
- printf("Didn't migrate %d pages\n", ret);
- }
- else
- perror("Couldn't migrate pages");
+ if (ret < 0) {
+ perror("Couldn't migrate pages");
+ return ret;
+ }
+ /* Migration is best effort. Try again */
+ if (ret > 0 || status < 0)
+ continue;
+ if (status != n2) {
+ printf("Page is on node %d instead of target node %d\n",
+ status, n2);
return -2;
}
- failures = 0;
+ success++;
tmp = n2;
n2 = n1;
n1 = tmp;
}
-
- return 0;
}
void *access_mem(void *ptr)
diff --git a/tools/testing/selftests/mm/mseal_test.c b/tools/testing/selftests/mm/mseal_test.c
index 93c2e13094d4..1a05e6921fed 100644
--- a/tools/testing/selftests/mm/mseal_test.c
+++ b/tools/testing/selftests/mm/mseal_test.c
@@ -1876,7 +1876,7 @@ int main(void)
if (!pkey_supported())
ksft_print_msg("PKEY not supported\n");
- ksft_set_plan(88);
+ ksft_set_plan(87);
test_seal_addseal();
test_seal_unmapped_start();
@@ -1914,7 +1914,6 @@ int main(void)
test_seal_mprotect_partial_mprotect(true);
test_seal_mprotect_two_vma_with_gap();
- test_seal_mprotect_two_vma_with_gap();
test_seal_mprotect_merge(false);
test_seal_mprotect_merge(true);
diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c
index 1b2dffcc999b..eadc7159ca5b 100644
--- a/tools/testing/selftests/mm/pagemap_ioctl.c
+++ b/tools/testing/selftests/mm/pagemap_ioctl.c
@@ -1085,7 +1085,7 @@ static void unpopulated_written_test(const char *name, char *mem, long size,
memset(mem, 1, size);
if (use_thp &&
(madvise(mem, size, MADV_COLLAPSE) ||
- !check_huge_anon(mem, size / hpage_size, hpage_size))) {
+ !check_huge_anon(mem, size, size / hpage_size, hpage_size))) {
ksft_test_result_skip("%s could not form a THP\n", name);
goto out;
}
@@ -1332,12 +1332,6 @@ int mprotect_tests(void)
int ret;
char *mem, *mem2;
struct page_region vec;
- int pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
-
- if (pagemap_fd < 0) {
- fprintf(stderr, "open() failed\n");
- exit(1);
- }
/* 1. Map two pages */
mem = mmap(0, 2 * page_size, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
diff --git a/tools/testing/selftests/mm/prctl_thp_disable.c b/tools/testing/selftests/mm/prctl_thp_disable.c
index d8d9d1de57b8..82c6e96ea6eb 100644
--- a/tools/testing/selftests/mm/prctl_thp_disable.c
+++ b/tools/testing/selftests/mm/prctl_thp_disable.c
@@ -67,7 +67,7 @@ static int test_mmap_thp(enum thp_collapse_type madvise_buf, size_t pmdsize)
/* HACK: make sure we have a separate VMA that we can check reliably. */
mprotect(mem, pmdsize, PROT_READ);
- ret = check_huge_anon(mem, 1, pmdsize);
+ ret = check_huge_anon(mem, pmdsize, 1, pmdsize);
munmap(mmap_mem, mmap_size);
return ret;
}
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 687d115e3bd8..d09f9f6a384e 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -410,6 +410,8 @@ CATEGORY="thp" run_test ./khugepaged all:shmem
CATEGORY="thp" run_test ./khugepaged -s 4 all:shmem
+CATEGORY="thp" run_test ./khugepaged -c 4 mthp_khugepaged:anon
+
# Try to create XFS if not provided
if [ -z "${SPLIT_HUGE_PAGE_TEST_XFS_PATH}" ]; then
if test_selected "thp"; then
diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c
index fb1864a68e1c..5f278913c4d7 100644
--- a/tools/testing/selftests/mm/soft-dirty.c
+++ b/tools/testing/selftests/mm/soft-dirty.c
@@ -103,7 +103,7 @@ static void test_hugepage(int pagemap_fd, int pagesize)
for (i = 0; i < hpage_len; i++)
map[i] = (char)i;
- if (check_huge_anon(map, 1, hpage_len)) {
+ if (check_huge_anon(map, hpage_len, 1, hpage_len)) {
ksft_test_result_pass("Test %s huge page allocation\n", __func__);
clear_softdirty();
@@ -152,7 +152,8 @@ static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
return;
}
unlink(fname);
- ftruncate(test_fd, pagesize);
+ if (ftruncate(test_fd, pagesize) != 0)
+ ksft_exit_fail_msg("ftruncate failed\n");
map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
MAP_SHARED, test_fd, 0);
if (map == MAP_FAILED)
diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 32b991472f74..86a603692826 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -104,129 +104,6 @@ fail:
return false;
}
-static int vaddr_pageflags_get(char *vaddr, int pagemap_fd, int kpageflags_fd,
- uint64_t *flags)
-{
- unsigned long pfn;
-
- pfn = pagemap_get_pfn(pagemap_fd, vaddr);
-
- /* non-present PFN */
- if (pfn == -1UL)
- return 1;
-
- if (pageflags_get(pfn, kpageflags_fd, flags))
- return -1;
-
- return 0;
-}
-
-/*
- * gather_after_split_folio_orders - scan through [vaddr_start, len) and record
- * folio orders
- *
- * @vaddr_start: start vaddr
- * @len: range length
- * @pagemap_fd: file descriptor to /proc/<pid>/pagemap
- * @kpageflags_fd: file descriptor to /proc/kpageflags
- * @orders: output folio order array
- * @nr_orders: folio order array size
- *
- * gather_after_split_folio_orders() scan through [vaddr_start, len) and check
- * all folios within the range and record their orders. All order-0 pages will
- * be recorded. Non-present vaddr is skipped.
- *
- * NOTE: the function is used to check folio orders after a split is performed,
- * so it assumes [vaddr_start, len) fully maps to after-split folios within that
- * range.
- *
- * Return: 0 - no error, -1 - unhandled cases
- */
-static int gather_after_split_folio_orders(char *vaddr_start, size_t len,
- int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders)
-{
- uint64_t page_flags = 0;
- int cur_order = -1;
- char *vaddr;
-
- if (pagemap_fd == -1 || kpageflags_fd == -1)
- return -1;
- if (!orders)
- return -1;
- if (nr_orders <= 0)
- return -1;
-
- for (vaddr = vaddr_start; vaddr < vaddr_start + len;) {
- char *next_folio_vaddr;
- int status;
-
- status = vaddr_pageflags_get(vaddr, pagemap_fd, kpageflags_fd,
- &page_flags);
- if (status < 0)
- return -1;
-
- /* skip non present vaddr */
- if (status == 1) {
- vaddr += psize();
- continue;
- }
-
- /* all order-0 pages with possible false postive (non folio) */
- if (!(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) {
- orders[0]++;
- vaddr += psize();
- continue;
- }
-
- /* skip non thp compound pages */
- if (!(page_flags & KPF_THP)) {
- vaddr += psize();
- continue;
- }
-
- /* vpn points to part of a THP at this point */
- if (page_flags & KPF_COMPOUND_HEAD)
- cur_order = 1;
- else {
- vaddr += psize();
- continue;
- }
-
- next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
-
- if (next_folio_vaddr >= vaddr_start + len)
- break;
-
- while ((status = vaddr_pageflags_get(next_folio_vaddr,
- pagemap_fd, kpageflags_fd,
- &page_flags)) >= 0) {
- /*
- * non present vaddr, next compound head page, or
- * order-0 page
- */
- if (status == 1 ||
- (page_flags & KPF_COMPOUND_HEAD) ||
- !(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) {
- if (cur_order < nr_orders) {
- orders[cur_order]++;
- cur_order = -1;
- vaddr = next_folio_vaddr;
- }
- break;
- }
-
- cur_order++;
- next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
- }
-
- if (status < 0)
- return status;
- }
- if (cur_order > 0 && cur_order < nr_orders)
- orders[cur_order]++;
- return 0;
-}
-
static int check_after_split_folio_orders(char *vaddr_start, size_t len,
int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders)
{
@@ -240,7 +117,7 @@ static int check_after_split_folio_orders(char *vaddr_start, size_t len,
ksft_exit_fail_msg("Cannot allocate memory for vaddr_orders");
memset(vaddr_orders, 0, sizeof(int) * nr_orders);
- status = gather_after_split_folio_orders(vaddr_start, len, pagemap_fd,
+ status = gather_folio_orders(vaddr_start, len, pagemap_fd,
kpageflags_fd, vaddr_orders, nr_orders);
if (status)
ksft_exit_fail_msg("gather folio info failed\n");
@@ -296,7 +173,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hp
unsigned long rss_anon_before, rss_anon_after;
size_t i;
- if (!check_huge_anon(one_page, nr_hpages, pmd_pagesize))
+ if (!check_huge_anon(one_page, nr_hpages * pmd_pagesize, nr_hpages, pmd_pagesize))
ksft_exit_fail_msg("No THP is allocated\n");
rss_anon_before = rss_anon();
@@ -311,7 +188,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hp
if (one_page[i] != (char)0)
ksft_exit_fail_msg("%ld byte corrupted\n", i);
- if (!check_huge_anon(one_page, 0, pmd_pagesize))
+ if (!check_huge_anon(one_page, nr_hpages * pmd_pagesize, 0, pmd_pagesize))
ksft_exit_fail_msg("Still AnonHugePages not split\n");
rss_anon_after = rss_anon();
@@ -347,7 +224,7 @@ static void split_pmd_thp_to_order(int order)
for (i = 0; i < len; i++)
one_page[i] = (char)i;
- if (!check_huge_anon(one_page, 4, pmd_pagesize))
+ if (!check_huge_anon(one_page, 4 * pmd_pagesize, 4, pmd_pagesize))
ksft_exit_fail_msg("No THP is allocated\n");
/* split all THPs */
@@ -366,7 +243,7 @@ static void split_pmd_thp_to_order(int order)
(pmd_order + 1)))
ksft_exit_fail_msg("Unexpected THP split\n");
- if (!check_huge_anon(one_page, 0, pmd_pagesize))
+ if (!check_huge_anon(one_page, 4 * pmd_pagesize, 0, pmd_pagesize))
ksft_exit_fail_msg("Still AnonHugePages not split\n");
ksft_test_result_pass("Split huge pages to order %d successful\n", order);
@@ -393,7 +270,7 @@ static void split_pte_mapped_thp(void)
for (i = 0; i < thp_area_size; i++)
thp_area[i] = (char)i;
- if (!check_huge_anon(thp_area, nr_thps, pmd_pagesize)) {
+ if (!check_huge_anon(thp_area, nr_thps * pmd_pagesize, nr_thps, pmd_pagesize)) {
ksft_test_result_skip("Not all THPs allocated\n");
goto out;
}
@@ -657,7 +534,7 @@ static int create_pagecache_thp_and_fd(const char *testfile, size_t fd_size,
force_read_pages(*addr, fd_size / pmd_pagesize, pmd_pagesize);
- if (!check_huge_file(*addr, fd_size / pmd_pagesize, pmd_pagesize)) {
+ if (!check_huge_file(*addr, fd_size, fd_size / pmd_pagesize, pmd_pagesize)) {
ksft_print_msg("No large pagecache folio generated, please provide a filesystem supporting large folio\n");
munmap(*addr, fd_size);
close(*fd);
@@ -735,7 +612,7 @@ static void split_thp_in_pagecache_to_order_at(size_t fd_size,
goto out;
}
- if (!check_huge_file(addr, 0, pmd_pagesize)) {
+ if (!check_huge_file(addr, fd_size, 0, pmd_pagesize)) {
ksft_print_msg("Still FilePmdMapped not split\n");
err = EXIT_FAILURE;
goto out;
diff --git a/tools/testing/selftests/mm/uffd-common.c b/tools/testing/selftests/mm/uffd-common.c
index f48f5d4594ab..1fb967ef4985 100644
--- a/tools/testing/selftests/mm/uffd-common.c
+++ b/tools/testing/selftests/mm/uffd-common.c
@@ -194,7 +194,9 @@ static void shmem_alias_mapping(uffd_global_test_opts_t *gopts, __u64 *start,
static void shmem_check_pmd_mapping(uffd_global_test_opts_t *gopts, void *p, int expect_nr_hpages)
{
- if (!check_huge_shmem(gopts->area_dst_alias, expect_nr_hpages,
+ size_t len = expect_nr_hpages * read_pmd_pagesize();
+
+ if (!check_huge_shmem(gopts->area_dst_alias, len, expect_nr_hpages,
read_pmd_pagesize()))
err("Did not find expected %d number of hugepages",
expect_nr_hpages);
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index ef1ea11981a7..4821a3563036 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -15,6 +15,9 @@
#define SMAP_FILE_PATH "/proc/self/smaps"
#define STATUS_FILE_PATH "/proc/self/status"
#define MAX_LINE_LENGTH 500
+#define PAGEMAP_PATH "/proc/self/pagemap"
+#define KPAGEFLAGS_PATH "/proc/kpageflags"
+#define MAX_NR_ORDERS 20
unsigned int __page_size;
unsigned int __page_shift;
@@ -31,7 +34,7 @@ uint64_t pagemap_get_entry(int fd, char *start)
return entry;
}
-static uint64_t __pagemap_scan_get_categories(int fd, char *start, struct page_region *r)
+static int __pagemap_scan_get_categories(int fd, char *start, struct page_region *r)
{
struct pm_scan_arg arg;
@@ -55,7 +58,7 @@ static uint64_t __pagemap_scan_get_categories(int fd, char *start, struct page_r
static uint64_t pagemap_scan_get_categories(int fd, char *start)
{
struct page_region r;
- long ret;
+ int ret;
ret = __pagemap_scan_get_categories(fd, start, &r);
if (ret < 0)
@@ -194,6 +197,125 @@ err_out:
return rss_anon;
}
+static int vaddr_pageflags_get(char *vaddr, int pagemap_fd, int kpageflags_fd,
+ uint64_t *flags)
+{
+ unsigned long pfn;
+
+ pfn = pagemap_get_pfn(pagemap_fd, vaddr);
+
+ /* non-present PFN */
+ if (pfn == -1UL)
+ return 1;
+
+ if (pageflags_get(pfn, kpageflags_fd, flags))
+ return -1;
+
+ return 0;
+}
+
+/*
+ * gather_folio_orders - scan through [vaddr_start, len) and record
+ * folio orders
+ *
+ * @vaddr_start: start vaddr
+ * @len: range length
+ * @pagemap_fd: file descriptor to /proc/<pid>/pagemap
+ * @kpageflags_fd: file descriptor to /proc/kpageflags
+ * @orders: output folio order array
+ * @nr_orders: folio order array size
+ *
+ * gather_folio_orders() scan through [vaddr_start, len) and check
+ * all folios within the range and record their orders. All order-0 pages will
+ * be recorded. Non-present vaddr is skipped.
+ *
+ * Return: 0 - no error, -1 - unhandled cases
+ */
+int gather_folio_orders(char *vaddr_start, size_t len,
+ int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders)
+{
+ uint64_t page_flags = 0;
+ int cur_order = -1;
+ char *vaddr;
+
+ if (pagemap_fd == -1 || kpageflags_fd == -1)
+ return -1;
+ if (!orders)
+ return -1;
+ if (nr_orders <= 0)
+ return -1;
+
+ for (vaddr = vaddr_start; vaddr < vaddr_start + len;) {
+ char *next_folio_vaddr;
+ int status;
+
+ status = vaddr_pageflags_get(vaddr, pagemap_fd, kpageflags_fd,
+ &page_flags);
+ if (status < 0)
+ return -1;
+
+ /* skip non present vaddr */
+ if (status == 1) {
+ vaddr += psize();
+ continue;
+ }
+
+ /* all order-0 pages with possible false postive (non folio) */
+ if (!(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) {
+ orders[0]++;
+ vaddr += psize();
+ continue;
+ }
+
+ /* skip non thp compound pages */
+ if (!(page_flags & KPF_THP)) {
+ vaddr += psize();
+ continue;
+ }
+
+ /* vpn points to part of a THP at this point */
+ if (page_flags & KPF_COMPOUND_HEAD)
+ cur_order = 1;
+ else {
+ vaddr += psize();
+ continue;
+ }
+
+ next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
+
+ if (next_folio_vaddr >= vaddr_start + len)
+ break;
+
+ while ((status = vaddr_pageflags_get(next_folio_vaddr,
+ pagemap_fd, kpageflags_fd,
+ &page_flags)) >= 0) {
+ /*
+ * non present vaddr, next compound head page, or
+ * order-0 page
+ */
+ if (status == 1 ||
+ (page_flags & KPF_COMPOUND_HEAD) ||
+ !(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) {
+ if (cur_order < nr_orders) {
+ orders[cur_order]++;
+ cur_order = -1;
+ vaddr = next_folio_vaddr;
+ }
+ break;
+ }
+
+ cur_order++;
+ next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
+ }
+
+ if (status < 0)
+ return status;
+ }
+ if (cur_order > 0 && cur_order < nr_orders)
+ orders[cur_order]++;
+ return 0;
+}
+
char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len)
{
int ret;
@@ -229,7 +351,7 @@ err_out:
return entry;
}
-bool __check_huge(void *addr, char *pattern, int nr_hpages,
+static bool __check_pmd_huge(void *addr, char *pattern, int nr_hpages,
uint64_t hpage_size)
{
char buffer[MAX_LINE_LENGTH];
@@ -247,19 +369,84 @@ err_out:
return thp == (nr_hpages * (hpage_size >> 10));
}
-bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size)
+static bool check_large_folios(void *addr, size_t len, int nr_hpages,
+ uint64_t hpage_size)
{
- return __check_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size);
+ int order = 0, pagesize = getpagesize();
+ unsigned int nr_pages = hpage_size / pagesize;
+ int orders[MAX_NR_ORDERS], status;
+ int pagemap_fd, kpageflags_fd;
+ bool ret = false;
+
+ if (!nr_pages)
+ ksft_exit_fail_msg("invalid hugepage size\n");
+
+ order = 31 - __builtin_clz(nr_pages);
+ if (!order || order >= MAX_NR_ORDERS)
+ ksft_exit_fail_msg("invalid order\n");
+
+ memset(orders, 0, sizeof(int) * MAX_NR_ORDERS);
+ pagemap_fd = open(PAGEMAP_PATH, O_RDONLY);
+ if (pagemap_fd == -1)
+ ksft_exit_fail_msg("read pagemap fail\n");
+
+ kpageflags_fd = open(KPAGEFLAGS_PATH, O_RDONLY);
+ if (kpageflags_fd == -1) {
+ close(pagemap_fd);
+ ksft_exit_fail_msg("read kpageflags fail\n");
+ }
+
+ status = gather_folio_orders(addr, len, pagemap_fd,
+ kpageflags_fd, orders, MAX_NR_ORDERS);
+ if (status)
+ goto out;
+
+ if (orders[order] == nr_hpages)
+ ret = true;
+
+out:
+ close(pagemap_fd);
+ close(kpageflags_fd);
+ return ret;
}
-bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size)
+bool check_huge_anon(void *addr, size_t len, int nr_hpages, uint64_t hpage_size)
{
- return __check_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size);
+ uint64_t pmd_pagesize = read_pmd_pagesize();
+
+ if (!pmd_pagesize)
+ ksft_exit_fail_msg("reading PMD pagesize failed\n");
+
+ if (hpage_size == pmd_pagesize)
+ return __check_pmd_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size);
+
+ return check_large_folios(addr, len, nr_hpages, hpage_size);
}
-bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size)
+bool check_huge_file(void *addr, size_t len, int nr_hpages, uint64_t hpage_size)
{
- return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size);
+ uint64_t pmd_pagesize = read_pmd_pagesize();
+
+ if (!pmd_pagesize)
+ ksft_exit_fail_msg("reading PMD pagesize failed\n");
+
+ if (hpage_size == pmd_pagesize)
+ return __check_pmd_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size);
+
+ return check_large_folios(addr, len, nr_hpages, hpage_size);
+}
+
+bool check_huge_shmem(void *addr, size_t len, int nr_hpages, uint64_t hpage_size)
+{
+ uint64_t pmd_pagesize = read_pmd_pagesize();
+
+ if (!pmd_pagesize)
+ ksft_exit_fail_msg("reading PMD pagesize failed\n");
+
+ if (hpage_size == pmd_pagesize)
+ return __check_pmd_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size);
+
+ return check_large_folios(addr, len, nr_hpages, hpage_size);
}
int64_t allocate_transhuge(void *ptr, int pagemap_fd)
@@ -755,7 +942,7 @@ unsigned long read_num(const char *path)
{
char buf[21];
- if (read_file(path, buf, sizeof(buf)) < 0)
+ if (!read_file(path, buf, sizeof(buf)))
ksft_exit_fail_perror("read_file()");
return strtoul(buf, NULL, 10);
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index 7799154b67ee..9a49af88702e 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -90,11 +90,13 @@ void clear_softdirty(void);
bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len);
uint64_t read_pmd_pagesize(void);
unsigned long rss_anon(void);
-bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size);
-bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size);
-bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size);
+bool check_huge_anon(void *addr, size_t len, int nr_hpages, uint64_t hpage_size);
+bool check_huge_file(void *addr, size_t len, int nr_hpages, uint64_t hpage_size);
+bool check_huge_shmem(void *addr, size_t len, int nr_hpages, uint64_t hpage_size);
int64_t allocate_transhuge(void *ptr, int pagemap_fd);
int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags);
+int gather_folio_orders(char *vaddr_start, size_t len,
+ int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders);
int uffd_register(int uffd, void *addr, uint64_t len,
bool miss, bool wp, bool minor);
diff --git a/tools/testing/selftests/proc/proc-maps-race.c b/tools/testing/selftests/proc/proc-maps-race.c
index 1026d8c400e1..415eccb70468 100644
--- a/tools/testing/selftests/proc/proc-maps-race.c
+++ b/tools/testing/selftests/proc/proc-maps-race.c
@@ -490,7 +490,8 @@ static bool query_addr_at(int maps_fd, void *addr,
static inline bool split_vma(FIXTURE_DATA(proc_maps_race) *self)
{
- return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot | PROT_EXEC,
+ /* PROT_NONE differs from both readable neighbors. */
+ return mmap(self->mod_info->addr, self->page_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED;
}
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index cdeb53bbdd1b..4c58487b764e 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -243,7 +243,7 @@ enum {
#define VM_NOHUGEPAGE INIT_VM_FLAG(NOHUGEPAGE)
#define VM_MERGEABLE INIT_VM_FLAG(MERGEABLE)
#define VM_STACK INIT_VM_FLAG(STACK)
-#ifdef CONFIG_STACK_GROWS_UP
+#ifdef CONFIG_STACK_GROWSUP
#define VM_STACK_EARLY INIT_VM_FLAG(STACK_EARLY)
#define VMA_STACK_EARLY mk_vma_flags(VMA_STACK_EARLY_BIT)
#else
@@ -577,6 +577,7 @@ struct vm_area_struct {
*/
unsigned int vm_lock_seq;
#endif
+ unsigned int __vm_anon_pgoff_lo;
/*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
@@ -613,6 +614,9 @@ struct vm_area_struct {
/* Unstable RCU readers are allowed to read this. */
refcount_t vm_refcnt;
#endif
+#ifdef CONFIG_64BIT
+ unsigned int __vm_anon_pgoff_hi;
+#endif
/*
* For areas with an address space and backing store,
* linkage into the address_space->i_mmap interval tree.
@@ -1158,6 +1162,17 @@ static inline bool vma_is_shared_maywrite(struct vm_area_struct *vma)
return is_shared_maywrite(&vma->flags);
}
+static inline bool vma_flags_is_cow_mapping(const vma_flags_t *flags)
+{
+ return vma_flags_test(flags, VMA_MAYWRITE_BIT) &&
+ !vma_flags_test(flags, VMA_SHARED_BIT);
+}
+
+static inline bool vma_is_cow_mapping(const struct vm_area_struct *vma)
+{
+ return vma_flags_is_cow_mapping(&vma->flags);
+}
+
static inline struct vm_area_struct *vma_next(struct vma_iterator *vmi)
{
/*
@@ -1320,6 +1335,28 @@ static inline pgoff_t vma_end_pgoff(const struct vm_area_struct *vma)
return vma_start_pgoff(vma) + vma_pages(vma);
}
+static inline pgoff_t vma_start_anon_pgoff(const struct vm_area_struct *vma)
+{
+ pgoff_t pgoff = 0;
+
+#ifdef CONFIG_64BIT
+ pgoff += vma->__vm_anon_pgoff_hi;
+ pgoff <<= 32;
+#endif
+ pgoff += vma->__vm_anon_pgoff_lo;
+ return pgoff;
+}
+
+static inline pgoff_t vma_end_anon_pgoff(const struct vm_area_struct *vma)
+{
+ return vma_start_anon_pgoff(vma) + vma_pages(vma);
+}
+
+static inline pgoff_t vma_last_anon_pgoff(const struct vm_area_struct *vma)
+{
+ return vma_end_anon_pgoff(vma) - 1;
+}
+
static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
{
return file->f_op->mmap_prepare(desc);
@@ -1391,7 +1428,7 @@ static inline void vma_iter_set(struct vma_iterator *vmi, unsigned long addr)
mas_set(&vmi->mas, addr);
}
-static inline bool vma_is_anonymous(struct vm_area_struct *vma)
+static inline bool vma_is_anonymous(const struct vm_area_struct *vma)
{
return !vma->vm_ops;
}
@@ -1584,3 +1621,26 @@ static inline pgprot_t vma_get_page_prot(const struct vm_area_struct *vma)
{
return vma_flags_to_page_prot(vma->flags);
}
+
+static inline pgoff_t __linear_anon_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
+{
+ pgoff_t pgoff;
+
+ pgoff = linear_page_delta(vma, address);
+ pgoff += vma_start_anon_pgoff(vma);
+ return pgoff;
+}
+
+static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
+{
+ const pgoff_t pgoff = __linear_anon_page_index(vma, address);
+
+ VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma));
+ /* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */
+ if (vma_is_anonymous(vma) && !vma->vm_file)
+ VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));
+
+ return pgoff;
+}
diff --git a/tools/testing/vma/shared.c b/tools/testing/vma/shared.c
index bea9ea6db02a..4a39c9d50489 100644
--- a/tools/testing/vma/shared.c
+++ b/tools/testing/vma/shared.c
@@ -23,7 +23,8 @@ struct vm_area_struct *alloc_vma(struct mm_struct *mm,
vma->vm_start = start;
vma->vm_end = end;
- vma->vm_pgoff = pgoff;
+ vma_set_pgoff(vma, pgoff);
+ vma_set_anon_pgoff(vma, start >> PAGE_SHIFT);
vma->flags = vma_flags;
vma_assert_detached(vma);
diff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c
index e357accc8499..acaab282939c 100644
--- a/tools/testing/vma/tests/merge.c
+++ b/tools/testing/vma/tests/merge.c
@@ -45,6 +45,7 @@ void vmg_set_range(struct vma_merge_struct *vmg, unsigned long start,
vmg->start = start;
vmg->end = end;
vmg->pgoff = pgoff;
+ vmg->anon_pgoff = start >> PAGE_SHIFT;
vmg->vma_flags = vma_flags;
vmg->just_expand = false;
@@ -108,6 +109,7 @@ static bool test_simple_merge(void)
.end = 0x2000,
.vma_flags = vma_flags,
.pgoff = 1,
+ .anon_pgoff = 1,
};
ASSERT_FALSE(attach_vma(&mm, vma_left));
@@ -119,6 +121,7 @@ static bool test_simple_merge(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_FLAGS_SAME_MASK(&vma->flags, vma_flags);
detach_free_vma(vma);
@@ -151,6 +154,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x1000);
ASSERT_EQ(vma->vm_end, 0x2000);
ASSERT_EQ(vma_start_pgoff(vma), 1);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 1);
/*
* Now walk through the three split VMAs and make sure they are as
@@ -163,6 +167,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x1000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
detach_free_vma(vma);
vma_iter_clear(&vmi);
@@ -172,6 +177,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x1000);
ASSERT_EQ(vma->vm_end, 0x2000);
ASSERT_EQ(vma_start_pgoff(vma), 1);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 1);
detach_free_vma(vma);
vma_iter_clear(&vmi);
@@ -181,6 +187,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x2000);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 2);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 2);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -210,6 +217,7 @@ static bool test_simple_expand(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -232,6 +240,7 @@ static bool test_simple_shrink(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x1000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -344,6 +353,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x5000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 3);
@@ -365,6 +375,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0x6000);
ASSERT_EQ(vma->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma), 6);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 6);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 3);
@@ -385,6 +396,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -405,6 +417,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0xa000);
ASSERT_EQ(vma->vm_end, 0xc000);
ASSERT_EQ(vma_start_pgoff(vma), 0xa);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0xa);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -424,6 +437,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0xc000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 1);
@@ -444,6 +458,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0xc000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
detach_free_vma(vma);
@@ -640,7 +655,8 @@ static bool test_vma_merge_with_close(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x5000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(cleanup_mm(&mm, &vmi), 2);
@@ -751,7 +767,8 @@ static bool test_vma_merge_with_close(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x5000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(cleanup_mm(&mm, &vmi), 2);
@@ -806,6 +823,7 @@ static bool test_vma_merge_new_with_close(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x5000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->vm_ops, &vm_ops);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -861,11 +879,13 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_next->vm_start, 0x3000);
ASSERT_EQ(vma_next->vm_end, 0x9000);
- ASSERT_EQ(vma_next->vm_pgoff, 3);
+ ASSERT_EQ(vma_start_pgoff(vma_next), 3);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_next), 3);
ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma);
ASSERT_EQ(vma->vm_start, 0x2000);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 2);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 2);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_TRUE(vma_write_started(vma_next));
ASSERT_EQ(mm.map_count, 2);
@@ -895,7 +915,8 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_next->vm_start, 0x2000);
ASSERT_EQ(vma_next->vm_end, 0x9000);
- ASSERT_EQ(vma_next->vm_pgoff, 2);
+ ASSERT_EQ(vma_start_pgoff(vma_next), 2);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_next), 2);
ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma_next));
ASSERT_EQ(mm.map_count, 1);
@@ -927,11 +948,13 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x6000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_EQ(vma->vm_start, 0x6000);
ASSERT_EQ(vma->vm_end, 0x7000);
ASSERT_EQ(vma_start_pgoff(vma), 6);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 6);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -962,7 +985,8 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x7000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_EQ(mm.map_count, 1);
@@ -994,7 +1018,8 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x9000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_EQ(mm.map_count, 1);
@@ -1124,7 +1149,8 @@ static bool test_anon_vma_non_mergeable(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x7000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_FALSE(vma_write_started(vma_next));
@@ -1155,7 +1181,8 @@ static bool test_anon_vma_non_mergeable(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x7000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_FALSE(vma_write_started(vma_next));
@@ -1417,6 +1444,7 @@ static bool test_merge_extend(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x4000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 1);
@@ -1431,7 +1459,7 @@ static bool test_expand_only_mode(void)
struct mm_struct mm = {};
VMA_ITERATOR(vmi, &mm, 0);
struct vm_area_struct *vma_prev, *vma;
- VMG_STATE(vmg, &mm, &vmi, 0x5000, 0x9000, vma_flags, 5);
+ VMG_STATE(vmg, &mm, &vmi, 0x5000, 0x9000, vma_flags, 5, 5);
/*
* Place a VMA prior to the one we're expanding so we assert that we do
@@ -1457,6 +1485,7 @@ static bool test_expand_only_mode(void)
ASSERT_EQ(vma->vm_start, 0x3000);
ASSERT_EQ(vma->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma), 3);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 3);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(vma_iter_addr(&vmi), 0x3000);
vma_assert_attached(vma);
diff --git a/tools/testing/vma/tests/vma.c b/tools/testing/vma/tests/vma.c
index 754a2da06321..c8ef7b8cd46b 100644
--- a/tools/testing/vma/tests/vma.c
+++ b/tools/testing/vma/tests/vma.c
@@ -33,12 +33,56 @@ static bool test_copy_vma(void)
struct mm_struct mm = {};
bool need_locks = false;
VMA_ITERATOR(vmi, &mm, 0);
- struct vm_area_struct *vma, *vma_new, *vma_next;
+ struct vm_area_struct *vma, *vma_prev, *vma_new, *vma_next, *vma_orig;
+
+ /* Move forwards, adjacent to old self - self-merge. */
+
+ vma = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags);
+ vma_set_anonymous(vma);
+ vma_orig = vma;
+ vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks);
+ ASSERT_EQ(vma_new, vma_orig);
+ ASSERT_EQ(vma, vma_orig);
+ ASSERT_EQ(vma_new->vm_start, 0x1000);
+ ASSERT_EQ(vma_new->vm_end, 0x3000);
+
+ cleanup_mm(&mm, &vmi);
+
+ /* Move backwards, adjacent to old self - self-merge. */
+
+ vma = alloc_and_link_vma(&mm, 0x2000, 0x3000, 2, vma_flags);
+ vma_set_anonymous(vma);
+ vma_orig = vma;
+ vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks);
+ ASSERT_EQ(vma_new, vma_orig);
+ ASSERT_EQ(vma, vma_orig);
+ ASSERT_EQ(vma_new->vm_start, 0x1000);
+ ASSERT_EQ(vma_new->vm_end, 0x3000);
+
+ cleanup_mm(&mm, &vmi);
+
+ /*
+ * Move backwards between prior VMA and old self - self-merge and vma
+ * updated to a new VMA.
+ */
+
+ vma_prev = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags);
+ vma_set_anonymous(vma_prev);
+ vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, vma_flags);
+ vma_set_anonymous(vma);
+ vma_orig = vma;
+ vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks);
+ ASSERT_NE(vma_new, vma_orig);
+ ASSERT_EQ(vma_new, vma);
+ ASSERT_EQ(vma_new->vm_start, 0x1000);
+ ASSERT_EQ(vma_new->vm_end, 0x4000);
+
+ cleanup_mm(&mm, &vmi);
/* Move backwards and do not merge. */
vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, vma_flags);
- vma_new = copy_vma(&vma, 0, 0x2000, 0, &need_locks);
+ vma_new = copy_vma(&vma, 0, 0x2000, 0, 3, &need_locks);
ASSERT_NE(vma_new, vma);
ASSERT_EQ(vma_new->vm_start, 0);
ASSERT_EQ(vma_new->vm_end, 0x2000);
@@ -51,7 +95,7 @@ static bool test_copy_vma(void)
vma = alloc_and_link_vma(&mm, 0, 0x2000, 0, vma_flags);
vma_next = alloc_and_link_vma(&mm, 0x6000, 0x8000, 6, vma_flags);
- vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, &need_locks);
+ vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, 4, &need_locks);
vma_assert_attached(vma_new);
ASSERT_EQ(vma_new, vma_next);
diff --git a/tools/testing/vma/vma_internal.h b/tools/testing/vma/vma_internal.h
index 4f6c5666ac07..8a48b231aa7a 100644
--- a/tools/testing/vma/vma_internal.h
+++ b/tools/testing/vma/vma_internal.h
@@ -53,6 +53,7 @@ typedef __bitwise unsigned int vm_fault_t;
#define VM_WARN_ON(_expr) (WARN_ON(_expr))
#define VM_WARN_ON_ONCE(_expr) (WARN_ON_ONCE(_expr))
+#define VM_WARN_ON_ONCE_VMA(_expr, _vma) (WARN_ON_ONCE(_expr))
#define VM_WARN_ON_VMG(_expr, _vmg) (WARN_ON(_expr))
#define VM_BUG_ON(_expr) (BUG_ON(_expr))
#define VM_BUG_ON_VMA(_expr, _vma) (BUG_ON(_expr))