diff --git a/include/linux/mm.h b/include/linux/mm.h index dd09c438fa23..f0ff4c9c5489 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -5254,6 +5254,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) { @@ -5266,6 +5268,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 @@ -212,6 +212,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 diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index 59fb9d71e9d8..8b0fc8a5d3c3 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -475,6 +475,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; @@ -484,7 +485,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; @@ -504,6 +507,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); @@ -520,6 +530,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); @@ -546,6 +557,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.