[PATCH v6 5/7] drivers/base/memory: count the poison a block already holds when creating it

From: Breno Leitao

Date: Fri Oct 16 2026 - 08:17:21 EST


A frame flagged poisoned before memory_dev_init() runs cannot be counted
on its memory block. memblk_nr_poison_inc() divides by sections_per_block,
which memory_dev_init() sets, and the block does not exist yet. The block
is then created reading zero, however many of its frames are poisoned.

Until now nothing could poison a frame that early. It becomes possible
once a kernel flags the frames it inherits from the one that kexeced into
it, as it initializes its memmap.

memory_block_online() refuses a block that holds a poisoned frame, and a
counter stuck at zero lets one through.

Count what a block holds when it is created at boot. The walk sits behind
num_poisoned_pages, so a machine with no poison does not pay for it.

Signed-off-by: Breno Leitao <leitao@xxxxxxxxxx>
---
drivers/base/memory.c | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

diff --git a/drivers/base/memory.c b/drivers/base/memory.c
index 5eead3346f1e32..77481f375c2206 100644
--- a/drivers/base/memory.c
+++ b/drivers/base/memory.c
@@ -220,11 +220,16 @@ int memory_notify(enum memory_block_state state, void *v)

#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG)
static unsigned long memblk_nr_poison(struct memory_block *mem);
+static void memblk_nr_poison_init(struct memory_block *mem);
#else
static inline unsigned long memblk_nr_poison(struct memory_block *mem)
{
return 0;
}
+
+static inline void memblk_nr_poison_init(struct memory_block *mem)
+{
+}
#endif

/*
@@ -807,6 +812,7 @@ static int add_memory_block(unsigned long block_id, int nid, unsigned long state
mem->state = state;
mem->nid = nid;
INIT_LIST_HEAD(&mem->group_next);
+ memblk_nr_poison_init(mem);

#ifndef CONFIG_NUMA
if (state == MEM_ONLINE)
@@ -1251,4 +1257,28 @@ static unsigned long memblk_nr_poison(struct memory_block *mem)
{
return atomic_long_read(&mem->nr_hwpoison);
}
+
+/*
+ * Frames poisoned before the block existed were never counted on it. Count
+ * them now, or the first unpoison takes the counter below zero.
+ */
+static void memblk_nr_poison_init(struct memory_block *mem)
+{
+ unsigned long pfn = section_nr_to_pfn(mem->start_section_nr);
+ unsigned long end = pfn + PAGES_PER_SECTION * sections_per_block;
+ unsigned long nr = 0;
+
+ /* A hotplugged block is created before its pages are online. */
+ if (mem->state != MEM_ONLINE || !atomic_long_read(&num_poisoned_pages))
+ return;
+
+ for (; pfn < end; pfn++) {
+ struct page *page = pfn_to_online_page(pfn);
+
+ if (page && PageHWPoison(page))
+ nr++;
+ }
+
+ atomic_long_set(&mem->nr_hwpoison, nr);
+}
#endif

--
2.53.0-Meta