* [PATCH v4] kexec: keep the next kernel off hardware-poisoned pages
@ 2026-08-07 14:05 Breno Leitao
2026-08-10 9:49 ` Kiryl Shutsemau
0 siblings, 1 reply; 3+ messages in thread
From: Breno Leitao @ 2026-08-07 14:05 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Baoquan He, Pasha Tatashin,
Pratyush Yadav, Miaohe Lin, Naoya Horiguchi, pratyush
Cc: linux-mm, linux-kernel, kexec, rmikey, riel, kernel-team,
Kiryl Shutsemau, Breno Leitao
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.
The two hole finders walk in opposite directions, so each asks for the
end of the poison it has to clear: the top-down walk for the first
poisoned page in the window, the bottom-up walk for the last. A poisoned
hugetlb folio counts in full, as hugetlb keeps the flag on the folio and
the poisoned subpages on its raw hwpoison list.
Suggested-by: Kiryl Shutsemau <kas@kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
Changes in v4:
- Anchor the top-down hole finder on the first poisoned page in the
window and the bottom-up one on the last, so each jumps clear of the
poison in one step. New range_first_hwpoison(). (Kiryl Shutsemau)
- Count a poisoned hugetlb folio in full: the flag lives on the folio,
not on the subpages, so the per-pfn scan missed poisoned tail pages.
(Kiryl Shutsemau)
- Link to v3: https://patch.msgid.link/20260803-kexec_posioned-v3-1-83aa6ede0351@debian.org
Changes in v3:
- Return the address of the last poisoned page in the range, or
PHYS_ADDR_MAX when it is clean, instead of a bool plus an output
parameter. Renamed to range_last_hwpoison(). (Pratyush Yadav)
- Add cond_resched() to the scan loop, as a segment can span half of
memory. (Sashiko)
- Link to v2: https://patch.msgid.link/20260730-kexec_posioned-v2-1-f92d18551f64@debian.org
Changes in v2:
- Change from pfn_to_page() to pfn_to_online_page(). (Miaohe Lin)
- Return the poisoned address once we find a hit, to avoid the O(n^2)
rescan. (Sashiko)
- Link to v1: https://patch.msgid.link/20260728-kexec_posioned-v1-1-160c81d180fe@debian.org
To: Andrew Morton <akpm@linux-foundation.org>
To: David Hildenbrand <david@kernel.org>
To: Lorenzo Stoakes <ljs@kernel.org>
To: "Liam R. Howlett" <liam@infradead.org>
To: Vlastimil Babka <vbabka@kernel.org>
To: Mike Rapoport <rppt@kernel.org>
To: Suren Baghdasaryan <surenb@google.com>
To: Michal Hocko <mhocko@suse.com>
To: Baoquan He <baoquan.he@linux.dev>
To: Pasha Tatashin <pasha.tatashin@soleen.com>
To: Pratyush Yadav <pratyush@kernel.org>
To: Miaohe Lin <linmiaohe@huawei.com>
To: Naoya Horiguchi <nao.horiguchi@gmail.com>
Cc: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org
Cc: kexec@lists.infradead.org
---
include/linux/mm.h | 14 +++++++++++
kernel/kexec_core.c | 10 ++++++++
kernel/kexec_file.c | 18 ++++++++++++++
mm/memory-failure.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 114 insertions(+)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 7fabe6c66b4b7..41b923901b193 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);
+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)
{
@@ -5204,6 +5206,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 dc770b9a6d053..e097e980b1439 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 -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..9ba6cc01af929 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;
@@ -504,6 +505,15 @@ 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 */
+ if (poison < kbuf->memsz)
+ return 0;
+ 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 a8b03e2920ba8..c485e205fb633 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -96,6 +96,78 @@ 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);
+ struct folio *folio;
+
+ cond_resched();
+
+ if (!page)
+ continue;
+
+ folio = page_folio(page);
+ if (folio_test_hugetlb(folio)) {
+ /*
+ * hugetlbfs is a bit special, given the poison
+ * information is at the folio, not at the page
+ */
+ unsigned long folio_end;
+
+ /*
+ * No hugetlb_lock: the scan is racy either way, a frame
+ * can be poisoned right after it. Just don't let a folio
+ * dissolved under us walk the scan backwards.
+ */
+ folio_end = folio_pfn(folio) + folio_nr_pages(folio) - 1;
+ folio_end = max(folio_end, pfn);
+
+ if (folio_test_hwpoison(folio)) {
+ if (first)
+ return PFN_PHYS(pfn);
+ poison = PFN_PHYS(min(folio_end, end_pfn));
+ }
+ /* skip all the pfns that belong to hugetlb */
+ pfn = folio_end;
+ continue;
+ }
+
+ if (!PageHWPoison(page))
+ /* page is good, let's go to the next one */
+ continue;
+
+ if (first)
+ return PFN_PHYS(pfn);
+
+ poison = PFN_PHYS(pfn);
+ }
+
+ 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.
---
base-commit: c5e32e86ca02b003f86e095d379b38148999293d
change-id: 20260727-kexec_posioned-72bb0a4143a0
Best regards,
--
Breno Leitao <leitao@debian.org>
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH v4] kexec: keep the next kernel off hardware-poisoned pages
2026-08-07 14:05 [PATCH v4] kexec: keep the next kernel off hardware-poisoned pages Breno Leitao
@ 2026-08-10 9:49 ` Kiryl Shutsemau
2026-08-10 12:19 ` Breno Leitao
0 siblings, 1 reply; 3+ messages in thread
From: Kiryl Shutsemau @ 2026-08-10 9:49 UTC (permalink / raw)
To: Breno Leitao
Cc: Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Baoquan He, Pasha Tatashin,
Pratyush Yadav, Miaohe Lin, Naoya Horiguchi, linux-mm,
linux-kernel, kexec, rmikey, riel, kernel-team
On Fri, Aug 07, 2026 at 07:05:09AM -0700, Breno Leitao wrote:
> diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
> index dc770b9a6d053..e097e980b1439 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 -EADDRNOTAVAIL;
Other -EADDRNOTAVAIL usage indicate error on user side. But this is not
a user fault. Maybe -EHWPOISON instead.
> + }
> +
> /*
> * The destination addresses are searched from system RAM rather than
> * being allocated from the buddy allocator, so they are not guaranteed
...
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index a8b03e2920ba8..c485e205fb633 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -96,6 +96,78 @@ 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);
> + struct folio *folio;
> +
> + cond_resched();
> +
> + if (!page)
> + continue;
> +
> + folio = page_folio(page);
> + if (folio_test_hugetlb(folio)) {
> + /*
> + * hugetlbfs is a bit special, given the poison
> + * information is at the folio, not at the page
> + */
> + unsigned long folio_end;
> +
> + /*
> + * No hugetlb_lock: the scan is racy either way, a frame
> + * can be poisoned right after it. Just don't let a folio
> + * dissolved under us walk the scan backwards.
> + */
> + folio_end = folio_pfn(folio) + folio_nr_pages(folio) - 1;
> + folio_end = max(folio_end, pfn);
> +
> + if (folio_test_hwpoison(folio)) {
> + if (first)
> + return PFN_PHYS(pfn);
> + poison = PFN_PHYS(min(folio_end, end_pfn));
> + }
> + /* skip all the pfns that belong to hugetlb */
> + pfn = folio_end;
> + continue;
> + }
> +
> + if (!PageHWPoison(page))
> + /* page is good, let's go to the next one */
> + continue;
If you don't care about re-using clean part of poisoned hugetlb folio,
use is_page_hwpoison(page).
This would do:
if (!page || !is_page_hwpoison(page))
continue;
You would spin a bit on the same folio, but shouldn't be a big deal.
> +
> + if (first)
> + return PFN_PHYS(pfn);
> +
> + poison = PFN_PHYS(pfn);
> + }
> +
> + return poison;
> +}
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH v4] kexec: keep the next kernel off hardware-poisoned pages
2026-08-10 9:49 ` Kiryl Shutsemau
@ 2026-08-10 12:19 ` Breno Leitao
0 siblings, 0 replies; 3+ messages in thread
From: Breno Leitao @ 2026-08-10 12:19 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Baoquan He, Pasha Tatashin,
Pratyush Yadav, Miaohe Lin, Naoya Horiguchi, linux-mm,
linux-kernel, kexec, rmikey, riel, kernel-team
On Mon, Aug 10, 2026 at 10:49:16AM +0100, Kiryl Shutsemau wrote:
> On Fri, Aug 07, 2026 at 07:05:09AM -0700, Breno Leitao wrote:
> > diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
> > index dc770b9a6d053..e097e980b1439 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 -EADDRNOTAVAIL;
>
> Other -EADDRNOTAVAIL usage indicate error on user side. But this is not
> a user fault. Maybe -EHWPOISON instead.
Ack!
> > + if (!PageHWPoison(page))
> > + /* page is good, let's go to the next one */
> > + continue;
>
> If you don't care about re-using clean part of poisoned hugetlb folio,
> use is_page_hwpoison(page).
>
> This would do:
>
> if (!page || !is_page_hwpoison(page))
> continue;
>
> You would spin a bit on the same folio, but shouldn't be a big deal.
right, it will spin a bit more when there is a poisoned page in the
hugetlbfs folio, but it keep the code simpler, which might be a good
trade-off.
Let me update it and respin,
--breno
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-10 12:19 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07 14:05 [PATCH v4] kexec: keep the next kernel off hardware-poisoned pages Breno Leitao
2026-08-10 9:49 ` Kiryl Shutsemau
2026-08-10 12:19 ` Breno Leitao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox