xref: /linux/tools/testing/selftests/mm/memory-failure.c (revision 18fbf5151d2c0bfe433c7428eef03cabf5fdb2fa)
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 injection_attempted;
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 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 	case MADV_HARD_CLEAN_PAGECACHE:
158 	case MADV_SOFT_CLEAN_PAGECACHE:
159 	case MADV_SOFT_DIRTY_PAGECACHE:
160 		/* It is not expected to receive a SIGBUS signal. */
161 		ASSERT_EQ(setjmp, 0);
162 
163 		/* The page content should remain unchanged. */
164 		ASSERT_TRUE(check_memory(vaddr, self->page_size));
165 
166 		/* The backing pfn of addr should have changed. */
167 		ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn);
168 		break;
169 	case MADV_HARD_ANON:
170 	case MADV_HARD_DIRTY_PAGECACHE:
171 		/* The SIGBUS signal should have been received. */
172 		ASSERT_EQ(setjmp, 1);
173 
174 		/* Check if siginfo contains correct SIGBUS context. */
175 		ASSERT_EQ(siginfo.si_signo, SIGBUS);
176 		ASSERT_EQ(siginfo.si_code, BUS_MCEERR_AR);
177 		ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size);
178 		ASSERT_EQ(siginfo.si_addr, vaddr);
179 
180 		/* XXX Check backing pte is hwpoison entry when supported. */
181 		ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
182 		break;
183 	default:
184 		SKIP(return, "unexpected inject type %d.\n", type);
185 	}
186 
187 	/* Check if the value of HardwareCorrupted has increased. */
188 	ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
189 	ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
190 
191 	/* Check if HWPoison flag is set. */
192 	ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
193 	ASSERT_EQ(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
194 }
195 
196 static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self)
197 {
198 	unsigned long size;
199 	uint64_t pfn_flags;
200 
201 	ASSERT_EQ(unpoison_memory(self->pfn), 0);
202 
203 	/* Check if HWPoison flag is cleared. */
204 	ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
205 	ASSERT_NE(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
206 
207 	/* Check if the value of HardwareCorrupted has decreased. */
208 	ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
209 	ASSERT_EQ(size, self->corrupted_size);
210 }
211 
212 FIXTURE_TEARDOWN(memory_failure)
213 {
214 	/*
215 	 * Injection may poison the page before failing or delivering SIGBUS, so
216 	 * clean up after every injection attempt.
217 	 */
218 	if (self->injection_attempted)
219 		cleanup(_metadata, self);
220 
221 	close(self->kpageflags_fd);
222 	close(self->pagemap_fd);
223 	teardown_sighandler();
224 }
225 
226 TEST_F(memory_failure, anon)
227 {
228 	char *addr;
229 	int ret;
230 
231 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
232 		    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
233 	if (addr == MAP_FAILED)
234 		SKIP(return, "mmap failed, not enough memory.\n");
235 	memset(addr, 0xce, self->page_size);
236 
237 	prepare(_metadata, self, addr);
238 
239 	ret = sigsetjmp(signal_jmp_buf, 1);
240 	if (!self->injection_attempted) {
241 		self->injection_attempted = true;
242 		ASSERT_EQ(variant->inject(self, addr), 0);
243 		FORCE_READ(*addr);
244 	}
245 
246 	if (variant->type == MADV_HARD)
247 		check(_metadata, self, addr, MADV_HARD_ANON, ret);
248 	else
249 		check(_metadata, self, addr, MADV_SOFT_ANON, ret);
250 
251 	ASSERT_EQ(munmap(addr, self->page_size), 0);
252 }
253 
254 static int prepare_file(const char *fname, unsigned long size)
255 {
256 	int fd;
257 
258 	fd = open(fname, O_RDWR | O_CREAT, 0664);
259 	if (fd >= 0) {
260 		unlink(fname);
261 		ftruncate(fd, size);
262 	}
263 	return fd;
264 }
265 
266 /* Borrowed from mm/gup_longterm.c. */
267 static int get_fs_type(int fd)
268 {
269 	struct statfs fs;
270 	int ret;
271 
272 	do {
273 		ret = fstatfs(fd, &fs);
274 	} while (ret && errno == EINTR);
275 
276 	return ret ? 0 : (int)fs.f_type;
277 }
278 
279 TEST_F(memory_failure, clean_pagecache)
280 {
281 	int fd;
282 	char *addr;
283 	int ret;
284 	int fs_type;
285 
286 	fd = prepare_file("./clean-page-cache-test-file", self->page_size);
287 	if (fd < 0)
288 		SKIP(return, "failed to open test file.\n");
289 	fs_type = get_fs_type(fd);
290 	if (!fs_type || fs_type == TMPFS_MAGIC) {
291 		close(fd);
292 		SKIP(return, "unsupported filesystem :%x\n", fs_type);
293 	}
294 
295 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
296 		    MAP_SHARED, fd, 0);
297 	if (addr == MAP_FAILED)
298 		SKIP(return, "mmap failed, not enough memory.\n");
299 	memset(addr, 0xce, self->page_size);
300 	fsync(fd);
301 
302 	prepare(_metadata, self, addr);
303 
304 	ret = sigsetjmp(signal_jmp_buf, 1);
305 	if (!self->injection_attempted) {
306 		self->injection_attempted = true;
307 		ASSERT_EQ(variant->inject(self, addr), 0);
308 		FORCE_READ(*addr);
309 	}
310 
311 	if (variant->type == MADV_HARD)
312 		check(_metadata, self, addr, MADV_HARD_CLEAN_PAGECACHE, ret);
313 	else
314 		check(_metadata, self, addr, MADV_SOFT_CLEAN_PAGECACHE, ret);
315 
316 	ASSERT_EQ(munmap(addr, self->page_size), 0);
317 
318 	ASSERT_EQ(close(fd), 0);
319 }
320 
321 TEST_F(memory_failure, dirty_pagecache)
322 {
323 	int fd;
324 	char *addr;
325 	int ret;
326 	int fs_type;
327 
328 	fd = prepare_file("./dirty-page-cache-test-file", self->page_size);
329 	if (fd < 0)
330 		SKIP(return, "failed to open test file.\n");
331 	fs_type = get_fs_type(fd);
332 	/*
333 	 * MADV_HARD poisoning of dirty page-cache data records an expected
334 	 * -EIO in the file mapping. NFS reports this error on close(), so
335 	 * skip this variant.
336 	 */
337 	if (!fs_type || fs_type == TMPFS_MAGIC ||
338 	    (fs_type == NFS_SUPER_MAGIC && variant->type == MADV_HARD)) {
339 		close(fd);
340 		SKIP(return, "unsupported filesystem :%x\n", fs_type);
341 	}
342 
343 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
344 		    MAP_SHARED, fd, 0);
345 	if (addr == MAP_FAILED)
346 		SKIP(return, "mmap failed, not enough memory.\n");
347 	memset(addr, 0xce, self->page_size);
348 
349 	prepare(_metadata, self, addr);
350 
351 	ret = sigsetjmp(signal_jmp_buf, 1);
352 	if (!self->injection_attempted) {
353 		self->injection_attempted = true;
354 		ASSERT_EQ(variant->inject(self, addr), 0);
355 		FORCE_READ(*addr);
356 	}
357 
358 	if (variant->type == MADV_HARD)
359 		check(_metadata, self, addr, MADV_HARD_DIRTY_PAGECACHE, ret);
360 	else
361 		check(_metadata, self, addr, MADV_SOFT_DIRTY_PAGECACHE, ret);
362 
363 	ASSERT_EQ(munmap(addr, self->page_size), 0);
364 
365 	ASSERT_EQ(close(fd), 0);
366 }
367 
368 TEST_HARNESS_MAIN
369