1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #define _GNU_SOURCE |
4 | #include <stdlib.h> |
5 | #include <stdio.h> |
6 | #include <string.h> |
7 | |
8 | #include <linux/magic.h> |
9 | #include <sys/mman.h> |
10 | #include <sys/statfs.h> |
11 | #include <errno.h> |
12 | #include <stdbool.h> |
13 | |
14 | #include "../kselftest.h" |
15 | |
16 | #define PREFIX " ... " |
17 | #define ERROR_PREFIX " !!! " |
18 | |
19 | #define MAX_WRITE_READ_CHUNK_SIZE (getpagesize() * 16) |
20 | #define MAX(a, b) (((a) > (b)) ? (a) : (b)) |
21 | |
22 | enum test_status { |
23 | TEST_PASSED = 0, |
24 | TEST_FAILED = 1, |
25 | TEST_SKIPPED = 2, |
26 | }; |
27 | |
28 | static char *status_to_str(enum test_status status) |
29 | { |
30 | switch (status) { |
31 | case TEST_PASSED: |
32 | return "TEST_PASSED" ; |
33 | case TEST_FAILED: |
34 | return "TEST_FAILED" ; |
35 | case TEST_SKIPPED: |
36 | return "TEST_SKIPPED" ; |
37 | default: |
38 | return "TEST_???" ; |
39 | } |
40 | } |
41 | |
42 | static int setup_filemap(char *filemap, size_t len, size_t wr_chunk_size) |
43 | { |
44 | char iter = 0; |
45 | |
46 | for (size_t offset = 0; offset < len; |
47 | offset += wr_chunk_size) { |
48 | iter++; |
49 | memset(filemap + offset, iter, wr_chunk_size); |
50 | } |
51 | |
52 | return 0; |
53 | } |
54 | |
55 | static bool verify_chunk(char *buf, size_t len, char val) |
56 | { |
57 | size_t i; |
58 | |
59 | for (i = 0; i < len; ++i) { |
60 | if (buf[i] != val) { |
61 | printf(PREFIX ERROR_PREFIX "check fail: buf[%lu] = %u != %u\n" , |
62 | i, buf[i], val); |
63 | return false; |
64 | } |
65 | } |
66 | |
67 | return true; |
68 | } |
69 | |
70 | static bool seek_read_hugepage_filemap(int fd, size_t len, size_t wr_chunk_size, |
71 | off_t offset, size_t expected) |
72 | { |
73 | char buf[MAX_WRITE_READ_CHUNK_SIZE]; |
74 | ssize_t ret_count = 0; |
75 | ssize_t total_ret_count = 0; |
76 | char val = offset / wr_chunk_size + offset % wr_chunk_size; |
77 | |
78 | printf(PREFIX PREFIX "init val=%u with offset=0x%lx\n" , val, offset); |
79 | printf(PREFIX PREFIX "expect to read 0x%lx bytes of data in total\n" , |
80 | expected); |
81 | if (lseek(fd, offset, SEEK_SET) < 0) { |
82 | perror(PREFIX ERROR_PREFIX "seek failed" ); |
83 | return false; |
84 | } |
85 | |
86 | while (offset + total_ret_count < len) { |
87 | ret_count = read(fd, buf, wr_chunk_size); |
88 | if (ret_count == 0) { |
89 | printf(PREFIX PREFIX "read reach end of the file\n" ); |
90 | break; |
91 | } else if (ret_count < 0) { |
92 | perror(PREFIX ERROR_PREFIX "read failed" ); |
93 | break; |
94 | } |
95 | ++val; |
96 | if (!verify_chunk(buf, ret_count, val)) |
97 | return false; |
98 | |
99 | total_ret_count += ret_count; |
100 | } |
101 | printf(PREFIX PREFIX "actually read 0x%lx bytes of data in total\n" , |
102 | total_ret_count); |
103 | |
104 | return total_ret_count == expected; |
105 | } |
106 | |
107 | static bool read_hugepage_filemap(int fd, size_t len, |
108 | size_t wr_chunk_size, size_t expected) |
109 | { |
110 | char buf[MAX_WRITE_READ_CHUNK_SIZE]; |
111 | ssize_t ret_count = 0; |
112 | ssize_t total_ret_count = 0; |
113 | char val = 0; |
114 | |
115 | printf(PREFIX PREFIX "expect to read 0x%lx bytes of data in total\n" , |
116 | expected); |
117 | while (total_ret_count < len) { |
118 | ret_count = read(fd, buf, wr_chunk_size); |
119 | if (ret_count == 0) { |
120 | printf(PREFIX PREFIX "read reach end of the file\n" ); |
121 | break; |
122 | } else if (ret_count < 0) { |
123 | perror(PREFIX ERROR_PREFIX "read failed" ); |
124 | break; |
125 | } |
126 | ++val; |
127 | if (!verify_chunk(buf, ret_count, val)) |
128 | return false; |
129 | |
130 | total_ret_count += ret_count; |
131 | } |
132 | printf(PREFIX PREFIX "actually read 0x%lx bytes of data in total\n" , |
133 | total_ret_count); |
134 | |
135 | return total_ret_count == expected; |
136 | } |
137 | |
138 | static enum test_status |
139 | test_hugetlb_read(int fd, size_t len, size_t wr_chunk_size) |
140 | { |
141 | enum test_status status = TEST_SKIPPED; |
142 | char *filemap = NULL; |
143 | |
144 | if (ftruncate(fd, len) < 0) { |
145 | perror(PREFIX ERROR_PREFIX "ftruncate failed" ); |
146 | return status; |
147 | } |
148 | |
149 | filemap = mmap(NULL, len, PROT_READ | PROT_WRITE, |
150 | MAP_SHARED | MAP_POPULATE, fd, 0); |
151 | if (filemap == MAP_FAILED) { |
152 | perror(PREFIX ERROR_PREFIX "mmap for primary mapping failed" ); |
153 | goto done; |
154 | } |
155 | |
156 | setup_filemap(filemap, len, wr_chunk_size); |
157 | status = TEST_FAILED; |
158 | |
159 | if (read_hugepage_filemap(fd, len, wr_chunk_size, len)) |
160 | status = TEST_PASSED; |
161 | |
162 | munmap(filemap, len); |
163 | done: |
164 | if (ftruncate(fd, 0) < 0) { |
165 | perror(PREFIX ERROR_PREFIX "ftruncate back to 0 failed" ); |
166 | status = TEST_FAILED; |
167 | } |
168 | |
169 | return status; |
170 | } |
171 | |
172 | static enum test_status |
173 | test_hugetlb_read_hwpoison(int fd, size_t len, size_t wr_chunk_size, |
174 | bool skip_hwpoison_page) |
175 | { |
176 | enum test_status status = TEST_SKIPPED; |
177 | char *filemap = NULL; |
178 | char *hwp_addr = NULL; |
179 | const unsigned long pagesize = getpagesize(); |
180 | |
181 | if (ftruncate(fd, len) < 0) { |
182 | perror(PREFIX ERROR_PREFIX "ftruncate failed" ); |
183 | return status; |
184 | } |
185 | |
186 | filemap = mmap(NULL, len, PROT_READ | PROT_WRITE, |
187 | MAP_SHARED | MAP_POPULATE, fd, 0); |
188 | if (filemap == MAP_FAILED) { |
189 | perror(PREFIX ERROR_PREFIX "mmap for primary mapping failed" ); |
190 | goto done; |
191 | } |
192 | |
193 | setup_filemap(filemap, len, wr_chunk_size); |
194 | status = TEST_FAILED; |
195 | |
196 | /* |
197 | * Poisoned hugetlb page layout (assume hugepagesize=2MB): |
198 | * |<---------------------- 1MB ---------------------->| |
199 | * |<---- healthy page ---->|<---- HWPOISON page ----->| |
200 | * |<------------------- (1MB - 8KB) ----------------->| |
201 | */ |
202 | hwp_addr = filemap + len / 2 + pagesize; |
203 | if (madvise(hwp_addr, pagesize, MADV_HWPOISON) < 0) { |
204 | perror(PREFIX ERROR_PREFIX "MADV_HWPOISON failed" ); |
205 | goto unmap; |
206 | } |
207 | |
208 | if (!skip_hwpoison_page) { |
209 | /* |
210 | * Userspace should be able to read (1MB + 1 page) from |
211 | * the beginning of the HWPOISONed hugepage. |
212 | */ |
213 | if (read_hugepage_filemap(fd, len, wr_chunk_size, |
214 | len / 2 + pagesize)) |
215 | status = TEST_PASSED; |
216 | } else { |
217 | /* |
218 | * Userspace should be able to read (1MB - 2 pages) from |
219 | * HWPOISONed hugepage. |
220 | */ |
221 | if (seek_read_hugepage_filemap(fd, len, wr_chunk_size, |
222 | len / 2 + MAX(2 * pagesize, wr_chunk_size), |
223 | len / 2 - MAX(2 * pagesize, wr_chunk_size))) |
224 | status = TEST_PASSED; |
225 | } |
226 | |
227 | unmap: |
228 | munmap(filemap, len); |
229 | done: |
230 | if (ftruncate(fd, 0) < 0) { |
231 | perror(PREFIX ERROR_PREFIX "ftruncate back to 0 failed" ); |
232 | status = TEST_FAILED; |
233 | } |
234 | |
235 | return status; |
236 | } |
237 | |
238 | static int create_hugetlbfs_file(struct statfs *file_stat) |
239 | { |
240 | int fd; |
241 | |
242 | fd = memfd_create("hugetlb_tmp" , MFD_HUGETLB); |
243 | if (fd < 0) { |
244 | perror(PREFIX ERROR_PREFIX "could not open hugetlbfs file" ); |
245 | return -1; |
246 | } |
247 | |
248 | memset(file_stat, 0, sizeof(*file_stat)); |
249 | if (fstatfs(fd, file_stat)) { |
250 | perror(PREFIX ERROR_PREFIX "fstatfs failed" ); |
251 | goto close; |
252 | } |
253 | if (file_stat->f_type != HUGETLBFS_MAGIC) { |
254 | printf(PREFIX ERROR_PREFIX "not hugetlbfs file\n" ); |
255 | goto close; |
256 | } |
257 | |
258 | return fd; |
259 | close: |
260 | close(fd); |
261 | return -1; |
262 | } |
263 | |
264 | int main(void) |
265 | { |
266 | int fd; |
267 | struct statfs file_stat; |
268 | enum test_status status; |
269 | /* Test read() in different granularity. */ |
270 | size_t wr_chunk_sizes[] = { |
271 | getpagesize() / 2, getpagesize(), |
272 | getpagesize() * 2, getpagesize() * 4 |
273 | }; |
274 | size_t i; |
275 | |
276 | for (i = 0; i < ARRAY_SIZE(wr_chunk_sizes); ++i) { |
277 | printf("Write/read chunk size=0x%lx\n" , |
278 | wr_chunk_sizes[i]); |
279 | |
280 | fd = create_hugetlbfs_file(&file_stat); |
281 | if (fd < 0) |
282 | goto create_failure; |
283 | printf(PREFIX "HugeTLB read regression test...\n" ); |
284 | status = test_hugetlb_read(fd, file_stat.f_bsize, |
285 | wr_chunk_sizes[i]); |
286 | printf(PREFIX "HugeTLB read regression test...%s\n" , |
287 | status_to_str(status)); |
288 | close(fd); |
289 | if (status == TEST_FAILED) |
290 | return -1; |
291 | |
292 | fd = create_hugetlbfs_file(&file_stat); |
293 | if (fd < 0) |
294 | goto create_failure; |
295 | printf(PREFIX "HugeTLB read HWPOISON test...\n" ); |
296 | status = test_hugetlb_read_hwpoison(fd, file_stat.f_bsize, |
297 | wr_chunk_sizes[i], false); |
298 | printf(PREFIX "HugeTLB read HWPOISON test...%s\n" , |
299 | status_to_str(status)); |
300 | close(fd); |
301 | if (status == TEST_FAILED) |
302 | return -1; |
303 | |
304 | fd = create_hugetlbfs_file(&file_stat); |
305 | if (fd < 0) |
306 | goto create_failure; |
307 | printf(PREFIX "HugeTLB seek then read HWPOISON test...\n" ); |
308 | status = test_hugetlb_read_hwpoison(fd, file_stat.f_bsize, |
309 | wr_chunk_sizes[i], true); |
310 | printf(PREFIX "HugeTLB seek then read HWPOISON test...%s\n" , |
311 | status_to_str(status)); |
312 | close(fd); |
313 | if (status == TEST_FAILED) |
314 | return -1; |
315 | } |
316 | |
317 | return 0; |
318 | |
319 | create_failure: |
320 | printf(ERROR_PREFIX "Abort test: failed to create hugetlbfs file\n" ); |
321 | return -1; |
322 | } |
323 | |