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