Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
23enum inject_type {
24 MADV_HARD,
25 MADV_SOFT,
26};
27
28enum 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
37static jmp_buf signal_jmp_buf;
38static siginfo_t siginfo;
39const char *pagemap_proc = "/proc/self/pagemap";
40const char *kpageflags_proc = "/proc/kpageflags";
41
42FIXTURE(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
52FIXTURE_VARIANT(memory_failure)
53{
54 enum inject_type type;
55 int (*inject)(FIXTURE_DATA(memory_failure) * self, void *vaddr);
56};
57
58static int madv_hard_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
59{
60 return madvise(vaddr, self->page_size, MADV_HWPOISON);
61}
62
63FIXTURE_VARIANT_ADD(memory_failure, madv_hard)
64{
65 .type = MADV_HARD,
66 .inject = madv_hard_inject,
67};
68
69static int madv_soft_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
70{
71 return madvise(vaddr, self->page_size, MADV_SOFT_OFFLINE);
72}
73
74FIXTURE_VARIANT_ADD(memory_failure, madv_soft)
75{
76 .type = MADV_SOFT,
77 .inject = madv_soft_inject,
78};
79
80static 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
86static 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
96FIXTURE_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
115static 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
125FIXTURE_TEARDOWN(memory_failure)
126{
127 close(self->kpageflags_fd);
128 close(self->pagemap_fd);
129 teardown_sighandler();
130}
131
132static 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
141static 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
156static 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
203static 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
220TEST_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
250static 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. */
263static 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
275TEST_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
317TEST_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
358TEST_HARNESS_MAIN