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