Re: [PATCH v2] kexec: keep the next kernel off hardware-poisoned pages
Pratyush Yadav <[email protected]> Fri, 31 Jul 2026 19:49:27 +0200
| Newsgroups | org.infradead.lists.kexec,org.kernel.vger.linux-kernel,org.kvack.linux-mm |
|---|---|
| Message-ID | <[email protected]> |
On Thu, Jul 30 2026, Breno Leitao wrote: > Memory failures (such as unrecoverable ECCs errors) are getting more and > more common. The kernel knows how to handle it while running, marking it > as poisoned (and SIGBUS user tasks). > > Poisoned memory is removed from the buddy allocator, but, not from > other places. A current problem is that kexec will load new kernel > on top of a bad/poisoned memory, which is undesirable. > > If the next kernel's image, initrd or purgatory lands on poisoned frame, > the relocation copy writes to the bad memory and the machine checks > during the kexec. > > Skip hardware-poisoned frames when placing segments: check them in the > kexec_file hole finder so it lays the next kernel down on good memory, > and reject a poisoned destination in sanity_check_segment_list() for > the kexec_load path, which cannot relocate. > > Suggested-by: Kiryl Shutsemau <[email protected]> > Signed-off-by: Breno Leitao <[email protected]> > --- > Changes in v2: > - EDITME: describe what is new in this series revision. > - EDITME: use bulletpoints and terse descriptions. > - Link to v1: https://patch.msgid.link/[email protected] > --- > include/linux/mm.h | 8 ++++++++ > kernel/kexec_core.c | 10 ++++++++++ > kernel/kexec_file.c | 19 +++++++++++++++++++ > mm/memory-failure.c | 28 ++++++++++++++++++++++++++++ > 4 files changed, 65 insertions(+) > > diff --git a/include/linux/mm.h b/include/linux/mm.h > index 7fabe6c66b4b7..48cad9a519d08 100644 > --- a/include/linux/mm.h > +++ b/include/linux/mm.h > @@ -5192,6 +5192,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); > +bool range_contains_hwpoison(phys_addr_t start, unsigned long size, > + phys_addr_t *poison); > #else > static inline void memory_failure_queue(unsigned long pfn, int flags) > { > @@ -5204,6 +5206,12 @@ static inline void num_poisoned_pages_inc(unsigned long pfn) > static inline void num_poisoned_pages_sub(unsigned long pfn, long i) > { > } > + > +static inline bool range_contains_hwpoison(phys_addr_t start, unsigned long size, > + phys_addr_t *poison) > +{ > + return false; > +} > #endif > > #if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG) > diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c > index dc770b9a6d053..9f6ed3c04299b 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_contains_hwpoison(image->segment[i].mem, > + image->segment[i].memsz, NULL)) > + return -EADDRNOTAVAIL; > + } > + > /* > * 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 59fb9d71e9d86..95cc981a557be 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 poisoned_addr; > > temp_end = min(end, kbuf->buf_max); > temp_start = temp_end - kbuf->memsz + 1; > @@ -504,6 +505,14 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end, > continue; > } > > + if (range_contains_hwpoison(temp_start, temp_end - temp_start + 1, > + &poisoned_addr)) { > + if (poisoned_addr < kbuf->memsz) > + return 0; > + temp_start = poisoned_addr - kbuf->memsz; > + continue; > + } > + > /* We found a suitable memory range */ > break; > } while (1); > @@ -520,6 +529,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 poisoned_addr; > > temp_start = max(start, kbuf->buf_min); > > @@ -546,6 +556,15 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end, > continue; > } > > + /* > + * Avoid placing the next kernel on hardware-poisoned memory. > + */ > + if (range_contains_hwpoison(temp_start, temp_end - temp_start + 1, > + &poisoned_addr)) { > + temp_start = poisoned_addr + 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 a8b03e2920ba8..04a1883d51fda 100644 > --- a/mm/memory-failure.c > +++ b/mm/memory-failure.c > @@ -96,6 +96,34 @@ void num_poisoned_pages_sub(unsigned long pfn, long i) > memblk_nr_poison_sub(pfn, i); > } > > +/* > + * Return true if any online page in [start, start + size) is hardware > + * poisoned. On a hit, when @poison is not NULL, @poison is set to the > + * address of the first poisoned page, which is ugly, but I cannot only Won't the last page be better? If two poisoned pages land in the range, you'd like to search past the last one. > + * return phys_addr_t, thus this "extra" parameter, instead of returning > + * the hit. > + */ > +bool range_contains_hwpoison(phys_addr_t start, unsigned long size, > + phys_addr_t *poison) Perhaps phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size) ? And if no poisoned page lands here then a default of 0 or ~0UL? > +{ > + unsigned long pfn, end_pfn; > + > + if (!size || !atomic_long_read(&num_poisoned_pages)) > + return false; > + > + 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 && PageHWPoison(page)) { > + if (poison) > + *poison = PFN_PHYS(pfn); > + return true; > + } > + } > + return false; > +} > + > /** > * MF_ATTR_RO - Create sysfs entry for each memory failure statistics. > * @_name: name of the file in the per NUMA sysfs directory. > > --- > base-commit: c5e32e86ca02b003f86e095d379b38148999293d > change-id: 20260727-kexec_posioned-72bb0a4143a0 > > Best regards, > -- > > Breno Leitao <[email protected]> > -- Regards, Pratyush Yadav