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