162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* Inject a hwpoison memory failure on a arbitrary pfn */
362306a36Sopenharmony_ci#include <linux/module.h>
462306a36Sopenharmony_ci#include <linux/debugfs.h>
562306a36Sopenharmony_ci#include <linux/kernel.h>
662306a36Sopenharmony_ci#include <linux/mm.h>
762306a36Sopenharmony_ci#include <linux/swap.h>
862306a36Sopenharmony_ci#include <linux/pagemap.h>
962306a36Sopenharmony_ci#include <linux/hugetlb.h>
1062306a36Sopenharmony_ci#include "internal.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistatic struct dentry *hwpoison_dir;
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistatic int hwpoison_inject(void *data, u64 val)
1562306a36Sopenharmony_ci{
1662306a36Sopenharmony_ci	unsigned long pfn = val;
1762306a36Sopenharmony_ci	struct page *p;
1862306a36Sopenharmony_ci	struct page *hpage;
1962306a36Sopenharmony_ci	int err;
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
2262306a36Sopenharmony_ci		return -EPERM;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	if (!pfn_valid(pfn))
2562306a36Sopenharmony_ci		return -ENXIO;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	p = pfn_to_page(pfn);
2862306a36Sopenharmony_ci	hpage = compound_head(p);
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	if (!hwpoison_filter_enable)
3162306a36Sopenharmony_ci		goto inject;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	shake_page(hpage);
3462306a36Sopenharmony_ci	/*
3562306a36Sopenharmony_ci	 * This implies unable to support non-LRU pages except free page.
3662306a36Sopenharmony_ci	 */
3762306a36Sopenharmony_ci	if (!PageLRU(hpage) && !PageHuge(p) && !is_free_buddy_page(p))
3862306a36Sopenharmony_ci		return 0;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	/*
4162306a36Sopenharmony_ci	 * do a racy check to make sure PG_hwpoison will only be set for
4262306a36Sopenharmony_ci	 * the targeted owner (or on a free page).
4362306a36Sopenharmony_ci	 * memory_failure() will redo the check reliably inside page lock.
4462306a36Sopenharmony_ci	 */
4562306a36Sopenharmony_ci	err = hwpoison_filter(hpage);
4662306a36Sopenharmony_ci	if (err)
4762306a36Sopenharmony_ci		return 0;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ciinject:
5062306a36Sopenharmony_ci	pr_info("Injecting memory failure at pfn %#lx\n", pfn);
5162306a36Sopenharmony_ci	err = memory_failure(pfn, MF_SW_SIMULATED);
5262306a36Sopenharmony_ci	return (err == -EOPNOTSUPP) ? 0 : err;
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int hwpoison_unpoison(void *data, u64 val)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
5862306a36Sopenharmony_ci		return -EPERM;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	return unpoison_memory(val);
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ciDEFINE_DEBUGFS_ATTRIBUTE(hwpoison_fops, NULL, hwpoison_inject, "%lli\n");
6462306a36Sopenharmony_ciDEFINE_DEBUGFS_ATTRIBUTE(unpoison_fops, NULL, hwpoison_unpoison, "%lli\n");
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic void __exit pfn_inject_exit(void)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	hwpoison_filter_enable = 0;
6962306a36Sopenharmony_ci	debugfs_remove_recursive(hwpoison_dir);
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic int __init pfn_inject_init(void)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	hwpoison_dir = debugfs_create_dir("hwpoison", NULL);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	/*
7762306a36Sopenharmony_ci	 * Note that the below poison/unpoison interfaces do not involve
7862306a36Sopenharmony_ci	 * hardware status change, hence do not require hardware support.
7962306a36Sopenharmony_ci	 * They are mainly for testing hwpoison in software level.
8062306a36Sopenharmony_ci	 */
8162306a36Sopenharmony_ci	debugfs_create_file("corrupt-pfn", 0200, hwpoison_dir, NULL,
8262306a36Sopenharmony_ci			    &hwpoison_fops);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	debugfs_create_file("unpoison-pfn", 0200, hwpoison_dir, NULL,
8562306a36Sopenharmony_ci			    &unpoison_fops);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	debugfs_create_u32("corrupt-filter-enable", 0600, hwpoison_dir,
8862306a36Sopenharmony_ci			   &hwpoison_filter_enable);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	debugfs_create_u32("corrupt-filter-dev-major", 0600, hwpoison_dir,
9162306a36Sopenharmony_ci			   &hwpoison_filter_dev_major);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	debugfs_create_u32("corrupt-filter-dev-minor", 0600, hwpoison_dir,
9462306a36Sopenharmony_ci			   &hwpoison_filter_dev_minor);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	debugfs_create_u64("corrupt-filter-flags-mask", 0600, hwpoison_dir,
9762306a36Sopenharmony_ci			   &hwpoison_filter_flags_mask);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	debugfs_create_u64("corrupt-filter-flags-value", 0600, hwpoison_dir,
10062306a36Sopenharmony_ci			   &hwpoison_filter_flags_value);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci#ifdef CONFIG_MEMCG
10362306a36Sopenharmony_ci	debugfs_create_u64("corrupt-filter-memcg", 0600, hwpoison_dir,
10462306a36Sopenharmony_ci			   &hwpoison_filter_memcg);
10562306a36Sopenharmony_ci#endif
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	return 0;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cimodule_init(pfn_inject_init);
11162306a36Sopenharmony_cimodule_exit(pfn_inject_exit);
11262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
113