1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * MADV_POPULATE_READ and MADV_POPULATE_WRITE tests
4 *
5 * Copyright 2021, Red Hat, Inc.
6 *
7 * Author(s): David Hildenbrand <david@redhat.com>
8 */
9#define _GNU_SOURCE
10#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, other
271	 * than by writing to a page and seeing if the bit was set. But the
272	 * tests are intended to check that the bit gets set when it should, so
273	 * doing that check would turn a potentially legitimate fail into a
274	 * skip. Fortunately, we know for sure that arm64 does not support
275	 * soft-dirty. So for now, let's just use the arch as a corse guide.
276	 */
277#if defined(__aarch64__)
278	return 0;
279#else
280	return 1;
281#endif
282}
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	return ksft_exit_pass();
311}
312