aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/vm/hwpoison.txt16
-rw-r--r--include/linux/mm.h1
-rw-r--r--include/linux/page-flags.h2
-rw-r--r--mm/hwpoison-inject.c36
-rw-r--r--mm/memory-failure.c68
5 files changed, 114 insertions, 9 deletions
diff --git a/Documentation/vm/hwpoison.txt b/Documentation/vm/hwpoison.txt
index 3ffadf8da61f..f047e75acb23 100644
--- a/Documentation/vm/hwpoison.txt
+++ b/Documentation/vm/hwpoison.txt
@@ -98,10 +98,22 @@ madvise(MADV_POISON, ....)
98 98
99 99
100hwpoison-inject module through debugfs 100hwpoison-inject module through debugfs
101 /sys/debug/hwpoison/corrupt-pfn
102 101
103Inject hwpoison fault at PFN echoed into this file 102/sys/debug/hwpoison/
104 103
104corrupt-pfn
105
106Inject hwpoison fault at PFN echoed into this file.
107
108unpoison-pfn
109
110Software-unpoison page at PFN echoed into this file. This
111way a page can be reused again.
112This only works for Linux injected failures, not for real
113memory failures.
114
115Note these injection interfaces are not stable and might change between
116kernel versions
105 117
106Architecture specific MCE injector 118Architecture specific MCE injector
107 119
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 135e19198cd3..8cdb941fc7b5 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1336,6 +1336,7 @@ enum mf_flags {
1336}; 1336};
1337extern void memory_failure(unsigned long pfn, int trapno); 1337extern void memory_failure(unsigned long pfn, int trapno);
1338extern int __memory_failure(unsigned long pfn, int trapno, int flags); 1338extern int __memory_failure(unsigned long pfn, int trapno, int flags);
1339extern int unpoison_memory(unsigned long pfn);
1339extern int sysctl_memory_failure_early_kill; 1340extern int sysctl_memory_failure_early_kill;
1340extern int sysctl_memory_failure_recovery; 1341extern int sysctl_memory_failure_recovery;
1341extern void shake_page(struct page *p); 1342extern void shake_page(struct page *p);
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 49e907bd067f..f9df6308af95 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -275,7 +275,7 @@ PAGEFLAG_FALSE(Uncached)
275 275
276#ifdef CONFIG_MEMORY_FAILURE 276#ifdef CONFIG_MEMORY_FAILURE
277PAGEFLAG(HWPoison, hwpoison) 277PAGEFLAG(HWPoison, hwpoison)
278TESTSETFLAG(HWPoison, hwpoison) 278TESTSCFLAG(HWPoison, hwpoison)
279#define __PG_HWPOISON (1UL << PG_hwpoison) 279#define __PG_HWPOISON (1UL << PG_hwpoison)
280#else 280#else
281PAGEFLAG_FALSE(HWPoison) 281PAGEFLAG_FALSE(HWPoison)
diff --git a/mm/hwpoison-inject.c b/mm/hwpoison-inject.c
index e1d85137f086..6e35e563bf50 100644
--- a/mm/hwpoison-inject.c
+++ b/mm/hwpoison-inject.c
@@ -4,7 +4,7 @@
4#include <linux/kernel.h> 4#include <linux/kernel.h>
5#include <linux/mm.h> 5#include <linux/mm.h>
6 6
7static struct dentry *hwpoison_dir, *corrupt_pfn; 7static struct dentry *hwpoison_dir;
8 8
9static int hwpoison_inject(void *data, u64 val) 9static int hwpoison_inject(void *data, u64 val)
10{ 10{
@@ -14,7 +14,16 @@ static int hwpoison_inject(void *data, u64 val)
14 return __memory_failure(val, 18, 0); 14 return __memory_failure(val, 18, 0);
15} 15}
16 16
17static int hwpoison_unpoison(void *data, u64 val)
18{
19 if (!capable(CAP_SYS_ADMIN))
20 return -EPERM;
21
22 return unpoison_memory(val);
23}
24
17DEFINE_SIMPLE_ATTRIBUTE(hwpoison_fops, NULL, hwpoison_inject, "%lli\n"); 25DEFINE_SIMPLE_ATTRIBUTE(hwpoison_fops, NULL, hwpoison_inject, "%lli\n");
26DEFINE_SIMPLE_ATTRIBUTE(unpoison_fops, NULL, hwpoison_unpoison, "%lli\n");
18 27
19static void pfn_inject_exit(void) 28static void pfn_inject_exit(void)
20{ 29{
@@ -24,16 +33,31 @@ static void pfn_inject_exit(void)
24 33
25static int pfn_inject_init(void) 34static int pfn_inject_init(void)
26{ 35{
36 struct dentry *dentry;
37
27 hwpoison_dir = debugfs_create_dir("hwpoison", NULL); 38 hwpoison_dir = debugfs_create_dir("hwpoison", NULL);
28 if (hwpoison_dir == NULL) 39 if (hwpoison_dir == NULL)
29 return -ENOMEM; 40 return -ENOMEM;
30 corrupt_pfn = debugfs_create_file("corrupt-pfn", 0600, hwpoison_dir, 41
42 /*
43 * Note that the below poison/unpoison interfaces do not involve
44 * hardware status change, hence do not require hardware support.
45 * They are mainly for testing hwpoison in software level.
46 */
47 dentry = debugfs_create_file("corrupt-pfn", 0600, hwpoison_dir,
31 NULL, &hwpoison_fops); 48 NULL, &hwpoison_fops);
32 if (corrupt_pfn == NULL) { 49 if (!dentry)
33 pfn_inject_exit(); 50 goto fail;
34 return -ENOMEM; 51
35 } 52 dentry = debugfs_create_file("unpoison-pfn", 0600, hwpoison_dir,
53 NULL, &unpoison_fops);
54 if (!dentry)
55 goto fail;
56
36 return 0; 57 return 0;
58fail:
59 pfn_inject_exit();
60 return -ENOMEM;
37} 61}
38 62
39module_init(pfn_inject_init); 63module_init(pfn_inject_init);
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 5055b940df5f..ed6e91c87a54 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -838,6 +838,16 @@ int __memory_failure(unsigned long pfn, int trapno, int flags)
838 * and in many cases impossible, so we just avoid it here. 838 * and in many cases impossible, so we just avoid it here.
839 */ 839 */
840 lock_page_nosync(p); 840 lock_page_nosync(p);
841
842 /*
843 * unpoison always clear PG_hwpoison inside page lock
844 */
845 if (!PageHWPoison(p)) {
846 action_result(pfn, "unpoisoned", IGNORED);
847 res = 0;
848 goto out;
849 }
850
841 wait_on_page_writeback(p); 851 wait_on_page_writeback(p);
842 852
843 /* 853 /*
@@ -893,3 +903,61 @@ void memory_failure(unsigned long pfn, int trapno)
893{ 903{
894 __memory_failure(pfn, trapno, 0); 904 __memory_failure(pfn, trapno, 0);
895} 905}
906
907/**
908 * unpoison_memory - Unpoison a previously poisoned page
909 * @pfn: Page number of the to be unpoisoned page
910 *
911 * Software-unpoison a page that has been poisoned by
912 * memory_failure() earlier.
913 *
914 * This is only done on the software-level, so it only works
915 * for linux injected failures, not real hardware failures
916 *
917 * Returns 0 for success, otherwise -errno.
918 */
919int unpoison_memory(unsigned long pfn)
920{
921 struct page *page;
922 struct page *p;
923 int freeit = 0;
924
925 if (!pfn_valid(pfn))
926 return -ENXIO;
927
928 p = pfn_to_page(pfn);
929 page = compound_head(p);
930
931 if (!PageHWPoison(p)) {
932 pr_debug("MCE: Page was already unpoisoned %#lx\n", pfn);
933 return 0;
934 }
935
936 if (!get_page_unless_zero(page)) {
937 if (TestClearPageHWPoison(p))
938 atomic_long_dec(&mce_bad_pages);
939 pr_debug("MCE: Software-unpoisoned free page %#lx\n", pfn);
940 return 0;
941 }
942
943 lock_page_nosync(page);
944 /*
945 * This test is racy because PG_hwpoison is set outside of page lock.
946 * That's acceptable because that won't trigger kernel panic. Instead,
947 * the PG_hwpoison page will be caught and isolated on the entrance to
948 * the free buddy page pool.
949 */
950 if (TestClearPageHWPoison(p)) {
951 pr_debug("MCE: Software-unpoisoned page %#lx\n", pfn);
952 atomic_long_dec(&mce_bad_pages);
953 freeit = 1;
954 }
955 unlock_page(page);
956
957 put_page(page);
958 if (freeit)
959 put_page(page);
960
961 return 0;
962}
963EXPORT_SYMBOL(unpoison_memory);