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