Merge branch 'linus' into x86/memory-corruption-check
authorIngo Molnar <mingo@elte.hu>
Sun, 12 Oct 2008 13:05:39 +0000 (15:05 +0200)
committerIngo Molnar <mingo@elte.hu>
Sun, 12 Oct 2008 13:05:39 +0000 (15:05 +0200)
Documentation/kernel-parameters.txt
arch/x86/Kconfig
arch/x86/kernel/setup.c
arch/x86/mm/init_32.c
arch/x86/mm/init_64.c
drivers/base/power/main.c
include/linux/kernel.h

index 25efbaf..bade177 100644 (file)
@@ -1231,6 +1231,29 @@ and is between 256 and 4096 characters. It is defined in the file
                                 or
                                 memmap=0x10000$0x18690000
 
+       memory_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.  Enabling this will
+                       both detect corruption and prevent the kernel
+                       from using the memory being corrupted.
+                       However, its intended as a diagnostic tool; if
+                       repeatable BIOS-originated corruption always
+                       affects the same memory, you can use memmap=
+                       to prevent the kernel from using that memory.
+
+       memory_corruption_check_size=size [X86]
+                       By default it checks for corruption in the low
+                       64k, making this memory unavailable for normal
+                       use.  Use this parameter to scan for
+                       corruption in more or less memory.
+
+       memory_corruption_check_period=seconds [X86]
+                       By default it checks for corruption every 60
+                       seconds.  Use this parameter to check at some
+                       other rate.  0 disables periodic checking.
+
        memtest=        [KNL,X86] Enable memtest
                        Format: <integer>
                        range: 0,4 : pattern number
index 44d4f21..25e2f7a 100644 (file)
@@ -1061,6 +1061,56 @@ config HIGHPTE
          low memory.  Setting this option will put user-space page table
          entries in high memory.
 
+config X86_CHECK_BIOS_CORRUPTION
+        bool "Check for low memory corruption"
+       help
+        Periodically check for memory corruption in low memory, which
+        is suspected to be caused by BIOS.  Even when enabled in the
+        configuration, it is disabled at runtime.  Enable it by
+        setting "memory_corruption_check=1" on the kernel command
+        line.  By default it scans the low 64k of memory every 60
+        seconds; see the memory_corruption_check_size and
+        memory_corruption_check_period parameters in
+        Documentation/kernel-parameters.txt to adjust this.
+
+        When enabled with the default parameters, this option has
+        almost no overhead, as it reserves a relatively small amount
+        of memory and scans it infrequently.  It both detects corruption
+        and prevents it from affecting the running system.
+
+        It is, however, intended as a diagnostic tool; if repeatable
+        BIOS-originated corruption always affects the same memory,
+        you can use memmap= to prevent the kernel from using that
+        memory.
+
+config X86_BOOTPARAM_MEMORY_CORRUPTION_CHECK
+        bool "Set the default setting of memory_corruption_check"
+       depends on X86_CHECK_BIOS_CORRUPTION
+       default y
+       help
+        Set whether the default state of memory_corruption_check is
+        on or off.
+
+config X86_RESERVE_LOW_64K
+        bool "Reserve low 64K of RAM on AMI/Phoenix BIOSen"
+       default y
+       help
+        Reserve the first 64K of physical RAM on BIOSes that are known
+        to potentially corrupt that memory range. A numbers of BIOSes are
+        known to utilize this area during suspend/resume, so it must not
+        be used by the kernel.
+
+        Set this to N if you are absolutely sure that you trust the BIOS
+        to get all its memory reservations and usages right.
+
+        If you have doubts about the BIOS (e.g. suspend/resume does not
+        work or there's kernel crashes after certain hardware hotplug
+        events) and it's not AMI or Phoenix, then you might want to enable
+        X86_CHECK_BIOS_CORRUPTION=y to allow the kernel to check typical
+        corruption patterns.
+
+        Say Y if unsure.
+
 config MATH_EMULATION
        bool
        prompt "Math emulation" if X86_32
index 46c98ef..21b8e0a 100644 (file)
@@ -581,6 +581,190 @@ 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 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 e820entry scan_areas[MAX_SCAN_AREAS];
+static int num_scan_areas;
+
+
+static int set_corruption_check(char *arg)
+{
+       char *end;
+
+       memory_corruption_check = simple_strtol(arg, &end, 10);
+
+       return (*end == 0) ? 0 : -EINVAL;
+}
+early_param("memory_corruption_check", set_corruption_check);
+
+static int set_corruption_check_period(char *arg)
+{
+       char *end;
+
+       corruption_check_period = simple_strtoul(arg, &end, 10);
+
+       return (*end == 0) ? 0 : -EINVAL;
+}
+early_param("memory_corruption_check_period", set_corruption_check_period);
+
+static 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);
+
+
+static void __init setup_bios_corruption_check(void)
+{
+       u64 addr = PAGE_SIZE;   /* assume first page is reserved anyway */
+
+       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);
+
+       while(addr < corruption_check_size && num_scan_areas < MAX_SCAN_AREAS) {
+               u64 size;
+               addr = find_e820_area_size(addr, &size, PAGE_SIZE);
+
+               if (addr == 0)
+                       break;
+
+               if ((addr + size) > corruption_check_size)
+                       size = corruption_check_size - 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 low memory corruption\n",
+              num_scan_areas);
+       update_e820();
+}
+
+static struct timer_list periodic_check_timer;
+
+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(corruption, KERN_ERR "Memory corruption detected in low memory\n");
+}
+
+static void periodic_check_for_corruption(unsigned long data)
+{
+       check_for_bios_corruption();
+       mod_timer(&periodic_check_timer, round_jiffies(jiffies + corruption_check_period*HZ));
+}
+
+void start_periodic_check_for_corruption(void)
+{
+       if (!memory_corruption_check || corruption_check_period == 0)
+               return;
+
+       printk(KERN_INFO "Scanning for low memory corruption every %d seconds\n",
+              corruption_check_period);
+
+       init_timer(&periodic_check_timer);
+       periodic_check_timer.function = &periodic_check_for_corruption;
+       periodic_check_for_corruption(0);
+}
+#endif
+
+static int __init dmi_low_memory_corruption(const struct dmi_system_id *d)
+{
+       printk(KERN_NOTICE
+               "%s detected: BIOS may corrupt low RAM, working it around.\n",
+               d->ident);
+
+       e820_update_range(0, 0x10000, E820_RAM, E820_RESERVED);
+       sanitize_e820_map(e820.map, ARRAY_SIZE(e820.map), &e820.nr_map);
+
+       return 0;
+}
+
+/* List of systems that have known low memory corruption BIOS problems */
+static struct dmi_system_id __initdata bad_bios_dmi_table[] = {
+#ifdef CONFIG_X86_RESERVE_LOW_64K
+       {
+               .callback = dmi_low_memory_corruption,
+               .ident = "AMI BIOS",
+               .matches = {
+                       DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
+               },
+       },
+       {
+               .callback = dmi_low_memory_corruption,
+               .ident = "Phoenix BIOS",
+               .matches = {
+                       DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies, LTD"),
+               },
+       },
+#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
@@ -715,6 +899,10 @@ void __init setup_arch(char **cmdline_p)
 
        finish_e820_parsing();
 
+       dmi_scan_machine();
+
+       dmi_check_system(bad_bios_dmi_table);
+
 #ifdef CONFIG_X86_32
        probe_roms();
 #endif
@@ -771,6 +959,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;
@@ -799,8 +991,6 @@ void __init setup_arch(char **cmdline_p)
        vsmp_init();
 #endif
 
-       dmi_scan_machine();
-
        io_delay_init();
 
        /*
@@ -903,3 +1093,5 @@ void __init setup_arch(char **cmdline_p)
 #endif
 #endif
 }
+
+
index c3789bb..7e05462 100644 (file)
@@ -969,6 +969,8 @@ void __init mem_init(void)
        int codesize, reservedpages, datasize, initsize;
        int tmp;
 
+       start_periodic_check_for_corruption();
+
 #ifdef CONFIG_FLATMEM
        BUG_ON(!mem_map);
 #endif
index 83e13f2..d84d3e9 100644 (file)
@@ -881,6 +881,8 @@ void __init mem_init(void)
 {
        long codesize, reservedpages, datasize, initsize;
 
+       start_periodic_check_for_corruption();
+
        pci_iommu_alloc();
 
        /* clear_bss() already clear the empty_zero_page */
index 273a944..bf6d355 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 2651f80..50873b2 100644 (file)
@@ -240,6 +240,23 @@ 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);
+void start_periodic_check_for_corruption(void);
+#else
+static inline void check_for_bios_corruption(void)
+{
+}
+
+static inline void start_periodic_check_for_corruption(void)
+{
+}
+#endif
+
 /* Values used for system_state */
 extern enum system_states {
        SYSTEM_BOOTING,