312 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * MADV_POPULATE_READ and MADV_POPULATE_WRITE tests4 *5 * Copyright 2021, Red Hat, Inc.6 *7 * Author(s): David Hildenbrand <david@redhat.com>8 */9#define _GNU_SOURCE10#include <stdlib.h>11#include <string.h>12#include <stdbool.h>13#include <stdint.h>14#include <unistd.h>15#include <errno.h>16#include <fcntl.h>17#include <linux/mman.h>18#include <sys/mman.h>19 20#include "../kselftest.h"21#include "vm_util.h"22 23/*24 * For now, we're using 2 MiB of private anonymous memory for all tests.25 */26#define SIZE (2 * 1024 * 1024)27 28static size_t pagesize;29 30static void sense_support(void)31{32 char *addr;33 int ret;34 35 addr = mmap(0, pagesize, PROT_READ | PROT_WRITE,36 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);37 if (!addr)38 ksft_exit_fail_msg("mmap failed\n");39 40 ret = madvise(addr, pagesize, MADV_POPULATE_READ);41 if (ret)42 ksft_exit_skip("MADV_POPULATE_READ is not available\n");43 44 ret = madvise(addr, pagesize, MADV_POPULATE_WRITE);45 if (ret)46 ksft_exit_skip("MADV_POPULATE_WRITE is not available\n");47 48 munmap(addr, pagesize);49}50 51static void test_prot_read(void)52{53 char *addr;54 int ret;55 56 ksft_print_msg("[RUN] %s\n", __func__);57 58 addr = mmap(0, SIZE, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);59 if (addr == MAP_FAILED)60 ksft_exit_fail_msg("mmap failed\n");61 62 ret = madvise(addr, SIZE, MADV_POPULATE_READ);63 ksft_test_result(!ret, "MADV_POPULATE_READ with PROT_READ\n");64 65 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);66 ksft_test_result(ret == -1 && errno == EINVAL,67 "MADV_POPULATE_WRITE with PROT_READ\n");68 69 munmap(addr, SIZE);70}71 72static void test_prot_write(void)73{74 char *addr;75 int ret;76 77 ksft_print_msg("[RUN] %s\n", __func__);78 79 addr = mmap(0, SIZE, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);80 if (addr == MAP_FAILED)81 ksft_exit_fail_msg("mmap failed\n");82 83 ret = madvise(addr, SIZE, MADV_POPULATE_READ);84 ksft_test_result(ret == -1 && errno == EINVAL,85 "MADV_POPULATE_READ with PROT_WRITE\n");86 87 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);88 ksft_test_result(!ret, "MADV_POPULATE_WRITE with PROT_WRITE\n");89 90 munmap(addr, SIZE);91}92 93static void test_holes(void)94{95 char *addr;96 int ret;97 98 ksft_print_msg("[RUN] %s\n", __func__);99 100 addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,101 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);102 if (addr == MAP_FAILED)103 ksft_exit_fail_msg("mmap failed\n");104 ret = munmap(addr + pagesize, pagesize);105 if (ret)106 ksft_exit_fail_msg("munmap failed\n");107 108 /* Hole in the middle */109 ret = madvise(addr, SIZE, MADV_POPULATE_READ);110 ksft_test_result(ret == -1 && errno == ENOMEM,111 "MADV_POPULATE_READ with holes in the middle\n");112 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);113 ksft_test_result(ret == -1 && errno == ENOMEM,114 "MADV_POPULATE_WRITE with holes in the middle\n");115 116 /* Hole at end */117 ret = madvise(addr, 2 * pagesize, MADV_POPULATE_READ);118 ksft_test_result(ret == -1 && errno == ENOMEM,119 "MADV_POPULATE_READ with holes at the end\n");120 ret = madvise(addr, 2 * pagesize, MADV_POPULATE_WRITE);121 ksft_test_result(ret == -1 && errno == ENOMEM,122 "MADV_POPULATE_WRITE with holes at the end\n");123 124 /* Hole at beginning */125 ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_READ);126 ksft_test_result(ret == -1 && errno == ENOMEM,127 "MADV_POPULATE_READ with holes at the beginning\n");128 ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_WRITE);129 ksft_test_result(ret == -1 && errno == ENOMEM,130 "MADV_POPULATE_WRITE with holes at the beginning\n");131 132 munmap(addr, SIZE);133}134 135static bool range_is_populated(char *start, ssize_t size)136{137 int fd = open("/proc/self/pagemap", O_RDONLY);138 bool ret = true;139 140 if (fd < 0)141 ksft_exit_fail_msg("opening pagemap failed\n");142 for (; size > 0 && ret; size -= pagesize, start += pagesize)143 if (!pagemap_is_populated(fd, start))144 ret = false;145 close(fd);146 return ret;147}148 149static bool range_is_not_populated(char *start, ssize_t size)150{151 int fd = open("/proc/self/pagemap", O_RDONLY);152 bool ret = true;153 154 if (fd < 0)155 ksft_exit_fail_msg("opening pagemap failed\n");156 for (; size > 0 && ret; size -= pagesize, start += pagesize)157 if (pagemap_is_populated(fd, start))158 ret = false;159 close(fd);160 return ret;161}162 163static void test_populate_read(void)164{165 char *addr;166 int ret;167 168 ksft_print_msg("[RUN] %s\n", __func__);169 170 addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,171 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);172 if (addr == MAP_FAILED)173 ksft_exit_fail_msg("mmap failed\n");174 ksft_test_result(range_is_not_populated(addr, SIZE),175 "range initially not populated\n");176 177 ret = madvise(addr, SIZE, MADV_POPULATE_READ);178 ksft_test_result(!ret, "MADV_POPULATE_READ\n");179 ksft_test_result(range_is_populated(addr, SIZE),180 "range is populated\n");181 182 munmap(addr, SIZE);183}184 185static void test_populate_write(void)186{187 char *addr;188 int ret;189 190 ksft_print_msg("[RUN] %s\n", __func__);191 192 addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,193 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);194 if (addr == MAP_FAILED)195 ksft_exit_fail_msg("mmap failed\n");196 ksft_test_result(range_is_not_populated(addr, SIZE),197 "range initially not populated\n");198 199 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);200 ksft_test_result(!ret, "MADV_POPULATE_WRITE\n");201 ksft_test_result(range_is_populated(addr, SIZE),202 "range is populated\n");203 204 munmap(addr, SIZE);205}206 207static bool range_is_softdirty(char *start, ssize_t size)208{209 int fd = open("/proc/self/pagemap", O_RDONLY);210 bool ret = true;211 212 if (fd < 0)213 ksft_exit_fail_msg("opening pagemap failed\n");214 for (; size > 0 && ret; size -= pagesize, start += pagesize)215 if (!pagemap_is_softdirty(fd, start))216 ret = false;217 close(fd);218 return ret;219}220 221static bool range_is_not_softdirty(char *start, ssize_t size)222{223 int fd = open("/proc/self/pagemap", O_RDONLY);224 bool ret = true;225 226 if (fd < 0)227 ksft_exit_fail_msg("opening pagemap failed\n");228 for (; size > 0 && ret; size -= pagesize, start += pagesize)229 if (pagemap_is_softdirty(fd, start))230 ret = false;231 close(fd);232 return ret;233}234 235static void test_softdirty(void)236{237 char *addr;238 int ret;239 240 ksft_print_msg("[RUN] %s\n", __func__);241 242 addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,243 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);244 if (addr == MAP_FAILED)245 ksft_exit_fail_msg("mmap failed\n");246 247 /* Clear any softdirty bits. */248 clear_softdirty();249 ksft_test_result(range_is_not_softdirty(addr, SIZE),250 "range is not softdirty\n");251 252 /* Populating READ should set softdirty. */253 ret = madvise(addr, SIZE, MADV_POPULATE_READ);254 ksft_test_result(!ret, "MADV_POPULATE_READ\n");255 ksft_test_result(range_is_not_softdirty(addr, SIZE),256 "range is not softdirty\n");257 258 /* Populating WRITE should set softdirty. */259 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);260 ksft_test_result(!ret, "MADV_POPULATE_WRITE\n");261 ksft_test_result(range_is_softdirty(addr, SIZE),262 "range is softdirty\n");263 264 munmap(addr, SIZE);265}266 267static int system_has_softdirty(void)268{269 /*270 * There is no way to check if the kernel supports soft-dirty, other271 * than by writing to a page and seeing if the bit was set. But the272 * tests are intended to check that the bit gets set when it should, so273 * doing that check would turn a potentially legitimate fail into a274 * skip. Fortunately, we know for sure that arm64 does not support275 * soft-dirty. So for now, let's just use the arch as a corse guide.276 */277#if defined(__aarch64__)278 return 0;279#else280 return 1;281#endif282}283 284int main(int argc, char **argv)285{286 int nr_tests = 16;287 int err;288 289 pagesize = getpagesize();290 291 if (system_has_softdirty())292 nr_tests += 5;293 294 ksft_print_header();295 ksft_set_plan(nr_tests);296 297 sense_support();298 test_prot_read();299 test_prot_write();300 test_holes();301 test_populate_read();302 test_populate_write();303 if (system_has_softdirty())304 test_softdirty();305 306 err = ksft_get_fail_cnt();307 if (err)308 ksft_exit_fail_msg("%d out of %d tests failed\n",309 err, ksft_test_num());310 ksft_exit_pass();311}312