162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * COW (Copy On Write) tests.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2022, Red Hat, Inc.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author(s): David Hildenbrand <david@redhat.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci#define _GNU_SOURCE
1062306a36Sopenharmony_ci#include <stdlib.h>
1162306a36Sopenharmony_ci#include <string.h>
1262306a36Sopenharmony_ci#include <stdbool.h>
1362306a36Sopenharmony_ci#include <stdint.h>
1462306a36Sopenharmony_ci#include <unistd.h>
1562306a36Sopenharmony_ci#include <errno.h>
1662306a36Sopenharmony_ci#include <fcntl.h>
1762306a36Sopenharmony_ci#include <assert.h>
1862306a36Sopenharmony_ci#include <linux/mman.h>
1962306a36Sopenharmony_ci#include <sys/mman.h>
2062306a36Sopenharmony_ci#include <sys/ioctl.h>
2162306a36Sopenharmony_ci#include <sys/wait.h>
2262306a36Sopenharmony_ci#include <linux/memfd.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include "local_config.h"
2562306a36Sopenharmony_ci#ifdef LOCAL_CONFIG_HAVE_LIBURING
2662306a36Sopenharmony_ci#include <liburing.h>
2762306a36Sopenharmony_ci#endif /* LOCAL_CONFIG_HAVE_LIBURING */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#include "../../../../mm/gup_test.h"
3062306a36Sopenharmony_ci#include "../kselftest.h"
3162306a36Sopenharmony_ci#include "vm_util.h"
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic size_t pagesize;
3462306a36Sopenharmony_cistatic int pagemap_fd;
3562306a36Sopenharmony_cistatic size_t thpsize;
3662306a36Sopenharmony_cistatic int nr_hugetlbsizes;
3762306a36Sopenharmony_cistatic size_t hugetlbsizes[10];
3862306a36Sopenharmony_cistatic int gup_fd;
3962306a36Sopenharmony_cistatic bool has_huge_zeropage;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic void detect_huge_zeropage(void)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	int fd = open("/sys/kernel/mm/transparent_hugepage/use_zero_page",
4462306a36Sopenharmony_ci		      O_RDONLY);
4562306a36Sopenharmony_ci	size_t enabled = 0;
4662306a36Sopenharmony_ci	char buf[15];
4762306a36Sopenharmony_ci	int ret;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (fd < 0)
5062306a36Sopenharmony_ci		return;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	ret = pread(fd, buf, sizeof(buf), 0);
5362306a36Sopenharmony_ci	if (ret > 0 && ret < sizeof(buf)) {
5462306a36Sopenharmony_ci		buf[ret] = 0;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci		enabled = strtoul(buf, NULL, 10);
5762306a36Sopenharmony_ci		if (enabled == 1) {
5862306a36Sopenharmony_ci			has_huge_zeropage = true;
5962306a36Sopenharmony_ci			ksft_print_msg("[INFO] huge zeropage is enabled\n");
6062306a36Sopenharmony_ci		}
6162306a36Sopenharmony_ci	}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	close(fd);
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic bool range_is_swapped(void *addr, size_t size)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	for (; size; addr += pagesize, size -= pagesize)
6962306a36Sopenharmony_ci		if (!pagemap_is_swapped(pagemap_fd, addr))
7062306a36Sopenharmony_ci			return false;
7162306a36Sopenharmony_ci	return true;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistruct comm_pipes {
7562306a36Sopenharmony_ci	int child_ready[2];
7662306a36Sopenharmony_ci	int parent_ready[2];
7762306a36Sopenharmony_ci};
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic int setup_comm_pipes(struct comm_pipes *comm_pipes)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	if (pipe(comm_pipes->child_ready) < 0)
8262306a36Sopenharmony_ci		return -errno;
8362306a36Sopenharmony_ci	if (pipe(comm_pipes->parent_ready) < 0) {
8462306a36Sopenharmony_ci		close(comm_pipes->child_ready[0]);
8562306a36Sopenharmony_ci		close(comm_pipes->child_ready[1]);
8662306a36Sopenharmony_ci		return -errno;
8762306a36Sopenharmony_ci	}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	return 0;
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic void close_comm_pipes(struct comm_pipes *comm_pipes)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	close(comm_pipes->child_ready[0]);
9562306a36Sopenharmony_ci	close(comm_pipes->child_ready[1]);
9662306a36Sopenharmony_ci	close(comm_pipes->parent_ready[0]);
9762306a36Sopenharmony_ci	close(comm_pipes->parent_ready[1]);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic int child_memcmp_fn(char *mem, size_t size,
10162306a36Sopenharmony_ci			   struct comm_pipes *comm_pipes)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	char *old = malloc(size);
10462306a36Sopenharmony_ci	char buf;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/* Backup the original content. */
10762306a36Sopenharmony_ci	memcpy(old, mem, size);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	/* Wait until the parent modified the page. */
11062306a36Sopenharmony_ci	write(comm_pipes->child_ready[1], "0", 1);
11162306a36Sopenharmony_ci	while (read(comm_pipes->parent_ready[0], &buf, 1) != 1)
11262306a36Sopenharmony_ci		;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	/* See if we still read the old values. */
11562306a36Sopenharmony_ci	return memcmp(old, mem, size);
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic int child_vmsplice_memcmp_fn(char *mem, size_t size,
11962306a36Sopenharmony_ci				    struct comm_pipes *comm_pipes)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	struct iovec iov = {
12262306a36Sopenharmony_ci		.iov_base = mem,
12362306a36Sopenharmony_ci		.iov_len = size,
12462306a36Sopenharmony_ci	};
12562306a36Sopenharmony_ci	ssize_t cur, total, transferred;
12662306a36Sopenharmony_ci	char *old, *new;
12762306a36Sopenharmony_ci	int fds[2];
12862306a36Sopenharmony_ci	char buf;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	old = malloc(size);
13162306a36Sopenharmony_ci	new = malloc(size);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	/* Backup the original content. */
13462306a36Sopenharmony_ci	memcpy(old, mem, size);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	if (pipe(fds) < 0)
13762306a36Sopenharmony_ci		return -errno;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	/* Trigger a read-only pin. */
14062306a36Sopenharmony_ci	transferred = vmsplice(fds[1], &iov, 1, 0);
14162306a36Sopenharmony_ci	if (transferred < 0)
14262306a36Sopenharmony_ci		return -errno;
14362306a36Sopenharmony_ci	if (transferred == 0)
14462306a36Sopenharmony_ci		return -EINVAL;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	/* Unmap it from our page tables. */
14762306a36Sopenharmony_ci	if (munmap(mem, size) < 0)
14862306a36Sopenharmony_ci		return -errno;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	/* Wait until the parent modified it. */
15162306a36Sopenharmony_ci	write(comm_pipes->child_ready[1], "0", 1);
15262306a36Sopenharmony_ci	while (read(comm_pipes->parent_ready[0], &buf, 1) != 1)
15362306a36Sopenharmony_ci		;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	/* See if we still read the old values via the pipe. */
15662306a36Sopenharmony_ci	for (total = 0; total < transferred; total += cur) {
15762306a36Sopenharmony_ci		cur = read(fds[0], new + total, transferred - total);
15862306a36Sopenharmony_ci		if (cur < 0)
15962306a36Sopenharmony_ci			return -errno;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	return memcmp(old, new, transferred);
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_citypedef int (*child_fn)(char *mem, size_t size, struct comm_pipes *comm_pipes);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic void do_test_cow_in_parent(char *mem, size_t size, bool do_mprotect,
16862306a36Sopenharmony_ci				  child_fn fn)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct comm_pipes comm_pipes;
17162306a36Sopenharmony_ci	char buf;
17262306a36Sopenharmony_ci	int ret;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	ret = setup_comm_pipes(&comm_pipes);
17562306a36Sopenharmony_ci	if (ret) {
17662306a36Sopenharmony_ci		ksft_test_result_fail("pipe() failed\n");
17762306a36Sopenharmony_ci		return;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	ret = fork();
18162306a36Sopenharmony_ci	if (ret < 0) {
18262306a36Sopenharmony_ci		ksft_test_result_fail("fork() failed\n");
18362306a36Sopenharmony_ci		goto close_comm_pipes;
18462306a36Sopenharmony_ci	} else if (!ret) {
18562306a36Sopenharmony_ci		exit(fn(mem, size, &comm_pipes));
18662306a36Sopenharmony_ci	}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	while (read(comm_pipes.child_ready[0], &buf, 1) != 1)
18962306a36Sopenharmony_ci		;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (do_mprotect) {
19262306a36Sopenharmony_ci		/*
19362306a36Sopenharmony_ci		 * mprotect() optimizations might try avoiding
19462306a36Sopenharmony_ci		 * write-faults by directly mapping pages writable.
19562306a36Sopenharmony_ci		 */
19662306a36Sopenharmony_ci		ret = mprotect(mem, size, PROT_READ);
19762306a36Sopenharmony_ci		ret |= mprotect(mem, size, PROT_READ|PROT_WRITE);
19862306a36Sopenharmony_ci		if (ret) {
19962306a36Sopenharmony_ci			ksft_test_result_fail("mprotect() failed\n");
20062306a36Sopenharmony_ci			write(comm_pipes.parent_ready[1], "0", 1);
20162306a36Sopenharmony_ci			wait(&ret);
20262306a36Sopenharmony_ci			goto close_comm_pipes;
20362306a36Sopenharmony_ci		}
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	/* Modify the page. */
20762306a36Sopenharmony_ci	memset(mem, 0xff, size);
20862306a36Sopenharmony_ci	write(comm_pipes.parent_ready[1], "0", 1);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	wait(&ret);
21162306a36Sopenharmony_ci	if (WIFEXITED(ret))
21262306a36Sopenharmony_ci		ret = WEXITSTATUS(ret);
21362306a36Sopenharmony_ci	else
21462306a36Sopenharmony_ci		ret = -EINVAL;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	ksft_test_result(!ret, "No leak from parent into child\n");
21762306a36Sopenharmony_ciclose_comm_pipes:
21862306a36Sopenharmony_ci	close_comm_pipes(&comm_pipes);
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic void test_cow_in_parent(char *mem, size_t size)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	do_test_cow_in_parent(mem, size, false, child_memcmp_fn);
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cistatic void test_cow_in_parent_mprotect(char *mem, size_t size)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	do_test_cow_in_parent(mem, size, true, child_memcmp_fn);
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic void test_vmsplice_in_child(char *mem, size_t size)
23262306a36Sopenharmony_ci{
23362306a36Sopenharmony_ci	do_test_cow_in_parent(mem, size, false, child_vmsplice_memcmp_fn);
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic void test_vmsplice_in_child_mprotect(char *mem, size_t size)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	do_test_cow_in_parent(mem, size, true, child_vmsplice_memcmp_fn);
23962306a36Sopenharmony_ci}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_cistatic void do_test_vmsplice_in_parent(char *mem, size_t size,
24262306a36Sopenharmony_ci				       bool before_fork)
24362306a36Sopenharmony_ci{
24462306a36Sopenharmony_ci	struct iovec iov = {
24562306a36Sopenharmony_ci		.iov_base = mem,
24662306a36Sopenharmony_ci		.iov_len = size,
24762306a36Sopenharmony_ci	};
24862306a36Sopenharmony_ci	ssize_t cur, total, transferred;
24962306a36Sopenharmony_ci	struct comm_pipes comm_pipes;
25062306a36Sopenharmony_ci	char *old, *new;
25162306a36Sopenharmony_ci	int ret, fds[2];
25262306a36Sopenharmony_ci	char buf;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	old = malloc(size);
25562306a36Sopenharmony_ci	new = malloc(size);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	memcpy(old, mem, size);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	ret = setup_comm_pipes(&comm_pipes);
26062306a36Sopenharmony_ci	if (ret) {
26162306a36Sopenharmony_ci		ksft_test_result_fail("pipe() failed\n");
26262306a36Sopenharmony_ci		goto free;
26362306a36Sopenharmony_ci	}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	if (pipe(fds) < 0) {
26662306a36Sopenharmony_ci		ksft_test_result_fail("pipe() failed\n");
26762306a36Sopenharmony_ci		goto close_comm_pipes;
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (before_fork) {
27162306a36Sopenharmony_ci		transferred = vmsplice(fds[1], &iov, 1, 0);
27262306a36Sopenharmony_ci		if (transferred <= 0) {
27362306a36Sopenharmony_ci			ksft_test_result_fail("vmsplice() failed\n");
27462306a36Sopenharmony_ci			goto close_pipe;
27562306a36Sopenharmony_ci		}
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	ret = fork();
27962306a36Sopenharmony_ci	if (ret < 0) {
28062306a36Sopenharmony_ci		ksft_test_result_fail("fork() failed\n");
28162306a36Sopenharmony_ci		goto close_pipe;
28262306a36Sopenharmony_ci	} else if (!ret) {
28362306a36Sopenharmony_ci		write(comm_pipes.child_ready[1], "0", 1);
28462306a36Sopenharmony_ci		while (read(comm_pipes.parent_ready[0], &buf, 1) != 1)
28562306a36Sopenharmony_ci			;
28662306a36Sopenharmony_ci		/* Modify page content in the child. */
28762306a36Sopenharmony_ci		memset(mem, 0xff, size);
28862306a36Sopenharmony_ci		exit(0);
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	if (!before_fork) {
29262306a36Sopenharmony_ci		transferred = vmsplice(fds[1], &iov, 1, 0);
29362306a36Sopenharmony_ci		if (transferred <= 0) {
29462306a36Sopenharmony_ci			ksft_test_result_fail("vmsplice() failed\n");
29562306a36Sopenharmony_ci			wait(&ret);
29662306a36Sopenharmony_ci			goto close_pipe;
29762306a36Sopenharmony_ci		}
29862306a36Sopenharmony_ci	}
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	while (read(comm_pipes.child_ready[0], &buf, 1) != 1)
30162306a36Sopenharmony_ci		;
30262306a36Sopenharmony_ci	if (munmap(mem, size) < 0) {
30362306a36Sopenharmony_ci		ksft_test_result_fail("munmap() failed\n");
30462306a36Sopenharmony_ci		goto close_pipe;
30562306a36Sopenharmony_ci	}
30662306a36Sopenharmony_ci	write(comm_pipes.parent_ready[1], "0", 1);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	/* Wait until the child is done writing. */
30962306a36Sopenharmony_ci	wait(&ret);
31062306a36Sopenharmony_ci	if (!WIFEXITED(ret)) {
31162306a36Sopenharmony_ci		ksft_test_result_fail("wait() failed\n");
31262306a36Sopenharmony_ci		goto close_pipe;
31362306a36Sopenharmony_ci	}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	/* See if we still read the old values. */
31662306a36Sopenharmony_ci	for (total = 0; total < transferred; total += cur) {
31762306a36Sopenharmony_ci		cur = read(fds[0], new + total, transferred - total);
31862306a36Sopenharmony_ci		if (cur < 0) {
31962306a36Sopenharmony_ci			ksft_test_result_fail("read() failed\n");
32062306a36Sopenharmony_ci			goto close_pipe;
32162306a36Sopenharmony_ci		}
32262306a36Sopenharmony_ci	}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	ksft_test_result(!memcmp(old, new, transferred),
32562306a36Sopenharmony_ci			 "No leak from child into parent\n");
32662306a36Sopenharmony_ciclose_pipe:
32762306a36Sopenharmony_ci	close(fds[0]);
32862306a36Sopenharmony_ci	close(fds[1]);
32962306a36Sopenharmony_ciclose_comm_pipes:
33062306a36Sopenharmony_ci	close_comm_pipes(&comm_pipes);
33162306a36Sopenharmony_cifree:
33262306a36Sopenharmony_ci	free(old);
33362306a36Sopenharmony_ci	free(new);
33462306a36Sopenharmony_ci}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_cistatic void test_vmsplice_before_fork(char *mem, size_t size)
33762306a36Sopenharmony_ci{
33862306a36Sopenharmony_ci	do_test_vmsplice_in_parent(mem, size, true);
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic void test_vmsplice_after_fork(char *mem, size_t size)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	do_test_vmsplice_in_parent(mem, size, false);
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci#ifdef LOCAL_CONFIG_HAVE_LIBURING
34762306a36Sopenharmony_cistatic void do_test_iouring(char *mem, size_t size, bool use_fork)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	struct comm_pipes comm_pipes;
35062306a36Sopenharmony_ci	struct io_uring_cqe *cqe;
35162306a36Sopenharmony_ci	struct io_uring_sqe *sqe;
35262306a36Sopenharmony_ci	struct io_uring ring;
35362306a36Sopenharmony_ci	ssize_t cur, total;
35462306a36Sopenharmony_ci	struct iovec iov;
35562306a36Sopenharmony_ci	char *buf, *tmp;
35662306a36Sopenharmony_ci	int ret, fd;
35762306a36Sopenharmony_ci	FILE *file;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	ret = setup_comm_pipes(&comm_pipes);
36062306a36Sopenharmony_ci	if (ret) {
36162306a36Sopenharmony_ci		ksft_test_result_fail("pipe() failed\n");
36262306a36Sopenharmony_ci		return;
36362306a36Sopenharmony_ci	}
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	file = tmpfile();
36662306a36Sopenharmony_ci	if (!file) {
36762306a36Sopenharmony_ci		ksft_test_result_fail("tmpfile() failed\n");
36862306a36Sopenharmony_ci		goto close_comm_pipes;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci	fd = fileno(file);
37162306a36Sopenharmony_ci	assert(fd);
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	tmp = malloc(size);
37462306a36Sopenharmony_ci	if (!tmp) {
37562306a36Sopenharmony_ci		ksft_test_result_fail("malloc() failed\n");
37662306a36Sopenharmony_ci		goto close_file;
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	/* Skip on errors, as we might just lack kernel support. */
38062306a36Sopenharmony_ci	ret = io_uring_queue_init(1, &ring, 0);
38162306a36Sopenharmony_ci	if (ret < 0) {
38262306a36Sopenharmony_ci		ksft_test_result_skip("io_uring_queue_init() failed\n");
38362306a36Sopenharmony_ci		goto free_tmp;
38462306a36Sopenharmony_ci	}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	/*
38762306a36Sopenharmony_ci	 * Register the range as a fixed buffer. This will FOLL_WRITE | FOLL_PIN
38862306a36Sopenharmony_ci	 * | FOLL_LONGTERM the range.
38962306a36Sopenharmony_ci	 *
39062306a36Sopenharmony_ci	 * Skip on errors, as we might just lack kernel support or might not
39162306a36Sopenharmony_ci	 * have sufficient MEMLOCK permissions.
39262306a36Sopenharmony_ci	 */
39362306a36Sopenharmony_ci	iov.iov_base = mem;
39462306a36Sopenharmony_ci	iov.iov_len = size;
39562306a36Sopenharmony_ci	ret = io_uring_register_buffers(&ring, &iov, 1);
39662306a36Sopenharmony_ci	if (ret) {
39762306a36Sopenharmony_ci		ksft_test_result_skip("io_uring_register_buffers() failed\n");
39862306a36Sopenharmony_ci		goto queue_exit;
39962306a36Sopenharmony_ci	}
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	if (use_fork) {
40262306a36Sopenharmony_ci		/*
40362306a36Sopenharmony_ci		 * fork() and keep the child alive until we're done. Note that
40462306a36Sopenharmony_ci		 * we expect the pinned page to not get shared with the child.
40562306a36Sopenharmony_ci		 */
40662306a36Sopenharmony_ci		ret = fork();
40762306a36Sopenharmony_ci		if (ret < 0) {
40862306a36Sopenharmony_ci			ksft_test_result_fail("fork() failed\n");
40962306a36Sopenharmony_ci			goto unregister_buffers;
41062306a36Sopenharmony_ci		} else if (!ret) {
41162306a36Sopenharmony_ci			write(comm_pipes.child_ready[1], "0", 1);
41262306a36Sopenharmony_ci			while (read(comm_pipes.parent_ready[0], &buf, 1) != 1)
41362306a36Sopenharmony_ci				;
41462306a36Sopenharmony_ci			exit(0);
41562306a36Sopenharmony_ci		}
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci		while (read(comm_pipes.child_ready[0], &buf, 1) != 1)
41862306a36Sopenharmony_ci			;
41962306a36Sopenharmony_ci	} else {
42062306a36Sopenharmony_ci		/*
42162306a36Sopenharmony_ci		 * Map the page R/O into the page table. Enable softdirty
42262306a36Sopenharmony_ci		 * tracking to stop the page from getting mapped R/W immediately
42362306a36Sopenharmony_ci		 * again by mprotect() optimizations. Note that we don't have an
42462306a36Sopenharmony_ci		 * easy way to test if that worked (the pagemap does not export
42562306a36Sopenharmony_ci		 * if the page is mapped R/O vs. R/W).
42662306a36Sopenharmony_ci		 */
42762306a36Sopenharmony_ci		ret = mprotect(mem, size, PROT_READ);
42862306a36Sopenharmony_ci		clear_softdirty();
42962306a36Sopenharmony_ci		ret |= mprotect(mem, size, PROT_READ | PROT_WRITE);
43062306a36Sopenharmony_ci		if (ret) {
43162306a36Sopenharmony_ci			ksft_test_result_fail("mprotect() failed\n");
43262306a36Sopenharmony_ci			goto unregister_buffers;
43362306a36Sopenharmony_ci		}
43462306a36Sopenharmony_ci	}
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	/*
43762306a36Sopenharmony_ci	 * Modify the page and write page content as observed by the fixed
43862306a36Sopenharmony_ci	 * buffer pin to the file so we can verify it.
43962306a36Sopenharmony_ci	 */
44062306a36Sopenharmony_ci	memset(mem, 0xff, size);
44162306a36Sopenharmony_ci	sqe = io_uring_get_sqe(&ring);
44262306a36Sopenharmony_ci	if (!sqe) {
44362306a36Sopenharmony_ci		ksft_test_result_fail("io_uring_get_sqe() failed\n");
44462306a36Sopenharmony_ci		goto quit_child;
44562306a36Sopenharmony_ci	}
44662306a36Sopenharmony_ci	io_uring_prep_write_fixed(sqe, fd, mem, size, 0, 0);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	ret = io_uring_submit(&ring);
44962306a36Sopenharmony_ci	if (ret < 0) {
45062306a36Sopenharmony_ci		ksft_test_result_fail("io_uring_submit() failed\n");
45162306a36Sopenharmony_ci		goto quit_child;
45262306a36Sopenharmony_ci	}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	ret = io_uring_wait_cqe(&ring, &cqe);
45562306a36Sopenharmony_ci	if (ret < 0) {
45662306a36Sopenharmony_ci		ksft_test_result_fail("io_uring_wait_cqe() failed\n");
45762306a36Sopenharmony_ci		goto quit_child;
45862306a36Sopenharmony_ci	}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	if (cqe->res != size) {
46162306a36Sopenharmony_ci		ksft_test_result_fail("write_fixed failed\n");
46262306a36Sopenharmony_ci		goto quit_child;
46362306a36Sopenharmony_ci	}
46462306a36Sopenharmony_ci	io_uring_cqe_seen(&ring, cqe);
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci	/* Read back the file content to the temporary buffer. */
46762306a36Sopenharmony_ci	total = 0;
46862306a36Sopenharmony_ci	while (total < size) {
46962306a36Sopenharmony_ci		cur = pread(fd, tmp + total, size - total, total);
47062306a36Sopenharmony_ci		if (cur < 0) {
47162306a36Sopenharmony_ci			ksft_test_result_fail("pread() failed\n");
47262306a36Sopenharmony_ci			goto quit_child;
47362306a36Sopenharmony_ci		}
47462306a36Sopenharmony_ci		total += cur;
47562306a36Sopenharmony_ci	}
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	/* Finally, check if we read what we expected. */
47862306a36Sopenharmony_ci	ksft_test_result(!memcmp(mem, tmp, size),
47962306a36Sopenharmony_ci			 "Longterm R/W pin is reliable\n");
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ciquit_child:
48262306a36Sopenharmony_ci	if (use_fork) {
48362306a36Sopenharmony_ci		write(comm_pipes.parent_ready[1], "0", 1);
48462306a36Sopenharmony_ci		wait(&ret);
48562306a36Sopenharmony_ci	}
48662306a36Sopenharmony_ciunregister_buffers:
48762306a36Sopenharmony_ci	io_uring_unregister_buffers(&ring);
48862306a36Sopenharmony_ciqueue_exit:
48962306a36Sopenharmony_ci	io_uring_queue_exit(&ring);
49062306a36Sopenharmony_cifree_tmp:
49162306a36Sopenharmony_ci	free(tmp);
49262306a36Sopenharmony_ciclose_file:
49362306a36Sopenharmony_ci	fclose(file);
49462306a36Sopenharmony_ciclose_comm_pipes:
49562306a36Sopenharmony_ci	close_comm_pipes(&comm_pipes);
49662306a36Sopenharmony_ci}
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_cistatic void test_iouring_ro(char *mem, size_t size)
49962306a36Sopenharmony_ci{
50062306a36Sopenharmony_ci	do_test_iouring(mem, size, false);
50162306a36Sopenharmony_ci}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_cistatic void test_iouring_fork(char *mem, size_t size)
50462306a36Sopenharmony_ci{
50562306a36Sopenharmony_ci	do_test_iouring(mem, size, true);
50662306a36Sopenharmony_ci}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci#endif /* LOCAL_CONFIG_HAVE_LIBURING */
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_cienum ro_pin_test {
51162306a36Sopenharmony_ci	RO_PIN_TEST,
51262306a36Sopenharmony_ci	RO_PIN_TEST_SHARED,
51362306a36Sopenharmony_ci	RO_PIN_TEST_PREVIOUSLY_SHARED,
51462306a36Sopenharmony_ci	RO_PIN_TEST_RO_EXCLUSIVE,
51562306a36Sopenharmony_ci};
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_cistatic void do_test_ro_pin(char *mem, size_t size, enum ro_pin_test test,
51862306a36Sopenharmony_ci			   bool fast)
51962306a36Sopenharmony_ci{
52062306a36Sopenharmony_ci	struct pin_longterm_test args;
52162306a36Sopenharmony_ci	struct comm_pipes comm_pipes;
52262306a36Sopenharmony_ci	char *tmp, buf;
52362306a36Sopenharmony_ci	__u64 tmp_val;
52462306a36Sopenharmony_ci	int ret;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	if (gup_fd < 0) {
52762306a36Sopenharmony_ci		ksft_test_result_skip("gup_test not available\n");
52862306a36Sopenharmony_ci		return;
52962306a36Sopenharmony_ci	}
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	tmp = malloc(size);
53262306a36Sopenharmony_ci	if (!tmp) {
53362306a36Sopenharmony_ci		ksft_test_result_fail("malloc() failed\n");
53462306a36Sopenharmony_ci		return;
53562306a36Sopenharmony_ci	}
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	ret = setup_comm_pipes(&comm_pipes);
53862306a36Sopenharmony_ci	if (ret) {
53962306a36Sopenharmony_ci		ksft_test_result_fail("pipe() failed\n");
54062306a36Sopenharmony_ci		goto free_tmp;
54162306a36Sopenharmony_ci	}
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	switch (test) {
54462306a36Sopenharmony_ci	case RO_PIN_TEST:
54562306a36Sopenharmony_ci		break;
54662306a36Sopenharmony_ci	case RO_PIN_TEST_SHARED:
54762306a36Sopenharmony_ci	case RO_PIN_TEST_PREVIOUSLY_SHARED:
54862306a36Sopenharmony_ci		/*
54962306a36Sopenharmony_ci		 * Share the pages with our child. As the pages are not pinned,
55062306a36Sopenharmony_ci		 * this should just work.
55162306a36Sopenharmony_ci		 */
55262306a36Sopenharmony_ci		ret = fork();
55362306a36Sopenharmony_ci		if (ret < 0) {
55462306a36Sopenharmony_ci			ksft_test_result_fail("fork() failed\n");
55562306a36Sopenharmony_ci			goto close_comm_pipes;
55662306a36Sopenharmony_ci		} else if (!ret) {
55762306a36Sopenharmony_ci			write(comm_pipes.child_ready[1], "0", 1);
55862306a36Sopenharmony_ci			while (read(comm_pipes.parent_ready[0], &buf, 1) != 1)
55962306a36Sopenharmony_ci				;
56062306a36Sopenharmony_ci			exit(0);
56162306a36Sopenharmony_ci		}
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci		/* Wait until our child is ready. */
56462306a36Sopenharmony_ci		while (read(comm_pipes.child_ready[0], &buf, 1) != 1)
56562306a36Sopenharmony_ci			;
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci		if (test == RO_PIN_TEST_PREVIOUSLY_SHARED) {
56862306a36Sopenharmony_ci			/*
56962306a36Sopenharmony_ci			 * Tell the child to quit now and wait until it quit.
57062306a36Sopenharmony_ci			 * The pages should now be mapped R/O into our page
57162306a36Sopenharmony_ci			 * tables, but they are no longer shared.
57262306a36Sopenharmony_ci			 */
57362306a36Sopenharmony_ci			write(comm_pipes.parent_ready[1], "0", 1);
57462306a36Sopenharmony_ci			wait(&ret);
57562306a36Sopenharmony_ci			if (!WIFEXITED(ret))
57662306a36Sopenharmony_ci				ksft_print_msg("[INFO] wait() failed\n");
57762306a36Sopenharmony_ci		}
57862306a36Sopenharmony_ci		break;
57962306a36Sopenharmony_ci	case RO_PIN_TEST_RO_EXCLUSIVE:
58062306a36Sopenharmony_ci		/*
58162306a36Sopenharmony_ci		 * Map the page R/O into the page table. Enable softdirty
58262306a36Sopenharmony_ci		 * tracking to stop the page from getting mapped R/W immediately
58362306a36Sopenharmony_ci		 * again by mprotect() optimizations. Note that we don't have an
58462306a36Sopenharmony_ci		 * easy way to test if that worked (the pagemap does not export
58562306a36Sopenharmony_ci		 * if the page is mapped R/O vs. R/W).
58662306a36Sopenharmony_ci		 */
58762306a36Sopenharmony_ci		ret = mprotect(mem, size, PROT_READ);
58862306a36Sopenharmony_ci		clear_softdirty();
58962306a36Sopenharmony_ci		ret |= mprotect(mem, size, PROT_READ | PROT_WRITE);
59062306a36Sopenharmony_ci		if (ret) {
59162306a36Sopenharmony_ci			ksft_test_result_fail("mprotect() failed\n");
59262306a36Sopenharmony_ci			goto close_comm_pipes;
59362306a36Sopenharmony_ci		}
59462306a36Sopenharmony_ci		break;
59562306a36Sopenharmony_ci	default:
59662306a36Sopenharmony_ci		assert(false);
59762306a36Sopenharmony_ci	}
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci	/* Take a R/O pin. This should trigger unsharing. */
60062306a36Sopenharmony_ci	args.addr = (__u64)(uintptr_t)mem;
60162306a36Sopenharmony_ci	args.size = size;
60262306a36Sopenharmony_ci	args.flags = fast ? PIN_LONGTERM_TEST_FLAG_USE_FAST : 0;
60362306a36Sopenharmony_ci	ret = ioctl(gup_fd, PIN_LONGTERM_TEST_START, &args);
60462306a36Sopenharmony_ci	if (ret) {
60562306a36Sopenharmony_ci		if (errno == EINVAL)
60662306a36Sopenharmony_ci			ksft_test_result_skip("PIN_LONGTERM_TEST_START failed\n");
60762306a36Sopenharmony_ci		else
60862306a36Sopenharmony_ci			ksft_test_result_fail("PIN_LONGTERM_TEST_START failed\n");
60962306a36Sopenharmony_ci		goto wait;
61062306a36Sopenharmony_ci	}
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	/* Modify the page. */
61362306a36Sopenharmony_ci	memset(mem, 0xff, size);
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	/*
61662306a36Sopenharmony_ci	 * Read back the content via the pin to the temporary buffer and
61762306a36Sopenharmony_ci	 * test if we observed the modification.
61862306a36Sopenharmony_ci	 */
61962306a36Sopenharmony_ci	tmp_val = (__u64)(uintptr_t)tmp;
62062306a36Sopenharmony_ci	ret = ioctl(gup_fd, PIN_LONGTERM_TEST_READ, &tmp_val);
62162306a36Sopenharmony_ci	if (ret)
62262306a36Sopenharmony_ci		ksft_test_result_fail("PIN_LONGTERM_TEST_READ failed\n");
62362306a36Sopenharmony_ci	else
62462306a36Sopenharmony_ci		ksft_test_result(!memcmp(mem, tmp, size),
62562306a36Sopenharmony_ci				 "Longterm R/O pin is reliable\n");
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	ret = ioctl(gup_fd, PIN_LONGTERM_TEST_STOP);
62862306a36Sopenharmony_ci	if (ret)
62962306a36Sopenharmony_ci		ksft_print_msg("[INFO] PIN_LONGTERM_TEST_STOP failed\n");
63062306a36Sopenharmony_ciwait:
63162306a36Sopenharmony_ci	switch (test) {
63262306a36Sopenharmony_ci	case RO_PIN_TEST_SHARED:
63362306a36Sopenharmony_ci		write(comm_pipes.parent_ready[1], "0", 1);
63462306a36Sopenharmony_ci		wait(&ret);
63562306a36Sopenharmony_ci		if (!WIFEXITED(ret))
63662306a36Sopenharmony_ci			ksft_print_msg("[INFO] wait() failed\n");
63762306a36Sopenharmony_ci		break;
63862306a36Sopenharmony_ci	default:
63962306a36Sopenharmony_ci		break;
64062306a36Sopenharmony_ci	}
64162306a36Sopenharmony_ciclose_comm_pipes:
64262306a36Sopenharmony_ci	close_comm_pipes(&comm_pipes);
64362306a36Sopenharmony_cifree_tmp:
64462306a36Sopenharmony_ci	free(tmp);
64562306a36Sopenharmony_ci}
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_cistatic void test_ro_pin_on_shared(char *mem, size_t size)
64862306a36Sopenharmony_ci{
64962306a36Sopenharmony_ci	do_test_ro_pin(mem, size, RO_PIN_TEST_SHARED, false);
65062306a36Sopenharmony_ci}
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_cistatic void test_ro_fast_pin_on_shared(char *mem, size_t size)
65362306a36Sopenharmony_ci{
65462306a36Sopenharmony_ci	do_test_ro_pin(mem, size, RO_PIN_TEST_SHARED, true);
65562306a36Sopenharmony_ci}
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_cistatic void test_ro_pin_on_ro_previously_shared(char *mem, size_t size)
65862306a36Sopenharmony_ci{
65962306a36Sopenharmony_ci	do_test_ro_pin(mem, size, RO_PIN_TEST_PREVIOUSLY_SHARED, false);
66062306a36Sopenharmony_ci}
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_cistatic void test_ro_fast_pin_on_ro_previously_shared(char *mem, size_t size)
66362306a36Sopenharmony_ci{
66462306a36Sopenharmony_ci	do_test_ro_pin(mem, size, RO_PIN_TEST_PREVIOUSLY_SHARED, true);
66562306a36Sopenharmony_ci}
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_cistatic void test_ro_pin_on_ro_exclusive(char *mem, size_t size)
66862306a36Sopenharmony_ci{
66962306a36Sopenharmony_ci	do_test_ro_pin(mem, size, RO_PIN_TEST_RO_EXCLUSIVE, false);
67062306a36Sopenharmony_ci}
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_cistatic void test_ro_fast_pin_on_ro_exclusive(char *mem, size_t size)
67362306a36Sopenharmony_ci{
67462306a36Sopenharmony_ci	do_test_ro_pin(mem, size, RO_PIN_TEST_RO_EXCLUSIVE, true);
67562306a36Sopenharmony_ci}
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_citypedef void (*test_fn)(char *mem, size_t size);
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_cistatic void do_run_with_base_page(test_fn fn, bool swapout)
68062306a36Sopenharmony_ci{
68162306a36Sopenharmony_ci	char *mem;
68262306a36Sopenharmony_ci	int ret;
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci	mem = mmap(NULL, pagesize, PROT_READ | PROT_WRITE,
68562306a36Sopenharmony_ci		   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
68662306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
68762306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
68862306a36Sopenharmony_ci		return;
68962306a36Sopenharmony_ci	}
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	ret = madvise(mem, pagesize, MADV_NOHUGEPAGE);
69262306a36Sopenharmony_ci	/* Ignore if not around on a kernel. */
69362306a36Sopenharmony_ci	if (ret && errno != EINVAL) {
69462306a36Sopenharmony_ci		ksft_test_result_fail("MADV_NOHUGEPAGE failed\n");
69562306a36Sopenharmony_ci		goto munmap;
69662306a36Sopenharmony_ci	}
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ci	/* Populate a base page. */
69962306a36Sopenharmony_ci	memset(mem, 0, pagesize);
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_ci	if (swapout) {
70262306a36Sopenharmony_ci		madvise(mem, pagesize, MADV_PAGEOUT);
70362306a36Sopenharmony_ci		if (!pagemap_is_swapped(pagemap_fd, mem)) {
70462306a36Sopenharmony_ci			ksft_test_result_skip("MADV_PAGEOUT did not work, is swap enabled?\n");
70562306a36Sopenharmony_ci			goto munmap;
70662306a36Sopenharmony_ci		}
70762306a36Sopenharmony_ci	}
70862306a36Sopenharmony_ci
70962306a36Sopenharmony_ci	fn(mem, pagesize);
71062306a36Sopenharmony_cimunmap:
71162306a36Sopenharmony_ci	munmap(mem, pagesize);
71262306a36Sopenharmony_ci}
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_cistatic void run_with_base_page(test_fn fn, const char *desc)
71562306a36Sopenharmony_ci{
71662306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with base page\n", desc);
71762306a36Sopenharmony_ci	do_run_with_base_page(fn, false);
71862306a36Sopenharmony_ci}
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_cistatic void run_with_base_page_swap(test_fn fn, const char *desc)
72162306a36Sopenharmony_ci{
72262306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with swapped out base page\n", desc);
72362306a36Sopenharmony_ci	do_run_with_base_page(fn, true);
72462306a36Sopenharmony_ci}
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_cienum thp_run {
72762306a36Sopenharmony_ci	THP_RUN_PMD,
72862306a36Sopenharmony_ci	THP_RUN_PMD_SWAPOUT,
72962306a36Sopenharmony_ci	THP_RUN_PTE,
73062306a36Sopenharmony_ci	THP_RUN_PTE_SWAPOUT,
73162306a36Sopenharmony_ci	THP_RUN_SINGLE_PTE,
73262306a36Sopenharmony_ci	THP_RUN_SINGLE_PTE_SWAPOUT,
73362306a36Sopenharmony_ci	THP_RUN_PARTIAL_MREMAP,
73462306a36Sopenharmony_ci	THP_RUN_PARTIAL_SHARED,
73562306a36Sopenharmony_ci};
73662306a36Sopenharmony_ci
73762306a36Sopenharmony_cistatic void do_run_with_thp(test_fn fn, enum thp_run thp_run)
73862306a36Sopenharmony_ci{
73962306a36Sopenharmony_ci	char *mem, *mmap_mem, *tmp, *mremap_mem = MAP_FAILED;
74062306a36Sopenharmony_ci	size_t size, mmap_size, mremap_size;
74162306a36Sopenharmony_ci	int ret;
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	/* For alignment purposes, we need twice the thp size. */
74462306a36Sopenharmony_ci	mmap_size = 2 * thpsize;
74562306a36Sopenharmony_ci	mmap_mem = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
74662306a36Sopenharmony_ci			MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
74762306a36Sopenharmony_ci	if (mmap_mem == MAP_FAILED) {
74862306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
74962306a36Sopenharmony_ci		return;
75062306a36Sopenharmony_ci	}
75162306a36Sopenharmony_ci
75262306a36Sopenharmony_ci	/* We need a THP-aligned memory area. */
75362306a36Sopenharmony_ci	mem = (char *)(((uintptr_t)mmap_mem + thpsize) & ~(thpsize - 1));
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_ci	ret = madvise(mem, thpsize, MADV_HUGEPAGE);
75662306a36Sopenharmony_ci	if (ret) {
75762306a36Sopenharmony_ci		ksft_test_result_fail("MADV_HUGEPAGE failed\n");
75862306a36Sopenharmony_ci		goto munmap;
75962306a36Sopenharmony_ci	}
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_ci	/*
76262306a36Sopenharmony_ci	 * Try to populate a THP. Touch the first sub-page and test if we get
76362306a36Sopenharmony_ci	 * another sub-page populated automatically.
76462306a36Sopenharmony_ci	 */
76562306a36Sopenharmony_ci	mem[0] = 0;
76662306a36Sopenharmony_ci	if (!pagemap_is_populated(pagemap_fd, mem + pagesize)) {
76762306a36Sopenharmony_ci		ksft_test_result_skip("Did not get a THP populated\n");
76862306a36Sopenharmony_ci		goto munmap;
76962306a36Sopenharmony_ci	}
77062306a36Sopenharmony_ci	memset(mem, 0, thpsize);
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci	size = thpsize;
77362306a36Sopenharmony_ci	switch (thp_run) {
77462306a36Sopenharmony_ci	case THP_RUN_PMD:
77562306a36Sopenharmony_ci	case THP_RUN_PMD_SWAPOUT:
77662306a36Sopenharmony_ci		break;
77762306a36Sopenharmony_ci	case THP_RUN_PTE:
77862306a36Sopenharmony_ci	case THP_RUN_PTE_SWAPOUT:
77962306a36Sopenharmony_ci		/*
78062306a36Sopenharmony_ci		 * Trigger PTE-mapping the THP by temporarily mapping a single
78162306a36Sopenharmony_ci		 * subpage R/O.
78262306a36Sopenharmony_ci		 */
78362306a36Sopenharmony_ci		ret = mprotect(mem + pagesize, pagesize, PROT_READ);
78462306a36Sopenharmony_ci		if (ret) {
78562306a36Sopenharmony_ci			ksft_test_result_fail("mprotect() failed\n");
78662306a36Sopenharmony_ci			goto munmap;
78762306a36Sopenharmony_ci		}
78862306a36Sopenharmony_ci		ret = mprotect(mem + pagesize, pagesize, PROT_READ | PROT_WRITE);
78962306a36Sopenharmony_ci		if (ret) {
79062306a36Sopenharmony_ci			ksft_test_result_fail("mprotect() failed\n");
79162306a36Sopenharmony_ci			goto munmap;
79262306a36Sopenharmony_ci		}
79362306a36Sopenharmony_ci		break;
79462306a36Sopenharmony_ci	case THP_RUN_SINGLE_PTE:
79562306a36Sopenharmony_ci	case THP_RUN_SINGLE_PTE_SWAPOUT:
79662306a36Sopenharmony_ci		/*
79762306a36Sopenharmony_ci		 * Discard all but a single subpage of that PTE-mapped THP. What
79862306a36Sopenharmony_ci		 * remains is a single PTE mapping a single subpage.
79962306a36Sopenharmony_ci		 */
80062306a36Sopenharmony_ci		ret = madvise(mem + pagesize, thpsize - pagesize, MADV_DONTNEED);
80162306a36Sopenharmony_ci		if (ret) {
80262306a36Sopenharmony_ci			ksft_test_result_fail("MADV_DONTNEED failed\n");
80362306a36Sopenharmony_ci			goto munmap;
80462306a36Sopenharmony_ci		}
80562306a36Sopenharmony_ci		size = pagesize;
80662306a36Sopenharmony_ci		break;
80762306a36Sopenharmony_ci	case THP_RUN_PARTIAL_MREMAP:
80862306a36Sopenharmony_ci		/*
80962306a36Sopenharmony_ci		 * Remap half of the THP. We need some new memory location
81062306a36Sopenharmony_ci		 * for that.
81162306a36Sopenharmony_ci		 */
81262306a36Sopenharmony_ci		mremap_size = thpsize / 2;
81362306a36Sopenharmony_ci		mremap_mem = mmap(NULL, mremap_size, PROT_NONE,
81462306a36Sopenharmony_ci				  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
81562306a36Sopenharmony_ci		if (mem == MAP_FAILED) {
81662306a36Sopenharmony_ci			ksft_test_result_fail("mmap() failed\n");
81762306a36Sopenharmony_ci			goto munmap;
81862306a36Sopenharmony_ci		}
81962306a36Sopenharmony_ci		tmp = mremap(mem + mremap_size, mremap_size, mremap_size,
82062306a36Sopenharmony_ci			     MREMAP_MAYMOVE | MREMAP_FIXED, mremap_mem);
82162306a36Sopenharmony_ci		if (tmp != mremap_mem) {
82262306a36Sopenharmony_ci			ksft_test_result_fail("mremap() failed\n");
82362306a36Sopenharmony_ci			goto munmap;
82462306a36Sopenharmony_ci		}
82562306a36Sopenharmony_ci		size = mremap_size;
82662306a36Sopenharmony_ci		break;
82762306a36Sopenharmony_ci	case THP_RUN_PARTIAL_SHARED:
82862306a36Sopenharmony_ci		/*
82962306a36Sopenharmony_ci		 * Share the first page of the THP with a child and quit the
83062306a36Sopenharmony_ci		 * child. This will result in some parts of the THP never
83162306a36Sopenharmony_ci		 * have been shared.
83262306a36Sopenharmony_ci		 */
83362306a36Sopenharmony_ci		ret = madvise(mem + pagesize, thpsize - pagesize, MADV_DONTFORK);
83462306a36Sopenharmony_ci		if (ret) {
83562306a36Sopenharmony_ci			ksft_test_result_fail("MADV_DONTFORK failed\n");
83662306a36Sopenharmony_ci			goto munmap;
83762306a36Sopenharmony_ci		}
83862306a36Sopenharmony_ci		ret = fork();
83962306a36Sopenharmony_ci		if (ret < 0) {
84062306a36Sopenharmony_ci			ksft_test_result_fail("fork() failed\n");
84162306a36Sopenharmony_ci			goto munmap;
84262306a36Sopenharmony_ci		} else if (!ret) {
84362306a36Sopenharmony_ci			exit(0);
84462306a36Sopenharmony_ci		}
84562306a36Sopenharmony_ci		wait(&ret);
84662306a36Sopenharmony_ci		/* Allow for sharing all pages again. */
84762306a36Sopenharmony_ci		ret = madvise(mem + pagesize, thpsize - pagesize, MADV_DOFORK);
84862306a36Sopenharmony_ci		if (ret) {
84962306a36Sopenharmony_ci			ksft_test_result_fail("MADV_DOFORK failed\n");
85062306a36Sopenharmony_ci			goto munmap;
85162306a36Sopenharmony_ci		}
85262306a36Sopenharmony_ci		break;
85362306a36Sopenharmony_ci	default:
85462306a36Sopenharmony_ci		assert(false);
85562306a36Sopenharmony_ci	}
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	switch (thp_run) {
85862306a36Sopenharmony_ci	case THP_RUN_PMD_SWAPOUT:
85962306a36Sopenharmony_ci	case THP_RUN_PTE_SWAPOUT:
86062306a36Sopenharmony_ci	case THP_RUN_SINGLE_PTE_SWAPOUT:
86162306a36Sopenharmony_ci		madvise(mem, size, MADV_PAGEOUT);
86262306a36Sopenharmony_ci		if (!range_is_swapped(mem, size)) {
86362306a36Sopenharmony_ci			ksft_test_result_skip("MADV_PAGEOUT did not work, is swap enabled?\n");
86462306a36Sopenharmony_ci			goto munmap;
86562306a36Sopenharmony_ci		}
86662306a36Sopenharmony_ci		break;
86762306a36Sopenharmony_ci	default:
86862306a36Sopenharmony_ci		break;
86962306a36Sopenharmony_ci	}
87062306a36Sopenharmony_ci
87162306a36Sopenharmony_ci	fn(mem, size);
87262306a36Sopenharmony_cimunmap:
87362306a36Sopenharmony_ci	munmap(mmap_mem, mmap_size);
87462306a36Sopenharmony_ci	if (mremap_mem != MAP_FAILED)
87562306a36Sopenharmony_ci		munmap(mremap_mem, mremap_size);
87662306a36Sopenharmony_ci}
87762306a36Sopenharmony_ci
87862306a36Sopenharmony_cistatic void run_with_thp(test_fn fn, const char *desc)
87962306a36Sopenharmony_ci{
88062306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with THP\n", desc);
88162306a36Sopenharmony_ci	do_run_with_thp(fn, THP_RUN_PMD);
88262306a36Sopenharmony_ci}
88362306a36Sopenharmony_ci
88462306a36Sopenharmony_cistatic void run_with_thp_swap(test_fn fn, const char *desc)
88562306a36Sopenharmony_ci{
88662306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with swapped-out THP\n", desc);
88762306a36Sopenharmony_ci	do_run_with_thp(fn, THP_RUN_PMD_SWAPOUT);
88862306a36Sopenharmony_ci}
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_cistatic void run_with_pte_mapped_thp(test_fn fn, const char *desc)
89162306a36Sopenharmony_ci{
89262306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with PTE-mapped THP\n", desc);
89362306a36Sopenharmony_ci	do_run_with_thp(fn, THP_RUN_PTE);
89462306a36Sopenharmony_ci}
89562306a36Sopenharmony_ci
89662306a36Sopenharmony_cistatic void run_with_pte_mapped_thp_swap(test_fn fn, const char *desc)
89762306a36Sopenharmony_ci{
89862306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with swapped-out, PTE-mapped THP\n", desc);
89962306a36Sopenharmony_ci	do_run_with_thp(fn, THP_RUN_PTE_SWAPOUT);
90062306a36Sopenharmony_ci}
90162306a36Sopenharmony_ci
90262306a36Sopenharmony_cistatic void run_with_single_pte_of_thp(test_fn fn, const char *desc)
90362306a36Sopenharmony_ci{
90462306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with single PTE of THP\n", desc);
90562306a36Sopenharmony_ci	do_run_with_thp(fn, THP_RUN_SINGLE_PTE);
90662306a36Sopenharmony_ci}
90762306a36Sopenharmony_ci
90862306a36Sopenharmony_cistatic void run_with_single_pte_of_thp_swap(test_fn fn, const char *desc)
90962306a36Sopenharmony_ci{
91062306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with single PTE of swapped-out THP\n", desc);
91162306a36Sopenharmony_ci	do_run_with_thp(fn, THP_RUN_SINGLE_PTE_SWAPOUT);
91262306a36Sopenharmony_ci}
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_cistatic void run_with_partial_mremap_thp(test_fn fn, const char *desc)
91562306a36Sopenharmony_ci{
91662306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with partially mremap()'ed THP\n", desc);
91762306a36Sopenharmony_ci	do_run_with_thp(fn, THP_RUN_PARTIAL_MREMAP);
91862306a36Sopenharmony_ci}
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_cistatic void run_with_partial_shared_thp(test_fn fn, const char *desc)
92162306a36Sopenharmony_ci{
92262306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with partially shared THP\n", desc);
92362306a36Sopenharmony_ci	do_run_with_thp(fn, THP_RUN_PARTIAL_SHARED);
92462306a36Sopenharmony_ci}
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_cistatic void run_with_hugetlb(test_fn fn, const char *desc, size_t hugetlbsize)
92762306a36Sopenharmony_ci{
92862306a36Sopenharmony_ci	int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB;
92962306a36Sopenharmony_ci	char *mem, *dummy;
93062306a36Sopenharmony_ci
93162306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with hugetlb (%zu kB)\n", desc,
93262306a36Sopenharmony_ci		       hugetlbsize / 1024);
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_ci	flags |= __builtin_ctzll(hugetlbsize) << MAP_HUGE_SHIFT;
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_ci	mem = mmap(NULL, hugetlbsize, PROT_READ | PROT_WRITE, flags, -1, 0);
93762306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
93862306a36Sopenharmony_ci		ksft_test_result_skip("need more free huge pages\n");
93962306a36Sopenharmony_ci		return;
94062306a36Sopenharmony_ci	}
94162306a36Sopenharmony_ci
94262306a36Sopenharmony_ci	/* Populate an huge page. */
94362306a36Sopenharmony_ci	memset(mem, 0, hugetlbsize);
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ci	/*
94662306a36Sopenharmony_ci	 * We need a total of two hugetlb pages to handle COW/unsharing
94762306a36Sopenharmony_ci	 * properly, otherwise we might get zapped by a SIGBUS.
94862306a36Sopenharmony_ci	 */
94962306a36Sopenharmony_ci	dummy = mmap(NULL, hugetlbsize, PROT_READ | PROT_WRITE, flags, -1, 0);
95062306a36Sopenharmony_ci	if (dummy == MAP_FAILED) {
95162306a36Sopenharmony_ci		ksft_test_result_skip("need more free huge pages\n");
95262306a36Sopenharmony_ci		goto munmap;
95362306a36Sopenharmony_ci	}
95462306a36Sopenharmony_ci	munmap(dummy, hugetlbsize);
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_ci	fn(mem, hugetlbsize);
95762306a36Sopenharmony_cimunmap:
95862306a36Sopenharmony_ci	munmap(mem, hugetlbsize);
95962306a36Sopenharmony_ci}
96062306a36Sopenharmony_ci
96162306a36Sopenharmony_cistruct test_case {
96262306a36Sopenharmony_ci	const char *desc;
96362306a36Sopenharmony_ci	test_fn fn;
96462306a36Sopenharmony_ci};
96562306a36Sopenharmony_ci
96662306a36Sopenharmony_ci/*
96762306a36Sopenharmony_ci * Test cases that are specific to anonymous pages: pages in private mappings
96862306a36Sopenharmony_ci * that may get shared via COW during fork().
96962306a36Sopenharmony_ci */
97062306a36Sopenharmony_cistatic const struct test_case anon_test_cases[] = {
97162306a36Sopenharmony_ci	/*
97262306a36Sopenharmony_ci	 * Basic COW tests for fork() without any GUP. If we miss to break COW,
97362306a36Sopenharmony_ci	 * either the child can observe modifications by the parent or the
97462306a36Sopenharmony_ci	 * other way around.
97562306a36Sopenharmony_ci	 */
97662306a36Sopenharmony_ci	{
97762306a36Sopenharmony_ci		"Basic COW after fork()",
97862306a36Sopenharmony_ci		test_cow_in_parent,
97962306a36Sopenharmony_ci	},
98062306a36Sopenharmony_ci	/*
98162306a36Sopenharmony_ci	 * Basic test, but do an additional mprotect(PROT_READ)+
98262306a36Sopenharmony_ci	 * mprotect(PROT_READ|PROT_WRITE) in the parent before write access.
98362306a36Sopenharmony_ci	 */
98462306a36Sopenharmony_ci	{
98562306a36Sopenharmony_ci		"Basic COW after fork() with mprotect() optimization",
98662306a36Sopenharmony_ci		test_cow_in_parent_mprotect,
98762306a36Sopenharmony_ci	},
98862306a36Sopenharmony_ci	/*
98962306a36Sopenharmony_ci	 * vmsplice() [R/O GUP] + unmap in the child; modify in the parent. If
99062306a36Sopenharmony_ci	 * we miss to break COW, the child observes modifications by the parent.
99162306a36Sopenharmony_ci	 * This is CVE-2020-29374 reported by Jann Horn.
99262306a36Sopenharmony_ci	 */
99362306a36Sopenharmony_ci	{
99462306a36Sopenharmony_ci		"vmsplice() + unmap in child",
99562306a36Sopenharmony_ci		test_vmsplice_in_child
99662306a36Sopenharmony_ci	},
99762306a36Sopenharmony_ci	/*
99862306a36Sopenharmony_ci	 * vmsplice() test, but do an additional mprotect(PROT_READ)+
99962306a36Sopenharmony_ci	 * mprotect(PROT_READ|PROT_WRITE) in the parent before write access.
100062306a36Sopenharmony_ci	 */
100162306a36Sopenharmony_ci	{
100262306a36Sopenharmony_ci		"vmsplice() + unmap in child with mprotect() optimization",
100362306a36Sopenharmony_ci		test_vmsplice_in_child_mprotect
100462306a36Sopenharmony_ci	},
100562306a36Sopenharmony_ci	/*
100662306a36Sopenharmony_ci	 * vmsplice() [R/O GUP] in parent before fork(), unmap in parent after
100762306a36Sopenharmony_ci	 * fork(); modify in the child. If we miss to break COW, the parent
100862306a36Sopenharmony_ci	 * observes modifications by the child.
100962306a36Sopenharmony_ci	 */
101062306a36Sopenharmony_ci	{
101162306a36Sopenharmony_ci		"vmsplice() before fork(), unmap in parent after fork()",
101262306a36Sopenharmony_ci		test_vmsplice_before_fork,
101362306a36Sopenharmony_ci	},
101462306a36Sopenharmony_ci	/*
101562306a36Sopenharmony_ci	 * vmsplice() [R/O GUP] + unmap in parent after fork(); modify in the
101662306a36Sopenharmony_ci	 * child. If we miss to break COW, the parent observes modifications by
101762306a36Sopenharmony_ci	 * the child.
101862306a36Sopenharmony_ci	 */
101962306a36Sopenharmony_ci	{
102062306a36Sopenharmony_ci		"vmsplice() + unmap in parent after fork()",
102162306a36Sopenharmony_ci		test_vmsplice_after_fork,
102262306a36Sopenharmony_ci	},
102362306a36Sopenharmony_ci#ifdef LOCAL_CONFIG_HAVE_LIBURING
102462306a36Sopenharmony_ci	/*
102562306a36Sopenharmony_ci	 * Take a R/W longterm pin and then map the page R/O into the page
102662306a36Sopenharmony_ci	 * table to trigger a write fault on next access. When modifying the
102762306a36Sopenharmony_ci	 * page, the page content must be visible via the pin.
102862306a36Sopenharmony_ci	 */
102962306a36Sopenharmony_ci	{
103062306a36Sopenharmony_ci		"R/O-mapping a page registered as iouring fixed buffer",
103162306a36Sopenharmony_ci		test_iouring_ro,
103262306a36Sopenharmony_ci	},
103362306a36Sopenharmony_ci	/*
103462306a36Sopenharmony_ci	 * Take a R/W longterm pin and then fork() a child. When modifying the
103562306a36Sopenharmony_ci	 * page, the page content must be visible via the pin. We expect the
103662306a36Sopenharmony_ci	 * pinned page to not get shared with the child.
103762306a36Sopenharmony_ci	 */
103862306a36Sopenharmony_ci	{
103962306a36Sopenharmony_ci		"fork() with an iouring fixed buffer",
104062306a36Sopenharmony_ci		test_iouring_fork,
104162306a36Sopenharmony_ci	},
104262306a36Sopenharmony_ci
104362306a36Sopenharmony_ci#endif /* LOCAL_CONFIG_HAVE_LIBURING */
104462306a36Sopenharmony_ci	/*
104562306a36Sopenharmony_ci	 * Take a R/O longterm pin on a R/O-mapped shared anonymous page.
104662306a36Sopenharmony_ci	 * When modifying the page via the page table, the page content change
104762306a36Sopenharmony_ci	 * must be visible via the pin.
104862306a36Sopenharmony_ci	 */
104962306a36Sopenharmony_ci	{
105062306a36Sopenharmony_ci		"R/O GUP pin on R/O-mapped shared page",
105162306a36Sopenharmony_ci		test_ro_pin_on_shared,
105262306a36Sopenharmony_ci	},
105362306a36Sopenharmony_ci	/* Same as above, but using GUP-fast. */
105462306a36Sopenharmony_ci	{
105562306a36Sopenharmony_ci		"R/O GUP-fast pin on R/O-mapped shared page",
105662306a36Sopenharmony_ci		test_ro_fast_pin_on_shared,
105762306a36Sopenharmony_ci	},
105862306a36Sopenharmony_ci	/*
105962306a36Sopenharmony_ci	 * Take a R/O longterm pin on a R/O-mapped exclusive anonymous page that
106062306a36Sopenharmony_ci	 * was previously shared. When modifying the page via the page table,
106162306a36Sopenharmony_ci	 * the page content change must be visible via the pin.
106262306a36Sopenharmony_ci	 */
106362306a36Sopenharmony_ci	{
106462306a36Sopenharmony_ci		"R/O GUP pin on R/O-mapped previously-shared page",
106562306a36Sopenharmony_ci		test_ro_pin_on_ro_previously_shared,
106662306a36Sopenharmony_ci	},
106762306a36Sopenharmony_ci	/* Same as above, but using GUP-fast. */
106862306a36Sopenharmony_ci	{
106962306a36Sopenharmony_ci		"R/O GUP-fast pin on R/O-mapped previously-shared page",
107062306a36Sopenharmony_ci		test_ro_fast_pin_on_ro_previously_shared,
107162306a36Sopenharmony_ci	},
107262306a36Sopenharmony_ci	/*
107362306a36Sopenharmony_ci	 * Take a R/O longterm pin on a R/O-mapped exclusive anonymous page.
107462306a36Sopenharmony_ci	 * When modifying the page via the page table, the page content change
107562306a36Sopenharmony_ci	 * must be visible via the pin.
107662306a36Sopenharmony_ci	 */
107762306a36Sopenharmony_ci	{
107862306a36Sopenharmony_ci		"R/O GUP pin on R/O-mapped exclusive page",
107962306a36Sopenharmony_ci		test_ro_pin_on_ro_exclusive,
108062306a36Sopenharmony_ci	},
108162306a36Sopenharmony_ci	/* Same as above, but using GUP-fast. */
108262306a36Sopenharmony_ci	{
108362306a36Sopenharmony_ci		"R/O GUP-fast pin on R/O-mapped exclusive page",
108462306a36Sopenharmony_ci		test_ro_fast_pin_on_ro_exclusive,
108562306a36Sopenharmony_ci	},
108662306a36Sopenharmony_ci};
108762306a36Sopenharmony_ci
108862306a36Sopenharmony_cistatic void run_anon_test_case(struct test_case const *test_case)
108962306a36Sopenharmony_ci{
109062306a36Sopenharmony_ci	int i;
109162306a36Sopenharmony_ci
109262306a36Sopenharmony_ci	run_with_base_page(test_case->fn, test_case->desc);
109362306a36Sopenharmony_ci	run_with_base_page_swap(test_case->fn, test_case->desc);
109462306a36Sopenharmony_ci	if (thpsize) {
109562306a36Sopenharmony_ci		run_with_thp(test_case->fn, test_case->desc);
109662306a36Sopenharmony_ci		run_with_thp_swap(test_case->fn, test_case->desc);
109762306a36Sopenharmony_ci		run_with_pte_mapped_thp(test_case->fn, test_case->desc);
109862306a36Sopenharmony_ci		run_with_pte_mapped_thp_swap(test_case->fn, test_case->desc);
109962306a36Sopenharmony_ci		run_with_single_pte_of_thp(test_case->fn, test_case->desc);
110062306a36Sopenharmony_ci		run_with_single_pte_of_thp_swap(test_case->fn, test_case->desc);
110162306a36Sopenharmony_ci		run_with_partial_mremap_thp(test_case->fn, test_case->desc);
110262306a36Sopenharmony_ci		run_with_partial_shared_thp(test_case->fn, test_case->desc);
110362306a36Sopenharmony_ci	}
110462306a36Sopenharmony_ci	for (i = 0; i < nr_hugetlbsizes; i++)
110562306a36Sopenharmony_ci		run_with_hugetlb(test_case->fn, test_case->desc,
110662306a36Sopenharmony_ci				 hugetlbsizes[i]);
110762306a36Sopenharmony_ci}
110862306a36Sopenharmony_ci
110962306a36Sopenharmony_cistatic void run_anon_test_cases(void)
111062306a36Sopenharmony_ci{
111162306a36Sopenharmony_ci	int i;
111262306a36Sopenharmony_ci
111362306a36Sopenharmony_ci	ksft_print_msg("[INFO] Anonymous memory tests in private mappings\n");
111462306a36Sopenharmony_ci
111562306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(anon_test_cases); i++)
111662306a36Sopenharmony_ci		run_anon_test_case(&anon_test_cases[i]);
111762306a36Sopenharmony_ci}
111862306a36Sopenharmony_ci
111962306a36Sopenharmony_cistatic int tests_per_anon_test_case(void)
112062306a36Sopenharmony_ci{
112162306a36Sopenharmony_ci	int tests = 2 + nr_hugetlbsizes;
112262306a36Sopenharmony_ci
112362306a36Sopenharmony_ci	if (thpsize)
112462306a36Sopenharmony_ci		tests += 8;
112562306a36Sopenharmony_ci	return tests;
112662306a36Sopenharmony_ci}
112762306a36Sopenharmony_ci
112862306a36Sopenharmony_cienum anon_thp_collapse_test {
112962306a36Sopenharmony_ci	ANON_THP_COLLAPSE_UNSHARED,
113062306a36Sopenharmony_ci	ANON_THP_COLLAPSE_FULLY_SHARED,
113162306a36Sopenharmony_ci	ANON_THP_COLLAPSE_LOWER_SHARED,
113262306a36Sopenharmony_ci	ANON_THP_COLLAPSE_UPPER_SHARED,
113362306a36Sopenharmony_ci};
113462306a36Sopenharmony_ci
113562306a36Sopenharmony_cistatic void do_test_anon_thp_collapse(char *mem, size_t size,
113662306a36Sopenharmony_ci				      enum anon_thp_collapse_test test)
113762306a36Sopenharmony_ci{
113862306a36Sopenharmony_ci	struct comm_pipes comm_pipes;
113962306a36Sopenharmony_ci	char buf;
114062306a36Sopenharmony_ci	int ret;
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci	ret = setup_comm_pipes(&comm_pipes);
114362306a36Sopenharmony_ci	if (ret) {
114462306a36Sopenharmony_ci		ksft_test_result_fail("pipe() failed\n");
114562306a36Sopenharmony_ci		return;
114662306a36Sopenharmony_ci	}
114762306a36Sopenharmony_ci
114862306a36Sopenharmony_ci	/*
114962306a36Sopenharmony_ci	 * Trigger PTE-mapping the THP by temporarily mapping a single subpage
115062306a36Sopenharmony_ci	 * R/O, such that we can try collapsing it later.
115162306a36Sopenharmony_ci	 */
115262306a36Sopenharmony_ci	ret = mprotect(mem + pagesize, pagesize, PROT_READ);
115362306a36Sopenharmony_ci	if (ret) {
115462306a36Sopenharmony_ci		ksft_test_result_fail("mprotect() failed\n");
115562306a36Sopenharmony_ci		goto close_comm_pipes;
115662306a36Sopenharmony_ci	}
115762306a36Sopenharmony_ci	ret = mprotect(mem + pagesize, pagesize, PROT_READ | PROT_WRITE);
115862306a36Sopenharmony_ci	if (ret) {
115962306a36Sopenharmony_ci		ksft_test_result_fail("mprotect() failed\n");
116062306a36Sopenharmony_ci		goto close_comm_pipes;
116162306a36Sopenharmony_ci	}
116262306a36Sopenharmony_ci
116362306a36Sopenharmony_ci	switch (test) {
116462306a36Sopenharmony_ci	case ANON_THP_COLLAPSE_UNSHARED:
116562306a36Sopenharmony_ci		/* Collapse before actually COW-sharing the page. */
116662306a36Sopenharmony_ci		ret = madvise(mem, size, MADV_COLLAPSE);
116762306a36Sopenharmony_ci		if (ret) {
116862306a36Sopenharmony_ci			ksft_test_result_skip("MADV_COLLAPSE failed: %s\n",
116962306a36Sopenharmony_ci					      strerror(errno));
117062306a36Sopenharmony_ci			goto close_comm_pipes;
117162306a36Sopenharmony_ci		}
117262306a36Sopenharmony_ci		break;
117362306a36Sopenharmony_ci	case ANON_THP_COLLAPSE_FULLY_SHARED:
117462306a36Sopenharmony_ci		/* COW-share the full PTE-mapped THP. */
117562306a36Sopenharmony_ci		break;
117662306a36Sopenharmony_ci	case ANON_THP_COLLAPSE_LOWER_SHARED:
117762306a36Sopenharmony_ci		/* Don't COW-share the upper part of the THP. */
117862306a36Sopenharmony_ci		ret = madvise(mem + size / 2, size / 2, MADV_DONTFORK);
117962306a36Sopenharmony_ci		if (ret) {
118062306a36Sopenharmony_ci			ksft_test_result_fail("MADV_DONTFORK failed\n");
118162306a36Sopenharmony_ci			goto close_comm_pipes;
118262306a36Sopenharmony_ci		}
118362306a36Sopenharmony_ci		break;
118462306a36Sopenharmony_ci	case ANON_THP_COLLAPSE_UPPER_SHARED:
118562306a36Sopenharmony_ci		/* Don't COW-share the lower part of the THP. */
118662306a36Sopenharmony_ci		ret = madvise(mem, size / 2, MADV_DONTFORK);
118762306a36Sopenharmony_ci		if (ret) {
118862306a36Sopenharmony_ci			ksft_test_result_fail("MADV_DONTFORK failed\n");
118962306a36Sopenharmony_ci			goto close_comm_pipes;
119062306a36Sopenharmony_ci		}
119162306a36Sopenharmony_ci		break;
119262306a36Sopenharmony_ci	default:
119362306a36Sopenharmony_ci		assert(false);
119462306a36Sopenharmony_ci	}
119562306a36Sopenharmony_ci
119662306a36Sopenharmony_ci	ret = fork();
119762306a36Sopenharmony_ci	if (ret < 0) {
119862306a36Sopenharmony_ci		ksft_test_result_fail("fork() failed\n");
119962306a36Sopenharmony_ci		goto close_comm_pipes;
120062306a36Sopenharmony_ci	} else if (!ret) {
120162306a36Sopenharmony_ci		switch (test) {
120262306a36Sopenharmony_ci		case ANON_THP_COLLAPSE_UNSHARED:
120362306a36Sopenharmony_ci		case ANON_THP_COLLAPSE_FULLY_SHARED:
120462306a36Sopenharmony_ci			exit(child_memcmp_fn(mem, size, &comm_pipes));
120562306a36Sopenharmony_ci			break;
120662306a36Sopenharmony_ci		case ANON_THP_COLLAPSE_LOWER_SHARED:
120762306a36Sopenharmony_ci			exit(child_memcmp_fn(mem, size / 2, &comm_pipes));
120862306a36Sopenharmony_ci			break;
120962306a36Sopenharmony_ci		case ANON_THP_COLLAPSE_UPPER_SHARED:
121062306a36Sopenharmony_ci			exit(child_memcmp_fn(mem + size / 2, size / 2,
121162306a36Sopenharmony_ci					     &comm_pipes));
121262306a36Sopenharmony_ci			break;
121362306a36Sopenharmony_ci		default:
121462306a36Sopenharmony_ci			assert(false);
121562306a36Sopenharmony_ci		}
121662306a36Sopenharmony_ci	}
121762306a36Sopenharmony_ci
121862306a36Sopenharmony_ci	while (read(comm_pipes.child_ready[0], &buf, 1) != 1)
121962306a36Sopenharmony_ci		;
122062306a36Sopenharmony_ci
122162306a36Sopenharmony_ci	switch (test) {
122262306a36Sopenharmony_ci	case ANON_THP_COLLAPSE_UNSHARED:
122362306a36Sopenharmony_ci		break;
122462306a36Sopenharmony_ci	case ANON_THP_COLLAPSE_UPPER_SHARED:
122562306a36Sopenharmony_ci	case ANON_THP_COLLAPSE_LOWER_SHARED:
122662306a36Sopenharmony_ci		/*
122762306a36Sopenharmony_ci		 * Revert MADV_DONTFORK such that we merge the VMAs and are
122862306a36Sopenharmony_ci		 * able to actually collapse.
122962306a36Sopenharmony_ci		 */
123062306a36Sopenharmony_ci		ret = madvise(mem, size, MADV_DOFORK);
123162306a36Sopenharmony_ci		if (ret) {
123262306a36Sopenharmony_ci			ksft_test_result_fail("MADV_DOFORK failed\n");
123362306a36Sopenharmony_ci			write(comm_pipes.parent_ready[1], "0", 1);
123462306a36Sopenharmony_ci			wait(&ret);
123562306a36Sopenharmony_ci			goto close_comm_pipes;
123662306a36Sopenharmony_ci		}
123762306a36Sopenharmony_ci		/* FALLTHROUGH */
123862306a36Sopenharmony_ci	case ANON_THP_COLLAPSE_FULLY_SHARED:
123962306a36Sopenharmony_ci		/* Collapse before anyone modified the COW-shared page. */
124062306a36Sopenharmony_ci		ret = madvise(mem, size, MADV_COLLAPSE);
124162306a36Sopenharmony_ci		if (ret) {
124262306a36Sopenharmony_ci			ksft_test_result_skip("MADV_COLLAPSE failed: %s\n",
124362306a36Sopenharmony_ci					      strerror(errno));
124462306a36Sopenharmony_ci			write(comm_pipes.parent_ready[1], "0", 1);
124562306a36Sopenharmony_ci			wait(&ret);
124662306a36Sopenharmony_ci			goto close_comm_pipes;
124762306a36Sopenharmony_ci		}
124862306a36Sopenharmony_ci		break;
124962306a36Sopenharmony_ci	default:
125062306a36Sopenharmony_ci		assert(false);
125162306a36Sopenharmony_ci	}
125262306a36Sopenharmony_ci
125362306a36Sopenharmony_ci	/* Modify the page. */
125462306a36Sopenharmony_ci	memset(mem, 0xff, size);
125562306a36Sopenharmony_ci	write(comm_pipes.parent_ready[1], "0", 1);
125662306a36Sopenharmony_ci
125762306a36Sopenharmony_ci	wait(&ret);
125862306a36Sopenharmony_ci	if (WIFEXITED(ret))
125962306a36Sopenharmony_ci		ret = WEXITSTATUS(ret);
126062306a36Sopenharmony_ci	else
126162306a36Sopenharmony_ci		ret = -EINVAL;
126262306a36Sopenharmony_ci
126362306a36Sopenharmony_ci	ksft_test_result(!ret, "No leak from parent into child\n");
126462306a36Sopenharmony_ciclose_comm_pipes:
126562306a36Sopenharmony_ci	close_comm_pipes(&comm_pipes);
126662306a36Sopenharmony_ci}
126762306a36Sopenharmony_ci
126862306a36Sopenharmony_cistatic void test_anon_thp_collapse_unshared(char *mem, size_t size)
126962306a36Sopenharmony_ci{
127062306a36Sopenharmony_ci	do_test_anon_thp_collapse(mem, size, ANON_THP_COLLAPSE_UNSHARED);
127162306a36Sopenharmony_ci}
127262306a36Sopenharmony_ci
127362306a36Sopenharmony_cistatic void test_anon_thp_collapse_fully_shared(char *mem, size_t size)
127462306a36Sopenharmony_ci{
127562306a36Sopenharmony_ci	do_test_anon_thp_collapse(mem, size, ANON_THP_COLLAPSE_FULLY_SHARED);
127662306a36Sopenharmony_ci}
127762306a36Sopenharmony_ci
127862306a36Sopenharmony_cistatic void test_anon_thp_collapse_lower_shared(char *mem, size_t size)
127962306a36Sopenharmony_ci{
128062306a36Sopenharmony_ci	do_test_anon_thp_collapse(mem, size, ANON_THP_COLLAPSE_LOWER_SHARED);
128162306a36Sopenharmony_ci}
128262306a36Sopenharmony_ci
128362306a36Sopenharmony_cistatic void test_anon_thp_collapse_upper_shared(char *mem, size_t size)
128462306a36Sopenharmony_ci{
128562306a36Sopenharmony_ci	do_test_anon_thp_collapse(mem, size, ANON_THP_COLLAPSE_UPPER_SHARED);
128662306a36Sopenharmony_ci}
128762306a36Sopenharmony_ci
128862306a36Sopenharmony_ci/*
128962306a36Sopenharmony_ci * Test cases that are specific to anonymous THP: pages in private mappings
129062306a36Sopenharmony_ci * that may get shared via COW during fork().
129162306a36Sopenharmony_ci */
129262306a36Sopenharmony_cistatic const struct test_case anon_thp_test_cases[] = {
129362306a36Sopenharmony_ci	/*
129462306a36Sopenharmony_ci	 * Basic COW test for fork() without any GUP when collapsing a THP
129562306a36Sopenharmony_ci	 * before fork().
129662306a36Sopenharmony_ci	 *
129762306a36Sopenharmony_ci	 * Re-mapping a PTE-mapped anon THP using a single PMD ("in-place
129862306a36Sopenharmony_ci	 * collapse") might easily get COW handling wrong when not collapsing
129962306a36Sopenharmony_ci	 * exclusivity information properly.
130062306a36Sopenharmony_ci	 */
130162306a36Sopenharmony_ci	{
130262306a36Sopenharmony_ci		"Basic COW after fork() when collapsing before fork()",
130362306a36Sopenharmony_ci		test_anon_thp_collapse_unshared,
130462306a36Sopenharmony_ci	},
130562306a36Sopenharmony_ci	/* Basic COW test, but collapse after COW-sharing a full THP. */
130662306a36Sopenharmony_ci	{
130762306a36Sopenharmony_ci		"Basic COW after fork() when collapsing after fork() (fully shared)",
130862306a36Sopenharmony_ci		test_anon_thp_collapse_fully_shared,
130962306a36Sopenharmony_ci	},
131062306a36Sopenharmony_ci	/*
131162306a36Sopenharmony_ci	 * Basic COW test, but collapse after COW-sharing the lower half of a
131262306a36Sopenharmony_ci	 * THP.
131362306a36Sopenharmony_ci	 */
131462306a36Sopenharmony_ci	{
131562306a36Sopenharmony_ci		"Basic COW after fork() when collapsing after fork() (lower shared)",
131662306a36Sopenharmony_ci		test_anon_thp_collapse_lower_shared,
131762306a36Sopenharmony_ci	},
131862306a36Sopenharmony_ci	/*
131962306a36Sopenharmony_ci	 * Basic COW test, but collapse after COW-sharing the upper half of a
132062306a36Sopenharmony_ci	 * THP.
132162306a36Sopenharmony_ci	 */
132262306a36Sopenharmony_ci	{
132362306a36Sopenharmony_ci		"Basic COW after fork() when collapsing after fork() (upper shared)",
132462306a36Sopenharmony_ci		test_anon_thp_collapse_upper_shared,
132562306a36Sopenharmony_ci	},
132662306a36Sopenharmony_ci};
132762306a36Sopenharmony_ci
132862306a36Sopenharmony_cistatic void run_anon_thp_test_cases(void)
132962306a36Sopenharmony_ci{
133062306a36Sopenharmony_ci	int i;
133162306a36Sopenharmony_ci
133262306a36Sopenharmony_ci	if (!thpsize)
133362306a36Sopenharmony_ci		return;
133462306a36Sopenharmony_ci
133562306a36Sopenharmony_ci	ksft_print_msg("[INFO] Anonymous THP tests\n");
133662306a36Sopenharmony_ci
133762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(anon_thp_test_cases); i++) {
133862306a36Sopenharmony_ci		struct test_case const *test_case = &anon_thp_test_cases[i];
133962306a36Sopenharmony_ci
134062306a36Sopenharmony_ci		ksft_print_msg("[RUN] %s\n", test_case->desc);
134162306a36Sopenharmony_ci		do_run_with_thp(test_case->fn, THP_RUN_PMD);
134262306a36Sopenharmony_ci	}
134362306a36Sopenharmony_ci}
134462306a36Sopenharmony_ci
134562306a36Sopenharmony_cistatic int tests_per_anon_thp_test_case(void)
134662306a36Sopenharmony_ci{
134762306a36Sopenharmony_ci	return thpsize ? 1 : 0;
134862306a36Sopenharmony_ci}
134962306a36Sopenharmony_ci
135062306a36Sopenharmony_citypedef void (*non_anon_test_fn)(char *mem, const char *smem, size_t size);
135162306a36Sopenharmony_ci
135262306a36Sopenharmony_cistatic void test_cow(char *mem, const char *smem, size_t size)
135362306a36Sopenharmony_ci{
135462306a36Sopenharmony_ci	char *old = malloc(size);
135562306a36Sopenharmony_ci
135662306a36Sopenharmony_ci	/* Backup the original content. */
135762306a36Sopenharmony_ci	memcpy(old, smem, size);
135862306a36Sopenharmony_ci
135962306a36Sopenharmony_ci	/* Modify the page. */
136062306a36Sopenharmony_ci	memset(mem, 0xff, size);
136162306a36Sopenharmony_ci
136262306a36Sopenharmony_ci	/* See if we still read the old values via the other mapping. */
136362306a36Sopenharmony_ci	ksft_test_result(!memcmp(smem, old, size),
136462306a36Sopenharmony_ci			 "Other mapping not modified\n");
136562306a36Sopenharmony_ci	free(old);
136662306a36Sopenharmony_ci}
136762306a36Sopenharmony_ci
136862306a36Sopenharmony_cistatic void test_ro_pin(char *mem, const char *smem, size_t size)
136962306a36Sopenharmony_ci{
137062306a36Sopenharmony_ci	do_test_ro_pin(mem, size, RO_PIN_TEST, false);
137162306a36Sopenharmony_ci}
137262306a36Sopenharmony_ci
137362306a36Sopenharmony_cistatic void test_ro_fast_pin(char *mem, const char *smem, size_t size)
137462306a36Sopenharmony_ci{
137562306a36Sopenharmony_ci	do_test_ro_pin(mem, size, RO_PIN_TEST, true);
137662306a36Sopenharmony_ci}
137762306a36Sopenharmony_ci
137862306a36Sopenharmony_cistatic void run_with_zeropage(non_anon_test_fn fn, const char *desc)
137962306a36Sopenharmony_ci{
138062306a36Sopenharmony_ci	char *mem, *smem, tmp;
138162306a36Sopenharmony_ci
138262306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with shared zeropage\n", desc);
138362306a36Sopenharmony_ci
138462306a36Sopenharmony_ci	mem = mmap(NULL, pagesize, PROT_READ | PROT_WRITE,
138562306a36Sopenharmony_ci		   MAP_PRIVATE | MAP_ANON, -1, 0);
138662306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
138762306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
138862306a36Sopenharmony_ci		return;
138962306a36Sopenharmony_ci	}
139062306a36Sopenharmony_ci
139162306a36Sopenharmony_ci	smem = mmap(NULL, pagesize, PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0);
139262306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
139362306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
139462306a36Sopenharmony_ci		goto munmap;
139562306a36Sopenharmony_ci	}
139662306a36Sopenharmony_ci
139762306a36Sopenharmony_ci	/* Read from the page to populate the shared zeropage. */
139862306a36Sopenharmony_ci	tmp = *mem + *smem;
139962306a36Sopenharmony_ci	asm volatile("" : "+r" (tmp));
140062306a36Sopenharmony_ci
140162306a36Sopenharmony_ci	fn(mem, smem, pagesize);
140262306a36Sopenharmony_cimunmap:
140362306a36Sopenharmony_ci	munmap(mem, pagesize);
140462306a36Sopenharmony_ci	if (smem != MAP_FAILED)
140562306a36Sopenharmony_ci		munmap(smem, pagesize);
140662306a36Sopenharmony_ci}
140762306a36Sopenharmony_ci
140862306a36Sopenharmony_cistatic void run_with_huge_zeropage(non_anon_test_fn fn, const char *desc)
140962306a36Sopenharmony_ci{
141062306a36Sopenharmony_ci	char *mem, *smem, *mmap_mem, *mmap_smem, tmp;
141162306a36Sopenharmony_ci	size_t mmap_size;
141262306a36Sopenharmony_ci	int ret;
141362306a36Sopenharmony_ci
141462306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with huge zeropage\n", desc);
141562306a36Sopenharmony_ci
141662306a36Sopenharmony_ci	if (!has_huge_zeropage) {
141762306a36Sopenharmony_ci		ksft_test_result_skip("Huge zeropage not enabled\n");
141862306a36Sopenharmony_ci		return;
141962306a36Sopenharmony_ci	}
142062306a36Sopenharmony_ci
142162306a36Sopenharmony_ci	/* For alignment purposes, we need twice the thp size. */
142262306a36Sopenharmony_ci	mmap_size = 2 * thpsize;
142362306a36Sopenharmony_ci	mmap_mem = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
142462306a36Sopenharmony_ci			MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
142562306a36Sopenharmony_ci	if (mmap_mem == MAP_FAILED) {
142662306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
142762306a36Sopenharmony_ci		return;
142862306a36Sopenharmony_ci	}
142962306a36Sopenharmony_ci	mmap_smem = mmap(NULL, mmap_size, PROT_READ,
143062306a36Sopenharmony_ci			 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
143162306a36Sopenharmony_ci	if (mmap_smem == MAP_FAILED) {
143262306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
143362306a36Sopenharmony_ci		goto munmap;
143462306a36Sopenharmony_ci	}
143562306a36Sopenharmony_ci
143662306a36Sopenharmony_ci	/* We need a THP-aligned memory area. */
143762306a36Sopenharmony_ci	mem = (char *)(((uintptr_t)mmap_mem + thpsize) & ~(thpsize - 1));
143862306a36Sopenharmony_ci	smem = (char *)(((uintptr_t)mmap_smem + thpsize) & ~(thpsize - 1));
143962306a36Sopenharmony_ci
144062306a36Sopenharmony_ci	ret = madvise(mem, thpsize, MADV_HUGEPAGE);
144162306a36Sopenharmony_ci	ret |= madvise(smem, thpsize, MADV_HUGEPAGE);
144262306a36Sopenharmony_ci	if (ret) {
144362306a36Sopenharmony_ci		ksft_test_result_fail("MADV_HUGEPAGE failed\n");
144462306a36Sopenharmony_ci		goto munmap;
144562306a36Sopenharmony_ci	}
144662306a36Sopenharmony_ci
144762306a36Sopenharmony_ci	/*
144862306a36Sopenharmony_ci	 * Read from the memory to populate the huge shared zeropage. Read from
144962306a36Sopenharmony_ci	 * the first sub-page and test if we get another sub-page populated
145062306a36Sopenharmony_ci	 * automatically.
145162306a36Sopenharmony_ci	 */
145262306a36Sopenharmony_ci	tmp = *mem + *smem;
145362306a36Sopenharmony_ci	asm volatile("" : "+r" (tmp));
145462306a36Sopenharmony_ci	if (!pagemap_is_populated(pagemap_fd, mem + pagesize) ||
145562306a36Sopenharmony_ci	    !pagemap_is_populated(pagemap_fd, smem + pagesize)) {
145662306a36Sopenharmony_ci		ksft_test_result_skip("Did not get THPs populated\n");
145762306a36Sopenharmony_ci		goto munmap;
145862306a36Sopenharmony_ci	}
145962306a36Sopenharmony_ci
146062306a36Sopenharmony_ci	fn(mem, smem, thpsize);
146162306a36Sopenharmony_cimunmap:
146262306a36Sopenharmony_ci	munmap(mmap_mem, mmap_size);
146362306a36Sopenharmony_ci	if (mmap_smem != MAP_FAILED)
146462306a36Sopenharmony_ci		munmap(mmap_smem, mmap_size);
146562306a36Sopenharmony_ci}
146662306a36Sopenharmony_ci
146762306a36Sopenharmony_cistatic void run_with_memfd(non_anon_test_fn fn, const char *desc)
146862306a36Sopenharmony_ci{
146962306a36Sopenharmony_ci	char *mem, *smem, tmp;
147062306a36Sopenharmony_ci	int fd;
147162306a36Sopenharmony_ci
147262306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with memfd\n", desc);
147362306a36Sopenharmony_ci
147462306a36Sopenharmony_ci	fd = memfd_create("test", 0);
147562306a36Sopenharmony_ci	if (fd < 0) {
147662306a36Sopenharmony_ci		ksft_test_result_fail("memfd_create() failed\n");
147762306a36Sopenharmony_ci		return;
147862306a36Sopenharmony_ci	}
147962306a36Sopenharmony_ci
148062306a36Sopenharmony_ci	/* File consists of a single page filled with zeroes. */
148162306a36Sopenharmony_ci	if (fallocate(fd, 0, 0, pagesize)) {
148262306a36Sopenharmony_ci		ksft_test_result_fail("fallocate() failed\n");
148362306a36Sopenharmony_ci		goto close;
148462306a36Sopenharmony_ci	}
148562306a36Sopenharmony_ci
148662306a36Sopenharmony_ci	/* Create a private mapping of the memfd. */
148762306a36Sopenharmony_ci	mem = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
148862306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
148962306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
149062306a36Sopenharmony_ci		goto close;
149162306a36Sopenharmony_ci	}
149262306a36Sopenharmony_ci	smem = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0);
149362306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
149462306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
149562306a36Sopenharmony_ci		goto munmap;
149662306a36Sopenharmony_ci	}
149762306a36Sopenharmony_ci
149862306a36Sopenharmony_ci	/* Fault the page in. */
149962306a36Sopenharmony_ci	tmp = *mem + *smem;
150062306a36Sopenharmony_ci	asm volatile("" : "+r" (tmp));
150162306a36Sopenharmony_ci
150262306a36Sopenharmony_ci	fn(mem, smem, pagesize);
150362306a36Sopenharmony_cimunmap:
150462306a36Sopenharmony_ci	munmap(mem, pagesize);
150562306a36Sopenharmony_ci	if (smem != MAP_FAILED)
150662306a36Sopenharmony_ci		munmap(smem, pagesize);
150762306a36Sopenharmony_ciclose:
150862306a36Sopenharmony_ci	close(fd);
150962306a36Sopenharmony_ci}
151062306a36Sopenharmony_ci
151162306a36Sopenharmony_cistatic void run_with_tmpfile(non_anon_test_fn fn, const char *desc)
151262306a36Sopenharmony_ci{
151362306a36Sopenharmony_ci	char *mem, *smem, tmp;
151462306a36Sopenharmony_ci	FILE *file;
151562306a36Sopenharmony_ci	int fd;
151662306a36Sopenharmony_ci
151762306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with tmpfile\n", desc);
151862306a36Sopenharmony_ci
151962306a36Sopenharmony_ci	file = tmpfile();
152062306a36Sopenharmony_ci	if (!file) {
152162306a36Sopenharmony_ci		ksft_test_result_fail("tmpfile() failed\n");
152262306a36Sopenharmony_ci		return;
152362306a36Sopenharmony_ci	}
152462306a36Sopenharmony_ci
152562306a36Sopenharmony_ci	fd = fileno(file);
152662306a36Sopenharmony_ci	if (fd < 0) {
152762306a36Sopenharmony_ci		ksft_test_result_skip("fileno() failed\n");
152862306a36Sopenharmony_ci		return;
152962306a36Sopenharmony_ci	}
153062306a36Sopenharmony_ci
153162306a36Sopenharmony_ci	/* File consists of a single page filled with zeroes. */
153262306a36Sopenharmony_ci	if (fallocate(fd, 0, 0, pagesize)) {
153362306a36Sopenharmony_ci		ksft_test_result_fail("fallocate() failed\n");
153462306a36Sopenharmony_ci		goto close;
153562306a36Sopenharmony_ci	}
153662306a36Sopenharmony_ci
153762306a36Sopenharmony_ci	/* Create a private mapping of the memfd. */
153862306a36Sopenharmony_ci	mem = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
153962306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
154062306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
154162306a36Sopenharmony_ci		goto close;
154262306a36Sopenharmony_ci	}
154362306a36Sopenharmony_ci	smem = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0);
154462306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
154562306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
154662306a36Sopenharmony_ci		goto munmap;
154762306a36Sopenharmony_ci	}
154862306a36Sopenharmony_ci
154962306a36Sopenharmony_ci	/* Fault the page in. */
155062306a36Sopenharmony_ci	tmp = *mem + *smem;
155162306a36Sopenharmony_ci	asm volatile("" : "+r" (tmp));
155262306a36Sopenharmony_ci
155362306a36Sopenharmony_ci	fn(mem, smem, pagesize);
155462306a36Sopenharmony_cimunmap:
155562306a36Sopenharmony_ci	munmap(mem, pagesize);
155662306a36Sopenharmony_ci	if (smem != MAP_FAILED)
155762306a36Sopenharmony_ci		munmap(smem, pagesize);
155862306a36Sopenharmony_ciclose:
155962306a36Sopenharmony_ci	fclose(file);
156062306a36Sopenharmony_ci}
156162306a36Sopenharmony_ci
156262306a36Sopenharmony_cistatic void run_with_memfd_hugetlb(non_anon_test_fn fn, const char *desc,
156362306a36Sopenharmony_ci				   size_t hugetlbsize)
156462306a36Sopenharmony_ci{
156562306a36Sopenharmony_ci	int flags = MFD_HUGETLB;
156662306a36Sopenharmony_ci	char *mem, *smem, tmp;
156762306a36Sopenharmony_ci	int fd;
156862306a36Sopenharmony_ci
156962306a36Sopenharmony_ci	ksft_print_msg("[RUN] %s ... with memfd hugetlb (%zu kB)\n", desc,
157062306a36Sopenharmony_ci		       hugetlbsize / 1024);
157162306a36Sopenharmony_ci
157262306a36Sopenharmony_ci	flags |= __builtin_ctzll(hugetlbsize) << MFD_HUGE_SHIFT;
157362306a36Sopenharmony_ci
157462306a36Sopenharmony_ci	fd = memfd_create("test", flags);
157562306a36Sopenharmony_ci	if (fd < 0) {
157662306a36Sopenharmony_ci		ksft_test_result_skip("memfd_create() failed\n");
157762306a36Sopenharmony_ci		return;
157862306a36Sopenharmony_ci	}
157962306a36Sopenharmony_ci
158062306a36Sopenharmony_ci	/* File consists of a single page filled with zeroes. */
158162306a36Sopenharmony_ci	if (fallocate(fd, 0, 0, hugetlbsize)) {
158262306a36Sopenharmony_ci		ksft_test_result_skip("need more free huge pages\n");
158362306a36Sopenharmony_ci		goto close;
158462306a36Sopenharmony_ci	}
158562306a36Sopenharmony_ci
158662306a36Sopenharmony_ci	/* Create a private mapping of the memfd. */
158762306a36Sopenharmony_ci	mem = mmap(NULL, hugetlbsize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd,
158862306a36Sopenharmony_ci		   0);
158962306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
159062306a36Sopenharmony_ci		ksft_test_result_skip("need more free huge pages\n");
159162306a36Sopenharmony_ci		goto close;
159262306a36Sopenharmony_ci	}
159362306a36Sopenharmony_ci	smem = mmap(NULL, hugetlbsize, PROT_READ, MAP_SHARED, fd, 0);
159462306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
159562306a36Sopenharmony_ci		ksft_test_result_fail("mmap() failed\n");
159662306a36Sopenharmony_ci		goto munmap;
159762306a36Sopenharmony_ci	}
159862306a36Sopenharmony_ci
159962306a36Sopenharmony_ci	/* Fault the page in. */
160062306a36Sopenharmony_ci	tmp = *mem + *smem;
160162306a36Sopenharmony_ci	asm volatile("" : "+r" (tmp));
160262306a36Sopenharmony_ci
160362306a36Sopenharmony_ci	fn(mem, smem, hugetlbsize);
160462306a36Sopenharmony_cimunmap:
160562306a36Sopenharmony_ci	munmap(mem, hugetlbsize);
160662306a36Sopenharmony_ci	if (mem != MAP_FAILED)
160762306a36Sopenharmony_ci		munmap(smem, hugetlbsize);
160862306a36Sopenharmony_ciclose:
160962306a36Sopenharmony_ci	close(fd);
161062306a36Sopenharmony_ci}
161162306a36Sopenharmony_ci
161262306a36Sopenharmony_cistruct non_anon_test_case {
161362306a36Sopenharmony_ci	const char *desc;
161462306a36Sopenharmony_ci	non_anon_test_fn fn;
161562306a36Sopenharmony_ci};
161662306a36Sopenharmony_ci
161762306a36Sopenharmony_ci/*
161862306a36Sopenharmony_ci * Test cases that target any pages in private mappings that are not anonymous:
161962306a36Sopenharmony_ci * pages that may get shared via COW ndependent of fork(). This includes
162062306a36Sopenharmony_ci * the shared zeropage(s), pagecache pages, ...
162162306a36Sopenharmony_ci */
162262306a36Sopenharmony_cistatic const struct non_anon_test_case non_anon_test_cases[] = {
162362306a36Sopenharmony_ci	/*
162462306a36Sopenharmony_ci	 * Basic COW test without any GUP. If we miss to break COW, changes are
162562306a36Sopenharmony_ci	 * visible via other private/shared mappings.
162662306a36Sopenharmony_ci	 */
162762306a36Sopenharmony_ci	{
162862306a36Sopenharmony_ci		"Basic COW",
162962306a36Sopenharmony_ci		test_cow,
163062306a36Sopenharmony_ci	},
163162306a36Sopenharmony_ci	/*
163262306a36Sopenharmony_ci	 * Take a R/O longterm pin. When modifying the page via the page table,
163362306a36Sopenharmony_ci	 * the page content change must be visible via the pin.
163462306a36Sopenharmony_ci	 */
163562306a36Sopenharmony_ci	{
163662306a36Sopenharmony_ci		"R/O longterm GUP pin",
163762306a36Sopenharmony_ci		test_ro_pin,
163862306a36Sopenharmony_ci	},
163962306a36Sopenharmony_ci	/* Same as above, but using GUP-fast. */
164062306a36Sopenharmony_ci	{
164162306a36Sopenharmony_ci		"R/O longterm GUP-fast pin",
164262306a36Sopenharmony_ci		test_ro_fast_pin,
164362306a36Sopenharmony_ci	},
164462306a36Sopenharmony_ci};
164562306a36Sopenharmony_ci
164662306a36Sopenharmony_cistatic void run_non_anon_test_case(struct non_anon_test_case const *test_case)
164762306a36Sopenharmony_ci{
164862306a36Sopenharmony_ci	int i;
164962306a36Sopenharmony_ci
165062306a36Sopenharmony_ci	run_with_zeropage(test_case->fn, test_case->desc);
165162306a36Sopenharmony_ci	run_with_memfd(test_case->fn, test_case->desc);
165262306a36Sopenharmony_ci	run_with_tmpfile(test_case->fn, test_case->desc);
165362306a36Sopenharmony_ci	if (thpsize)
165462306a36Sopenharmony_ci		run_with_huge_zeropage(test_case->fn, test_case->desc);
165562306a36Sopenharmony_ci	for (i = 0; i < nr_hugetlbsizes; i++)
165662306a36Sopenharmony_ci		run_with_memfd_hugetlb(test_case->fn, test_case->desc,
165762306a36Sopenharmony_ci				       hugetlbsizes[i]);
165862306a36Sopenharmony_ci}
165962306a36Sopenharmony_ci
166062306a36Sopenharmony_cistatic void run_non_anon_test_cases(void)
166162306a36Sopenharmony_ci{
166262306a36Sopenharmony_ci	int i;
166362306a36Sopenharmony_ci
166462306a36Sopenharmony_ci	ksft_print_msg("[RUN] Non-anonymous memory tests in private mappings\n");
166562306a36Sopenharmony_ci
166662306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(non_anon_test_cases); i++)
166762306a36Sopenharmony_ci		run_non_anon_test_case(&non_anon_test_cases[i]);
166862306a36Sopenharmony_ci}
166962306a36Sopenharmony_ci
167062306a36Sopenharmony_cistatic int tests_per_non_anon_test_case(void)
167162306a36Sopenharmony_ci{
167262306a36Sopenharmony_ci	int tests = 3 + nr_hugetlbsizes;
167362306a36Sopenharmony_ci
167462306a36Sopenharmony_ci	if (thpsize)
167562306a36Sopenharmony_ci		tests += 1;
167662306a36Sopenharmony_ci	return tests;
167762306a36Sopenharmony_ci}
167862306a36Sopenharmony_ci
167962306a36Sopenharmony_ciint main(int argc, char **argv)
168062306a36Sopenharmony_ci{
168162306a36Sopenharmony_ci	int err;
168262306a36Sopenharmony_ci
168362306a36Sopenharmony_ci	ksft_print_header();
168462306a36Sopenharmony_ci
168562306a36Sopenharmony_ci	pagesize = getpagesize();
168662306a36Sopenharmony_ci	thpsize = read_pmd_pagesize();
168762306a36Sopenharmony_ci	if (thpsize)
168862306a36Sopenharmony_ci		ksft_print_msg("[INFO] detected THP size: %zu KiB\n",
168962306a36Sopenharmony_ci			       thpsize / 1024);
169062306a36Sopenharmony_ci	nr_hugetlbsizes = detect_hugetlb_page_sizes(hugetlbsizes,
169162306a36Sopenharmony_ci						    ARRAY_SIZE(hugetlbsizes));
169262306a36Sopenharmony_ci	detect_huge_zeropage();
169362306a36Sopenharmony_ci
169462306a36Sopenharmony_ci	ksft_set_plan(ARRAY_SIZE(anon_test_cases) * tests_per_anon_test_case() +
169562306a36Sopenharmony_ci		      ARRAY_SIZE(anon_thp_test_cases) * tests_per_anon_thp_test_case() +
169662306a36Sopenharmony_ci		      ARRAY_SIZE(non_anon_test_cases) * tests_per_non_anon_test_case());
169762306a36Sopenharmony_ci
169862306a36Sopenharmony_ci	gup_fd = open("/sys/kernel/debug/gup_test", O_RDWR);
169962306a36Sopenharmony_ci	pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
170062306a36Sopenharmony_ci	if (pagemap_fd < 0)
170162306a36Sopenharmony_ci		ksft_exit_fail_msg("opening pagemap failed\n");
170262306a36Sopenharmony_ci
170362306a36Sopenharmony_ci	run_anon_test_cases();
170462306a36Sopenharmony_ci	run_anon_thp_test_cases();
170562306a36Sopenharmony_ci	run_non_anon_test_cases();
170662306a36Sopenharmony_ci
170762306a36Sopenharmony_ci	err = ksft_get_fail_cnt();
170862306a36Sopenharmony_ci	if (err)
170962306a36Sopenharmony_ci		ksft_exit_fail_msg("%d out of %d tests failed\n",
171062306a36Sopenharmony_ci				   err, ksft_test_num());
171162306a36Sopenharmony_ci	return ksft_exit_pass();
171262306a36Sopenharmony_ci}
1713