xref: /linux/tools/testing/selftests/mm/memory-failure.c (revision 40286d6379aacfcc053253ef78dc78b09addffda)
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