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 AV |
29 | { |
30 | char *end; | |
31 | ||
32 | memory_corruption_check = simple_strtol(arg, &end, 10); | |
33 | ||
34 | return (*end == 0) ? 0 : -EINVAL; | |
35 | } | |
36 | early_param("memory_corruption_check", set_corruption_check); | |
37 | ||
b43d196c | 38 | static __init int set_corruption_check_period(char *arg) |
6784f7d0 AV |
39 | { |
40 | char *end; | |
41 | ||
42 | corruption_check_period = simple_strtoul(arg, &end, 10); | |
43 | ||
44 | return (*end == 0) ? 0 : -EINVAL; | |
45 | } | |
46 | early_param("memory_corruption_check_period", set_corruption_check_period); | |
47 | ||
b43d196c | 48 | static __init int set_corruption_check_size(char *arg) |
6784f7d0 AV |
49 | { |
50 | char *end; | |
51 | unsigned size; | |
52 | ||
53 | size = memparse(arg, &end); | |
54 | ||
55 | if (*end == '\0') | |
56 | corruption_check_size = size; | |
57 | ||
58 | return (size == corruption_check_size) ? 0 : -EINVAL; | |
59 | } | |
60 | early_param("memory_corruption_check_size", set_corruption_check_size); | |
61 | ||
62 | ||
63 | void __init setup_bios_corruption_check(void) | |
64 | { | |
65 | u64 addr = PAGE_SIZE; /* assume first page is reserved anyway */ | |
66 | ||
67 | if (memory_corruption_check == -1) { | |
68 | memory_corruption_check = | |
69 | #ifdef CONFIG_X86_BOOTPARAM_MEMORY_CORRUPTION_CHECK | |
70 | 1 | |
71 | #else | |
72 | 0 | |
73 | #endif | |
74 | ; | |
75 | } | |
76 | ||
77 | if (corruption_check_size == 0) | |
78 | memory_corruption_check = 0; | |
79 | ||
80 | if (!memory_corruption_check) | |
81 | return; | |
82 | ||
83 | corruption_check_size = round_up(corruption_check_size, PAGE_SIZE); | |
84 | ||
85 | while (addr < corruption_check_size && num_scan_areas < MAX_SCAN_AREAS) { | |
86 | u64 size; | |
72d7c3b3 | 87 | addr = memblock_x86_find_in_range_size(addr, &size, PAGE_SIZE); |
6784f7d0 | 88 | |
72d7c3b3 | 89 | if (addr == MEMBLOCK_ERROR) |
6784f7d0 AV |
90 | break; |
91 | ||
6d7942dc YL |
92 | if (addr >= corruption_check_size) |
93 | break; | |
94 | ||
6784f7d0 AV |
95 | if ((addr + size) > corruption_check_size) |
96 | size = corruption_check_size - addr; | |
97 | ||
72d7c3b3 | 98 | memblock_x86_reserve_range(addr, addr + size, "SCAN RAM"); |
6784f7d0 AV |
99 | scan_areas[num_scan_areas].addr = addr; |
100 | scan_areas[num_scan_areas].size = size; | |
101 | num_scan_areas++; | |
102 | ||
103 | /* Assume we've already mapped this early memory */ | |
104 | memset(__va(addr), 0, size); | |
105 | ||
106 | addr += size; | |
107 | } | |
108 | ||
109 | printk(KERN_INFO "Scanning %d areas for low memory corruption\n", | |
110 | num_scan_areas); | |
6784f7d0 AV |
111 | } |
112 | ||
6784f7d0 AV |
113 | |
114 | void check_for_bios_corruption(void) | |
115 | { | |
116 | int i; | |
117 | int corruption = 0; | |
118 | ||
119 | if (!memory_corruption_check) | |
120 | return; | |
121 | ||
122 | for (i = 0; i < num_scan_areas; i++) { | |
123 | unsigned long *addr = __va(scan_areas[i].addr); | |
124 | unsigned long size = scan_areas[i].size; | |
125 | ||
126 | for (; size; addr++, size -= sizeof(unsigned long)) { | |
127 | if (!*addr) | |
128 | continue; | |
129 | printk(KERN_ERR "Corrupted low memory at %p (%lx phys) = %08lx\n", | |
130 | addr, __pa(addr), *addr); | |
131 | corruption = 1; | |
132 | *addr = 0; | |
133 | } | |
134 | } | |
135 | ||
b43d196c | 136 | WARN_ONCE(corruption, KERN_ERR "Memory corruption detected in low memory\n"); |
6784f7d0 AV |
137 | } |
138 | ||
304e629b AV |
139 | static void check_corruption(struct work_struct *dummy); |
140 | static DECLARE_DELAYED_WORK(bios_check_work, check_corruption); | |
141 | ||
142 | static void check_corruption(struct work_struct *dummy) | |
6784f7d0 AV |
143 | { |
144 | check_for_bios_corruption(); | |
304e629b AV |
145 | schedule_delayed_work(&bios_check_work, |
146 | round_jiffies_relative(corruption_check_period*HZ)); | |
6784f7d0 AV |
147 | } |
148 | ||
304e629b | 149 | static int start_periodic_check_for_corruption(void) |
6784f7d0 AV |
150 | { |
151 | if (!memory_corruption_check || corruption_check_period == 0) | |
304e629b | 152 | return 0; |
6784f7d0 AV |
153 | |
154 | printk(KERN_INFO "Scanning for low memory corruption every %d seconds\n", | |
155 | corruption_check_period); | |
156 | ||
304e629b AV |
157 | /* First time we run the checks right away */ |
158 | schedule_delayed_work(&bios_check_work, 0); | |
159 | return 0; | |
6784f7d0 | 160 | } |
304e629b AV |
161 | |
162 | module_init(start_periodic_check_for_corruption); | |
6784f7d0 | 163 |