1// SPDX-License-Identifier: GPL-2.0
2#include <stdio.h>
3#include <string.h>
4#include <stdbool.h>
5#include <fcntl.h>
6#include <stdint.h>
7#include <malloc.h>
8#include <sys/mman.h>
9#include "../kselftest.h"
10#include "vm_util.h"
11
12#define PAGEMAP_FILE_PATH "/proc/self/pagemap"
13#define TEST_ITERATIONS 10000
14
15static void test_simple(int pagemap_fd, int pagesize)
16{
17 int i;
18 char *map;
19
20 map = aligned_alloc(pagesize, pagesize);
21 if (!map)
22 ksft_exit_fail_msg(msg: "mmap failed\n");
23
24 clear_softdirty();
25
26 for (i = 0 ; i < TEST_ITERATIONS; i++) {
27 if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
28 ksft_print_msg(msg: "dirty bit was 1, but should be 0 (i=%d)\n", i);
29 break;
30 }
31
32 clear_softdirty();
33 // Write something to the page to get the dirty bit enabled on the page
34 map[0]++;
35
36 if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
37 ksft_print_msg(msg: "dirty bit was 0, but should be 1 (i=%d)\n", i);
38 break;
39 }
40
41 clear_softdirty();
42 }
43 free(map);
44
45 ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__);
46}
47
48static void test_vma_reuse(int pagemap_fd, int pagesize)
49{
50 char *map, *map2;
51
52 map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
53 if (map == MAP_FAILED)
54 ksft_exit_fail_msg(msg: "mmap failed");
55
56 // The kernel always marks new regions as soft dirty
57 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
58 "Test %s dirty bit of allocated page\n", __func__);
59
60 clear_softdirty();
61 munmap(map, pagesize);
62
63 map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
64 if (map2 == MAP_FAILED)
65 ksft_exit_fail_msg(msg: "mmap failed");
66
67 // Dirty bit is set for new regions even if they are reused
68 if (map == map2)
69 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
70 "Test %s dirty bit of reused address page\n", __func__);
71 else
72 ksft_test_result_skip(msg: "Test %s dirty bit of reused address page\n", __func__);
73
74 munmap(map2, pagesize);
75}
76
77static void test_hugepage(int pagemap_fd, int pagesize)
78{
79 char *map;
80 int i, ret;
81 size_t hpage_len = read_pmd_pagesize();
82
83 if (!hpage_len)
84 ksft_exit_fail_msg(msg: "Reading PMD pagesize failed");
85
86 map = memalign(hpage_len, hpage_len);
87 if (!map)
88 ksft_exit_fail_msg(msg: "memalign failed\n");
89
90 ret = madvise(map, hpage_len, MADV_HUGEPAGE);
91 if (ret)
92 ksft_exit_fail_msg(msg: "madvise failed %d\n", ret);
93
94 for (i = 0; i < hpage_len; i++)
95 map[i] = (char)i;
96
97 if (check_huge_anon(map, 1, hpage_len)) {
98 ksft_test_result_pass(msg: "Test %s huge page allocation\n", __func__);
99
100 clear_softdirty();
101 for (i = 0 ; i < TEST_ITERATIONS ; i++) {
102 if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
103 ksft_print_msg(msg: "dirty bit was 1, but should be 0 (i=%d)\n", i);
104 break;
105 }
106
107 clear_softdirty();
108 // Write something to the page to get the dirty bit enabled on the page
109 map[0]++;
110
111 if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
112 ksft_print_msg(msg: "dirty bit was 0, but should be 1 (i=%d)\n", i);
113 break;
114 }
115 clear_softdirty();
116 }
117
118 ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__);
119 } else {
120 // hugepage allocation failed. skip these tests
121 ksft_test_result_skip(msg: "Test %s huge page allocation\n", __func__);
122 ksft_test_result_skip(msg: "Test %s huge page dirty bit\n", __func__);
123 }
124 free(map);
125}
126
127static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
128{
129 const char *type[] = {"file", "anon"};
130 const char *fname = "./soft-dirty-test-file";
131 int test_fd;
132 char *map;
133
134 if (anon) {
135 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
136 MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
137 if (!map)
138 ksft_exit_fail_msg(msg: "anon mmap failed\n");
139 } else {
140 test_fd = open(fname, O_RDWR | O_CREAT, 0664);
141 if (test_fd < 0) {
142 ksft_test_result_skip(msg: "Test %s open() file failed\n", __func__);
143 return;
144 }
145 unlink(fname);
146 ftruncate(test_fd, pagesize);
147 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
148 MAP_SHARED, test_fd, 0);
149 if (!map)
150 ksft_exit_fail_msg(msg: "file mmap failed\n");
151 }
152
153 *map = 1;
154 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
155 "Test %s-%s dirty bit of new written page\n",
156 __func__, type[anon]);
157 clear_softdirty();
158 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
159 "Test %s-%s soft-dirty clear after clear_refs\n",
160 __func__, type[anon]);
161 mprotect(map, pagesize, PROT_READ);
162 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
163 "Test %s-%s soft-dirty clear after marking RO\n",
164 __func__, type[anon]);
165 mprotect(map, pagesize, PROT_READ|PROT_WRITE);
166 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
167 "Test %s-%s soft-dirty clear after marking RW\n",
168 __func__, type[anon]);
169 *map = 2;
170 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
171 "Test %s-%s soft-dirty after rewritten\n",
172 __func__, type[anon]);
173
174 munmap(map, pagesize);
175
176 if (!anon)
177 close(test_fd);
178}
179
180static void test_mprotect_anon(int pagemap_fd, int pagesize)
181{
182 test_mprotect(pagemap_fd, pagesize, true);
183}
184
185static void test_mprotect_file(int pagemap_fd, int pagesize)
186{
187 test_mprotect(pagemap_fd, pagesize, false);
188}
189
190int main(int argc, char **argv)
191{
192 int pagemap_fd;
193 int pagesize;
194
195 ksft_print_header();
196 ksft_set_plan(plan: 15);
197
198 pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
199 if (pagemap_fd < 0)
200 ksft_exit_fail_msg(msg: "Failed to open %s\n", PAGEMAP_FILE_PATH);
201
202 pagesize = getpagesize();
203
204 test_simple(pagemap_fd, pagesize);
205 test_vma_reuse(pagemap_fd, pagesize);
206 test_hugepage(pagemap_fd, pagesize);
207 test_mprotect_anon(pagemap_fd, pagesize);
208 test_mprotect_file(pagemap_fd, pagesize);
209
210 close(pagemap_fd);
211
212 return ksft_exit_pass();
213}
214

source code of linux/tools/testing/selftests/mm/soft-dirty.c