summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/linux/mm.h14
-rw-r--r--kernel/kexec_core.c10
-rw-r--r--kernel/kexec_file.c20
-rw-r--r--mm/memory-failure.c40
4 files changed, 83 insertions, 1 deletions
diff --git a/include/linux/mm.h b/include/linux/mm.h
index c49ef99b4413..274fa880077c 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5225,6 +5225,8 @@ extern const struct attribute_group memory_failure_attr_group;
extern void memory_failure_queue(unsigned long pfn, int flags);
void num_poisoned_pages_inc(unsigned long pfn);
void num_poisoned_pages_sub(unsigned long pfn, long i);
+phys_addr_t range_first_hwpoison(phys_addr_t start, unsigned long size);
+phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size);
#else
static inline void memory_failure_queue(unsigned long pfn, int flags)
{
@@ -5237,6 +5239,18 @@ static inline void num_poisoned_pages_inc(unsigned long pfn)
static inline void num_poisoned_pages_sub(unsigned long pfn, long i)
{
}
+
+static inline phys_addr_t range_first_hwpoison(phys_addr_t start,
+ unsigned long size)
+{
+ return PHYS_ADDR_MAX;
+}
+
+static inline phys_addr_t range_last_hwpoison(phys_addr_t start,
+ unsigned long size)
+{
+ return PHYS_ADDR_MAX;
+}
#endif
#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG)
diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index dc770b9a6d05..7ee8c9f078f6 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -213,6 +213,16 @@ int sanity_check_segment_list(struct kimage *image)
#endif
/*
+ * Reject destinations that land on hardware-poisoned memory: the
+ * relocation copy would machine-check on the bad frame.
+ */
+ for (i = 0; i < nr_segments; i++) {
+ if (range_first_hwpoison(image->segment[i].mem,
+ image->segment[i].memsz) != PHYS_ADDR_MAX)
+ return -EHWPOISON;
+ }
+
+ /*
* The destination addresses are searched from system RAM rather than
* being allocated from the buddy allocator, so they are not guaranteed
* to be accepted by the current kernel. Accept the destination
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index c11a815e2235..d218a53f5f6c 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -477,6 +477,7 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
{
struct kimage *image = kbuf->image;
unsigned long temp_start, temp_end;
+ phys_addr_t poison;
temp_end = min(end, kbuf->buf_max);
temp_start = temp_end - kbuf->memsz + 1;
@@ -486,7 +487,9 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
/* align down start */
temp_start = ALIGN_DOWN(temp_start, kbuf->buf_align);
- if (temp_start < start || temp_start < kbuf->buf_min)
+ /* A candidate above the range means the walk wrapped around */
+ if (temp_start < start || temp_start < kbuf->buf_min ||
+ temp_start > end)
return 0;
temp_end = temp_start + kbuf->memsz - 1;
@@ -506,6 +509,13 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
continue;
}
+ poison = range_first_hwpoison(temp_start, kbuf->memsz);
+ if (poison != PHYS_ADDR_MAX) {
+ /* we hit a poisoned page */
+ temp_start = poison - kbuf->memsz;
+ continue;
+ }
+
/* We found a suitable memory range */
break;
} while (1);
@@ -522,6 +532,7 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end,
{
struct kimage *image = kbuf->image;
unsigned long temp_start, temp_end;
+ phys_addr_t poison;
temp_start = max(start, kbuf->buf_min);
@@ -548,6 +559,13 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end,
continue;
}
+ poison = range_last_hwpoison(temp_start, kbuf->memsz);
+ if (poison != PHYS_ADDR_MAX) {
+ /* we hit a poisoned page */
+ temp_start = poison + PAGE_SIZE;
+ continue;
+ }
+
/* We found a suitable memory range */
break;
} while (1);
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index a8b03e2920ba..a2ca8df501ca 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -96,6 +96,46 @@ void num_poisoned_pages_sub(unsigned long pfn, long i)
memblk_nr_poison_sub(pfn, i);
}
+/*
+ * Return the first or the last hardware-poisoned online page in [start,
+ * start + size), or PHYS_ADDR_MAX if the range is clean.
+ */
+static phys_addr_t range_hwpoison(phys_addr_t start, unsigned long size,
+ bool first)
+{
+ phys_addr_t poison = PHYS_ADDR_MAX;
+ unsigned long pfn, end_pfn;
+
+ if (!size || !atomic_long_read(&num_poisoned_pages))
+ return poison;
+
+ end_pfn = PHYS_PFN(start + size - 1);
+ for (pfn = PHYS_PFN(start); pfn <= end_pfn; pfn++) {
+ struct page *page = pfn_to_online_page(pfn);
+
+ if (page && is_page_hwpoison(page)) {
+ if (first)
+ return PFN_PHYS(pfn);
+
+ poison = PFN_PHYS(pfn);
+ }
+
+ cond_resched();
+ }
+
+ return poison;
+}
+
+phys_addr_t range_first_hwpoison(phys_addr_t start, unsigned long size)
+{
+ return range_hwpoison(start, size, true);
+}
+
+phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size)
+{
+ return range_hwpoison(start, size, false);
+}
+
/**
* MF_ATTR_RO - Create sysfs entry for each memory failure statistics.
* @_name: name of the file in the per NUMA sysfs directory.