* [PATCH 0/3] mm: fixes for THP migration, swap-in and device-private smaps
@ 2026-11-11 11:38 Usama Arif
2026-11-11 11:38 ` [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage Usama Arif
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Usama Arif @ 2026-11-11 11:38 UTC (permalink / raw)
To: Andrew Morton, apopple, byungchul, david, gourry, jannh,
joshua.hahnjy, liam, linux-fsdevel, linux-kernel, linux-mm, ljs,
matthew.brost, mhocko, pfalcato, rppt, surenb, vbabka,
ying.huang, ziy, kas, hannes, riel, shakeel.butt, kernel-team,
baolin.wang, Nico Pache, ryan.roberts, dev.jain, baohua,
lance.yang
Cc: Usama Arif
This series contains three fixes for existing THP migration, PTE swap-in
and smaps paths. They were extracted from the work on PMD swap entries
for anonymous THPs [1] and can be merged independently. Sending this
separately as requested by Andrew (should have sent them separately
anyways, my bad).
1. Avoid a folio reference leak when concurrent MADV_DONTNEED clears
or splits a migration PMD before the device migration split helper
acquires the PMD lock.
2. Prevent PTE swap-in batching from mapping poisoned subpages of a
large swap-cache folio. Include the subpage scan helper with this
fix so it can be applied on its own.
3. Account device-private PMDs using mapcounts in smaps, matching the
PTE path. Shared mappings get proportional PSS, and exclusive
mappings are counted as private.
[1] PMD-level swap entries for anonymous THPs:
https://lore.kernel.org/r/20261002095503.3585565-1-usama.arif@linux.dev/
Usama Arif (3):
mm: don't PTE-batch a swap-in over a hardware-poisoned subpage
fs/proc/task_mmu: don't read young/dirty from a non-present PMD in
smaps
fs/proc/task_mmu: account device-private PMDs as present in smaps
fs/proc/task_mmu.c | 12 ++++++++----
mm/internal.h | 15 +++++++++++++++
mm/memory.c | 4 ++++
3 files changed, 27 insertions(+), 4 deletions(-)
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage
2026-11-11 11:38 [PATCH 0/3] mm: fixes for THP migration, swap-in and device-private smaps Usama Arif
@ 2026-11-11 11:38 ` Usama Arif
2026-11-11 13:18 ` Lorenzo Stoakes (ARM)
2026-11-11 18:50 ` Zi Yan
2026-11-11 11:38 ` [PATCH 2/3] fs/proc/task_mmu: don't read young/dirty from a non-present PMD in smaps Usama Arif
2026-11-11 11:38 ` [PATCH 3/3] fs/proc/task_mmu: account device-private PMDs as present " Usama Arif
2 siblings, 2 replies; 9+ messages in thread
From: Usama Arif @ 2026-11-11 11:38 UTC (permalink / raw)
To: Andrew Morton, apopple, byungchul, david, gourry, jannh,
joshua.hahnjy, liam, linux-fsdevel, linux-kernel, linux-mm, ljs,
matthew.brost, mhocko, pfalcato, rppt, surenb, vbabka,
ying.huang, ziy, kas, hannes, riel, shakeel.butt, kernel-team,
baolin.wang, Nico Pache, ryan.roberts, dev.jain, baohua,
lance.yang
Cc: Usama Arif, stable
do_swap_page() checks PageHWPoison() on the faulting page, but the
large-folio batching path then maps the rest of the folio without looking
at the other subpages. A poisoned subpage that is not the one being
faulted gets mapped anyway, and the fault that would have reported it
never happens.
Skip batching for such a folio and let each page take its own fault,
where the existing check catches it.
Scan the subpages rather than testing the folio-level PG_has_hwpoisoned:
memory_failure() sets PageHWPoison() on the subpage first and only sets
the folio flag much later, and the bail-outs in between - for example
get_hwpoison_page() losing a page lifecycle race - return with the
subpage marked and the folio flag never set.
Fixes: 508758960b8d ("mm: swap: entirely map large folios found in swapcache")
Cc: stable@vger.kernel.org
Signed-off-by: Usama Arif <usama.arif@linux.dev>
---
mm/internal.h | 15 +++++++++++++++
mm/memory.c | 4 ++++
2 files changed, 19 insertions(+)
diff --git a/mm/internal.h b/mm/internal.h
index cd3675c044e0a..e61c02f4019b0 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -28,6 +28,21 @@ struct folio_batch;
int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
void *buf, int len, unsigned int gup_flags);
+/*
+ * Check every page of the folio for HWPoison, rather than relying on the
+ * folio-level PG_has_hwpoisoned, which memory_failure() can leave clear.
+ */
+static inline bool folio_has_any_hwpoisoned_page(const struct folio *folio)
+{
+ long nr = folio_nr_pages(folio);
+ long i;
+
+ for (i = 0; i < nr; i++)
+ if (PageHWPoison(folio_page(folio, i)))
+ return true;
+ return false;
+}
+
/* mm/workingset.c */
bool workingset_test_recent(void *shadow, bool file, bool *workingset,
bool flush);
diff --git a/mm/memory.c b/mm/memory.c
index d477a4f986bc8..4b7d456332200 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -5158,6 +5158,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
swap_pte_batch(folio_ptep, nr, folio_pte) != nr)
goto check_folio;
+ /* Let a poisoned subpage take its own fault. */
+ if (folio_has_any_hwpoisoned_page(folio))
+ goto check_folio;
+
page_idx = idx;
address = folio_start;
ptep = folio_ptep;
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 2/3] fs/proc/task_mmu: don't read young/dirty from a non-present PMD in smaps
2026-11-11 11:38 [PATCH 0/3] mm: fixes for THP migration, swap-in and device-private smaps Usama Arif
2026-11-11 11:38 ` [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage Usama Arif
@ 2026-11-11 11:38 ` Usama Arif
2026-11-11 13:27 ` Lorenzo Stoakes (ARM)
2026-11-11 11:38 ` [PATCH 3/3] fs/proc/task_mmu: account device-private PMDs as present " Usama Arif
2 siblings, 1 reply; 9+ messages in thread
From: Usama Arif @ 2026-11-11 11:38 UTC (permalink / raw)
To: Andrew Morton, apopple, byungchul, david, gourry, jannh,
joshua.hahnjy, liam, linux-fsdevel, linux-kernel, linux-mm, ljs,
matthew.brost, mhocko, pfalcato, rppt, surenb, vbabka,
ying.huang, ziy, kas, hannes, riel, shakeel.butt, kernel-team,
baolin.wang, Nico Pache, ryan.roberts, dev.jain, baohua,
lance.yang
Cc: Usama Arif, stable
smaps_pmd_entry() passes pmd_young(*pmd) and pmd_dirty(*pmd) to
smaps_account() for every PMD, including migration and device-private
entries, where those bits hold part of the swap offset rather than
accessed/dirty state. smaps_pte_entry() only reads them when the entry
is present.
On x86_64 the offset is stored inverted across bits 9-58, which overlap
_PAGE_SAVED_DIRTY, so pmd_dirty() returns garbage: a clean 2MB
device-private mapping is accounted as 2048 kB dirty, and lazyfree
accounting is suppressed. pgtable_64.h also warns that the hardware can
set the accessed and dirty bits on non-present entries by mistake.
Only read the bits when the PMD is present.
Fixes: c94b6923fa0a ("/proc/PID/smaps: Add PMD migration entry parsing")
Cc: stable@vger.kernel.org
Signed-off-by: Usama Arif <usama.arif@linux.dev>
---
fs/proc/task_mmu.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index 22361607020d1..c0d391186c2b8 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -1019,13 +1019,15 @@ static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr,
struct vm_area_struct *vma = walk->vma;
bool locked = !!(vma->vm_flags & VM_LOCKED);
struct page *page = NULL;
- bool present = false;
+ bool present = false, young = false, dirty = false;
struct folio *folio;
if (pmd_none(*pmd))
return;
if (pmd_present(*pmd)) {
page = vm_normal_page_pmd(vma, addr, *pmd);
+ young = pmd_young(*pmd);
+ dirty = pmd_dirty(*pmd);
present = true;
} else if (unlikely(thp_migration_supported())) {
const softleaf_t entry = softleaf_from_pmd(*pmd);
@@ -1045,8 +1047,7 @@ static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr,
else
mss->file_thp += HPAGE_PMD_SIZE;
- smaps_account(mss, page, true, pmd_young(*pmd), pmd_dirty(*pmd),
- locked, present);
+ smaps_account(mss, page, true, young, dirty, locked, present);
}
#else
static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr,
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 3/3] fs/proc/task_mmu: account device-private PMDs as present in smaps
2026-11-11 11:38 [PATCH 0/3] mm: fixes for THP migration, swap-in and device-private smaps Usama Arif
2026-11-11 11:38 ` [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage Usama Arif
2026-11-11 11:38 ` [PATCH 2/3] fs/proc/task_mmu: don't read young/dirty from a non-present PMD in smaps Usama Arif
@ 2026-11-11 11:38 ` Usama Arif
2026-11-11 13:30 ` Lorenzo Stoakes (ARM)
2 siblings, 1 reply; 9+ messages in thread
From: Usama Arif @ 2026-11-11 11:38 UTC (permalink / raw)
To: Andrew Morton, apopple, byungchul, david, gourry, jannh,
joshua.hahnjy, liam, linux-fsdevel, linux-kernel, linux-mm, ljs,
matthew.brost, mhocko, pfalcato, rppt, surenb, vbabka,
ying.huang, ziy, kas, hannes, riel, shakeel.butt, kernel-team,
baolin.wang, Nico Pache, ryan.roberts, dev.jain, baohua,
lance.yang
Cc: Usama Arif, stable
Device-private PMDs represent mapped pages, but smaps_pmd_entry()
passes present=false to smaps_account(). That treats them like migration
entries: mapcounts are ignored, PSS is charged in full, and exclusive
mappings are accounted as shared.
Match smaps_pte_entry() and mark device-private PMDs as present so
smaps_account() uses mapcounts for PSS and private/shared accounting.
Fixes: a30b48bf1b24 ("mm/migrate_device: implement THP migration of zone device pages")
Cc: stable@vger.kernel.org
Signed-off-by: Usama Arif <usama.arif@linux.dev>
---
fs/proc/task_mmu.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index c0d391186c2b8..ad97532fc1184 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -1032,8 +1032,11 @@ static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr,
} else if (unlikely(thp_migration_supported())) {
const softleaf_t entry = softleaf_from_pmd(*pmd);
- if (softleaf_has_pfn(entry))
+ if (softleaf_has_pfn(entry)) {
+ if (softleaf_is_device_private(entry))
+ present = true;
page = softleaf_to_page(entry);
+ }
}
if (IS_ERR_OR_NULL(page))
return;
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage
2026-11-11 11:38 ` [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage Usama Arif
@ 2026-11-11 13:18 ` Lorenzo Stoakes (ARM)
2026-11-11 18:39 ` Usama Arif
2026-11-11 18:50 ` Zi Yan
1 sibling, 1 reply; 9+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 13:18 UTC (permalink / raw)
To: Usama Arif
Cc: Andrew Morton, apopple, byungchul, david, gourry, jannh,
joshua.hahnjy, liam, linux-fsdevel, linux-kernel, linux-mm,
matthew.brost, mhocko, pfalcato, rppt, surenb, vbabka,
ying.huang, ziy, kas, hannes, riel, shakeel.butt, kernel-team,
baolin.wang, Nico Pache, ryan.roberts, dev.jain, baohua,
lance.yang, stable
On Wed, Nov 11, 2026 at 03:38:29AM -0800, Usama Arif wrote:
> do_swap_page() checks PageHWPoison() on the faulting page, but the
> large-folio batching path then maps the rest of the folio without looking
> at the other subpages. A poisoned subpage that is not the one being
> faulted gets mapped anyway, and the fault that would have reported it
> never happens.
>
> Skip batching for such a folio and let each page take its own fault,
> where the existing check catches it.
>
> Scan the subpages rather than testing the folio-level PG_has_hwpoisoned:
> memory_failure() sets PageHWPoison() on the subpage first and only sets
> the folio flag much later, and the bail-outs in between - for example
> get_hwpoison_page() losing a page lifecycle race - return with the
> subpage marked and the folio flag never set.
>
> Fixes: 508758960b8d ("mm: swap: entirely map large folios found in swapcache")
> Cc: stable@vger.kernel.org
> Signed-off-by: Usama Arif <usama.arif@linux.dev>
> ---
> mm/internal.h | 15 +++++++++++++++
> mm/memory.c | 4 ++++
> 2 files changed, 19 insertions(+)
>
> diff --git a/mm/internal.h b/mm/internal.h
> index cd3675c044e0a..e61c02f4019b0 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -28,6 +28,21 @@ struct folio_batch;
> int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
> void *buf, int len, unsigned int gup_flags);
>
> +/*
> + * Check every page of the folio for HWPoison, rather than relying on the
> + * folio-level PG_has_hwpoisoned, which memory_failure() can leave clear.
> + */
> +static inline bool folio_has_any_hwpoisoned_page(const struct folio *folio)
> +{
> + long nr = folio_nr_pages(folio);
> + long i;
> +
> + for (i = 0; i < nr; i++)
> + if (PageHWPoison(folio_page(folio, i)))
> + return true;
> + return false;
> +}
> +
> /* mm/workingset.c */
> bool workingset_test_recent(void *shadow, bool file, bool *workingset,
> bool flush);
> diff --git a/mm/memory.c b/mm/memory.c
> index d477a4f986bc8..4b7d456332200 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -5158,6 +5158,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
> swap_pte_batch(folio_ptep, nr, folio_pte) != nr)
> goto check_folio;
>
> + /* Let a poisoned subpage take its own fault. */
> + if (folio_has_any_hwpoisoned_page(folio))
> + goto check_folio;
> +
Hm, isn't scanning every single page in the folio potentially quite costly
here? I thought the point of batching was to improve performance overall?
It seems broken to have stuff potentially interact with hwpoison but then
to only set the folio-level flag much later?
Also why are we swapping out folios containing hwpoison pages? Shouldn't we
migrate away from the hwpoison? :) (I'm not really familiar with the hw
poison stuff)
> page_idx = idx;
> address = folio_start;
> ptep = folio_ptep;
> --
> 2.53.0-Meta
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 2/3] fs/proc/task_mmu: don't read young/dirty from a non-present PMD in smaps
2026-11-11 11:38 ` [PATCH 2/3] fs/proc/task_mmu: don't read young/dirty from a non-present PMD in smaps Usama Arif
@ 2026-11-11 13:27 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 9+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 13:27 UTC (permalink / raw)
To: Usama Arif
Cc: Andrew Morton, apopple, byungchul, david, gourry, jannh,
joshua.hahnjy, liam, linux-fsdevel, linux-kernel, linux-mm,
matthew.brost, mhocko, pfalcato, rppt, surenb, vbabka,
ying.huang, ziy, kas, hannes, riel, shakeel.butt, kernel-team,
baolin.wang, Nico Pache, ryan.roberts, dev.jain, baohua,
lance.yang, stable
On Wed, Nov 11, 2026 at 03:38:30AM -0800, Usama Arif wrote:
> smaps_pmd_entry() passes pmd_young(*pmd) and pmd_dirty(*pmd) to
> smaps_account() for every PMD, including migration and device-private
> entries, where those bits hold part of the swap offset rather than
> accessed/dirty state. smaps_pte_entry() only reads them when the entry
> is present.
Could say something something softleaf here ;)
>
> On x86_64 the offset is stored inverted across bits 9-58, which overlap
> _PAGE_SAVED_DIRTY, so pmd_dirty() returns garbage: a clean 2MB
> device-private mapping is accounted as 2048 kB dirty, and lazyfree
> accounting is suppressed. pgtable_64.h also warns that the hardware can
> set the accessed and dirty bits on non-present entries by mistake.
>
> Only read the bits when the PMD is present.
>
> Fixes: c94b6923fa0a ("/proc/PID/smaps: Add PMD migration entry parsing")
> Cc: stable@vger.kernel.org
> Signed-off-by: Usama Arif <usama.arif@linux.dev>
Damn, yikes. LGTM so:
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> fs/proc/task_mmu.c | 7 ++++---
> 1 file changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
> index 22361607020d1..c0d391186c2b8 100644
> --- a/fs/proc/task_mmu.c
> +++ b/fs/proc/task_mmu.c
> @@ -1019,13 +1019,15 @@ static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr,
> struct vm_area_struct *vma = walk->vma;
> bool locked = !!(vma->vm_flags & VM_LOCKED);
> struct page *page = NULL;
> - bool present = false;
> + bool present = false, young = false, dirty = false;
> struct folio *folio;
>
> if (pmd_none(*pmd))
> return;
> if (pmd_present(*pmd)) {
> page = vm_normal_page_pmd(vma, addr, *pmd);
> + young = pmd_young(*pmd);
> + dirty = pmd_dirty(*pmd);
> present = true;
> } else if (unlikely(thp_migration_supported())) {
> const softleaf_t entry = softleaf_from_pmd(*pmd);
> @@ -1045,8 +1047,7 @@ static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr,
> else
> mss->file_thp += HPAGE_PMD_SIZE;
>
> - smaps_account(mss, page, true, pmd_young(*pmd), pmd_dirty(*pmd),
> - locked, present);
> + smaps_account(mss, page, true, young, dirty, locked, present);
> }
> #else
> static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr,
> --
> 2.53.0-Meta
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 3/3] fs/proc/task_mmu: account device-private PMDs as present in smaps
2026-11-11 11:38 ` [PATCH 3/3] fs/proc/task_mmu: account device-private PMDs as present " Usama Arif
@ 2026-11-11 13:30 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 9+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 13:30 UTC (permalink / raw)
To: Usama Arif
Cc: Andrew Morton, apopple, byungchul, david, gourry, jannh,
joshua.hahnjy, liam, linux-fsdevel, linux-kernel, linux-mm,
matthew.brost, mhocko, pfalcato, rppt, surenb, vbabka,
ying.huang, ziy, kas, hannes, riel, shakeel.butt, kernel-team,
baolin.wang, Nico Pache, ryan.roberts, dev.jain, baohua,
lance.yang, stable
On Wed, Nov 11, 2026 at 03:38:31AM -0800, Usama Arif wrote:
> Device-private PMDs represent mapped pages, but smaps_pmd_entry()
> passes present=false to smaps_account(). That treats them like migration
> entries: mapcounts are ignored, PSS is charged in full, and exclusive
> mappings are accounted as shared.
>
> Match smaps_pte_entry() and mark device-private PMDs as present so
> smaps_account() uses mapcounts for PSS and private/shared accounting.
>
> Fixes: a30b48bf1b24 ("mm/migrate_device: implement THP migration of zone device pages")
> Cc: stable@vger.kernel.org
> Signed-off-by: Usama Arif <usama.arif@linux.dev>
It feels like PMD level device private entries have been the source of a lot of
issues, maybe a good area to audit in general... :)
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> fs/proc/task_mmu.c | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
> index c0d391186c2b8..ad97532fc1184 100644
> --- a/fs/proc/task_mmu.c
> +++ b/fs/proc/task_mmu.c
> @@ -1032,8 +1032,11 @@ static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr,
> } else if (unlikely(thp_migration_supported())) {
> const softleaf_t entry = softleaf_from_pmd(*pmd);
>
> - if (softleaf_has_pfn(entry))
> + if (softleaf_has_pfn(entry)) {
> + if (softleaf_is_device_private(entry))
> + present = true;
> page = softleaf_to_page(entry);
> + }
> }
> if (IS_ERR_OR_NULL(page))
> return;
> --
> 2.53.0-Meta
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage
2026-11-11 13:18 ` Lorenzo Stoakes (ARM)
@ 2026-11-11 18:39 ` Usama Arif
0 siblings, 0 replies; 9+ messages in thread
From: Usama Arif @ 2026-11-11 18:39 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Usama Arif, Andrew Morton, apopple, byungchul, david, gourry,
jannh, joshua.hahnjy, liam, linux-fsdevel, linux-kernel,
linux-mm, matthew.brost, mhocko, pfalcato, rppt, surenb, vbabka,
ying.huang, ziy, kas, hannes, riel, shakeel.butt, kernel-team,
baolin.wang, Nico Pache, ryan.roberts, dev.jain, baohua,
lance.yang, stable
On Wed, 11 Nov 2026 13:18:36 +0000 "Lorenzo Stoakes (ARM)" <ljs@kernel.org> wrote:
> On Wed, Nov 11, 2026 at 03:38:29AM -0800, Usama Arif wrote:
> > do_swap_page() checks PageHWPoison() on the faulting page, but the
> > large-folio batching path then maps the rest of the folio without looking
> > at the other subpages. A poisoned subpage that is not the one being
> > faulted gets mapped anyway, and the fault that would have reported it
> > never happens.
> >
> > Skip batching for such a folio and let each page take its own fault,
> > where the existing check catches it.
> >
> > Scan the subpages rather than testing the folio-level PG_has_hwpoisoned:
> > memory_failure() sets PageHWPoison() on the subpage first and only sets
> > the folio flag much later, and the bail-outs in between - for example
> > get_hwpoison_page() losing a page lifecycle race - return with the
> > subpage marked and the folio flag never set.
> >
> > Fixes: 508758960b8d ("mm: swap: entirely map large folios found in swapcache")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Usama Arif <usama.arif@linux.dev>
> > ---
> > mm/internal.h | 15 +++++++++++++++
> > mm/memory.c | 4 ++++
> > 2 files changed, 19 insertions(+)
> >
> > diff --git a/mm/internal.h b/mm/internal.h
> > index cd3675c044e0a..e61c02f4019b0 100644
> > --- a/mm/internal.h
> > +++ b/mm/internal.h
> > @@ -28,6 +28,21 @@ struct folio_batch;
> > int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
> > void *buf, int len, unsigned int gup_flags);
> >
> > +/*
> > + * Check every page of the folio for HWPoison, rather than relying on the
> > + * folio-level PG_has_hwpoisoned, which memory_failure() can leave clear.
> > + */
> > +static inline bool folio_has_any_hwpoisoned_page(const struct folio *folio)
> > +{
> > + long nr = folio_nr_pages(folio);
> > + long i;
> > +
> > + for (i = 0; i < nr; i++)
> > + if (PageHWPoison(folio_page(folio, i)))
> > + return true;
> > + return false;
> > +}
> > +
> > /* mm/workingset.c */
> > bool workingset_test_recent(void *shadow, bool file, bool *workingset,
> > bool flush);
> > diff --git a/mm/memory.c b/mm/memory.c
> > index d477a4f986bc8..4b7d456332200 100644
> > --- a/mm/memory.c
> > +++ b/mm/memory.c
> > @@ -5158,6 +5158,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
> > swap_pte_batch(folio_ptep, nr, folio_pte) != nr)
> > goto check_folio;
> >
> > + /* Let a poisoned subpage take its own fault. */
> > + if (folio_has_any_hwpoisoned_page(folio))
> > + goto check_folio;
> > +
Hello Lorenzo,
Thanks for the reviews!
>
> Hm, isn't scanning every single page in the folio potentially quite costly
> here? I thought the point of batching was to improve performance overall?
Swapping in requires checking every subpage, so there would be a cost.
I guess trading it off for correctness :(
>
> It seems broken to have stuff potentially interact with hwpoison but then
> to only set the folio-level flag much later?
>
PG_has_hwpoisoned lives in the second page, so memory_failure() only
sets it once it holds a folio reference, otherwise it could race with a
THP split. If get_hwpoison_page() can't get a reference,
memory_failure() gives up with only the subpage marked. The comment
above folio_set_has_hwpoisoned() in memory_failure() says that is fine:
Don't need care about the above error handling paths for
get_hwpoison_page() since they handle either free page
or unhandlable page.
But a large folio that is briefly off the LRU, e.g. isolated by
reclaim, takes those paths too and can still be mapped afterwards.
> Also why are we swapping out folios containing hwpoison pages? Shouldn't we
> migrate away from the hwpoison? :) (I'm not really familiar with the hw
> poison stuff)
>
>
The error hits a folio that is already in the swap
cache, e.g. while reclaim has it off the LRU to write it out, and
get_hwpoison_page() fails for any folio that is off the LRU. Normally
memory_failure() would split the folio and isolate the bad page (for
an uncorrected error there is nothing to migrate; that is soft
offline). Here it does neither, and the folio stays in the swap cache
with only the subpage marked.
While looking at this I also noticed that the fallback drops a freshly
read (not yet anon) folio from the swap cache and frees it with the
poisoned page in it. We should actually actually also check folio_test_anon()
before checking folio_has_any_hwpoisoned_page().
> > page_idx = idx;
> > address = folio_start;
> > ptep = folio_ptep;
> > --
> > 2.53.0-Meta
> >
>
> --
> Cheers, Lorenzo
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage
2026-11-11 11:38 ` [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage Usama Arif
2026-11-11 13:18 ` Lorenzo Stoakes (ARM)
@ 2026-11-11 18:50 ` Zi Yan
1 sibling, 0 replies; 9+ messages in thread
From: Zi Yan @ 2026-11-11 18:50 UTC (permalink / raw)
To: Usama Arif
Cc: Andrew Morton, apopple, byungchul, david, gourry, jannh,
joshua.hahnjy, liam, linux-fsdevel, linux-kernel, linux-mm, ljs,
matthew.brost, mhocko, pfalcato, rppt, surenb, vbabka,
ying.huang, kas, hannes, riel, shakeel.butt, kernel-team,
baolin.wang, Nico Pache, ryan.roberts, dev.jain, baohua,
lance.yang, stable
On 11 Nov 2026, at 6:38, Usama Arif wrote:
> do_swap_page() checks PageHWPoison() on the faulting page, but the
> large-folio batching path then maps the rest of the folio without looking
> at the other subpages. A poisoned subpage that is not the one being
> faulted gets mapped anyway, and the fault that would have reported it
> never happens.
>
> Skip batching for such a folio and let each page take its own fault,
> where the existing check catches it.
>
> Scan the subpages rather than testing the folio-level PG_has_hwpoisoned:
> memory_failure() sets PageHWPoison() on the subpage first and only sets
> the folio flag much later, and the bail-outs in between - for example
> get_hwpoison_page() losing a page lifecycle race - return with the
> subpage marked and the folio flag never set.
>
> Fixes: 508758960b8d ("mm: swap: entirely map large folios found in swapcache")
> Cc: stable@vger.kernel.org
> Signed-off-by: Usama Arif <usama.arif@linux.dev>
> ---
> mm/internal.h | 15 +++++++++++++++
> mm/memory.c | 4 ++++
> 2 files changed, 19 insertions(+)
>
> diff --git a/mm/internal.h b/mm/internal.h
> index cd3675c044e0a..e61c02f4019b0 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -28,6 +28,21 @@ struct folio_batch;
> int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
> void *buf, int len, unsigned int gup_flags);
>
> +/*
> + * Check every page of the folio for HWPoison, rather than relying on the
> + * folio-level PG_has_hwpoisoned, which memory_failure() can leave clear.
> + */
> +static inline bool folio_has_any_hwpoisoned_page(const struct folio *folio)
> +{
> + long nr = folio_nr_pages(folio);
> + long i;
> +
> + for (i = 0; i < nr; i++)
> + if (PageHWPoison(folio_page(folio, i)))
> + return true;
> + return false;
> +}
It looks almost the same as page_range_has_hwpoisoned() in mm/huge_memory.c.
Maybe change the function to call page_range_has_hwpoisoned() instead.
> +
> /* mm/workingset.c */
> bool workingset_test_recent(void *shadow, bool file, bool *workingset,
> bool flush);
> diff --git a/mm/memory.c b/mm/memory.c
> index d477a4f986bc8..4b7d456332200 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -5158,6 +5158,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
> swap_pte_batch(folio_ptep, nr, folio_pte) != nr)
> goto check_folio;
>
> + /* Let a poisoned subpage take its own fault. */
> + if (folio_has_any_hwpoisoned_page(folio))
> + goto check_folio;
> +
> page_idx = idx;
> address = folio_start;
> ptep = folio_ptep;
> --
> 2.53.0-Meta
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-11-11 18:50 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 11:38 [PATCH 0/3] mm: fixes for THP migration, swap-in and device-private smaps Usama Arif
2026-11-11 11:38 ` [PATCH 1/3] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage Usama Arif
2026-11-11 13:18 ` Lorenzo Stoakes (ARM)
2026-11-11 18:39 ` Usama Arif
2026-11-11 18:50 ` Zi Yan
2026-11-11 11:38 ` [PATCH 2/3] fs/proc/task_mmu: don't read young/dirty from a non-present PMD in smaps Usama Arif
2026-11-11 13:27 ` Lorenzo Stoakes (ARM)
2026-11-11 11:38 ` [PATCH 3/3] fs/proc/task_mmu: account device-private PMDs as present " Usama Arif
2026-11-11 13:30 ` Lorenzo Stoakes (ARM)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®