[PATCH v5 9/9] mm/memory-failure: keep inherited poisoned frames out of the buddy allocator

From: Breno Leitao

Date: Tue Sep 15 2026 - 09:20:29 EST


When the pages are being given to the allocator, check if they are
poisoned, and mark them as such.

Similar to unaccepted memory, hook it in __free_pages_core(), and thus
the frames never enter the allocator, rather than being taken back out
of it.

A block runs up to MAX_PAGE_ORDER and a unit is 2MB, so hold back only
the frames the table covers and free the rest one at a time. The
allocator merges them back up, so a unit costs the frames it names, not
the whole block.

The frames that are freed go back through accept_and_free_block(), so a
clean frame in a block that is still unaccepted is accepted first.

A frame is flagged before its memory block exists, so the per block
counter is seeded from the page flag when the block is created rather
than incremented here.

Suggested-by: Kiryl Shutsemau <kas@xxxxxxxxxx>
Signed-off-by: Breno Leitao <leitao@xxxxxxxxxx>
---
mm/page_alloc.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index b07b5f4751cb95..cca67a2702a421 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -1597,6 +1597,25 @@ static void __meminit accept_and_free_block(struct page *page,
__free_pages_ok(page, order, FPI_TO_TAIL);
}

+static void __meminit free_poisoned_block(struct page *page, unsigned int order,
+ enum meminit_context context)
+{
+ unsigned long i, nr_pages = 1UL << order;
+
+ for (i = 0; i < nr_pages; i++) {
+ struct page *p = page + i;
+ phys_addr_t phys = page_to_phys(p);
+
+ if (range_contains_poisoned_memory(phys, PAGE_SIZE)) {
+ hwpoison_boot_page(p, context);
+ continue;
+ }
+
+ /* this part of the block is not poisoned */
+ accept_and_free_block(p, 0);
+ }
+}
+
void __meminit __free_pages_core(struct page *page, unsigned int order,
enum meminit_context context)
{
@@ -1631,6 +1650,13 @@ void __meminit __free_pages_core(struct page *page, unsigned int order,
atomic_long_add(nr_pages, &page_zone(page)->managed_pages);
}

+ /* First: a block parked by __free_unaccepted() never returns here. */
+ if (range_contains_poisoned_memory(page_to_phys(page),
+ PAGE_SIZE << order)) {
+ free_poisoned_block(page, order, context);
+ return;
+ }
+
accept_and_free_block(page, order);
}


--
2.53.0-Meta