[PATCH v6 4/7] mm/memblock: add a reserved-region flag for poisoned frames

From: Breno Leitao

Date: Fri Oct 16 2026 - 08:19:07 EST


memblock allocates from and frees to buddy whatever is not reserved, and
knows nothing about frames an earlier kernel found hardware-poisoned.
Such a frame can back the memmap, a page table or a percpu area before
any hwpoison code runs, and otherwise reaches the buddy allocator like
any other free frame.

Add MEMBLOCK_RSRV_HWPOISON for a reserved range holding those frames.
memblock neither allocates from a flagged range nor frees it to buddy.
memmap_init_reserved_pages() initializes its struct pages like any other
reserved range, including above first_deferred_pfn, but flags the RAM
frames PG_hwpoison instead of PG_reserved. That is the state of a frame
poisoned by this kernel. Holes in the range stay reserved.

PG_reserved is left clear on purpose. page_is_unmovable() treats a
reserved page as unmovable but skips a counted PG_hwpoison one, so a
memory block holding an inherited frame can still be offlined.

memblock_reserve_hwpoison() is the entry point. It is best effort. Until
the reserved array can be resized it keeps spare slots for the
reservations that must not fail. It flags only what no earlier
reservation covers, a kexec segment for one: that was placed around the
bad frames, and memblock_add_range() would warn about the overlap. A page
shared with such a reservation stays with it, so no page is flagged
twice.

It returns 0 only when every free part of the range is reserved. If any
part cannot be reserved it returns an error, even when another part was.

Name the flag in the debugfs flag table. MEMBLOCK_RSRV_HUGETLB gets a
name too: it has none, and would be left as a NULL slot below the new
flag, which memblock_debug_show() passes on to seq_puts().

Signed-off-by: Breno Leitao <leitao@xxxxxxxxxx>
---
include/linux/memblock.h | 10 ++++++
mm/memblock.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/include/linux/memblock.h b/include/linux/memblock.h
index aa845f488327f2..86755be249666d 100644
--- a/include/linux/memblock.h
+++ b/include/linux/memblock.h
@@ -52,6 +52,8 @@ extern unsigned long long max_possible_pfn;
* kernel that we know is good to use. It is the only memory that
* allocations may happen from in this phase.
* @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages
+ * @MEMBLOCK_RSRV_HWPOISON: reserved memory region holding frames an earlier
+ * kernel found to be hardware-poisoned.
*/
enum memblock_flags {
MEMBLOCK_NONE = 0x0, /* No special request */
@@ -63,6 +65,7 @@ enum memblock_flags {
MEMBLOCK_RSRV_KERN = 0x20, /* memory reserved for kernel use */
MEMBLOCK_KHO_SCRATCH = 0x40, /* scratch memory for kexec handover */
MEMBLOCK_RSRV_HUGETLB = 0x80, /* memory reserved for hugetlb pages */
+ MEMBLOCK_RSRV_HWPOISON = 0x100, /* poisoned by an earlier kernel */
};

/**
@@ -142,6 +145,8 @@ static __always_inline int memblock_reserve_kern(phys_addr_t base, phys_addr_t s
return __memblock_reserve(base, size, NUMA_NO_NODE, MEMBLOCK_RSRV_KERN);
}

+int memblock_reserve_hwpoison(phys_addr_t base, phys_addr_t size);
+
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
int memblock_physmem_add(phys_addr_t base, phys_addr_t size);
#endif
@@ -295,6 +300,11 @@ static inline bool memblock_is_reserved_noinit(struct memblock_region *m)
return m->flags & MEMBLOCK_RSRV_NOINIT;
}

+static inline bool memblock_is_reserved_hwpoison(struct memblock_region *m)
+{
+ return m->flags & MEMBLOCK_RSRV_HWPOISON;
+}
+
static inline bool memblock_is_driver_managed(struct memblock_region *m)
{
return m->flags & MEMBLOCK_DRIVER_MANAGED;
diff --git a/mm/memblock.c b/mm/memblock.c
index 31961ed628b0ad..d20d99972a4db0 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -36,6 +36,9 @@
# define INIT_MEMBLOCK_RESERVED_REGIONS INIT_MEMBLOCK_REGIONS
#endif

+/* Reserved slots memblock_reserve_hwpoison() leaves free until a resize. */
+#define MEMBLOCK_HWPOISON_SPARE 32
+
#ifndef INIT_MEMBLOCK_MEMORY_REGIONS
#define INIT_MEMBLOCK_MEMORY_REGIONS INIT_MEMBLOCK_REGIONS
#endif
@@ -1015,6 +1018,55 @@ int __init_memblock __memblock_reserve(phys_addr_t base, phys_addr_t size,
return memblock_add_range(&memblock.reserved, base, size, nid, flags);
}

+int __init memblock_reserve_hwpoison(phys_addr_t base, phys_addr_t size)
+{
+ phys_addr_t end = base + size;
+ bool reserved = false;
+ int err = 0;
+
+ while (base < end) {
+ phys_addr_t next = end;
+ struct memblock_region *r;
+ bool covered = false;
+
+ for_each_reserved_mem_region(r) {
+ if (r->base + r->size <= base)
+ continue;
+ if (r->base >= end)
+ break;
+ if (PAGE_ALIGN_DOWN(r->base) <= base) {
+ base = min(end, PAGE_ALIGN(r->base + r->size));
+ covered = true;
+ } else {
+ next = PAGE_ALIGN_DOWN(r->base);
+ }
+ break;
+ }
+ if (covered)
+ continue;
+
+ if (!memblock_can_resize &&
+ memblock.reserved.cnt + MEMBLOCK_HWPOISON_SPARE >=
+ memblock.reserved.max) {
+ err = -ENOSPC;
+ break;
+ }
+
+ err = __memblock_reserve(base, next - base, NUMA_NO_NODE,
+ MEMBLOCK_RSRV_HWPOISON);
+ if (err)
+ break;
+
+ reserved = true;
+ base = next;
+ }
+
+ if (err)
+ return err;
+
+ return reserved ? 0 : -EBUSY;
+}
+
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
int __init_memblock memblock_physmem_add(phys_addr_t base, phys_addr_t size)
{
@@ -2394,6 +2446,27 @@ static void __init memmap_init_reserved_range(phys_addr_t start,
}
}

+/*
+ * Frames an earlier kernel found poisoned end up as a frame poisoned by this
+ * kernel would: PG_hwpoison, not PG_reserved. Holes in the range stay reserved.
+ */
+static void __init memmap_init_hwpoison_range(phys_addr_t start,
+ phys_addr_t end, int nid)
+{
+ unsigned long pfn;
+
+ for_each_valid_pfn(pfn, PFN_DOWN(start), PFN_UP(end)) {
+ struct page *page = pfn_to_page(pfn);
+
+ init_deferred_page(pfn, nid);
+
+ if (memblock_is_map_memory(PFN_PHYS(pfn)))
+ hwpoison_boot_page(page);
+ else
+ __SetPageReserved(page);
+ }
+}
+
static void __init memmap_init_reserved_pages(void)
{
struct memblock_region *region;
@@ -2438,7 +2511,10 @@ static void __init memmap_init_reserved_pages(void)
if (!numa_valid_node(nid))
nid = early_pfn_to_nid(PFN_DOWN(start));

- memmap_init_reserved_range(start, end, nid);
+ if (memblock_is_reserved_hwpoison(region))
+ memmap_init_hwpoison_range(start, end, nid);
+ else
+ memmap_init_reserved_range(start, end, nid);
}
}
}
@@ -2874,6 +2950,8 @@ static const char * const flagname[] = {
[ilog2(MEMBLOCK_RSRV_NOINIT)] = "RSV_NIT",
[ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN",
[ilog2(MEMBLOCK_KHO_SCRATCH)] = "KHO_SCRATCH",
+ [ilog2(MEMBLOCK_RSRV_HUGETLB)] = "RSV_HUGETLB",
+ [ilog2(MEMBLOCK_RSRV_HWPOISON)] = "RSV_HWPOISON",
};

static int memblock_debug_show(struct seq_file *m, void *private)

--
2.53.0-Meta