// SPDX-License-Identifier: GPL-2.0 #define __SANE_USERSPACE_TYPES__ // Use ll64 #include #include #include #include #include #include #include #include #include #include "vm_util.h" #include "kselftest_harness.h" #define MB (1UL << 20) /* Just the flags we need, copied from the kernel internals. */ #define FOLL_WRITE 0x01 /* check pte is writable */ /* Page counts exercising single, THP-batch, partial, and full-mapping GUP. */ static const int nr_pages_list[] = { 1, 512, 123, -1 }; #define GUP_TEST_FILE "/sys/kernel/debug/gup_test" #define NR_HUGETLB_PAGES 2 static unsigned long hp_size; FIXTURE(gup_test) { int gup_fd; char *addr; unsigned long size; }; FIXTURE_VARIANT(gup_test) { bool thp; bool hugetlb; bool write; bool shared; }; FIXTURE_VARIANT_ADD(gup_test, private_write) { .thp = false, .hugetlb = false, .write = true, .shared = false, }; FIXTURE_VARIANT_ADD(gup_test, private_read) { .thp = false, .hugetlb = false, .write = false, .shared = false, }; FIXTURE_VARIANT_ADD(gup_test, private_write_thp) { .thp = true, .hugetlb = false, .write = true, .shared = false, }; FIXTURE_VARIANT_ADD(gup_test, private_read_thp) { .thp = true, .hugetlb = false, .write = false, .shared = false, }; FIXTURE_VARIANT_ADD(gup_test, private_write_hugetlb) { .thp = false, .hugetlb = true, .write = true, .shared = false, }; FIXTURE_VARIANT_ADD(gup_test, private_read_hugetlb) { .thp = false, .hugetlb = true, .write = false, .shared = false, }; FIXTURE_VARIANT_ADD(gup_test, shared_write) { .thp = false, .hugetlb = false, .write = true, .shared = true, }; FIXTURE_VARIANT_ADD(gup_test, shared_read) { .thp = false, .hugetlb = false, .write = false, .shared = true, }; FIXTURE_VARIANT_ADD(gup_test, shared_write_thp) { .thp = true, .hugetlb = false, .write = true, .shared = true, }; FIXTURE_VARIANT_ADD(gup_test, shared_read_thp) { .thp = true, .hugetlb = false, .write = false, .shared = true, }; FIXTURE_VARIANT_ADD(gup_test, shared_write_hugetlb) { .thp = false, .hugetlb = true, .write = true, .shared = true, }; FIXTURE_VARIANT_ADD(gup_test, shared_read_hugetlb) { .thp = false, .hugetlb = true, .write = false, .shared = true, }; FIXTURE_SETUP(gup_test) { int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS; char *p; self->size = 128 * MB; if (variant->hugetlb) { if (!hp_size) SKIP(return, "HugeTLB not available\n"); if (hugetlb_free_default_pages() < NR_HUGETLB_PAGES) SKIP(return, "Not enough huge pages\n"); self->size = NR_HUGETLB_PAGES * hp_size; mmap_flags |= MAP_HUGETLB; } if (variant->shared) mmap_flags = (mmap_flags & ~MAP_PRIVATE) | MAP_SHARED; /* gup_fd has to be >= 0. Already checked in main() */ self->gup_fd = open(GUP_TEST_FILE, O_RDWR); ASSERT_GE(self->gup_fd, 0); self->addr = mmap(NULL, self->size, PROT_READ | PROT_WRITE, mmap_flags, -1, 0); ASSERT_NE(self->addr, MAP_FAILED) { int err = errno; close(self->gup_fd); TH_LOG("mmap failed: %s", strerror(err)); } if (variant->thp) madvise(self->addr, self->size, MADV_HUGEPAGE); else if (!variant->hugetlb) madvise(self->addr, self->size, MADV_NOHUGEPAGE); for (p = self->addr; (unsigned long)p < (unsigned long)self->addr + self->size; p += psize()) p[0] = 0; } FIXTURE_TEARDOWN(gup_test) { munmap(self->addr, self->size); close(self->gup_fd); } static void run_gup_cmd(struct __test_metadata *_metadata, FIXTURE_DATA(gup_test) *self, const FIXTURE_VARIANT(gup_test) *variant, unsigned long command) { int i; for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) { struct gup_test gup = { .addr = (unsigned long)self->addr, .size = self->size, .nr_pages_per_call = nr_pages_list[i] < 0 ? self->size / psize() : nr_pages_list[i], .gup_flags = variant->write ? FOLL_WRITE : 0, }; TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call); ASSERT_EQ(ioctl(self->gup_fd, command, &gup), 0); ASSERT_EQ(gup.size, self->size); } } TEST_F(gup_test, get_user_pages) { run_gup_cmd(_metadata, self, variant, GUP_BASIC_TEST); } TEST_F(gup_test, pin_user_pages) { run_gup_cmd(_metadata, self, variant, PIN_BASIC_TEST); } TEST_F(gup_test, get_user_pages_fast) { run_gup_cmd(_metadata, self, variant, GUP_FAST_BENCHMARK); } TEST_F(gup_test, pin_user_pages_fast) { run_gup_cmd(_metadata, self, variant, PIN_FAST_BENCHMARK); } TEST_F(gup_test, pin_user_pages_longterm) { run_gup_cmd(_metadata, self, variant, PIN_LONGTERM_BENCHMARK); } int main(int argc, char **argv) { const int fd = open(GUP_TEST_FILE, O_RDWR); if (fd == -1) { ksft_print_header(); if (errno == EACCES) ksft_exit_skip("Please run this test as root\n"); if (errno == ENOENT) { DIR *debugfs = opendir("/sys/kernel/debug"); if (!debugfs) ksft_exit_skip("Mount debugfs at /sys/kernel/debug\n"); closedir(debugfs); ksft_exit_skip("Check CONFIG_GUP_TEST in kernel config\n"); } ksft_exit_fail_msg("Failed to open %s: %s\n", GUP_TEST_FILE, strerror(errno)); } close(fd); hp_size = default_huge_page_size(); if (hp_size) hugetlb_setup_default(NR_HUGETLB_PAGES); return test_harness_run(argc, argv); }