18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/* Inject a hwpoison memory failure on a arbitrary pfn */
38c2ecf20Sopenharmony_ci#include <linux/module.h>
48c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
58c2ecf20Sopenharmony_ci#include <linux/kernel.h>
68c2ecf20Sopenharmony_ci#include <linux/mm.h>
78c2ecf20Sopenharmony_ci#include <linux/swap.h>
88c2ecf20Sopenharmony_ci#include <linux/pagemap.h>
98c2ecf20Sopenharmony_ci#include <linux/hugetlb.h>
108c2ecf20Sopenharmony_ci#include "internal.h"
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_cistatic struct dentry *hwpoison_dir;
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cistatic int hwpoison_inject(void *data, u64 val)
158c2ecf20Sopenharmony_ci{
168c2ecf20Sopenharmony_ci	unsigned long pfn = val;
178c2ecf20Sopenharmony_ci	struct page *p;
188c2ecf20Sopenharmony_ci	struct page *hpage;
198c2ecf20Sopenharmony_ci	int err;
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
228c2ecf20Sopenharmony_ci		return -EPERM;
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci	if (!pfn_valid(pfn))
258c2ecf20Sopenharmony_ci		return -ENXIO;
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci	p = pfn_to_page(pfn);
288c2ecf20Sopenharmony_ci	hpage = compound_head(p);
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	if (!hwpoison_filter_enable)
318c2ecf20Sopenharmony_ci		goto inject;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	shake_page(hpage, 0);
348c2ecf20Sopenharmony_ci	/*
358c2ecf20Sopenharmony_ci	 * This implies unable to support non-LRU pages.
368c2ecf20Sopenharmony_ci	 */
378c2ecf20Sopenharmony_ci	if (!PageLRU(hpage) && !PageHuge(p))
388c2ecf20Sopenharmony_ci		return 0;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	/*
418c2ecf20Sopenharmony_ci	 * do a racy check to make sure PG_hwpoison will only be set for
428c2ecf20Sopenharmony_ci	 * the targeted owner (or on a free page).
438c2ecf20Sopenharmony_ci	 * memory_failure() will redo the check reliably inside page lock.
448c2ecf20Sopenharmony_ci	 */
458c2ecf20Sopenharmony_ci	err = hwpoison_filter(hpage);
468c2ecf20Sopenharmony_ci	if (err)
478c2ecf20Sopenharmony_ci		return 0;
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ciinject:
508c2ecf20Sopenharmony_ci	pr_info("Injecting memory failure at pfn %#lx\n", pfn);
518c2ecf20Sopenharmony_ci	return memory_failure(pfn, 0);
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistatic int hwpoison_unpoison(void *data, u64 val)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
578c2ecf20Sopenharmony_ci		return -EPERM;
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	return unpoison_memory(val);
608c2ecf20Sopenharmony_ci}
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ciDEFINE_DEBUGFS_ATTRIBUTE(hwpoison_fops, NULL, hwpoison_inject, "%lli\n");
638c2ecf20Sopenharmony_ciDEFINE_DEBUGFS_ATTRIBUTE(unpoison_fops, NULL, hwpoison_unpoison, "%lli\n");
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic void pfn_inject_exit(void)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	debugfs_remove_recursive(hwpoison_dir);
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic int pfn_inject_init(void)
718c2ecf20Sopenharmony_ci{
728c2ecf20Sopenharmony_ci	hwpoison_dir = debugfs_create_dir("hwpoison", NULL);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	/*
758c2ecf20Sopenharmony_ci	 * Note that the below poison/unpoison interfaces do not involve
768c2ecf20Sopenharmony_ci	 * hardware status change, hence do not require hardware support.
778c2ecf20Sopenharmony_ci	 * They are mainly for testing hwpoison in software level.
788c2ecf20Sopenharmony_ci	 */
798c2ecf20Sopenharmony_ci	debugfs_create_file("corrupt-pfn", 0200, hwpoison_dir, NULL,
808c2ecf20Sopenharmony_ci			    &hwpoison_fops);
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	debugfs_create_file("unpoison-pfn", 0200, hwpoison_dir, NULL,
838c2ecf20Sopenharmony_ci			    &unpoison_fops);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	debugfs_create_u32("corrupt-filter-enable", 0600, hwpoison_dir,
868c2ecf20Sopenharmony_ci			   &hwpoison_filter_enable);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	debugfs_create_u32("corrupt-filter-dev-major", 0600, hwpoison_dir,
898c2ecf20Sopenharmony_ci			   &hwpoison_filter_dev_major);
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	debugfs_create_u32("corrupt-filter-dev-minor", 0600, hwpoison_dir,
928c2ecf20Sopenharmony_ci			   &hwpoison_filter_dev_minor);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	debugfs_create_u64("corrupt-filter-flags-mask", 0600, hwpoison_dir,
958c2ecf20Sopenharmony_ci			   &hwpoison_filter_flags_mask);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	debugfs_create_u64("corrupt-filter-flags-value", 0600, hwpoison_dir,
988c2ecf20Sopenharmony_ci			   &hwpoison_filter_flags_value);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci#ifdef CONFIG_MEMCG
1018c2ecf20Sopenharmony_ci	debugfs_create_u64("corrupt-filter-memcg", 0600, hwpoison_dir,
1028c2ecf20Sopenharmony_ci			   &hwpoison_filter_memcg);
1038c2ecf20Sopenharmony_ci#endif
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return 0;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cimodule_init(pfn_inject_init);
1098c2ecf20Sopenharmony_cimodule_exit(pfn_inject_exit);
1108c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
111