xref: /linux/tools/testing/selftests/mm/memory-failure.c (revision ff4ef2fbd10192357da76fd80796b7262df21b78)
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 <signal.h>
14 #include <setjmp.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 
18 #include "vm_util.h"
19 
20 enum inject_type {
21 	MADV_HARD,
22 	MADV_SOFT,
23 };
24 
25 enum result_type {
26 	MADV_HARD_ANON,
27 	MADV_SOFT_ANON,
28 };
29 
30 static jmp_buf signal_jmp_buf;
31 static siginfo_t siginfo;
32 const char *pagemap_proc = "/proc/self/pagemap";
33 const char *kpageflags_proc = "/proc/kpageflags";
34 
35 FIXTURE(memory_failure)
36 {
37 	unsigned long page_size;
38 	unsigned long corrupted_size;
39 	unsigned long pfn;
40 	int pagemap_fd;
41 	int kpageflags_fd;
42 	bool triggered;
43 };
44 
45 FIXTURE_VARIANT(memory_failure)
46 {
47 	enum inject_type type;
48 	int (*inject)(FIXTURE_DATA(memory_failure) * self, void *vaddr);
49 };
50 
51 static int madv_hard_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
52 {
53 	return madvise(vaddr, self->page_size, MADV_HWPOISON);
54 }
55 
56 FIXTURE_VARIANT_ADD(memory_failure, madv_hard)
57 {
58 	.type = MADV_HARD,
59 	.inject = madv_hard_inject,
60 };
61 
62 static int madv_soft_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
63 {
64 	return madvise(vaddr, self->page_size, MADV_SOFT_OFFLINE);
65 }
66 
67 FIXTURE_VARIANT_ADD(memory_failure, madv_soft)
68 {
69 	.type = MADV_SOFT,
70 	.inject = madv_soft_inject,
71 };
72 
73 static void sigbus_action(int signo, siginfo_t *si, void *args)
74 {
75 	memcpy(&siginfo, si, sizeof(siginfo_t));
76 	siglongjmp(signal_jmp_buf, 1);
77 }
78 
79 static int setup_sighandler(void)
80 {
81 	struct sigaction sa = {
82 		.sa_sigaction = sigbus_action,
83 		.sa_flags = SA_SIGINFO,
84 	};
85 
86 	return sigaction(SIGBUS, &sa, NULL);
87 }
88 
89 FIXTURE_SETUP(memory_failure)
90 {
91 	memset(self, 0, sizeof(*self));
92 
93 	self->page_size = (unsigned long)sysconf(_SC_PAGESIZE);
94 
95 	memset(&siginfo, 0, sizeof(siginfo));
96 	if (setup_sighandler())
97 		SKIP(return, "setup sighandler failed.\n");
98 
99 	self->pagemap_fd = open(pagemap_proc, O_RDONLY);
100 	if (self->pagemap_fd == -1)
101 		SKIP(return, "open %s failed.\n", pagemap_proc);
102 
103 	self->kpageflags_fd = open(kpageflags_proc, O_RDONLY);
104 	if (self->kpageflags_fd == -1)
105 		SKIP(return, "open %s failed.\n", kpageflags_proc);
106 }
107 
108 static void teardown_sighandler(void)
109 {
110 	struct sigaction sa = {
111 		.sa_handler = SIG_DFL,
112 		.sa_flags = SA_SIGINFO,
113 	};
114 
115 	sigaction(SIGBUS, &sa, NULL);
116 }
117 
118 FIXTURE_TEARDOWN(memory_failure)
119 {
120 	close(self->kpageflags_fd);
121 	close(self->pagemap_fd);
122 	teardown_sighandler();
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 		/* It is not expected to receive a SIGBUS signal. */
158 		ASSERT_EQ(setjmp, 0);
159 
160 		/* The page content should remain unchanged. */
161 		ASSERT_TRUE(check_memory(vaddr, self->page_size));
162 
163 		/* The backing pfn of addr should have changed. */
164 		ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn);
165 		break;
166 	case MADV_HARD_ANON:
167 		/* The SIGBUS signal should have been received. */
168 		ASSERT_EQ(setjmp, 1);
169 
170 		/* Check if siginfo contains correct SIGBUS context. */
171 		ASSERT_EQ(siginfo.si_signo, SIGBUS);
172 		ASSERT_EQ(siginfo.si_code, BUS_MCEERR_AR);
173 		ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size);
174 		ASSERT_EQ(siginfo.si_addr, vaddr);
175 
176 		/* XXX Check backing pte is hwpoison entry when supported. */
177 		ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
178 		break;
179 	default:
180 		SKIP(return, "unexpected inject type %d.\n", type);
181 	}
182 
183 	/* Check if the value of HardwareCorrupted has increased. */
184 	ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
185 	ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
186 
187 	/* Check if HWPoison flag is set. */
188 	ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
189 	ASSERT_EQ(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
190 }
191 
192 static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
193 		    void *vaddr)
194 {
195 	unsigned long size;
196 	uint64_t pfn_flags;
197 
198 	ASSERT_EQ(unpoison_memory(self->pfn), 0);
199 
200 	/* Check if HWPoison flag is cleared. */
201 	ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
202 	ASSERT_NE(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
203 
204 	/* Check if the value of HardwareCorrupted has decreased. */
205 	ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
206 	ASSERT_EQ(size, self->corrupted_size);
207 }
208 
209 TEST_F(memory_failure, anon)
210 {
211 	char *addr;
212 	int ret;
213 
214 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
215 		    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
216 	if (addr == MAP_FAILED)
217 		SKIP(return, "mmap failed, not enough memory.\n");
218 	memset(addr, 0xce, self->page_size);
219 
220 	prepare(_metadata, self, addr);
221 
222 	ret = sigsetjmp(signal_jmp_buf, 1);
223 	if (!self->triggered) {
224 		self->triggered = true;
225 		ASSERT_EQ(variant->inject(self, addr), 0);
226 		FORCE_READ(*addr);
227 	}
228 
229 	if (variant->type == MADV_HARD)
230 		check(_metadata, self, addr, MADV_HARD_ANON, ret);
231 	else
232 		check(_metadata, self, addr, MADV_SOFT_ANON, ret);
233 
234 	cleanup(_metadata, self, addr);
235 
236 	ASSERT_EQ(munmap(addr, self->page_size), 0);
237 }
238 
239 TEST_HARNESS_MAIN
240