diff options
| -rw-r--r-- | include/linux/mm.h | 14 | ||||
| -rw-r--r-- | kernel/kexec_core.c | 10 | ||||
| -rw-r--r-- | kernel/kexec_file.c | 20 | ||||
| -rw-r--r-- | mm/memory-failure.c | 40 |
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. |
