]>
Commit | Line | Data |
---|---|---|
6784f7d0 AV |
1 | #include <linux/module.h> |
2 | #include <linux/sched.h> | |
304e629b AV |
3 | #include <linux/kthread.h> |
4 | #include <linux/workqueue.h> | |
72d7c3b3 YL |
5 | #include <linux/memblock.h> |
6 | ||
6784f7d0 AV |
7 | #include <asm/proto.h> |
8 | ||
9 | /* | |
10 | * Some BIOSes seem to corrupt the low 64k of memory during events | |
11 | * like suspend/resume and unplugging an HDMI cable. Reserve all | |
12 | * remaining free memory in that area and fill it with a distinct | |
13 | * pattern. | |
14 | */ | |
6784f7d0 AV |
15 | #define MAX_SCAN_AREAS 8 |
16 | ||
17 | static int __read_mostly memory_corruption_check = -1; | |
18 | ||
19 | static unsigned __read_mostly corruption_check_size = 64*1024; | |
20 | static unsigned __read_mostly corruption_check_period = 60; /* seconds */ | |
21 | ||
72d7c3b3 YL |
22 | static struct scan_area { |
23 | u64 addr; | |
24 | u64 size; | |
25 | } scan_areas[MAX_SCAN_AREAS]; | |
6784f7d0 AV |
26 | static int num_scan_areas; |
27 | ||
b43d196c | 28 | static __init int set_corruption_check(char *arg) |
6784f7d0 | 29 | { |
5abe68e4 SK |
30 | ssize_t ret; |
31 | unsigned long val; | |
6784f7d0 | 32 | |
5abe68e4 SK |
33 | ret = kstrtoul(arg, 10, &val); |
34 | if (ret) | |
35 | return ret; | |
6784f7d0 | 36 | |
5abe68e4 SK |
37 | memory_corruption_check = val; |
38 | return 0; | |
6784f7d0 AV |
39 | } |
40 | early_param("memory_corruption_check", set_corruption_check); | |
41 | ||
b43d196c | 42 | static __init int set_corruption_check_period(char *arg) |
6784f7d0 | 43 | { |
5abe68e4 SK |
44 | ssize_t ret; |
45 | unsigned long val; | |
6784f7d0 | 46 | |
5abe68e4 SK |
47 | ret = kstrtoul(arg, 10, &val); |
48 | if (ret) | |
49 | return ret; | |
6784f7d0 | 50 | |
5abe68e4 SK |
51 | corruption_check_period = val; |
52 | return 0; | |
6784f7d0 AV |
53 | } |
54 | early_param("memory_corruption_check_period", set_corruption_check_period); | |
55 | ||
b43d196c | 56 | static __init int set_corruption_check_size(char *arg) |
6784f7d0 AV |
57 | { |
58 | char *end; | |
59 | unsigned size; | |
60 | ||
61 | size = memparse(arg, &end); | |
62 | ||
63 | if (*end == '\0') | |
64 | corruption_check_size = size; | |
65 | ||
66 | return (size == corruption_check_size) ? 0 : -EINVAL; | |
67 | } | |
68 | early_param("memory_corruption_check_size", set_corruption_check_size); | |
69 | ||
70 | ||
71 | void __init setup_bios_corruption_check(void) | |
72 | { | |
8d89ac80 TH |
73 | phys_addr_t start, end; |
74 | u64 i; | |
6784f7d0 AV |
75 | |
76 | if (memory_corruption_check == -1) { | |
77 | memory_corruption_check = | |
78 | #ifdef CONFIG_X86_BOOTPARAM_MEMORY_CORRUPTION_CHECK | |
79 | 1 | |
80 | #else | |
81 | 0 | |
82 | #endif | |
83 | ; | |
84 | } | |
85 | ||
86 | if (corruption_check_size == 0) | |
87 | memory_corruption_check = 0; | |
88 | ||
89 | if (!memory_corruption_check) | |
90 | return; | |
91 | ||
92 | corruption_check_size = round_up(corruption_check_size, PAGE_SIZE); | |
93 | ||
9a28f9dc | 94 | for_each_free_mem_range(i, NUMA_NO_NODE, &start, &end, NULL) { |
8d89ac80 TH |
95 | start = clamp_t(phys_addr_t, round_up(start, PAGE_SIZE), |
96 | PAGE_SIZE, corruption_check_size); | |
97 | end = clamp_t(phys_addr_t, round_down(end, PAGE_SIZE), | |
98 | PAGE_SIZE, corruption_check_size); | |
99 | if (start >= end) | |
100 | continue; | |
6784f7d0 | 101 | |
24aa0788 | 102 | memblock_reserve(start, end - start); |
8d89ac80 TH |
103 | scan_areas[num_scan_areas].addr = start; |
104 | scan_areas[num_scan_areas].size = end - start; | |
6784f7d0 AV |
105 | |
106 | /* Assume we've already mapped this early memory */ | |
8d89ac80 | 107 | memset(__va(start), 0, end - start); |
6784f7d0 | 108 | |
8d89ac80 TH |
109 | if (++num_scan_areas >= MAX_SCAN_AREAS) |
110 | break; | |
6784f7d0 AV |
111 | } |
112 | ||
a7bd1daf NC |
113 | if (num_scan_areas) |
114 | printk(KERN_INFO "Scanning %d areas for low memory corruption\n", num_scan_areas); | |
6784f7d0 AV |
115 | } |
116 | ||
6784f7d0 AV |
117 | |
118 | void check_for_bios_corruption(void) | |
119 | { | |
120 | int i; | |
121 | int corruption = 0; | |
122 | ||
123 | if (!memory_corruption_check) | |
124 | return; | |
125 | ||
126 | for (i = 0; i < num_scan_areas; i++) { | |
127 | unsigned long *addr = __va(scan_areas[i].addr); | |
128 | unsigned long size = scan_areas[i].size; | |
129 | ||
130 | for (; size; addr++, size -= sizeof(unsigned long)) { | |
131 | if (!*addr) | |
132 | continue; | |
133 | printk(KERN_ERR "Corrupted low memory at %p (%lx phys) = %08lx\n", | |
134 | addr, __pa(addr), *addr); | |
135 | corruption = 1; | |
136 | *addr = 0; | |
137 | } | |
138 | } | |
139 | ||
b43d196c | 140 | WARN_ONCE(corruption, KERN_ERR "Memory corruption detected in low memory\n"); |
6784f7d0 AV |
141 | } |
142 | ||
304e629b AV |
143 | static void check_corruption(struct work_struct *dummy); |
144 | static DECLARE_DELAYED_WORK(bios_check_work, check_corruption); | |
145 | ||
146 | static void check_corruption(struct work_struct *dummy) | |
6784f7d0 AV |
147 | { |
148 | check_for_bios_corruption(); | |
304e629b | 149 | schedule_delayed_work(&bios_check_work, |
a7bd1daf | 150 | round_jiffies_relative(corruption_check_period*HZ)); |
6784f7d0 AV |
151 | } |
152 | ||
304e629b | 153 | static int start_periodic_check_for_corruption(void) |
6784f7d0 | 154 | { |
a7bd1daf | 155 | if (!num_scan_areas || !memory_corruption_check || corruption_check_period == 0) |
304e629b | 156 | return 0; |
6784f7d0 AV |
157 | |
158 | printk(KERN_INFO "Scanning for low memory corruption every %d seconds\n", | |
159 | corruption_check_period); | |
160 | ||
304e629b AV |
161 | /* First time we run the checks right away */ |
162 | schedule_delayed_work(&bios_check_work, 0); | |
163 | return 0; | |
6784f7d0 | 164 | } |
304e629b AV |
165 | |
166 | module_init(start_periodic_check_for_corruption); | |
6784f7d0 | 167 |