forked from Minki/linux
fc6daaf931
Some high end Intel Xeon systems report uncorrectable memory errors as a recoverable machine check. Linux has included code for some time to process these and just signal the affected processes (or even recover completely if the error was in a read only page that can be replaced by reading from disk). But we have no recovery path for errors encountered during kernel code execution. Except for some very specific cases were are unlikely to ever be able to recover. Enter memory mirroring. Actually 3rd generation of memory mirroing. Gen1: All memory is mirrored Pro: No s/w enabling - h/w just gets good data from other side of the mirror Con: Halves effective memory capacity available to OS/applications Gen2: Partial memory mirror - just mirror memory begind some memory controllers Pro: Keep more of the capacity Con: Nightmare to enable. Have to choose between allocating from mirrored memory for safety vs. NUMA local memory for performance Gen3: Address range partial memory mirror - some mirror on each memory controller Pro: Can tune the amount of mirror and keep NUMA performance Con: I have to write memory management code to implement The current plan is just to use mirrored memory for kernel allocations. This has been broken into two phases: 1) This patch series - find the mirrored memory, use it for boot time allocations 2) Wade into mm/page_alloc.c and define a ZONE_MIRROR to pick up the unused mirrored memory from mm/memblock.c and only give it out to select kernel allocations (this is still being scoped because page_alloc.c is scary). This patch (of 3): Add extra "flags" to memblock to allow selection of memory based on attribute. No functional changes Signed-off-by: Tony Luck <tony.luck@intel.com> Cc: Xishi Qiu <qiuxishi@huawei.com> Cc: Hanjun Guo <guohanjun@huawei.com> Cc: Xiexiuqi <xiexiuqi@huawei.com> Cc: Ingo Molnar <mingo@elte.hu> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Yinghai Lu <yinghai@kernel.org> Cc: Naoya Horiguchi <nao.horiguchi@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
169 lines
3.8 KiB
C
169 lines
3.8 KiB
C
#include <linux/module.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;
|
|
}
|
|
|
|
module_init(start_periodic_check_for_corruption);
|
|
|