Re: [RFC PATCH 4/5] mm/page_alloc: Keep pre-online poison out of the buddy allocator

From: Shaikh Kamaluddin

Date: Thu Oct 22 2026 - 16:13:00 EST


On Thu, Oct 15, 2026 at 03:27:54PM +0800, Richard Cheng wrote:
> On Sat, Oct 10, 2026 at 09:50:12PM +0800, Shaikh Kamaluddin wrote:
> > The inherited-poison path checks initialized blocks before releasing
> > them to the buddy allocator. CXL device poison discovered before
> > hot-added memory is onlined is recorded separately in the pre-online
> > poison registry.
> >
> > Extend __free_pages_core() to send a block to free_poisoned_block() when
> > it intersects either poison source. In free_poisoned_block(), withhold a
> > page when its PFN is registered as pre-online poison or its physical
> > address is present in the inherited-poison table.
> >
> > Pages identified by either source continue through
> > hwpoison_boot_page(), while clean pages are released through
> > accept_and_free_block().
> >
> > Signed-off-by: Shaikh Kamaluddin <shaikhkamal2012@xxxxxxxxx>
> > ---
> > mm/page_alloc.c | 8 ++++++--
> > 1 file changed, 6 insertions(+), 2 deletions(-)
> >
> > diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> > index cca67a2702a4..57843df01b2a 100644
> > --- a/mm/page_alloc.c
> > +++ b/mm/page_alloc.c
> > @@ -33,6 +33,7 @@
> > #include <linux/cpuset.h>
> > #include <linux/folio_batch.h>
> > #include <linux/memory_hotplug.h>
> > +#include <linux/memory-failure.h>
> > #include <linux/nodemask.h>
> > #include <linux/vmstat.h>
> > #include <linux/fault-inject.h>
> > @@ -1601,12 +1602,14 @@ static void __meminit free_poisoned_block(struct page *page, unsigned int order,
> > enum meminit_context context)
> > {
> > unsigned long i, nr_pages = 1UL << order;
> > + unsigned long pfn = page_to_pfn(page);
> >
> > 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)) {
> > + if (range_contains_poisoned_memory(phys, PAGE_SIZE) ||
> > + preonline_hwpoison_contains(pfn + i)) {
> > hwpoison_boot_page(p, context);
> > continue;
> > }
> > @@ -1652,7 +1655,8 @@ void __meminit __free_pages_core(struct page *page, unsigned int order,
> >
> > /* First: a block parked by __free_unaccepted() never returns here. */
> > if (range_contains_poisoned_memory(page_to_phys(page),
> > - PAGE_SIZE << order)) {
> > + PAGE_SIZE << order) ||
> > + preonline_hwpoison_intersects(page_to_pfn(page), nr_pages)) {
> > free_poisoned_block(page, order, context);
> > return;
> > }
> > --
> > 2.43.0
> >
>
> Hi Shaikh,
>
> I think the idea of discovering device poison first is good, but the boundary
> should be more strictly designed.
>

Thanks for the review, Richard.

> Now if I don't get it wrong, if we turn on memory_hotplug.memmap_on_memory,
> we can have part of CXL memory to do the bookkeeping for itself, without
> using the existing RAM.
>
> What if that bookkeeping part is poisoned ? the check ignores this situation
> right ?

Yes, you are right. With memmap_on_memory, each 128 MiB block's struct
pages are placed in its first 2 MiB (0x200 pages) by vmemmap_populate()
when the memory is added, and memory_block_online() then onlines only
start_pfn + 0x200 onwards. Those vmemmap pages never reach
__free_pages_core(), so the registry is never consulted for them.

I reproduced it in QEMU 9.1 (TCG) on next-20260908 + [1] v5 + this RFC,
with two Internal poison records in the first block: DPA 0x100000
(PFN 0x190100, inside the vmemmap area 0x190000-0x1901ff) and DPA
0x400000 (PFN 0x190400, outside it). Both are registered, but only
0x190400 is withheld.

Steps:

1. Config: CONFIG_SPARSEMEM_VMEMMAP=y, CONFIG_MHP_MEMMAP_ON_MEMORY=y.

2. Boot with memmap_on_memory, without auto-online:

vng --disable-kvm --run ./arch/x86/boot/bzImage \
--disable-microvm --memory 4G --cpus 4 \
--append "memory_hotplug.memmap_on_memory=1" \
--qemu-opts="-machine q35,cxl=on \
-object memory-backend-ram,id=vmem0,share=on,size=512M \
-device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \
-device cxl-rp,port=0,bus=cxl.1,id=rp0,chassis=0,slot=2 \
-device cxl-type3,bus=rp0,volatile-memdev=vmem0,id=cxl-vmem0 \
-M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G \
-qmp unix:/tmp/qmp.sock,server=on,wait=off"

# cat /sys/module/memory_hotplug/parameters/memmap_on_memory
Y

3. Inject Internal poison over QMP (host):

{ "execute": "cxl-inject-poison",
"arguments": { "path": "/machine/peripheral/cxl-vmem0",
"start": 1048576, "length": 64 } }
{ "execute": "cxl-inject-poison",
"arguments": { "path": "/machine/peripheral/cxl-vmem0",
"start": 4194304, "length": 64 } }

4. Create the region with kmem not yet bound, then enable
memmap_on_memory on the dax device:

# grep MemTotal /proc/meminfo
MemTotal: 3999996 kB
# echo 0 > /sys/bus/dax/drivers_autoprobe
# cxl create-region -m -t ram -d decoder0.0 -w 1 mem0
cxl_region region0: withholding 2 frame(s) reported poisoned by the device
# echo 1 > /sys/bus/dax/devices/dax0.0/memmap_on_memory

5. Add and online the memory:

# daxctl reconfigure-device --mode=system-ram dax0.0
# lsmem
0x0000000190000000-0x00000001afffffff 512M online yes 50-53

6. Result:

# grep -E 'MemTotal|HardwareCorrupted' /proc/meminfo
MemTotal: 4516092 kB
HardwareCorrupted: 4 kB
# tools/mm/page-types -a 0x190000+0x20000 -b hwpoison -l
offset len flags
190400 1 ___________________X_______________________

MemTotal grows by 504 MiB rather than 512 MiB (4 blocks x 2 MiB of
memmap), so memmap_on_memory was in effect. Only 0x190400 is
hwpoisoned; 0x190100 holds struct pages.

>
> What about draw a clear line between normal RAM and hot-add CXL memory, have
> the latter gone through the thorough check and do the rest you want ?
>
> Best regards,
> Richard Cheng.
>
>
>
>
>
>
>
>
>
>