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