xref: /linux/tools/testing/selftests/mm/memory-failure.c (revision 12e8a2fae372c55c17a410929cfa60f96b93d17a)
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_SOFT_ANON,
33 	MADV_SOFT_CLEAN_PAGECACHE,
34 };
35 
36 static jmp_buf signal_jmp_buf;
37 static siginfo_t siginfo;
38 const char *pagemap_proc = "/proc/self/pagemap";
39 const char *kpageflags_proc = "/proc/kpageflags";
40 
41 FIXTURE(memory_failure)
42 {
43 	unsigned long page_size;
44 	unsigned long corrupted_size;
45 	unsigned long pfn;
46 	int pagemap_fd;
47 	int kpageflags_fd;
48 	bool triggered;
49 };
50 
51 FIXTURE_VARIANT(memory_failure)
52 {
53 	enum inject_type type;
54 	int (*inject)(FIXTURE_DATA(memory_failure) * self, void *vaddr);
55 };
56 
57 static int madv_hard_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
58 {
59 	return madvise(vaddr, self->page_size, MADV_HWPOISON);
60 }
61 
62 FIXTURE_VARIANT_ADD(memory_failure, madv_hard)
63 {
64 	.type = MADV_HARD,
65 	.inject = madv_hard_inject,
66 };
67 
68 static int madv_soft_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
69 {
70 	return madvise(vaddr, self->page_size, MADV_SOFT_OFFLINE);
71 }
72 
73 FIXTURE_VARIANT_ADD(memory_failure, madv_soft)
74 {
75 	.type = MADV_SOFT,
76 	.inject = madv_soft_inject,
77 };
78 
79 static void sigbus_action(int signo, siginfo_t *si, void *args)
80 {
81 	memcpy(&siginfo, si, sizeof(siginfo_t));
82 	siglongjmp(signal_jmp_buf, 1);
83 }
84 
85 static int setup_sighandler(void)
86 {
87 	struct sigaction sa = {
88 		.sa_sigaction = sigbus_action,
89 		.sa_flags = SA_SIGINFO,
90 	};
91 
92 	return sigaction(SIGBUS, &sa, NULL);
93 }
94 
95 FIXTURE_SETUP(memory_failure)
96 {
97 	memset(self, 0, sizeof(*self));
98 
99 	self->page_size = (unsigned long)sysconf(_SC_PAGESIZE);
100 
101 	memset(&siginfo, 0, sizeof(siginfo));
102 	if (setup_sighandler())
103 		SKIP(return, "setup sighandler failed.\n");
104 
105 	self->pagemap_fd = open(pagemap_proc, O_RDONLY);
106 	if (self->pagemap_fd == -1)
107 		SKIP(return, "open %s failed.\n", pagemap_proc);
108 
109 	self->kpageflags_fd = open(kpageflags_proc, O_RDONLY);
110 	if (self->kpageflags_fd == -1)
111 		SKIP(return, "open %s failed.\n", kpageflags_proc);
112 }
113 
114 static void teardown_sighandler(void)
115 {
116 	struct sigaction sa = {
117 		.sa_handler = SIG_DFL,
118 		.sa_flags = SA_SIGINFO,
119 	};
120 
121 	sigaction(SIGBUS, &sa, NULL);
122 }
123 
124 FIXTURE_TEARDOWN(memory_failure)
125 {
126 	close(self->kpageflags_fd);
127 	close(self->pagemap_fd);
128 	teardown_sighandler();
129 }
130 
131 static void prepare(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
132 		    void *vaddr)
133 {
134 	self->pfn = pagemap_get_pfn(self->pagemap_fd, vaddr);
135 	ASSERT_NE(self->pfn, -1UL);
136 
137 	ASSERT_EQ(get_hardware_corrupted_size(&self->corrupted_size), 0);
138 }
139 
140 static bool check_memory(void *vaddr, unsigned long size)
141 {
142 	char buf[64];
143 
144 	memset(buf, 0xce, sizeof(buf));
145 	while (size >= sizeof(buf)) {
146 		if (memcmp(vaddr, buf, sizeof(buf)))
147 			return false;
148 		size -= sizeof(buf);
149 		vaddr += sizeof(buf);
150 	}
151 
152 	return true;
153 }
154 
155 static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
156 		  void *vaddr, enum result_type type, int setjmp)
157 {
158 	unsigned long size;
159 	uint64_t pfn_flags;
160 
161 	switch (type) {
162 	case MADV_SOFT_ANON:
163 	case MADV_HARD_CLEAN_PAGECACHE:
164 	case MADV_SOFT_CLEAN_PAGECACHE:
165 		/* It is not expected to receive a SIGBUS signal. */
166 		ASSERT_EQ(setjmp, 0);
167 
168 		/* The page content should remain unchanged. */
169 		ASSERT_TRUE(check_memory(vaddr, self->page_size));
170 
171 		/* The backing pfn of addr should have changed. */
172 		ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn);
173 		break;
174 	case MADV_HARD_ANON:
175 		/* The SIGBUS signal should have been received. */
176 		ASSERT_EQ(setjmp, 1);
177 
178 		/* Check if siginfo contains correct SIGBUS context. */
179 		ASSERT_EQ(siginfo.si_signo, SIGBUS);
180 		ASSERT_EQ(siginfo.si_code, BUS_MCEERR_AR);
181 		ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size);
182 		ASSERT_EQ(siginfo.si_addr, vaddr);
183 
184 		/* XXX Check backing pte is hwpoison entry when supported. */
185 		ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
186 		break;
187 	default:
188 		SKIP(return, "unexpected inject type %d.\n", type);
189 	}
190 
191 	/* Check if the value of HardwareCorrupted has increased. */
192 	ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
193 	ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
194 
195 	/* Check if HWPoison flag is set. */
196 	ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
197 	ASSERT_EQ(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
198 }
199 
200 static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
201 		    void *vaddr)
202 {
203 	unsigned long size;
204 	uint64_t pfn_flags;
205 
206 	ASSERT_EQ(unpoison_memory(self->pfn), 0);
207 
208 	/* Check if HWPoison flag is cleared. */
209 	ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
210 	ASSERT_NE(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
211 
212 	/* Check if the value of HardwareCorrupted has decreased. */
213 	ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
214 	ASSERT_EQ(size, self->corrupted_size);
215 }
216 
217 TEST_F(memory_failure, anon)
218 {
219 	char *addr;
220 	int ret;
221 
222 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
223 		    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
224 	if (addr == MAP_FAILED)
225 		SKIP(return, "mmap failed, not enough memory.\n");
226 	memset(addr, 0xce, self->page_size);
227 
228 	prepare(_metadata, self, addr);
229 
230 	ret = sigsetjmp(signal_jmp_buf, 1);
231 	if (!self->triggered) {
232 		self->triggered = true;
233 		ASSERT_EQ(variant->inject(self, addr), 0);
234 		FORCE_READ(*addr);
235 	}
236 
237 	if (variant->type == MADV_HARD)
238 		check(_metadata, self, addr, MADV_HARD_ANON, ret);
239 	else
240 		check(_metadata, self, addr, MADV_SOFT_ANON, ret);
241 
242 	cleanup(_metadata, self, addr);
243 
244 	ASSERT_EQ(munmap(addr, self->page_size), 0);
245 }
246 
247 /* Borrowed from mm/gup_longterm.c. */
248 static int get_fs_type(int fd)
249 {
250 	struct statfs fs;
251 	int ret;
252 
253 	do {
254 		ret = fstatfs(fd, &fs);
255 	} while (ret && errno == EINTR);
256 
257 	return ret ? 0 : (int)fs.f_type;
258 }
259 
260 TEST_F(memory_failure, clean_pagecache)
261 {
262 	const char *fname = "./clean-page-cache-test-file";
263 	int fd;
264 	char *addr;
265 	int ret;
266 	int fs_type;
267 
268 	fd = open(fname, O_RDWR | O_CREAT, 0664);
269 	if (fd < 0)
270 		SKIP(return, "failed to open test file.\n");
271 	unlink(fname);
272 	ftruncate(fd, self->page_size);
273 	fs_type = get_fs_type(fd);
274 	if (!fs_type || fs_type == TMPFS_MAGIC)
275 		SKIP(return, "unsupported filesystem :%x\n", fs_type);
276 
277 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
278 		    MAP_SHARED, fd, 0);
279 	if (addr == MAP_FAILED)
280 		SKIP(return, "mmap failed, not enough memory.\n");
281 	memset(addr, 0xce, self->page_size);
282 	fsync(fd);
283 
284 	prepare(_metadata, self, addr);
285 
286 	ret = sigsetjmp(signal_jmp_buf, 1);
287 	if (!self->triggered) {
288 		self->triggered = true;
289 		ASSERT_EQ(variant->inject(self, addr), 0);
290 		FORCE_READ(*addr);
291 	}
292 
293 	if (variant->type == MADV_HARD)
294 		check(_metadata, self, addr, MADV_HARD_CLEAN_PAGECACHE, ret);
295 	else
296 		check(_metadata, self, addr, MADV_SOFT_CLEAN_PAGECACHE, ret);
297 
298 	cleanup(_metadata, self, addr);
299 
300 	ASSERT_EQ(munmap(addr, self->page_size), 0);
301 
302 	ASSERT_EQ(close(fd), 0);
303 }
304 
305 TEST_HARNESS_MAIN
306