Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
@ 2026-07-05 18:07 Jiaqi Yan
  2026-07-05 18:07 ` [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t Jiaqi Yan
                   ` (6 more replies)
  0 siblings, 7 replies; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-05 18:07 UTC (permalink / raw)
  To: linmiaohe, ljs, ziy, vbabka
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn, Jiaqi Yan

At the end of dissolve_free_hugetlb_folio(), a free HugeTLB
folio becomes non-HugeTLB and is released to buddy allocator
as a high-order folio, e.g. a folio that contains 262144 pages
if the folio was a 1G HugeTLB hugepage.

This is problematic if the HugeTLB hugepage contained HWPoison
subpages. In that case, since buddy allocator does not check
HWPoison for non-zero-order folio, the raw HWPoison page can
be given out with its buddy page and be re-used by either
kernel or userspace.

Memory failure recovery (MFR) code (mm/memory-failure.c) does
attempt to take raw HWPoison page off buddy allocator after
dissolve_free_hugetlb_folio(). However, there is always a time
window between dissolve_free_hugetlb_folio() frees a HWPoison
high-order folio to buddy allocator and MFR takes HWPoison
raw page off buddy allocator.

Another similar situation is when a transparent huge page (THP)
is handled by MFR but splitting failed. Such THP will eventually
be released to buddy allocator when owning userspace processes
are gone, but with certain subpages having HWPoison [9].

One obvious way to avoid both problems is to add page sanity
checks in page allocate or free path. However, it is against
the past efforts to reduce sanity check overhead [1,2,3].

Introduce free_has_hwpoisoned() to only free the healthy pages
and exclude the HWPoison ones in the high-order folio.
free_has_hwpoisoned() happens at the end of free_pages_prepare(),
which already deals with both decomposing the original compound
page, updating page metadata like alloc tag and page owner.
It is also only applied when PG_has_hwpoisoned indicates folio
contains certain HWPoison page(s) for performance reason.
Its idea is to iterate through the sub-pages of the folio to
identify contiguous ranges of healthy pages. Instead of freeing
pages one by one, free_has_hwpoisoned() then re-use
free_prepared_contig_range() [11] to decompose healthy ranges into
the largest possible chunks of different orders. Every chunk is
freed via __free_frozen_pages().

free_has_hwpoisoned() has linear time complexity wrt the number
of pages in the folio. While the power-of-two decomposition
ensures that the number of calls to the buddy allocator is
logarithmic for each contiguous healthy range, the mandatory
linear scan of pages to identify PageHWPoison() defines the
overall time complexity.

I tested with some test-only code [4] and hugetlb-mfr [5], by
checking the status of pcplist and freelist immediately after
dissolve_free_hugetlb_folio() a free 2M or 1G HugeTLB page that
contains 1~8 HWPoison raw pages:

- HWPoison pages are excluded by free_has_hwpoisoned().

- Some healthy pages can be in zone->per_cpu_pageset (pcplist)
  because pcp_count is not high enough. Many healthy pages are
  in some order's zone->free_area[order].free_list (freelist).

- In rare cases, some healthy pages are in neither pcplist
  nor freelist. My best guest is they are allocated before
  the test checks.

To illustrate the latency free_has_hwpoisoned() added to the
memory freeing path, I tested its time cost with 8 HWPoison
pages with instrument code in [4] for 20 sample runs on a machine
having 56 Intel Skylake physical cores and 768GB memory:

- Has HWPoison path: mean=1030us, stdev=21us

- No HWPoison path: mean=66us, stdev=6us

free_has_hwpoisoned() is around 15x the baseline. Its cost is
nontrivial, but still far from triggering soft lockup, and fair
for handling exceptional hardware memory errors.

Now that free_has_hwpoisoned() ensures HWPoison pages never made
into buddy allocator, MFR don't need to take_page_off_buddy() anymore
after disovling HWPoison hugepages. So replace __page_handle_poison()
with new __hugepage_handle_poison() for HugeTLB specific call sites.
It may worthy to note that this patchset doesn't affect the soft
offline behavior in MFR. This is because soft offline does not
folio_set_hwpoison() upfront, and for HugeTLB case, doesn't involve
get_huge_page_for_hwpoison().

To provide test coverage for the new __hugepage_handle_poison()
in me_huge_page(), the last commit adds a MADV_HARD test variant
for anonymous HugeTLB pages. It also cover the code path that
frees a HugeTLB page that contains 1 raw HWPoison page.

Based on commit cfb8731f5396 ("mm: fix CONFIG_STACK_GROWSUP typo in tools/testing/vma/include/dup.h")

Changelog

v5 [11] -> v6

- Rebase to recent akpm/mm-unstable and address comments from Zi Yan,
  Miaohe Lin, Vlastimil Babka.

- Extract free_pages_sanitize() to avoid touching HWPoison page(s)
  at the end of __free_pages_prepare().

- Introduce FPI_SANITIZE and add a fpi_t argument to the freeing path
  free_has_hwpoisoned() -> __free_prepared_contig_range(), so that
  healthy page blocks are sanitized before __free_frozen_pages().

- Make free_has_hwpoisoned() be compatible with order==0.

- Repeat the previous test done on both X86 and ARM64 machines.
  CONFIG_KASAN + CONFIG_KASAN_SW_TAGS + kasan.fault=report are
  enabled on the ARM64 machine.

v4 [10] -> v5

- Rebase to very recent akpm/mm-unstable.

- Re-use free_prepared_contig_range() introduced by [11], and remove
  free_contiguous_pages() in v4.

- Instead of using struct page pointer, iterate over pfn in
  free_has_hwpoison().

- Re-ested and re-evaluated free_has_hwpoison()'s time cost.

- Add memory failure recovery test for anonymous 1G HugeTLB page to
  gain test coverage for __hugepage_handle_poison() and for freeing
  1G HugeTLB page that has 1 HWPoison page.

v3 [8] -> v4

- Address comments from Zi Yan, Miaohe Lin, Harry Yoo.

- Set has_hwpoisoned flag after introducing free_has_hwpoisoned().

- Unwrap free_pages_prepare_has_hwpoisoned() into free_pages_prepare().

- If folio has HWPoison, its healthy pages will be freed with FPI_NONE
  right in free_pages_prepare(), who returns false to indicate caller
  should not proceeding its own freeing action.

- Rework the commit on __page_handle_poison. Only change the handling
  for HWPoison HugeTLB page, leaving free buddy page and soft offline
  handling alone.

v2 [7] -> v3:

- Address comments from Mathew Wilcox, Harry Hoo, Miaohe Lin.

- Let free_has_hwpoisoned() happen after free_pages_prepare(),
  which help to deal with decomposing the original compound page,
  and with page metadata like alloc tag and page owner.

- Tested with "page_owner=on" and CONFIG_MEM_ALLOC_PROFILING*=y.

- Wrap checking PG_has_hwpoisoned and free_has_hwpoisoned() into
  free_pages_prepare_has_hwpoisoned(), which replaces
  free_pages_prepare() calls in free_frozen_pages().

- Rename free_has_hwpoison_page() to free_has_hwpoisoned().

- Measure latency added by free_has_hwpoisoned().

- Ensure struct page *end is only used for pointer arithmetic,
  instead of accessed as page.

- Refactor page_handl_poison instead of just __page_handle_poison().

v1 [6] -> v2:

- Total reimplementation based on discussions with Mathew Wilcox,
  Harry Hoo, Zi Yan etc

- hugetlb_free_hwpoison_folio() => free_has_hwpoison_pages().

- Utilize has_hwpoisoned flag to tell buddy allocator a high-order
  folio contains HWPoison.

- Simplify __page_handle_poison() given that the HWPoison page(s)
  won't be freed within high-order folio.

[1] https://lore.kernel.org/linux-mm/1460711275-1130-15-git-send-email-mgorman@techsingularity.net
[2] https://lore.kernel.org/linux-mm/1460711275-1130-16-git-send-email-mgorman@techsingularity.net
[3] https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
[4] https://drive.google.com/file/d/1CzJn1Cc4wCCm183Y77h244fyZIkTLzCt/view?usp=sharing
[5] https://lore.kernel.org/linux-mm/20251116013223.1557158-3-jiaqiyan@google.com
[6] https://lore.kernel.org/linux-mm/20251116014721.1561456-1-jiaqiyan@google.com
[7] https://lore.kernel.org/linux-mm/20251219183346.3627510-1-jiaqiyan@google.com
[8] https://lore.kernel.org/linux-mm/20260112004923.888429-1-jiaqiyan@google.com
[9] https://lore.kernel.org/linux-mm/20260113205441.506897-1-boudewijn@delta-utec.com
[10] https://lore.kernel.org/linux-mm/20260202194125.2191216-1-jiaqiyan@google.com
[11] https://lore.kernel.org/all/20260401101634.2868165-2-usama.anjum@arm.com
[12] https://lore.kernel.org/linux-mm/20260531055829.3636554-1-jiaqiyan@google.com

Jiaqi Yan (5):
  mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t
  mm/page_alloc: only free healthy pages in high-order has_hwpoisoned
    folio
  mm/memory-failure: set has_hwpoisoned flags on dissolved HugeTLB folio
  mm/memory-failure: skip take_page_off_buddy after dissolving HWPoison
    HugeTLB page
  selftests/mm: add hard memory failure anonymous HugeTLB test

 include/linux/page-flags.h                  |   2 +-
 mm/memory-failure.c                         |  37 +++-
 mm/page_alloc.c                             | 176 ++++++++++++++++----
 tools/testing/selftests/mm/memory-failure.c |  70 +++++++-
 4 files changed, 247 insertions(+), 38 deletions(-)

-- 
2.55.0.rc0.799.gd6f94ed593-goog



^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t
  2026-07-05 18:07 [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
@ 2026-07-05 18:07 ` Jiaqi Yan
  2026-07-17  7:17   ` Miaohe Lin
                     ` (2 more replies)
  2026-07-05 18:07 ` [PATCH v6 2/5] mm/page_alloc: only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
                   ` (5 subsequent siblings)
  6 siblings, 3 replies; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-05 18:07 UTC (permalink / raw)
  To: linmiaohe, ljs, ziy, vbabka
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn, Jiaqi Yan

A future commit introducing free_has_hwpoison() to be called within
__free_pages_prepare() will need to pass specific fpi_t flags when
freeing a contiguous range of pages. Currently,
free_prepared_contig_range() hardcodes the FPI_PREPARED flag and
does not accept any caller-provided flags.

Rename the core logic to __free_prepared_contig_range() to accept
an fpi_t argument. It bitwise ORs with the required FPI_PREPARED
flag.

This is a preparatory commit with no functional changes.

Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
---
 mm/page_alloc.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 762d9b6bc792..7d27aff48b15 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -6852,8 +6852,8 @@ void __init page_alloc_sysctl_init(void)
 	register_sysctl_init("vm", page_alloc_sysctl_table);
 }
 
-static void free_prepared_contig_range(struct page *page,
-		unsigned long nr_pages)
+static void __free_prepared_contig_range(struct page *page,
+		unsigned long nr_pages, fpi_t fpi_flags)
 {
 	unsigned long pfn = page_to_pfn(page);
 
@@ -6870,7 +6870,7 @@ static void free_prepared_contig_range(struct page *page,
 		 * Free the chunk as a single block. Our caller has already
 		 * called free_pages_prepare() for each order-0 page.
 		 */
-		__free_frozen_pages(page, order, FPI_PREPARED);
+		__free_frozen_pages(page, order, fpi_flags | FPI_PREPARED);
 
 		pfn += 1UL << order;
 		page += 1UL << order;
@@ -6878,6 +6878,11 @@ static void free_prepared_contig_range(struct page *page,
 	}
 }
 
+static void free_prepared_contig_range(struct page *page, unsigned long nr_pages)
+{
+	__free_prepared_contig_range(page, nr_pages, FPI_NONE);
+}
+
 static void __free_contig_range_common(unsigned long pfn, unsigned long nr_pages,
 		bool is_frozen)
 {
-- 
2.55.0.rc0.799.gd6f94ed593-goog



^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v6 2/5] mm/page_alloc: only free healthy pages in high-order has_hwpoisoned folio
  2026-07-05 18:07 [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
  2026-07-05 18:07 ` [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t Jiaqi Yan
@ 2026-07-05 18:07 ` Jiaqi Yan
  2026-07-17  7:19   ` Miaohe Lin
  2026-07-22  9:40   ` Vlastimil Babka (SUSE)
  2026-07-05 18:07 ` [PATCH v6 3/5] mm/memory-failure: set has_hwpoisoned flags on dissolved HugeTLB folio Jiaqi Yan
                   ` (4 subsequent siblings)
  6 siblings, 2 replies; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-05 18:07 UTC (permalink / raw)
  To: linmiaohe, ljs, ziy, vbabka
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn, Jiaqi Yan

At the end of dissolve_free_hugetlb_folio(), a free HugeTLB folio
becomes non-HugeTLB, and it is released to buddy allocator
as a high-order folio, e.g. a folio that contains 262144 pages
if the folio was a 1G HugeTLB hugepage.

This is problematic if the HugeTLB hugepage contained HWPoison
subpages. In that case, since buddy allocator does not check
HWPoison for non-zero-order folio, the raw HWPoison page can
be given out with its buddy page and be re-used by either
kernel or userspace.

Memory failure recovery (MFR) in kernel does attempt to take
raw HWPoison page off buddy allocator after
dissolve_free_hugetlb_folio(). However, there is always a time
window between dissolve_free_hugetlb_folio() frees a HWPoison
high-order folio to buddy allocator and MFR takes HWPoison
raw page off buddy allocator.

Another similar situation is when a transparent huge page (THP)
runs into memory failure but splitting failed. Such THP will
eventually be released to buddy allocator when owning userspace
processes are gone, but with certain subpages having HWPoison.

One obvious way to avoid both problems is to add page sanity
checks in page allocate or free path. However, it is against
the past efforts to reduce sanity check overhead [1,2,3].

Introduce free_has_hwpoisoned() to only free the healthy pages
and to exclude the HWPoison ones in the high-order folio.
The idea is to iterate through the sub-pages of the folio to
identify contiguous ranges of healthy pages.

free_has_hwpoisoned() is added at the end of __free_pages_prepare()
as a shortcut and only if PG_has_hwpoisoned indicates HWPoison page
exists and after checks and preparations in __free_pages_prepare()
all succeeded. It then use __free_prepared_contig_range() to
decompose healthy range into the largest possible chunks of
different orders, then freed via __free_frozen_pages().

free_has_hwpoisoned() has linear time complexity wrt the number
of pages in the folio. While the power-of-two decomposition
ensures that the number of calls to the buddy allocator is
logarithmic for each contiguous healthy range, the mandatory
linear scan of pages to identify PageHWPoison() defines the
overall time complexity. For a 1G hugepage having 8 HWPoison
pages, free_has_hwpoisoned() takes around 1ms on average on
a system having 56 Intel Skylake physical cores. This is
15x to the case of freeing no HWPoison page. The cost is far
from triggering soft lockup, and fair for handling exceptional
hardware memory errors.

[1] https://lore.kernel.org/linux-mm/1460711275-1130-15-git-send-email-mgorman@techsingularity.net
[2] https://lore.kernel.org/linux-mm/1460711275-1130-16-git-send-email-mgorman@techsingularity.net
[3] https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz

Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
---
 mm/page_alloc.c | 165 ++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 140 insertions(+), 25 deletions(-)

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 7d27aff48b15..6418896c9df6 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -93,6 +93,13 @@ typedef int __bitwise fpi_t;
 /* free_pages_prepare() has already been called for page(s) being freed. */
 #define FPI_PREPARED		((__force fpi_t)BIT(3))
 
+/*
+ * Page(s) needs to go through free_pages_sanitize(), for example, because
+ * free_pages_prepare() cannot sanitize a high-order page block due to
+ * hardware error in some page(s).
+ */
+#define FPI_SANITIZE		((__force fpi_t)BIT(4))
+
 /* prevent >1 _updater_ of zone percpu pageset ->high and ->batch fields */
 static DEFINE_MUTEX(pcp_batch_high_lock);
 #define MIN_PERCPU_PAGELIST_HIGH_FRACTION (8)
@@ -210,6 +217,8 @@ gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK;
 unsigned int pageblock_order __read_mostly;
 #endif
 
+static void free_has_hwpoisoned(struct page *page, unsigned int order,
+				fpi_t fpi_flags);
 static void __free_pages_ok(struct page *page, unsigned int order,
 			    fpi_t fpi_flags);
 static void reserve_highatomic_pageblock(struct page *page, int order,
@@ -1311,14 +1320,73 @@ static inline void pgalloc_tag_sub_pages(struct alloc_tag *tag, unsigned int nr)
 
 #endif /* CONFIG_MEM_ALLOC_PROFILING */
 
+/*
+ * Sanitize, which requires writing, a block of pages at the last moment of
+ * preparing to freeing them, i.e. __free_pages_prepare().
+ */
+static void free_pages_sanitize(struct page *page, unsigned int order)
+{
+	bool init = want_init_on_free();
+	/*
+	 * __kasan_unpoison_pages() sets kasan tag on every tail page, so
+	 * it is fine to use should_skip_kasan_poison() when pages here
+	 * were a set of tail pages from a compound folio.
+	 */
+	bool skip_kasan_poison = should_skip_kasan_poison(page);
+
+	kernel_poison_pages(page, 1 << order);
+
+	/*
+	 * As memory initialization might be integrated into KASAN,
+	 * KASAN poisoning and memory initialization code must be
+	 * kept together to avoid discrepancies in behavior.
+	 *
+	 * With hardware tag-based KASAN, memory tags must be set before the
+	 * page becomes unavailable via debug_pagealloc or arch_free_page.
+	 */
+	if (!skip_kasan_poison) {
+		kasan_poison_pages(page, order, init);
+
+		/* Memory is already initialized if KASAN did it internally. */
+		if (kasan_has_integrated_init())
+			init = false;
+	}
+	if (init)
+		clear_highpages_kasan_tagged(page, 1 << order);
+
+	/*
+	 * arch_free_page() can make the page's contents inaccessible.  s390
+	 * does this.  So nothing which can access the page's contents should
+	 * happen after this.
+	 */
+	arch_free_page(page, order);
+
+	debug_pagealloc_unmap_pages(page, 1 << order);
+}
+
+/*
+ * Returns
+ * - true: checks and preparations all good, caller can proceed freeing.
+ * - false: do not proceed freeing for one of the following reasons:
+ *   1. Some check failed so it is not safe to proceed freeing.
+ *   2. A compound page has some HWPoison pages. The healthy pages
+ *      are already safely freed, and the HWPoison ones isolated.
+ */
 static __always_inline bool __free_pages_prepare(struct page *page,
 		unsigned int order, fpi_t fpi_flags)
 {
 	int bad = 0;
-	bool skip_kasan_poison = should_skip_kasan_poison(page);
-	bool init = want_init_on_free();
 	bool compound = PageCompound(page);
 	struct folio *folio = page_folio(page);
+	/*
+	 * When dealing with compound page, PG_has_hwpoisoned is cleared
+	 * with PAGE_FLAGS_SECOND. So the check must be done first.
+	 *
+	 * Note we can't exclude PG_has_hwpoisoned from PAGE_FLAGS_SECOND.
+	 * Because PG_has_hwpoisoned == PG_active, free_page_is_bad() will
+	 * confuse and complaint that the first tail page is still active.
+	 */
+	bool should_fhh = compound && folio_test_has_hwpoisoned(folio);
 
 	if (fpi_flags & FPI_PREPARED)
 		return true;
@@ -1416,34 +1484,19 @@ static __always_inline bool __free_pages_prepare(struct page *page,
 					   PAGE_SIZE << order);
 	}
 
-	kernel_poison_pages(page, 1 << order);
-
 	/*
-	 * As memory initialization might be integrated into KASAN,
-	 * KASAN poisoning and memory initialization code must be
-	 * kept together to avoid discrepancies in behavior.
+	 * After breaking down compound page and dealing with page metadata
+	 * (e.g. page owner and page alloc tags), take a shortcut if this
+	 * was a compound page containing certain HWPoison subpages.
 	 *
-	 * With hardware tag-based KASAN, memory tags must be set before the
-	 * page becomes unavailable via debug_pagealloc or arch_free_page.
+	 * FPI_SANITIZE to remember free_pages_sanitize() healthy pages.
 	 */
-	if (!skip_kasan_poison) {
-		kasan_poison_pages(page, order, init);
-
-		/* Memory is already initialized if KASAN did it internally. */
-		if (kasan_has_integrated_init())
-			init = false;
+	if (should_fhh) {
+		free_has_hwpoisoned(page, order, fpi_flags | FPI_SANITIZE);
+		return false;
 	}
-	if (init)
-		clear_highpages_kasan_tagged(page, 1 << order);
-
-	/*
-	 * arch_free_page() can make the page's contents inaccessible.  s390
-	 * does this.  So nothing which can access the page's contents should
-	 * happen after this.
-	 */
-	arch_free_page(page, order);
 
-	debug_pagealloc_unmap_pages(page, 1 << order);
+	free_pages_sanitize(page, order);
 
 	return true;
 }
@@ -6869,7 +6922,14 @@ static void __free_prepared_contig_range(struct page *page,
 		/*
 		 * Free the chunk as a single block. Our caller has already
 		 * called free_pages_prepare() for each order-0 page.
+		 *
+		 * If the original compound page has HWPoison page,
+		 * free_pages_prepare() has to skip sanitize at that time,
+		 * but now it is good time to do that.
 		 */
+		if (fpi_flags & FPI_SANITIZE)
+			free_pages_sanitize(page, order);
+
 		__free_frozen_pages(page, order, fpi_flags | FPI_PREPARED);
 
 		pfn += 1UL << order;
@@ -6956,6 +7016,61 @@ void __free_contig_range(unsigned long pfn, unsigned long nr_pages)
 	__free_contig_range_common(pfn, nr_pages, /* is_frozen= */ false);
 }
 
+/*
+ * Given some contiguous pages that have certain number of HWPoison page(s),
+ * free only the healthy ones.
+ *
+ * Used at the end of __free_pages_prepare(). Even if having HWPoison pages,
+ * breaking down compound page and clearing metadata (e.g. page owner, alloc
+ * tag) can be done together during __free_pages_prepare(), which simplifies
+ * the splitting here: unlike __split_unmapped_folio(), there is no need to
+ * turn split pages into a compound page or to carry metadata.
+ *
+ * It scans every raw page of the compound page and causes nontrivial overhead.
+ * So only use this when the compound page contains HWPoison page(s).
+ *
+ * It also works when order == 0, regardless of PageHWPoison() or not.
+ *
+ * This implementation needs rework in memdesc world.
+ */
+static void free_has_hwpoisoned(struct page *page, unsigned int order,
+				fpi_t fpi_flags)
+{
+	unsigned long curr = page_to_pfn(page);
+	unsigned long end_pfn = curr + (1 << order);
+	unsigned long next;
+	unsigned long total_freed = 0;
+	unsigned long total_hwp = 0;
+
+	while (curr < end_pfn) {
+		next = curr;
+
+		while (next < end_pfn && !PageHWPoison(pfn_to_page(next)))
+			++next;
+
+		if (next != end_pfn) {
+			/*
+			 * Avoid accounting error when the page is freed
+			 * by unpoison_memory().
+			 */
+			clear_page_tag_ref(pfn_to_page(next));
+			++total_hwp;
+		}
+
+		__free_prepared_contig_range(pfn_to_page(curr), next - curr,
+					     fpi_flags);
+		total_freed += next - curr;
+
+		if (next == end_pfn)
+			break;
+
+		curr = next + 1;
+	}
+
+	pr_info("Freed %#lx pages, excluded %#lx HWPoison pages\n",
+		total_freed, total_hwp);
+}
+
 #ifdef CONFIG_CONTIG_ALLOC
 /* Usage: See admin-guide/dynamic-debug-howto.rst */
 static void alloc_contig_dump_pages(struct list_head *page_list)
-- 
2.55.0.rc0.799.gd6f94ed593-goog



^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v6 3/5] mm/memory-failure: set has_hwpoisoned flags on dissolved HugeTLB folio
  2026-07-05 18:07 [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
  2026-07-05 18:07 ` [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t Jiaqi Yan
  2026-07-05 18:07 ` [PATCH v6 2/5] mm/page_alloc: only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
@ 2026-07-05 18:07 ` Jiaqi Yan
  2026-07-25  3:05   ` Jiaqi Yan
  2026-07-05 18:07 ` [PATCH v6 4/5] mm/memory-failure: skip take_page_off_buddy after dissolving HWPoison HugeTLB page Jiaqi Yan
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-05 18:07 UTC (permalink / raw)
  To: linmiaohe, ljs, ziy, vbabka
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn, Jiaqi Yan

When a free HWPoison HugeTLB folio is dissolved, it becomes
non-HugeTLB and is released to buddy allocator as a high-order
folio.

Set has_hwpoisoned flags on the high-order folio so that buddy
allocator can tell that it contains certain HWPoison page(s),
and can handle it specially with free_has_hwpoisoned().

Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
Acked-by: Miaohe Lin <linmiaohe@huawei.com>
---
 include/linux/page-flags.h | 2 +-
 mm/memory-failure.c        | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 7a863572adce..3cd524e04a30 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -879,7 +879,7 @@ FOLIO_FLAG_FALSE(partially_mapped)
 
 #define PG_head_mask ((1UL << PG_head))
 
-#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_TRANSPARENT_HUGEPAGE)
+#if defined(CONFIG_MEMORY_FAILURE) && (defined(CONFIG_TRANSPARENT_HUGEPAGE) || defined(CONFIG_HUGETLB_PAGE))
 /*
  * PageHasHWPoisoned indicates that at least one subpage is hwpoisoned in the
  * compound page.
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 4916ab145325..3d15b4c1b694 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -2008,6 +2008,7 @@ void folio_clear_hugetlb_hwpoison(struct folio *folio)
 	if (folio_test_hugetlb_vmemmap_optimized(folio))
 		return;
 	folio_clear_hwpoison(folio);
+	folio_set_has_hwpoisoned(folio);
 	folio_free_raw_hwp(folio, true);
 }
 
-- 
2.55.0.rc0.799.gd6f94ed593-goog



^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v6 4/5] mm/memory-failure: skip take_page_off_buddy after dissolving HWPoison HugeTLB page
  2026-07-05 18:07 [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
                   ` (2 preceding siblings ...)
  2026-07-05 18:07 ` [PATCH v6 3/5] mm/memory-failure: set has_hwpoisoned flags on dissolved HugeTLB folio Jiaqi Yan
@ 2026-07-05 18:07 ` Jiaqi Yan
  2026-07-05 18:07 ` [PATCH v6 5/5] selftests/mm: add hard memory failure anonymous HugeTLB test Jiaqi Yan
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-05 18:07 UTC (permalink / raw)
  To: linmiaohe, ljs, ziy, vbabka
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn, Jiaqi Yan

Now that HWPoison subpage(s) within HugeTLB page will be rejected by
buddy allocator during dissolve_free_hugetlb_folio(), there is no
need to drain_all_pages() and take_page_off_buddy() anymore. In fact,
calling take_page_off_buddy() after dissolve_free_hugetlb_folio()
succeeded returns false, making caller think __page_handle_poison()
failed.

Add __hugepage_handle_poison() and replace __page_handle_poison() at
HugeTLB specific call sites. The being handled HugeTLB page either
is free at the moment of try_memory_failure_hugetlb(), or becomes
free at the moment of me_huge_page().

Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
---
 mm/memory-failure.c | 36 ++++++++++++++++++++++++++++++------
 1 file changed, 30 insertions(+), 6 deletions(-)

diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 3d15b4c1b694..a37b67550718 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -174,6 +174,30 @@ static struct rb_root_cached pfn_space_itree = RB_ROOT_CACHED;
 static DEFINE_MUTEX(pfn_space_lock);
 
 /*
+ * Only for a HugeTLB page being handled by memory_failure(). The key
+ * difference to soft_offline() is that, no HWPoison subpage will make
+ * into buddy allocator after a successful dissolve_free_hugetlb_folio(),
+ * so take_page_off_buddy() is unnecessary.
+ */
+static int __hugepage_handle_poison(struct page *page)
+{
+	struct folio *folio = page_folio(page);
+
+	/*
+	 * Can't use dissolve_free_hugetlb_folio() without a reliable
+	 * raw_hwp_list telling which subpage is HWPoison. So do not free
+	 * them to the buddy allocator. dequeue_hugetlb_folio_node_exact()
+	 * will ensure to never re-allocate this hugepage.
+	 */
+	if (folio_test_hugetlb_raw_hwp_unreliable(folio))
+		/* raw_hwp_list becomes unreliable when kmalloc() fails. */
+		return -ENOMEM;
+
+	return dissolve_free_hugetlb_folio(folio);
+}
+
+/*
+ * Only for a free or HugeTLB page being handled by soft_offline().
  * Return values:
  *   1:   the page is dissolved (if needed) and taken off from buddy,
  *   0:   the page is dissolved (if needed) and not taken off from buddy,
@@ -1166,11 +1190,11 @@ static int me_huge_page(struct page_state *ps, struct page *p)
 		 * subpages.
 		 */
 		folio_put(folio);
-		if (__page_handle_poison(p) > 0) {
+		if (__hugepage_handle_poison(p)) {
+			res = MF_FAILED;
+		} else {
 			page_ref_inc(p);
 			res = MF_RECOVERED;
-		} else {
-			res = MF_FAILED;
 		}
 	}
 
@@ -2133,11 +2157,11 @@ static int try_memory_failure_hugetlb(unsigned long pfn, int flags)
 	 */
 	if (res == MF_HUGETLB_FREED) {
 		folio_unlock(folio);
-		if (__page_handle_poison(p) > 0) {
+		if (__hugepage_handle_poison(p)) {
+			res = MF_FAILED;
+		} else {
 			page_ref_inc(p);
 			res = MF_RECOVERED;
-		} else {
-			res = MF_FAILED;
 		}
 		return action_result(pfn, MF_MSG_FREE_HUGE, res);
 	}
-- 
2.55.0.rc0.799.gd6f94ed593-goog



^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v6 5/5] selftests/mm: add hard memory failure anonymous HugeTLB test
  2026-07-05 18:07 [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
                   ` (3 preceding siblings ...)
  2026-07-05 18:07 ` [PATCH v6 4/5] mm/memory-failure: skip take_page_off_buddy after dissolving HWPoison HugeTLB page Jiaqi Yan
@ 2026-07-05 18:07 ` Jiaqi Yan
  2026-07-25  3:05   ` Jiaqi Yan
  2026-07-05 18:50 ` [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Andrew Morton
       [not found] ` <85cb7ea8-8116-4092-8310-69b61eb8602c@kernel.org>
  6 siblings, 1 reply; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-05 18:07 UTC (permalink / raw)
  To: linmiaohe, ljs, ziy, vbabka
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn, Jiaqi Yan

Add a new testcase to validate memory failure recovery for HWPoison
anonymous HugeTLB page, including proper SIGBUS delivery, releasing
a HugeTLB page containing one HWPoison page to buddy allocator, and
isolation of the raw HWPoison page.

The test uses HugeTLB's default hugesize.

Although can be added in future, this patch does not support testing
the MADV_SOFT variant.

Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
---
 tools/testing/selftests/mm/memory-failure.c | 70 ++++++++++++++++++++-
 1 file changed, 67 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/mm/memory-failure.c b/tools/testing/selftests/mm/memory-failure.c
index 032ed952057c..78c105ce2af1 100644
--- a/tools/testing/selftests/mm/memory-failure.c
+++ b/tools/testing/selftests/mm/memory-failure.c
@@ -18,6 +18,7 @@
 #include <linux/magic.h>
 #include <errno.h>
 
+#include "hugepage_settings.h"
 #include "vm_util.h"
 
 enum inject_type {
@@ -27,6 +28,7 @@ enum inject_type {
 
 enum result_type {
 	MADV_HARD_ANON,
+	MADV_HARD_ANON_HUGETLB,
 	MADV_HARD_CLEAN_PAGECACHE,
 	MADV_HARD_DIRTY_PAGECACHE,
 	MADV_SOFT_ANON,
@@ -47,6 +49,8 @@ FIXTURE(memory_failure)
 	int pagemap_fd;
 	int kpageflags_fd;
 	bool triggered;
+	/* Number of initial HugeTLB pages with default page size. */
+	unsigned long nr_hugetlb_pages;
 };
 
 FIXTURE_VARIANT(memory_failure)
@@ -157,6 +161,7 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
 		  void *vaddr, enum result_type type, int setjmp)
 {
 	unsigned long size;
+	unsigned long nr_hugetlb_pages;
 	uint64_t pfn_flags;
 
 	switch (type) {
@@ -174,6 +179,7 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
 		ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn);
 		break;
 	case MADV_HARD_ANON:
+	case MADV_HARD_ANON_HUGETLB:
 	case MADV_HARD_DIRTY_PAGECACHE:
 		/* The SIGBUS signal should have been received. */
 		ASSERT_EQ(setjmp, 1);
@@ -184,8 +190,15 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
 		ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size);
 		ASSERT_EQ(siginfo.si_addr, vaddr);
 
-		/* XXX Check backing pte is hwpoison entry when supported. */
-		ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
+		if (type != MADV_HARD_ANON_HUGETLB)
+			/*
+			 * Check backing pte is hwpoison entry when supported.
+			 * Although try_to_unmap_one() also installs hwpoison
+			 * entry for HugeTLB, pagemap_hugetlb_range() doesn't
+			 * parse swap entries at all.
+			 */
+			ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
+
 		break;
 	default:
 		SKIP(return, "unexpected inject type %d.\n", type);
@@ -193,7 +206,19 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
 
 	/* Check if the value of HardwareCorrupted has increased. */
 	ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
-	ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
+
+	if (type == MADV_HARD_ANON_HUGETLB) {
+		/*
+		 * Only one page is hardware corrupted; the rest should all be
+		 * released to buddy allocator.
+		 */
+		ASSERT_EQ(size, self->corrupted_size + getpagesize() / 1024);
+		/* HugeTLB should have lost the HWPoison HugeTLB page. */
+		nr_hugetlb_pages = hugetlb_nr_default_pages();
+		ASSERT_EQ(nr_hugetlb_pages + 1, self->nr_hugetlb_pages);
+	} else {
+		ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
+	}
 
 	/* Check if HWPoison flag is set. */
 	ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
@@ -247,6 +272,45 @@ TEST_F(memory_failure, anon)
 	ASSERT_EQ(munmap(addr, self->page_size), 0);
 }
 
+TEST_F(memory_failure, anon_hugetlb)
+{
+	char *addr;
+	int ret;
+	const unsigned long nr_alloc_hugetlb_pages = 4;
+	unsigned long alloc_size;
+
+	if (variant->type == MADV_SOFT)
+		SKIP(return, "Soft offline test is not implemented");
+
+	/* HugeTLB settings will be automatically restored when test exits. */
+	hugetlb_setup_default(nr_alloc_hugetlb_pages);
+
+	alloc_size = default_huge_page_size() * nr_alloc_hugetlb_pages;
+	self->page_size = default_huge_page_size();
+	self->nr_hugetlb_pages = hugetlb_nr_default_pages();
+
+	addr = mmap(0, alloc_size, PROT_READ | PROT_WRITE,
+		    MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1, 0);
+	if (addr == MAP_FAILED)
+		SKIP(return, "mmap failed, not enough memory or hugetlb not supported.\n");
+	memset(addr, 0xce, alloc_size);
+
+	prepare(_metadata, self, addr);
+
+	ret = sigsetjmp(signal_jmp_buf, 1);
+	if (!self->triggered) {
+		self->triggered = true;
+		ASSERT_EQ(variant->inject(self, addr), 0);
+		FORCE_READ(*addr);
+	}
+
+	check(_metadata, self, addr, MADV_HARD_ANON_HUGETLB, ret);
+
+	cleanup(_metadata, self, addr);
+
+	ASSERT_EQ(munmap(addr, alloc_size), 0);
+}
+
 static int prepare_file(const char *fname, unsigned long size)
 {
 	int fd;
-- 
2.55.0.rc0.799.gd6f94ed593-goog



^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-05 18:07 [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
                   ` (4 preceding siblings ...)
  2026-07-05 18:07 ` [PATCH v6 5/5] selftests/mm: add hard memory failure anonymous HugeTLB test Jiaqi Yan
@ 2026-07-05 18:50 ` Andrew Morton
  2026-07-06  9:03   ` David Hildenbrand (Arm)
  2026-07-25  3:06   ` Jiaqi Yan
       [not found] ` <85cb7ea8-8116-4092-8310-69b61eb8602c@kernel.org>
  6 siblings, 2 replies; 26+ messages in thread
From: Andrew Morton @ 2026-07-05 18:50 UTC (permalink / raw)
  To: Jiaqi Yan
  Cc: linmiaohe, ljs, ziy, vbabka, osalvador, harry.yoo, willy,
	osalvador, jackmanb, hannes, nao.horiguchi, david, william.roche,
	tony.luck, wangkefeng.wang, jane.chu, muchun.song, liam, rientjes,
	duenwen, jthoughton, linux-mm, linux-kernel, vbabka, rppt, shuah,
	surenb, mhocko, boudewijn

On Sun,  5 Jul 2026 18:07:09 +0000 Jiaqi Yan <jiaqiyan@google.com> wrote:

> At the end of dissolve_free_hugetlb_folio(), a free HugeTLB
> folio becomes non-HugeTLB and is released to buddy allocator
> as a high-order folio, e.g. a folio that contains 262144 pages
> if the folio was a 1G HugeTLB hugepage.
> 
> ...
> 
> Introduce free_has_hwpoisoned() to only free the healthy pages
> and exclude the HWPoison ones in the high-order folio.
> free_has_hwpoisoned() happens at the end of free_pages_prepare(),
> which already deals with both decomposing the original compound
> page, updating page metadata like alloc tag and page owner.
> It is also only applied when PG_has_hwpoisoned indicates folio
> contains certain HWPoison page(s) for performance reason.
> Its idea is to iterate through the sub-pages of the folio to
> identify contiguous ranges of healthy pages. Instead of freeing
> pages one by one, free_has_hwpoisoned() then re-use
> free_prepared_contig_range() [11] to decompose healthy ranges into
> the largest possible chunks of different orders. Every chunk is
> freed via __free_frozen_pages().

Thanks.  I'll await further reviewer input before taking any action
with this series.

AI review flags several possible issues, some of them pre-existing:

	https://sashiko.dev/#/patchset/20260705180714.3708947-1-jiaqiyan@google.com




^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-05 18:50 ` [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Andrew Morton
@ 2026-07-06  9:03   ` David Hildenbrand (Arm)
  2026-07-25  3:06   ` Jiaqi Yan
  1 sibling, 0 replies; 26+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-06  9:03 UTC (permalink / raw)
  To: Andrew Morton, Jiaqi Yan
  Cc: linmiaohe, ljs, ziy, vbabka, osalvador, harry.yoo, willy,
	osalvador, jackmanb, hannes, nao.horiguchi, william.roche,
	tony.luck, wangkefeng.wang, jane.chu, muchun.song, liam, rientjes,
	duenwen, jthoughton, linux-mm, linux-kernel, vbabka, rppt, shuah,
	surenb, mhocko, boudewijn

On 7/5/26 20:50, Andrew Morton wrote:
> On Sun,  5 Jul 2026 18:07:09 +0000 Jiaqi Yan <jiaqiyan@google.com> wrote:
> 
>> At the end of dissolve_free_hugetlb_folio(), a free HugeTLB
>> folio becomes non-HugeTLB and is released to buddy allocator
>> as a high-order folio, e.g. a folio that contains 262144 pages
>> if the folio was a 1G HugeTLB hugepage.
>>
>> ...
>>
>> Introduce free_has_hwpoisoned() to only free the healthy pages
>> and exclude the HWPoison ones in the high-order folio.
>> free_has_hwpoisoned() happens at the end of free_pages_prepare(),
>> which already deals with both decomposing the original compound
>> page, updating page metadata like alloc tag and page owner.
>> It is also only applied when PG_has_hwpoisoned indicates folio
>> contains certain HWPoison page(s) for performance reason.
>> Its idea is to iterate through the sub-pages of the folio to
>> identify contiguous ranges of healthy pages. Instead of freeing
>> pages one by one, free_has_hwpoisoned() then re-use
>> free_prepared_contig_range() [11] to decompose healthy ranges into
>> the largest possible chunks of different orders. Every chunk is
>> freed via __free_frozen_pages().
> 
> Thanks.  I'll await further reviewer input before taking any action
> with this series.

FWIW, I want Vlastimil to ack this before this goes upstream.

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t
  2026-07-05 18:07 ` [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t Jiaqi Yan
@ 2026-07-17  7:17   ` Miaohe Lin
  2026-07-22  9:38   ` Vlastimil Babka (SUSE)
  2026-07-25  3:54   ` Matthew Wilcox
  2 siblings, 0 replies; 26+ messages in thread
From: Miaohe Lin @ 2026-07-17  7:17 UTC (permalink / raw)
  To: Jiaqi Yan
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn, ljs, ziy, vbabka

On 2026/7/6 2:07, Jiaqi Yan wrote:
> A future commit introducing free_has_hwpoison() to be called within
> __free_pages_prepare() will need to pass specific fpi_t flags when
> freeing a contiguous range of pages. Currently,
> free_prepared_contig_range() hardcodes the FPI_PREPARED flag and
> does not accept any caller-provided flags.
> 
> Rename the core logic to __free_prepared_contig_range() to accept
> an fpi_t argument. It bitwise ORs with the required FPI_PREPARED
> flag.
> 
> This is a preparatory commit with no functional changes.
> 
> Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
> ---
>  mm/page_alloc.c | 11 ++++++++---
>  1 file changed, 8 insertions(+), 3 deletions(-)
> 

Reviewed-by: Miaohe Lin <linmiaohe@huawei.com>

Thanks.
.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 2/5] mm/page_alloc: only free healthy pages in high-order has_hwpoisoned folio
  2026-07-05 18:07 ` [PATCH v6 2/5] mm/page_alloc: only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
@ 2026-07-17  7:19   ` Miaohe Lin
  2026-07-22  9:40   ` Vlastimil Babka (SUSE)
  1 sibling, 0 replies; 26+ messages in thread
From: Miaohe Lin @ 2026-07-17  7:19 UTC (permalink / raw)
  To: Jiaqi Yan
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn, ljs, ziy, vbabka

On 2026/7/6 2:07, Jiaqi Yan wrote:
> At the end of dissolve_free_hugetlb_folio(), a free HugeTLB folio
> becomes non-HugeTLB, and it is released to buddy allocator
> as a high-order folio, e.g. a folio that contains 262144 pages
> if the folio was a 1G HugeTLB hugepage.
> 
> This is problematic if the HugeTLB hugepage contained HWPoison
> subpages. In that case, since buddy allocator does not check
> HWPoison for non-zero-order folio, the raw HWPoison page can
> be given out with its buddy page and be re-used by either
> kernel or userspace.
> 
> Memory failure recovery (MFR) in kernel does attempt to take
> raw HWPoison page off buddy allocator after
> dissolve_free_hugetlb_folio(). However, there is always a time
> window between dissolve_free_hugetlb_folio() frees a HWPoison
> high-order folio to buddy allocator and MFR takes HWPoison
> raw page off buddy allocator.
> 
> Another similar situation is when a transparent huge page (THP)
> runs into memory failure but splitting failed. Such THP will
> eventually be released to buddy allocator when owning userspace
> processes are gone, but with certain subpages having HWPoison.
> 
> One obvious way to avoid both problems is to add page sanity
> checks in page allocate or free path. However, it is against
> the past efforts to reduce sanity check overhead [1,2,3].
> 
> Introduce free_has_hwpoisoned() to only free the healthy pages
> and to exclude the HWPoison ones in the high-order folio.
> The idea is to iterate through the sub-pages of the folio to
> identify contiguous ranges of healthy pages.
> 
> free_has_hwpoisoned() is added at the end of __free_pages_prepare()
> as a shortcut and only if PG_has_hwpoisoned indicates HWPoison page
> exists and after checks and preparations in __free_pages_prepare()
> all succeeded. It then use __free_prepared_contig_range() to
> decompose healthy range into the largest possible chunks of
> different orders, then freed via __free_frozen_pages().
> 
> free_has_hwpoisoned() has linear time complexity wrt the number
> of pages in the folio. While the power-of-two decomposition
> ensures that the number of calls to the buddy allocator is
> logarithmic for each contiguous healthy range, the mandatory
> linear scan of pages to identify PageHWPoison() defines the
> overall time complexity. For a 1G hugepage having 8 HWPoison
> pages, free_has_hwpoisoned() takes around 1ms on average on
> a system having 56 Intel Skylake physical cores. This is
> 15x to the case of freeing no HWPoison page. The cost is far
> from triggering soft lockup, and fair for handling exceptional
> hardware memory errors.
> 
> [1] https://lore.kernel.org/linux-mm/1460711275-1130-15-git-send-email-mgorman@techsingularity.net
> [2] https://lore.kernel.org/linux-mm/1460711275-1130-16-git-send-email-mgorman@techsingularity.net
> [3] https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
> 
> Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>

Reviewed-by: Miaohe Lin <linmiaohe@huawei.com>

Nits:

> ---
>  mm/page_alloc.c | 165 ++++++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 140 insertions(+), 25 deletions(-)
> 
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 7d27aff48b15..6418896c9df6 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -93,6 +93,13 @@ typedef int __bitwise fpi_t;
>  /* free_pages_prepare() has already been called for page(s) being freed. */
>  #define FPI_PREPARED		((__force fpi_t)BIT(3))
>  
> +/*
> + * Page(s) needs to go through free_pages_sanitize(), for example, because
> + * free_pages_prepare() cannot sanitize a high-order page block due to
> + * hardware error in some page(s).
> + */
> +#define FPI_SANITIZE		((__force fpi_t)BIT(4))
> +
>  /* prevent >1 _updater_ of zone percpu pageset ->high and ->batch fields */
>  static DEFINE_MUTEX(pcp_batch_high_lock);
>  #define MIN_PERCPU_PAGELIST_HIGH_FRACTION (8)
> @@ -210,6 +217,8 @@ gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK;
>  unsigned int pageblock_order __read_mostly;
>  #endif
>  
> +static void free_has_hwpoisoned(struct page *page, unsigned int order,
> +				fpi_t fpi_flags);
>  static void __free_pages_ok(struct page *page, unsigned int order,
>  			    fpi_t fpi_flags);
>  static void reserve_highatomic_pageblock(struct page *page, int order,
> @@ -1311,14 +1320,73 @@ static inline void pgalloc_tag_sub_pages(struct alloc_tag *tag, unsigned int nr)
>  
>  #endif /* CONFIG_MEM_ALLOC_PROFILING */
>  
> +/*
> + * Sanitize, which requires writing, a block of pages at the last moment of
> + * preparing to freeing them, i.e. __free_pages_prepare().

s/freeing/free/ ?

> + */
> +static void free_pages_sanitize(struct page *page, unsigned int order)
> +{
> +	bool init = want_init_on_free();
> +	/*
> +	 * __kasan_unpoison_pages() sets kasan tag on every tail page, so
> +	 * it is fine to use should_skip_kasan_poison() when pages here
> +	 * were a set of tail pages from a compound folio.
> +	 */
> +	bool skip_kasan_poison = should_skip_kasan_poison(page);
> +
> +	kernel_poison_pages(page, 1 << order);
> +
> +	/*
> +	 * As memory initialization might be integrated into KASAN,
> +	 * KASAN poisoning and memory initialization code must be
> +	 * kept together to avoid discrepancies in behavior.
> +	 *
> +	 * With hardware tag-based KASAN, memory tags must be set before the
> +	 * page becomes unavailable via debug_pagealloc or arch_free_page.
> +	 */
> +	if (!skip_kasan_poison) {
> +		kasan_poison_pages(page, order, init);
> +
> +		/* Memory is already initialized if KASAN did it internally. */
> +		if (kasan_has_integrated_init())
> +			init = false;
> +	}
> +	if (init)
> +		clear_highpages_kasan_tagged(page, 1 << order);
> +
> +	/*
> +	 * arch_free_page() can make the page's contents inaccessible.  s390
> +	 * does this.  So nothing which can access the page's contents should
> +	 * happen after this.
> +	 */
> +	arch_free_page(page, order);
> +
> +	debug_pagealloc_unmap_pages(page, 1 << order);
> +}
> +
> +/*
> + * Returns
> + * - true: checks and preparations all good, caller can proceed freeing.
> + * - false: do not proceed freeing for one of the following reasons:
> + *   1. Some check failed so it is not safe to proceed freeing.
> + *   2. A compound page has some HWPoison pages. The healthy pages
> + *      are already safely freed, and the HWPoison ones isolated.
> + */
>  static __always_inline bool __free_pages_prepare(struct page *page,
>  		unsigned int order, fpi_t fpi_flags)
>  {
>  	int bad = 0;
> -	bool skip_kasan_poison = should_skip_kasan_poison(page);
> -	bool init = want_init_on_free();
>  	bool compound = PageCompound(page);
>  	struct folio *folio = page_folio(page);
> +	/*
> +	 * When dealing with compound page, PG_has_hwpoisoned is cleared
> +	 * with PAGE_FLAGS_SECOND. So the check must be done first.
> +	 *
> +	 * Note we can't exclude PG_has_hwpoisoned from PAGE_FLAGS_SECOND.
> +	 * Because PG_has_hwpoisoned == PG_active, free_page_is_bad() will
> +	 * confuse and complaint that the first tail page is still active.
> +	 */
> +	bool should_fhh = compound && folio_test_has_hwpoisoned(folio);
>  
>  	if (fpi_flags & FPI_PREPARED)
>  		return true;
> @@ -1416,34 +1484,19 @@ static __always_inline bool __free_pages_prepare(struct page *page,
>  					   PAGE_SIZE << order);
>  	}
>  
> -	kernel_poison_pages(page, 1 << order);
> -
>  	/*
> -	 * As memory initialization might be integrated into KASAN,
> -	 * KASAN poisoning and memory initialization code must be
> -	 * kept together to avoid discrepancies in behavior.
> +	 * After breaking down compound page and dealing with page metadata
> +	 * (e.g. page owner and page alloc tags), take a shortcut if this
> +	 * was a compound page containing certain HWPoison subpages.
>  	 *
> -	 * With hardware tag-based KASAN, memory tags must be set before the
> -	 * page becomes unavailable via debug_pagealloc or arch_free_page.
> +	 * FPI_SANITIZE to remember free_pages_sanitize() healthy pages.
>  	 */
> -	if (!skip_kasan_poison) {
> -		kasan_poison_pages(page, order, init);
> -
> -		/* Memory is already initialized if KASAN did it internally. */
> -		if (kasan_has_integrated_init())
> -			init = false;
> +	if (should_fhh) {
> +		free_has_hwpoisoned(page, order, fpi_flags | FPI_SANITIZE);
> +		return false;
>  	}
> -	if (init)
> -		clear_highpages_kasan_tagged(page, 1 << order);
> -
> -	/*
> -	 * arch_free_page() can make the page's contents inaccessible.  s390
> -	 * does this.  So nothing which can access the page's contents should
> -	 * happen after this.
> -	 */
> -	arch_free_page(page, order);
>  
> -	debug_pagealloc_unmap_pages(page, 1 << order);
> +	free_pages_sanitize(page, order);
>  
>  	return true;
>  }
> @@ -6869,7 +6922,14 @@ static void __free_prepared_contig_range(struct page *page,
>  		/*
>  		 * Free the chunk as a single block. Our caller has already
>  		 * called free_pages_prepare() for each order-0 page.
> +		 *
> +		 * If the original compound page has HWPoison page,
> +		 * free_pages_prepare() has to skip sanitize at that time,
> +		 * but now it is good time to do that.
>  		 */
> +		if (fpi_flags & FPI_SANITIZE)
> +			free_pages_sanitize(page, order);
> +
>  		__free_frozen_pages(page, order, fpi_flags | FPI_PREPARED);
>  
>  		pfn += 1UL << order;
> @@ -6956,6 +7016,61 @@ void __free_contig_range(unsigned long pfn, unsigned long nr_pages)
>  	__free_contig_range_common(pfn, nr_pages, /* is_frozen= */ false);
>  }
>  
> +/*
> + * Given some contiguous pages that have certain number of HWPoison page(s),
> + * free only the healthy ones.
> + *
> + * Used at the end of __free_pages_prepare(). Even if having HWPoison pages,
> + * breaking down compound page and clearing metadata (e.g. page owner, alloc
> + * tag) can be done together during __free_pages_prepare(), which simplifies
> + * the splitting here: unlike __split_unmapped_folio(), there is no need to
> + * turn split pages into a compound page or to carry metadata.
> + *
> + * It scans every raw page of the compound page and causes nontrivial overhead.
> + * So only use this when the compound page contains HWPoison page(s).
> + *
> + * It also works when order == 0, regardless of PageHWPoison() or not.

It works but won't be called when order == 0?

Thanks.
.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
       [not found]   ` <ee6bcc74-0846-43f2-bcca-b1bedff74351@oracle.com>
@ 2026-07-22  8:27     ` Vlastimil Babka (SUSE)
  2026-07-22  9:03       ` Vlastimil Babka (SUSE)
                         ` (2 more replies)
  0 siblings, 3 replies; 26+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-07-22  8:27 UTC (permalink / raw)
  To: William Roche, David Hildenbrand (Arm), Jiaqi Yan, linmiaohe, ljs,
	ziy
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On 7/17/26 15:06, William Roche wrote:
> On 7/17/26 12:18, David Hildenbrand (Arm) wrote:
>> On 7/5/26 20:07, Jiaqi Yan wrote:
>>> At the end of dissolve_free_hugetlb_folio(), a free HugeTLB
>>> folio becomes non-HugeTLB and is released to buddy allocator
>>> as a high-order folio, e.g. a folio that contains 262144 pages
>>> if the folio was a 1G HugeTLB hugepage.
>>>
>>> This is problematic if the HugeTLB hugepage contained HWPoison
>>> subpages. In that case, since buddy allocator does not check
>>> HWPoison for non-zero-order folio, the raw HWPoison page can
>>> be given out with its buddy page and be re-used by either
>>> kernel or userspace.
>> 
>> I still don't like the complexity of this, in particular, as we have different
>> mechanisms in the page allocator already to try handling this,
>> 
>> We also do have cases where we set the hwpoison bit, while a page is just about
>> to get allocated from the buddy. So before we take it off the buddy, we might
>> just hand out the page.
>> 
>> check_new_pages() seems to check for PageHWPoison() and make us not hand out
>> such pages. It's guarded by "check_pages" but it seems to do exactly what we are
>> looking for, now?
> 
> 
> Just adding a comment about this aspect:
> The check_new_pages() mechanism used by the __rmqueue functions should
> filter these pages out, but this has been disabled by default in 2023
> with:
> [PATCH] mm, page_alloc: reduce page alloc/free sanity checks
> https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
> 
> So it would need to be enabled back, taking some of the performance hit.
> (and I personally think that it has to be done)

Would it truly fix the issue, or rather there would still be a race window
left where we check that there's no hwpoison flag in the re-enabled check,
and only then someone sets it?

Also, can the hardware actually detect a problem with a page that nobody
accesses? I guess if yes, it's only in some corner cases.

So I'm wary about penalizing the allocator paths again. If the page is in
the buddy allocator, shouldn't it be isolated away as part of setting the
hwpoison? I thought we already did that? So assuming we don't just leave
hwpoison pages in the buddy and this is only about some small race window
where it's being taken away from the buddy? Then the extra check would only
make a small window smaller, but is it worth it?

>    A note about the related project:
> This patch is an addition to the "mm: memfd/hugetlb: introduce 
> memfd-based userspace MFR policy"
> project -- recycling the impacted hugetlb pages.
> 
> I do think that "memfd-based userspace MFR policy" is a valuable
> enhancement, and if the impacted large page can be more easily recycled
> enabling check_new_pages() it's even better !
> 
> HTH.



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-22  8:27     ` Vlastimil Babka (SUSE)
@ 2026-07-22  9:03       ` Vlastimil Babka (SUSE)
  2026-07-25  6:00         ` Jiaqi Yan
  2026-07-25  6:52       ` Jiaqi Yan
  2026-07-27 14:20       ` David Hildenbrand (Arm)
  2 siblings, 1 reply; 26+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-07-22  9:03 UTC (permalink / raw)
  To: William Roche, David Hildenbrand (Arm), Jiaqi Yan, linmiaohe, ljs,
	ziy
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On 7/22/26 10:27, Vlastimil Babka (SUSE) wrote:
> On 7/17/26 15:06, William Roche wrote:
>> On 7/17/26 12:18, David Hildenbrand (Arm) wrote:
>>> On 7/5/26 20:07, Jiaqi Yan wrote:
>>>> At the end of dissolve_free_hugetlb_folio(), a free HugeTLB
>>>> folio becomes non-HugeTLB and is released to buddy allocator
>>>> as a high-order folio, e.g. a folio that contains 262144 pages
>>>> if the folio was a 1G HugeTLB hugepage.
>>>>
>>>> This is problematic if the HugeTLB hugepage contained HWPoison
>>>> subpages. In that case, since buddy allocator does not check
>>>> HWPoison for non-zero-order folio, the raw HWPoison page can
>>>> be given out with its buddy page and be re-used by either
>>>> kernel or userspace.
>>> 
>>> I still don't like the complexity of this, in particular, as we have different
>>> mechanisms in the page allocator already to try handling this,
>>> 
>>> We also do have cases where we set the hwpoison bit, while a page is just about
>>> to get allocated from the buddy. So before we take it off the buddy, we might
>>> just hand out the page.
>>> 
>>> check_new_pages() seems to check for PageHWPoison() and make us not hand out
>>> such pages. It's guarded by "check_pages" but it seems to do exactly what we are
>>> looking for, now?
>> 
>> 
>> Just adding a comment about this aspect:
>> The check_new_pages() mechanism used by the __rmqueue functions should
>> filter these pages out, but this has been disabled by default in 2023
>> with:
>> [PATCH] mm, page_alloc: reduce page alloc/free sanity checks
>> https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
>> 
>> So it would need to be enabled back, taking some of the performance hit.
>> (and I personally think that it has to be done)
> 
> Would it truly fix the issue, or rather there would still be a race window
> left where we check that there's no hwpoison flag in the re-enabled check,
> and only then someone sets it?
> 
> Also, can the hardware actually detect a problem with a page that nobody
> accesses? I guess if yes, it's only in some corner cases.
> 
> So I'm wary about penalizing the allocator paths again. If the page is in
> the buddy allocator, shouldn't it be isolated away as part of setting the
> hwpoison? I thought we already did that?

Looking at the code, seems soft_offline_page() tries to take a refcount
first and then sets the hwpoison flag, so it should be completely race-free
wrt the page allocator.

memory_failure() seems to start with TestSetPageHWPoison() and only then
tries to get the refcount. Wonder if it should be that way.

> So assuming we don't just leave
> hwpoison pages in the buddy and this is only about some small race window
> where it's being taken away from the buddy? Then the extra check would only
> make a small window smaller, but is it worth it?
> 
>>    A note about the related project:
>> This patch is an addition to the "mm: memfd/hugetlb: introduce 
>> memfd-based userspace MFR policy"
>> project -- recycling the impacted hugetlb pages.
>> 
>> I do think that "memfd-based userspace MFR policy" is a valuable
>> enhancement, and if the impacted large page can be more easily recycled
>> enabling check_new_pages() it's even better !
>> 
>> HTH.
> 



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t
  2026-07-05 18:07 ` [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t Jiaqi Yan
  2026-07-17  7:17   ` Miaohe Lin
@ 2026-07-22  9:38   ` Vlastimil Babka (SUSE)
  2026-07-25  3:54   ` Matthew Wilcox
  2 siblings, 0 replies; 26+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-07-22  9:38 UTC (permalink / raw)
  To: Jiaqi Yan, linmiaohe, ljs, ziy
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On 7/5/26 20:07, Jiaqi Yan wrote:
> A future commit introducing free_has_hwpoison() to be called within
> __free_pages_prepare() will need to pass specific fpi_t flags when
> freeing a contiguous range of pages. Currently,
> free_prepared_contig_range() hardcodes the FPI_PREPARED flag and
> does not accept any caller-provided flags.
> 
> Rename the core logic to __free_prepared_contig_range() to accept
> an fpi_t argument. It bitwise ORs with the required FPI_PREPARED
> flag.
> 
> This is a preparatory commit with no functional changes.
> 
> Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>

Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 2/5] mm/page_alloc: only free healthy pages in high-order has_hwpoisoned folio
  2026-07-05 18:07 ` [PATCH v6 2/5] mm/page_alloc: only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
  2026-07-17  7:19   ` Miaohe Lin
@ 2026-07-22  9:40   ` Vlastimil Babka (SUSE)
  1 sibling, 0 replies; 26+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-07-22  9:40 UTC (permalink / raw)
  To: Jiaqi Yan, linmiaohe, ljs, ziy
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On 7/5/26 20:07, Jiaqi Yan wrote:
> At the end of dissolve_free_hugetlb_folio(), a free HugeTLB folio
> becomes non-HugeTLB, and it is released to buddy allocator
> as a high-order folio, e.g. a folio that contains 262144 pages
> if the folio was a 1G HugeTLB hugepage.
> 
> This is problematic if the HugeTLB hugepage contained HWPoison
> subpages. In that case, since buddy allocator does not check
> HWPoison for non-zero-order folio, the raw HWPoison page can
> be given out with its buddy page and be re-used by either
> kernel or userspace.
> 
> Memory failure recovery (MFR) in kernel does attempt to take
> raw HWPoison page off buddy allocator after
> dissolve_free_hugetlb_folio(). However, there is always a time
> window between dissolve_free_hugetlb_folio() frees a HWPoison
> high-order folio to buddy allocator and MFR takes HWPoison
> raw page off buddy allocator.
> 
> Another similar situation is when a transparent huge page (THP)
> runs into memory failure but splitting failed. Such THP will
> eventually be released to buddy allocator when owning userspace
> processes are gone, but with certain subpages having HWPoison.
> 
> One obvious way to avoid both problems is to add page sanity
> checks in page allocate or free path. However, it is against
> the past efforts to reduce sanity check overhead [1,2,3].
> 
> Introduce free_has_hwpoisoned() to only free the healthy pages
> and to exclude the HWPoison ones in the high-order folio.
> The idea is to iterate through the sub-pages of the folio to
> identify contiguous ranges of healthy pages.
> 
> free_has_hwpoisoned() is added at the end of __free_pages_prepare()
> as a shortcut and only if PG_has_hwpoisoned indicates HWPoison page
> exists and after checks and preparations in __free_pages_prepare()
> all succeeded. It then use __free_prepared_contig_range() to
> decompose healthy range into the largest possible chunks of
> different orders, then freed via __free_frozen_pages().
> 
> free_has_hwpoisoned() has linear time complexity wrt the number
> of pages in the folio. While the power-of-two decomposition
> ensures that the number of calls to the buddy allocator is
> logarithmic for each contiguous healthy range, the mandatory
> linear scan of pages to identify PageHWPoison() defines the
> overall time complexity. For a 1G hugepage having 8 HWPoison
> pages, free_has_hwpoisoned() takes around 1ms on average on
> a system having 56 Intel Skylake physical cores. This is
> 15x to the case of freeing no HWPoison page. The cost is far
> from triggering soft lockup, and fair for handling exceptional
> hardware memory errors.
> 
> [1] https://lore.kernel.org/linux-mm/1460711275-1130-15-git-send-email-mgorman@techsingularity.net
> [2] https://lore.kernel.org/linux-mm/1460711275-1130-16-git-send-email-mgorman@techsingularity.net
> [3] https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
> 
> Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>

Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>

One thing below:

> @@ -6956,6 +7016,61 @@ void __free_contig_range(unsigned long pfn, unsigned long nr_pages)
>  	__free_contig_range_common(pfn, nr_pages, /* is_frozen= */ false);
>  }
>  
> +/*
> + * Given some contiguous pages that have certain number of HWPoison page(s),
> + * free only the healthy ones.
> + *
> + * Used at the end of __free_pages_prepare(). Even if having HWPoison pages,
> + * breaking down compound page and clearing metadata (e.g. page owner, alloc
> + * tag) can be done together during __free_pages_prepare(), which simplifies
> + * the splitting here: unlike __split_unmapped_folio(), there is no need to
> + * turn split pages into a compound page or to carry metadata.
> + *
> + * It scans every raw page of the compound page and causes nontrivial overhead.
> + * So only use this when the compound page contains HWPoison page(s).
> + *
> + * It also works when order == 0, regardless of PageHWPoison() or not.
> + *
> + * This implementation needs rework in memdesc world.
> + */
> +static void free_has_hwpoisoned(struct page *page, unsigned int order,
> +				fpi_t fpi_flags)
> +{
> +	unsigned long curr = page_to_pfn(page);
> +	unsigned long end_pfn = curr + (1 << order);
> +	unsigned long next;
> +	unsigned long total_freed = 0;
> +	unsigned long total_hwp = 0;
> +
> +	while (curr < end_pfn) {
> +		next = curr;
> +
> +		while (next < end_pfn && !PageHWPoison(pfn_to_page(next)))
> +			++next;
> +
> +		if (next != end_pfn) {
> +			/*
> +			 * Avoid accounting error when the page is freed
> +			 * by unpoison_memory().
> +			 */
> +			clear_page_tag_ref(pfn_to_page(next));
> +			++total_hwp;
> +		}
> +
> +		__free_prepared_contig_range(pfn_to_page(curr), next - curr,
> +					     fpi_flags);
> +		total_freed += next - curr;
> +
> +		if (next == end_pfn)
> +			break;
> +
> +		curr = next + 1;
> +	}
> +
> +	pr_info("Freed %#lx pages, excluded %#lx HWPoison pages\n",
> +		total_freed, total_hwp);

Should we really print this? Maybe just pr_debug() or not at all?

> +}
> +
>  #ifdef CONFIG_CONTIG_ALLOC
>  /* Usage: See admin-guide/dynamic-debug-howto.rst */
>  static void alloc_contig_dump_pages(struct list_head *page_list)



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 5/5] selftests/mm: add hard memory failure anonymous HugeTLB test
  2026-07-05 18:07 ` [PATCH v6 5/5] selftests/mm: add hard memory failure anonymous HugeTLB test Jiaqi Yan
@ 2026-07-25  3:05   ` Jiaqi Yan
  0 siblings, 0 replies; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-25  3:05 UTC (permalink / raw)
  To: linmiaohe, ljs, ziy, vbabka
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn

On Sun, Jul 5, 2026 at 11:07 AM Jiaqi Yan <jiaqiyan@google.com> wrote:
>
> Add a new testcase to validate memory failure recovery for HWPoison
> anonymous HugeTLB page, including proper SIGBUS delivery, releasing
> a HugeTLB page containing one HWPoison page to buddy allocator, and
> isolation of the raw HWPoison page.
>
> The test uses HugeTLB's default hugesize.
>
> Although can be added in future, this patch does not support testing
> the MADV_SOFT variant.
>
> Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
> ---
>  tools/testing/selftests/mm/memory-failure.c | 70 ++++++++++++++++++++-
>  1 file changed, 67 insertions(+), 3 deletions(-)
>
> diff --git a/tools/testing/selftests/mm/memory-failure.c b/tools/testing/selftests/mm/memory-failure.c
> index 032ed952057c..78c105ce2af1 100644
> --- a/tools/testing/selftests/mm/memory-failure.c
> +++ b/tools/testing/selftests/mm/memory-failure.c
> @@ -18,6 +18,7 @@
>  #include <linux/magic.h>
>  #include <errno.h>
>
> +#include "hugepage_settings.h"
>  #include "vm_util.h"
>
>  enum inject_type {
> @@ -27,6 +28,7 @@ enum inject_type {
>
>  enum result_type {
>         MADV_HARD_ANON,
> +       MADV_HARD_ANON_HUGETLB,
>         MADV_HARD_CLEAN_PAGECACHE,
>         MADV_HARD_DIRTY_PAGECACHE,
>         MADV_SOFT_ANON,
> @@ -47,6 +49,8 @@ FIXTURE(memory_failure)
>         int pagemap_fd;
>         int kpageflags_fd;
>         bool triggered;
> +       /* Number of initial HugeTLB pages with default page size. */
> +       unsigned long nr_hugetlb_pages;
>  };
>
>  FIXTURE_VARIANT(memory_failure)
> @@ -157,6 +161,7 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
>                   void *vaddr, enum result_type type, int setjmp)
>  {
>         unsigned long size;
> +       unsigned long nr_hugetlb_pages;
>         uint64_t pfn_flags;
>
>         switch (type) {
> @@ -174,6 +179,7 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
>                 ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn);
>                 break;
>         case MADV_HARD_ANON:
> +       case MADV_HARD_ANON_HUGETLB:
>         case MADV_HARD_DIRTY_PAGECACHE:
>                 /* The SIGBUS signal should have been received. */
>                 ASSERT_EQ(setjmp, 1);
> @@ -184,8 +190,15 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
>                 ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size);
>                 ASSERT_EQ(siginfo.si_addr, vaddr);
>
> -               /* XXX Check backing pte is hwpoison entry when supported. */
> -               ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
> +               if (type != MADV_HARD_ANON_HUGETLB)
> +                       /*
> +                        * Check backing pte is hwpoison entry when supported.
> +                        * Although try_to_unmap_one() also installs hwpoison
> +                        * entry for HugeTLB, pagemap_hugetlb_range() doesn't
> +                        * parse swap entries at all.
> +                        */
> +                       ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
> +
>                 break;
>         default:
>                 SKIP(return, "unexpected inject type %d.\n", type);
> @@ -193,7 +206,19 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
>
>         /* Check if the value of HardwareCorrupted has increased. */
>         ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
> -       ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
> +
> +       if (type == MADV_HARD_ANON_HUGETLB) {
> +               /*
> +                * Only one page is hardware corrupted; the rest should all be
> +                * released to buddy allocator.
> +                */
> +               ASSERT_EQ(size, self->corrupted_size + getpagesize() / 1024);
> +               /* HugeTLB should have lost the HWPoison HugeTLB page. */
> +               nr_hugetlb_pages = hugetlb_nr_default_pages();
> +               ASSERT_EQ(nr_hugetlb_pages + 1, self->nr_hugetlb_pages);
> +       } else {
> +               ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
> +       }
>
>         /* Check if HWPoison flag is set. */
>         ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
> @@ -247,6 +272,45 @@ TEST_F(memory_failure, anon)
>         ASSERT_EQ(munmap(addr, self->page_size), 0);
>  }
>
> +TEST_F(memory_failure, anon_hugetlb)
> +{
> +       char *addr;
> +       int ret;
> +       const unsigned long nr_alloc_hugetlb_pages = 4;
> +       unsigned long alloc_size;
> +
> +       if (variant->type == MADV_SOFT)
> +               SKIP(return, "Soft offline test is not implemented");
> +
> +       /* HugeTLB settings will be automatically restored when test exits. */
> +       hugetlb_setup_default(nr_alloc_hugetlb_pages);

From Sashiko [1]

> Will the sysfs hugepage settings actually be restored when the test exits?

> The anon_hugetlb test calls hugetlb_setup_default() which registers the
> hugepage_restore_settings_atexit handler via atexit().

> However, this test is implemented using the kselftest_harness.h TEST_F()
> macro, which forks a child process to run the test case. When the test
> completes, the child process terminates using _exit() or abort(), both of
> which bypass atexit() handlers.

>  As a result, the cleanup handler is never executed, permanently leaking the
> modified nr_hugepages sysfs setting on the host machine.

This is true, and I double checked with this test.

> Could this cleanup be moved to a FIXTURE_TEARDOWN() block so the kselftest
> harness guarantees it will execute?

However, this suggestion doesn't fix the problem. The harness runs
FIXTURE_TEARDOWN() inside the forked child process too, so it will be completely
bypassed in any of the following cases:
- An assertion fails
- The test crashes with a segmentation fault
- The test process is killed by a signal

I believe the correct fix is to use HUGETLB_SETUP_DEFAULT_PAGES() like
[2] did, and I have validated:

$ echo 16 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
$ ./memory-failure -r memory_failure.madv_hard.anon_hugetlb
# [INFO] detected hugetlb page size: 2048 KiB
# [INFO] detected hugetlb page size: 32768 KiB
# [INFO] detected hugetlb page size: 64 KiB
# [INFO] detected hugetlb page size: 1048576 KiB
TAP version 13
1..1
# Starting 1 tests from 1 test cases.
#  RUN           memory_failure.madv_hard.anon_hugetlb ...
#            OK  memory_failure.madv_hard.anon_hugetlb
ok 1 memory_failure.madv_hard.anon_hugetlb
# PASSED: 1 / 1 tests passed.
# Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0
$ cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
16

[1] https://sashiko.dev/#/patchset/20260705180714.3708947-1-jiaqiyan@google.com
[2] https://patchew.org/linux/20260511162840.375890-1-rppt@kernel.org/20260511162840.375890-46-rppt@kernel.org/

> +
> +       alloc_size = default_huge_page_size() * nr_alloc_hugetlb_pages;
> +       self->page_size = default_huge_page_size();
> +       self->nr_hugetlb_pages = hugetlb_nr_default_pages();
> +
> +       addr = mmap(0, alloc_size, PROT_READ | PROT_WRITE,
> +                   MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1, 0);
> +       if (addr == MAP_FAILED)
> +               SKIP(return, "mmap failed, not enough memory or hugetlb not supported.\n");
> +       memset(addr, 0xce, alloc_size);
> +
> +       prepare(_metadata, self, addr);
> +
> +       ret = sigsetjmp(signal_jmp_buf, 1);
> +       if (!self->triggered) {
> +               self->triggered = true;
> +               ASSERT_EQ(variant->inject(self, addr), 0);
> +               FORCE_READ(*addr);
> +       }
> +
> +       check(_metadata, self, addr, MADV_HARD_ANON_HUGETLB, ret);
> +
> +       cleanup(_metadata, self, addr);
> +
> +       ASSERT_EQ(munmap(addr, alloc_size), 0);
> +}
> +
>  static int prepare_file(const char *fname, unsigned long size)
>  {
>         int fd;
> --
> 2.55.0.rc0.799.gd6f94ed593-goog
>


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 3/5] mm/memory-failure: set has_hwpoisoned flags on dissolved HugeTLB folio
  2026-07-05 18:07 ` [PATCH v6 3/5] mm/memory-failure: set has_hwpoisoned flags on dissolved HugeTLB folio Jiaqi Yan
@ 2026-07-25  3:05   ` Jiaqi Yan
  0 siblings, 0 replies; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-25  3:05 UTC (permalink / raw)
  To: linmiaohe, ljs, ziy, vbabka
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, david, william.roche, tony.luck, wangkefeng.wang,
	jane.chu, akpm, muchun.song, liam, rientjes, duenwen, jthoughton,
	linux-mm, linux-kernel, vbabka, rppt, shuah, surenb, mhocko,
	boudewijn

On Sun, Jul 5, 2026 at 11:07 AM Jiaqi Yan <jiaqiyan@google.com> wrote:
>
> When a free HWPoison HugeTLB folio is dissolved, it becomes
> non-HugeTLB and is released to buddy allocator as a high-order
> folio.
>
> Set has_hwpoisoned flags on the high-order folio so that buddy
> allocator can tell that it contains certain HWPoison page(s),
> and can handle it specially with free_has_hwpoisoned().
>
> Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
> Acked-by: Miaohe Lin <linmiaohe@huawei.com>
> ---
>  include/linux/page-flags.h | 2 +-
>  mm/memory-failure.c        | 1 +
>  2 files changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
> index 7a863572adce..3cd524e04a30 100644
> --- a/include/linux/page-flags.h
> +++ b/include/linux/page-flags.h
> @@ -879,7 +879,7 @@ FOLIO_FLAG_FALSE(partially_mapped)
>
>  #define PG_head_mask ((1UL << PG_head))
>
> -#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_TRANSPARENT_HUGEPAGE)
> +#if defined(CONFIG_MEMORY_FAILURE) && (defined(CONFIG_TRANSPARENT_HUGEPAGE) || defined(CONFIG_HUGETLB_PAGE))
>  /*
>   * PageHasHWPoisoned indicates that at least one subpage is hwpoisoned in the
>   * compound page.
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index 4916ab145325..3d15b4c1b694 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -2008,6 +2008,7 @@ void folio_clear_hugetlb_hwpoison(struct folio *folio)
>         if (folio_test_hugetlb_vmemmap_optimized(folio))
>                 return;
>         folio_clear_hwpoison(folio);
> +       folio_set_has_hwpoisoned(folio);

From Sashiko [1]:

> Does setting this flag and relying on free_has_hwpoisoned() to skip the
> poisoned subpages introduce a permanent memory leak if unpoison_memory() is
> later called?

> Since the subpages are isolated without using take_page_off_buddy(), they
> are left with a refcount of 0 and without the MAGIC_HWPOISON
> (PageHWPoisonTakenOff) tag.

> If an administrator later runs unpoison_memory() on these pages,
> __get_unpoison_page() checks PageHWPoisonTakenOff() (which is false) and
> evaluates get_page_unless_zero() (which returns 0 because refcount is 0).
> unpoison_memory() observes ghp == 0:
> unpoison_memory() {
>    ...
>    ghp = get_hwpoison_page(p, MF_UNPOISON);
>    if (!ghp) {
>    ...
>    ret = folio_test_clear_hwpoison(folio) ? 0 : -EBUSY;
>    } else if (ghp < 0) {
>    ...
> }

> Will this cause unpoison_memory() to clear the poison flag and return success
> without ever calling put_page_back_buddy(), permanently leaking the memory?

I believe so, the software-poisoned page will leak.

To prevent that, free_has_hwpoisoned() can just do
SetPageHWPoisonTakenOff() for each page it found HWPoison. That should
be enough in my test, and no need to do take_page_off_buddy().
me_huge_page() will page_ref_inc(). Later during unpoison, ghp will
become -EHWPOISON and page's refcount will be 1 when it is invoked
with put_page_back_buddy().

[1] https://sashiko.dev/#/patchset/20260705180714.3708947-1-jiaqiyan@google.com

>         folio_free_raw_hwp(folio, true);
>  }
>
> --
> 2.55.0.rc0.799.gd6f94ed593-goog
>


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-05 18:50 ` [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Andrew Morton
  2026-07-06  9:03   ` David Hildenbrand (Arm)
@ 2026-07-25  3:06   ` Jiaqi Yan
  1 sibling, 0 replies; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-25  3:06 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linmiaohe, ljs, ziy, vbabka, osalvador, harry.yoo, willy,
	osalvador, jackmanb, hannes, nao.horiguchi, david, william.roche,
	tony.luck, wangkefeng.wang, jane.chu, muchun.song, liam, rientjes,
	duenwen, jthoughton, linux-mm, linux-kernel, vbabka, rppt, shuah,
	surenb, mhocko, boudewijn

On Sun, Jul 5, 2026 at 11:50 AM Andrew Morton <akpm@linux-foundation.org> wrote:
>
> On Sun,  5 Jul 2026 18:07:09 +0000 Jiaqi Yan <jiaqiyan@google.com> wrote:
>
> > At the end of dissolve_free_hugetlb_folio(), a free HugeTLB
> > folio becomes non-HugeTLB and is released to buddy allocator
> > as a high-order folio, e.g. a folio that contains 262144 pages
> > if the folio was a 1G HugeTLB hugepage.
> >
> > ...
> >
> > Introduce free_has_hwpoisoned() to only free the healthy pages
> > and exclude the HWPoison ones in the high-order folio.
> > free_has_hwpoisoned() happens at the end of free_pages_prepare(),
> > which already deals with both decomposing the original compound
> > page, updating page metadata like alloc tag and page owner.
> > It is also only applied when PG_has_hwpoisoned indicates folio
> > contains certain HWPoison page(s) for performance reason.
> > Its idea is to iterate through the sub-pages of the folio to
> > identify contiguous ranges of healthy pages. Instead of freeing
> > pages one by one, free_has_hwpoisoned() then re-use
> > free_prepared_contig_range() [11] to decompose healthy ranges into
> > the largest possible chunks of different orders. Every chunk is
> > freed via __free_frozen_pages().
>
> Thanks.  I'll await further reviewer input before taking any action
> with this series.
>
> AI review flags several possible issues, some of them pre-existing:
>
>         https://sashiko.dev/#/patchset/20260705180714.3708947-1-jiaqiyan@google.com
>
>

Thanks for the reminder, Andrew! Two of the issues reported by Sashiko
actually are relevant and real, and I just replied to the mailing list
with fixes.

I will soon reply to the reviews/comments from human.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t
  2026-07-05 18:07 ` [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t Jiaqi Yan
  2026-07-17  7:17   ` Miaohe Lin
  2026-07-22  9:38   ` Vlastimil Babka (SUSE)
@ 2026-07-25  3:54   ` Matthew Wilcox
  2 siblings, 0 replies; 26+ messages in thread
From: Matthew Wilcox @ 2026-07-25  3:54 UTC (permalink / raw)
  To: Jiaqi Yan
  Cc: linmiaohe, ljs, ziy, vbabka, osalvador, harry.yoo, osalvador,
	jackmanb, hannes, nao.horiguchi, david, william.roche, tony.luck,
	wangkefeng.wang, jane.chu, akpm, muchun.song, liam, rientjes,
	duenwen, jthoughton, linux-mm, linux-kernel, vbabka, rppt, shuah,
	surenb, mhocko, boudewijn

On Sun, Jul 05, 2026 at 06:07:10PM +0000, Jiaqi Yan wrote:
> Rename the core logic to __free_prepared_contig_range() to accept
> an fpi_t argument. It bitwise ORs with the required FPI_PREPARED
> flag.

There are only three callers of free_prepared_contig_range(), all
in the same function.  It would seem like less effort to just
modify free_prepared_contig_range() to take an fpi_t than
introduce this wrapper.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-22  9:03       ` Vlastimil Babka (SUSE)
@ 2026-07-25  6:00         ` Jiaqi Yan
  0 siblings, 0 replies; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-25  6:00 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE)
  Cc: William Roche, David Hildenbrand (Arm), linmiaohe, ljs, ziy,
	osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On Wed, Jul 22, 2026 at 2:03 AM Vlastimil Babka (SUSE)
<vbabka@kernel.org> wrote:
>
> On 7/22/26 10:27, Vlastimil Babka (SUSE) wrote:
> > On 7/17/26 15:06, William Roche wrote:
> >> On 7/17/26 12:18, David Hildenbrand (Arm) wrote:
> >>> On 7/5/26 20:07, Jiaqi Yan wrote:
> >>>> At the end of dissolve_free_hugetlb_folio(), a free HugeTLB
> >>>> folio becomes non-HugeTLB and is released to buddy allocator
> >>>> as a high-order folio, e.g. a folio that contains 262144 pages
> >>>> if the folio was a 1G HugeTLB hugepage.
> >>>>
> >>>> This is problematic if the HugeTLB hugepage contained HWPoison
> >>>> subpages. In that case, since buddy allocator does not check
> >>>> HWPoison for non-zero-order folio, the raw HWPoison page can
> >>>> be given out with its buddy page and be re-used by either
> >>>> kernel or userspace.
> >>>
> >>> I still don't like the complexity of this, in particular, as we have different
> >>> mechanisms in the page allocator already to try handling this,
> >>>
> >>> We also do have cases where we set the hwpoison bit, while a page is just about
> >>> to get allocated from the buddy. So before we take it off the buddy, we might
> >>> just hand out the page.
> >>>
> >>> check_new_pages() seems to check for PageHWPoison() and make us not hand out
> >>> such pages. It's guarded by "check_pages" but it seems to do exactly what we are
> >>> looking for, now?
> >>
> >>
> >> Just adding a comment about this aspect:
> >> The check_new_pages() mechanism used by the __rmqueue functions should
> >> filter these pages out, but this has been disabled by default in 2023
> >> with:
> >> [PATCH] mm, page_alloc: reduce page alloc/free sanity checks
> >> https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
> >>
> >> So it would need to be enabled back, taking some of the performance hit.
> >> (and I personally think that it has to be done)
> >
> > Would it truly fix the issue, or rather there would still be a race window
> > left where we check that there's no hwpoison flag in the re-enabled check,
> > and only then someone sets it?
> >
> > Also, can the hardware actually detect a problem with a page that nobody
> > accesses? I guess if yes, it's only in some corner cases.
> >
> > So I'm wary about penalizing the allocator paths again. If the page is in
> > the buddy allocator, shouldn't it be isolated away as part of setting the
> > hwpoison? I thought we already did that?
>
> Looking at the code, seems soft_offline_page() tries to take a refcount
> first and then sets the hwpoison flag, so it should be completely race-free
> wrt the page allocator.
>
> memory_failure() seems to start with TestSetPageHWPoison() and only then
> tries to get the refcount. Wonder if it should be that way.

Miaohe may correct me, but I think this diff between
soft_offline_page() and memory_failure() is intended.

soft_offline_page() is handling a still valid but soon may become
corrupted page. Its main goal is to gracefully migrate the still
perfectly valid data to a new page. We can only set the HWPoison bit
after (taking a refcount and) finishing migration.

memory_failure() is handling already hw corrupted data in the page, so
the top priority is to stop anyone from accessing the page, hence
TestSetPageHWPoison() should take precedence. Both get_hwpoison_page()
and get_huge_page_for_hwpoison() could be heavy and slow; generally we
can't tolerant the window between getting the refcount and marking
page toxic. In the worst case if someone re-accessed the corrupted
page, the 2nd Machine Check Exception would be fatal (if nested) and
bring down the system.

>
> > So assuming we don't just leave
> > hwpoison pages in the buddy and this is only about some small race window
> > where it's being taken away from the buddy? Then the extra check would only
> > make a small window smaller, but is it worth it?
> >
> >>    A note about the related project:
> >> This patch is an addition to the "mm: memfd/hugetlb: introduce
> >> memfd-based userspace MFR policy"
> >> project -- recycling the impacted hugetlb pages.
> >>
> >> I do think that "memfd-based userspace MFR policy" is a valuable
> >> enhancement, and if the impacted large page can be more easily recycled
> >> enabling check_new_pages() it's even better !
> >>
> >> HTH.
> >
>


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-22  8:27     ` Vlastimil Babka (SUSE)
  2026-07-22  9:03       ` Vlastimil Babka (SUSE)
@ 2026-07-25  6:52       ` Jiaqi Yan
  2026-07-27 14:20       ` David Hildenbrand (Arm)
  2 siblings, 0 replies; 26+ messages in thread
From: Jiaqi Yan @ 2026-07-25  6:52 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE)
  Cc: William Roche, David Hildenbrand (Arm), linmiaohe, ljs, ziy,
	osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On Wed, Jul 22, 2026 at 1:27 AM Vlastimil Babka (SUSE)
<vbabka@kernel.org> wrote:
>
> On 7/17/26 15:06, William Roche wrote:
> > On 7/17/26 12:18, David Hildenbrand (Arm) wrote:
> >> On 7/5/26 20:07, Jiaqi Yan wrote:
> >>> At the end of dissolve_free_hugetlb_folio(), a free HugeTLB
> >>> folio becomes non-HugeTLB and is released to buddy allocator
> >>> as a high-order folio, e.g. a folio that contains 262144 pages
> >>> if the folio was a 1G HugeTLB hugepage.
> >>>
> >>> This is problematic if the HugeTLB hugepage contained HWPoison
> >>> subpages. In that case, since buddy allocator does not check
> >>> HWPoison for non-zero-order folio, the raw HWPoison page can
> >>> be given out with its buddy page and be re-used by either
> >>> kernel or userspace.
> >>
> >> I still don't like the complexity of this, in particular, as we have different
> >> mechanisms in the page allocator already to try handling this,
> >>
> >> We also do have cases where we set the hwpoison bit, while a page is just about
> >> to get allocated from the buddy. So before we take it off the buddy, we might
> >> just hand out the page.
> >>
> >> check_new_pages() seems to check for PageHWPoison() and make us not hand out
> >> such pages. It's guarded by "check_pages" but it seems to do exactly what we are
> >> looking for, now?
> >
> >
> > Just adding a comment about this aspect:
> > The check_new_pages() mechanism used by the __rmqueue functions should
> > filter these pages out, but this has been disabled by default in 2023
> > with:
> > [PATCH] mm, page_alloc: reduce page alloc/free sanity checks
> > https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
> >
> > So it would need to be enabled back, taking some of the performance hit.
> > (and I personally think that it has to be done)
>
> Would it truly fix the issue, or rather there would still be a race window
> left where we check that there's no hwpoison flag in the re-enabled check,
> and only then someone sets it?
>
> Also, can the hardware actually detect a problem with a page that nobody
> accesses? I guess if yes, it's only in some corner cases.

Certain CPU's memory controller supports patrol scrubbing; most the
cases it corrects the correctable errors, and in some cases it signals
uncorrectable error, which could belong to free or allocated page(s).

>
> So I'm wary about penalizing the allocator paths again. If the page is in
> the buddy allocator, shouldn't it be isolated away as part of setting the
> hwpoison? I thought we already did that? So assuming we don't just leave

Yes - if is_free_buddy_page(p), memory_failure() will
take_page_off_buddy(p) after get_hwpoison_page().

> hwpoison pages in the buddy and this is only about some small race window
> where it's being taken away from the buddy? Then the extra check would only
> make a small window smaller, but is it worth it?

I agree that if we re-enable check_new_pages(), the window will be
smaller; because check_new_pages() can just check HWPoison bit and
don't need to wait for get_hwpoison_page() and take_page_off_buddy().

Worth it? Maybe not. There is still a window between handing out the
page and marking the page HWPoison. For example, when
ghes_do_memory_failure() handles memory error (typically on ARM64), it
either asks current to run memory_failure() when it exits kernel and
returns to userspace, or just queues memory_failure() to some cpu's
kworker thread. check_new_pages() may not even see __PG_HWPOISON if it
is racing against them.

That being said, my patch also won't help the issue above. My point is
if check_new_pages() is expensive but still can't work 100%, maybe not
workth it.

>
> >    A note about the related project:
> > This patch is an addition to the "mm: memfd/hugetlb: introduce
> > memfd-based userspace MFR policy"
> > project -- recycling the impacted hugetlb pages.
> >
> > I do think that "memfd-based userspace MFR policy" is a valuable
> > enhancement, and if the impacted large page can be more easily recycled
> > enabling check_new_pages() it's even better !
> >
> > HTH.
>


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-22  8:27     ` Vlastimil Babka (SUSE)
  2026-07-22  9:03       ` Vlastimil Babka (SUSE)
  2026-07-25  6:52       ` Jiaqi Yan
@ 2026-07-27 14:20       ` David Hildenbrand (Arm)
  2026-07-27 18:20         ` Matthew Wilcox
  2026-07-28 17:18         ` Vlastimil Babka (SUSE)
  2 siblings, 2 replies; 26+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-27 14:20 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE), William Roche, Jiaqi Yan, linmiaohe, ljs,
	ziy
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On 7/22/26 10:27, Vlastimil Babka (SUSE) wrote:
> On 7/17/26 15:06, William Roche wrote:
>> On 7/17/26 12:18, David Hildenbrand (Arm) wrote:
>>>
>>> I still don't like the complexity of this, in particular, as we have different
>>> mechanisms in the page allocator already to try handling this,
>>>
>>> We also do have cases where we set the hwpoison bit, while a page is just about
>>> to get allocated from the buddy. So before we take it off the buddy, we might
>>> just hand out the page.
>>>
>>> check_new_pages() seems to check for PageHWPoison() and make us not hand out
>>> such pages. It's guarded by "check_pages" but it seems to do exactly what we are
>>> looking for, now?
>>
>>
>> Just adding a comment about this aspect:
>> The check_new_pages() mechanism used by the __rmqueue functions should
>> filter these pages out, but this has been disabled by default in 2023
>> with:
>> [PATCH] mm, page_alloc: reduce page alloc/free sanity checks
>> https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
>>
>> So it would need to be enabled back, taking some of the performance hit.
>> (and I personally think that it has to be done)
> 
> Would it truly fix the issue, or rather there would still be a race window
> left where we check that there's no hwpoison flag in the re-enabled check,
> and only then someone sets it?

Why are we checking PageHWPoison at all then in check_new_page()?

I think we created a mess.

The PageHWPoison check is not just a "nice to have" sanity check for kernel bugs.

So it should never have been optimized out that way before reworking the bigger
picture.

> 
> Also, can the hardware actually detect a problem with a page that nobody
> accesses? I guess if yes, it's only in some corner cases.

Yes, quite frequently I think.

> 
> So I'm wary about penalizing the allocator paths again.

I get the feeling that we don't have a proper plan on how to handle HWPoisoned
pages. We should take a step back and discuss how we actually want to handle
them instead of optimizing here and there and creating more of a mess.

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-27 14:20       ` David Hildenbrand (Arm)
@ 2026-07-27 18:20         ` Matthew Wilcox
  2026-07-28 17:18         ` Vlastimil Babka (SUSE)
  1 sibling, 0 replies; 26+ messages in thread
From: Matthew Wilcox @ 2026-07-27 18:20 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: Vlastimil Babka (SUSE), William Roche, Jiaqi Yan, linmiaohe, ljs,
	ziy, osalvador, harry.yoo, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On Mon, Jul 27, 2026 at 04:20:54PM +0200, David Hildenbrand (Arm) wrote:
> >> Just adding a comment about this aspect:
> >> The check_new_pages() mechanism used by the __rmqueue functions should
> >> filter these pages out, but this has been disabled by default in 2023
> >> with:
> >> [PATCH] mm, page_alloc: reduce page alloc/free sanity checks
> >> https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
> >>
> >> So it would need to be enabled back, taking some of the performance hit.
> >> (and I personally think that it has to be done)
> > 
> > Would it truly fix the issue, or rather there would still be a race window
> > left where we check that there's no hwpoison flag in the re-enabled check,
> > and only then someone sets it?
> 
> Why are we checking PageHWPoison at all then in check_new_page()?
> 
> I think we created a mess.
> 
> The PageHWPoison check is not just a "nice to have" sanity check for kernel bugs.
> 
> So it should never have been optimized out that way before reworking the bigger
> picture.

There's A Lot Going On (and I don't think I understand it all yet).
We can soft-poison pages while they're in Buddy, for example.
And then soft-unpoison them again.  Is it handled properly?  I doubt
it.  Looks to me like it's full of races.

> > Also, can the hardware actually detect a problem with a page that nobody
> > accesses? I guess if yes, it's only in some corner cases.
> 
> Yes, quite frequently I think.
> 
> > 
> > So I'm wary about penalizing the allocator paths again.
> 
> I get the feeling that we don't have a proper plan on how to handle HWPoisoned
> pages. We should take a step back and discuss how we actually want to handle
> them instead of optimizing here and there and creating more of a mess.

I feel like there's a general lack of understanding of how hwpoison
works amongst those of us who work on the core of memory handling,
and the memory-failure code has not been kept up to date with how we
think about page/folio/memdesc handling.  It might be good to have a
BOF at Plumbers to share our (mis)understandings of how all of this
works yesterday/today/tomorrow.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-27 14:20       ` David Hildenbrand (Arm)
  2026-07-27 18:20         ` Matthew Wilcox
@ 2026-07-28 17:18         ` Vlastimil Babka (SUSE)
  2026-08-04 19:51           ` David Hildenbrand (Arm)
  1 sibling, 1 reply; 26+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-07-28 17:18 UTC (permalink / raw)
  To: David Hildenbrand (Arm), William Roche, Jiaqi Yan, linmiaohe, ljs,
	ziy
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On 7/27/26 16:20, David Hildenbrand (Arm) wrote:
> On 7/22/26 10:27, Vlastimil Babka (SUSE) wrote:
>> On 7/17/26 15:06, William Roche wrote:
>>> On 7/17/26 12:18, David Hildenbrand (Arm) wrote:
>>>>
>>>> I still don't like the complexity of this, in particular, as we have different
>>>> mechanisms in the page allocator already to try handling this,
>>>>
>>>> We also do have cases where we set the hwpoison bit, while a page is just about
>>>> to get allocated from the buddy. So before we take it off the buddy, we might
>>>> just hand out the page.
>>>>
>>>> check_new_pages() seems to check for PageHWPoison() and make us not hand out
>>>> such pages. It's guarded by "check_pages" but it seems to do exactly what we are
>>>> looking for, now?
>>>
>>>
>>> Just adding a comment about this aspect:
>>> The check_new_pages() mechanism used by the __rmqueue functions should
>>> filter these pages out, but this has been disabled by default in 2023
>>> with:
>>> [PATCH] mm, page_alloc: reduce page alloc/free sanity checks
>>> https://lore.kernel.org/all/20230216095131.17336-1-vbabka@suse.cz
>>>
>>> So it would need to be enabled back, taking some of the performance hit.
>>> (and I personally think that it has to be done)
>> 
>> Would it truly fix the issue, or rather there would still be a race window
>> left where we check that there's no hwpoison flag in the re-enabled check,
>> and only then someone sets it?
> 
> Why are we checking PageHWPoison at all then in check_new_page()?

We check all kinds of unexpected state, when that's enable. PageHWPoison
could have been considered unexpected too, when the checks were made more
and more optional (first by Mel and then me).

But indeed it seems the PageHWPoison check is supposed to be load-bearing
(hi, Lorenzo!). It's intentionally handled before bad_page() (with a taint)
in check_new_page_bad(). Commits 2a7684a23e9c and f4c18e6f7b5b are also a hint.

> I think we created a mess.

Yes, the PageHWPoison handling was made part of debugging sanity check and
then not recognized properly as load-bearing later.

> The PageHWPoison check is not just a "nice to have" sanity check for kernel bugs.
> 
> So it should never have been optimized out that way before reworking the bigger
> picture.

Sorry!

>> 
>> Also, can the hardware actually detect a problem with a page that nobody
>> accesses? I guess if yes, it's only in some corner cases.
> 
> Yes, quite frequently I think.
> 
>> 
>> So I'm wary about penalizing the allocator paths again.
> 
> I get the feeling that we don't have a proper plan on how to handle HWPoisoned
> pages. We should take a step back and discuss how we actually want to handle
> them instead of optimizing here and there and creating more of a mess.

Fully agree on having a proper plan, because I got the feeling it's been a
whack-a-mole approach for years. If we then decide to put the check back
(and live with the residual race window), it should be handled completely
separately from check_new_page().



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-07-28 17:18         ` Vlastimil Babka (SUSE)
@ 2026-08-04 19:51           ` David Hildenbrand (Arm)
  2026-08-05  8:58             ` Vlastimil Babka (SUSE)
  0 siblings, 1 reply; 26+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-04 19:51 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE), William Roche, Jiaqi Yan, linmiaohe, ljs,
	ziy
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On 7/28/26 19:18, Vlastimil Babka (SUSE) wrote:
> On 7/27/26 16:20, David Hildenbrand (Arm) wrote:
>> On 7/22/26 10:27, Vlastimil Babka (SUSE) wrote:
>>>
>>> Would it truly fix the issue, or rather there would still be a race window
>>> left where we check that there's no hwpoison flag in the re-enabled check,
>>> and only then someone sets it?
>>
>> Why are we checking PageHWPoison at all then in check_new_page()?
> 
> We check all kinds of unexpected state, when that's enable. PageHWPoison
> could have been considered unexpected too, when the checks were made more
> and more optional (first by Mel and then me).
> 
> But indeed it seems the PageHWPoison check is supposed to be load-bearing
> (hi, Lorenzo!). It's intentionally handled before bad_page() (with a taint)
> in check_new_page_bad(). Commits 2a7684a23e9c and f4c18e6f7b5b are also a hint.
> 
>> I think we created a mess.
> 
> Yes, the PageHWPoison handling was made part of debugging sanity check and
> then not recognized properly as load-bearing later.

BTW, I'd assume we can check for PageHWPoison at free time fairly efficiently, as we
touch all page flags already.

Something along the lines of:

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 520ebd2fa40b5..57f89f8684168 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -1321,6 +1321,7 @@ static __always_inline bool __free_pages_prepare(struct page *page,
        bool init = want_init_on_free();
        bool compound = PageCompound(page);
        struct folio *folio = page_folio(page);
+       unsigned int hwpoisoned = 0;
 
        if (fpi_flags & FPI_PREPARED)
                return true;
@@ -1347,21 +1348,6 @@ static __always_inline bool __free_pages_prepare(struct page *page,
                count_vm_events(UNEVICTABLE_PGCLEARED, nr_pages);
        }
 
-       if (unlikely(PageHWPoison(page)) && !order) {
-               /* Do not let hwpoison pages hit pcplists/buddy */
-               reset_page_owner(page, order);
-               page_table_check_free(page, order);
-               pgalloc_tag_sub(page, 1 << order);
-
-               /*
-                * The page is isolated and accounted for.
-                * Mark the codetag as empty to avoid accounting error
-                * when the page is freed by unpoison_memory().
-                */
-               clear_page_tag_ref(page);
-               return false;
-       }
-
        VM_BUG_ON_PAGE(compound && compound_order(page) != order, page);
 
        /*
@@ -1395,8 +1381,26 @@ static __always_inline bool __free_pages_prepare(struct page *page,
                                }
                        }
                        tail_page->flags.f &= ~PAGE_FLAGS_CHECK_AT_PREP;
+                       hwpoisoned += PageHWPoison(tail_page);
                }
        }
+       hwpoisoned += PageHWPoison(page);
+
+       if (unlikely(hwpoisoned)) {
+               /* Do not let hwpoison pages hit pcplists/buddy */
+               reset_page_owner(page, order);
+               page_table_check_free(page, order);
+               pgalloc_tag_sub(page, 1 << order);
+
+               /*
+                * The page is isolated and accounted for.
+                * Mark the codetag as empty to avoid accounting error
+                * when the page is freed by unpoison_memory().
+                */
+               clear_page_tag_ref(page);
+               return false;
+       }
+
        if (folio_test_anon(folio)) {
                mod_mthp_stat(order, MTHP_STAT_NR_ANON, -1);
                folio->mapping = NULL;
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile


Of course, we could also fallback for order>0 to do some smart splitting.


Checking on allocation efficiently is indeed a bit more tricky. For compound
pages prep_compound_page() will already walk all pages.

Having hwpoison logic to just sync with the buddy when setting hwpoison flags
would be nicer.

Then we could (maybe) have the rule that no hwpoisoned page can enter the buddy, and no
page can become hwpoisoned while in the buddy. Consequently, no hwpoisoned page can
leave the buddy (except weird races while allocating, but then it's just an allocated
page).

Maybe.

-- 
Cheers,

David


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-08-04 19:51           ` David Hildenbrand (Arm)
@ 2026-08-05  8:58             ` Vlastimil Babka (SUSE)
  2026-08-05  9:09               ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 26+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-08-05  8:58 UTC (permalink / raw)
  To: David Hildenbrand (Arm), William Roche, Jiaqi Yan, linmiaohe, ljs,
	ziy
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On 8/4/26 21:51, David Hildenbrand (Arm) wrote:
> On 7/28/26 19:18, Vlastimil Babka (SUSE) wrote:
>> On 7/27/26 16:20, David Hildenbrand (Arm) wrote:
>>> On 7/22/26 10:27, Vlastimil Babka (SUSE) wrote:
>>>>
>>>> Would it truly fix the issue, or rather there would still be a race window
>>>> left where we check that there's no hwpoison flag in the re-enabled check,
>>>> and only then someone sets it?
>>>
>>> Why are we checking PageHWPoison at all then in check_new_page()?
>> 
>> We check all kinds of unexpected state, when that's enable. PageHWPoison
>> could have been considered unexpected too, when the checks were made more
>> and more optional (first by Mel and then me).
>> 
>> But indeed it seems the PageHWPoison check is supposed to be load-bearing
>> (hi, Lorenzo!). It's intentionally handled before bad_page() (with a taint)
>> in check_new_page_bad(). Commits 2a7684a23e9c and f4c18e6f7b5b are also a hint.
>> 
>>> I think we created a mess.
>> 
>> Yes, the PageHWPoison handling was made part of debugging sanity check and
>> then not recognized properly as load-bearing later.
> 
> BTW, I'd assume we can check for PageHWPoison at free time fairly efficiently, as we
> touch all page flags already.
> 
> Something along the lines of:
> 
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 520ebd2fa40b5..57f89f8684168 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -1321,6 +1321,7 @@ static __always_inline bool __free_pages_prepare(struct page *page,
>         bool init = want_init_on_free();
>         bool compound = PageCompound(page);
>         struct folio *folio = page_folio(page);
> +       unsigned int hwpoisoned = 0;
>  
>         if (fpi_flags & FPI_PREPARED)
>                 return true;
> @@ -1347,21 +1348,6 @@ static __always_inline bool __free_pages_prepare(struct page *page,
>                 count_vm_events(UNEVICTABLE_PGCLEARED, nr_pages);
>         }
>  
> -       if (unlikely(PageHWPoison(page)) && !order) {
> -               /* Do not let hwpoison pages hit pcplists/buddy */
> -               reset_page_owner(page, order);
> -               page_table_check_free(page, order);
> -               pgalloc_tag_sub(page, 1 << order);
> -
> -               /*
> -                * The page is isolated and accounted for.
> -                * Mark the codetag as empty to avoid accounting error
> -                * when the page is freed by unpoison_memory().
> -                */
> -               clear_page_tag_ref(page);
> -               return false;
> -       }
> -
>         VM_BUG_ON_PAGE(compound && compound_order(page) != order, page);
>  
>         /*
> @@ -1395,8 +1381,26 @@ static __always_inline bool __free_pages_prepare(struct page *page,
>                                 }
>                         }
>                         tail_page->flags.f &= ~PAGE_FLAGS_CHECK_AT_PREP;
> +                       hwpoisoned += PageHWPoison(tail_page);
>                 }
>         }
> +       hwpoisoned += PageHWPoison(page);
> +
> +       if (unlikely(hwpoisoned)) {
> +               /* Do not let hwpoison pages hit pcplists/buddy */
> +               reset_page_owner(page, order);
> +               page_table_check_free(page, order);
> +               pgalloc_tag_sub(page, 1 << order);
> +
> +               /*
> +                * The page is isolated and accounted for.
> +                * Mark the codetag as empty to avoid accounting error
> +                * when the page is freed by unpoison_memory().
> +                */
> +               clear_page_tag_ref(page);
> +               return false;
> +       }
> +
>         if (folio_test_anon(folio)) {
>                 mod_mthp_stat(order, MTHP_STAT_NR_ANON, -1);
>                 folio->mapping = NULL;
> diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
> 
> 
> Of course, we could also fallback for order>0 to do some smart splitting.

I think the freeing part is relatively fine, as tackled by this series.

> Checking on allocation efficiently is indeed a bit more tricky. For compound
> pages prep_compound_page() will already walk all pages.

Yeah and non-compound high-order pages should just go away eventually, so I
wouldn't worry about making them slower now.
But it's tricky because prep_compound_page() happens later when we don't
expect anything to fail anymore, so it would take some refactoring.

> Having hwpoison logic to just sync with the buddy when setting hwpoison flags
> would be nicer.

I suggested that (soft-offline does it). There might be some downsides, but
I can't judge how much they would manifest in practice:

https://lore.kernel.org/all/CACw3F50nVECJ%2Bk4g%3DQVZ80yzi_awCOcUiCuB0CYcUfxND9-7Tw@mail.gmail.com/

> Then we could (maybe) have the rule that no hwpoisoned page can enter the buddy, and no
> page can become hwpoisoned while in the buddy. Consequently, no hwpoisoned page can
> leave the buddy (except weird races while allocating, but then it's just an allocated
> page).

I'd love if that approach was feasible, yeah.

> Maybe.
> 



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio
  2026-08-05  8:58             ` Vlastimil Babka (SUSE)
@ 2026-08-05  9:09               ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 26+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-05  9:09 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE), William Roche, Jiaqi Yan, linmiaohe, ljs,
	ziy
  Cc: osalvador, harry.yoo, willy, osalvador, jackmanb, hannes,
	nao.horiguchi, tony.luck, wangkefeng.wang, jane.chu, akpm,
	muchun.song, liam, rientjes, duenwen, jthoughton, linux-mm,
	linux-kernel, rppt, shuah, surenb, mhocko, boudewijn

On 8/5/26 10:58, Vlastimil Babka (SUSE) wrote:
> On 8/4/26 21:51, David Hildenbrand (Arm) wrote:
>> On 7/28/26 19:18, Vlastimil Babka (SUSE) wrote:
>>>
>>> We check all kinds of unexpected state, when that's enable. PageHWPoison
>>> could have been considered unexpected too, when the checks were made more
>>> and more optional (first by Mel and then me).
>>>
>>> But indeed it seems the PageHWPoison check is supposed to be load-bearing
>>> (hi, Lorenzo!). It's intentionally handled before bad_page() (with a taint)
>>> in check_new_page_bad(). Commits 2a7684a23e9c and f4c18e6f7b5b are also a hint.
>>>
>>>
>>> Yes, the PageHWPoison handling was made part of debugging sanity check and
>>> then not recognized properly as load-bearing later.
>>
>> BTW, I'd assume we can check for PageHWPoison at free time fairly efficiently, as we
>> touch all page flags already.
>>
>> Something along the lines of:
>>
>> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
>> index 520ebd2fa40b5..57f89f8684168 100644
>> --- a/mm/page_alloc.c
>> +++ b/mm/page_alloc.c
>> @@ -1321,6 +1321,7 @@ static __always_inline bool __free_pages_prepare(struct page *page,
>>         bool init = want_init_on_free();
>>         bool compound = PageCompound(page);
>>         struct folio *folio = page_folio(page);
>> +       unsigned int hwpoisoned = 0;
>>  
>>         if (fpi_flags & FPI_PREPARED)
>>                 return true;
>> @@ -1347,21 +1348,6 @@ static __always_inline bool __free_pages_prepare(struct page *page,
>>                 count_vm_events(UNEVICTABLE_PGCLEARED, nr_pages);
>>         }
>>  
>> -       if (unlikely(PageHWPoison(page)) && !order) {
>> -               /* Do not let hwpoison pages hit pcplists/buddy */
>> -               reset_page_owner(page, order);
>> -               page_table_check_free(page, order);
>> -               pgalloc_tag_sub(page, 1 << order);
>> -
>> -               /*
>> -                * The page is isolated and accounted for.
>> -                * Mark the codetag as empty to avoid accounting error
>> -                * when the page is freed by unpoison_memory().
>> -                */
>> -               clear_page_tag_ref(page);
>> -               return false;
>> -       }
>> -
>>         VM_BUG_ON_PAGE(compound && compound_order(page) != order, page);
>>  
>>         /*
>> @@ -1395,8 +1381,26 @@ static __always_inline bool __free_pages_prepare(struct page *page,
>>                                 }
>>                         }
>>                         tail_page->flags.f &= ~PAGE_FLAGS_CHECK_AT_PREP;
>> +                       hwpoisoned += PageHWPoison(tail_page);
>>                 }
>>         }
>> +       hwpoisoned += PageHWPoison(page);
>> +
>> +       if (unlikely(hwpoisoned)) {
>> +               /* Do not let hwpoison pages hit pcplists/buddy */
>> +               reset_page_owner(page, order);
>> +               page_table_check_free(page, order);
>> +               pgalloc_tag_sub(page, 1 << order);
>> +
>> +               /*
>> +                * The page is isolated and accounted for.
>> +                * Mark the codetag as empty to avoid accounting error
>> +                * when the page is freed by unpoison_memory().
>> +                */
>> +               clear_page_tag_ref(page);
>> +               return false;
>> +       }
>> +
>>         if (folio_test_anon(folio)) {
>>                 mod_mthp_stat(order, MTHP_STAT_NR_ANON, -1);
>>                 folio->mapping = NULL;
>> diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
>>
>>
>> Of course, we could also fallback for order>0 to do some smart splitting.
> 
> I think the freeing part is relatively fine, as tackled by this series.

I think we should really do something like the above to detect hwpoison, to then
fallback to a slow path.

> 
>> Checking on allocation efficiently is indeed a bit more tricky. For compound
>> pages prep_compound_page() will already walk all pages.
> 
> Yeah and non-compound high-order pages should just go away eventually, so I
> wouldn't worry about making them slower now.
> But it's tricky because prep_compound_page() happens later when we don't
> expect anything to fail anymore, so it would take some refactoring.

Agreed.

> 
>> Having hwpoison logic to just sync with the buddy when setting hwpoison flags
>> would be nicer.
> 
> I suggested that (soft-offline does it). There might be some downsides, but
> I can't judge how much they would manifest in practice:
> 
> https://lore.kernel.org/all/CACw3F50nVECJ%2Bk4g%3DQVZ80yzi_awCOcUiCuB0CYcUfxND9-7Tw@mail.gmail.com/

Agreed.

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2026-08-05  9:10 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-05 18:07 [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
2026-07-05 18:07 ` [PATCH v6 1/5] mm/page_alloc: introduce __free_prepared_contig_range() with fpi_t Jiaqi Yan
2026-07-17  7:17   ` Miaohe Lin
2026-07-22  9:38   ` Vlastimil Babka (SUSE)
2026-07-25  3:54   ` Matthew Wilcox
2026-07-05 18:07 ` [PATCH v6 2/5] mm/page_alloc: only free healthy pages in high-order has_hwpoisoned folio Jiaqi Yan
2026-07-17  7:19   ` Miaohe Lin
2026-07-22  9:40   ` Vlastimil Babka (SUSE)
2026-07-05 18:07 ` [PATCH v6 3/5] mm/memory-failure: set has_hwpoisoned flags on dissolved HugeTLB folio Jiaqi Yan
2026-07-25  3:05   ` Jiaqi Yan
2026-07-05 18:07 ` [PATCH v6 4/5] mm/memory-failure: skip take_page_off_buddy after dissolving HWPoison HugeTLB page Jiaqi Yan
2026-07-05 18:07 ` [PATCH v6 5/5] selftests/mm: add hard memory failure anonymous HugeTLB test Jiaqi Yan
2026-07-25  3:05   ` Jiaqi Yan
2026-07-05 18:50 ` [PATCH v6 0/5] Only free healthy pages in high-order has_hwpoisoned folio Andrew Morton
2026-07-06  9:03   ` David Hildenbrand (Arm)
2026-07-25  3:06   ` Jiaqi Yan
     [not found] ` <85cb7ea8-8116-4092-8310-69b61eb8602c@kernel.org>
     [not found]   ` <ee6bcc74-0846-43f2-bcca-b1bedff74351@oracle.com>
2026-07-22  8:27     ` Vlastimil Babka (SUSE)
2026-07-22  9:03       ` Vlastimil Babka (SUSE)
2026-07-25  6:00         ` Jiaqi Yan
2026-07-25  6:52       ` Jiaqi Yan
2026-07-27 14:20       ` David Hildenbrand (Arm)
2026-07-27 18:20         ` Matthew Wilcox
2026-07-28 17:18         ` Vlastimil Babka (SUSE)
2026-08-04 19:51           ` David Hildenbrand (Arm)
2026-08-05  8:58             ` Vlastimil Babka (SUSE)
2026-08-05  9:09               ` David Hildenbrand (Arm)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox