* [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs
@ 2026-07-23 14:30 Matthew Wilcox (Oracle)
2026-07-23 14:30 ` [PATCH v4 01/14] memory-failure: Fix hardware poison check in unpoison_memory() again Matthew Wilcox (Oracle)
` (13 more replies)
0 siblings, 14 replies; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
This set of patches are separated out from Jane's earlier series.
https://lore.kernel.org/linux-mm/20260617172534.1740152-1-jane.chu@oracle.com/
I've made a lot of changes over the last week; mostly with regard to
hwpoison handling of hugetlb memory. Thanks to Miaohe Lin for
suggesting using the hugetlb_lock to stablise the hugetlb folio.
v3 here:
https://lore.kernel.org/linux-mm/20260714180840.3731244-1-willy@infradead.org/
Changes from v3:
- Fix unpoisoning a hugetlb folio
- Went back to hugetlb_hwpoison in the folio instead of having a global
list of poisoned pages
- Rename CONFIG_FILEMAP_LARGE_FOLIO to CONFIG_LARGE_FOLIO
- Added missing call to mapping_set_authoritative()
- Fixed polarity of check in mapping_set_folio_order_range()
- Added is_ref_page_hwpoison() so we don't need to do the extra work
of handling an unreferenced folio
- Use the has_hwpoisoned() flag for hugetlb as well as THP
- Added folio_set_huge_poison() so we can detect unreferenced
hwpoisoned hugetlb folios reliably
Jane Chu (2):
filemap: Add hwpoison handling to filemap_read()
hugetlb: replace hugetlbfs_read_iter() with generic_file_read_iter()
Matthew Wilcox (Oracle) (12):
memory-failure: Fix hardware poison check in unpoison_memory() again
memory-failure: Test the page is hwpoison before taking the mutex
mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page()
hugetlb: Mark some function arguments as const
guest_memfd: Use folio_has_hwpoisoned_page()
memory-failure: Remove raw_hwp_list_head()
hugetlb: Use the has_hwpoisoned flag
mm: Remove locking mf_mutex in is_raw_hwpoison_page_in_hugepage()
mm: Check individual hugetlb pages for poison
filemap: Remove checks in mapping_set_folio_order_range()
hugetlb: Set mapping folio order
filemap: Add support for authoritative mappings
fs/Kconfig | 2 +-
fs/hugetlbfs/inode.c | 123 ++++---------------------------
include/linux/hugetlb.h | 24 +++---
include/linux/mm_types.h | 4 +-
include/linux/page-flags.h | 59 ++++++++++++---
include/linux/pagemap.h | 45 +++++++-----
mm/Kconfig | 6 +-
mm/filemap.c | 51 ++++++++++++-
mm/huge_memory.c | 2 +-
mm/hugetlb.c | 16 ++--
mm/memory-failure.c | 146 +++++++++++++++++++++++--------------
mm/memory_hotplug.c | 2 +-
mm/shmem.c | 2 +-
mm/vmscan.c | 2 +-
virt/kvm/guest_memfd.c | 2 +-
15 files changed, 258 insertions(+), 228 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH v4 01/14] memory-failure: Fix hardware poison check in unpoison_memory() again
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 12:32 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 02/14] memory-failure: Test the page is hwpoison before taking the mutex Matthew Wilcox (Oracle)
` (12 subsequent siblings)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan, stable,
Naoya Horiguchi
The earlier patch (6c54312f9689) that attempted to fix unpoison_memory()
was incorrect for hugetlb folios. Before a6fddef49eef, we checked
the head page for poison (which was correct for hugetlb and incorrect
for THP). We are currently incapable of unpoisoning pages (other than
the first page) in a hugetlb folio.
Use is_page_hwpoison() which handles hugetlb pages sufficiently well for
this purpose. This is racy as we don't have a reference on the folio at
this point, but fixing that properly requires deeper surgery and this
is a CAP_SYS_ADMIN path only so this will do for the moment and can be
easily backported. It's no worse than the situation before a6fddef49eef.
The other bug in a6fddef49eef is that we currently clear the HWPoison
flag on the precise page, which is wrong for hugetlb. Fix that too.
Fixes: a6fddef49eef ("mm/memory-failure: convert unpoison_memory() to folios")
Cc: stable@vger.kernel.org
Cc: Naoya Horiguchi <naoya.horiguchi@nec.com>
Cc: Miaohe Lin <linmiaohe@huawei.com>
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
mm/memory-failure.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 51508a55c405..944e6e1d4971 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -2695,8 +2695,8 @@ int unpoison_memory(unsigned long pfn)
goto unlock_mutex;
}
- if (!PageHWPoison(p)) {
- unpoison_pr_info("%#lx: page was already unpoisoned\n",
+ if (!is_page_hwpoison(p)) {
+ unpoison_pr_info("%#lx: page is not poisoned\n",
pfn, &unpoison_rs);
goto unlock_mutex;
}
@@ -2748,6 +2748,7 @@ int unpoison_memory(unsigned long pfn)
folio_put(folio);
goto unlock_mutex;
}
+ p = &folio->page;
}
folio_put(folio);
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 02/14] memory-failure: Test the page is hwpoison before taking the mutex
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
2026-07-23 14:30 ` [PATCH v4 01/14] memory-failure: Fix hardware poison check in unpoison_memory() again Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 12:34 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page() Matthew Wilcox (Oracle)
` (11 subsequent siblings)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
We don't need to synchronize with memory failure to filter out pages
which are not hwpoisoned. It's inherently racy, so it doesn't matter.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
mm/memory-failure.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 944e6e1d4971..3615ced0a11b 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -2677,7 +2677,12 @@ int unpoison_memory(unsigned long pfn)
p = pfn_to_online_page(pfn);
if (!p)
return -EIO;
- folio = page_folio(p);
+
+ if (!is_page_hwpoison(p)) {
+ unpoison_pr_info("%#lx: page is not poisoned\n",
+ pfn, &unpoison_rs);
+ return -EBUSY;
+ }
mutex_lock(&mf_mutex);
@@ -2688,6 +2693,8 @@ int unpoison_memory(unsigned long pfn)
goto unlock_mutex;
}
+ folio = page_folio(p);
+
if (is_huge_zero_folio(folio)) {
unpoison_pr_info("%#lx: huge zero page is not supported\n",
pfn, &unpoison_rs);
@@ -2695,12 +2702,6 @@ int unpoison_memory(unsigned long pfn)
goto unlock_mutex;
}
- if (!is_page_hwpoison(p)) {
- unpoison_pr_info("%#lx: page is not poisoned\n",
- pfn, &unpoison_rs);
- goto unlock_mutex;
- }
-
if (folio_ref_count(folio) > 1) {
unpoison_pr_info("%#lx: someone grabs the hwpoison page\n",
pfn, &unpoison_rs);
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page()
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
2026-07-23 14:30 ` [PATCH v4 01/14] memory-failure: Fix hardware poison check in unpoison_memory() again Matthew Wilcox (Oracle)
2026-07-23 14:30 ` [PATCH v4 02/14] memory-failure: Test the page is hwpoison before taking the mutex Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 12:36 ` Matthew Wilcox
2026-07-24 12:52 ` Michael S. Tsirkin
2026-07-23 14:30 ` [PATCH v4 04/14] hugetlb: Mark some function arguments as const Matthew Wilcox (Oracle)
` (10 subsequent siblings)
13 siblings, 2 replies; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan, Usama Arif
This is a better name for this predicate. Also make the argument const
to allow other callers to be made const. No functional change.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
Acked-by: Usama Arif <usama.arif@linux.dev>
---
include/linux/page-flags.h | 4 ++--
mm/huge_memory.c | 2 +-
mm/memory_hotplug.c | 2 +-
mm/shmem.c | 2 +-
mm/vmscan.c | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 7223f6f4e2b4..4185a03a45cf 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -1083,10 +1083,10 @@ static inline bool is_page_hwpoison(const struct page *page)
return folio_test_hugetlb(folio) && PageHWPoison(&folio->page);
}
-static inline bool folio_contain_hwpoisoned_page(struct folio *folio)
+static inline bool folio_has_hwpoisoned_page(const struct folio *folio)
{
return folio_test_hwpoison(folio) ||
- (folio_test_large(folio) && folio_test_has_hwpoisoned(folio));
+ (folio_test_large(folio) && folio_test_has_hwpoisoned(folio));
}
bool is_free_buddy_page(const struct page *page);
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2bccb0a53a0a..353340309107 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4413,7 +4413,7 @@ static bool thp_underused(struct folio *folio)
if (khugepaged_max_ptes_none == HPAGE_PMD_NR - 1)
return false;
- if (folio_contain_hwpoisoned_page(folio))
+ if (folio_has_hwpoisoned_page(folio))
return false;
for (i = 0; i < folio_nr_pages(folio); i++) {
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 7ac19fab2263..33007e8e546a 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -1813,7 +1813,7 @@ static void do_migrate_range(unsigned long start_pfn, unsigned long end_pfn)
if (folio_test_large(folio))
pfn = folio_pfn(folio) + folio_nr_pages(folio) - 1;
- if (folio_contain_hwpoisoned_page(folio)) {
+ if (folio_has_hwpoisoned_page(folio)) {
/*
* unmap_poisoned_folio() cannot handle large folios
* in all cases yet.
diff --git a/mm/shmem.c b/mm/shmem.c
index b51f83c970bb..492c9bef3620 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -3240,7 +3240,7 @@ shmem_write_begin(const struct kiocb *iocb, struct address_space *mapping,
if (ret)
return ret;
- if (folio_contain_hwpoisoned_page(folio)) {
+ if (folio_has_hwpoisoned_page(folio)) {
folio_unlock(folio);
folio_put(folio);
return -EIO;
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 35c3bb15ae96..70e54ace0e74 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -1086,7 +1086,7 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
if (!folio_trylock(folio))
goto keep;
- if (folio_contain_hwpoisoned_page(folio)) {
+ if (folio_has_hwpoisoned_page(folio)) {
/*
* unmap_poisoned_folio() can't handle large
* folio, just skip it. memory_failure() will
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 04/14] hugetlb: Mark some function arguments as const
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (2 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page() Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 12:38 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page() Matthew Wilcox (Oracle)
` (9 subsequent siblings)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan, Usama Arif
These functions do not modify their folio argument, so we can mark them
as being const which will allow for some minor optimisations and let us
mark other function arguments as being const in the future.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
Acked-by: Usama Arif <usama.arif@linux.dev>
---
include/linux/hugetlb.h | 19 +++++++++----------
mm/hugetlb.c | 2 +-
2 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 2abaf99321e9..18661a7cdb37 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -165,7 +165,7 @@ bool hugetlbfs_pagecache_present(struct hstate *h,
struct vm_area_struct *vma,
unsigned long address);
-struct address_space *hugetlb_folio_mapping_lock_write(struct folio *folio);
+struct address_space *hugetlb_folio_mapping_lock_write(const struct folio *folio);
extern int movable_gigantic_pages __read_mostly;
extern int sysctl_hugetlb_shm_group __read_mostly;
@@ -292,8 +292,7 @@ static inline unsigned long hugetlb_total_pages(void)
return 0;
}
-static inline struct address_space *hugetlb_folio_mapping_lock_write(
- struct folio *folio)
+static inline struct address_space *hugetlb_folio_mapping_lock_write(const struct folio *folio)
{
return NULL;
}
@@ -595,8 +594,8 @@ enum hugetlb_page_flags {
#ifdef CONFIG_HUGETLB_PAGE
#define TESTHPAGEFLAG(uname, flname) \
static __always_inline \
-bool folio_test_hugetlb_##flname(struct folio *folio) \
- { void *private = &folio->private; \
+bool folio_test_hugetlb_##flname(const struct folio *folio) \
+ { const void *private = &folio->private; \
return test_bit(HPG_##flname, private); \
}
@@ -616,7 +615,7 @@ void folio_clear_hugetlb_##flname(struct folio *folio) \
#else
#define TESTHPAGEFLAG(uname, flname) \
static inline bool \
-folio_test_hugetlb_##flname(struct folio *folio) \
+folio_test_hugetlb_##flname(const struct folio *folio) \
{ return 0; }
#define SETHPAGEFLAG(uname, flname) \
@@ -727,7 +726,7 @@ static inline struct hugepage_subpool *subpool_inode(struct inode *inode)
return HUGETLBFS_SB(inode->i_sb)->spool;
}
-static inline struct hugepage_subpool *hugetlb_folio_subpool(struct folio *folio)
+static inline struct hugepage_subpool *hugetlb_folio_subpool(const struct folio *folio)
{
return folio->_hugetlb_subpool;
}
@@ -857,7 +856,7 @@ static inline bool arch_has_huge_bootmem_alloc(void)
}
#endif
-static inline struct hstate *folio_hstate(struct folio *folio)
+static inline struct hstate *folio_hstate(const struct folio *folio)
{
VM_BUG_ON_FOLIO(!folio_test_hugetlb(folio), folio);
return size_to_hstate(folio_size(folio));
@@ -1088,7 +1087,7 @@ static inline unsigned long huge_page_mask_align(struct file *file)
return 0;
}
-static inline struct hugepage_subpool *hugetlb_folio_subpool(struct folio *folio)
+static inline struct hugepage_subpool *hugetlb_folio_subpool(const struct folio *folio)
{
return NULL;
}
@@ -1157,7 +1156,7 @@ static inline struct hstate *hstate_vma(struct vm_area_struct *vma)
return NULL;
}
-static inline struct hstate *folio_hstate(struct folio *folio)
+static inline struct hstate *folio_hstate(const struct folio *folio)
{
return NULL;
}
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 571212b80835..cc8d98399913 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1775,7 +1775,7 @@ void init_new_hugetlb_folio(struct folio *folio)
* stable. Due to locking order, we can only trylock_write. If we can
* not get the lock, simply return NULL to caller.
*/
-struct address_space *hugetlb_folio_mapping_lock_write(struct folio *folio)
+struct address_space *hugetlb_folio_mapping_lock_write(const struct folio *folio)
{
struct address_space *mapping = folio_mapping(folio);
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page()
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (3 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 04/14] hugetlb: Mark some function arguments as const Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 12:44 ` Matthew Wilcox
2026-07-24 15:16 ` Sean Christopherson
2026-07-23 14:30 ` [PATCH v4 06/14] memory-failure: Remove raw_hwp_list_head() Matthew Wilcox (Oracle)
` (8 subsequent siblings)
13 siblings, 2 replies; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
folio_test_hwpoison() only works on hugetlb folios today and will give
the wrong answer for other large folios. folio_has_hwpoisoned_page()
works on both.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
virt/kvm/guest_memfd.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index db57c5766ab6..94eedcc7f1a9 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -774,7 +774,7 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
if (IS_ERR(folio))
return folio;
- if (folio_test_hwpoison(folio)) {
+ if (folio_has_hwpoisoned_page(folio)) {
folio_unlock(folio);
folio_put(folio);
return ERR_PTR(-EHWPOISON);
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 06/14] memory-failure: Remove raw_hwp_list_head()
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (4 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page() Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 12:45 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 07/14] hugetlb: Use the has_hwpoisoned flag Matthew Wilcox (Oracle)
` (7 subsequent siblings)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
There's no need to hide the llist_head; we can just declare it
directly in struct folio and do away with raw_hwp_list_head().
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
include/linux/mm_types.h | 4 ++--
mm/memory-failure.c | 19 +++++--------------
2 files changed, 7 insertions(+), 16 deletions(-)
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index b18c2b2e7d2c..d7a9b0d8bbdf 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -387,7 +387,7 @@ typedef unsigned short mm_id_t;
* @_hugetlb_subpool: Do not use directly, use accessor in hugetlb.h.
* @_hugetlb_cgroup: Do not use directly, use accessor in hugetlb_cgroup.h.
* @_hugetlb_cgroup_rsvd: Do not use directly, use accessor in hugetlb_cgroup.h.
- * @_hugetlb_hwpoison: Do not use directly, call raw_hwp_list_head().
+ * @hugetlb_hwpoison: List of pages with hwpoison.
* @_deferred_list: Folios to be split under memory pressure.
* @_unused_slab_obj_exts: Placeholder to match obj_exts in struct slab.
*
@@ -499,7 +499,7 @@ struct folio {
void *_hugetlb_subpool;
void *_hugetlb_cgroup;
void *_hugetlb_cgroup_rsvd;
- void *_hugetlb_hwpoison;
+ struct llist_head hugetlb_hwpoison;
/* private: the union with struct page is transitional */
};
struct page __page_3;
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 3615ced0a11b..18691c905ac9 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1810,21 +1810,15 @@ EXPORT_SYMBOL_GPL(mf_dax_kill_procs);
/*
* Struct raw_hwp_page represents information about "raw error page",
- * constructing singly linked list from ->_hugetlb_hwpoison field of folio.
+ * constructing singly linked list from folio->hugetlb_hwpoison field.
*/
struct raw_hwp_page {
struct llist_node node;
struct page *page;
};
-static inline struct llist_head *raw_hwp_list_head(struct folio *folio)
-{
- return (struct llist_head *)&folio->_hugetlb_hwpoison;
-}
-
bool is_raw_hwpoison_page_in_hugepage(struct page *page)
{
- struct llist_head *raw_hwp_head;
struct raw_hwp_page *p;
struct folio *folio = page_folio(page);
bool ret = false;
@@ -1844,8 +1838,7 @@ bool is_raw_hwpoison_page_in_hugepage(struct page *page)
mutex_lock(&mf_mutex);
- raw_hwp_head = raw_hwp_list_head(folio);
- llist_for_each_entry(p, raw_hwp_head->first, node) {
+ llist_for_each_entry(p, folio->hugetlb_hwpoison.first, node) {
if (page == p->page) {
ret = true;
break;
@@ -1863,7 +1856,7 @@ static unsigned long __folio_free_raw_hwp(struct folio *folio, bool move_flag)
struct raw_hwp_page *p, *next;
unsigned long count = 0;
- head = llist_del_all(raw_hwp_list_head(folio));
+ head = llist_del_all(&folio->hugetlb_hwpoison);
llist_for_each_entry_safe(p, next, head, node) {
if (move_flag)
SetPageHWPoison(p->page);
@@ -1887,7 +1880,6 @@ static unsigned long __folio_free_raw_hwp(struct folio *folio, bool move_flag)
*/
static int hugetlb_update_hwpoison(struct folio *folio, struct page *page)
{
- struct llist_head *head;
struct raw_hwp_page *raw_hwp;
struct raw_hwp_page *p;
int ret = folio_test_set_hwpoison(folio) ? MF_HUGETLB_FOLIO_PRE_POISONED : 0;
@@ -1899,8 +1891,7 @@ static int hugetlb_update_hwpoison(struct folio *folio, struct page *page)
*/
if (folio_test_hugetlb_raw_hwp_unreliable(folio))
return MF_HUGETLB_FOLIO_PRE_POISONED;
- head = raw_hwp_list_head(folio);
- llist_for_each_entry(p, head->first, node) {
+ llist_for_each_entry(p, folio->hugetlb_hwpoison.first, node) {
if (p->page == page)
return MF_HUGETLB_PAGE_PRE_POISONED;
}
@@ -1908,7 +1899,7 @@ static int hugetlb_update_hwpoison(struct folio *folio, struct page *page)
raw_hwp = kmalloc_obj(struct raw_hwp_page, GFP_ATOMIC);
if (raw_hwp) {
raw_hwp->page = page;
- llist_add(&raw_hwp->node, head);
+ llist_add(&raw_hwp->node, &folio->hugetlb_hwpoison);
} else {
/*
* Failed to save raw error info. We no longer trace all
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 07/14] hugetlb: Use the has_hwpoisoned flag
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (5 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 06/14] memory-failure: Remove raw_hwp_list_head() Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 13:21 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 08/14] mm: Remove locking mf_mutex in is_raw_hwpoison_page_in_hugepage() Matthew Wilcox (Oracle)
` (6 subsequent siblings)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
Other large folios use the has_hwpoisoned flag. Convert hugetlb to match.
This will help us use the per-page hwpoison flag in the future.
Also introduce a folio_test_huge_poison(). This has exactly the same
meaning as folio_test_has_hwpoisoned() but can be used when we don't have
a reference to the folio containing the page. folio_test_has_hwpoisoned()
can race with folio splitting / reallocation and trip the assertions
in const_folio_flags().
This closes a gap where a page in a previously-poisoned hugetlb folio
could be observed to not have the hwpoison bit set.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/Kconfig | 2 +-
fs/hugetlbfs/inode.c | 2 +-
include/linux/page-flags.h | 42 +++++++++++++++++++++++------
mm/Kconfig | 6 ++++-
mm/hugetlb.c | 14 +++++-----
mm/memory-failure.c | 55 +++++++++++++++++++++++++++-----------
6 files changed, 87 insertions(+), 34 deletions(-)
diff --git a/fs/Kconfig b/fs/Kconfig
index cf6ae64776e6..eddac4ed214b 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -272,7 +272,7 @@ endif # HUGETLBFS
config HUGETLB_PAGE
def_bool HUGETLBFS
- select XARRAY_MULTI
+ select LARGE_FOLIO
config HUGETLB_PAGE_OPTIMIZE_VMEMMAP
def_bool HUGETLB_PAGE
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 216e1a0dd0b2..fbac554886c3 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -258,7 +258,7 @@ static ssize_t hugetlbfs_read_iter(struct kiocb *iocb, struct iov_iter *to)
} else {
folio_unlock(folio);
- if (!folio_test_hwpoison(folio))
+ if (!folio_test_has_hwpoisoned(folio))
want = nr;
else {
/*
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 4185a03a45cf..7c4652dd517a 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -893,14 +893,19 @@ static inline int PageTransCompound(const struct page *page)
TESTPAGEFLAG_FALSE(TransCompound, transcompound)
#endif
-#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_TRANSPARENT_HUGEPAGE)
+#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_LARGE_FOLIO)
/*
- * PageHasHWPoisoned indicates that at least one subpage is hwpoisoned in the
- * compound page.
+ * folio_has_hwpoisoned indicates that at least one page is hwpoisoned in the
+ * folio. That page will usually also have the HWPoison flag set, but this
+ * is not possible for folios which have HVO (see memory-failure for the
+ * scheme used in that case). You probably don't want to call this directly;
+ * use folio_has_hwpoisoned_page() instead.
*
* This flag is set by hwpoison handler. Cleared by THP split or free page.
*/
FOLIO_FLAG(has_hwpoisoned, FOLIO_SECOND_PAGE)
+FOLIO_TEST_SET_FLAG(has_hwpoisoned, FOLIO_SECOND_PAGE)
+FOLIO_TEST_CLEAR_FLAG(has_hwpoisoned, FOLIO_SECOND_PAGE)
#else
FOLIO_FLAG_FALSE(has_hwpoisoned)
#endif
@@ -1041,8 +1046,25 @@ PAGE_TYPE_OPS(Slab, slab, slab)
#ifdef CONFIG_HUGETLB_PAGE
FOLIO_TYPE_OPS(hugetlb, hugetlb)
+
+static inline bool folio_test_huge_poison(const struct folio *folio)
+{
+ return (READ_ONCE(folio->page.page_type) >> 23) ==
+ ((PGTY_hugetlb << 1) | 1);
+}
+
+static inline void folio_set_huge_poison(struct folio *folio)
+{
+ folio->page.page_type |= (1 << 23);
+}
+
+static inline void folio_clear_huge_poison(struct folio *folio)
+{
+ folio->page.page_type &= ~(1 << 23);
+}
#else
FOLIO_TEST_FLAG_FALSE(hugetlb)
+FOLIO_TEST_FLAG_FALSE(huge_poison)
#endif
PAGE_TYPE_OPS(Zsmalloc, zsmalloc, zsmalloc)
@@ -1069,9 +1091,10 @@ static inline bool PageHuge(const struct page *page)
}
/*
- * Check if a page is currently marked HWPoisoned. Note that this check is
- * best effort only and inherently racy: there is no way to synchronize with
- * failing hardware.
+ * Check if a page is currently marked HWPoisoned. This check is best
+ * effort only and inherently racy: there is no way to synchronize with
+ * failing hardware. The caller may not have a refcount on the folio
+ * containing the page, so we must be careful to not trip any assertions.
*/
static inline bool is_page_hwpoison(const struct page *page)
{
@@ -1080,12 +1103,15 @@ static inline bool is_page_hwpoison(const struct page *page)
if (PageHWPoison(page))
return true;
folio = page_folio(page);
- return folio_test_hugetlb(folio) && PageHWPoison(&folio->page);
+ if (folio_test_huge_poison(folio))
+ return true;
+ /* In case we raced with hugetlb transferring flags */
+ return PageHWPoison(page);
}
static inline bool folio_has_hwpoisoned_page(const struct folio *folio)
{
- return folio_test_hwpoison(folio) ||
+ return PageHWPoison(&folio->page) ||
(folio_test_large(folio) && folio_test_has_hwpoisoned(folio));
}
diff --git a/mm/Kconfig b/mm/Kconfig
index 9e0ca4824905..e666dd14ca0c 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -843,11 +843,15 @@ config PERSISTENT_HUGE_ZERO_FOLIO
config MM_ID
def_bool n
+config LARGE_FOLIO
+ def_bool n
+ select XARRAY_MULTI
+
menuconfig TRANSPARENT_HUGEPAGE
bool "Transparent Hugepage Support"
depends on HAVE_ARCH_TRANSPARENT_HUGEPAGE && !PREEMPT_RT
select COMPACTION
- select XARRAY_MULTI
+ select LARGE_FOLIO
select MM_ID
help
Transparent Hugepages allows the kernel to use huge pages and
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index cc8d98399913..aaac6e2af4c2 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1255,7 +1255,7 @@ static struct folio *dequeue_hugetlb_folio_node_exact(struct hstate *h,
if (pin && !folio_is_longterm_pinnable(folio))
continue;
- if (folio_test_hwpoison(folio))
+ if (folio_test_has_hwpoisoned(folio))
continue;
if (is_migrate_isolate_page(&folio->page))
@@ -1498,10 +1498,10 @@ static void __update_and_free_hugetlb_folio(struct hstate *h,
}
/*
- * Move PageHWPoison flag from head page to the raw error pages,
- * which makes any healthy subpages reusable.
+ * Move HWPoison flag to each error page
+ * which makes any healthy pages reusable.
*/
- if (unlikely(folio_test_hwpoison(folio)))
+ if (unlikely(folio_test_has_hwpoisoned(folio)))
folio_clear_hugetlb_hwpoison(folio);
VM_BUG_ON_FOLIO(folio_ref_count(folio), folio);
@@ -3998,7 +3998,7 @@ long demote_pool_huge_page(struct hstate *src, nodemask_t *nodes_allowed,
struct folio *folio, *next;
list_for_each_entry_safe(folio, next, &src->hugepage_freelists[node], lru) {
- if (folio_test_hwpoison(folio))
+ if (folio_test_has_hwpoisoned(folio))
continue;
remove_hugetlb_folio(src, folio, false);
@@ -5809,7 +5809,7 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
* don't have hwpoisoned swap entry for errored virtual address.
* So we need to block hugepage fault by PG_hwpoison bit check.
*/
- if (unlikely(folio_test_hwpoison(folio))) {
+ if (unlikely(folio_test_has_hwpoisoned(folio))) {
ret = VM_FAULT_HWPOISON_LARGE |
VM_FAULT_SET_HINDEX(hstate_index(h));
goto backout_unlocked;
@@ -6318,7 +6318,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
ptl = huge_pte_lock(h, dst_mm, dst_pte);
ret = -EIO;
- if (folio_test_hwpoison(folio))
+ if (folio_test_has_hwpoisoned(folio))
goto out_release_unlock;
ret = -EEXIST;
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 18691c905ac9..4200deab2c3c 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1823,7 +1823,7 @@ bool is_raw_hwpoison_page_in_hugepage(struct page *page)
struct folio *folio = page_folio(page);
bool ret = false;
- if (!folio_test_hwpoison(folio))
+ if (!folio_test_has_hwpoisoned(folio))
return false;
if (!folio_test_hugetlb(folio))
@@ -1874,6 +1874,23 @@ static unsigned long __folio_free_raw_hwp(struct folio *folio, bool move_flag)
#define MF_HUGETLB_FOLIO_PRE_POISONED 3 /* folio already poisoned */
#define MF_HUGETLB_PAGE_PRE_POISONED 4 /* exact page already poisoned */
#define MF_HUGETLB_RETRY 5 /* hugepage is busy, retry */
+
+static inline int hugetlb_set_poison(struct folio *folio)
+{
+ if (folio_test_set_has_hwpoisoned(folio))
+ return MF_HUGETLB_FOLIO_PRE_POISONED;
+ folio_set_huge_poison(folio);
+ return 0;
+}
+
+static inline int hugetlb_clear_poison(struct folio *folio)
+{
+ if (!folio_test_clear_has_hwpoisoned(folio))
+ return -EBUSY;
+ folio_clear_huge_poison(folio);
+ return 0;
+}
+
/*
* Set hugetlb folio as hwpoisoned, update folio private raw hwpoison list
* to keep track of the poisoned pages.
@@ -1882,12 +1899,12 @@ static int hugetlb_update_hwpoison(struct folio *folio, struct page *page)
{
struct raw_hwp_page *raw_hwp;
struct raw_hwp_page *p;
- int ret = folio_test_set_hwpoison(folio) ? MF_HUGETLB_FOLIO_PRE_POISONED : 0;
+ int ret = hugetlb_set_poison(folio);
/*
* Once the hwpoison hugepage has lost reliable raw error info,
- * there is little meaning to keep additional error info precisely,
- * so skip to add additional raw error info.
+ * there is no point in keeping additional error info precisely,
+ * so skip adding additional raw error info.
*/
if (folio_test_hugetlb_raw_hwp_unreliable(folio))
return MF_HUGETLB_FOLIO_PRE_POISONED;
@@ -1941,8 +1958,8 @@ void folio_clear_hugetlb_hwpoison(struct folio *folio)
return;
if (folio_test_hugetlb_vmemmap_optimized(folio))
return;
- folio_clear_hwpoison(folio);
folio_free_raw_hwp(folio, true);
+ folio_clear_has_hwpoisoned(folio);
}
static int get_huge_page_for_hwpoison(unsigned long pfn, int flags,
@@ -2095,6 +2112,11 @@ static inline unsigned long folio_free_raw_hwp(struct folio *folio, bool flag)
{
return 0;
}
+
+static inline int hugetlb_clear_poison(struct folio *folio)
+{
+ return 0;
+}
#endif /* CONFIG_HUGETLB_PAGE */
/* Drop the extra refcount in case we come from madvise() */
@@ -2722,8 +2744,8 @@ int unpoison_memory(unsigned long pfn)
count = folio_free_raw_hwp(folio, false);
if (count == 0)
goto unlock_mutex;
+ ret = hugetlb_clear_poison(folio);
}
- ret = folio_test_clear_hwpoison(folio) ? 0 : -EBUSY;
} else if (ghp < 0) {
if (ghp == -EHWPOISON) {
ret = put_page_back_buddy(p) ? 0 : -EBUSY;
@@ -2732,17 +2754,18 @@ int unpoison_memory(unsigned long pfn)
unpoison_pr_info("%#lx: failed to grab page\n",
pfn, &unpoison_rs);
}
- } else {
- if (folio_test_hugetlb(folio)) {
- huge = true;
- count = folio_free_raw_hwp(folio, false);
- if (count == 0) {
- folio_put(folio);
- goto unlock_mutex;
- }
- p = &folio->page;
+ } else if (folio_test_hugetlb(folio)) {
+ huge = true;
+ count = folio_free_raw_hwp(folio, false);
+ if (count == 0) {
+ folio_put(folio);
+ goto unlock_mutex;
}
-
+ folio_put(folio);
+ ret = hugetlb_clear_poison(folio);
+ if (!ret)
+ folio_put(folio);
+ } else {
folio_put(folio);
if (TestClearPageHWPoison(p)) {
folio_put(folio);
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 08/14] mm: Remove locking mf_mutex in is_raw_hwpoison_page_in_hugepage()
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (6 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 07/14] hugetlb: Use the has_hwpoisoned flag Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 13:41 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 09/14] mm: Check individual hugetlb pages for poison Matthew Wilcox (Oracle)
` (5 subsequent siblings)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
We don't need the mutex to check the list, as long as we use READ_ONCE()
to access the list head. Updates only add to the head of the list, so
we can miss new entries, but not old ones. It's perfectly acceptable
to miss new entries as callers cannot tell which order the add and check
executed in anyway.
Rename is_raw_hwpoison_page_in_hugepage() to hugetlb_page_hwpoison()
and make it take the folio (since the callers naturally have the folio).
Also remove the handling of non-hugetlb folios and make the arguments
const.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/hugetlbfs/inode.c | 4 ++--
include/linux/hugetlb.h | 5 +----
mm/memory-failure.c | 31 ++++++++++++-------------------
3 files changed, 15 insertions(+), 25 deletions(-)
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index fbac554886c3..dbca3f713bbf 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -198,7 +198,7 @@ static size_t adjust_range_hwpoison(struct folio *folio, size_t offset,
struct page *page = folio_page(folio, offset / PAGE_SIZE);
size_t safe_bytes;
- if (is_raw_hwpoison_page_in_hugepage(page))
+ if (hugetlb_page_hwpoison(folio, page))
return 0;
/* Safe to read the remaining bytes in this page. */
safe_bytes = PAGE_SIZE - (offset % PAGE_SIZE);
@@ -206,7 +206,7 @@ static size_t adjust_range_hwpoison(struct folio *folio, size_t offset,
/* Check each remaining page as long as we are not done yet. */
for (; safe_bytes < bytes; safe_bytes += PAGE_SIZE, page++)
- if (is_raw_hwpoison_page_in_hugepage(page))
+ if (hugetlb_page_hwpoison(folio, page))
break;
return min(safe_bytes, bytes);
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 18661a7cdb37..325f0404a7eb 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -1069,10 +1069,7 @@ void hugetlb_register_node(struct node *node);
void hugetlb_unregister_node(struct node *node);
#endif
-/*
- * Check if a given raw @page in a hugepage is HWPOISON.
- */
-bool is_raw_hwpoison_page_in_hugepage(struct page *page);
+bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page);
static inline unsigned long huge_page_mask_align(struct file *file)
{
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 4200deab2c3c..5076ee5d4788 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1817,37 +1817,30 @@ struct raw_hwp_page {
struct page *page;
};
-bool is_raw_hwpoison_page_in_hugepage(struct page *page)
+/*
+ * Check if a given @page in a hugetlb folio is HWPOISON.
+ */
+bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page)
{
- struct raw_hwp_page *p;
- struct folio *folio = page_folio(page);
- bool ret = false;
+ const struct llist_head *list = &folio->hugetlb_hwpoison;
+ const struct raw_hwp_page *p;
if (!folio_test_has_hwpoisoned(folio))
return false;
- if (!folio_test_hugetlb(folio))
- return PageHWPoison(page);
-
/*
- * When RawHwpUnreliable is set, kernel lost track of which subpages
- * are HWPOISON. So return as if ALL subpages are HWPOISONed.
+ * When RawHwpUnreliable is set, kernel lost track of which pages
+ * are HWPOISON. So return as if ALL pages are HWPOISONed.
*/
if (folio_test_hugetlb_raw_hwp_unreliable(folio))
return true;
- mutex_lock(&mf_mutex);
-
- llist_for_each_entry(p, folio->hugetlb_hwpoison.first, node) {
- if (page == p->page) {
- ret = true;
- break;
- }
+ llist_for_each_entry(p, READ_ONCE(list->first), node) {
+ if (page == p->page)
+ return true;
}
- mutex_unlock(&mf_mutex);
-
- return ret;
+ return false;
}
static unsigned long __folio_free_raw_hwp(struct folio *folio, bool move_flag)
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 09/14] mm: Check individual hugetlb pages for poison
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (7 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 08/14] mm: Remove locking mf_mutex in is_raw_hwpoison_page_in_hugepage() Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 14:00 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 10/14] filemap: Add hwpoison handling to filemap_read() Matthew Wilcox (Oracle)
` (4 subsequent siblings)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
So far, is_page_hwpoison() has returned true for the entire poisoned
hugetlb folio, but we can usually allow access to the pages which
aren't poisoned. It's slightly tricky due to not having a reference
to the folio, but the hugetlb lock can save us here.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
include/linux/page-flags.h | 4 +++-
mm/memory-failure.c | 43 +++++++++++++++++++++++++++++++-------
2 files changed, 39 insertions(+), 8 deletions(-)
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 7c4652dd517a..9cde74320e48 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -1090,6 +1090,8 @@ static inline bool PageHuge(const struct page *page)
return folio_test_hugetlb(page_folio(page));
}
+bool hugetlb_unref_page_hwpoison(const struct page *page);
+
/*
* Check if a page is currently marked HWPoisoned. This check is best
* effort only and inherently racy: there is no way to synchronize with
@@ -1104,7 +1106,7 @@ static inline bool is_page_hwpoison(const struct page *page)
return true;
folio = page_folio(page);
if (folio_test_huge_poison(folio))
- return true;
+ return hugetlb_unref_page_hwpoison(page);
/* In case we raced with hugetlb transferring flags */
return PageHWPoison(page);
}
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 5076ee5d4788..73ebbc935661 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1817,17 +1817,12 @@ struct raw_hwp_page {
struct page *page;
};
-/*
- * Check if a given @page in a hugetlb folio is HWPOISON.
- */
-bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page)
+static bool precise_page_poisoned(const struct folio *folio,
+ const struct page *page)
{
const struct llist_head *list = &folio->hugetlb_hwpoison;
const struct raw_hwp_page *p;
- if (!folio_test_has_hwpoisoned(folio))
- return false;
-
/*
* When RawHwpUnreliable is set, kernel lost track of which pages
* are HWPOISON. So return as if ALL pages are HWPOISONed.
@@ -1843,6 +1838,40 @@ bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page)
return false;
}
+/*
+ * Check if a given @page in a hugetlb folio is HWPOISON.
+ */
+bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page)
+{
+ if (!folio_test_has_hwpoisoned(folio))
+ return false;
+
+ return precise_page_poisoned(folio, page);
+}
+
+/*
+ * We have no reference on the folio containing this page.
+ * The hugetlb_lock keeps hugetlb folios from being freed.
+ */
+bool hugetlb_unref_page_hwpoison(const struct page *page)
+{
+ const struct folio *folio;
+ unsigned long flags;
+ bool ret;
+
+ spin_lock_irqsave(&hugetlb_lock, flags);
+ folio = page_folio(page);
+ if (!folio_test_huge_poison(folio)) {
+ ret = PageHWPoison(page);
+ goto unlock;
+ }
+
+ ret = precise_page_poisoned(folio, page);
+unlock:
+ spin_unlock_irqrestore(&hugetlb_lock, flags);
+ return ret;
+}
+
static unsigned long __folio_free_raw_hwp(struct folio *folio, bool move_flag)
{
struct llist_node *head;
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 10/14] filemap: Add hwpoison handling to filemap_read()
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (8 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 09/14] mm: Check individual hugetlb pages for poison Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 16:06 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 11/14] filemap: Remove checks in mapping_set_folio_order_range() Matthew Wilcox (Oracle)
` (3 subsequent siblings)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan, Matthew Wilcox
From: Jane Chu <jane.chu@oracle.com>
Add hwpoison handling to filemap_read() such that .read_iter() could
make best effort copying data out of clean pages without risking
MCE in case page cache contains HWpoison.
Signed-off-by: Jane Chu <jane.chu@oracle.com>
Co-developed-by: Matthew Wilcox <willy@infradead.org>
Signed-off-by: Matthew Wilcox <willy@infradead.org>
---
include/linux/hugetlb.h | 2 --
include/linux/page-flags.h | 11 +++++++++++
mm/filemap.c | 33 +++++++++++++++++++++++++++++++--
3 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 325f0404a7eb..1878642e75ec 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -1069,8 +1069,6 @@ void hugetlb_register_node(struct node *node);
void hugetlb_unregister_node(struct node *node);
#endif
-bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page);
-
static inline unsigned long huge_page_mask_align(struct file *file)
{
return PAGE_MASK & ~huge_page_mask(hstate_file(file));
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 9cde74320e48..b146c5228741 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -1090,6 +1090,7 @@ static inline bool PageHuge(const struct page *page)
return folio_test_hugetlb(page_folio(page));
}
+bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page);
bool hugetlb_unref_page_hwpoison(const struct page *page);
/*
@@ -1111,6 +1112,16 @@ static inline bool is_page_hwpoison(const struct page *page)
return PageHWPoison(page);
}
+static inline bool is_ref_page_hwpoison(const struct folio *folio,
+ const struct page *page)
+{
+ if (PageHWPoison(page))
+ return true;
+ if (folio_test_hugetlb(folio))
+ return hugetlb_page_hwpoison(folio, page);
+ return false;
+}
+
static inline bool folio_has_hwpoisoned_page(const struct folio *folio)
{
return PageHWPoison(&folio->page) ||
diff --git a/mm/filemap.c b/mm/filemap.c
index 58eb9d240643..26a5f18121f9 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -2483,6 +2483,8 @@ static void filemap_get_read_batch(struct address_space *mapping,
if (!folio_batch_add(fbatch, folio))
break;
+ if (folio_has_hwpoisoned_page(folio))
+ break;
if (!folio_test_uptodate(folio))
break;
if (folio_test_readahead(folio))
@@ -2749,6 +2751,29 @@ static inline bool pos_same_folio(loff_t pos1, loff_t pos2, struct folio *folio)
return (pos1 >> shift == pos2 >> shift);
}
+static size_t adjust_range_hwpoison(const struct folio *folio, size_t offset,
+ size_t bytes)
+{
+ const struct page *page = folio_page(folio, offset / PAGE_SIZE);
+ size_t safe_bytes;
+
+ if (!folio_has_hwpoisoned_page(folio))
+ return bytes;
+ if (is_ref_page_hwpoison(folio, page))
+ return 0;
+
+ /* Safe to read the remaining bytes in this page. */
+ safe_bytes = PAGE_SIZE - (offset % PAGE_SIZE);
+ page++;
+
+ /* Check each remaining page as long as we are not done yet. */
+ for (; safe_bytes < bytes; safe_bytes += PAGE_SIZE, page++)
+ if (is_ref_page_hwpoison(folio, page))
+ break;
+
+ return min(safe_bytes, bytes);
+}
+
static void filemap_end_dropbehind_read(struct folio *folio)
{
if (!folio_test_dropbehind(folio))
@@ -2862,14 +2887,18 @@ ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,
if (writably_mapped)
flush_dcache_folio(folio);
- copied = copy_folio_to_iter(folio, offset, bytes, iter);
+ copied = adjust_range_hwpoison(folio, offset, bytes);
+ if (copied < bytes)
+ error = -EIO;
+ copied = copy_folio_to_iter(folio, offset, copied, iter);
already_read += copied;
iocb->ki_pos += copied;
last_pos = iocb->ki_pos;
if (copied < bytes) {
- error = -EFAULT;
+ if (!error)
+ error = -EFAULT;
break;
}
}
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 11/14] filemap: Remove checks in mapping_set_folio_order_range()
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (9 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 10/14] filemap: Add hwpoison handling to filemap_read() Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 16:08 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 12/14] hugetlb: Set mapping folio order Matthew Wilcox (Oracle)
` (2 subsequent siblings)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
These checks make it impossible to use mapping_set_folio_order_range()
in hugetlb. They add very little value and can simply be removed.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
include/linux/pagemap.h | 34 ++++++++++++++--------------------
1 file changed, 14 insertions(+), 20 deletions(-)
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 2c3718d592d6..436987f47aed 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -373,7 +373,7 @@ static inline void mapping_set_gfp_mask(struct address_space *m, gfp_t mask)
* assumptions about maximum order if THP are disabled, but 8 seems like
* a good order (that's 1MB if you're using 4kB pages)
*/
-#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+#ifdef CONFIG_FILEMAP_LARGE_FOLIO
#define PREFERRED_MAX_PAGECACHE_ORDER HPAGE_PMD_ORDER
#else
#define PREFERRED_MAX_PAGECACHE_ORDER 8
@@ -394,7 +394,7 @@ static inline void mapping_set_gfp_mask(struct address_space *m, gfp_t mask)
*/
static inline size_t mapping_max_folio_size_supported(void)
{
- if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+ if (IS_ENABLED(CONFIG_FILEMAP_LARGE_FOLIO))
return 1U << (PAGE_SHIFT + MAX_PAGECACHE_ORDER);
return PAGE_SIZE;
}
@@ -405,29 +405,23 @@ static inline size_t mapping_max_folio_size_supported(void)
* @min: Minimum folio order (between 0-MAX_PAGECACHE_ORDER inclusive).
* @max: Maximum folio order (between @min-MAX_PAGECACHE_ORDER inclusive).
*
- * The filesystem should call this function in its inode constructor to
- * indicate which base size (min) and maximum size (max) of folio the VFS
- * can use to cache the contents of the file. This should only be used
- * if the filesystem needs special handling of folio sizes (ie there is
- * something the core cannot know).
+ * The filesystem should call this function in its inode constructor
+ * to indicate which size folios can be used to cache the contents of
+ * the inode. This should only be used if the filesystem needs special
+ * handling of folio sizes (ie there is something the core cannot know).
* Do not tune it based on, eg, i_size.
*
+ * hugetlb calls this with orders larger than MAX_PAGECACHE_ORDER.
+ * Normal filesystems should not do this.
+ *
* Context: This should not be called while the inode is active as it
* is non-atomic.
*/
static inline void mapping_set_folio_order_range(struct address_space *mapping,
- unsigned int min,
- unsigned int max)
+ unsigned int min, unsigned int max)
{
- if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+ if (!IS_ENABLED(CONFIG_FILEMAP_LARGE_FOLIO))
return;
-
- if (min > MAX_PAGECACHE_ORDER)
- min = MAX_PAGECACHE_ORDER;
-
- if (max > MAX_PAGECACHE_ORDER)
- max = MAX_PAGECACHE_ORDER;
-
if (max < min)
max = min;
@@ -460,7 +454,7 @@ static inline void mapping_set_large_folios(struct address_space *mapping)
static inline unsigned int
mapping_max_folio_order(const struct address_space *mapping)
{
- if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+ if (!IS_ENABLED(CONFIG_FILEMAP_LARGE_FOLIO))
return 0;
return (mapping->flags & AS_FOLIO_ORDER_MAX_MASK) >> AS_FOLIO_ORDER_MAX;
}
@@ -468,7 +462,7 @@ mapping_max_folio_order(const struct address_space *mapping)
static inline unsigned int
mapping_min_folio_order(const struct address_space *mapping)
{
- if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+ if (!IS_ENABLED(CONFIG_FILEMAP_LARGE_FOLIO))
return 0;
return (mapping->flags & AS_FOLIO_ORDER_MIN_MASK) >> AS_FOLIO_ORDER_MIN;
}
@@ -524,7 +518,7 @@ static inline bool mapping_large_folio_support(const struct address_space *mappi
*
* Return: True if PMD-sized folios are supported, otherwise false.
*/
-#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+#ifdef CONFIG_FILEMAP_LARGE_FOLIO
static inline bool mapping_pmd_folio_support(const struct address_space *mapping)
{
/* AS_FOLIO_ORDER is only reasonable for pagecache folios */
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 12/14] hugetlb: Set mapping folio order
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (10 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 11/14] filemap: Remove checks in mapping_set_folio_order_range() Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 16:16 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 13/14] filemap: Add support for authoritative mappings Matthew Wilcox (Oracle)
2026-07-23 14:30 ` [PATCH v4 14/14] hugetlb: replace hugetlbfs_read_iter() with generic_file_read_iter() Matthew Wilcox (Oracle)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
hugetlbfs currently uses an fs-specific way to determine the size of
the folios in its page cache. Thanks to the support of block sizes
larger than page sizes, we now support this as part of the page cache.
It's somewhat more efficient as we have this information directly in the
mapping rather than going from inode->i_sb->s_fs_info. We can convert
the rest of the hugetlb code to use this at our leisure; this is needed
now so that we can use filemap_get_pages() unmodified.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/hugetlbfs/inode.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index dbca3f713bbf..8d15413a67ee 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -883,6 +883,16 @@ static struct inode *hugetlbfs_get_root(struct super_block *sb,
return inode;
}
+static void hugetlbfs_init_regular_inode(struct inode *inode)
+{
+ struct hstate *hstate = hstate_inode(inode);
+ unsigned int order = huge_page_order(hstate);
+
+ inode->i_op = &hugetlbfs_inode_operations;
+ inode->i_fop = &hugetlbfs_file_operations;
+ mapping_set_folio_order_range(inode->i_mapping, order, order);
+}
+
/*
* Hugetlbfs is not reclaimable; therefore its i_mmap_rwsem will never
* be taken from reclaim -- unlike regular filesystems. This needs an
@@ -926,8 +936,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb,
init_special_inode(inode, mode, dev);
break;
case S_IFREG:
- inode->i_op = &hugetlbfs_inode_operations;
- inode->i_fop = &hugetlbfs_file_operations;
+ hugetlbfs_init_regular_inode(inode);
break;
case S_IFDIR:
inode->i_op = &hugetlbfs_dir_inode_operations;
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 13/14] filemap: Add support for authoritative mappings
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (11 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 12/14] hugetlb: Set mapping folio order Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
2026-07-24 16:15 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 14/14] hugetlb: replace hugetlbfs_read_iter() with generic_file_read_iter() Matthew Wilcox (Oracle)
13 siblings, 1 reply; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Matthew Wilcox (Oracle), Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
An authoritative mapping knows about all the folios in the mapping.
If read() finds a missing folio, there's no reason to allocate one and
try to read it because we know it's a zero region of the file. We can
just call iov_iter_zero() instead.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
include/linux/pagemap.h | 11 +++++++++++
mm/filemap.c | 18 ++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 436987f47aed..6fdee5b03621 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -210,6 +210,7 @@ enum mapping_flags {
AS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM = 9,
AS_KERNEL_FILE = 10, /* mapping for a fake kernel file that shouldn't
account usage to user cgroups */
+ AS_AUTHORITATIVE = 11, /* If we miss in the page cache, it's a hole */
/* Bits 16-25 are used for FOLIO_ORDER */
AS_FOLIO_ORDER_BITS = 5,
AS_FOLIO_ORDER_MIN = 16,
@@ -345,6 +346,16 @@ static inline bool mapping_writeback_may_deadlock_on_reclaim(const struct addres
return test_bit(AS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM, &mapping->flags);
}
+static inline void mapping_set_authoritative(struct address_space *mapping)
+{
+ set_bit(AS_AUTHORITATIVE, &mapping->flags);
+}
+
+static inline bool mapping_is_authoritative(const struct address_space *mapping)
+{
+ return test_bit(AS_AUTHORITATIVE, &mapping->flags);
+}
+
static inline gfp_t mapping_gfp_mask(const struct address_space *mapping)
{
return mapping->gfp_mask;
diff --git a/mm/filemap.c b/mm/filemap.c
index 26a5f18121f9..5a8cc20e624e 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -2697,6 +2697,8 @@ static int filemap_get_pages(struct kiocb *iocb, size_t count,
if (!folio_batch_count(fbatch)) {
DEFINE_READAHEAD(ractl, filp, &filp->f_ra, mapping, index);
+ if (mapping_is_authoritative(mapping))
+ return 0;
if (iocb->ki_flags & IOCB_NOIO)
return -EAGAIN;
if (iocb->ki_flags & IOCB_NOWAIT)
@@ -2853,6 +2855,22 @@ ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,
goto put_folios;
end_offset = min_t(loff_t, isize, iocb->ki_pos + iter->count);
+ if (!folio_batch_count(&fbatch)) {
+ size_t fsize = mapping_min_folio_nrbytes(mapping);
+ size_t offset = iocb->ki_pos & (fsize - 1);
+ size_t bytes = min_t(loff_t, end_offset - iocb->ki_pos,
+ fsize - offset);
+ size_t copied = iov_iter_zero(bytes, iter);
+
+ already_read += copied;
+ iocb->ki_pos += copied;
+ last_pos = iocb->ki_pos;
+
+ if (copied < bytes)
+ error = -EFAULT;
+ continue;
+ }
+
/*
* Once we start copying data, we don't want to be touching any
* cachelines that might be contended:
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v4 14/14] hugetlb: replace hugetlbfs_read_iter() with generic_file_read_iter()
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
` (12 preceding siblings ...)
2026-07-23 14:30 ` [PATCH v4 13/14] filemap: Add support for authoritative mappings Matthew Wilcox (Oracle)
@ 2026-07-23 14:30 ` Matthew Wilcox (Oracle)
13 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-07-23 14:30 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan, Matthew Wilcox (Oracle)
From: Jane Chu <jane.chu@oracle.com>
generic_file_read_iter() now handles hugetlb poison, hugetlb page
size and missing folios correctly, so we no longer need a separate
hugetlbfs_read_iter().
Signed-off-by: Jane Chu <jane.chu@oracle.com>
Co-developed-by: Matthew Wilcox (Oracle) <willy@infradead.org>
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/hugetlbfs/inode.c | 110 +------------------------------------------
1 file changed, 2 insertions(+), 108 deletions(-)
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 8d15413a67ee..cef7e61b827d 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -187,113 +187,6 @@ hugetlb_get_unmapped_area(struct file *file, unsigned long addr,
return mm_get_unmapped_area_vmflags(file, addr0, len, pgoff, flags, 0);
}
-/*
- * Someone wants to read @bytes from a HWPOISON hugetlb @folio from @offset.
- * Returns the maximum number of bytes one can read without touching the 1st raw
- * HWPOISON page.
- */
-static size_t adjust_range_hwpoison(struct folio *folio, size_t offset,
- size_t bytes)
-{
- struct page *page = folio_page(folio, offset / PAGE_SIZE);
- size_t safe_bytes;
-
- if (hugetlb_page_hwpoison(folio, page))
- return 0;
- /* Safe to read the remaining bytes in this page. */
- safe_bytes = PAGE_SIZE - (offset % PAGE_SIZE);
- page++;
-
- /* Check each remaining page as long as we are not done yet. */
- for (; safe_bytes < bytes; safe_bytes += PAGE_SIZE, page++)
- if (hugetlb_page_hwpoison(folio, page))
- break;
-
- return min(safe_bytes, bytes);
-}
-
-/*
- * Support for read() - Find the page attached to f_mapping and copy out the
- * data. This provides functionality similar to filemap_read().
- */
-static ssize_t hugetlbfs_read_iter(struct kiocb *iocb, struct iov_iter *to)
-{
- struct file *file = iocb->ki_filp;
- struct hstate *h = hstate_file(file);
- struct address_space *mapping = file->f_mapping;
- struct inode *inode = mapping->host;
- unsigned long index = iocb->ki_pos >> huge_page_shift(h);
- unsigned long offset = iocb->ki_pos & ~huge_page_mask(h);
- unsigned long end_index;
- loff_t isize;
- ssize_t retval = 0;
-
- while (iov_iter_count(to)) {
- struct folio *folio;
- size_t nr, copied, want;
-
- /* nr is the maximum number of bytes to copy from this page */
- nr = huge_page_size(h);
- isize = i_size_read(inode);
- if (!isize)
- break;
- end_index = (isize - 1) >> huge_page_shift(h);
- if (index > end_index)
- break;
- if (index == end_index) {
- nr = ((isize - 1) & ~huge_page_mask(h)) + 1;
- if (nr <= offset)
- break;
- }
- nr = nr - offset;
-
- /* Find the folio */
- folio = filemap_lock_hugetlb_folio(h, mapping, index);
- if (IS_ERR(folio)) {
- /*
- * We have a HOLE, zero out the user-buffer for the
- * length of the hole or request.
- */
- copied = iov_iter_zero(nr, to);
- } else {
- folio_unlock(folio);
-
- if (!folio_test_has_hwpoisoned(folio))
- want = nr;
- else {
- /*
- * Adjust how many bytes safe to read without
- * touching the 1st raw HWPOISON page after
- * offset.
- */
- want = adjust_range_hwpoison(folio, offset, nr);
- if (want == 0) {
- folio_put(folio);
- retval = -EIO;
- break;
- }
- }
-
- /*
- * We have the folio, copy it to user space buffer.
- */
- copied = copy_folio_to_iter(folio, offset, want, to);
- folio_put(folio);
- }
- offset += copied;
- retval += copied;
- if (copied != nr && iov_iter_count(to)) {
- if (!retval)
- retval = -EFAULT;
- break;
- }
- index += offset >> huge_page_shift(h);
- offset &= ~huge_page_mask(h);
- }
- iocb->ki_pos = ((loff_t)index << huge_page_shift(h)) + offset;
- return retval;
-}
-
static int hugetlbfs_write_begin(const struct kiocb *iocb,
struct address_space *mapping,
loff_t pos, unsigned len,
@@ -891,6 +784,7 @@ static void hugetlbfs_init_regular_inode(struct inode *inode)
inode->i_op = &hugetlbfs_inode_operations;
inode->i_fop = &hugetlbfs_file_operations;
mapping_set_folio_order_range(inode->i_mapping, order, order);
+ mapping_set_authoritative(inode->i_mapping);
}
/*
@@ -1215,7 +1109,7 @@ static void init_once(void *foo)
}
static const struct file_operations hugetlbfs_file_operations = {
- .read_iter = hugetlbfs_read_iter,
+ .read_iter = generic_file_read_iter,
.mmap = hugetlbfs_file_mmap,
.fsync = noop_fsync,
.get_unmapped_area = hugetlb_get_unmapped_area,
--
2.47.3
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v4 01/14] memory-failure: Fix hardware poison check in unpoison_memory() again
2026-07-23 14:30 ` [PATCH v4 01/14] memory-failure: Fix hardware poison check in unpoison_memory() again Matthew Wilcox (Oracle)
@ 2026-07-24 12:32 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 12:32 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan, stable, Naoya Horiguchi
Sashiko feedback:
> --- Patch [1]: [PATCH v4 01/14] memory-failure: Fix hardware poison check in unpoison_memory() again ---
> commit 5a43cce770d0c716bb5c78ee2c21e1cb711e5331
> Author: Matthew Wilcox (Oracle) <willy@infradead.org>
>
> memory-failure: Fix hardware poison check in unpoison_memory() again
>
> This commit addresses a bug in unpoison_memory() for hugetlb folios. It uses
> is_page_hwpoison() instead of checking the head page, and correctly targets
> the precise page for clearing the HWPoison flag.
>
> > diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> > index 51508a55c4055..944e6e1d49719 100644
> > --- a/mm/memory-failure.c
> > +++ b/mm/memory-failure.c
> > @@ -2695,8 +2695,8 @@ int unpoison_memory(unsigned long pfn)
> > goto unlock_mutex;
> > }
> >
> > - if (!PageHWPoison(p)) {
> > - unpoison_pr_info("%#lx: page was already unpoisoned\n",
> > + if (!is_page_hwpoison(p)) {
> > + unpoison_pr_info("%#lx: page is not poisoned\n",
> > pfn, &unpoison_rs);
> > goto unlock_mutex;
> > }
>
> This isn't a bug introduced by this patch, but can the speculative folio
> access without stabilization cause a use-after-free and reference leak?
>
> Looking at unpoison_memory(), if the PFN belongs to a THP tail page and
> the THP is concurrently split, could the folio pointer refer to a stale
> head page?
>
> unpoison_memory() {
> ...
> folio = page_folio(p);
> ...
> ghp = get_hwpoison_page(p, MF_UNPOISON);
> ...
> folio_put(folio);
> }
>
> Since get_hwpoison_page() takes a reference on the new order-0 page, does
> calling folio_put() on the stale head page drop the wrong reference and leak
> the reference on the order-0 page?
Uh. I don't know. If it does, I didn't make this problem _worse_,
so I'm not going to address it.
> This is a pre-existing issue, but does unpoison_memory() clear the
> PG_hwpoison flag on the wrong page for unsplit THPs?
>
> For THP tail pages, if get_hwpoison_page() returns 0 because their
> _refcount is 0, does the code fall into the !ghp branch and call
> folio_test_clear_hwpoison(folio)?
>
> Because PF_HEAD redirects to the head page, this clears the flag on the
> head page. Since THPs set PG_hwpoison on the exact corrupted tail page
> (PF_ANY), does the tail page remain poisoned and fail to restore?
Hm. Maybe? This code probably needs to be more careful in general.
> [ ... ]
>
> > @@ -2748,6 +2748,7 @@ int unpoison_memory(unsigned long pfn)
> > folio_put(folio);
> > goto unlock_mutex;
> > }
> > + p = &folio->page;
> > }
> >
> > folio_put(folio);
>
> This is a pre-existing issue, but does unpoison_memory() fail to clear
> the per-folio PG_has_hwpoisoned flag when unpoisoning THPs?
>
> When successfully unpoisoning a THP head page (where get_hwpoison_page
> succeeds), TestClearPageHWPoison() clears the per-page PG_hwpoison flag.
>
> However, since the per-folio fast-indicator PG_has_hwpoisoned is never
> cleared, will folio_contain_hwpoisoned_page() continue to return true,
> causing the kernel to erroneously treat the unpoisoned THP as poisoned in
> future operations?
Yes, it does fail to clear the has_hwpoisoned flag. We can have more
than one HWPoison page in a folio, so we either have to leave it set,
or scan all pages in the folio to know that it's safe to clear.
Again, I'm not making this worse for THP in this series, so I'm
not addressing it. For hugetlb pages, later on I change to use the
has_hwpoisoned flag, and that does get cleared because there's no scan
of page bits involved, just a list of poisoned pages, and it's easy to
check when that's empty.
It's mostly moot because we always try to split large folios that aren't
THP, and that usually succeeds. So all the large folio has_hwpoisoned
paths are very undertested.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 02/14] memory-failure: Test the page is hwpoison before taking the mutex
2026-07-23 14:30 ` [PATCH v4 02/14] memory-failure: Test the page is hwpoison before taking the mutex Matthew Wilcox (Oracle)
@ 2026-07-24 12:34 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 12:34 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
Sashiko review:
> --- Patch [2]: [PATCH v4 02/14] memory-failure: Test the page is hwpoison before taking the mutex ---
> commit 1cca344401408425fcfd4ca129165f85229f9227
> Author: Matthew Wilcox (Oracle) <willy@infradead.org>
>
> memory-failure: Test the page is hwpoison before taking the mutex
>
> This patch moves the is_page_hwpoison() check in unpoison_memory() outside
> of mf_mutex. This avoids synchronizing with memory failure just to filter
> out pages which are not hwpoisoned.
>
> > diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> > index 944e6e1d49719..3615ced0a11bc 100644
> > --- a/mm/memory-failure.c
> > +++ b/mm/memory-failure.c
> > @@ -2677,7 +2677,12 @@ int unpoison_memory(unsigned long pfn)
> > p = pfn_to_online_page(pfn);
> > if (!p)
> > return -EIO;
> > - folio = page_folio(p);
> > +
> > + if (!is_page_hwpoison(p)) {
> > + unpoison_pr_info("%#lx: page is not poisoned\n",
> > + pfn, &unpoison_rs);
> > + return -EBUSY;
> > + }
> >
> > mutex_lock(&mf_mutex);
> >
> > @@ -2688,6 +2693,8 @@ int unpoison_memory(unsigned long pfn)
> > goto unlock_mutex;
> > }
> >
> > + folio = page_folio(p);
> > +
> > if (is_huge_zero_folio(folio)) {
> > unpoison_pr_info("%#lx: huge zero page is not supported\n",
> > pfn, &unpoison_rs);
> > @@ -2695,12 +2702,6 @@ int unpoison_memory(unsigned long pfn)
> > goto unlock_mutex;
> > }
> >
> > - if (!is_page_hwpoison(p)) {
> > - unpoison_pr_info("%#lx: page is not poisoned\n",
> > - pfn, &unpoison_rs);
> > - goto unlock_mutex;
> > - }
> > -
>
> Does removing this check from inside the mutex introduce a time-of-check to
> time-of-use race?
>
> If two threads call unpoison_memory() concurrently on the same PFN, both could
> pass the lockless check. Thread A takes the lock, unpoisons the page, and the
> page is returned to the buddy allocator.
>
> When Thread B acquires the lock, it will now proceed without re-checking
> is_page_hwpoison(). If the page was concurrently reallocated as a compound tail
> page, the speculative folio_ref_count() or folio_test_slab() checks below
> could trigger a VM_BUG_ON_PGFLAGS crash.
>
> > if (folio_ref_count(folio) > 1) {
> > unpoison_pr_info("%#lx: someone grabs the hwpoison page\n",
> > pfn, &unpoison_rs);
Uh. Yeah. I wasn't thinking about two simultaneous calls to
unpoison_memory(). I'll just drop this patch; it wasn't important.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page()
2026-07-23 14:30 ` [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page() Matthew Wilcox (Oracle)
@ 2026-07-24 12:36 ` Matthew Wilcox
2026-07-24 12:52 ` Michael S. Tsirkin
1 sibling, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 12:36 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan, Usama Arif
Sashiko review:
> --- Patch [3]: [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page() ---
> commit 82722dc6d8a8b8030006cbc0fae2e5f36ec21fa1
> Author: Matthew Wilcox (Oracle) <willy@infradead.org>
>
> mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page()
>
> This commit renames folio_contain_hwpoison_page() to
> folio_has_hwpoison_page() for better naming clarity. It also makes the
> argument const to allow other callers to be made const, with no functional
> change.
>
> > diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
> > index 7223f6f4e2b40..4185a03a45cf7 100644
> > --- a/include/linux/page-flags.h
> > +++ b/include/linux/page-flags.h
> > @@ -1083,10 +1083,10 @@ static inline bool is_page_hwpoison(const struct page *page)
> > return folio_test_hugetlb(folio) && PageHWPoison(&folio->page);
> > }
>
> This is a pre-existing issue, but while reviewing the surrounding context I
> noticed a speculative folio access here.
>
> When a privileged process reads /proc/kcore, read_kcore_iter() in
> fs/proc/kcore.c iterates over PFNs and calls is_page_hwpoison() on raw
> page pointers without holding a reference:
>
> fs/proc/kcore.c:read_kcore_iter() {
> ...
> page = pfn_to_online_page(pfn);
> if (!page || PageOffline(page) ||
> is_page_hwpoison(page) || !pfn_is_ram(pfn) ||
> ...
> }
>
> Inside is_page_hwpoison(), the page is converted to a folio and its flags
> are accessed immediately:
>
> const struct folio *folio = page_folio(page);
> return folio_test_hugetlb(folio) && ...
>
> Since the folio is unreferenced, could this cause a kernel panic via
> VM_BUG_ON_PGFLAGS(PageTail(page), page) in const_folio_flags() if the
> compound page is split or freed concurrently?
>
> Should this path stabilize the folio with folio_try_get() and re-validate
> before checking folio_test_hugetlb()?
This is fixed later in the patch series. Bad bot, no cookie.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 04/14] hugetlb: Mark some function arguments as const
2026-07-23 14:30 ` [PATCH v4 04/14] hugetlb: Mark some function arguments as const Matthew Wilcox (Oracle)
@ 2026-07-24 12:38 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 12:38 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan, Usama Arif
On Thu, Jul 23, 2026 at 03:30:21PM +0100, Matthew Wilcox (Oracle) wrote:
> These functions do not modify their folio argument, so we can mark them
> as being const which will allow for some minor optimisations and let us
> mark other function arguments as being const in the future.
>
> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
> Acked-by: Usama Arif <usama.arif@linux.dev>
Sashiko finds no issues with this patch.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page()
2026-07-23 14:30 ` [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page() Matthew Wilcox (Oracle)
@ 2026-07-24 12:44 ` Matthew Wilcox
2026-07-24 15:16 ` Sean Christopherson
2026-07-24 15:16 ` Sean Christopherson
1 sibling, 1 reply; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 12:44 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm, kvm, Paolo Bonzini,
Ackerley Tng, Michael Roth, Michael S. Tsirkin,
Sean Christopherson
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
Sashiko review:
> --- Patch [5]: [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page() ---
> commit b4db4098463261fdf87a2e92b28ce2b4c9445e74
> Author: Matthew Wilcox (Oracle) <willy@infradead.org>
> guest_memfd: Use folio_has_hwpoisoned_page()
>
> This commit changes guest_memfd to use folio_has_hwpoisoned_page() instead of
> folio_test_hwpoison() to correctly detect poisoned pages on all large folios,
> not just hugetlb folios.
>
> > diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
> > index db57c5766ab60..94eedcc7f1a9e 100644
> > --- a/virt/kvm/guest_memfd.c
> > +++ b/virt/kvm/guest_memfd.c
> > @@ -774,7 +774,7 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
> > if (IS_ERR(folio))
> > return folio;
> >
> > - if (folio_test_hwpoison(folio)) {
> > + if (folio_has_hwpoisoned_page(folio)) {
> > folio_unlock(folio);
> > folio_put(folio);
> > return ERR_PTR(-EHWPOISON);
>
> This is a pre-existing issue, but while looking at hwpoison
> checks in guest_memfd.c, I noticed a missing check in
> kvm_gmem_fault_user_mapping() that might trigger a host kernel MCE panic.
>
> If a guest_memfd folio is allocated (e.g. via fallocate) but not zeroed, it
> remains in the page cache without the uptodate flag. If a memory failure
> occurs on this page, it is marked hwpoisoned.
>
> Later, a fault could trigger kvm_gmem_fault_user_mapping() which executes:
>
> folio = kvm_gmem_get_folio(inode, vmf->pgoff);
> ...
> if (!folio_test_uptodate(folio)) {
> clear_highpage(folio_page(folio, 0));
> folio_mark_uptodate(folio);
> }
>
> Because there is no folio_has_hwpoisoned_page() check before clearing the
> page, could the kernel-mode write in clear_highpage() to the uninitialized,
> poisoned page trigger an unrecoverable MCE panic?
>
> Should kvm_gmem_fault_user_mapping() include the same HWPoison check as
> __kvm_gmem_get_pfn() and return VM_FAULT_HWPOISON if the page is poisoned?
I have no idea, but the purpose of this patch is to stop kvm from using
the wrong API. This is definitely scope creep beyond my level of interest
and the aims of this patch series. I've added the KVM people to the cc
here, but I'm not going to do anything more with this review.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 06/14] memory-failure: Remove raw_hwp_list_head()
2026-07-23 14:30 ` [PATCH v4 06/14] memory-failure: Remove raw_hwp_list_head() Matthew Wilcox (Oracle)
@ 2026-07-24 12:45 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 12:45 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
On Thu, Jul 23, 2026 at 03:30:23PM +0100, Matthew Wilcox (Oracle) wrote:
> There's no need to hide the llist_head; we can just declare it
> directly in struct folio and do away with raw_hwp_list_head().
Sashiko finds no issues with this patch.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page()
2026-07-23 14:30 ` [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page() Matthew Wilcox (Oracle)
2026-07-24 12:36 ` Matthew Wilcox
@ 2026-07-24 12:52 ` Michael S. Tsirkin
2026-07-24 13:24 ` Matthew Wilcox
1 sibling, 1 reply; 32+ messages in thread
From: Michael S. Tsirkin @ 2026-07-24 12:52 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Andrew Morton, Jane Chu, linux-mm, Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan, Usama Arif
On Thu, Jul 23, 2026 at 03:30:20PM +0100, Matthew Wilcox (Oracle) wrote:
> This is a better name for this predicate. Also make the argument const
> to allow other callers to be made const. No functional change.
>
> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
> Acked-by: Usama Arif <usama.arif@linux.dev>
> ---
> include/linux/page-flags.h | 4 ++--
> mm/huge_memory.c | 2 +-
> mm/memory_hotplug.c | 2 +-
> mm/shmem.c | 2 +-
> mm/vmscan.c | 2 +-
> 5 files changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
> index 7223f6f4e2b4..4185a03a45cf 100644
> --- a/include/linux/page-flags.h
> +++ b/include/linux/page-flags.h
> @@ -1083,10 +1083,10 @@ static inline bool is_page_hwpoison(const struct page *page)
> return folio_test_hugetlb(folio) && PageHWPoison(&folio->page);
> }
>
> -static inline bool folio_contain_hwpoisoned_page(struct folio *folio)
> +static inline bool folio_has_hwpoisoned_page(const struct folio *folio)
> {
> return folio_test_hwpoison(folio) ||
> - (folio_test_large(folio) && folio_test_has_hwpoisoned(folio));
> + (folio_test_large(folio) && folio_test_has_hwpoisoned(folio));
> }
>
It probably makes sense to document when this should be used
in preference to folio_test_hwpoison, given a lot of people
got this wrong?
> bool is_free_buddy_page(const struct page *page);
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 2bccb0a53a0a..353340309107 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4413,7 +4413,7 @@ static bool thp_underused(struct folio *folio)
> if (khugepaged_max_ptes_none == HPAGE_PMD_NR - 1)
> return false;
>
> - if (folio_contain_hwpoisoned_page(folio))
> + if (folio_has_hwpoisoned_page(folio))
> return false;
>
> for (i = 0; i < folio_nr_pages(folio); i++) {
> diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
> index 7ac19fab2263..33007e8e546a 100644
> --- a/mm/memory_hotplug.c
> +++ b/mm/memory_hotplug.c
> @@ -1813,7 +1813,7 @@ static void do_migrate_range(unsigned long start_pfn, unsigned long end_pfn)
> if (folio_test_large(folio))
> pfn = folio_pfn(folio) + folio_nr_pages(folio) - 1;
>
> - if (folio_contain_hwpoisoned_page(folio)) {
> + if (folio_has_hwpoisoned_page(folio)) {
> /*
> * unmap_poisoned_folio() cannot handle large folios
> * in all cases yet.
> diff --git a/mm/shmem.c b/mm/shmem.c
> index b51f83c970bb..492c9bef3620 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
> @@ -3240,7 +3240,7 @@ shmem_write_begin(const struct kiocb *iocb, struct address_space *mapping,
> if (ret)
> return ret;
>
> - if (folio_contain_hwpoisoned_page(folio)) {
> + if (folio_has_hwpoisoned_page(folio)) {
> folio_unlock(folio);
> folio_put(folio);
> return -EIO;
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 35c3bb15ae96..70e54ace0e74 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -1086,7 +1086,7 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
> if (!folio_trylock(folio))
> goto keep;
>
> - if (folio_contain_hwpoisoned_page(folio)) {
> + if (folio_has_hwpoisoned_page(folio)) {
> /*
> * unmap_poisoned_folio() can't handle large
> * folio, just skip it. memory_failure() will
> --
> 2.47.3
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 07/14] hugetlb: Use the has_hwpoisoned flag
2026-07-23 14:30 ` [PATCH v4 07/14] hugetlb: Use the has_hwpoisoned flag Matthew Wilcox (Oracle)
@ 2026-07-24 13:21 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 13:21 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
Sashiko review:
> --- Patch [7]: [PATCH v4 07/14] hugetlb: Use the has_hwpoisoned flag ---
> commit f67cbd0b3ba7a67258490d423b9fc8352cb972aa
> Author: Matthew Wilcox (Oracle) <willy@infradead.org>
>
> hugetlb: Use the has_hwpoisoned flag
>
> Other large folios use the has_hwpoisoned flag. Convert hugetlb to match.
> This will help us use the per-page hwpoison flag in the future.
>
> > diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> > index 18691c905ac94..4200deab2c3c4 100644
> > --- a/mm/memory-failure.c
> > +++ b/mm/memory-failure.c
>
> [ ... ]
>
> > @@ -1874,6 +1874,23 @@ static unsigned long __folio_free_raw_hwp(struct folio *folio, bool move_flag)
> > #define MF_HUGETLB_FOLIO_PRE_POISONED 3 /* folio already poisoned */
> > #define MF_HUGETLB_PAGE_PRE_POISONED 4 /* exact page already poisoned */
> > #define MF_HUGETLB_RETRY 5 /* hugepage is busy, retry */
> > +
> > +static inline int hugetlb_set_poison(struct folio *folio)
> > +{
> > + if (folio_test_set_has_hwpoisoned(folio))
> > + return MF_HUGETLB_FOLIO_PRE_POISONED;
> > + folio_set_huge_poison(folio);
> > + return 0;
> > +}
>
> By replacing folio_test_set_hwpoison(folio) and no longer propagating
> PG_hwpoison to the head page, does this break memory failure unmapping and
> silently bypass hwpoison swap entry creation?
>
> If a hardware memory error occurs on a tail page of a hugetlb folio,
> memory_failure() calls TestSetPageHWPoison(p) on the exact tail page. It then
> calls hugetlb_set_poison(), which sets PG_has_hwpoisoned but no longer sets
> PG_hwpoison on the head page.
>
> When unmapping the folio in try_to_unmap_one() in mm/rmap.c, the code
> extracts the base PFN from the hugetlb PTE, calculates subpage as the head
> page, and checks PageHWPoison(subpage):
>
> mm/rmap.c:try_to_unmap_one() {
> ...
> VM_BUG_ON_PAGE(!PageHWPoison(subpage), subpage);
> ...
> if (PageHWPoison(subpage) && (flags & TTU_HWPOISON)) {
> pteval = swp_entry_to_pte(make_hwpoison_entry(subpage));
> ...
> }
>
> Since PG_hwpoison is no longer on the head page, this check fails, triggering
> VM_BUG_ON_PAGE. Without DEBUG_VM, the code skips inserting the hwpoison swap
> entry, simply clearing the PTE, which silently allocates a new zeroed page on
> subsequent accesses instead of delivering SIGBUS.
Oh; this is a good catch. I have now reworked try_to_unmap() to handle
hugetlb pages a bit better and it'll be part of this patch in v5.
> > +
> > +static inline int hugetlb_clear_poison(struct folio *folio)
> > +{
> > + if (!folio_test_clear_has_hwpoisoned(folio))
> > + return -EBUSY;
> > + folio_clear_huge_poison(folio);
> > + return 0;
> > +}
>
> Does this sequence open a race window with concurrent hugetlb freeing that
> results in memory corruption?
>
> When unpoison_memory() is called on a free hugetlb folio,
> hugetlb_clear_poison() atomically clears PG_has_hwpoisoned first. This
> exposes the folio to concurrent hugetlb demotion (demote_pool_huge_page()),
> which can pick up the folio and call __update_and_free_hugetlb_folio(). This
> function sets folio->page.page_type = UINT_MAX to return it to the buddy
> allocator.
>
> Concurrently, hugetlb_clear_poison() proceeds to execute
> folio_clear_huge_poison() (defined in include/linux/page-flags.h), which
> performs a non-atomic read-modify-write:
>
> include/linux/page-flags.h:folio_clear_huge_poison() {
> folio->page.page_type &= ~(1 << 23);
> }
>
> This non-atomic write can overwrite the buddy allocator's UINT_MAX with
> 0xFF7FFFFF. When the buddy allocator receives the page, it expects
> page_type == UINT_MAX and will crash.
I'm not sure that it can be demoted while we're holding a reference to
the folio. But I think it makes sense to change thusly:
+static inline int hugetlb_clear_poison(struct folio *folio)
+{
+ if (!folio_test_has_hwpoisoned(folio))
+ return -EBUSY;
+ folio_clear_huge_poison(folio);
+ folio_clear_has_hwpoisoned(folio));
+ return 0;
+}
(looking at Jiaqi's patches, maybe we shouldn't be clearing has_hwpoisoned
before freeing the folio?)
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page()
2026-07-24 12:52 ` Michael S. Tsirkin
@ 2026-07-24 13:24 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 13:24 UTC (permalink / raw)
To: Michael S. Tsirkin
Cc: Andrew Morton, Jane Chu, linux-mm, Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan, Usama Arif
On Fri, Jul 24, 2026 at 08:52:13AM -0400, Michael S. Tsirkin wrote:
> > -static inline bool folio_contain_hwpoisoned_page(struct folio *folio)
> > +static inline bool folio_has_hwpoisoned_page(const struct folio *folio)
> > {
> > return folio_test_hwpoison(folio) ||
> > - (folio_test_large(folio) && folio_test_has_hwpoisoned(folio));
> > + (folio_test_large(folio) && folio_test_has_hwpoisoned(folio));
> > }
> >
>
> It probably makes sense to document when this should be used
> in preference to folio_test_hwpoison, given a lot of people
> got this wrong?
folio_test_hwpoison() / folio_set_hwpoison() / folio_clear_hwpoison()
need to stop existing. I know how to do it, but it's a lot of churn
to page-flags.h. Not something for this series, but it's on my radar.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 08/14] mm: Remove locking mf_mutex in is_raw_hwpoison_page_in_hugepage()
2026-07-23 14:30 ` [PATCH v4 08/14] mm: Remove locking mf_mutex in is_raw_hwpoison_page_in_hugepage() Matthew Wilcox (Oracle)
@ 2026-07-24 13:41 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 13:41 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
Sashiko review:
> --- Patch [8]: [PATCH v4 08/14] mm: Remove locking mf_mutex in is_raw_hwpoison_page_in_hugepage() ---
> commit c0f68557b399585852271ea0bc6a590caa47b3ad
> Author: Matthew Wilcox (Oracle) <willy@infradead.org>
>
> mm: Remove locking mf_mutex in is_raw_hwpoison_page_in_hugepage()
>
> This commit removes the mf_mutex protection from the raw hwpoison list
> iteration in hugetlb_page_hwpoison(), relying on lockless llist traversal
> using READ_ONCE(). It also renames the function and modifies it to take a
> folio.
>
> > diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> > index 4200deab2c3c4..5076ee5d4788d 100644
> > --- a/mm/memory-failure.c
> > +++ b/mm/memory-failure.c
> [ ... ]
> > @@ -1817,37 +1817,30 @@ struct raw_hwp_page {
> > struct page *page;
> > };
> >
> > -bool is_raw_hwpoison_page_in_hugepage(struct page *page)
> > +/*
> > + * Check if a given @page in a hugetlb folio is HWPOISON.
> > + */
> > +bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page)
> > {
> [ ... ]
> > - mutex_lock(&mf_mutex);
> > -
> > - llist_for_each_entry(p, folio->hugetlb_hwpoison.first, node) {
> > - if (page == p->page) {
> > - ret = true;
> > - break;
> > - }
> > + llist_for_each_entry(p, READ_ONCE(list->first), node) {
> > + if (page == p->page)
> > + return true;
> > }
>
> Is the assumption in the commit message that updates only add to the head of
> the list accurate?
>
> Looking at __folio_free_raw_hwp() in mm/memory-failure.c, the entire list can
> be concurrently detached and its nodes freed via kfree() without an RCU grace
> period:
>
> head = llist_del_all(&folio->hugetlb_hwpoison);
> llist_for_each_entry_safe(p, next, head, node) {
> ...
> kfree(p);
> }
>
> This freeing can happen while the folio is still in active use. For example,
> if a subsequent memory failure on the same folio fails a GFP_ATOMIC allocation
> in hugetlb_update_hwpoison():
>
> } else {
> folio_set_hugetlb_raw_hwp_unreliable(folio);
> __folio_free_raw_hwp(folio, false);
> }
>
> Since callers like hugetlbfs_read_iter() explicitly drop the folio lock before
> calling adjust_range_hwpoison() (which triggers this lockless traversal),
> could a concurrent memory failure free these list nodes while this loop is
> still accessing them, leading to a use-after-free?
>
> > - mutex_unlock(&mf_mutex);
> > -
> > - return ret;
> > + return false;
> > }
argh. I give up on this cleverness. I'm going with the new spinlock
approach (like I did in v3) but retaining the per-folio list of pages
rather than having a global hash table.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 09/14] mm: Check individual hugetlb pages for poison
2026-07-23 14:30 ` [PATCH v4 09/14] mm: Check individual hugetlb pages for poison Matthew Wilcox (Oracle)
@ 2026-07-24 14:00 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 14:00 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
Sashiko review:
> --- Patch [9]: [PATCH v4 09/14] mm: Check individual hugetlb pages for poison ---
> commit 5da9009498dbc7d3d1fea29d7065847731ac20dc
> Author: Matthew Wilcox (Oracle) <willy@infradead.org>
> Subject: mm: Check individual hugetlb pages for poison
>
> This commit modifies the hugetlb hardware poison checking logic to verify
> individual subpages instead of the entire folio. It introduces a new
> function to safely perform this check without holding a folio reference by
> relying on the hugetlb_lock.
>
> Link: N/A
>
> > diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
> > index 7c4652dd517a3..9cde74320e48a 100644
> > --- a/include/linux/page-flags.h
> > +++ b/include/linux/page-flags.h
>
> [ ... ]
>
> > @@ -1090,6 +1090,8 @@ static inline bool PageHuge(const struct page *page)
> > return folio_test_hugetlb(page_folio(page));
> > }
> >
> > +bool hugetlb_unref_page_hwpoison(const struct page *page);
>
> Does this cause a build failure when CONFIG_MEMORY_FAILURE is not set?
>
> When CONFIG_MEMORY_FAILURE=n, mm/memory-failure.c isn't compiled, meaning
> hugetlb_unref_page_hwpoison() is not defined.
>
> Since is_page_hwpoison() is an inline function that evaluates a runtime
> condition when CONFIG_HUGETLB_PAGE=y, the compiler can't optimize the
> branch away.
>
> Will this result in an undefined reference to hugetlb_unref_page_hwpoison()
> for callers like fs/proc/kcore.c?
Oh, right, need to define FOLIO_TEST_FLAG_FALSE(huge_poison) if
CONFIG_MEMORY_FAILURE isn't defined.
> > @@ -1104,7 +1106,7 @@ static inline bool is_page_hwpoison(const struct page *page)
> > return true;
> > folio = page_folio(page);
>
> This is a pre-existing issue, but can this speculative folio access cause a
> kernel panic?
>
> In PFN-scanning contexts like /proc/kcore, is_page_hwpoison() is called
> without holding a stabilizing reference.
>
> It calls page_folio() speculatively and reads flags via
> folio_test_huge_poison().
>
> If the page is concurrently freed and reallocated to a non-compound user
> (like SLAB) that stores data in the compound_head field, page_folio() will
> return a garbage pointer.
... what? Slab is a good compound page user. You're drinking the
bad bot juice again.
> This isn't a bug introduced by this patch, but could the non-atomic
> modification of the poison flag corrupt the buddy allocator?
>
> unpoison_memory() calls hugetlb_clear_poison(folio) which performs a
> non-atomic read-modify-write:
>
> folio->page.page_type &= ~(1 << 23);
>
> When get_hwpoison_page() returns 0 for a free hugetlb folio,
> unpoison_memory() does not hold a reference. Concurrently, the folio can
> be freed to the buddy allocator.
>
> If the non-atomic modification in unpoison_memory() races with
> __folio_clear_hugetlb() or the buddy allocator's __SetPageBuddy(), it can
> overwrite their updates.
>
> For example, if it overwrites the buddy flag, PageBuddy() becomes
> false for a page on the buddy list, crashing the allocator when accessed.
I think it re-found the same problem it complained about earlier.
> > +bool hugetlb_unref_page_hwpoison(const struct page *page)
> > +{
> > + const struct folio *folio;
> > + unsigned long flags;
> > + bool ret;
> > +
> > + spin_lock_irqsave(&hugetlb_lock, flags);
> > + folio = page_folio(page);
> > + if (!folio_test_huge_poison(folio)) {
> > + ret = PageHWPoison(page);
> > + goto unlock;
> > + }
> > +
> > + ret = precise_page_poisoned(folio, page);
>
> Can this list traversal result in a use-after-free?
As earlier, I'll protect this with a spinlock.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page()
2026-07-24 12:44 ` Matthew Wilcox
@ 2026-07-24 15:16 ` Sean Christopherson
0 siblings, 0 replies; 32+ messages in thread
From: Sean Christopherson @ 2026-07-24 15:16 UTC (permalink / raw)
To: Matthew Wilcox
Cc: Andrew Morton, Jane Chu, linux-mm, kvm, Paolo Bonzini,
Ackerley Tng, Michael Roth, Michael S. Tsirkin, Muchun Song,
Oscar Salvador, David Hildenbrand, Miaohe Lin, Naoya Horiguchi,
Jan Kara, linux-fsdevel, Christian Brauner, Jiaqi Yan
On Fri, Jul 24, 2026, Matthew Wilcox wrote:
> Sashiko review:
> > --- Patch [5]: [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page() ---
> > commit b4db4098463261fdf87a2e92b28ce2b4c9445e74
> > Author: Matthew Wilcox (Oracle) <willy@infradead.org>
> > guest_memfd: Use folio_has_hwpoisoned_page()
> >
> > This commit changes guest_memfd to use folio_has_hwpoisoned_page() instead of
> > folio_test_hwpoison() to correctly detect poisoned pages on all large folios,
> > not just hugetlb folios.
> >
> > > diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
> > > index db57c5766ab60..94eedcc7f1a9e 100644
> > > --- a/virt/kvm/guest_memfd.c
> > > +++ b/virt/kvm/guest_memfd.c
> > > @@ -774,7 +774,7 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
> > > if (IS_ERR(folio))
> > > return folio;
> > >
> > > - if (folio_test_hwpoison(folio)) {
> > > + if (folio_has_hwpoisoned_page(folio)) {
> > > folio_unlock(folio);
> > > folio_put(folio);
> > > return ERR_PTR(-EHWPOISON);
> >
> > This is a pre-existing issue, but while looking at hwpoison
> > checks in guest_memfd.c, I noticed a missing check in
> > kvm_gmem_fault_user_mapping() that might trigger a host kernel MCE panic.
> >
> > If a guest_memfd folio is allocated (e.g. via fallocate) but not zeroed, it
> > remains in the page cache without the uptodate flag. If a memory failure
> > occurs on this page, it is marked hwpoisoned.
> >
> > Later, a fault could trigger kvm_gmem_fault_user_mapping() which executes:
> >
> > folio = kvm_gmem_get_folio(inode, vmf->pgoff);
> > ...
> > if (!folio_test_uptodate(folio)) {
> > clear_highpage(folio_page(folio, 0));
> > folio_mark_uptodate(folio);
> > }
> >
> > Because there is no folio_has_hwpoisoned_page() check before clearing the
> > page, could the kernel-mode write in clear_highpage() to the uninitialized,
> > poisoned page trigger an unrecoverable MCE panic?
> >
> > Should kvm_gmem_fault_user_mapping() include the same HWPoison check as
> > __kvm_gmem_get_pfn() and return VM_FAULT_HWPOISON if the page is poisoned?
>
> I have no idea, but the purpose of this patch is to stop kvm from using
> the wrong API. This is definitely scope creep beyond my level of interest
> and the aims of this patch series. I've added the KVM people to the cc
> here, but I'm not going to do anything more with this review.
Heh, you don't want to sign up for a week or two of onion peeling?
In the future, please Cc at least kvm@ on any guest_memfd changes, both to avoid
unexpected conflicts (the code is under heavy development), and because us KVM
people are still learning how the hell do filesystems stuff, and can use all the
help and knowledge sharing we can get.
Thanks!
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page()
2026-07-23 14:30 ` [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page() Matthew Wilcox (Oracle)
2026-07-24 12:44 ` Matthew Wilcox
@ 2026-07-24 15:16 ` Sean Christopherson
1 sibling, 0 replies; 32+ messages in thread
From: Sean Christopherson @ 2026-07-24 15:16 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Andrew Morton, Jane Chu, linux-mm, Muchun Song, Oscar Salvador,
David Hildenbrand, Miaohe Lin, Naoya Horiguchi, Jan Kara,
linux-fsdevel, Christian Brauner, Jiaqi Yan
On Thu, Jul 23, 2026, Matthew Wilcox (Oracle) wrote:
> folio_test_hwpoison() only works on hugetlb folios today and will give
> the wrong answer for other large folios. folio_has_hwpoisoned_page()
> works on both.
>
> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
> ---
Acked-by: Sean Christopherson <seanjc@google.com>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 10/14] filemap: Add hwpoison handling to filemap_read()
2026-07-23 14:30 ` [PATCH v4 10/14] filemap: Add hwpoison handling to filemap_read() Matthew Wilcox (Oracle)
@ 2026-07-24 16:06 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 16:06 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
Sashiko review:
> --- Patch [10]: [PATCH v4 10/14] filemap: Add hwpoison handling to filemap_read() ---
> commit c46fb3fcf6cb7055457888cfb186af5ae3a493c2
> Author: Jane Chu <jane.chu@oracle.com>
>
> filemap: Add hwpoison handling to filemap_read()
>
> This commit adds hardware poison handling to filemap_read(). It allows the
> read iterator to make a best-effort copy of data from clean pages,
> preventing Machine Check Exceptions when the page cache contains HWpoison.
>
> > diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
> > index 9cde74320e48a..b146c5228741c 100644
> > --- a/include/linux/page-flags.h
> > +++ b/include/linux/page-flags.h
> > @@ -1111,6 +1112,16 @@ static inline bool is_page_hwpoison(const struct page *page)
> > return PageHWPoison(page);
> > }
> >
> > +static inline bool is_ref_page_hwpoison(const struct folio *folio,
> > + const struct page *page)
> > +{
> > + if (PageHWPoison(page))
> > + return true;
> > + if (folio_test_hugetlb(folio))
> > + return hugetlb_page_hwpoison(folio, page);
>
> Does this lockless traversal risk a Use-After-Free?
>
> When this function calls into hugetlb_page_hwpoison(), it eventually reaches
> precise_page_poisoned() which performs a lockless iteration of the
> hugetlb_hwpoison llist:
>
> precise_page_poisoned()
> llist_for_each_entry(p, READ_ONCE(list->first), node) {
> ...
> }
>
> However, a concurrent memory_failure() can free the list nodes while the
> lockless traversal is in progress, since __folio_free_raw_hwp() calls
> kfree(p) on the nodes without an RCU grace period.
>
> While filemap_read() does not process hugetlb folios, hugetlbfs_read_iter()
> (added earlier in this series) does, making the Use-After-Free directly
> reachable.
Will be addressed by the spinlock I mentioned earlier.
> [ ... ]
>
> > diff --git a/mm/filemap.c b/mm/filemap.c
> > index 58eb9d2406434..26a5f18121f90 100644
> > --- a/mm/filemap.c
> > +++ b/mm/filemap.c
> > @@ -2483,6 +2483,8 @@ static void filemap_get_read_batch(struct address_space *mapping,
> >
> > if (!folio_batch_add(fbatch, folio))
> > break;
> > + if (folio_has_hwpoisoned_page(folio))
> > + break;
>
> This isn't a bug introduced by this patch, but does returning a poisoned
> folio here risk a Machine Check Exception if a disk read populates the folio?
Sure, but we remove hwpoisoned pages from the pagecache for
storage-backed files. So this doesn't come up.
> If a poisoned folio in the page cache is not uptodate,
> filemap_get_read_batch() will return it as the last folio in the batch.
>
> Then filemap_get_pages() will see !folio_test_uptodate(folio) and call
> filemap_update_page(), which locks the folio and passes it to the
> filesystem's read_folio callback:
>
> filemap_update_page()
> error = filemap_read_folio(iocb->ki_filp, mapping->a_ops->read_folio,
> folio);
>
> If the filesystem populates the folio using CPU operations (which is common
> for inline data, fscrypt, squashfs, or block device emulation), the CPU will
> access the poisoned memory and trigger an unrecoverable Machine Check
> Exception. The patch guards the userspace copy path, but misses guarding the
> cache-miss path where the kernel itself reads into the poisoned folio.
>
> > if (!folio_test_uptodate(folio))
> > break;
>
> [ ... ]
>
> > @@ -2862,14 +2887,18 @@ ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,
> > if (writably_mapped)
> > flush_dcache_folio(folio);
> >
> > - copied = copy_folio_to_iter(folio, offset, bytes, iter);
> > + copied = adjust_range_hwpoison(folio, offset, bytes);
> > + if (copied < bytes)
> > + error = -EIO;
> > + copied = copy_folio_to_iter(folio, offset, copied, iter);
>
> This isn't a bug introduced by this patch, but will filemap_splice_read()
> still trigger a Machine Check Exception on poisoned pages since it lacks
> similar protection?
>
> While this patch successfully updates filemap_read() to use
> adjust_range_hwpoison() to prevent reading poisoned memory,
> filemap_splice_read() was left unmodified.
Hugetlb doesn't implement splice_read(), so this is irrelevant.
> filemap_splice_read() uses the same filemap_get_pages() logic, loops over
> the returned batch, and unconditionally splices each folio into the pipe:
>
> filemap_splice_read()
> ...
> n = splice_folio_into_pipe(pipe, folio, *ppos, n);
>
> When a userspace process subsequently reads from the pipe, the kernel will
> access the poisoned physical page (e.g., via copy_page_to_iter()), triggering
> an unrecoverable Machine Check Exception.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 11/14] filemap: Remove checks in mapping_set_folio_order_range()
2026-07-23 14:30 ` [PATCH v4 11/14] filemap: Remove checks in mapping_set_folio_order_range() Matthew Wilcox (Oracle)
@ 2026-07-24 16:08 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 16:08 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
Sashiko review:
> --- Patch [11]: [PATCH v4 11/14] filemap: Remove checks in mapping_set_folio_order_range() ---
> commit 795dd7139e11d246506bb93b2bb441409e4bbfbf
> Author: Matthew Wilcox (Oracle) <willy@infradead.org>
>
> filemap: Remove checks in mapping_set_folio_order_range()
>
> This patch removes max and min order bounds checking from
> mapping_set_folio_order_range() to allow its use with hugetlb. It also
> switches the configuration gate from CONFIG_TRANSPARENT_HUGEPAGE to
> CONFIG_FILEMAP_LARGE_FOLIO across several folio order functions.
>
> > diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
> > index 2c3718d592d6d..436987f47aed4 100644
> > --- a/include/linux/pagemap.h
> > +++ b/include/linux/pagemap.h
> [ ... ]
> > static inline void mapping_set_folio_order_range(struct address_space *mapping,
> > - unsigned int min,
> > - unsigned int max)
> > + unsigned int min, unsigned int max)
> > {
> > - if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
> > + if (!IS_ENABLED(CONFIG_FILEMAP_LARGE_FOLIO))
> > return;
>
> Is CONFIG_FILEMAP_LARGE_FOLIO defined anywhere in the codebase?
Oops. Forgot to continue the rename into this patch. Fixed.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 13/14] filemap: Add support for authoritative mappings
2026-07-23 14:30 ` [PATCH v4 13/14] filemap: Add support for authoritative mappings Matthew Wilcox (Oracle)
@ 2026-07-24 16:15 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 16:15 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
Sashiko review identifies only a problem identified in the previous patch.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v4 12/14] hugetlb: Set mapping folio order
2026-07-23 14:30 ` [PATCH v4 12/14] hugetlb: Set mapping folio order Matthew Wilcox (Oracle)
@ 2026-07-24 16:16 ` Matthew Wilcox
0 siblings, 0 replies; 32+ messages in thread
From: Matthew Wilcox @ 2026-07-24 16:16 UTC (permalink / raw)
To: Andrew Morton, Jane Chu, linux-mm
Cc: Muchun Song, Oscar Salvador, David Hildenbrand, Miaohe Lin,
Naoya Horiguchi, Jan Kara, linux-fsdevel, Christian Brauner,
Jiaqi Yan
Sashiko review indicates only the problem in the previous patch.
^ permalink raw reply [flat|nested] 32+ messages in thread
end of thread, other threads:[~2026-07-24 16:16 UTC | newest]
Thread overview: 32+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 14:30 [PATCH v4 00/14] Use generic_file_read_iter() in hugetlbfs Matthew Wilcox (Oracle)
2026-07-23 14:30 ` [PATCH v4 01/14] memory-failure: Fix hardware poison check in unpoison_memory() again Matthew Wilcox (Oracle)
2026-07-24 12:32 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 02/14] memory-failure: Test the page is hwpoison before taking the mutex Matthew Wilcox (Oracle)
2026-07-24 12:34 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 03/14] mm: Rename folio_contain_hwpoison_page() to folio_has_hwpoison_page() Matthew Wilcox (Oracle)
2026-07-24 12:36 ` Matthew Wilcox
2026-07-24 12:52 ` Michael S. Tsirkin
2026-07-24 13:24 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 04/14] hugetlb: Mark some function arguments as const Matthew Wilcox (Oracle)
2026-07-24 12:38 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 05/14] guest_memfd: Use folio_has_hwpoisoned_page() Matthew Wilcox (Oracle)
2026-07-24 12:44 ` Matthew Wilcox
2026-07-24 15:16 ` Sean Christopherson
2026-07-24 15:16 ` Sean Christopherson
2026-07-23 14:30 ` [PATCH v4 06/14] memory-failure: Remove raw_hwp_list_head() Matthew Wilcox (Oracle)
2026-07-24 12:45 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 07/14] hugetlb: Use the has_hwpoisoned flag Matthew Wilcox (Oracle)
2026-07-24 13:21 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 08/14] mm: Remove locking mf_mutex in is_raw_hwpoison_page_in_hugepage() Matthew Wilcox (Oracle)
2026-07-24 13:41 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 09/14] mm: Check individual hugetlb pages for poison Matthew Wilcox (Oracle)
2026-07-24 14:00 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 10/14] filemap: Add hwpoison handling to filemap_read() Matthew Wilcox (Oracle)
2026-07-24 16:06 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 11/14] filemap: Remove checks in mapping_set_folio_order_range() Matthew Wilcox (Oracle)
2026-07-24 16:08 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 12/14] hugetlb: Set mapping folio order Matthew Wilcox (Oracle)
2026-07-24 16:16 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 13/14] filemap: Add support for authoritative mappings Matthew Wilcox (Oracle)
2026-07-24 16:15 ` Matthew Wilcox
2026-07-23 14:30 ` [PATCH v4 14/14] hugetlb: replace hugetlbfs_read_iter() with generic_file_read_iter() Matthew Wilcox (Oracle)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox