xref: /linux/mm/hwpoison-inject.c (revision 8804d970fab45726b3c7cd7f240b31122aa94219)
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 
14 static u32 hwpoison_filter_enable;
15 static u32 hwpoison_filter_dev_major = ~0U;
16 static u32 hwpoison_filter_dev_minor = ~0U;
17 static u64 hwpoison_filter_flags_mask;
18 static u64 hwpoison_filter_flags_value;
19 
hwpoison_filter_dev(struct page * p)20 static 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 
hwpoison_filter_flags(struct page * p)45 static 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
68 static u64 hwpoison_filter_memcg;
hwpoison_filter_task(struct page * p)69 static 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
hwpoison_filter_task(struct page * p)80 static int hwpoison_filter_task(struct page *p) { return 0; }
81 #endif
82 
hwpoison_filter(struct page * p)83 static 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 
100 static struct dentry *hwpoison_dir;
101 
hwpoison_inject(void * data,u64 val)102 static 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 
138 inject:
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 
hwpoison_unpoison(void * data,u64 val)144 static 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 
152 DEFINE_DEBUGFS_ATTRIBUTE(hwpoison_fops, NULL, hwpoison_inject, "%lli\n");
153 DEFINE_DEBUGFS_ATTRIBUTE(unpoison_fops, NULL, hwpoison_unpoison, "%lli\n");
154 
pfn_inject_exit(void)155 static void __exit pfn_inject_exit(void)
156 {
157 	hwpoison_filter_enable = 0;
158 	hwpoison_filter_unregister();
159 	debugfs_remove_recursive(hwpoison_dir);
160 }
161 
pfn_inject_init(void)162 static 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 
202 module_init(pfn_inject_init);
203 module_exit(pfn_inject_exit);
204 MODULE_DESCRIPTION("HWPoison pages injector");
205 MODULE_LICENSE("GPL");
206