xref: /linux/mm/hwpoison-inject.c (revision a4a508df2aa34f8650afde54ea804321c618f45f)
109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
225985edcSLucas De Marchi /* Inject a hwpoison memory failure on a arbitrary pfn */
3cae681fcSAndi Kleen #include <linux/module.h>
4cae681fcSAndi Kleen #include <linux/debugfs.h>
5cae681fcSAndi Kleen #include <linux/kernel.h>
6cae681fcSAndi Kleen #include <linux/mm.h>
731d3d348SWu Fengguang #include <linux/swap.h>
831d3d348SWu Fengguang #include <linux/pagemap.h>
943131e14SNaoya Horiguchi #include <linux/hugetlb.h>
10*5ce1dbfdSMiaohe Lin #include <linux/page-flags.h>
11*5ce1dbfdSMiaohe Lin #include <linux/memcontrol.h>
127c116f2bSWu Fengguang #include "internal.h"
13cae681fcSAndi Kleen 
14*5ce1dbfdSMiaohe Lin static u32 hwpoison_filter_enable;
15*5ce1dbfdSMiaohe Lin static u32 hwpoison_filter_dev_major = ~0U;
16*5ce1dbfdSMiaohe Lin static u32 hwpoison_filter_dev_minor = ~0U;
17*5ce1dbfdSMiaohe Lin static u64 hwpoison_filter_flags_mask;
18*5ce1dbfdSMiaohe Lin static u64 hwpoison_filter_flags_value;
19*5ce1dbfdSMiaohe Lin 
hwpoison_filter_dev(struct page * p)20*5ce1dbfdSMiaohe Lin static int hwpoison_filter_dev(struct page *p)
21*5ce1dbfdSMiaohe Lin {
22*5ce1dbfdSMiaohe Lin 	struct folio *folio = page_folio(p);
23*5ce1dbfdSMiaohe Lin 	struct address_space *mapping;
24*5ce1dbfdSMiaohe Lin 	dev_t dev;
25*5ce1dbfdSMiaohe Lin 
26*5ce1dbfdSMiaohe Lin 	if (hwpoison_filter_dev_major == ~0U &&
27*5ce1dbfdSMiaohe Lin 	    hwpoison_filter_dev_minor == ~0U)
28*5ce1dbfdSMiaohe Lin 		return 0;
29*5ce1dbfdSMiaohe Lin 
30*5ce1dbfdSMiaohe Lin 	mapping = folio_mapping(folio);
31*5ce1dbfdSMiaohe Lin 	if (mapping == NULL || mapping->host == NULL)
32*5ce1dbfdSMiaohe Lin 		return -EINVAL;
33*5ce1dbfdSMiaohe Lin 
34*5ce1dbfdSMiaohe Lin 	dev = mapping->host->i_sb->s_dev;
35*5ce1dbfdSMiaohe Lin 	if (hwpoison_filter_dev_major != ~0U &&
36*5ce1dbfdSMiaohe Lin 	    hwpoison_filter_dev_major != MAJOR(dev))
37*5ce1dbfdSMiaohe Lin 		return -EINVAL;
38*5ce1dbfdSMiaohe Lin 	if (hwpoison_filter_dev_minor != ~0U &&
39*5ce1dbfdSMiaohe Lin 	    hwpoison_filter_dev_minor != MINOR(dev))
40*5ce1dbfdSMiaohe Lin 		return -EINVAL;
41*5ce1dbfdSMiaohe Lin 
42*5ce1dbfdSMiaohe Lin 	return 0;
43*5ce1dbfdSMiaohe Lin }
44*5ce1dbfdSMiaohe Lin 
hwpoison_filter_flags(struct page * p)45*5ce1dbfdSMiaohe Lin static int hwpoison_filter_flags(struct page *p)
46*5ce1dbfdSMiaohe Lin {
47*5ce1dbfdSMiaohe Lin 	if (!hwpoison_filter_flags_mask)
48*5ce1dbfdSMiaohe Lin 		return 0;
49*5ce1dbfdSMiaohe Lin 
50*5ce1dbfdSMiaohe Lin 	if ((stable_page_flags(p) & hwpoison_filter_flags_mask) ==
51*5ce1dbfdSMiaohe Lin 				    hwpoison_filter_flags_value)
52*5ce1dbfdSMiaohe Lin 		return 0;
53*5ce1dbfdSMiaohe Lin 	else
54*5ce1dbfdSMiaohe Lin 		return -EINVAL;
55*5ce1dbfdSMiaohe Lin }
56*5ce1dbfdSMiaohe Lin 
57*5ce1dbfdSMiaohe Lin /*
58*5ce1dbfdSMiaohe Lin  * This allows stress tests to limit test scope to a collection of tasks
59*5ce1dbfdSMiaohe Lin  * by putting them under some memcg. This prevents killing unrelated/important
60*5ce1dbfdSMiaohe Lin  * processes such as /sbin/init. Note that the target task may share clean
61*5ce1dbfdSMiaohe Lin  * pages with init (eg. libc text), which is harmless. If the target task
62*5ce1dbfdSMiaohe Lin  * share _dirty_ pages with another task B, the test scheme must make sure B
63*5ce1dbfdSMiaohe Lin  * is also included in the memcg. At last, due to race conditions this filter
64*5ce1dbfdSMiaohe Lin  * can only guarantee that the page either belongs to the memcg tasks, or is
65*5ce1dbfdSMiaohe Lin  * a freed page.
66*5ce1dbfdSMiaohe Lin  */
67*5ce1dbfdSMiaohe Lin #ifdef CONFIG_MEMCG
68*5ce1dbfdSMiaohe Lin static u64 hwpoison_filter_memcg;
hwpoison_filter_task(struct page * p)69*5ce1dbfdSMiaohe Lin static int hwpoison_filter_task(struct page *p)
70*5ce1dbfdSMiaohe Lin {
71*5ce1dbfdSMiaohe Lin 	if (!hwpoison_filter_memcg)
72*5ce1dbfdSMiaohe Lin 		return 0;
73*5ce1dbfdSMiaohe Lin 
74*5ce1dbfdSMiaohe Lin 	if (page_cgroup_ino(p) != hwpoison_filter_memcg)
75*5ce1dbfdSMiaohe Lin 		return -EINVAL;
76*5ce1dbfdSMiaohe Lin 
77*5ce1dbfdSMiaohe Lin 	return 0;
78*5ce1dbfdSMiaohe Lin }
79*5ce1dbfdSMiaohe Lin #else
hwpoison_filter_task(struct page * p)80*5ce1dbfdSMiaohe Lin static int hwpoison_filter_task(struct page *p) { return 0; }
81*5ce1dbfdSMiaohe Lin #endif
82*5ce1dbfdSMiaohe Lin 
hwpoison_filter(struct page * p)83*5ce1dbfdSMiaohe Lin static int hwpoison_filter(struct page *p)
84*5ce1dbfdSMiaohe Lin {
85*5ce1dbfdSMiaohe Lin 	if (!hwpoison_filter_enable)
86*5ce1dbfdSMiaohe Lin 		return 0;
87*5ce1dbfdSMiaohe Lin 
88*5ce1dbfdSMiaohe Lin 	if (hwpoison_filter_dev(p))
89*5ce1dbfdSMiaohe Lin 		return -EINVAL;
90*5ce1dbfdSMiaohe Lin 
91*5ce1dbfdSMiaohe Lin 	if (hwpoison_filter_flags(p))
92*5ce1dbfdSMiaohe Lin 		return -EINVAL;
93*5ce1dbfdSMiaohe Lin 
94*5ce1dbfdSMiaohe Lin 	if (hwpoison_filter_task(p))
95*5ce1dbfdSMiaohe Lin 		return -EINVAL;
96*5ce1dbfdSMiaohe Lin 
97*5ce1dbfdSMiaohe Lin 	return 0;
98*5ce1dbfdSMiaohe Lin }
99*5ce1dbfdSMiaohe Lin 
100847ce401SWu Fengguang static struct dentry *hwpoison_dir;
101cae681fcSAndi Kleen 
hwpoison_inject(void * data,u64 val)102cae681fcSAndi Kleen static int hwpoison_inject(void *data, u64 val)
103cae681fcSAndi Kleen {
10431d3d348SWu Fengguang 	unsigned long pfn = val;
10531d3d348SWu Fengguang 	struct page *p;
106fed5348eSMatthew Wilcox (Oracle) 	struct folio *folio;
10731d3d348SWu Fengguang 	int err;
10831d3d348SWu Fengguang 
109cae681fcSAndi Kleen 	if (!capable(CAP_SYS_ADMIN))
110cae681fcSAndi Kleen 		return -EPERM;
11131d3d348SWu Fengguang 
11231d3d348SWu Fengguang 	if (!pfn_valid(pfn))
11331d3d348SWu Fengguang 		return -ENXIO;
11431d3d348SWu Fengguang 
11531d3d348SWu Fengguang 	p = pfn_to_page(pfn);
116fed5348eSMatthew Wilcox (Oracle) 	folio = page_folio(p);
11731d3d348SWu Fengguang 
118fb31ba30SWanpeng Li 	if (!hwpoison_filter_enable)
119fb31ba30SWanpeng Li 		goto inject;
120fb31ba30SWanpeng Li 
121fed5348eSMatthew Wilcox (Oracle) 	shake_folio(folio);
12231d3d348SWu Fengguang 	/*
123a581865eSMiaohe Lin 	 * This implies unable to support non-LRU pages except free page.
12431d3d348SWu Fengguang 	 */
125fed5348eSMatthew Wilcox (Oracle) 	if (!folio_test_lru(folio) && !folio_test_hugetlb(folio) &&
126fed5348eSMatthew Wilcox (Oracle) 	    !is_free_buddy_page(p))
127fd476720SNaoya Horiguchi 		return 0;
12831d3d348SWu Fengguang 
12931d3d348SWu Fengguang 	/*
130fd476720SNaoya Horiguchi 	 * do a racy check to make sure PG_hwpoison will only be set for
131fd476720SNaoya Horiguchi 	 * the targeted owner (or on a free page).
132cd42f4a3STony Luck 	 * memory_failure() will redo the check reliably inside page lock.
13331d3d348SWu Fengguang 	 */
134fed5348eSMatthew Wilcox (Oracle) 	err = hwpoison_filter(&folio->page);
13531d3d348SWu Fengguang 	if (err)
136fd476720SNaoya Horiguchi 		return 0;
13731d3d348SWu Fengguang 
1380d57eb8dSAndi Kleen inject:
1394883e997SWanpeng Li 	pr_info("Injecting memory failure at pfn %#lx\n", pfn);
14067f22ba7Szhenwei pi 	err = memory_failure(pfn, MF_SW_SIMULATED);
141d1fe111fSluofei 	return (err == -EOPNOTSUPP) ? 0 : err;
142cae681fcSAndi Kleen }
143cae681fcSAndi Kleen 
hwpoison_unpoison(void * data,u64 val)144847ce401SWu Fengguang static int hwpoison_unpoison(void *data, u64 val)
145847ce401SWu Fengguang {
146847ce401SWu Fengguang 	if (!capable(CAP_SYS_ADMIN))
147847ce401SWu Fengguang 		return -EPERM;
148847ce401SWu Fengguang 
149847ce401SWu Fengguang 	return unpoison_memory(val);
150847ce401SWu Fengguang }
151847ce401SWu Fengguang 
15235e3d566Szhong jiang DEFINE_DEBUGFS_ATTRIBUTE(hwpoison_fops, NULL, hwpoison_inject, "%lli\n");
15335e3d566Szhong jiang DEFINE_DEBUGFS_ATTRIBUTE(unpoison_fops, NULL, hwpoison_unpoison, "%lli\n");
154cae681fcSAndi Kleen 
pfn_inject_exit(void)1554e07acddSXiu Jianfeng static void __exit pfn_inject_exit(void)
156cae681fcSAndi Kleen {
157f0696cb4Szhenwei pi 	hwpoison_filter_enable = 0;
158*5ce1dbfdSMiaohe Lin 	hwpoison_filter_unregister();
159cae681fcSAndi Kleen 	debugfs_remove_recursive(hwpoison_dir);
160cae681fcSAndi Kleen }
161cae681fcSAndi Kleen 
pfn_inject_init(void)1624e07acddSXiu Jianfeng static int __init pfn_inject_init(void)
163cae681fcSAndi Kleen {
164cae681fcSAndi Kleen 	hwpoison_dir = debugfs_create_dir("hwpoison", NULL);
165847ce401SWu Fengguang 
166847ce401SWu Fengguang 	/*
167847ce401SWu Fengguang 	 * Note that the below poison/unpoison interfaces do not involve
168847ce401SWu Fengguang 	 * hardware status change, hence do not require hardware support.
169847ce401SWu Fengguang 	 * They are mainly for testing hwpoison in software level.
170847ce401SWu Fengguang 	 */
1712fcc6e20SGreg Kroah-Hartman 	debugfs_create_file("corrupt-pfn", 0200, hwpoison_dir, NULL,
1722fcc6e20SGreg Kroah-Hartman 			    &hwpoison_fops);
173847ce401SWu Fengguang 
1742fcc6e20SGreg Kroah-Hartman 	debugfs_create_file("unpoison-pfn", 0200, hwpoison_dir, NULL,
1752fcc6e20SGreg Kroah-Hartman 			    &unpoison_fops);
176847ce401SWu Fengguang 
1772fcc6e20SGreg Kroah-Hartman 	debugfs_create_u32("corrupt-filter-enable", 0600, hwpoison_dir,
1782fcc6e20SGreg Kroah-Hartman 			   &hwpoison_filter_enable);
1791bfe5febSHaicheng Li 
1802fcc6e20SGreg Kroah-Hartman 	debugfs_create_u32("corrupt-filter-dev-major", 0600, hwpoison_dir,
1812fcc6e20SGreg Kroah-Hartman 			   &hwpoison_filter_dev_major);
1827c116f2bSWu Fengguang 
1832fcc6e20SGreg Kroah-Hartman 	debugfs_create_u32("corrupt-filter-dev-minor", 0600, hwpoison_dir,
1842fcc6e20SGreg Kroah-Hartman 			   &hwpoison_filter_dev_minor);
1857c116f2bSWu Fengguang 
1862fcc6e20SGreg Kroah-Hartman 	debugfs_create_u64("corrupt-filter-flags-mask", 0600, hwpoison_dir,
1872fcc6e20SGreg Kroah-Hartman 			   &hwpoison_filter_flags_mask);
188478c5ffcSWu Fengguang 
1892fcc6e20SGreg Kroah-Hartman 	debugfs_create_u64("corrupt-filter-flags-value", 0600, hwpoison_dir,
1902fcc6e20SGreg Kroah-Hartman 			   &hwpoison_filter_flags_value);
191478c5ffcSWu Fengguang 
19294a59fb3SVladimir Davydov #ifdef CONFIG_MEMCG
1932fcc6e20SGreg Kroah-Hartman 	debugfs_create_u64("corrupt-filter-memcg", 0600, hwpoison_dir,
1942fcc6e20SGreg Kroah-Hartman 			   &hwpoison_filter_memcg);
1954fd466ebSAndi Kleen #endif
1964fd466ebSAndi Kleen 
197*5ce1dbfdSMiaohe Lin 	hwpoison_filter_register(hwpoison_filter);
198*5ce1dbfdSMiaohe Lin 
199847ce401SWu Fengguang 	return 0;
200cae681fcSAndi Kleen }
201cae681fcSAndi Kleen 
202cae681fcSAndi Kleen module_init(pfn_inject_init);
203cae681fcSAndi Kleen module_exit(pfn_inject_exit);
2042f57ced6SJeff Johnson MODULE_DESCRIPTION("HWPoison pages injector");
205cae681fcSAndi Kleen MODULE_LICENSE("GPL");
206