x86: check for and defend against BIOS memory corruption
authorJeremy Fitzhardinge <jeremy@goop.org>
Sun, 7 Sep 2008 08:51:32 +0000 (01:51 -0700)
committerIngo Molnar <mingo@elte.hu>
Sun, 7 Sep 2008 15:39:59 +0000 (17:39 +0200)
Some BIOSes have been observed to corrupt memory in the low 64k.  This
change:
 - Reserves all memory which does not have to be in that area, to
   prevent it from being used as general memory by the kernel.  Things
   like the SMP trampoline are still in the memory, however.
 - Clears the reserved memory so we can observe changes to it.
 - Adds a function check_for_bios_corruption() which checks and reports on
   memory becoming unexpectedly non-zero.  Currently it's called in the
   x86 fault handler, and the powermanagement debug output.

Signed-off-by: Jeremy Fitzhardinge <jeremy@goop.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Documentation/kernel-parameters.txt
arch/x86/Kconfig
arch/x86/kernel/setup.c
arch/x86/mm/fault.c
drivers/base/power/main.c
include/linux/kernel.h

index 1150444a21ab64140f13e706797f16664e28244f..df48af505d15466b9f2312cda9feb7135ad5155c 100644 (file)
@@ -360,6 +360,11 @@ and is between 256 and 4096 characters. It is defined in the file
                        Format: <io>,<irq>,<mode>
                        See header of drivers/net/hamradio/baycom_ser_hdx.c.
 
+       bios_corruption_check=0/1 [X86]
+                       Some BIOSes seem to corrupt the first 64k of memory
+                       when doing things like suspend/resume.  Setting this
+                       option will scan the memory looking for corruption.
+
        boot_delay=     Milliseconds to delay each printk during boot.
                        Values larger than 10 seconds (10000) are changed to
                        no delay (0).
index ed92864d1325c09092f3e8ed4918f26341f23161..1bb52e2ca02e710c3604d7531c4931d14a08afcb 100644 (file)
@@ -201,6 +201,9 @@ config X86_TRAMPOLINE
        depends on X86_SMP || (X86_VOYAGER && SMP) || (64BIT && ACPI_SLEEP)
        default y
 
+config X86_CHECK_BIOS_CORRUPTION
+        def_bool y
+
 config KTIME_SCALAR
        def_bool X86_32
 source "init/Kconfig"
index 362d4e7f2d389b81e345fa27b13db78e80c5980a..ee89ebc5aabc7679e46bff29db43e062176c0a26 100644 (file)
@@ -578,6 +578,89 @@ static struct x86_quirks default_x86_quirks __initdata;
 
 struct x86_quirks *x86_quirks __initdata = &default_x86_quirks;
 
+/*
+ * 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.
+ */
+#ifdef CONFIG_X86_CHECK_BIOS_CORRUPTION
+#define MAX_SCAN_AREAS 8
+static struct e820entry scan_areas[MAX_SCAN_AREAS];
+static int num_scan_areas;
+
+static void __init setup_bios_corruption_check(void)
+{
+       u64 addr = PAGE_SIZE;   /* assume first page is reserved anyway */
+
+       while(addr < 0x10000 && num_scan_areas < MAX_SCAN_AREAS) {
+               u64 size;
+               addr = find_e820_area_size(addr, &size, PAGE_SIZE);
+
+               if (addr == 0)
+                       break;
+
+               if ((addr + size) > 0x10000)
+                       size = 0x10000 - addr;
+
+               if (size == 0)
+                       break;
+
+               e820_update_range(addr, size, E820_RAM, E820_RESERVED);
+               scan_areas[num_scan_areas].addr = addr;
+               scan_areas[num_scan_areas].size = size;
+               num_scan_areas++;
+
+               /* Assume we've already mapped this early memory */
+               memset(__va(addr), 0, size);
+
+               addr += size;
+       }
+
+       printk(KERN_INFO "scanning %d areas for BIOS corruption\n",
+              num_scan_areas);
+       update_e820();
+}
+
+static int __read_mostly bios_corruption_check = 1;
+
+void check_for_bios_corruption(void)
+{
+       int i;
+       int corruption = 0;
+
+       if (!bios_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;
+               }
+       }
+
+       if (corruption)
+               dump_stack();
+}
+
+static int set_bios_corruption_check(char *arg)
+{
+       char *end;
+
+       bios_corruption_check = simple_strtol(arg, &end, 10);
+
+       return (*end == 0) ? 0 : -EINVAL;
+}
+early_param("bios_corruption_check", set_bios_corruption_check);
+#endif
+
 /*
  * Determine if we were loaded by an EFI loader.  If so, then we have also been
  * passed the efi memmap, systab, etc., so we should use these data structures
@@ -750,6 +833,10 @@ void __init setup_arch(char **cmdline_p)
        high_memory = (void *)__va(max_pfn * PAGE_SIZE - 1) + 1;
 #endif
 
+#ifdef CONFIG_X86_CHECK_BIOS_CORRUPTION
+       setup_bios_corruption_check();
+#endif
+
        /* max_pfn_mapped is updated here */
        max_low_pfn_mapped = init_memory_mapping(0, max_low_pfn<<PAGE_SHIFT);
        max_pfn_mapped = max_low_pfn_mapped;
index 455f3fe67b42412c8acda3eb63e33678ebec1845..5140bdf03020d7311ffe8e03cd069b1e4125577a 100644 (file)
@@ -848,6 +848,8 @@ no_context:
  * Oops. The kernel tried to access some bad page. We'll have to
  * terminate things with extreme prejudice.
  */
+       check_for_bios_corruption();
+
 #ifdef CONFIG_X86_32
        bust_spinlocks(1);
 #else
index 273a944d4040e861afa6a1ea6c601264eec6c4a7..bf6d3554e506c46b8da6dcb346dce0bffeb0bfce 100644 (file)
@@ -254,6 +254,7 @@ static char *pm_verb(int event)
 
 static void pm_dev_dbg(struct device *dev, pm_message_t state, char *info)
 {
+       check_for_bios_corruption();
        dev_dbg(dev, "%s%s%s\n", info, pm_verb(state.event),
                ((state.event & PM_EVENT_SLEEP) && device_may_wakeup(dev)) ?
                ", may wakeup" : "");
index 2651f805ba6d771b9ec1f26078609aebdb198853..8017129e6b631eb67779cb5dec0c9b2b54a5a4ae 100644 (file)
@@ -240,6 +240,18 @@ extern const char *print_tainted(void);
 extern void add_taint(unsigned);
 extern int root_mountflags;
 
+#ifdef CONFIG_X86_CHECK_BIOS_CORRUPTION
+/*
+ * This is obviously not a great place for this, but we want to be
+ * able to scatter it around anywhere in the kernel.
+ */
+void check_for_bios_corruption(void);
+#else
+static inline void check_for_bios_corruption(void)
+{
+}
+#endif
+
 /* Values used for system_state */
 extern enum system_states {
        SYSTEM_BOOTING,