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-only
2/* Inject a hwpoison memory failure on a arbitrary pfn */
3#include <linux/module.h>
4#include <linux/debugfs.h>
5#include <linux/kernel.h>
6#include <linux/mm.h>
7#include <linux/swap.h>
8#include <linux/pagemap.h>
9#include <linux/hugetlb.h>
10#include <linux/page-flags.h>
11#include <linux/memcontrol.h>
12#include "internal.h"
13
14static u32 hwpoison_filter_enable;
15static u32 hwpoison_filter_dev_major = ~0U;
16static u32 hwpoison_filter_dev_minor = ~0U;
17static u64 hwpoison_filter_flags_mask;
18static u64 hwpoison_filter_flags_value;
19
20static int hwpoison_filter_dev(struct page *p)
21{
22 struct folio *folio = page_folio(p);
23 struct address_space *mapping;
24 dev_t dev;
25
26 if (hwpoison_filter_dev_major == ~0U &&
27 hwpoison_filter_dev_minor == ~0U)
28 return 0;
29
30 mapping = folio_mapping(folio);
31 if (mapping == NULL || mapping->host == NULL)
32 return -EINVAL;
33
34 dev = mapping->host->i_sb->s_dev;
35 if (hwpoison_filter_dev_major != ~0U &&
36 hwpoison_filter_dev_major != MAJOR(dev))
37 return -EINVAL;
38 if (hwpoison_filter_dev_minor != ~0U &&
39 hwpoison_filter_dev_minor != MINOR(dev))
40 return -EINVAL;
41
42 return 0;
43}
44
45static int hwpoison_filter_flags(struct page *p)
46{
47 if (!hwpoison_filter_flags_mask)
48 return 0;
49
50 if ((stable_page_flags(p) & hwpoison_filter_flags_mask) ==
51 hwpoison_filter_flags_value)
52 return 0;
53 else
54 return -EINVAL;
55}
56
57/*
58 * This allows stress tests to limit test scope to a collection of tasks
59 * by putting them under some memcg. This prevents killing unrelated/important
60 * processes such as /sbin/init. Note that the target task may share clean
61 * pages with init (eg. libc text), which is harmless. If the target task
62 * share _dirty_ pages with another task B, the test scheme must make sure B
63 * is also included in the memcg. At last, due to race conditions this filter
64 * can only guarantee that the page either belongs to the memcg tasks, or is
65 * a freed page.
66 */
67#ifdef CONFIG_MEMCG
68static u64 hwpoison_filter_memcg;
69static int hwpoison_filter_task(struct page *p)
70{
71 if (!hwpoison_filter_memcg)
72 return 0;
73
74 if (page_cgroup_ino(p) != hwpoison_filter_memcg)
75 return -EINVAL;
76
77 return 0;
78}
79#else
80static int hwpoison_filter_task(struct page *p) { return 0; }
81#endif
82
83static int hwpoison_filter(struct page *p)
84{
85 if (!hwpoison_filter_enable)
86 return 0;
87
88 if (hwpoison_filter_dev(p))
89 return -EINVAL;
90
91 if (hwpoison_filter_flags(p))
92 return -EINVAL;
93
94 if (hwpoison_filter_task(p))
95 return -EINVAL;
96
97 return 0;
98}
99
100static struct dentry *hwpoison_dir;
101
102static int hwpoison_inject(void *data, u64 val)
103{
104 unsigned long pfn = val;
105 struct page *p;
106 struct folio *folio;
107 int err;
108
109 if (!capable(CAP_SYS_ADMIN))
110 return -EPERM;
111
112 if (!pfn_valid(pfn))
113 return -ENXIO;
114
115 p = pfn_to_page(pfn);
116 folio = page_folio(p);
117
118 if (!hwpoison_filter_enable)
119 goto inject;
120
121 shake_folio(folio);
122 /*
123 * This implies unable to support non-LRU pages except free page.
124 */
125 if (!folio_test_lru(folio) && !folio_test_hugetlb(folio) &&
126 !is_free_buddy_page(p))
127 return 0;
128
129 /*
130 * do a racy check to make sure PG_hwpoison will only be set for
131 * the targeted owner (or on a free page).
132 * memory_failure() will redo the check reliably inside page lock.
133 */
134 err = hwpoison_filter(&folio->page);
135 if (err)
136 return 0;
137
138inject:
139 pr_info("Injecting memory failure at pfn %#lx\n", pfn);
140 err = memory_failure(pfn, MF_SW_SIMULATED);
141 return (err == -EOPNOTSUPP) ? 0 : err;
142}
143
144static int hwpoison_unpoison(void *data, u64 val)
145{
146 if (!capable(CAP_SYS_ADMIN))
147 return -EPERM;
148
149 return unpoison_memory(val);
150}
151
152DEFINE_DEBUGFS_ATTRIBUTE(hwpoison_fops, NULL, hwpoison_inject, "%lli\n");
153DEFINE_DEBUGFS_ATTRIBUTE(unpoison_fops, NULL, hwpoison_unpoison, "%lli\n");
154
155static void __exit pfn_inject_exit(void)
156{
157 hwpoison_filter_enable = 0;
158 hwpoison_filter_unregister();
159 debugfs_remove_recursive(hwpoison_dir);
160}
161
162static int __init pfn_inject_init(void)
163{
164 hwpoison_dir = debugfs_create_dir("hwpoison", NULL);
165
166 /*
167 * Note that the below poison/unpoison interfaces do not involve
168 * hardware status change, hence do not require hardware support.
169 * They are mainly for testing hwpoison in software level.
170 */
171 debugfs_create_file("corrupt-pfn", 0200, hwpoison_dir, NULL,
172 &hwpoison_fops);
173
174 debugfs_create_file("unpoison-pfn", 0200, hwpoison_dir, NULL,
175 &unpoison_fops);
176
177 debugfs_create_u32("corrupt-filter-enable", 0600, hwpoison_dir,
178 &hwpoison_filter_enable);
179
180 debugfs_create_u32("corrupt-filter-dev-major", 0600, hwpoison_dir,
181 &hwpoison_filter_dev_major);
182
183 debugfs_create_u32("corrupt-filter-dev-minor", 0600, hwpoison_dir,
184 &hwpoison_filter_dev_minor);
185
186 debugfs_create_u64("corrupt-filter-flags-mask", 0600, hwpoison_dir,
187 &hwpoison_filter_flags_mask);
188
189 debugfs_create_u64("corrupt-filter-flags-value", 0600, hwpoison_dir,
190 &hwpoison_filter_flags_value);
191
192#ifdef CONFIG_MEMCG
193 debugfs_create_u64("corrupt-filter-memcg", 0600, hwpoison_dir,
194 &hwpoison_filter_memcg);
195#endif
196
197 hwpoison_filter_register(hwpoison_filter);
198
199 return 0;
200}
201
202module_init(pfn_inject_init);
203module_exit(pfn_inject_exit);
204MODULE_DESCRIPTION("HWPoison pages injector");
205MODULE_LICENSE("GPL");