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