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