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