* [PATCH 1/6] mm/compaction: wait for pfn walkers before poisoning offlined memmap
[not found] <20261027042204.3741307-1-gourry@gourry.net>
@ 2026-10-27 4:21 ` Gregory Price (Meta)
2026-10-27 13:38 ` David Hildenbrand (Arm)
2026-10-27 4:22 ` [PATCH 2/6] mm/page_owner: hold off memory offline while a page is read Gregory Price (Meta)
` (4 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Gregory Price (Meta) @ 2026-10-27 4:21 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, linux-cxl, kernel-team, akpm, vbabka, surenb,
mhocko, brendan.jackman, hannes, ziy, linmiaohe, nao.horiguchi,
david, ljs, liam, rppt, osalvador, muchun.song, iamjoonsoo.kim,
anshuman.khandual, stable
Compaction checks that a pageblock is online, then uses its pages across
lock drops and sleeps. If offline poisons the memmap meanwhile, a
DEBUG_VM kernel faults:
CPU 1 (compaction) CPU 2 (offline_pages)
pageblock_pfn_to_page()
block is online
isolate_migratepages_block()
cond_resched()
offline_mem_sections()
remove_pfn_range_from_zone()
page_init_poison()
set_pageblock_skip(valid_page)
reads the zone of a poisoned page
Oops: general protection fault, probably for non-canonical address 0xdffffc00000006a2
RIP: 0010:set_pfnblock_bit+0xa6/0x2b0
isolate_migratepages_block+0x2456/0x3c80
compact_zone+0x1638/0x3900
compact_node+0x12a/0x1f0
sysctl_compaction_handler+0xec/0x160
The free scanner faults the same way. A contiguous zone skips the
online check, since offline clears zone->contiguous only after
poisoning.
Hold an SRCU-fast read section (pfn_walk) from the block check to the
last use of its pages, and make remove_pfn_range_from_zone() clear
zone->contiguous and wait for it before poisoning. Offline pays one
grace period per range.
Scanner positions outlive the section, and offline can shrink the zone
meanwhile, so take the contiguous fast path only inside the current
span, publish zone->contiguous with release, and keep the migrate
scanner inside the zone.
With a kprobe parking the migrate scanner across the offline, base
faults in 3 of 3 runs and this patch in none. Unforced offline/online
cycling under compaction faults in 8 of 24 runs on base and 0 of 12 with
this patch.
Fixes: d33695b16a9f ("mm/memory_hotplug: poison memmap in remove_pfn_range_from_zone()")
Cc: stable@vger.kernel.org
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
mm/compaction.c | 8 ++++++++
mm/internal.h | 41 +++++++++++++++++++++++++++++++++++++++++
mm/memory_hotplug.c | 14 ++++++++++++--
mm/mm_init.c | 4 ++--
mm/page_alloc.h | 7 ++++++-
5 files changed, 69 insertions(+), 5 deletions(-)
diff --git a/mm/compaction.c b/mm/compaction.c
index f1b2060eb2016..2aeb18b6d54dd 100644
--- a/mm/compaction.c
+++ b/mm/compaction.c
@@ -360,6 +360,7 @@ static void __reset_isolation_suitable(struct zone *zone)
if (!zone->compact_blockskip_flush)
return;
+ guard(pfn_walk)();
zone->compact_blockskip_flush = false;
/*
@@ -716,6 +717,7 @@ isolate_freepages_range(struct compact_control *cc,
unsigned long isolated, pfn, block_start_pfn, block_end_pfn;
int order;
+ guard(pfn_walk)();
for (order = 0; order < NR_PAGE_ORDERS; order++)
INIT_LIST_HEAD(&cc->freepages[order]);
@@ -1340,6 +1342,7 @@ isolate_migratepages_range(struct compact_control *cc, unsigned long start_pfn,
unsigned long pfn, block_start_pfn, block_end_pfn;
int ret = 0;
+ guard(pfn_walk)();
/* Scan block by block. First and last block may be incomplete */
pfn = start_pfn;
block_start_pfn = pageblock_start_pfn(pfn);
@@ -1732,6 +1735,7 @@ static void isolate_freepages(struct compact_control *cc)
unsigned long low_pfn; /* lowest pfn scanner is able to scan */
unsigned int stride;
+ guard(pfn_walk)();
/* Try a small search of the free lists for a candidate */
fast_isolate_freepages(cc);
if (cc->nr_freepages)
@@ -2099,6 +2103,7 @@ static isolate_migrate_t isolate_migratepages(struct compact_control *cc)
(cc->mode != MIGRATE_SYNC ? ISOLATE_ASYNC_MIGRATE : 0);
bool fast_find_block;
+ guard(pfn_walk)();
/*
* Start at where we last stopped, or beginning of the zone as
* initialized by compact_zone(). The first failure will use
@@ -2116,6 +2121,9 @@ static isolate_migrate_t isolate_migratepages(struct compact_control *cc)
*/
fast_find_block = low_pfn != cc->migrate_pfn && !cc->fast_search_fail;
+ /* Offline may have shrunk the zone start past an old migrate_pfn */
+ low_pfn = max(low_pfn, block_start_pfn);
+
/* Only scan within a pageblock boundary */
block_end_pfn = pageblock_end_pfn(low_pfn);
diff --git a/mm/internal.h b/mm/internal.h
index d497c35ee7833..8a2faa74403b5 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -18,6 +18,7 @@
#include <linux/rmap.h>
#include <linux/swap.h>
#include <linux/leafops.h>
+#include <linux/srcu.h>
#include <linux/tracepoint-defs.h>
/* Internal core VMA manipulation functions. */
@@ -1206,6 +1207,46 @@ struct tlbflush_unmap_batch;
*/
extern struct workqueue_struct *mm_percpu_wq;
+/*
+ * Compaction checks that a pageblock is online and then uses its pages,
+ * possibly across sleeps. remove_pfn_range_from_zone() waits for these
+ * readers before it poisons the memmap of a range taken offline.
+ */
+#ifdef CONFIG_MEMORY_HOTREMOVE
+extern struct srcu_struct pfn_walk_srcu;
+
+static inline struct srcu_ctr __percpu *pfn_walk_lock(void)
+{
+ return srcu_read_lock_fast(&pfn_walk_srcu);
+}
+
+static inline void pfn_walk_unlock(struct srcu_ctr __percpu *scp)
+{
+ srcu_read_unlock_fast(&pfn_walk_srcu, scp);
+}
+
+static inline void pfn_walk_synchronize(void)
+{
+ synchronize_srcu(&pfn_walk_srcu);
+}
+#else
+static inline struct srcu_ctr __percpu *pfn_walk_lock(void)
+{
+ return NULL;
+}
+
+static inline void pfn_walk_unlock(struct srcu_ctr __percpu *scp)
+{
+}
+
+static inline void pfn_walk_synchronize(void)
+{
+}
+#endif
+
+DEFINE_LOCK_GUARD_0(pfn_walk, _T->scp = pfn_walk_lock(),
+ pfn_walk_unlock(_T->scp), struct srcu_ctr __percpu *scp)
+
#ifdef CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
void try_to_unmap_flush(void);
void try_to_unmap_flush_dirty(void);
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 796af1028ee23..b5d9e367b893d 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -543,6 +543,16 @@ void remove_pfn_range_from_zone(struct zone *zone,
struct pglist_data *pgdat = zone->zone_pgdat;
unsigned long pfn, cur_nr_pages;
+ /*
+ * The range is offline, but compaction may still use pages it found
+ * online. Make new scans check the online state instead of trusting
+ * zone->contiguous, and wait for the old ones before poisoning.
+ */
+ if (!zone_is_zone_device(zone)) {
+ clear_zone_contiguous(zone);
+ pfn_walk_synchronize();
+ }
+
/* Poison struct pages because they are now uninitialized again. */
for (pfn = start_pfn; pfn < end_pfn; pfn += cur_nr_pages) {
cond_resched();
@@ -563,8 +573,6 @@ void remove_pfn_range_from_zone(struct zone *zone,
if (zone_is_zone_device(zone))
return;
- clear_zone_contiguous(zone);
-
shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
update_pgdat_span(pgdat);
@@ -1783,6 +1791,8 @@ bool mhp_range_allowed(u64 start, u64 size, bool need_mapping)
#ifdef CONFIG_MEMORY_HOTREMOVE
+DEFINE_SRCU_FAST(pfn_walk_srcu);
+
static int check_no_memblock_for_node_cb(struct memory_block *mem, void *arg)
{
int nid = *(int *)arg;
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 01ced0553ce9b..db610ddc37398 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -2270,8 +2270,8 @@ void set_zone_contiguous(struct zone *zone)
cond_resched();
}
- /* We confirm that there is no hole */
- zone->contiguous = true;
+ /* We confirm that there is no hole; pairs with pageblock_pfn_to_page() */
+ smp_store_release(&zone->contiguous, true);
}
/*
diff --git a/mm/page_alloc.h b/mm/page_alloc.h
index 53df6b8a0c00a..7e185326b80ab 100644
--- a/mm/page_alloc.h
+++ b/mm/page_alloc.h
@@ -216,7 +216,12 @@ extern struct page *__pageblock_pfn_to_page(unsigned long start_pfn,
static inline struct page *pageblock_pfn_to_page(unsigned long start_pfn,
unsigned long end_pfn, struct zone *zone)
{
- if (zone->contiguous)
+ /*
+ * Offline can shrink the zone under a caller's old pfns and then set
+ * zone->contiguous again, so only trust it inside the current span.
+ */
+ if (smp_load_acquire(&zone->contiguous) &&
+ zone_spans_pfn(zone, start_pfn) && zone_spans_pfn(zone, end_pfn - 1))
return pfn_to_page(start_pfn);
return __pageblock_pfn_to_page(start_pfn, end_pfn, zone);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 1/6] mm/compaction: wait for pfn walkers before poisoning offlined memmap
2026-10-27 4:21 ` [PATCH 1/6] mm/compaction: wait for pfn walkers before poisoning offlined memmap Gregory Price (Meta)
@ 2026-10-27 13:38 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 15+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-27 13:38 UTC (permalink / raw)
To: Gregory Price (Meta), linux-mm
Cc: linux-kernel, linux-cxl, kernel-team, akpm, vbabka, surenb,
mhocko, brendan.jackman, hannes, ziy, linmiaohe, nao.horiguchi,
ljs, liam, rppt, osalvador, muchun.song, iamjoonsoo.kim,
anshuman.khandual, stable
On 10/27/26 05:21, Gregory Price (Meta) wrote:
> Compaction checks that a pageblock is online, then uses its pages across
> lock drops and sleeps. If offline poisons the memmap meanwhile, a
> DEBUG_VM kernel faults:
>
> CPU 1 (compaction) CPU 2 (offline_pages)
> pageblock_pfn_to_page()
> block is online
> isolate_migratepages_block()
> cond_resched()
> offline_mem_sections()
> remove_pfn_range_from_zone()
> page_init_poison()
> set_pageblock_skip(valid_page)
> reads the zone of a poisoned page
>
> Oops: general protection fault, probably for non-canonical address 0xdffffc00000006a2
> RIP: 0010:set_pfnblock_bit+0xa6/0x2b0
> isolate_migratepages_block+0x2456/0x3c80
> compact_zone+0x1638/0x3900
> compact_node+0x12a/0x1f0
> sysctl_compaction_handler+0xec/0x160
>
> The free scanner faults the same way. A contiguous zone skips the
> online check, since offline clears zone->contiguous only after
> poisoning.
>
> Hold an SRCU-fast read section (pfn_walk) from the block check to the
> last use of its pages, and make remove_pfn_range_from_zone() clear
> zone->contiguous and wait for it before poisoning. Offline pays one
> grace period per range.
>
> Scanner positions outlive the section, and offline can shrink the zone
> meanwhile, so take the contiguous fast path only inside the current
> span, publish zone->contiguous with release, and keep the migrate
> scanner inside the zone.
>
> With a kprobe parking the migrate scanner across the offline, base
> faults in 3 of 3 runs and this patch in none. Unforced offline/online
> cycling under compaction faults in 8 of 24 runs on base and 0 of 12 with
> this patch.
>
> Fixes: d33695b16a9f ("mm/memory_hotplug: poison memmap in remove_pfn_range_from_zone()")
> Cc: stable@vger.kernel.org
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
BTW, the order of these patches is all wrong.
There are much more important fixes hiding in this series. And this change here
would certainly not be the first thing we'd backport.
Please create a proper series that only addresses the pfn_to_online_page()
situation, without different, more urgent fixes.
Because there is no way we will fast track anything like this here into stable
kernels.
--
Cheers,
David
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 2/6] mm/page_owner: hold off memory offline while a page is read
[not found] <20261027042204.3741307-1-gourry@gourry.net>
2026-10-27 4:21 ` [PATCH 1/6] mm/compaction: wait for pfn walkers before poisoning offlined memmap Gregory Price (Meta)
@ 2026-10-27 4:22 ` Gregory Price (Meta)
2026-10-27 4:22 ` [PATCH 3/6] mm/memory-failure: keep the page online while handling it Gregory Price (Meta)
` (3 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Gregory Price (Meta) @ 2026-10-27 4:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, linux-cxl, kernel-team, akpm, vbabka, surenb,
mhocko, brendan.jackman, hannes, ziy, linmiaohe, nao.horiguchi,
david, ljs, liam, rppt, osalvador, muchun.song, iamjoonsoo.kim,
anshuman.khandual, stable
Reading /sys/kernel/debug/page_owner while memory is removed can oops.
read_page_owner() checks a pfn with pfn_valid(), and print_page_owner()
can sleep in kmalloc() before it reads the struct page:
CPU 1 (read page_owner) CPU 2 (remove the memory block)
pfn_valid(pfn)
page = pfn_to_page(pfn)
page_ext_get()
page_ext_put()
print_page_owner()
kmalloc()
offline_pages()
remove_pfn_range_from_zone()
arch_remove_memory()
frees the vmemmap
get_pageblock_migratetype(page)
BUG: unable to handle page fault for address: ffd4000006000000
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
Oops: Oops: 0000 [#1] SMP KASAN
RIP: 0010:__get_pfnblock_flags_mask+0x3e/0x2b0
Call Trace:
<TASK>
get_pfnblock_migratetype+0xf/0x20
read_page_owner+0x629/0xf00
full_proxy_read+0x102/0x190
vfs_read+0x153/0xcd0
ksys_read+0xe4/0x1b0
do_syscall_64+0xbd/0x630
entry_SYSCALL_64_after_hwframe+0x4b/0x53
Offline alone faults the same way on DEBUG_VM kernels. The page_ext is
safe; offline_page_ext() frees it after an RCU grace period.
Check the pfn with pfn_to_online_page() inside a pfn_walk read section,
which offline waits for, and move kmalloc() and copy_to_user() out of
it, since offline waits with the hotplug locks held. A full read of
page_owner in a 5G guest takes 1.36 s before and 1.34 s after.
With a kprobe sleeping the reader on a DIMM pfn across an offline or a
removal, base faults in 3 of 3 runs of each and this patch in none.
Fixes: 48c96a368579 ("mm/page_owner: keep track of page owners")
Cc: stable@vger.kernel.org
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
mm/page_owner.c | 45 ++++++++++++++++++++++++---------------------
1 file changed, 24 insertions(+), 21 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index cfc31c92d7657..d2c0b831fcd3b 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -13,6 +13,7 @@
#include <linux/memcontrol.h>
#include <linux/sched/clock.h>
+#include "internal.h"
#include "page_alloc.h"
/*
@@ -600,20 +601,14 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
#endif
static ssize_t
-print_page_owner(char __user *buf, size_t count, unsigned long pfn,
+print_page_owner(char *kbuf, size_t count, unsigned long pfn,
struct page *page, struct page_owner *page_owner,
depot_stack_handle_t handle,
struct page_owner_filter_state *state)
{
int ret, pageblock_mt, page_mt;
- char *kbuf;
enum page_owner_print_mode print_mode;
- count = min_t(size_t, count, PAGE_SIZE);
- kbuf = kmalloc(count, GFP_KERNEL);
- if (!kbuf)
- return -ENOMEM;
-
print_mode = state->print_mode;
ret = scnprintf(kbuf, count,
@@ -637,14 +632,14 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
if (print_mode != PAGE_OWNER_PRINT_HANDLE) {
ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
if (ret >= count)
- goto err;
+ return -ENOMEM;
}
if (print_mode != PAGE_OWNER_PRINT_STACK) {
ret += scnprintf(kbuf + ret, count - ret, "handle: %u\n",
handle);
if (ret >= count)
- goto err;
+ return -ENOMEM;
}
if (page_owner->last_migrate_reason != MR_NEVER) {
@@ -657,17 +652,9 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
ret += snprintf(kbuf + ret, count - ret, "\n");
if (ret >= count)
- goto err;
-
- if (copy_to_user(buf, kbuf, ret))
- ret = -EFAULT;
+ return -ENOMEM;
- kfree(kbuf);
return ret;
-
-err:
- kfree(kbuf);
- return -ENOMEM;
}
void __dump_page_owner(const struct page *page)
@@ -734,10 +721,17 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
struct page_owner *page_owner;
depot_stack_handle_t handle;
struct page_owner_filter_state *state = file->private_data;
+ ssize_t ret = 0;
+ char *kbuf;
if (!static_branch_unlikely(&page_owner_inited))
return -EINVAL;
+ count = min_t(size_t, count, PAGE_SIZE);
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
page = NULL;
if (*ppos == 0)
pfn = min_low_pfn;
@@ -766,7 +760,12 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
continue;
}
- page = pfn_to_page(pfn);
+ /* Offline waits for us before it poisons the memmap */
+ guard(pfn_walk)();
+ page = pfn_to_online_page(pfn);
+ if (!page)
+ continue;
+
if (skip_buddy_pages(&pfn, page))
continue;
@@ -825,14 +824,18 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
page_owner_tmp = *page_owner;
page_ext_put(page_ext);
- return print_page_owner(buf, count, pfn, page,
+ ret = print_page_owner(kbuf, count, pfn, page,
&page_owner_tmp, handle, state);
+ break;
ext_put_continue:
page_ext_put(page_ext);
cond_resched();
}
- return 0;
+ if (ret > 0 && copy_to_user(buf, kbuf, ret))
+ ret = -EFAULT;
+ kfree(kbuf);
+ return ret;
}
static loff_t lseek_page_owner(struct file *file, loff_t offset, int orig)
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 3/6] mm/memory-failure: keep the page online while handling it
[not found] <20261027042204.3741307-1-gourry@gourry.net>
2026-10-27 4:21 ` [PATCH 1/6] mm/compaction: wait for pfn walkers before poisoning offlined memmap Gregory Price (Meta)
2026-10-27 4:22 ` [PATCH 2/6] mm/page_owner: hold off memory offline while a page is read Gregory Price (Meta)
@ 2026-10-27 4:22 ` Gregory Price (Meta)
2026-10-27 4:22 ` [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range Gregory Price (Meta)
` (2 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Gregory Price (Meta) @ 2026-10-27 4:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, linux-cxl, kernel-team, akpm, vbabka, surenb,
mhocko, brendan.jackman, hannes, ziy, linmiaohe, nao.horiguchi,
david, ljs, liam, rppt, osalvador, muchun.song, iamjoonsoo.kim,
anshuman.khandual, stable
memory_failure(), soft_offline_page() and unpoison_memory() check that a
pfn is online, then sleep on mf_mutex before using its page. If the
block goes offline meanwhile, a DEBUG_VM kernel faults on the poisoned
memmap, and removal frees it on any kernel:
CPU 1 (soft_offline_page) CPU 2 (offline_pages)
pfn_to_online_page()
page is online
mutex_lock(&mf_mutex)
offline_mem_sections()
remove_pfn_range_from_zone()
page_init_poison()
get_hwpoison_page()
zone_pcp_disable(page_zone(page))
zone of a poisoned page
Oops: general protection fault, probably for non-canonical address 0xdffffc000000069e: 0000 [#1] SMP KASAN
RIP: 0010:__zone_set_pageset_high_and_batch+0x89/0x270
zone_pcp_disable+0x28/0x40
get_hwpoison_page+0xc2/0x1160
soft_offline_page.part.0+0xe7/0xbf0
memory_failure() faults the same way. unpoison_memory() does not fault,
but it clears flags on the poisoned page and returns success.
Hold get_online_mems() from before the online check to the last use of
the page. soft_offline_page() took it only around get_hwpoison_page();
it now takes it once, earlier.
With a kprobe parking each entry point across the offline, base faults,
or for unpoison_memory() succeeds, in 3 of 3 runs each. With this patch
all 9 runs wait and fail with -EIO or -ENXIO, and lockdep stays clean
over offline/online cycles mixed with error injection.
Fixes: 96c804a6ae8c ("mm/memory-failure.c: don't access uninitialized memmaps in memory_failure()")
Fixes: feec24a6139d ("mm, soft-offline: convert parameter to pfn")
Fixes: d613f53c83ec ("mm/memory-failure: fix VM_BUG_ON_PAGE(PagePoisoned(page)) when unpoison memory")
Cc: stable@vger.kernel.org
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
mm/memory-failure.c | 31 ++++++++++++++++++++++---------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 0c96eb5119976..cdae2a1988bb8 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -2394,6 +2394,8 @@ int memory_failure(unsigned long pfn, int flags)
if (!sysctl_memory_failure_recovery)
panic("Memory failure on page %lx", pfn);
+ /* Keep the page online from the check below until we are done */
+ get_online_mems();
mutex_lock(&mf_mutex);
if (!(flags & MF_SW_SIMULATED))
@@ -2608,11 +2610,13 @@ int memory_failure(unsigned long pfn, int flags)
identify_page_state:
res = identify_page_state(pfn, p, page_flags);
mutex_unlock(&mf_mutex);
+ put_online_mems();
return res;
unlock_page:
folio_unlock(folio);
unlock_mutex:
mutex_unlock(&mf_mutex);
+ put_online_mems();
return res;
}
EXPORT_SYMBOL_GPL(memory_failure);
@@ -2742,9 +2746,12 @@ int unpoison_memory(unsigned long pfn)
static DEFINE_RATELIMIT_STATE(unpoison_rs, DEFAULT_RATELIMIT_INTERVAL,
DEFAULT_RATELIMIT_BURST);
+ get_online_mems();
p = pfn_to_online_page(pfn);
- if (!p)
+ if (!p) {
+ put_online_mems();
return -EIO;
+ }
folio = page_folio(p);
mutex_lock(&mf_mutex);
@@ -2833,6 +2840,7 @@ int unpoison_memory(unsigned long pfn)
unpoison_pr_info("%#lx: software-unpoisoned page\n",
page_to_pfn(p), &unpoison_rs);
}
+ put_online_mems();
return ret;
}
EXPORT_SYMBOL(unpoison_memory);
@@ -2974,38 +2982,42 @@ int soft_offline_page(unsigned long pfn, int flags)
return -ENXIO;
}
+ if (!sysctl_enable_soft_offline) {
+ pr_info_once("disabled by /proc/sys/vm/enable_soft_offline\n");
+ put_ref_page(pfn, flags);
+ return -EOPNOTSUPP;
+ }
+
+ /* Keep the page online from the check below until we are done */
+ get_online_mems();
+
/* Only online pages can be soft-offlined (esp., not ZONE_DEVICE). */
page = pfn_to_online_page(pfn);
if (!page) {
+ put_online_mems();
put_ref_page(pfn, flags);
return -EIO;
}
- if (!sysctl_enable_soft_offline) {
- pr_info_once("disabled by /proc/sys/vm/enable_soft_offline\n");
- put_ref_page(pfn, flags);
- return -EOPNOTSUPP;
- }
-
mutex_lock(&mf_mutex);
if (PageHWPoison(page)) {
pr_info("%#lx: page already poisoned\n", pfn);
put_ref_page(pfn, flags);
mutex_unlock(&mf_mutex);
+ put_online_mems();
return 0;
}
retry:
- get_online_mems();
ret = get_hwpoison_page(page, flags | MF_SOFT_OFFLINE);
- put_online_mems();
if (hwpoison_filter(page)) {
if (ret > 0)
put_page(page);
mutex_unlock(&mf_mutex);
+ put_online_mems();
return -EOPNOTSUPP;
}
@@ -3023,6 +3035,7 @@ int soft_offline_page(unsigned long pfn, int flags)
}
mutex_unlock(&mf_mutex);
+ put_online_mems();
return ret;
}
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range
[not found] <20261027042204.3741307-1-gourry@gourry.net>
` (2 preceding siblings ...)
2026-10-27 4:22 ` [PATCH 3/6] mm/memory-failure: keep the page online while handling it Gregory Price (Meta)
@ 2026-10-27 4:22 ` Gregory Price (Meta)
2026-10-27 13:37 ` David Hildenbrand (Arm)
2026-11-03 15:12 ` Vlastimil Babka (SUSE)
2026-10-27 4:22 ` [PATCH 5/6] mm/page_alloc: keep the range online while alloc_contig isolates it Gregory Price (Meta)
2026-10-27 4:22 ` [PATCH 6/6] mm/hwpoison-inject: keep the page online while filtering it Gregory Price (Meta)
5 siblings, 2 replies; 15+ messages in thread
From: Gregory Price (Meta) @ 2026-10-27 4:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, linux-cxl, kernel-team, akpm, vbabka, surenb,
mhocko, brendan.jackman, hannes, ziy, linmiaohe, nao.horiguchi,
david, ljs, liam, rppt, osalvador, muchun.song, iamjoonsoo.kim,
anshuman.khandual, stable
An alloc_contig_range() that overlaps a block going offline can leave
the offline spinning until it gets a signal, holding the device hotplug
lock. When isolation fails, alloc_contig_frozen_range() undoes it over
the whole range, clearing the offline's MIGRATE_ISOLATE too:
CPU 1 (offline_pages) CPU 2 (alloc_contig_range)
start_isolate_page_range()
blocks are MIGRATE_ISOLATE
start_isolate_page_range()
-EBUSY on CPU 1's blocks
undo_isolate_page_range()
blocks are MIGRATE_MOVABLE
test_pages_isolated()
never succeeds again
start_isolate_page_range() fails only on a block someone else isolated,
after undoing its own, so this undo only ever clears another caller's.
Return directly, as before alloc_contig_range() went pageblock granular.
With offline_pages() parked after isolating a DIMM block and a 1G
hugetlb allocation made over it, the offline on base still loops 30s
later in 4 of 4 runs:
offline_pages+0x5b2/0x1570
memory_subsys_offline+0x17e/0x470
device_offline+0x250/0x340
state_store+0x88/0x110
With this patch the allocation still fails, and the offline completes in
3 of 3 runs.
Fixes: b2c9e2fbba32 ("mm: make alloc_contig_range work at pageblock granularity")
Cc: stable@vger.kernel.org
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
mm/page_alloc.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 55d3fc4b307f5..bb3c7e95f2ed4 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -7396,9 +7396,13 @@ int alloc_contig_frozen_range_noprof(unsigned long start, unsigned long end,
* put back to page allocator so that buddy can use them.
*/
+ /*
+ * On failure this has undone its own isolation, and any other
+ * isolated blocks in the range belong to someone else, e.g. offline.
+ */
ret = start_isolate_page_range(start, end, mode);
if (ret)
- goto done;
+ return ret;
drain_all_pages(cc.zone);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range
2026-10-27 4:22 ` [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range Gregory Price (Meta)
@ 2026-10-27 13:37 ` David Hildenbrand (Arm)
2026-10-27 14:06 ` Gregory Price (Meta)
2026-11-03 15:12 ` Vlastimil Babka (SUSE)
1 sibling, 1 reply; 15+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-27 13:37 UTC (permalink / raw)
To: Gregory Price (Meta), linux-mm
Cc: linux-kernel, linux-cxl, kernel-team, akpm, vbabka, surenb,
mhocko, brendan.jackman, hannes, ziy, linmiaohe, nao.horiguchi,
ljs, liam, rppt, osalvador, muchun.song, iamjoonsoo.kim,
anshuman.khandual, stable
On 10/27/26 05:22, Gregory Price (Meta) wrote:
> An alloc_contig_range() that overlaps a block going offline can leave
> the offline spinning until it gets a signal, holding the device hotplug
> lock. When isolation fails, alloc_contig_frozen_range() undoes it over
> the whole range, clearing the offline's MIGRATE_ISOLATE too:
>
> CPU 1 (offline_pages) CPU 2 (alloc_contig_range)
> start_isolate_page_range()
> blocks are MIGRATE_ISOLATE
> start_isolate_page_range()
> -EBUSY on CPU 1's blocks
> undo_isolate_page_range()
> blocks are MIGRATE_MOVABLE
> test_pages_isolated()
> never succeeds again
>
> start_isolate_page_range() fails only on a block someone else isolated,
> after undoing its own, so this undo only ever clears another caller's.
> Return directly, as before alloc_contig_range() went pageblock granular.
>
> With offline_pages() parked after isolating a DIMM block and a 1G
> hugetlb allocation made over it, the offline on base still loops 30s
> later in 4 of 4 runs:
>
> offline_pages+0x5b2/0x1570
> memory_subsys_offline+0x17e/0x470
> device_offline+0x250/0x340
> state_store+0x88/0x110
>
> With this patch the allocation still fails, and the offline completes in
> 3 of 3 runs.
>
> Fixes: b2c9e2fbba32 ("mm: make alloc_contig_range work at pageblock granularity")
> Cc: stable@vger.kernel.org
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
We have an existing AI fix for that as well already handled by Vlasta.
I think we'll have to establish some model of "ask David first before posting"
because otherwise we just create duplicate work for everyone.
--
Cheers,
David
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range
2026-10-27 13:37 ` David Hildenbrand (Arm)
@ 2026-10-27 14:06 ` Gregory Price (Meta)
2026-10-27 14:13 ` David Hildenbrand (Arm)
0 siblings, 1 reply; 15+ messages in thread
From: Gregory Price (Meta) @ 2026-10-27 14:06 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Gregory Price (Meta), linux-mm, linux-kernel, linux-cxl,
kernel-team, akpm, vbabka, surenb, mhocko, brendan.jackman,
hannes, ziy, linmiaohe, nao.horiguchi, ljs, liam, rppt, osalvador,
muchun.song, iamjoonsoo.kim, anshuman.khandual, stable
On 2026-10-27 14:37 +0100, David Hildenbrand (Arm) wrote:
> On 10/27/26 05:22, Gregory Price (Meta) wrote:
>
> We have an existing AI fix for that as well already handled by Vlasta.
>
> I think we'll have to establish some model of "ask David first before posting"
> because otherwise we just create duplicate work for everyone.
>
Yeah, this is going to happen more.
I've been sitting in these patches for over a month while they soaked on
hardware getting performance tests.
Maybe worth a private tree we can rebase on regularly for these kinds of
un-vetted/posted fixes to see what's coming?
--
~Gregory
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range
2026-10-27 14:06 ` Gregory Price (Meta)
@ 2026-10-27 14:13 ` David Hildenbrand (Arm)
2026-10-27 18:53 ` Zi Yan
0 siblings, 1 reply; 15+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-27 14:13 UTC (permalink / raw)
To: Gregory Price (Meta)
Cc: linux-mm, linux-kernel, linux-cxl, kernel-team, akpm, vbabka,
surenb, mhocko, brendan.jackman, hannes, ziy, linmiaohe,
nao.horiguchi, ljs, liam, rppt, osalvador, muchun.song,
iamjoonsoo.kim, anshuman.khandual, stable
On 10/27/26 15:06, Gregory Price (Meta) wrote:
> On 2026-10-27 14:37 +0100, David Hildenbrand (Arm) wrote:
>> On 10/27/26 05:22, Gregory Price (Meta) wrote:
>>
>> We have an existing AI fix for that as well already handled by Vlasta.
>>
>> I think we'll have to establish some model of "ask David first before posting"
>> because otherwise we just create duplicate work for everyone.
>>
>
> Yeah, this is going to happen more.
>
> I've been sitting in these patches for over a month while they soaked on
> hardware getting performance tests.
I mean this fix here is really just independent and shouldn't wait that long :)
>
> Maybe worth a private tree we can rebase on regularly for these kinds of
> un-vetted/posted fixes to see what's coming?
Maybe. But if it's just us core people we can just talk on IRC etc (like Zi did
two days ago when he was about to post something that I already had on my queue).
--
Cheers,
David
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range
2026-10-27 14:13 ` David Hildenbrand (Arm)
@ 2026-10-27 18:53 ` Zi Yan
2026-10-27 18:58 ` David Hildenbrand (Arm)
0 siblings, 1 reply; 15+ messages in thread
From: Zi Yan @ 2026-10-27 18:53 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Gregory Price (Meta), linux-mm, linux-kernel, linux-cxl,
kernel-team, akpm, vbabka, surenb, mhocko, brendan.jackman,
hannes, linmiaohe, nao.horiguchi, ljs, liam, rppt, osalvador,
muchun.song, iamjoonsoo.kim, anshuman.khandual, stable
On 27 Oct 2026, at 10:13, David Hildenbrand (Arm) wrote:
> On 10/27/26 15:06, Gregory Price (Meta) wrote:
>> On 2026-10-27 14:37 +0100, David Hildenbrand (Arm) wrote:
>>> On 10/27/26 05:22, Gregory Price (Meta) wrote:
>>>
>>> We have an existing AI fix for that as well already handled by Vlasta.
>>>
>>> I think we'll have to establish some model of "ask David first before posting"
>>> because otherwise we just create duplicate work for everyone.
>>>
>>
>> Yeah, this is going to happen more.
>>
>> I've been sitting in these patches for over a month while they soaked on
>> hardware getting performance tests.
>
> I mean this fix here is really just independent and shouldn't wait that long :)
>
>>
>> Maybe worth a private tree we can rebase on regularly for these kinds of
>> un-vetted/posted fixes to see what's coming?
>
> Maybe. But if it's just us core people we can just talk on IRC etc (like Zi did
> two days ago when he was about to post something that I already had on my queue).
One idea is that maybe Sashiko is able to identify patches fixing
the same issue and does some grouping or tagging on the website.
Or send an email about it with a lore link.
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range
2026-10-27 18:53 ` Zi Yan
@ 2026-10-27 18:58 ` David Hildenbrand (Arm)
2026-10-27 19:38 ` Gregory Price
0 siblings, 1 reply; 15+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-27 18:58 UTC (permalink / raw)
To: Zi Yan
Cc: Gregory Price (Meta), linux-mm, linux-kernel, linux-cxl,
kernel-team, akpm, vbabka, surenb, mhocko, brendan.jackman,
hannes, linmiaohe, nao.horiguchi, ljs, liam, rppt, osalvador,
muchun.song, iamjoonsoo.kim, anshuman.khandual, stable
On 10/27/26 19:53, Zi Yan wrote:
> On 27 Oct 2026, at 10:13, David Hildenbrand (Arm) wrote:
>
>> On 10/27/26 15:06, Gregory Price (Meta) wrote:
>>>
>>> Yeah, this is going to happen more.
>>>
>>> I've been sitting in these patches for over a month while they soaked on
>>> hardware getting performance tests.
>>
>> I mean this fix here is really just independent and shouldn't wait that long :)
>>
>>>
>>> Maybe worth a private tree we can rebase on regularly for these kinds of
>>> un-vetted/posted fixes to see what's coming?
>>
>> Maybe. But if it's just us core people we can just talk on IRC etc (like Zi did
>> two days ago when he was about to post something that I already had on my queue).
>
> One idea is that maybe Sashiko is able to identify patches fixing
> the same issue and does some grouping or tagging on the website.
> Or send an email about it with a lore link.
I think that's too late. For us core devs we just need to sync on "has someone
already a fix for that or is it worth cleaning the AI shite up and sending it out".
--
Cheers,
David
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range
2026-10-27 18:58 ` David Hildenbrand (Arm)
@ 2026-10-27 19:38 ` Gregory Price
0 siblings, 0 replies; 15+ messages in thread
From: Gregory Price @ 2026-10-27 19:38 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Zi Yan, Gregory Price (Meta), linux-mm, linux-kernel, linux-cxl,
kernel-team, akpm, vbabka, surenb, mhocko, brendan.jackman,
hannes, linmiaohe, nao.horiguchi, ljs, liam, rppt, osalvador,
muchun.song, iamjoonsoo.kim, anshuman.khandual, stable
On 2026-10-27 19:58 +0100, David Hildenbrand (Arm) wrote:
> On 10/27/26 19:53, Zi Yan wrote:
> > On 27 Oct 2026, at 10:13, David Hildenbrand (Arm) wrote:
> >
> >> Maybe. But if it's just us core people we can just talk on IRC etc (like Zi did
> >> two days ago when he was about to post something that I already had on my queue).
> >
> > One idea is that maybe Sashiko is able to identify patches fixing
> > the same issue and does some grouping or tagging on the website.
> > Or send an email about it with a lore link.
>
> I think that's too late. For us core devs we just need to sync on "has someone
> already a fix for that or is it worth cleaning the AI shite up and sending it out".
>
It might be worth just having a maintainer Fixes:-only review branch
that automates pulling these things in and responding to conflicts.
I've been working on some b4 workbench automation to make triaging the
list a little more sustainable. It's getting there, but it needs time.
~Gregory
--
Gregory Price <gourry@gourry.net>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range
2026-10-27 4:22 ` [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range Gregory Price (Meta)
2026-10-27 13:37 ` David Hildenbrand (Arm)
@ 2026-11-03 15:12 ` Vlastimil Babka (SUSE)
2026-11-03 16:12 ` Zi Yan
1 sibling, 1 reply; 15+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-11-03 15:12 UTC (permalink / raw)
To: Gregory Price (Meta), linux-mm
Cc: linux-kernel, linux-cxl, kernel-team, akpm, surenb, mhocko,
brendan.jackman, hannes, ziy, linmiaohe, nao.horiguchi, david,
ljs, liam, rppt, osalvador, muchun.song, iamjoonsoo.kim,
anshuman.khandual, stable
On 10/27/26 05:22, Gregory Price (Meta) wrote:
> An alloc_contig_range() that overlaps a block going offline can leave
> the offline spinning until it gets a signal, holding the device hotplug
> lock. When isolation fails, alloc_contig_frozen_range() undoes it over
> the whole range, clearing the offline's MIGRATE_ISOLATE too:
>
> CPU 1 (offline_pages) CPU 2 (alloc_contig_range)
> start_isolate_page_range()
> blocks are MIGRATE_ISOLATE
> start_isolate_page_range()
> -EBUSY on CPU 1's blocks
> undo_isolate_page_range()
> blocks are MIGRATE_MOVABLE
> test_pages_isolated()
> never succeeds again
>
> start_isolate_page_range() fails only on a block someone else isolated,
> after undoing its own, so this undo only ever clears another caller's.
> Return directly, as before alloc_contig_range() went pageblock granular.
>
> With offline_pages() parked after isolating a DIMM block and a 1G
> hugetlb allocation made over it, the offline on base still loops 30s
> later in 4 of 4 runs:
>
> offline_pages+0x5b2/0x1570
> memory_subsys_offline+0x17e/0x470
> device_offline+0x250/0x340
> state_store+0x88/0x110
>
> With this patch the allocation still fails, and the offline completes in
> 3 of 3 runs.
>
> Fixes: b2c9e2fbba32 ("mm: make alloc_contig_range work at pageblock granularity")
> Cc: stable@vger.kernel.org
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
I've applied this single patch to page_alloc/for-next-fixes and will drop
the version from Josef [1]. Also transferred Zi's R-b since it's the same
code change (plus comment).
[1]
https://lore.kernel.org/all/20261027-pgalloc-ai-hotfixes-v1-1-9c7d1aca801e@kernel.org/
> ---
> mm/page_alloc.c | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 55d3fc4b307f5..bb3c7e95f2ed4 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -7396,9 +7396,13 @@ int alloc_contig_frozen_range_noprof(unsigned long start, unsigned long end,
> * put back to page allocator so that buddy can use them.
> */
>
> + /*
> + * On failure this has undone its own isolation, and any other
> + * isolated blocks in the range belong to someone else, e.g. offline.
> + */
> ret = start_isolate_page_range(start, end, mode);
> if (ret)
> - goto done;
> + return ret;
>
> drain_all_pages(cc.zone);
>
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range
2026-11-03 15:12 ` Vlastimil Babka (SUSE)
@ 2026-11-03 16:12 ` Zi Yan
0 siblings, 0 replies; 15+ messages in thread
From: Zi Yan @ 2026-11-03 16:12 UTC (permalink / raw)
To: Vlastimil Babka (SUSE)
Cc: Gregory Price (Meta), linux-mm, linux-kernel, linux-cxl,
kernel-team, akpm, surenb, mhocko, brendan.jackman, hannes,
linmiaohe, nao.horiguchi, david, ljs, liam, rppt, osalvador,
muchun.song, iamjoonsoo.kim, anshuman.khandual, stable
On 3 Nov 2026, at 10:12, Vlastimil Babka (SUSE) wrote:
> On 10/27/26 05:22, Gregory Price (Meta) wrote:
>> An alloc_contig_range() that overlaps a block going offline can leave
>> the offline spinning until it gets a signal, holding the device hotplug
>> lock. When isolation fails, alloc_contig_frozen_range() undoes it over
>> the whole range, clearing the offline's MIGRATE_ISOLATE too:
>>
>> CPU 1 (offline_pages) CPU 2 (alloc_contig_range)
>> start_isolate_page_range()
>> blocks are MIGRATE_ISOLATE
>> start_isolate_page_range()
>> -EBUSY on CPU 1's blocks
>> undo_isolate_page_range()
>> blocks are MIGRATE_MOVABLE
>> test_pages_isolated()
>> never succeeds again
>>
>> start_isolate_page_range() fails only on a block someone else isolated,
>> after undoing its own, so this undo only ever clears another caller's.
>> Return directly, as before alloc_contig_range() went pageblock granular.
>>
>> With offline_pages() parked after isolating a DIMM block and a 1G
>> hugetlb allocation made over it, the offline on base still loops 30s
>> later in 4 of 4 runs:
>>
>> offline_pages+0x5b2/0x1570
>> memory_subsys_offline+0x17e/0x470
>> device_offline+0x250/0x340
>> state_store+0x88/0x110
>>
>> With this patch the allocation still fails, and the offline completes in
>> 3 of 3 runs.
>>
>> Fixes: b2c9e2fbba32 ("mm: make alloc_contig_range work at pageblock granularity")
>> Cc: stable@vger.kernel.org
>> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
>
> I've applied this single patch to page_alloc/for-next-fixes and will drop
> the version from Josef [1]. Also transferred Zi's R-b since it's the same
> code change (plus comment).
>
> [1]
> https://lore.kernel.org/all/20261027-pgalloc-ai-hotfixes-v1-1-9c7d1aca801e@kernel.org/
Thanks.
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 5/6] mm/page_alloc: keep the range online while alloc_contig isolates it
[not found] <20261027042204.3741307-1-gourry@gourry.net>
` (3 preceding siblings ...)
2026-10-27 4:22 ` [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range Gregory Price (Meta)
@ 2026-10-27 4:22 ` Gregory Price (Meta)
2026-10-27 4:22 ` [PATCH 6/6] mm/hwpoison-inject: keep the page online while filtering it Gregory Price (Meta)
5 siblings, 0 replies; 15+ messages in thread
From: Gregory Price (Meta) @ 2026-10-27 4:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, linux-cxl, kernel-team, akpm, vbabka, surenb,
mhocko, brendan.jackman, hannes, ziy, linmiaohe, nao.horiguchi,
david, ljs, liam, rppt, osalvador, muchun.song, iamjoonsoo.kim,
anshuman.khandual, stable
A gigantic hugetlb allocation racing with memory offline can crash.
alloc_contig_pages() checks that a range is online under zone->lock,
drops the lock, and alloc_contig_range() then reads the zone of its
pages. If the range goes offline in between, a DEBUG_VM kernel faults
on the poisoned memmap, and removal frees it on any kernel:
CPU 1 (alloc_contig_pages) CPU 2 (offline_pages)
pfn_range_valid_contig()
range is online
spin_unlock(&zone->lock)
offline_mem_sections()
remove_pfn_range_from_zone()
page_init_poison()
alloc_contig_range()
start_isolate_page_range()
isolate_single_pageblock()
zone of a poisoned page
Oops: general protection fault, probably for non-canonical address 0xdffffc00000006a2: 0000 [#1] SMP KASAN
RIP: 0010:isolate_single_pageblock+0xe5/0x820
start_isolate_page_range+0x74/0x1a0
alloc_contig_frozen_range_noprof+0x21c/0xcc0
alloc_contig_frozen_pages_noprof+0x4cf/0x700
A range that goes offline during isolation is no better, since
start_isolate_page_range() skips offline blocks.
Check that the range is online, isolate it and check again under
pfn_walk, and look up the zone only then; an isolated range cannot go
offline. This adds one online lookup per pageblock on each side of
isolation.
With a kprobe holding a 1G allocation at start_isolate_page_range()
across the offline, base faults in 3 of 3 runs. With this patch the
offline waits for the allocation in 6 of 6, and both finish.
Fixes: 5e27a2df03b8 ("mm/page_alloc: add alloc_contig_pages()")
Cc: stable@vger.kernel.org
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
mm/page_alloc.c | 33 +++++++++++++++++++++++++++++----
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index bb3c7e95f2ed4..82f35816a0711 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -7319,6 +7319,16 @@ static void __free_contig_frozen_range(unsigned long pfn, unsigned long nr_pages
__free_contig_range_common(pfn, nr_pages, /* is_frozen= */ true);
}
+static bool pfn_range_online(unsigned long start, unsigned long end)
+{
+ unsigned long pfn;
+
+ for (pfn = start; pfn < end; pfn += pageblock_nr_pages)
+ if (!pfn_to_online_page(pfn))
+ return false;
+ return true;
+}
+
/**
* alloc_contig_frozen_range() -- tries to allocate given range of frozen pages
* @start: start PFN to allocate
@@ -7352,7 +7362,6 @@ int alloc_contig_frozen_range_noprof(unsigned long start, unsigned long end,
struct compact_control cc = {
.nr_migratepages = 0,
.order = -1,
- .zone = page_zone(pfn_to_page(start)),
.mode = MIGRATE_SYNC,
.ignore_skip_hint = true,
.no_set_skip_hint = true,
@@ -7397,13 +7406,29 @@ int alloc_contig_frozen_range_noprof(unsigned long start, unsigned long end,
*/
/*
- * On failure this has undone its own isolation, and any other
- * isolated blocks in the range belong to someone else, e.g. offline.
+ * Part of the range may go offline under us. Offline cannot poison
+ * the memmap while we hold pfn_walk, but isolation silently skips
+ * blocks that are already offline, so check before and after. On
+ * failure, start_isolate_page_range() has undone its own isolation,
+ * and any other isolated blocks belong to someone else, e.g. offline.
*/
- ret = start_isolate_page_range(start, end, mode);
+ scoped_guard(pfn_walk) {
+ unsigned long first = pageblock_start_pfn(start);
+ unsigned long last = pageblock_align(end);
+
+ if (!pfn_range_online(first, last))
+ return -EBUSY;
+ ret = start_isolate_page_range(start, end, mode);
+ if (!ret && !pfn_range_online(first, last)) {
+ /* Every online isolated block in the range is ours */
+ undo_isolate_page_range(start, end);
+ ret = -EBUSY;
+ }
+ }
if (ret)
return ret;
+ cc.zone = page_zone(pfn_to_page(start));
drain_all_pages(cc.zone);
/*
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 6/6] mm/hwpoison-inject: keep the page online while filtering it
[not found] <20261027042204.3741307-1-gourry@gourry.net>
` (4 preceding siblings ...)
2026-10-27 4:22 ` [PATCH 5/6] mm/page_alloc: keep the range online while alloc_contig isolates it Gregory Price (Meta)
@ 2026-10-27 4:22 ` Gregory Price (Meta)
5 siblings, 0 replies; 15+ messages in thread
From: Gregory Price (Meta) @ 2026-10-27 4:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, linux-cxl, kernel-team, akpm, vbabka, surenb,
mhocko, brendan.jackman, hannes, ziy, linmiaohe, nao.horiguchi,
david, ljs, liam, rppt, osalvador, muchun.song, iamjoonsoo.kim,
anshuman.khandual, stable
Injecting a memory error with the filter enabled while the memory is
hot-removed can crash. hwpoison_inject() checks the pfn with
pfn_valid(), then runs shake_folio() and the filter on its page, which
can sleep in lru_add_drain_all():
CPU 1 (hwpoison_inject) CPU 2 (ACPI eject)
pfn_valid()
folio = page_folio(pfn_to_page())
offline_pages()
arch_remove_memory()
vmemmap freed
shake_folio(folio)
reads folio->flags
BUG: unable to handle page fault for address: ffd4000006040033
#PF: error_code(0x0000) - not-present page
RIP: 0010:shake_folio+0x2a/0x50
hwpoison_inject+0x232/0x4b0 [hwpoison_inject]
simple_attr_write_xsigned.constprop.0.isra.0+0x179/0x220
debugfs_attr_write+0x63/0x90
Until the filter stopped pinning the page, that reference held off
offline.
Hold get_online_mems() around the filter, check the pfn with
pfn_to_online_page() under it, and drop the hold before
memory_failure(), which takes it again. Offline and ZONE_DEVICE pages
never passed the filter anyway. Export get/put_online_mems() to the
module.
With the injector held at shake_folio() on a free DIMM page across an
eject, base faults in 3 of 3 runs. With this patch the eject waits, and
the injection then fails with -ENXIO, in 3 of 3.
Fixes: fd476720c9ba ("mm,hwpoison-inject: don't pin for hwpoison_filter")
Cc: stable@vger.kernel.org
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
mm/hwpoison-inject.c | 55 +++++++++++++++++++++++++++++---------------
mm/memory_hotplug.c | 2 ++
2 files changed, 39 insertions(+), 18 deletions(-)
diff --git a/mm/hwpoison-inject.c b/mm/hwpoison-inject.c
index a11222572f978..f6cc967b3a468 100644
--- a/mm/hwpoison-inject.c
+++ b/mm/hwpoison-inject.c
@@ -9,6 +9,7 @@
#include <linux/hugetlb.h>
#include <linux/page-flags.h>
#include <linux/memcontrol.h>
+#include <linux/memory_hotplug.h>
#include "internal.h"
static u32 hwpoison_filter_enable;
@@ -99,43 +100,61 @@ static int hwpoison_filter(struct page *p)
static struct dentry *hwpoison_dir;
-static int hwpoison_inject(void *data, u64 val)
+/* Returns true if the filter rejects the page at @pfn */
+static bool hwpoison_inject_filtered(unsigned long pfn)
{
- unsigned long pfn = val;
struct page *p;
struct folio *folio;
- int err;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EPERM;
-
- if (!pfn_valid(pfn))
- return -ENXIO;
- p = pfn_to_page(pfn);
+ /*
+ * Offline and ZONE_DEVICE pages are not LRU, hugetlb or free buddy
+ * pages, so the checks below never let them through.
+ */
+ p = pfn_to_online_page(pfn);
+ if (!p)
+ return true;
folio = page_folio(p);
- if (!hwpoison_filter_enable)
- goto inject;
-
shake_folio(folio);
/*
* This implies unable to support non-LRU pages except free page.
*/
if (!folio_test_lru(folio) && !folio_test_hugetlb(folio) &&
!is_free_buddy_page(p))
- return 0;
+ return true;
/*
* do a racy check to make sure PG_hwpoison will only be set for
* the targeted owner (or on a free page).
* memory_failure() will redo the check reliably inside page lock.
*/
- err = hwpoison_filter(&folio->page);
- if (err)
- return 0;
+ return hwpoison_filter(&folio->page);
+}
+
+static int hwpoison_inject(void *data, u64 val)
+{
+ unsigned long pfn = val;
+ bool filtered;
+ int err;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (!pfn_valid(pfn))
+ return -ENXIO;
+
+ if (hwpoison_filter_enable) {
+ /*
+ * Keep the page online while the filter looks at it.
+ * memory_failure() takes this again, so drop it first.
+ */
+ get_online_mems();
+ filtered = hwpoison_inject_filtered(pfn);
+ put_online_mems();
+ if (filtered)
+ return 0;
+ }
-inject:
pr_info("Injecting memory failure at pfn %#lx\n", pfn);
err = memory_failure(pfn, MF_SW_SIMULATED);
return (err == -EOPNOTSUPP) ? 0 : err;
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index b5d9e367b893d..fe2ed0894d49d 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -215,11 +215,13 @@ void get_online_mems(void)
{
percpu_down_read(&mem_hotplug_lock);
}
+EXPORT_SYMBOL_FOR_MODULES(get_online_mems, "hwpoison-inject");
void put_online_mems(void)
{
percpu_up_read(&mem_hotplug_lock);
}
+EXPORT_SYMBOL_FOR_MODULES(put_online_mems, "hwpoison-inject");
bool movable_node_enabled = false;
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread