1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Memory-failure functional tests. 4 * 5 * Author(s): Miaohe Lin <linmiaohe@huawei.com> 6 */ 7 8 #include "../kselftest_harness.h" 9 10 #include <sys/mman.h> 11 #include <linux/mman.h> 12 #include <linux/string.h> 13 #include <unistd.h> 14 #include <signal.h> 15 #include <setjmp.h> 16 #include <unistd.h> 17 #include <fcntl.h> 18 #include <sys/vfs.h> 19 #include <linux/magic.h> 20 #include <errno.h> 21 22 #include "vm_util.h" 23 24 enum inject_type { 25 MADV_HARD, 26 MADV_SOFT, 27 }; 28 29 enum result_type { 30 MADV_HARD_ANON, 31 MADV_HARD_CLEAN_PAGECACHE, 32 MADV_HARD_DIRTY_PAGECACHE, 33 MADV_SOFT_ANON, 34 MADV_SOFT_CLEAN_PAGECACHE, 35 MADV_SOFT_DIRTY_PAGECACHE, 36 }; 37 38 static jmp_buf signal_jmp_buf; 39 static siginfo_t siginfo; 40 const char *pagemap_proc = "/proc/self/pagemap"; 41 const char *kpageflags_proc = "/proc/kpageflags"; 42 43 FIXTURE(memory_failure) 44 { 45 unsigned long page_size; 46 unsigned long corrupted_size; 47 unsigned long pfn; 48 int pagemap_fd; 49 int kpageflags_fd; 50 bool triggered; 51 }; 52 53 FIXTURE_VARIANT(memory_failure) 54 { 55 enum inject_type type; 56 int (*inject)(FIXTURE_DATA(memory_failure) * self, void *vaddr); 57 }; 58 59 static int madv_hard_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr) 60 { 61 return madvise(vaddr, self->page_size, MADV_HWPOISON); 62 } 63 64 FIXTURE_VARIANT_ADD(memory_failure, madv_hard) 65 { 66 .type = MADV_HARD, 67 .inject = madv_hard_inject, 68 }; 69 70 static int madv_soft_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr) 71 { 72 return madvise(vaddr, self->page_size, MADV_SOFT_OFFLINE); 73 } 74 75 FIXTURE_VARIANT_ADD(memory_failure, madv_soft) 76 { 77 .type = MADV_SOFT, 78 .inject = madv_soft_inject, 79 }; 80 81 static void sigbus_action(int signo, siginfo_t *si, void *args) 82 { 83 memcpy(&siginfo, si, sizeof(siginfo_t)); 84 siglongjmp(signal_jmp_buf, 1); 85 } 86 87 static int setup_sighandler(void) 88 { 89 struct sigaction sa = { 90 .sa_sigaction = sigbus_action, 91 .sa_flags = SA_SIGINFO, 92 }; 93 94 return sigaction(SIGBUS, &sa, NULL); 95 } 96 97 FIXTURE_SETUP(memory_failure) 98 { 99 memset(self, 0, sizeof(*self)); 100 101 self->page_size = (unsigned long)sysconf(_SC_PAGESIZE); 102 103 memset(&siginfo, 0, sizeof(siginfo)); 104 if (setup_sighandler()) 105 SKIP(return, "setup sighandler failed.\n"); 106 107 self->pagemap_fd = open(pagemap_proc, O_RDONLY); 108 if (self->pagemap_fd == -1) 109 SKIP(return, "open %s failed.\n", pagemap_proc); 110 111 self->kpageflags_fd = open(kpageflags_proc, O_RDONLY); 112 if (self->kpageflags_fd == -1) 113 SKIP(return, "open %s failed.\n", kpageflags_proc); 114 } 115 116 static void teardown_sighandler(void) 117 { 118 struct sigaction sa = { 119 .sa_handler = SIG_DFL, 120 .sa_flags = SA_SIGINFO, 121 }; 122 123 sigaction(SIGBUS, &sa, NULL); 124 } 125 126 FIXTURE_TEARDOWN(memory_failure) 127 { 128 close(self->kpageflags_fd); 129 close(self->pagemap_fd); 130 teardown_sighandler(); 131 } 132 133 static void prepare(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self, 134 void *vaddr) 135 { 136 self->pfn = pagemap_get_pfn(self->pagemap_fd, vaddr); 137 ASSERT_NE(self->pfn, -1UL); 138 139 ASSERT_EQ(get_hardware_corrupted_size(&self->corrupted_size), 0); 140 } 141 142 static bool check_memory(void *vaddr, unsigned long size) 143 { 144 char buf[64]; 145 146 memset(buf, 0xce, sizeof(buf)); 147 while (size >= sizeof(buf)) { 148 if (memcmp(vaddr, buf, sizeof(buf))) 149 return false; 150 size -= sizeof(buf); 151 vaddr += sizeof(buf); 152 } 153 154 return true; 155 } 156 157 static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self, 158 void *vaddr, enum result_type type, int setjmp) 159 { 160 unsigned long size; 161 uint64_t pfn_flags; 162 163 switch (type) { 164 case MADV_SOFT_ANON: 165 case MADV_HARD_CLEAN_PAGECACHE: 166 case MADV_SOFT_CLEAN_PAGECACHE: 167 case MADV_SOFT_DIRTY_PAGECACHE: 168 /* It is not expected to receive a SIGBUS signal. */ 169 ASSERT_EQ(setjmp, 0); 170 171 /* The page content should remain unchanged. */ 172 ASSERT_TRUE(check_memory(vaddr, self->page_size)); 173 174 /* The backing pfn of addr should have changed. */ 175 ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn); 176 break; 177 case MADV_HARD_ANON: 178 case MADV_HARD_DIRTY_PAGECACHE: 179 /* The SIGBUS signal should have been received. */ 180 ASSERT_EQ(setjmp, 1); 181 182 /* Check if siginfo contains correct SIGBUS context. */ 183 ASSERT_EQ(siginfo.si_signo, SIGBUS); 184 ASSERT_EQ(siginfo.si_code, BUS_MCEERR_AR); 185 ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size); 186 ASSERT_EQ(siginfo.si_addr, vaddr); 187 188 /* XXX Check backing pte is hwpoison entry when supported. */ 189 ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr)); 190 break; 191 default: 192 SKIP(return, "unexpected inject type %d.\n", type); 193 } 194 195 /* Check if the value of HardwareCorrupted has increased. */ 196 ASSERT_EQ(get_hardware_corrupted_size(&size), 0); 197 ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024); 198 199 /* Check if HWPoison flag is set. */ 200 ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0); 201 ASSERT_EQ(pfn_flags & KPF_HWPOISON, KPF_HWPOISON); 202 } 203 204 static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self, 205 void *vaddr) 206 { 207 unsigned long size; 208 uint64_t pfn_flags; 209 210 ASSERT_EQ(unpoison_memory(self->pfn), 0); 211 212 /* Check if HWPoison flag is cleared. */ 213 ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0); 214 ASSERT_NE(pfn_flags & KPF_HWPOISON, KPF_HWPOISON); 215 216 /* Check if the value of HardwareCorrupted has decreased. */ 217 ASSERT_EQ(get_hardware_corrupted_size(&size), 0); 218 ASSERT_EQ(size, self->corrupted_size); 219 } 220 221 TEST_F(memory_failure, anon) 222 { 223 char *addr; 224 int ret; 225 226 addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE, 227 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 228 if (addr == MAP_FAILED) 229 SKIP(return, "mmap failed, not enough memory.\n"); 230 memset(addr, 0xce, self->page_size); 231 232 prepare(_metadata, self, addr); 233 234 ret = sigsetjmp(signal_jmp_buf, 1); 235 if (!self->triggered) { 236 self->triggered = true; 237 ASSERT_EQ(variant->inject(self, addr), 0); 238 FORCE_READ(*addr); 239 } 240 241 if (variant->type == MADV_HARD) 242 check(_metadata, self, addr, MADV_HARD_ANON, ret); 243 else 244 check(_metadata, self, addr, MADV_SOFT_ANON, ret); 245 246 cleanup(_metadata, self, addr); 247 248 ASSERT_EQ(munmap(addr, self->page_size), 0); 249 } 250 251 static int prepare_file(const char *fname, unsigned long size) 252 { 253 int fd; 254 255 fd = open(fname, O_RDWR | O_CREAT, 0664); 256 if (fd >= 0) { 257 unlink(fname); 258 ftruncate(fd, size); 259 } 260 return fd; 261 } 262 263 /* Borrowed from mm/gup_longterm.c. */ 264 static int get_fs_type(int fd) 265 { 266 struct statfs fs; 267 int ret; 268 269 do { 270 ret = fstatfs(fd, &fs); 271 } while (ret && errno == EINTR); 272 273 return ret ? 0 : (int)fs.f_type; 274 } 275 276 TEST_F(memory_failure, clean_pagecache) 277 { 278 int fd; 279 char *addr; 280 int ret; 281 int fs_type; 282 283 fd = prepare_file("./clean-page-cache-test-file", self->page_size); 284 if (fd < 0) 285 SKIP(return, "failed to open test file.\n"); 286 fs_type = get_fs_type(fd); 287 if (!fs_type || fs_type == TMPFS_MAGIC) 288 SKIP(return, "unsupported filesystem :%x\n", fs_type); 289 290 addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE, 291 MAP_SHARED, fd, 0); 292 if (addr == MAP_FAILED) 293 SKIP(return, "mmap failed, not enough memory.\n"); 294 memset(addr, 0xce, self->page_size); 295 fsync(fd); 296 297 prepare(_metadata, self, addr); 298 299 ret = sigsetjmp(signal_jmp_buf, 1); 300 if (!self->triggered) { 301 self->triggered = true; 302 ASSERT_EQ(variant->inject(self, addr), 0); 303 FORCE_READ(*addr); 304 } 305 306 if (variant->type == MADV_HARD) 307 check(_metadata, self, addr, MADV_HARD_CLEAN_PAGECACHE, ret); 308 else 309 check(_metadata, self, addr, MADV_SOFT_CLEAN_PAGECACHE, ret); 310 311 cleanup(_metadata, self, addr); 312 313 ASSERT_EQ(munmap(addr, self->page_size), 0); 314 315 ASSERT_EQ(close(fd), 0); 316 } 317 318 TEST_F(memory_failure, dirty_pagecache) 319 { 320 int fd; 321 char *addr; 322 int ret; 323 int fs_type; 324 325 fd = prepare_file("./dirty-page-cache-test-file", self->page_size); 326 if (fd < 0) 327 SKIP(return, "failed to open test file.\n"); 328 fs_type = get_fs_type(fd); 329 if (!fs_type || fs_type == TMPFS_MAGIC) 330 SKIP(return, "unsupported filesystem :%x\n", fs_type); 331 332 addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE, 333 MAP_SHARED, fd, 0); 334 if (addr == MAP_FAILED) 335 SKIP(return, "mmap failed, not enough memory.\n"); 336 memset(addr, 0xce, self->page_size); 337 338 prepare(_metadata, self, addr); 339 340 ret = sigsetjmp(signal_jmp_buf, 1); 341 if (!self->triggered) { 342 self->triggered = true; 343 ASSERT_EQ(variant->inject(self, addr), 0); 344 FORCE_READ(*addr); 345 } 346 347 if (variant->type == MADV_HARD) 348 check(_metadata, self, addr, MADV_HARD_DIRTY_PAGECACHE, ret); 349 else 350 check(_metadata, self, addr, MADV_SOFT_DIRTY_PAGECACHE, ret); 351 352 cleanup(_metadata, self, addr); 353 354 ASSERT_EQ(munmap(addr, self->page_size), 0); 355 356 ASSERT_EQ(close(fd), 0); 357 } 358 359 TEST_HARNESS_MAIN 360