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 <signal.h> 14 #include <setjmp.h> 15 #include <unistd.h> 16 #include <fcntl.h> 17 18 #include "vm_util.h" 19 20 enum inject_type { 21 MADV_HARD, 22 MADV_SOFT, 23 }; 24 25 enum result_type { 26 MADV_HARD_ANON, 27 MADV_SOFT_ANON, 28 }; 29 30 static jmp_buf signal_jmp_buf; 31 static siginfo_t siginfo; 32 const char *pagemap_proc = "/proc/self/pagemap"; 33 const char *kpageflags_proc = "/proc/kpageflags"; 34 35 FIXTURE(memory_failure) 36 { 37 unsigned long page_size; 38 unsigned long corrupted_size; 39 unsigned long pfn; 40 int pagemap_fd; 41 int kpageflags_fd; 42 bool triggered; 43 }; 44 45 FIXTURE_VARIANT(memory_failure) 46 { 47 enum inject_type type; 48 int (*inject)(FIXTURE_DATA(memory_failure) * self, void *vaddr); 49 }; 50 51 static int madv_hard_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr) 52 { 53 return madvise(vaddr, self->page_size, MADV_HWPOISON); 54 } 55 56 FIXTURE_VARIANT_ADD(memory_failure, madv_hard) 57 { 58 .type = MADV_HARD, 59 .inject = madv_hard_inject, 60 }; 61 62 static int madv_soft_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr) 63 { 64 return madvise(vaddr, self->page_size, MADV_SOFT_OFFLINE); 65 } 66 67 FIXTURE_VARIANT_ADD(memory_failure, madv_soft) 68 { 69 .type = MADV_SOFT, 70 .inject = madv_soft_inject, 71 }; 72 73 static void sigbus_action(int signo, siginfo_t *si, void *args) 74 { 75 memcpy(&siginfo, si, sizeof(siginfo_t)); 76 siglongjmp(signal_jmp_buf, 1); 77 } 78 79 static int setup_sighandler(void) 80 { 81 struct sigaction sa = { 82 .sa_sigaction = sigbus_action, 83 .sa_flags = SA_SIGINFO, 84 }; 85 86 return sigaction(SIGBUS, &sa, NULL); 87 } 88 89 FIXTURE_SETUP(memory_failure) 90 { 91 memset(self, 0, sizeof(*self)); 92 93 self->page_size = (unsigned long)sysconf(_SC_PAGESIZE); 94 95 memset(&siginfo, 0, sizeof(siginfo)); 96 if (setup_sighandler()) 97 SKIP(return, "setup sighandler failed.\n"); 98 99 self->pagemap_fd = open(pagemap_proc, O_RDONLY); 100 if (self->pagemap_fd == -1) 101 SKIP(return, "open %s failed.\n", pagemap_proc); 102 103 self->kpageflags_fd = open(kpageflags_proc, O_RDONLY); 104 if (self->kpageflags_fd == -1) 105 SKIP(return, "open %s failed.\n", kpageflags_proc); 106 } 107 108 static void teardown_sighandler(void) 109 { 110 struct sigaction sa = { 111 .sa_handler = SIG_DFL, 112 .sa_flags = SA_SIGINFO, 113 }; 114 115 sigaction(SIGBUS, &sa, NULL); 116 } 117 118 FIXTURE_TEARDOWN(memory_failure) 119 { 120 close(self->kpageflags_fd); 121 close(self->pagemap_fd); 122 teardown_sighandler(); 123 } 124 125 static void prepare(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self, 126 void *vaddr) 127 { 128 self->pfn = pagemap_get_pfn(self->pagemap_fd, vaddr); 129 ASSERT_NE(self->pfn, -1UL); 130 131 ASSERT_EQ(get_hardware_corrupted_size(&self->corrupted_size), 0); 132 } 133 134 static bool check_memory(void *vaddr, unsigned long size) 135 { 136 char buf[64]; 137 138 memset(buf, 0xce, sizeof(buf)); 139 while (size >= sizeof(buf)) { 140 if (memcmp(vaddr, buf, sizeof(buf))) 141 return false; 142 size -= sizeof(buf); 143 vaddr += sizeof(buf); 144 } 145 146 return true; 147 } 148 149 static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self, 150 void *vaddr, enum result_type type, int setjmp) 151 { 152 unsigned long size; 153 uint64_t pfn_flags; 154 155 switch (type) { 156 case MADV_SOFT_ANON: 157 /* It is not expected to receive a SIGBUS signal. */ 158 ASSERT_EQ(setjmp, 0); 159 160 /* The page content should remain unchanged. */ 161 ASSERT_TRUE(check_memory(vaddr, self->page_size)); 162 163 /* The backing pfn of addr should have changed. */ 164 ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn); 165 break; 166 case MADV_HARD_ANON: 167 /* The SIGBUS signal should have been received. */ 168 ASSERT_EQ(setjmp, 1); 169 170 /* Check if siginfo contains correct SIGBUS context. */ 171 ASSERT_EQ(siginfo.si_signo, SIGBUS); 172 ASSERT_EQ(siginfo.si_code, BUS_MCEERR_AR); 173 ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size); 174 ASSERT_EQ(siginfo.si_addr, vaddr); 175 176 /* XXX Check backing pte is hwpoison entry when supported. */ 177 ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr)); 178 break; 179 default: 180 SKIP(return, "unexpected inject type %d.\n", type); 181 } 182 183 /* Check if the value of HardwareCorrupted has increased. */ 184 ASSERT_EQ(get_hardware_corrupted_size(&size), 0); 185 ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024); 186 187 /* Check if HWPoison flag is set. */ 188 ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0); 189 ASSERT_EQ(pfn_flags & KPF_HWPOISON, KPF_HWPOISON); 190 } 191 192 static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self, 193 void *vaddr) 194 { 195 unsigned long size; 196 uint64_t pfn_flags; 197 198 ASSERT_EQ(unpoison_memory(self->pfn), 0); 199 200 /* Check if HWPoison flag is cleared. */ 201 ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0); 202 ASSERT_NE(pfn_flags & KPF_HWPOISON, KPF_HWPOISON); 203 204 /* Check if the value of HardwareCorrupted has decreased. */ 205 ASSERT_EQ(get_hardware_corrupted_size(&size), 0); 206 ASSERT_EQ(size, self->corrupted_size); 207 } 208 209 TEST_F(memory_failure, anon) 210 { 211 char *addr; 212 int ret; 213 214 addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE, 215 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 216 if (addr == MAP_FAILED) 217 SKIP(return, "mmap failed, not enough memory.\n"); 218 memset(addr, 0xce, self->page_size); 219 220 prepare(_metadata, self, addr); 221 222 ret = sigsetjmp(signal_jmp_buf, 1); 223 if (!self->triggered) { 224 self->triggered = true; 225 ASSERT_EQ(variant->inject(self, addr), 0); 226 FORCE_READ(*addr); 227 } 228 229 if (variant->type == MADV_HARD) 230 check(_metadata, self, addr, MADV_HARD_ANON, ret); 231 else 232 check(_metadata, self, addr, MADV_SOFT_ANON, ret); 233 234 cleanup(_metadata, self, addr); 235 236 ASSERT_EQ(munmap(addr, self->page_size), 0); 237 } 238 239 TEST_HARNESS_MAIN 240