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