162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Userfaultfd unit tests.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2015-2023  Red Hat, Inc.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include "uffd-common.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include "../../../../mm/gup_test.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#ifdef __NR_userfaultfd
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci/* The unit test doesn't need a large or random size, make it 32MB for now */
1562306a36Sopenharmony_ci#define  UFFD_TEST_MEM_SIZE               (32UL << 20)
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define  MEM_ANON                         BIT_ULL(0)
1862306a36Sopenharmony_ci#define  MEM_SHMEM                        BIT_ULL(1)
1962306a36Sopenharmony_ci#define  MEM_SHMEM_PRIVATE                BIT_ULL(2)
2062306a36Sopenharmony_ci#define  MEM_HUGETLB                      BIT_ULL(3)
2162306a36Sopenharmony_ci#define  MEM_HUGETLB_PRIVATE              BIT_ULL(4)
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define  MEM_ALL  (MEM_ANON | MEM_SHMEM | MEM_SHMEM_PRIVATE | \
2462306a36Sopenharmony_ci		   MEM_HUGETLB | MEM_HUGETLB_PRIVATE)
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct mem_type {
2762306a36Sopenharmony_ci	const char *name;
2862306a36Sopenharmony_ci	unsigned int mem_flag;
2962306a36Sopenharmony_ci	uffd_test_ops_t *mem_ops;
3062306a36Sopenharmony_ci	bool shared;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_citypedef struct mem_type mem_type_t;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cimem_type_t mem_types[] = {
3562306a36Sopenharmony_ci	{
3662306a36Sopenharmony_ci		.name = "anon",
3762306a36Sopenharmony_ci		.mem_flag = MEM_ANON,
3862306a36Sopenharmony_ci		.mem_ops = &anon_uffd_test_ops,
3962306a36Sopenharmony_ci		.shared = false,
4062306a36Sopenharmony_ci	},
4162306a36Sopenharmony_ci	{
4262306a36Sopenharmony_ci		.name = "shmem",
4362306a36Sopenharmony_ci		.mem_flag = MEM_SHMEM,
4462306a36Sopenharmony_ci		.mem_ops = &shmem_uffd_test_ops,
4562306a36Sopenharmony_ci		.shared = true,
4662306a36Sopenharmony_ci	},
4762306a36Sopenharmony_ci	{
4862306a36Sopenharmony_ci		.name = "shmem-private",
4962306a36Sopenharmony_ci		.mem_flag = MEM_SHMEM_PRIVATE,
5062306a36Sopenharmony_ci		.mem_ops = &shmem_uffd_test_ops,
5162306a36Sopenharmony_ci		.shared = false,
5262306a36Sopenharmony_ci	},
5362306a36Sopenharmony_ci	{
5462306a36Sopenharmony_ci		.name = "hugetlb",
5562306a36Sopenharmony_ci		.mem_flag = MEM_HUGETLB,
5662306a36Sopenharmony_ci		.mem_ops = &hugetlb_uffd_test_ops,
5762306a36Sopenharmony_ci		.shared = true,
5862306a36Sopenharmony_ci	},
5962306a36Sopenharmony_ci	{
6062306a36Sopenharmony_ci		.name = "hugetlb-private",
6162306a36Sopenharmony_ci		.mem_flag = MEM_HUGETLB_PRIVATE,
6262306a36Sopenharmony_ci		.mem_ops = &hugetlb_uffd_test_ops,
6362306a36Sopenharmony_ci		.shared = false,
6462306a36Sopenharmony_ci	},
6562306a36Sopenharmony_ci};
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/* Arguments to be passed over to each uffd unit test */
6862306a36Sopenharmony_cistruct uffd_test_args {
6962306a36Sopenharmony_ci	mem_type_t *mem_type;
7062306a36Sopenharmony_ci};
7162306a36Sopenharmony_citypedef struct uffd_test_args uffd_test_args_t;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci/* Returns: UFFD_TEST_* */
7462306a36Sopenharmony_citypedef void (*uffd_test_fn)(uffd_test_args_t *);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_citypedef struct {
7762306a36Sopenharmony_ci	const char *name;
7862306a36Sopenharmony_ci	uffd_test_fn uffd_fn;
7962306a36Sopenharmony_ci	unsigned int mem_targets;
8062306a36Sopenharmony_ci	uint64_t uffd_feature_required;
8162306a36Sopenharmony_ci} uffd_test_case_t;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic void uffd_test_report(void)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	printf("Userfaults unit tests: pass=%u, skip=%u, fail=%u (total=%u)\n",
8662306a36Sopenharmony_ci	       ksft_get_pass_cnt(),
8762306a36Sopenharmony_ci	       ksft_get_xskip_cnt(),
8862306a36Sopenharmony_ci	       ksft_get_fail_cnt(),
8962306a36Sopenharmony_ci	       ksft_test_num());
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic void uffd_test_pass(void)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	printf("done\n");
9562306a36Sopenharmony_ci	ksft_inc_pass_cnt();
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci#define  uffd_test_start(...)  do {		\
9962306a36Sopenharmony_ci		printf("Testing ");		\
10062306a36Sopenharmony_ci		printf(__VA_ARGS__);		\
10162306a36Sopenharmony_ci		printf("... ");			\
10262306a36Sopenharmony_ci		fflush(stdout);			\
10362306a36Sopenharmony_ci	} while (0)
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci#define  uffd_test_fail(...)  do {		\
10662306a36Sopenharmony_ci		printf("failed [reason: ");	\
10762306a36Sopenharmony_ci		printf(__VA_ARGS__);		\
10862306a36Sopenharmony_ci		printf("]\n");			\
10962306a36Sopenharmony_ci		ksft_inc_fail_cnt();		\
11062306a36Sopenharmony_ci	} while (0)
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic void uffd_test_skip(const char *message)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	printf("skipped [reason: %s]\n", message);
11562306a36Sopenharmony_ci	ksft_inc_xskip_cnt();
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci/*
11962306a36Sopenharmony_ci * Returns 1 if specific userfaultfd supported, 0 otherwise.  Note, we'll
12062306a36Sopenharmony_ci * return 1 even if some test failed as long as uffd supported, because in
12162306a36Sopenharmony_ci * that case we still want to proceed with the rest uffd unit tests.
12262306a36Sopenharmony_ci */
12362306a36Sopenharmony_cistatic int test_uffd_api(bool use_dev)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	struct uffdio_api uffdio_api;
12662306a36Sopenharmony_ci	int uffd;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	uffd_test_start("UFFDIO_API (with %s)",
12962306a36Sopenharmony_ci			use_dev ? "/dev/userfaultfd" : "syscall");
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	if (use_dev)
13262306a36Sopenharmony_ci		uffd = uffd_open_dev(UFFD_FLAGS);
13362306a36Sopenharmony_ci	else
13462306a36Sopenharmony_ci		uffd = uffd_open_sys(UFFD_FLAGS);
13562306a36Sopenharmony_ci	if (uffd < 0) {
13662306a36Sopenharmony_ci		uffd_test_skip("cannot open userfaultfd handle");
13762306a36Sopenharmony_ci		return 0;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	/* Test wrong UFFD_API */
14162306a36Sopenharmony_ci	uffdio_api.api = 0xab;
14262306a36Sopenharmony_ci	uffdio_api.features = 0;
14362306a36Sopenharmony_ci	if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) {
14462306a36Sopenharmony_ci		uffd_test_fail("UFFDIO_API should fail with wrong api but didn't");
14562306a36Sopenharmony_ci		goto out;
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	/* Test wrong feature bit */
14962306a36Sopenharmony_ci	uffdio_api.api = UFFD_API;
15062306a36Sopenharmony_ci	uffdio_api.features = BIT_ULL(63);
15162306a36Sopenharmony_ci	if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) {
15262306a36Sopenharmony_ci		uffd_test_fail("UFFDIO_API should fail with wrong feature but didn't");
15362306a36Sopenharmony_ci		goto out;
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/* Test normal UFFDIO_API */
15762306a36Sopenharmony_ci	uffdio_api.api = UFFD_API;
15862306a36Sopenharmony_ci	uffdio_api.features = 0;
15962306a36Sopenharmony_ci	if (ioctl(uffd, UFFDIO_API, &uffdio_api)) {
16062306a36Sopenharmony_ci		uffd_test_fail("UFFDIO_API should succeed but failed");
16162306a36Sopenharmony_ci		goto out;
16262306a36Sopenharmony_ci	}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/* Test double requests of UFFDIO_API with a random feature set */
16562306a36Sopenharmony_ci	uffdio_api.features = BIT_ULL(0);
16662306a36Sopenharmony_ci	if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) {
16762306a36Sopenharmony_ci		uffd_test_fail("UFFDIO_API should reject initialized uffd");
16862306a36Sopenharmony_ci		goto out;
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	uffd_test_pass();
17262306a36Sopenharmony_ciout:
17362306a36Sopenharmony_ci	close(uffd);
17462306a36Sopenharmony_ci	/* We have a valid uffd handle */
17562306a36Sopenharmony_ci	return 1;
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci/*
17962306a36Sopenharmony_ci * This function initializes the global variables.  TODO: remove global
18062306a36Sopenharmony_ci * vars and then remove this.
18162306a36Sopenharmony_ci */
18262306a36Sopenharmony_cistatic int
18362306a36Sopenharmony_ciuffd_setup_environment(uffd_test_args_t *args, uffd_test_case_t *test,
18462306a36Sopenharmony_ci		       mem_type_t *mem_type, const char **errmsg)
18562306a36Sopenharmony_ci{
18662306a36Sopenharmony_ci	map_shared = mem_type->shared;
18762306a36Sopenharmony_ci	uffd_test_ops = mem_type->mem_ops;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	if (mem_type->mem_flag & (MEM_HUGETLB_PRIVATE | MEM_HUGETLB))
19062306a36Sopenharmony_ci		page_size = default_huge_page_size();
19162306a36Sopenharmony_ci	else
19262306a36Sopenharmony_ci		page_size = psize();
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	nr_pages = UFFD_TEST_MEM_SIZE / page_size;
19562306a36Sopenharmony_ci	/* TODO: remove this global var.. it's so ugly */
19662306a36Sopenharmony_ci	nr_cpus = 1;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	/* Initialize test arguments */
19962306a36Sopenharmony_ci	args->mem_type = mem_type;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	return uffd_test_ctx_init(test->uffd_feature_required, errmsg);
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic bool uffd_feature_supported(uffd_test_case_t *test)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	uint64_t features;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (uffd_get_features(&features))
20962306a36Sopenharmony_ci		return false;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	return (features & test->uffd_feature_required) ==
21262306a36Sopenharmony_ci	    test->uffd_feature_required;
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic int pagemap_open(void)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	int fd = open("/proc/self/pagemap", O_RDONLY);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	if (fd < 0)
22062306a36Sopenharmony_ci		err("open pagemap");
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return fd;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci/* This macro let __LINE__ works in err() */
22662306a36Sopenharmony_ci#define  pagemap_check_wp(value, wp) do {				\
22762306a36Sopenharmony_ci		if (!!(value & PM_UFFD_WP) != wp)			\
22862306a36Sopenharmony_ci			err("pagemap uffd-wp bit error: 0x%"PRIx64, value); \
22962306a36Sopenharmony_ci	} while (0)
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_citypedef struct {
23262306a36Sopenharmony_ci	int parent_uffd, child_uffd;
23362306a36Sopenharmony_ci} fork_event_args;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic void *fork_event_consumer(void *data)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	fork_event_args *args = data;
23862306a36Sopenharmony_ci	struct uffd_msg msg = { 0 };
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	/* Read until a full msg received */
24162306a36Sopenharmony_ci	while (uffd_read_msg(args->parent_uffd, &msg));
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	if (msg.event != UFFD_EVENT_FORK)
24462306a36Sopenharmony_ci		err("wrong message: %u\n", msg.event);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	/* Just to be properly freed later */
24762306a36Sopenharmony_ci	args->child_uffd = msg.arg.fork.ufd;
24862306a36Sopenharmony_ci	return NULL;
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_citypedef struct {
25262306a36Sopenharmony_ci	int gup_fd;
25362306a36Sopenharmony_ci	bool pinned;
25462306a36Sopenharmony_ci} pin_args;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci/*
25762306a36Sopenharmony_ci * Returns 0 if succeed, <0 for errors.  pin_pages() needs to be paired
25862306a36Sopenharmony_ci * with unpin_pages().  Currently it needs to be RO longterm pin to satisfy
25962306a36Sopenharmony_ci * all needs of the test cases (e.g., trigger unshare, trigger fork() early
26062306a36Sopenharmony_ci * CoW, etc.).
26162306a36Sopenharmony_ci */
26262306a36Sopenharmony_cistatic int pin_pages(pin_args *args, void *buffer, size_t size)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	struct pin_longterm_test test = {
26562306a36Sopenharmony_ci		.addr = (uintptr_t)buffer,
26662306a36Sopenharmony_ci		.size = size,
26762306a36Sopenharmony_ci		/* Read-only pins */
26862306a36Sopenharmony_ci		.flags = 0,
26962306a36Sopenharmony_ci	};
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	if (args->pinned)
27262306a36Sopenharmony_ci		err("already pinned");
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	args->gup_fd = open("/sys/kernel/debug/gup_test", O_RDWR);
27562306a36Sopenharmony_ci	if (args->gup_fd < 0)
27662306a36Sopenharmony_ci		return -errno;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	if (ioctl(args->gup_fd, PIN_LONGTERM_TEST_START, &test)) {
27962306a36Sopenharmony_ci		/* Even if gup_test existed, can be an old gup_test / kernel */
28062306a36Sopenharmony_ci		close(args->gup_fd);
28162306a36Sopenharmony_ci		return -errno;
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci	args->pinned = true;
28462306a36Sopenharmony_ci	return 0;
28562306a36Sopenharmony_ci}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_cistatic void unpin_pages(pin_args *args)
28862306a36Sopenharmony_ci{
28962306a36Sopenharmony_ci	if (!args->pinned)
29062306a36Sopenharmony_ci		err("unpin without pin first");
29162306a36Sopenharmony_ci	if (ioctl(args->gup_fd, PIN_LONGTERM_TEST_STOP))
29262306a36Sopenharmony_ci		err("PIN_LONGTERM_TEST_STOP");
29362306a36Sopenharmony_ci	close(args->gup_fd);
29462306a36Sopenharmony_ci	args->pinned = false;
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_cistatic int pagemap_test_fork(int uffd, bool with_event, bool test_pin)
29862306a36Sopenharmony_ci{
29962306a36Sopenharmony_ci	fork_event_args args = { .parent_uffd = uffd, .child_uffd = -1 };
30062306a36Sopenharmony_ci	pthread_t thread;
30162306a36Sopenharmony_ci	pid_t child;
30262306a36Sopenharmony_ci	uint64_t value;
30362306a36Sopenharmony_ci	int fd, result;
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	/* Prepare a thread to resolve EVENT_FORK */
30662306a36Sopenharmony_ci	if (with_event) {
30762306a36Sopenharmony_ci		if (pthread_create(&thread, NULL, fork_event_consumer, &args))
30862306a36Sopenharmony_ci			err("pthread_create()");
30962306a36Sopenharmony_ci	}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	child = fork();
31262306a36Sopenharmony_ci	if (!child) {
31362306a36Sopenharmony_ci		/* Open the pagemap fd of the child itself */
31462306a36Sopenharmony_ci		pin_args args = {};
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci		fd = pagemap_open();
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci		if (test_pin && pin_pages(&args, area_dst, page_size))
31962306a36Sopenharmony_ci			/*
32062306a36Sopenharmony_ci			 * Normally when reach here we have pinned in
32162306a36Sopenharmony_ci			 * previous tests, so shouldn't fail anymore
32262306a36Sopenharmony_ci			 */
32362306a36Sopenharmony_ci			err("pin page failed in child");
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci		value = pagemap_get_entry(fd, area_dst);
32662306a36Sopenharmony_ci		/*
32762306a36Sopenharmony_ci		 * After fork(), we should handle uffd-wp bit differently:
32862306a36Sopenharmony_ci		 *
32962306a36Sopenharmony_ci		 * (1) when with EVENT_FORK, it should persist
33062306a36Sopenharmony_ci		 * (2) when without EVENT_FORK, it should be dropped
33162306a36Sopenharmony_ci		 */
33262306a36Sopenharmony_ci		pagemap_check_wp(value, with_event);
33362306a36Sopenharmony_ci		if (test_pin)
33462306a36Sopenharmony_ci			unpin_pages(&args);
33562306a36Sopenharmony_ci		/* Succeed */
33662306a36Sopenharmony_ci		exit(0);
33762306a36Sopenharmony_ci	}
33862306a36Sopenharmony_ci	waitpid(child, &result, 0);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	if (with_event) {
34162306a36Sopenharmony_ci		if (pthread_join(thread, NULL))
34262306a36Sopenharmony_ci			err("pthread_join()");
34362306a36Sopenharmony_ci		if (args.child_uffd < 0)
34462306a36Sopenharmony_ci			err("Didn't receive child uffd");
34562306a36Sopenharmony_ci		close(args.child_uffd);
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	return result;
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_cistatic void uffd_wp_unpopulated_test(uffd_test_args_t *args)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	uint64_t value;
35462306a36Sopenharmony_ci	int pagemap_fd;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	if (uffd_register(uffd, area_dst, nr_pages * page_size,
35762306a36Sopenharmony_ci			  false, true, false))
35862306a36Sopenharmony_ci		err("register failed");
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	pagemap_fd = pagemap_open();
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	/* Test applying pte marker to anon unpopulated */
36362306a36Sopenharmony_ci	wp_range(uffd, (uint64_t)area_dst, page_size, true);
36462306a36Sopenharmony_ci	value = pagemap_get_entry(pagemap_fd, area_dst);
36562306a36Sopenharmony_ci	pagemap_check_wp(value, true);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	/* Test unprotect on anon pte marker */
36862306a36Sopenharmony_ci	wp_range(uffd, (uint64_t)area_dst, page_size, false);
36962306a36Sopenharmony_ci	value = pagemap_get_entry(pagemap_fd, area_dst);
37062306a36Sopenharmony_ci	pagemap_check_wp(value, false);
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	/* Test zap on anon marker */
37362306a36Sopenharmony_ci	wp_range(uffd, (uint64_t)area_dst, page_size, true);
37462306a36Sopenharmony_ci	if (madvise(area_dst, page_size, MADV_DONTNEED))
37562306a36Sopenharmony_ci		err("madvise(MADV_DONTNEED) failed");
37662306a36Sopenharmony_ci	value = pagemap_get_entry(pagemap_fd, area_dst);
37762306a36Sopenharmony_ci	pagemap_check_wp(value, false);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	/* Test fault in after marker removed */
38062306a36Sopenharmony_ci	*area_dst = 1;
38162306a36Sopenharmony_ci	value = pagemap_get_entry(pagemap_fd, area_dst);
38262306a36Sopenharmony_ci	pagemap_check_wp(value, false);
38362306a36Sopenharmony_ci	/* Drop it to make pte none again */
38462306a36Sopenharmony_ci	if (madvise(area_dst, page_size, MADV_DONTNEED))
38562306a36Sopenharmony_ci		err("madvise(MADV_DONTNEED) failed");
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	/* Test read-zero-page upon pte marker */
38862306a36Sopenharmony_ci	wp_range(uffd, (uint64_t)area_dst, page_size, true);
38962306a36Sopenharmony_ci	*(volatile char *)area_dst;
39062306a36Sopenharmony_ci	/* Drop it to make pte none again */
39162306a36Sopenharmony_ci	if (madvise(area_dst, page_size, MADV_DONTNEED))
39262306a36Sopenharmony_ci		err("madvise(MADV_DONTNEED) failed");
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	uffd_test_pass();
39562306a36Sopenharmony_ci}
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_cistatic void uffd_wp_fork_test_common(uffd_test_args_t *args,
39862306a36Sopenharmony_ci				     bool with_event)
39962306a36Sopenharmony_ci{
40062306a36Sopenharmony_ci	int pagemap_fd;
40162306a36Sopenharmony_ci	uint64_t value;
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	if (uffd_register(uffd, area_dst, nr_pages * page_size,
40462306a36Sopenharmony_ci			  false, true, false))
40562306a36Sopenharmony_ci		err("register failed");
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	pagemap_fd = pagemap_open();
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	/* Touch the page */
41062306a36Sopenharmony_ci	*area_dst = 1;
41162306a36Sopenharmony_ci	wp_range(uffd, (uint64_t)area_dst, page_size, true);
41262306a36Sopenharmony_ci	value = pagemap_get_entry(pagemap_fd, area_dst);
41362306a36Sopenharmony_ci	pagemap_check_wp(value, true);
41462306a36Sopenharmony_ci	if (pagemap_test_fork(uffd, with_event, false)) {
41562306a36Sopenharmony_ci		uffd_test_fail("Detected %s uffd-wp bit in child in present pte",
41662306a36Sopenharmony_ci			       with_event ? "missing" : "stall");
41762306a36Sopenharmony_ci		goto out;
41862306a36Sopenharmony_ci	}
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	/*
42162306a36Sopenharmony_ci	 * This is an attempt for zapping the pgtable so as to test the
42262306a36Sopenharmony_ci	 * markers.
42362306a36Sopenharmony_ci	 *
42462306a36Sopenharmony_ci	 * For private mappings, PAGEOUT will only work on exclusive ptes
42562306a36Sopenharmony_ci	 * (PM_MMAP_EXCLUSIVE) which we should satisfy.
42662306a36Sopenharmony_ci	 *
42762306a36Sopenharmony_ci	 * For shared, PAGEOUT may not work.  Use DONTNEED instead which
42862306a36Sopenharmony_ci	 * plays a similar role of zapping (rather than freeing the page)
42962306a36Sopenharmony_ci	 * to expose pte markers.
43062306a36Sopenharmony_ci	 */
43162306a36Sopenharmony_ci	if (args->mem_type->shared) {
43262306a36Sopenharmony_ci		if (madvise(area_dst, page_size, MADV_DONTNEED))
43362306a36Sopenharmony_ci			err("MADV_DONTNEED");
43462306a36Sopenharmony_ci	} else {
43562306a36Sopenharmony_ci		/*
43662306a36Sopenharmony_ci		 * NOTE: ignore retval because private-hugetlb doesn't yet
43762306a36Sopenharmony_ci		 * support swapping, so it could fail.
43862306a36Sopenharmony_ci		 */
43962306a36Sopenharmony_ci		madvise(area_dst, page_size, MADV_PAGEOUT);
44062306a36Sopenharmony_ci	}
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	/* Uffd-wp should persist even swapped out */
44362306a36Sopenharmony_ci	value = pagemap_get_entry(pagemap_fd, area_dst);
44462306a36Sopenharmony_ci	pagemap_check_wp(value, true);
44562306a36Sopenharmony_ci	if (pagemap_test_fork(uffd, with_event, false)) {
44662306a36Sopenharmony_ci		uffd_test_fail("Detected %s uffd-wp bit in child in zapped pte",
44762306a36Sopenharmony_ci			       with_event ? "missing" : "stall");
44862306a36Sopenharmony_ci		goto out;
44962306a36Sopenharmony_ci	}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	/* Unprotect; this tests swap pte modifications */
45262306a36Sopenharmony_ci	wp_range(uffd, (uint64_t)area_dst, page_size, false);
45362306a36Sopenharmony_ci	value = pagemap_get_entry(pagemap_fd, area_dst);
45462306a36Sopenharmony_ci	pagemap_check_wp(value, false);
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	/* Fault in the page from disk */
45762306a36Sopenharmony_ci	*area_dst = 2;
45862306a36Sopenharmony_ci	value = pagemap_get_entry(pagemap_fd, area_dst);
45962306a36Sopenharmony_ci	pagemap_check_wp(value, false);
46062306a36Sopenharmony_ci	uffd_test_pass();
46162306a36Sopenharmony_ciout:
46262306a36Sopenharmony_ci	if (uffd_unregister(uffd, area_dst, nr_pages * page_size))
46362306a36Sopenharmony_ci		err("unregister failed");
46462306a36Sopenharmony_ci	close(pagemap_fd);
46562306a36Sopenharmony_ci}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_cistatic void uffd_wp_fork_test(uffd_test_args_t *args)
46862306a36Sopenharmony_ci{
46962306a36Sopenharmony_ci	uffd_wp_fork_test_common(args, false);
47062306a36Sopenharmony_ci}
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_cistatic void uffd_wp_fork_with_event_test(uffd_test_args_t *args)
47362306a36Sopenharmony_ci{
47462306a36Sopenharmony_ci	uffd_wp_fork_test_common(args, true);
47562306a36Sopenharmony_ci}
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_cistatic void uffd_wp_fork_pin_test_common(uffd_test_args_t *args,
47862306a36Sopenharmony_ci					 bool with_event)
47962306a36Sopenharmony_ci{
48062306a36Sopenharmony_ci	int pagemap_fd;
48162306a36Sopenharmony_ci	pin_args pin_args = {};
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	if (uffd_register(uffd, area_dst, page_size, false, true, false))
48462306a36Sopenharmony_ci		err("register failed");
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	pagemap_fd = pagemap_open();
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	/* Touch the page */
48962306a36Sopenharmony_ci	*area_dst = 1;
49062306a36Sopenharmony_ci	wp_range(uffd, (uint64_t)area_dst, page_size, true);
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	/*
49362306a36Sopenharmony_ci	 * 1. First pin, then fork().  This tests fork() special path when
49462306a36Sopenharmony_ci	 * doing early CoW if the page is private.
49562306a36Sopenharmony_ci	 */
49662306a36Sopenharmony_ci	if (pin_pages(&pin_args, area_dst, page_size)) {
49762306a36Sopenharmony_ci		uffd_test_skip("Possibly CONFIG_GUP_TEST missing "
49862306a36Sopenharmony_ci			       "or unprivileged");
49962306a36Sopenharmony_ci		close(pagemap_fd);
50062306a36Sopenharmony_ci		uffd_unregister(uffd, area_dst, page_size);
50162306a36Sopenharmony_ci		return;
50262306a36Sopenharmony_ci	}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	if (pagemap_test_fork(uffd, with_event, false)) {
50562306a36Sopenharmony_ci		uffd_test_fail("Detected %s uffd-wp bit in early CoW of fork()",
50662306a36Sopenharmony_ci			       with_event ? "missing" : "stall");
50762306a36Sopenharmony_ci		unpin_pages(&pin_args);
50862306a36Sopenharmony_ci		goto out;
50962306a36Sopenharmony_ci	}
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	unpin_pages(&pin_args);
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	/*
51462306a36Sopenharmony_ci	 * 2. First fork(), then pin (in the child, where test_pin==true).
51562306a36Sopenharmony_ci	 * This tests COR, aka, page unsharing on private memories.
51662306a36Sopenharmony_ci	 */
51762306a36Sopenharmony_ci	if (pagemap_test_fork(uffd, with_event, true)) {
51862306a36Sopenharmony_ci		uffd_test_fail("Detected %s uffd-wp bit when RO pin",
51962306a36Sopenharmony_ci			       with_event ? "missing" : "stall");
52062306a36Sopenharmony_ci		goto out;
52162306a36Sopenharmony_ci	}
52262306a36Sopenharmony_ci	uffd_test_pass();
52362306a36Sopenharmony_ciout:
52462306a36Sopenharmony_ci	if (uffd_unregister(uffd, area_dst, page_size))
52562306a36Sopenharmony_ci		err("register failed");
52662306a36Sopenharmony_ci	close(pagemap_fd);
52762306a36Sopenharmony_ci}
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_cistatic void uffd_wp_fork_pin_test(uffd_test_args_t *args)
53062306a36Sopenharmony_ci{
53162306a36Sopenharmony_ci	uffd_wp_fork_pin_test_common(args, false);
53262306a36Sopenharmony_ci}
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_cistatic void uffd_wp_fork_pin_with_event_test(uffd_test_args_t *args)
53562306a36Sopenharmony_ci{
53662306a36Sopenharmony_ci	uffd_wp_fork_pin_test_common(args, true);
53762306a36Sopenharmony_ci}
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_cistatic void check_memory_contents(char *p)
54062306a36Sopenharmony_ci{
54162306a36Sopenharmony_ci	unsigned long i, j;
54262306a36Sopenharmony_ci	uint8_t expected_byte;
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci	for (i = 0; i < nr_pages; ++i) {
54562306a36Sopenharmony_ci		expected_byte = ~((uint8_t)(i % ((uint8_t)-1)));
54662306a36Sopenharmony_ci		for (j = 0; j < page_size; j++) {
54762306a36Sopenharmony_ci			uint8_t v = *(uint8_t *)(p + (i * page_size) + j);
54862306a36Sopenharmony_ci			if (v != expected_byte)
54962306a36Sopenharmony_ci				err("unexpected page contents");
55062306a36Sopenharmony_ci		}
55162306a36Sopenharmony_ci	}
55262306a36Sopenharmony_ci}
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_cistatic void uffd_minor_test_common(bool test_collapse, bool test_wp)
55562306a36Sopenharmony_ci{
55662306a36Sopenharmony_ci	unsigned long p;
55762306a36Sopenharmony_ci	pthread_t uffd_mon;
55862306a36Sopenharmony_ci	char c;
55962306a36Sopenharmony_ci	struct uffd_args args = { 0 };
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	/*
56262306a36Sopenharmony_ci	 * NOTE: MADV_COLLAPSE is not yet compatible with WP, so testing
56362306a36Sopenharmony_ci	 * both do not make much sense.
56462306a36Sopenharmony_ci	 */
56562306a36Sopenharmony_ci	assert(!(test_collapse && test_wp));
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci	if (uffd_register(uffd, area_dst_alias, nr_pages * page_size,
56862306a36Sopenharmony_ci			  /* NOTE! MADV_COLLAPSE may not work with uffd-wp */
56962306a36Sopenharmony_ci			  false, test_wp, true))
57062306a36Sopenharmony_ci		err("register failure");
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	/*
57362306a36Sopenharmony_ci	 * After registering with UFFD, populate the non-UFFD-registered side of
57462306a36Sopenharmony_ci	 * the shared mapping. This should *not* trigger any UFFD minor faults.
57562306a36Sopenharmony_ci	 */
57662306a36Sopenharmony_ci	for (p = 0; p < nr_pages; ++p)
57762306a36Sopenharmony_ci		memset(area_dst + (p * page_size), p % ((uint8_t)-1),
57862306a36Sopenharmony_ci		       page_size);
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	args.apply_wp = test_wp;
58162306a36Sopenharmony_ci	if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args))
58262306a36Sopenharmony_ci		err("uffd_poll_thread create");
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	/*
58562306a36Sopenharmony_ci	 * Read each of the pages back using the UFFD-registered mapping. We
58662306a36Sopenharmony_ci	 * expect that the first time we touch a page, it will result in a minor
58762306a36Sopenharmony_ci	 * fault. uffd_poll_thread will resolve the fault by bit-flipping the
58862306a36Sopenharmony_ci	 * page's contents, and then issuing a CONTINUE ioctl.
58962306a36Sopenharmony_ci	 */
59062306a36Sopenharmony_ci	check_memory_contents(area_dst_alias);
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	if (write(pipefd[1], &c, sizeof(c)) != sizeof(c))
59362306a36Sopenharmony_ci		err("pipe write");
59462306a36Sopenharmony_ci	if (pthread_join(uffd_mon, NULL))
59562306a36Sopenharmony_ci		err("join() failed");
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	if (test_collapse) {
59862306a36Sopenharmony_ci		if (madvise(area_dst_alias, nr_pages * page_size,
59962306a36Sopenharmony_ci			    MADV_COLLAPSE)) {
60062306a36Sopenharmony_ci			/* It's fine to fail for this one... */
60162306a36Sopenharmony_ci			uffd_test_skip("MADV_COLLAPSE failed");
60262306a36Sopenharmony_ci			return;
60362306a36Sopenharmony_ci		}
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci		uffd_test_ops->check_pmd_mapping(area_dst,
60662306a36Sopenharmony_ci						 nr_pages * page_size /
60762306a36Sopenharmony_ci						 read_pmd_pagesize());
60862306a36Sopenharmony_ci		/*
60962306a36Sopenharmony_ci		 * This won't cause uffd-fault - it purely just makes sure there
61062306a36Sopenharmony_ci		 * was no corruption.
61162306a36Sopenharmony_ci		 */
61262306a36Sopenharmony_ci		check_memory_contents(area_dst_alias);
61362306a36Sopenharmony_ci	}
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	if (args.missing_faults != 0 || args.minor_faults != nr_pages)
61662306a36Sopenharmony_ci		uffd_test_fail("stats check error");
61762306a36Sopenharmony_ci	else
61862306a36Sopenharmony_ci		uffd_test_pass();
61962306a36Sopenharmony_ci}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_civoid uffd_minor_test(uffd_test_args_t *args)
62262306a36Sopenharmony_ci{
62362306a36Sopenharmony_ci	uffd_minor_test_common(false, false);
62462306a36Sopenharmony_ci}
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_civoid uffd_minor_wp_test(uffd_test_args_t *args)
62762306a36Sopenharmony_ci{
62862306a36Sopenharmony_ci	uffd_minor_test_common(false, true);
62962306a36Sopenharmony_ci}
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_civoid uffd_minor_collapse_test(uffd_test_args_t *args)
63262306a36Sopenharmony_ci{
63362306a36Sopenharmony_ci	uffd_minor_test_common(true, false);
63462306a36Sopenharmony_ci}
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_cistatic sigjmp_buf jbuf, *sigbuf;
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_cistatic void sighndl(int sig, siginfo_t *siginfo, void *ptr)
63962306a36Sopenharmony_ci{
64062306a36Sopenharmony_ci	if (sig == SIGBUS) {
64162306a36Sopenharmony_ci		if (sigbuf)
64262306a36Sopenharmony_ci			siglongjmp(*sigbuf, 1);
64362306a36Sopenharmony_ci		abort();
64462306a36Sopenharmony_ci	}
64562306a36Sopenharmony_ci}
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci/*
64862306a36Sopenharmony_ci * For non-cooperative userfaultfd test we fork() a process that will
64962306a36Sopenharmony_ci * generate pagefaults, will mremap the area monitored by the
65062306a36Sopenharmony_ci * userfaultfd and at last this process will release the monitored
65162306a36Sopenharmony_ci * area.
65262306a36Sopenharmony_ci * For the anonymous and shared memory the area is divided into two
65362306a36Sopenharmony_ci * parts, the first part is accessed before mremap, and the second
65462306a36Sopenharmony_ci * part is accessed after mremap. Since hugetlbfs does not support
65562306a36Sopenharmony_ci * mremap, the entire monitored area is accessed in a single pass for
65662306a36Sopenharmony_ci * HUGETLB_TEST.
65762306a36Sopenharmony_ci * The release of the pages currently generates event for shmem and
65862306a36Sopenharmony_ci * anonymous memory (UFFD_EVENT_REMOVE), hence it is not checked
65962306a36Sopenharmony_ci * for hugetlb.
66062306a36Sopenharmony_ci * For signal test(UFFD_FEATURE_SIGBUS), signal_test = 1, we register
66162306a36Sopenharmony_ci * monitored area, generate pagefaults and test that signal is delivered.
66262306a36Sopenharmony_ci * Use UFFDIO_COPY to allocate missing page and retry. For signal_test = 2
66362306a36Sopenharmony_ci * test robustness use case - we release monitored area, fork a process
66462306a36Sopenharmony_ci * that will generate pagefaults and verify signal is generated.
66562306a36Sopenharmony_ci * This also tests UFFD_FEATURE_EVENT_FORK event along with the signal
66662306a36Sopenharmony_ci * feature. Using monitor thread, verify no userfault events are generated.
66762306a36Sopenharmony_ci */
66862306a36Sopenharmony_cistatic int faulting_process(int signal_test, bool wp)
66962306a36Sopenharmony_ci{
67062306a36Sopenharmony_ci	unsigned long nr, i;
67162306a36Sopenharmony_ci	unsigned long long count;
67262306a36Sopenharmony_ci	unsigned long split_nr_pages;
67362306a36Sopenharmony_ci	unsigned long lastnr;
67462306a36Sopenharmony_ci	struct sigaction act;
67562306a36Sopenharmony_ci	volatile unsigned long signalled = 0;
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci	split_nr_pages = (nr_pages + 1) / 2;
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	if (signal_test) {
68062306a36Sopenharmony_ci		sigbuf = &jbuf;
68162306a36Sopenharmony_ci		memset(&act, 0, sizeof(act));
68262306a36Sopenharmony_ci		act.sa_sigaction = sighndl;
68362306a36Sopenharmony_ci		act.sa_flags = SA_SIGINFO;
68462306a36Sopenharmony_ci		if (sigaction(SIGBUS, &act, 0))
68562306a36Sopenharmony_ci			err("sigaction");
68662306a36Sopenharmony_ci		lastnr = (unsigned long)-1;
68762306a36Sopenharmony_ci	}
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	for (nr = 0; nr < split_nr_pages; nr++) {
69062306a36Sopenharmony_ci		volatile int steps = 1;
69162306a36Sopenharmony_ci		unsigned long offset = nr * page_size;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci		if (signal_test) {
69462306a36Sopenharmony_ci			if (sigsetjmp(*sigbuf, 1) != 0) {
69562306a36Sopenharmony_ci				if (steps == 1 && nr == lastnr)
69662306a36Sopenharmony_ci					err("Signal repeated");
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ci				lastnr = nr;
69962306a36Sopenharmony_ci				if (signal_test == 1) {
70062306a36Sopenharmony_ci					if (steps == 1) {
70162306a36Sopenharmony_ci						/* This is a MISSING request */
70262306a36Sopenharmony_ci						steps++;
70362306a36Sopenharmony_ci						if (copy_page(uffd, offset, wp))
70462306a36Sopenharmony_ci							signalled++;
70562306a36Sopenharmony_ci					} else {
70662306a36Sopenharmony_ci						/* This is a WP request */
70762306a36Sopenharmony_ci						assert(steps == 2);
70862306a36Sopenharmony_ci						wp_range(uffd,
70962306a36Sopenharmony_ci							 (__u64)area_dst +
71062306a36Sopenharmony_ci							 offset,
71162306a36Sopenharmony_ci							 page_size, false);
71262306a36Sopenharmony_ci					}
71362306a36Sopenharmony_ci				} else {
71462306a36Sopenharmony_ci					signalled++;
71562306a36Sopenharmony_ci					continue;
71662306a36Sopenharmony_ci				}
71762306a36Sopenharmony_ci			}
71862306a36Sopenharmony_ci		}
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci		count = *area_count(area_dst, nr);
72162306a36Sopenharmony_ci		if (count != count_verify[nr])
72262306a36Sopenharmony_ci			err("nr %lu memory corruption %llu %llu\n",
72362306a36Sopenharmony_ci			    nr, count, count_verify[nr]);
72462306a36Sopenharmony_ci		/*
72562306a36Sopenharmony_ci		 * Trigger write protection if there is by writing
72662306a36Sopenharmony_ci		 * the same value back.
72762306a36Sopenharmony_ci		 */
72862306a36Sopenharmony_ci		*area_count(area_dst, nr) = count;
72962306a36Sopenharmony_ci	}
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_ci	if (signal_test)
73262306a36Sopenharmony_ci		return signalled != split_nr_pages;
73362306a36Sopenharmony_ci
73462306a36Sopenharmony_ci	area_dst = mremap(area_dst, nr_pages * page_size,  nr_pages * page_size,
73562306a36Sopenharmony_ci			  MREMAP_MAYMOVE | MREMAP_FIXED, area_src);
73662306a36Sopenharmony_ci	if (area_dst == MAP_FAILED)
73762306a36Sopenharmony_ci		err("mremap");
73862306a36Sopenharmony_ci	/* Reset area_src since we just clobbered it */
73962306a36Sopenharmony_ci	area_src = NULL;
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_ci	for (; nr < nr_pages; nr++) {
74262306a36Sopenharmony_ci		count = *area_count(area_dst, nr);
74362306a36Sopenharmony_ci		if (count != count_verify[nr]) {
74462306a36Sopenharmony_ci			err("nr %lu memory corruption %llu %llu\n",
74562306a36Sopenharmony_ci			    nr, count, count_verify[nr]);
74662306a36Sopenharmony_ci		}
74762306a36Sopenharmony_ci		/*
74862306a36Sopenharmony_ci		 * Trigger write protection if there is by writing
74962306a36Sopenharmony_ci		 * the same value back.
75062306a36Sopenharmony_ci		 */
75162306a36Sopenharmony_ci		*area_count(area_dst, nr) = count;
75262306a36Sopenharmony_ci	}
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_ci	uffd_test_ops->release_pages(area_dst);
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci	for (nr = 0; nr < nr_pages; nr++)
75762306a36Sopenharmony_ci		for (i = 0; i < page_size; i++)
75862306a36Sopenharmony_ci			if (*(area_dst + nr * page_size + i) != 0)
75962306a36Sopenharmony_ci				err("page %lu offset %lu is not zero", nr, i);
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_ci	return 0;
76262306a36Sopenharmony_ci}
76362306a36Sopenharmony_ci
76462306a36Sopenharmony_cistatic void uffd_sigbus_test_common(bool wp)
76562306a36Sopenharmony_ci{
76662306a36Sopenharmony_ci	unsigned long userfaults;
76762306a36Sopenharmony_ci	pthread_t uffd_mon;
76862306a36Sopenharmony_ci	pid_t pid;
76962306a36Sopenharmony_ci	int err;
77062306a36Sopenharmony_ci	char c;
77162306a36Sopenharmony_ci	struct uffd_args args = { 0 };
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	fcntl(uffd, F_SETFL, uffd_flags | O_NONBLOCK);
77462306a36Sopenharmony_ci
77562306a36Sopenharmony_ci	if (uffd_register(uffd, area_dst, nr_pages * page_size,
77662306a36Sopenharmony_ci			  true, wp, false))
77762306a36Sopenharmony_ci		err("register failure");
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	if (faulting_process(1, wp))
78062306a36Sopenharmony_ci		err("faulting process failed");
78162306a36Sopenharmony_ci
78262306a36Sopenharmony_ci	uffd_test_ops->release_pages(area_dst);
78362306a36Sopenharmony_ci
78462306a36Sopenharmony_ci	args.apply_wp = wp;
78562306a36Sopenharmony_ci	if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args))
78662306a36Sopenharmony_ci		err("uffd_poll_thread create");
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_ci	pid = fork();
78962306a36Sopenharmony_ci	if (pid < 0)
79062306a36Sopenharmony_ci		err("fork");
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	if (!pid)
79362306a36Sopenharmony_ci		exit(faulting_process(2, wp));
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_ci	waitpid(pid, &err, 0);
79662306a36Sopenharmony_ci	if (err)
79762306a36Sopenharmony_ci		err("faulting process failed");
79862306a36Sopenharmony_ci	if (write(pipefd[1], &c, sizeof(c)) != sizeof(c))
79962306a36Sopenharmony_ci		err("pipe write");
80062306a36Sopenharmony_ci	if (pthread_join(uffd_mon, (void **)&userfaults))
80162306a36Sopenharmony_ci		err("pthread_join()");
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci	if (userfaults)
80462306a36Sopenharmony_ci		uffd_test_fail("Signal test failed, userfaults: %ld", userfaults);
80562306a36Sopenharmony_ci	else
80662306a36Sopenharmony_ci		uffd_test_pass();
80762306a36Sopenharmony_ci}
80862306a36Sopenharmony_ci
80962306a36Sopenharmony_cistatic void uffd_sigbus_test(uffd_test_args_t *args)
81062306a36Sopenharmony_ci{
81162306a36Sopenharmony_ci	uffd_sigbus_test_common(false);
81262306a36Sopenharmony_ci}
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_cistatic void uffd_sigbus_wp_test(uffd_test_args_t *args)
81562306a36Sopenharmony_ci{
81662306a36Sopenharmony_ci	uffd_sigbus_test_common(true);
81762306a36Sopenharmony_ci}
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_cistatic void uffd_events_test_common(bool wp)
82062306a36Sopenharmony_ci{
82162306a36Sopenharmony_ci	pthread_t uffd_mon;
82262306a36Sopenharmony_ci	pid_t pid;
82362306a36Sopenharmony_ci	int err;
82462306a36Sopenharmony_ci	char c;
82562306a36Sopenharmony_ci	struct uffd_args args = { 0 };
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	fcntl(uffd, F_SETFL, uffd_flags | O_NONBLOCK);
82862306a36Sopenharmony_ci	if (uffd_register(uffd, area_dst, nr_pages * page_size,
82962306a36Sopenharmony_ci			  true, wp, false))
83062306a36Sopenharmony_ci		err("register failure");
83162306a36Sopenharmony_ci
83262306a36Sopenharmony_ci	args.apply_wp = wp;
83362306a36Sopenharmony_ci	if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args))
83462306a36Sopenharmony_ci		err("uffd_poll_thread create");
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_ci	pid = fork();
83762306a36Sopenharmony_ci	if (pid < 0)
83862306a36Sopenharmony_ci		err("fork");
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci	if (!pid)
84162306a36Sopenharmony_ci		exit(faulting_process(0, wp));
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci	waitpid(pid, &err, 0);
84462306a36Sopenharmony_ci	if (err)
84562306a36Sopenharmony_ci		err("faulting process failed");
84662306a36Sopenharmony_ci	if (write(pipefd[1], &c, sizeof(c)) != sizeof(c))
84762306a36Sopenharmony_ci		err("pipe write");
84862306a36Sopenharmony_ci	if (pthread_join(uffd_mon, NULL))
84962306a36Sopenharmony_ci		err("pthread_join()");
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci	if (args.missing_faults != nr_pages)
85262306a36Sopenharmony_ci		uffd_test_fail("Fault counts wrong");
85362306a36Sopenharmony_ci	else
85462306a36Sopenharmony_ci		uffd_test_pass();
85562306a36Sopenharmony_ci}
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_cistatic void uffd_events_test(uffd_test_args_t *args)
85862306a36Sopenharmony_ci{
85962306a36Sopenharmony_ci	uffd_events_test_common(false);
86062306a36Sopenharmony_ci}
86162306a36Sopenharmony_ci
86262306a36Sopenharmony_cistatic void uffd_events_wp_test(uffd_test_args_t *args)
86362306a36Sopenharmony_ci{
86462306a36Sopenharmony_ci	uffd_events_test_common(true);
86562306a36Sopenharmony_ci}
86662306a36Sopenharmony_ci
86762306a36Sopenharmony_cistatic void retry_uffdio_zeropage(int ufd,
86862306a36Sopenharmony_ci				  struct uffdio_zeropage *uffdio_zeropage)
86962306a36Sopenharmony_ci{
87062306a36Sopenharmony_ci	uffd_test_ops->alias_mapping(&uffdio_zeropage->range.start,
87162306a36Sopenharmony_ci				     uffdio_zeropage->range.len,
87262306a36Sopenharmony_ci				     0);
87362306a36Sopenharmony_ci	if (ioctl(ufd, UFFDIO_ZEROPAGE, uffdio_zeropage)) {
87462306a36Sopenharmony_ci		if (uffdio_zeropage->zeropage != -EEXIST)
87562306a36Sopenharmony_ci			err("UFFDIO_ZEROPAGE error: %"PRId64,
87662306a36Sopenharmony_ci			    (int64_t)uffdio_zeropage->zeropage);
87762306a36Sopenharmony_ci	} else {
87862306a36Sopenharmony_ci		err("UFFDIO_ZEROPAGE error: %"PRId64,
87962306a36Sopenharmony_ci		    (int64_t)uffdio_zeropage->zeropage);
88062306a36Sopenharmony_ci	}
88162306a36Sopenharmony_ci}
88262306a36Sopenharmony_ci
88362306a36Sopenharmony_cistatic bool do_uffdio_zeropage(int ufd, bool has_zeropage)
88462306a36Sopenharmony_ci{
88562306a36Sopenharmony_ci	struct uffdio_zeropage uffdio_zeropage = { 0 };
88662306a36Sopenharmony_ci	int ret;
88762306a36Sopenharmony_ci	__s64 res;
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_ci	uffdio_zeropage.range.start = (unsigned long) area_dst;
89062306a36Sopenharmony_ci	uffdio_zeropage.range.len = page_size;
89162306a36Sopenharmony_ci	uffdio_zeropage.mode = 0;
89262306a36Sopenharmony_ci	ret = ioctl(ufd, UFFDIO_ZEROPAGE, &uffdio_zeropage);
89362306a36Sopenharmony_ci	res = uffdio_zeropage.zeropage;
89462306a36Sopenharmony_ci	if (ret) {
89562306a36Sopenharmony_ci		/* real retval in ufdio_zeropage.zeropage */
89662306a36Sopenharmony_ci		if (has_zeropage)
89762306a36Sopenharmony_ci			err("UFFDIO_ZEROPAGE error: %"PRId64, (int64_t)res);
89862306a36Sopenharmony_ci		else if (res != -EINVAL)
89962306a36Sopenharmony_ci			err("UFFDIO_ZEROPAGE not -EINVAL");
90062306a36Sopenharmony_ci	} else if (has_zeropage) {
90162306a36Sopenharmony_ci		if (res != page_size)
90262306a36Sopenharmony_ci			err("UFFDIO_ZEROPAGE unexpected size");
90362306a36Sopenharmony_ci		else
90462306a36Sopenharmony_ci			retry_uffdio_zeropage(ufd, &uffdio_zeropage);
90562306a36Sopenharmony_ci		return true;
90662306a36Sopenharmony_ci	} else
90762306a36Sopenharmony_ci		err("UFFDIO_ZEROPAGE succeeded");
90862306a36Sopenharmony_ci
90962306a36Sopenharmony_ci	return false;
91062306a36Sopenharmony_ci}
91162306a36Sopenharmony_ci
91262306a36Sopenharmony_ci/*
91362306a36Sopenharmony_ci * Registers a range with MISSING mode only for zeropage test.  Return true
91462306a36Sopenharmony_ci * if UFFDIO_ZEROPAGE supported, false otherwise. Can't use uffd_register()
91562306a36Sopenharmony_ci * because we want to detect .ioctls along the way.
91662306a36Sopenharmony_ci */
91762306a36Sopenharmony_cistatic bool
91862306a36Sopenharmony_ciuffd_register_detect_zeropage(int uffd, void *addr, uint64_t len)
91962306a36Sopenharmony_ci{
92062306a36Sopenharmony_ci	uint64_t ioctls = 0;
92162306a36Sopenharmony_ci
92262306a36Sopenharmony_ci	if (uffd_register_with_ioctls(uffd, addr, len, true,
92362306a36Sopenharmony_ci				      false, false, &ioctls))
92462306a36Sopenharmony_ci		err("zeropage register fail");
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci	return ioctls & (1 << _UFFDIO_ZEROPAGE);
92762306a36Sopenharmony_ci}
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci/* exercise UFFDIO_ZEROPAGE */
93062306a36Sopenharmony_cistatic void uffd_zeropage_test(uffd_test_args_t *args)
93162306a36Sopenharmony_ci{
93262306a36Sopenharmony_ci	bool has_zeropage;
93362306a36Sopenharmony_ci	int i;
93462306a36Sopenharmony_ci
93562306a36Sopenharmony_ci	has_zeropage = uffd_register_detect_zeropage(uffd, area_dst, page_size);
93662306a36Sopenharmony_ci	if (area_dst_alias)
93762306a36Sopenharmony_ci		/* Ignore the retval; we already have it */
93862306a36Sopenharmony_ci		uffd_register_detect_zeropage(uffd, area_dst_alias, page_size);
93962306a36Sopenharmony_ci
94062306a36Sopenharmony_ci	if (do_uffdio_zeropage(uffd, has_zeropage))
94162306a36Sopenharmony_ci		for (i = 0; i < page_size; i++)
94262306a36Sopenharmony_ci			if (area_dst[i] != 0)
94362306a36Sopenharmony_ci				err("data non-zero at offset %d\n", i);
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ci	if (uffd_unregister(uffd, area_dst, page_size))
94662306a36Sopenharmony_ci		err("unregister");
94762306a36Sopenharmony_ci
94862306a36Sopenharmony_ci	if (area_dst_alias && uffd_unregister(uffd, area_dst_alias, page_size))
94962306a36Sopenharmony_ci		err("unregister");
95062306a36Sopenharmony_ci
95162306a36Sopenharmony_ci	uffd_test_pass();
95262306a36Sopenharmony_ci}
95362306a36Sopenharmony_ci
95462306a36Sopenharmony_cistatic void uffd_register_poison(int uffd, void *addr, uint64_t len)
95562306a36Sopenharmony_ci{
95662306a36Sopenharmony_ci	uint64_t ioctls = 0;
95762306a36Sopenharmony_ci	uint64_t expected = (1 << _UFFDIO_COPY) | (1 << _UFFDIO_POISON);
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_ci	if (uffd_register_with_ioctls(uffd, addr, len, true,
96062306a36Sopenharmony_ci				      false, false, &ioctls))
96162306a36Sopenharmony_ci		err("poison register fail");
96262306a36Sopenharmony_ci
96362306a36Sopenharmony_ci	if ((ioctls & expected) != expected)
96462306a36Sopenharmony_ci		err("registered area doesn't support COPY and POISON ioctls");
96562306a36Sopenharmony_ci}
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_cistatic void do_uffdio_poison(int uffd, unsigned long offset)
96862306a36Sopenharmony_ci{
96962306a36Sopenharmony_ci	struct uffdio_poison uffdio_poison = { 0 };
97062306a36Sopenharmony_ci	int ret;
97162306a36Sopenharmony_ci	__s64 res;
97262306a36Sopenharmony_ci
97362306a36Sopenharmony_ci	uffdio_poison.range.start = (unsigned long) area_dst + offset;
97462306a36Sopenharmony_ci	uffdio_poison.range.len = page_size;
97562306a36Sopenharmony_ci	uffdio_poison.mode = 0;
97662306a36Sopenharmony_ci	ret = ioctl(uffd, UFFDIO_POISON, &uffdio_poison);
97762306a36Sopenharmony_ci	res = uffdio_poison.updated;
97862306a36Sopenharmony_ci
97962306a36Sopenharmony_ci	if (ret)
98062306a36Sopenharmony_ci		err("UFFDIO_POISON error: %"PRId64, (int64_t)res);
98162306a36Sopenharmony_ci	else if (res != page_size)
98262306a36Sopenharmony_ci		err("UFFDIO_POISON unexpected size: %"PRId64, (int64_t)res);
98362306a36Sopenharmony_ci}
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_cistatic void uffd_poison_handle_fault(
98662306a36Sopenharmony_ci	struct uffd_msg *msg, struct uffd_args *args)
98762306a36Sopenharmony_ci{
98862306a36Sopenharmony_ci	unsigned long offset;
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci	if (msg->event != UFFD_EVENT_PAGEFAULT)
99162306a36Sopenharmony_ci		err("unexpected msg event %u", msg->event);
99262306a36Sopenharmony_ci
99362306a36Sopenharmony_ci	if (msg->arg.pagefault.flags &
99462306a36Sopenharmony_ci	    (UFFD_PAGEFAULT_FLAG_WP | UFFD_PAGEFAULT_FLAG_MINOR))
99562306a36Sopenharmony_ci		err("unexpected fault type %llu", msg->arg.pagefault.flags);
99662306a36Sopenharmony_ci
99762306a36Sopenharmony_ci	offset = (char *)(unsigned long)msg->arg.pagefault.address - area_dst;
99862306a36Sopenharmony_ci	offset &= ~(page_size-1);
99962306a36Sopenharmony_ci
100062306a36Sopenharmony_ci	/* Odd pages -> copy zeroed page; even pages -> poison. */
100162306a36Sopenharmony_ci	if (offset & page_size)
100262306a36Sopenharmony_ci		copy_page(uffd, offset, false);
100362306a36Sopenharmony_ci	else
100462306a36Sopenharmony_ci		do_uffdio_poison(uffd, offset);
100562306a36Sopenharmony_ci}
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_cistatic void uffd_poison_test(uffd_test_args_t *targs)
100862306a36Sopenharmony_ci{
100962306a36Sopenharmony_ci	pthread_t uffd_mon;
101062306a36Sopenharmony_ci	char c;
101162306a36Sopenharmony_ci	struct uffd_args args = { 0 };
101262306a36Sopenharmony_ci	struct sigaction act = { 0 };
101362306a36Sopenharmony_ci	unsigned long nr_sigbus = 0;
101462306a36Sopenharmony_ci	unsigned long nr;
101562306a36Sopenharmony_ci
101662306a36Sopenharmony_ci	fcntl(uffd, F_SETFL, uffd_flags | O_NONBLOCK);
101762306a36Sopenharmony_ci
101862306a36Sopenharmony_ci	uffd_register_poison(uffd, area_dst, nr_pages * page_size);
101962306a36Sopenharmony_ci	memset(area_src, 0, nr_pages * page_size);
102062306a36Sopenharmony_ci
102162306a36Sopenharmony_ci	args.handle_fault = uffd_poison_handle_fault;
102262306a36Sopenharmony_ci	if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args))
102362306a36Sopenharmony_ci		err("uffd_poll_thread create");
102462306a36Sopenharmony_ci
102562306a36Sopenharmony_ci	sigbuf = &jbuf;
102662306a36Sopenharmony_ci	act.sa_sigaction = sighndl;
102762306a36Sopenharmony_ci	act.sa_flags = SA_SIGINFO;
102862306a36Sopenharmony_ci	if (sigaction(SIGBUS, &act, 0))
102962306a36Sopenharmony_ci		err("sigaction");
103062306a36Sopenharmony_ci
103162306a36Sopenharmony_ci	for (nr = 0; nr < nr_pages; ++nr) {
103262306a36Sopenharmony_ci		unsigned long offset = nr * page_size;
103362306a36Sopenharmony_ci		const char *bytes = (const char *) area_dst + offset;
103462306a36Sopenharmony_ci		const char *i;
103562306a36Sopenharmony_ci
103662306a36Sopenharmony_ci		if (sigsetjmp(*sigbuf, 1)) {
103762306a36Sopenharmony_ci			/*
103862306a36Sopenharmony_ci			 * Access below triggered a SIGBUS, which was caught by
103962306a36Sopenharmony_ci			 * sighndl, which then jumped here. Count this SIGBUS,
104062306a36Sopenharmony_ci			 * and move on to next page.
104162306a36Sopenharmony_ci			 */
104262306a36Sopenharmony_ci			++nr_sigbus;
104362306a36Sopenharmony_ci			continue;
104462306a36Sopenharmony_ci		}
104562306a36Sopenharmony_ci
104662306a36Sopenharmony_ci		for (i = bytes; i < bytes + page_size; ++i) {
104762306a36Sopenharmony_ci			if (*i)
104862306a36Sopenharmony_ci				err("nonzero byte in area_dst (%p) at %p: %u",
104962306a36Sopenharmony_ci				    area_dst, i, *i);
105062306a36Sopenharmony_ci		}
105162306a36Sopenharmony_ci	}
105262306a36Sopenharmony_ci
105362306a36Sopenharmony_ci	if (write(pipefd[1], &c, sizeof(c)) != sizeof(c))
105462306a36Sopenharmony_ci		err("pipe write");
105562306a36Sopenharmony_ci	if (pthread_join(uffd_mon, NULL))
105662306a36Sopenharmony_ci		err("pthread_join()");
105762306a36Sopenharmony_ci
105862306a36Sopenharmony_ci	if (nr_sigbus != nr_pages / 2)
105962306a36Sopenharmony_ci		err("expected to receive %lu SIGBUS, actually received %lu",
106062306a36Sopenharmony_ci		    nr_pages / 2, nr_sigbus);
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci	uffd_test_pass();
106362306a36Sopenharmony_ci}
106462306a36Sopenharmony_ci
106562306a36Sopenharmony_ci/*
106662306a36Sopenharmony_ci * Test the returned uffdio_register.ioctls with different register modes.
106762306a36Sopenharmony_ci * Note that _UFFDIO_ZEROPAGE is tested separately in the zeropage test.
106862306a36Sopenharmony_ci */
106962306a36Sopenharmony_cistatic void
107062306a36Sopenharmony_cido_register_ioctls_test(uffd_test_args_t *args, bool miss, bool wp, bool minor)
107162306a36Sopenharmony_ci{
107262306a36Sopenharmony_ci	uint64_t ioctls = 0, expected = BIT_ULL(_UFFDIO_WAKE);
107362306a36Sopenharmony_ci	mem_type_t *mem_type = args->mem_type;
107462306a36Sopenharmony_ci	int ret;
107562306a36Sopenharmony_ci
107662306a36Sopenharmony_ci	ret = uffd_register_with_ioctls(uffd, area_dst, page_size,
107762306a36Sopenharmony_ci					miss, wp, minor, &ioctls);
107862306a36Sopenharmony_ci
107962306a36Sopenharmony_ci	/*
108062306a36Sopenharmony_ci	 * Handle special cases of UFFDIO_REGISTER here where it should
108162306a36Sopenharmony_ci	 * just fail with -EINVAL first..
108262306a36Sopenharmony_ci	 *
108362306a36Sopenharmony_ci	 * Case 1: register MINOR on anon
108462306a36Sopenharmony_ci	 * Case 2: register with no mode selected
108562306a36Sopenharmony_ci	 */
108662306a36Sopenharmony_ci	if ((minor && (mem_type->mem_flag == MEM_ANON)) ||
108762306a36Sopenharmony_ci	    (!miss && !wp && !minor)) {
108862306a36Sopenharmony_ci		if (ret != -EINVAL)
108962306a36Sopenharmony_ci			err("register (miss=%d, wp=%d, minor=%d) failed "
109062306a36Sopenharmony_ci			    "with wrong errno=%d", miss, wp, minor, ret);
109162306a36Sopenharmony_ci		return;
109262306a36Sopenharmony_ci	}
109362306a36Sopenharmony_ci
109462306a36Sopenharmony_ci	/* UFFDIO_REGISTER should succeed, then check ioctls returned */
109562306a36Sopenharmony_ci	if (miss)
109662306a36Sopenharmony_ci		expected |= BIT_ULL(_UFFDIO_COPY);
109762306a36Sopenharmony_ci	if (wp)
109862306a36Sopenharmony_ci		expected |= BIT_ULL(_UFFDIO_WRITEPROTECT);
109962306a36Sopenharmony_ci	if (minor)
110062306a36Sopenharmony_ci		expected |= BIT_ULL(_UFFDIO_CONTINUE);
110162306a36Sopenharmony_ci
110262306a36Sopenharmony_ci	if ((ioctls & expected) != expected)
110362306a36Sopenharmony_ci		err("unexpected uffdio_register.ioctls "
110462306a36Sopenharmony_ci		    "(miss=%d, wp=%d, minor=%d): expected=0x%"PRIx64", "
110562306a36Sopenharmony_ci		    "returned=0x%"PRIx64, miss, wp, minor, expected, ioctls);
110662306a36Sopenharmony_ci
110762306a36Sopenharmony_ci	if (uffd_unregister(uffd, area_dst, page_size))
110862306a36Sopenharmony_ci		err("unregister");
110962306a36Sopenharmony_ci}
111062306a36Sopenharmony_ci
111162306a36Sopenharmony_cistatic void uffd_register_ioctls_test(uffd_test_args_t *args)
111262306a36Sopenharmony_ci{
111362306a36Sopenharmony_ci	int miss, wp, minor;
111462306a36Sopenharmony_ci
111562306a36Sopenharmony_ci	for (miss = 0; miss <= 1; miss++)
111662306a36Sopenharmony_ci		for (wp = 0; wp <= 1; wp++)
111762306a36Sopenharmony_ci			for (minor = 0; minor <= 1; minor++)
111862306a36Sopenharmony_ci				do_register_ioctls_test(args, miss, wp, minor);
111962306a36Sopenharmony_ci
112062306a36Sopenharmony_ci	uffd_test_pass();
112162306a36Sopenharmony_ci}
112262306a36Sopenharmony_ci
112362306a36Sopenharmony_ciuffd_test_case_t uffd_tests[] = {
112462306a36Sopenharmony_ci	{
112562306a36Sopenharmony_ci		/* Test returned uffdio_register.ioctls. */
112662306a36Sopenharmony_ci		.name = "register-ioctls",
112762306a36Sopenharmony_ci		.uffd_fn = uffd_register_ioctls_test,
112862306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
112962306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_MISSING_HUGETLBFS |
113062306a36Sopenharmony_ci		UFFD_FEATURE_MISSING_SHMEM |
113162306a36Sopenharmony_ci		UFFD_FEATURE_PAGEFAULT_FLAG_WP |
113262306a36Sopenharmony_ci		UFFD_FEATURE_WP_HUGETLBFS_SHMEM |
113362306a36Sopenharmony_ci		UFFD_FEATURE_MINOR_HUGETLBFS |
113462306a36Sopenharmony_ci		UFFD_FEATURE_MINOR_SHMEM,
113562306a36Sopenharmony_ci	},
113662306a36Sopenharmony_ci	{
113762306a36Sopenharmony_ci		.name = "zeropage",
113862306a36Sopenharmony_ci		.uffd_fn = uffd_zeropage_test,
113962306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
114062306a36Sopenharmony_ci		.uffd_feature_required = 0,
114162306a36Sopenharmony_ci	},
114262306a36Sopenharmony_ci	{
114362306a36Sopenharmony_ci		.name = "wp-fork",
114462306a36Sopenharmony_ci		.uffd_fn = uffd_wp_fork_test,
114562306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
114662306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP |
114762306a36Sopenharmony_ci		UFFD_FEATURE_WP_HUGETLBFS_SHMEM,
114862306a36Sopenharmony_ci	},
114962306a36Sopenharmony_ci	{
115062306a36Sopenharmony_ci		.name = "wp-fork-with-event",
115162306a36Sopenharmony_ci		.uffd_fn = uffd_wp_fork_with_event_test,
115262306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
115362306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP |
115462306a36Sopenharmony_ci		UFFD_FEATURE_WP_HUGETLBFS_SHMEM |
115562306a36Sopenharmony_ci		/* when set, child process should inherit uffd-wp bits */
115662306a36Sopenharmony_ci		UFFD_FEATURE_EVENT_FORK,
115762306a36Sopenharmony_ci	},
115862306a36Sopenharmony_ci	{
115962306a36Sopenharmony_ci		.name = "wp-fork-pin",
116062306a36Sopenharmony_ci		.uffd_fn = uffd_wp_fork_pin_test,
116162306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
116262306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP |
116362306a36Sopenharmony_ci		UFFD_FEATURE_WP_HUGETLBFS_SHMEM,
116462306a36Sopenharmony_ci	},
116562306a36Sopenharmony_ci	{
116662306a36Sopenharmony_ci		.name = "wp-fork-pin-with-event",
116762306a36Sopenharmony_ci		.uffd_fn = uffd_wp_fork_pin_with_event_test,
116862306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
116962306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP |
117062306a36Sopenharmony_ci		UFFD_FEATURE_WP_HUGETLBFS_SHMEM |
117162306a36Sopenharmony_ci		/* when set, child process should inherit uffd-wp bits */
117262306a36Sopenharmony_ci		UFFD_FEATURE_EVENT_FORK,
117362306a36Sopenharmony_ci	},
117462306a36Sopenharmony_ci	{
117562306a36Sopenharmony_ci		.name = "wp-unpopulated",
117662306a36Sopenharmony_ci		.uffd_fn = uffd_wp_unpopulated_test,
117762306a36Sopenharmony_ci		.mem_targets = MEM_ANON,
117862306a36Sopenharmony_ci		.uffd_feature_required =
117962306a36Sopenharmony_ci		UFFD_FEATURE_PAGEFAULT_FLAG_WP | UFFD_FEATURE_WP_UNPOPULATED,
118062306a36Sopenharmony_ci	},
118162306a36Sopenharmony_ci	{
118262306a36Sopenharmony_ci		.name = "minor",
118362306a36Sopenharmony_ci		.uffd_fn = uffd_minor_test,
118462306a36Sopenharmony_ci		.mem_targets = MEM_SHMEM | MEM_HUGETLB,
118562306a36Sopenharmony_ci		.uffd_feature_required =
118662306a36Sopenharmony_ci		UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM,
118762306a36Sopenharmony_ci	},
118862306a36Sopenharmony_ci	{
118962306a36Sopenharmony_ci		.name = "minor-wp",
119062306a36Sopenharmony_ci		.uffd_fn = uffd_minor_wp_test,
119162306a36Sopenharmony_ci		.mem_targets = MEM_SHMEM | MEM_HUGETLB,
119262306a36Sopenharmony_ci		.uffd_feature_required =
119362306a36Sopenharmony_ci		UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM |
119462306a36Sopenharmony_ci		UFFD_FEATURE_PAGEFAULT_FLAG_WP |
119562306a36Sopenharmony_ci		/*
119662306a36Sopenharmony_ci		 * HACK: here we leveraged WP_UNPOPULATED to detect whether
119762306a36Sopenharmony_ci		 * minor mode supports wr-protect.  There's no feature flag
119862306a36Sopenharmony_ci		 * for it so this is the best we can test against.
119962306a36Sopenharmony_ci		 */
120062306a36Sopenharmony_ci		UFFD_FEATURE_WP_UNPOPULATED,
120162306a36Sopenharmony_ci	},
120262306a36Sopenharmony_ci	{
120362306a36Sopenharmony_ci		.name = "minor-collapse",
120462306a36Sopenharmony_ci		.uffd_fn = uffd_minor_collapse_test,
120562306a36Sopenharmony_ci		/* MADV_COLLAPSE only works with shmem */
120662306a36Sopenharmony_ci		.mem_targets = MEM_SHMEM,
120762306a36Sopenharmony_ci		/* We can't test MADV_COLLAPSE, so try our luck */
120862306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_MINOR_SHMEM,
120962306a36Sopenharmony_ci	},
121062306a36Sopenharmony_ci	{
121162306a36Sopenharmony_ci		.name = "sigbus",
121262306a36Sopenharmony_ci		.uffd_fn = uffd_sigbus_test,
121362306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
121462306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_SIGBUS |
121562306a36Sopenharmony_ci		UFFD_FEATURE_EVENT_FORK,
121662306a36Sopenharmony_ci	},
121762306a36Sopenharmony_ci	{
121862306a36Sopenharmony_ci		.name = "sigbus-wp",
121962306a36Sopenharmony_ci		.uffd_fn = uffd_sigbus_wp_test,
122062306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
122162306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_SIGBUS |
122262306a36Sopenharmony_ci		UFFD_FEATURE_EVENT_FORK | UFFD_FEATURE_PAGEFAULT_FLAG_WP,
122362306a36Sopenharmony_ci	},
122462306a36Sopenharmony_ci	{
122562306a36Sopenharmony_ci		.name = "events",
122662306a36Sopenharmony_ci		.uffd_fn = uffd_events_test,
122762306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
122862306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_EVENT_FORK |
122962306a36Sopenharmony_ci		UFFD_FEATURE_EVENT_REMAP | UFFD_FEATURE_EVENT_REMOVE,
123062306a36Sopenharmony_ci	},
123162306a36Sopenharmony_ci	{
123262306a36Sopenharmony_ci		.name = "events-wp",
123362306a36Sopenharmony_ci		.uffd_fn = uffd_events_wp_test,
123462306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
123562306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_EVENT_FORK |
123662306a36Sopenharmony_ci		UFFD_FEATURE_EVENT_REMAP | UFFD_FEATURE_EVENT_REMOVE |
123762306a36Sopenharmony_ci		UFFD_FEATURE_PAGEFAULT_FLAG_WP |
123862306a36Sopenharmony_ci		UFFD_FEATURE_WP_HUGETLBFS_SHMEM,
123962306a36Sopenharmony_ci	},
124062306a36Sopenharmony_ci	{
124162306a36Sopenharmony_ci		.name = "poison",
124262306a36Sopenharmony_ci		.uffd_fn = uffd_poison_test,
124362306a36Sopenharmony_ci		.mem_targets = MEM_ALL,
124462306a36Sopenharmony_ci		.uffd_feature_required = UFFD_FEATURE_POISON,
124562306a36Sopenharmony_ci	},
124662306a36Sopenharmony_ci};
124762306a36Sopenharmony_ci
124862306a36Sopenharmony_cistatic void usage(const char *prog)
124962306a36Sopenharmony_ci{
125062306a36Sopenharmony_ci	printf("usage: %s [-f TESTNAME]\n", prog);
125162306a36Sopenharmony_ci	puts("");
125262306a36Sopenharmony_ci	puts(" -f: test name to filter (e.g., event)");
125362306a36Sopenharmony_ci	puts(" -h: show the help msg");
125462306a36Sopenharmony_ci	puts(" -l: list tests only");
125562306a36Sopenharmony_ci	puts("");
125662306a36Sopenharmony_ci	exit(KSFT_FAIL);
125762306a36Sopenharmony_ci}
125862306a36Sopenharmony_ci
125962306a36Sopenharmony_ciint main(int argc, char *argv[])
126062306a36Sopenharmony_ci{
126162306a36Sopenharmony_ci	int n_tests = sizeof(uffd_tests) / sizeof(uffd_test_case_t);
126262306a36Sopenharmony_ci	int n_mems = sizeof(mem_types) / sizeof(mem_type_t);
126362306a36Sopenharmony_ci	const char *test_filter = NULL;
126462306a36Sopenharmony_ci	bool list_only = false;
126562306a36Sopenharmony_ci	uffd_test_case_t *test;
126662306a36Sopenharmony_ci	mem_type_t *mem_type;
126762306a36Sopenharmony_ci	uffd_test_args_t args;
126862306a36Sopenharmony_ci	const char *errmsg;
126962306a36Sopenharmony_ci	int has_uffd, opt;
127062306a36Sopenharmony_ci	int i, j;
127162306a36Sopenharmony_ci
127262306a36Sopenharmony_ci	while ((opt = getopt(argc, argv, "f:hl")) != -1) {
127362306a36Sopenharmony_ci		switch (opt) {
127462306a36Sopenharmony_ci		case 'f':
127562306a36Sopenharmony_ci			test_filter = optarg;
127662306a36Sopenharmony_ci			break;
127762306a36Sopenharmony_ci		case 'l':
127862306a36Sopenharmony_ci			list_only = true;
127962306a36Sopenharmony_ci			break;
128062306a36Sopenharmony_ci		case 'h':
128162306a36Sopenharmony_ci		default:
128262306a36Sopenharmony_ci			/* Unknown */
128362306a36Sopenharmony_ci			usage(argv[0]);
128462306a36Sopenharmony_ci			break;
128562306a36Sopenharmony_ci		}
128662306a36Sopenharmony_ci	}
128762306a36Sopenharmony_ci
128862306a36Sopenharmony_ci	if (!test_filter && !list_only) {
128962306a36Sopenharmony_ci		has_uffd = test_uffd_api(false);
129062306a36Sopenharmony_ci		has_uffd |= test_uffd_api(true);
129162306a36Sopenharmony_ci
129262306a36Sopenharmony_ci		if (!has_uffd) {
129362306a36Sopenharmony_ci			printf("Userfaultfd not supported or unprivileged, skip all tests\n");
129462306a36Sopenharmony_ci			exit(KSFT_SKIP);
129562306a36Sopenharmony_ci		}
129662306a36Sopenharmony_ci	}
129762306a36Sopenharmony_ci
129862306a36Sopenharmony_ci	for (i = 0; i < n_tests; i++) {
129962306a36Sopenharmony_ci		test = &uffd_tests[i];
130062306a36Sopenharmony_ci		if (test_filter && !strstr(test->name, test_filter))
130162306a36Sopenharmony_ci			continue;
130262306a36Sopenharmony_ci		if (list_only) {
130362306a36Sopenharmony_ci			printf("%s\n", test->name);
130462306a36Sopenharmony_ci			continue;
130562306a36Sopenharmony_ci		}
130662306a36Sopenharmony_ci		for (j = 0; j < n_mems; j++) {
130762306a36Sopenharmony_ci			mem_type = &mem_types[j];
130862306a36Sopenharmony_ci			if (!(test->mem_targets & mem_type->mem_flag))
130962306a36Sopenharmony_ci				continue;
131062306a36Sopenharmony_ci
131162306a36Sopenharmony_ci			uffd_test_start("%s on %s", test->name, mem_type->name);
131262306a36Sopenharmony_ci			if ((mem_type->mem_flag == MEM_HUGETLB ||
131362306a36Sopenharmony_ci			    mem_type->mem_flag == MEM_HUGETLB_PRIVATE) &&
131462306a36Sopenharmony_ci			    (default_huge_page_size() == 0)) {
131562306a36Sopenharmony_ci				uffd_test_skip("huge page size is 0, feature missing?");
131662306a36Sopenharmony_ci				continue;
131762306a36Sopenharmony_ci			}
131862306a36Sopenharmony_ci			if (!uffd_feature_supported(test)) {
131962306a36Sopenharmony_ci				uffd_test_skip("feature missing");
132062306a36Sopenharmony_ci				continue;
132162306a36Sopenharmony_ci			}
132262306a36Sopenharmony_ci			if (uffd_setup_environment(&args, test, mem_type,
132362306a36Sopenharmony_ci						   &errmsg)) {
132462306a36Sopenharmony_ci				uffd_test_skip(errmsg);
132562306a36Sopenharmony_ci				continue;
132662306a36Sopenharmony_ci			}
132762306a36Sopenharmony_ci			test->uffd_fn(&args);
132862306a36Sopenharmony_ci		}
132962306a36Sopenharmony_ci	}
133062306a36Sopenharmony_ci
133162306a36Sopenharmony_ci	if (!list_only)
133262306a36Sopenharmony_ci		uffd_test_report();
133362306a36Sopenharmony_ci
133462306a36Sopenharmony_ci	return ksft_get_fail_cnt() ? KSFT_FAIL : KSFT_PASS;
133562306a36Sopenharmony_ci}
133662306a36Sopenharmony_ci
133762306a36Sopenharmony_ci#else /* __NR_userfaultfd */
133862306a36Sopenharmony_ci
133962306a36Sopenharmony_ci#warning "missing __NR_userfaultfd definition"
134062306a36Sopenharmony_ci
134162306a36Sopenharmony_ciint main(void)
134262306a36Sopenharmony_ci{
134362306a36Sopenharmony_ci	printf("Skipping %s (missing __NR_userfaultfd)\n", __file__);
134462306a36Sopenharmony_ci	return KSFT_SKIP;
134562306a36Sopenharmony_ci}
134662306a36Sopenharmony_ci
134762306a36Sopenharmony_ci#endif /* __NR_userfaultfd */
1348