diff options
| -rw-r--r-- | Documentation/vm/hwpoison.txt | 16 | ||||
| -rw-r--r-- | include/linux/mm.h | 1 | ||||
| -rw-r--r-- | include/linux/page-flags.h | 2 | ||||
| -rw-r--r-- | mm/hwpoison-inject.c | 36 | ||||
| -rw-r--r-- | mm/memory-failure.c | 68 |
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 | ||
| 100 | hwpoison-inject module through debugfs | 100 | hwpoison-inject module through debugfs |
| 101 | /sys/debug/hwpoison/corrupt-pfn | ||
| 102 | 101 | ||
| 103 | Inject hwpoison fault at PFN echoed into this file | 102 | /sys/debug/hwpoison/ |
| 104 | 103 | ||
| 104 | corrupt-pfn | ||
| 105 | |||
| 106 | Inject hwpoison fault at PFN echoed into this file. | ||
| 107 | |||
| 108 | unpoison-pfn | ||
| 109 | |||
| 110 | Software-unpoison page at PFN echoed into this file. This | ||
| 111 | way a page can be reused again. | ||
| 112 | This only works for Linux injected failures, not for real | ||
| 113 | memory failures. | ||
| 114 | |||
| 115 | Note these injection interfaces are not stable and might change between | ||
| 116 | kernel versions | ||
| 105 | 117 | ||
| 106 | Architecture specific MCE injector | 118 | Architecture 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 | }; |
| 1337 | extern void memory_failure(unsigned long pfn, int trapno); | 1337 | extern void memory_failure(unsigned long pfn, int trapno); |
| 1338 | extern int __memory_failure(unsigned long pfn, int trapno, int flags); | 1338 | extern int __memory_failure(unsigned long pfn, int trapno, int flags); |
| 1339 | extern int unpoison_memory(unsigned long pfn); | ||
| 1339 | extern int sysctl_memory_failure_early_kill; | 1340 | extern int sysctl_memory_failure_early_kill; |
| 1340 | extern int sysctl_memory_failure_recovery; | 1341 | extern int sysctl_memory_failure_recovery; |
| 1341 | extern void shake_page(struct page *p); | 1342 | extern 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 |
| 277 | PAGEFLAG(HWPoison, hwpoison) | 277 | PAGEFLAG(HWPoison, hwpoison) |
| 278 | TESTSETFLAG(HWPoison, hwpoison) | 278 | TESTSCFLAG(HWPoison, hwpoison) |
| 279 | #define __PG_HWPOISON (1UL << PG_hwpoison) | 279 | #define __PG_HWPOISON (1UL << PG_hwpoison) |
| 280 | #else | 280 | #else |
| 281 | PAGEFLAG_FALSE(HWPoison) | 281 | PAGEFLAG_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 | ||
| 7 | static struct dentry *hwpoison_dir, *corrupt_pfn; | 7 | static struct dentry *hwpoison_dir; |
| 8 | 8 | ||
| 9 | static int hwpoison_inject(void *data, u64 val) | 9 | static 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 | ||
| 17 | static 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 | |||
| 17 | DEFINE_SIMPLE_ATTRIBUTE(hwpoison_fops, NULL, hwpoison_inject, "%lli\n"); | 25 | DEFINE_SIMPLE_ATTRIBUTE(hwpoison_fops, NULL, hwpoison_inject, "%lli\n"); |
| 26 | DEFINE_SIMPLE_ATTRIBUTE(unpoison_fops, NULL, hwpoison_unpoison, "%lli\n"); | ||
| 18 | 27 | ||
| 19 | static void pfn_inject_exit(void) | 28 | static void pfn_inject_exit(void) |
| 20 | { | 29 | { |
| @@ -24,16 +33,31 @@ static void pfn_inject_exit(void) | |||
| 24 | 33 | ||
| 25 | static int pfn_inject_init(void) | 34 | static 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; |
| 58 | fail: | ||
| 59 | pfn_inject_exit(); | ||
| 60 | return -ENOMEM; | ||
| 37 | } | 61 | } |
| 38 | 62 | ||
| 39 | module_init(pfn_inject_init); | 63 | module_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 | */ | ||
| 919 | int 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 | } | ||
| 963 | EXPORT_SYMBOL(unpoison_memory); | ||
