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
FIXTURE(memory_failure)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
FIXTURE_VARIANT(memory_failure)53 FIXTURE_VARIANT(memory_failure)
54 {
55 enum inject_type type;
56 int (*inject)(FIXTURE_DATA(memory_failure) * self, void *vaddr);
57 };
58
madv_hard_inject(FIXTURE_DATA (memory_failure)* self,void * vaddr)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
FIXTURE_VARIANT_ADD(memory_failure,madv_hard)64 FIXTURE_VARIANT_ADD(memory_failure, madv_hard)
65 {
66 .type = MADV_HARD,
67 .inject = madv_hard_inject,
68 };
69
madv_soft_inject(FIXTURE_DATA (memory_failure)* self,void * vaddr)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
FIXTURE_VARIANT_ADD(memory_failure,madv_soft)75 FIXTURE_VARIANT_ADD(memory_failure, madv_soft)
76 {
77 .type = MADV_SOFT,
78 .inject = madv_soft_inject,
79 };
80
sigbus_action(int signo,siginfo_t * si,void * args)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
setup_sighandler(void)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
FIXTURE_SETUP(memory_failure)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
teardown_sighandler(void)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
FIXTURE_TEARDOWN(memory_failure)126 FIXTURE_TEARDOWN(memory_failure)
127 {
128 close(self->kpageflags_fd);
129 close(self->pagemap_fd);
130 teardown_sighandler();
131 }
132
prepare(struct __test_metadata * _metadata,FIXTURE_DATA (memory_failure)* self,void * vaddr)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
check_memory(void * vaddr,unsigned long size)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
check(struct __test_metadata * _metadata,FIXTURE_DATA (memory_failure)* self,void * vaddr,enum result_type type,int setjmp)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
cleanup(struct __test_metadata * _metadata,FIXTURE_DATA (memory_failure)* self,void * vaddr)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
TEST_F(memory_failure,anon)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
prepare_file(const char * fname,unsigned long size)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. */
get_fs_type(int fd)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
TEST_F(memory_failure,clean_pagecache)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
TEST_F(memory_failure,dirty_pagecache)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