// SPDX-License-Identifier: GPL-2.0-only /* * MADV_POPULATE_READ and MADV_POPULATE_WRITE tests * * Copyright 2021, Red Hat, Inc. * * Author(s): David Hildenbrand <david@redhat.com> */ #define _GNU_SOURCE #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <stdint.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <linux/mman.h> #include <sys/mman.h> #include "../kselftest.h" #include "vm_util.h" /* * For now, we're using 2 MiB of private anonymous memory for all tests. */ #define SIZE (2 * 1024 * 1024) static size_t pagesize; static void sense_support(void) { char *addr; int ret; addr = mmap(0, pagesize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (!addr) ksft_exit_fail_msg("mmap failed\n"); ret = madvise(addr, pagesize, MADV_POPULATE_READ); if (ret) ksft_exit_skip("MADV_POPULATE_READ is not available\n"); ret = madvise(addr, pagesize, MADV_POPULATE_WRITE); if (ret) ksft_exit_skip("MADV_POPULATE_WRITE is not available\n"); munmap(addr, pagesize); } static void test_prot_read(void) { char *addr; int ret; ksft_print_msg("[RUN] %s\n", __func__); addr = mmap(0, SIZE, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (addr == MAP_FAILED) ksft_exit_fail_msg("mmap failed\n"); ret = madvise(addr, SIZE, MADV_POPULATE_READ); ksft_test_result(!ret, "MADV_POPULATE_READ with PROT_READ\n"); ret = madvise(addr, SIZE, MADV_POPULATE_WRITE); ksft_test_result(ret == -1 && errno == EINVAL, "MADV_POPULATE_WRITE with PROT_READ\n"); munmap(addr, SIZE); } static void test_prot_write(void) { char *addr; int ret; ksft_print_msg("[RUN] %s\n", __func__); addr = mmap(0, SIZE, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (addr == MAP_FAILED) ksft_exit_fail_msg("mmap failed\n"); ret = madvise(addr, SIZE, MADV_POPULATE_READ); ksft_test_result(ret == -1 && errno == EINVAL, "MADV_POPULATE_READ with PROT_WRITE\n"); ret = madvise(addr, SIZE, MADV_POPULATE_WRITE); ksft_test_result(!ret, "MADV_POPULATE_WRITE with PROT_WRITE\n"); munmap(addr, SIZE); } static void test_holes(void) { char *addr; int ret; ksft_print_msg("[RUN] %s\n", __func__); addr = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (addr == MAP_FAILED) ksft_exit_fail_msg("mmap failed\n"); ret = munmap(addr + pagesize, pagesize); if (ret) ksft_exit_fail_msg("munmap failed\n"); /* Hole in the middle */ ret = madvise(addr, SIZE, MADV_POPULATE_READ); ksft_test_result(ret == -1 && errno == ENOMEM, "MADV_POPULATE_READ with holes in the middle\n"); ret = madvise(addr, SIZE, MADV_POPULATE_WRITE); ksft_test_result(ret == -1 && errno == ENOMEM, "MADV_POPULATE_WRITE with holes in the middle\n"); /* Hole at end */ ret = madvise(addr, 2 * pagesize, MADV_POPULATE_READ); ksft_test_result(ret == -1 && errno == ENOMEM, "MADV_POPULATE_READ with holes at the end\n"); ret = madvise(addr, 2 * pagesize, MADV_POPULATE_WRITE); ksft_test_result(ret == -1 && errno == ENOMEM, "MADV_POPULATE_WRITE with holes at the end\n"); /* Hole at beginning */ ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_READ); ksft_test_result(ret == -1 && errno == ENOMEM, "MADV_POPULATE_READ with holes at the beginning\n"); ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_WRITE); ksft_test_result(ret == -1 && errno == ENOMEM, "MADV_POPULATE_WRITE with holes at the beginning\n"); munmap(addr, SIZE); } static bool range_is_populated(char *start, ssize_t size) { int fd = open("/proc/self/pagemap", O_RDONLY); bool ret = true; if (fd < 0) ksft_exit_fail_msg("opening pagemap failed\n"); for (; size > 0 && ret; size -= pagesize, start += pagesize) if (!pagemap_is_populated(fd, start)) ret = false; close(fd); return ret; } static bool range_is_not_populated(char *start, ssize_t size) { int fd = open("/proc/self/pagemap", O_RDONLY); bool ret = true; if (fd < 0) ksft_exit_fail_msg("opening pagemap failed\n"); for (; size > 0 && ret; size -= pagesize, start += pagesize) if (pagemap_is_populated(fd, start)) ret = false; close(fd); return ret; } static void test_populate_read(void) { char *addr; int ret; ksft_print_msg("[RUN] %s\n", __func__); addr = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (addr == MAP_FAILED) ksft_exit_fail_msg("mmap failed\n"); ksft_test_result(range_is_not_populated(addr, SIZE), "range initially not populated\n"); ret = madvise(addr, SIZE, MADV_POPULATE_READ); ksft_test_result(!ret, "MADV_POPULATE_READ\n"); ksft_test_result(range_is_populated(addr, SIZE), "range is populated\n"); munmap(addr, SIZE); } static void test_populate_write(void) { char *addr; int ret; ksft_print_msg("[RUN] %s\n", __func__); addr = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (addr == MAP_FAILED) ksft_exit_fail_msg("mmap failed\n"); ksft_test_result(range_is_not_populated(addr, SIZE), "range initially not populated\n"); ret = madvise(addr, SIZE, MADV_POPULATE_WRITE); ksft_test_result(!ret, "MADV_POPULATE_WRITE\n"); ksft_test_result(range_is_populated(addr, SIZE), "range is populated\n"); munmap(addr, SIZE); } static bool range_is_softdirty(char *start, ssize_t size) { int fd = open("/proc/self/pagemap", O_RDONLY); bool ret = true; if (fd < 0) ksft_exit_fail_msg("opening pagemap failed\n"); for (; size > 0 && ret; size -= pagesize, start += pagesize) if (!pagemap_is_softdirty(fd, start)) ret = false; close(fd); return ret; } static bool range_is_not_softdirty(char *start, ssize_t size) { int fd = open("/proc/self/pagemap", O_RDONLY); bool ret = true; if (fd < 0) ksft_exit_fail_msg("opening pagemap failed\n"); for (; size > 0 && ret; size -= pagesize, start += pagesize) if (pagemap_is_softdirty(fd, start)) ret = false; close(fd); return ret; } static void test_softdirty(void) { char *addr; int ret; ksft_print_msg("[RUN] %s\n", __func__); addr = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (addr == MAP_FAILED) ksft_exit_fail_msg("mmap failed\n"); /* Clear any softdirty bits. */ clear_softdirty(); ksft_test_result(range_is_not_softdirty(addr, SIZE), "range is not softdirty\n"); /* Populating READ should set softdirty. */ ret = madvise(addr, SIZE, MADV_POPULATE_READ); ksft_test_result(!ret, "MADV_POPULATE_READ\n"); ksft_test_result(range_is_not_softdirty(addr, SIZE), "range is not softdirty\n"); /* Populating WRITE should set softdirty. */ ret = madvise(addr, SIZE, MADV_POPULATE_WRITE); ksft_test_result(!ret, "MADV_POPULATE_WRITE\n"); ksft_test_result(range_is_softdirty(addr, SIZE), "range is softdirty\n"); munmap(addr, SIZE); } static int system_has_softdirty(void) { /* * There is no way to check if the kernel supports soft-dirty, other * than by writing to a page and seeing if the bit was set. But the * tests are intended to check that the bit gets set when it should, so * doing that check would turn a potentially legitimate fail into a * skip. Fortunately, we know for sure that arm64 does not support * soft-dirty. So for now, let's just use the arch as a corse guide. */ #if defined(__aarch64__) return 0; #else return 1; #endif } int main(int argc, char **argv) { int nr_tests = 16; int err; pagesize = getpagesize(); if (system_has_softdirty()) nr_tests += 5; ksft_print_header(); ksft_set_plan(nr_tests); sense_support(); test_prot_read(); test_prot_write(); test_holes(); test_populate_read(); test_populate_write(); if (system_has_softdirty()) test_softdirty(); err = ksft_get_fail_cnt(); if (err) ksft_exit_fail_msg("%d out of %d tests failed\n", err, ksft_test_num()); return ksft_exit_pass(); }