AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/30 02:36 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "HUGETLBFS",
    "MEMORY_FAILURE"
  ],
  "FocusSymbols": [
    "hugetlb_page_hwpoison",
    "hugetlb_unref_page_hwpoison",
    "unpoison_memory",
    "folio_clear_hugetlb_hwpoison"
  ],
  "Reasoning": "The patch changes how hwpoison is handled for large folios and hugetlbfs. It modifies `filemap_read` to handle hwpoisoned folios and updates memory failure recovery logic. These are functional changes in core MM and hugetlbfs that should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/07/30 02:36 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 4320ddc1fa86f1285b57efbad741b30f5189a375\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Jul 30 02:36:37 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/fs/Kconfig b/fs/Kconfig\nindex cf6ae64776e62..eddac4ed214b1 100644\n--- a/fs/Kconfig\n+++ b/fs/Kconfig\n@@ -272,7 +272,7 @@ endif # HUGETLBFS\n \n config HUGETLB_PAGE\n \tdef_bool HUGETLBFS\n-\tselect XARRAY_MULTI\n+\tselect LARGE_FOLIO\n \n config HUGETLB_PAGE_OPTIMIZE_VMEMMAP\n \tdef_bool HUGETLB_PAGE\ndiff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c\nindex 216e1a0dd0b23..cef7e61b827d7 100644\n--- a/fs/hugetlbfs/inode.c\n+++ b/fs/hugetlbfs/inode.c\n@@ -187,113 +187,6 @@ hugetlb_get_unmapped_area(struct file *file, unsigned long addr,\n \treturn mm_get_unmapped_area_vmflags(file, addr0, len, pgoff, flags, 0);\n }\n \n-/*\n- * Someone wants to read @bytes from a HWPOISON hugetlb @folio from @offset.\n- * Returns the maximum number of bytes one can read without touching the 1st raw\n- * HWPOISON page.\n- */\n-static size_t adjust_range_hwpoison(struct folio *folio, size_t offset,\n-\t\tsize_t bytes)\n-{\n-\tstruct page *page = folio_page(folio, offset / PAGE_SIZE);\n-\tsize_t safe_bytes;\n-\n-\tif (is_raw_hwpoison_page_in_hugepage(page))\n-\t\treturn 0;\n-\t/* Safe to read the remaining bytes in this page. */\n-\tsafe_bytes = PAGE_SIZE - (offset % PAGE_SIZE);\n-\tpage++;\n-\n-\t/* Check each remaining page as long as we are not done yet. */\n-\tfor (; safe_bytes \u003c bytes; safe_bytes += PAGE_SIZE, page++)\n-\t\tif (is_raw_hwpoison_page_in_hugepage(page))\n-\t\t\tbreak;\n-\n-\treturn min(safe_bytes, bytes);\n-}\n-\n-/*\n- * Support for read() - Find the page attached to f_mapping and copy out the\n- * data. This provides functionality similar to filemap_read().\n- */\n-static ssize_t hugetlbfs_read_iter(struct kiocb *iocb, struct iov_iter *to)\n-{\n-\tstruct file *file = iocb-\u003eki_filp;\n-\tstruct hstate *h = hstate_file(file);\n-\tstruct address_space *mapping = file-\u003ef_mapping;\n-\tstruct inode *inode = mapping-\u003ehost;\n-\tunsigned long index = iocb-\u003eki_pos \u003e\u003e huge_page_shift(h);\n-\tunsigned long offset = iocb-\u003eki_pos \u0026 ~huge_page_mask(h);\n-\tunsigned long end_index;\n-\tloff_t isize;\n-\tssize_t retval = 0;\n-\n-\twhile (iov_iter_count(to)) {\n-\t\tstruct folio *folio;\n-\t\tsize_t nr, copied, want;\n-\n-\t\t/* nr is the maximum number of bytes to copy from this page */\n-\t\tnr = huge_page_size(h);\n-\t\tisize = i_size_read(inode);\n-\t\tif (!isize)\n-\t\t\tbreak;\n-\t\tend_index = (isize - 1) \u003e\u003e huge_page_shift(h);\n-\t\tif (index \u003e end_index)\n-\t\t\tbreak;\n-\t\tif (index == end_index) {\n-\t\t\tnr = ((isize - 1) \u0026 ~huge_page_mask(h)) + 1;\n-\t\t\tif (nr \u003c= offset)\n-\t\t\t\tbreak;\n-\t\t}\n-\t\tnr = nr - offset;\n-\n-\t\t/* Find the folio */\n-\t\tfolio = filemap_lock_hugetlb_folio(h, mapping, index);\n-\t\tif (IS_ERR(folio)) {\n-\t\t\t/*\n-\t\t\t * We have a HOLE, zero out the user-buffer for the\n-\t\t\t * length of the hole or request.\n-\t\t\t */\n-\t\t\tcopied = iov_iter_zero(nr, to);\n-\t\t} else {\n-\t\t\tfolio_unlock(folio);\n-\n-\t\t\tif (!folio_test_hwpoison(folio))\n-\t\t\t\twant = nr;\n-\t\t\telse {\n-\t\t\t\t/*\n-\t\t\t\t * Adjust how many bytes safe to read without\n-\t\t\t\t * touching the 1st raw HWPOISON page after\n-\t\t\t\t * offset.\n-\t\t\t\t */\n-\t\t\t\twant = adjust_range_hwpoison(folio, offset, nr);\n-\t\t\t\tif (want == 0) {\n-\t\t\t\t\tfolio_put(folio);\n-\t\t\t\t\tretval = -EIO;\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t\t}\n-\n-\t\t\t/*\n-\t\t\t * We have the folio, copy it to user space buffer.\n-\t\t\t */\n-\t\t\tcopied = copy_folio_to_iter(folio, offset, want, to);\n-\t\t\tfolio_put(folio);\n-\t\t}\n-\t\toffset += copied;\n-\t\tretval += copied;\n-\t\tif (copied != nr \u0026\u0026 iov_iter_count(to)) {\n-\t\t\tif (!retval)\n-\t\t\t\tretval = -EFAULT;\n-\t\t\tbreak;\n-\t\t}\n-\t\tindex += offset \u003e\u003e huge_page_shift(h);\n-\t\toffset \u0026= ~huge_page_mask(h);\n-\t}\n-\tiocb-\u003eki_pos = ((loff_t)index \u003c\u003c huge_page_shift(h)) + offset;\n-\treturn retval;\n-}\n-\n static int hugetlbfs_write_begin(const struct kiocb *iocb,\n \t\t\tstruct address_space *mapping,\n \t\t\tloff_t pos, unsigned len,\n@@ -883,6 +776,17 @@ static struct inode *hugetlbfs_get_root(struct super_block *sb,\n \treturn inode;\n }\n \n+static void hugetlbfs_init_regular_inode(struct inode *inode)\n+{\n+\tstruct hstate *hstate = hstate_inode(inode);\n+\tunsigned int order = huge_page_order(hstate);\n+\n+\tinode-\u003ei_op = \u0026hugetlbfs_inode_operations;\n+\tinode-\u003ei_fop = \u0026hugetlbfs_file_operations;\n+\tmapping_set_folio_order_range(inode-\u003ei_mapping, order, order);\n+\tmapping_set_authoritative(inode-\u003ei_mapping);\n+}\n+\n /*\n  * Hugetlbfs is not reclaimable; therefore its i_mmap_rwsem will never\n  * be taken from reclaim -- unlike regular filesystems. This needs an\n@@ -926,8 +830,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb,\n \t\t\tinit_special_inode(inode, mode, dev);\n \t\t\tbreak;\n \t\tcase S_IFREG:\n-\t\t\tinode-\u003ei_op = \u0026hugetlbfs_inode_operations;\n-\t\t\tinode-\u003ei_fop = \u0026hugetlbfs_file_operations;\n+\t\t\thugetlbfs_init_regular_inode(inode);\n \t\t\tbreak;\n \t\tcase S_IFDIR:\n \t\t\tinode-\u003ei_op = \u0026hugetlbfs_dir_inode_operations;\n@@ -1206,7 +1109,7 @@ static void init_once(void *foo)\n }\n \n static const struct file_operations hugetlbfs_file_operations = {\n-\t.read_iter\t\t= hugetlbfs_read_iter,\n+\t.read_iter\t\t= generic_file_read_iter,\n \t.mmap\t\t\t= hugetlbfs_file_mmap,\n \t.fsync\t\t\t= noop_fsync,\n \t.get_unmapped_area\t= hugetlb_get_unmapped_area,\ndiff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h\nindex 2abaf99321e90..1878642e75ec5 100644\n--- a/include/linux/hugetlb.h\n+++ b/include/linux/hugetlb.h\n@@ -165,7 +165,7 @@ bool hugetlbfs_pagecache_present(struct hstate *h,\n \t\t\t\t struct vm_area_struct *vma,\n \t\t\t\t unsigned long address);\n \n-struct address_space *hugetlb_folio_mapping_lock_write(struct folio *folio);\n+struct address_space *hugetlb_folio_mapping_lock_write(const struct folio *folio);\n \n extern int movable_gigantic_pages __read_mostly;\n extern int sysctl_hugetlb_shm_group __read_mostly;\n@@ -292,8 +292,7 @@ static inline unsigned long hugetlb_total_pages(void)\n \treturn 0;\n }\n \n-static inline struct address_space *hugetlb_folio_mapping_lock_write(\n-\t\t\t\t\t\t\tstruct folio *folio)\n+static inline struct address_space *hugetlb_folio_mapping_lock_write(const struct folio *folio)\n {\n \treturn NULL;\n }\n@@ -595,8 +594,8 @@ enum hugetlb_page_flags {\n #ifdef CONFIG_HUGETLB_PAGE\n #define TESTHPAGEFLAG(uname, flname)\t\t\t\t\\\n static __always_inline\t\t\t\t\t\t\\\n-bool folio_test_hugetlb_##flname(struct folio *folio)\t\t\\\n-\t{\tvoid *private = \u0026folio-\u003eprivate;\t\t\\\n+bool folio_test_hugetlb_##flname(const struct folio *folio)\t\\\n+\t{\tconst void *private = \u0026folio-\u003eprivate;\t\t\\\n \t\treturn test_bit(HPG_##flname, private);\t\t\\\n \t}\n \n@@ -616,7 +615,7 @@ void folio_clear_hugetlb_##flname(struct folio *folio)\t\t\\\n #else\n #define TESTHPAGEFLAG(uname, flname)\t\t\t\t\\\n static inline bool\t\t\t\t\t\t\\\n-folio_test_hugetlb_##flname(struct folio *folio)\t\t\\\n+folio_test_hugetlb_##flname(const struct folio *folio)\t\t\\\n \t{ return 0; }\n \n #define SETHPAGEFLAG(uname, flname)\t\t\t\t\\\n@@ -727,7 +726,7 @@ static inline struct hugepage_subpool *subpool_inode(struct inode *inode)\n \treturn HUGETLBFS_SB(inode-\u003ei_sb)-\u003espool;\n }\n \n-static inline struct hugepage_subpool *hugetlb_folio_subpool(struct folio *folio)\n+static inline struct hugepage_subpool *hugetlb_folio_subpool(const struct folio *folio)\n {\n \treturn folio-\u003e_hugetlb_subpool;\n }\n@@ -857,7 +856,7 @@ static inline bool arch_has_huge_bootmem_alloc(void)\n }\n #endif\n \n-static inline struct hstate *folio_hstate(struct folio *folio)\n+static inline struct hstate *folio_hstate(const struct folio *folio)\n {\n \tVM_BUG_ON_FOLIO(!folio_test_hugetlb(folio), folio);\n \treturn size_to_hstate(folio_size(folio));\n@@ -1070,11 +1069,6 @@ void hugetlb_register_node(struct node *node);\n void hugetlb_unregister_node(struct node *node);\n #endif\n \n-/*\n- * Check if a given raw @page in a hugepage is HWPOISON.\n- */\n-bool is_raw_hwpoison_page_in_hugepage(struct page *page);\n-\n static inline unsigned long huge_page_mask_align(struct file *file)\n {\n \treturn PAGE_MASK \u0026 ~huge_page_mask(hstate_file(file));\n@@ -1088,7 +1082,7 @@ static inline unsigned long huge_page_mask_align(struct file *file)\n \treturn 0;\n }\n \n-static inline struct hugepage_subpool *hugetlb_folio_subpool(struct folio *folio)\n+static inline struct hugepage_subpool *hugetlb_folio_subpool(const struct folio *folio)\n {\n \treturn NULL;\n }\n@@ -1157,7 +1151,7 @@ static inline struct hstate *hstate_vma(struct vm_area_struct *vma)\n \treturn NULL;\n }\n \n-static inline struct hstate *folio_hstate(struct folio *folio)\n+static inline struct hstate *folio_hstate(const struct folio *folio)\n {\n \treturn NULL;\n }\ndiff --git a/include/linux/mm_types.h b/include/linux/mm_types.h\nindex b18c2b2e7d2c6..86a2d0fee557b 100644\n--- a/include/linux/mm_types.h\n+++ b/include/linux/mm_types.h\n@@ -387,7 +387,7 @@ typedef unsigned short mm_id_t;\n  * @_hugetlb_subpool: Do not use directly, use accessor in hugetlb.h.\n  * @_hugetlb_cgroup: Do not use directly, use accessor in hugetlb_cgroup.h.\n  * @_hugetlb_cgroup_rsvd: Do not use directly, use accessor in hugetlb_cgroup.h.\n- * @_hugetlb_hwpoison: Do not use directly, call raw_hwp_list_head().\n+ * @hugetlb_hwpoison: List of pages with hwpoison.\n  * @_deferred_list: Folios to be split under memory pressure.\n  * @_unused_slab_obj_exts: Placeholder to match obj_exts in struct slab.\n  *\n@@ -499,7 +499,7 @@ struct folio {\n \t\t\tvoid *_hugetlb_subpool;\n \t\t\tvoid *_hugetlb_cgroup;\n \t\t\tvoid *_hugetlb_cgroup_rsvd;\n-\t\t\tvoid *_hugetlb_hwpoison;\n+\t\t\tstruct hwp_page *hugetlb_hwpoison;\n \t/* private: the union with struct page is transitional */\n \t\t};\n \t\tstruct page __page_3;\ndiff --git a/include/linux/page-flags.h b/include/linux/page-flags.h\nindex 7223f6f4e2b40..07cc7854b2de1 100644\n--- a/include/linux/page-flags.h\n+++ b/include/linux/page-flags.h\n@@ -893,14 +893,19 @@ static inline int PageTransCompound(const struct page *page)\n TESTPAGEFLAG_FALSE(TransCompound, transcompound)\n #endif\n \n-#if defined(CONFIG_MEMORY_FAILURE) \u0026\u0026 defined(CONFIG_TRANSPARENT_HUGEPAGE)\n+#if defined(CONFIG_MEMORY_FAILURE) \u0026\u0026 defined(CONFIG_LARGE_FOLIO)\n /*\n- * PageHasHWPoisoned indicates that at least one subpage is hwpoisoned in the\n- * compound page.\n+ * folio_has_hwpoisoned indicates that at least one page is hwpoisoned in the\n+ * folio.  That page will usually also have the HWPoison flag set, but this\n+ * is not possible for folios which have HVO (see memory-failure for the\n+ * scheme used in that case).  You probably don't want to call this directly;\n+ * use folio_has_hwpoisoned_page() instead.\n  *\n  * This flag is set by hwpoison handler.  Cleared by THP split or free page.\n  */\n FOLIO_FLAG(has_hwpoisoned, FOLIO_SECOND_PAGE)\n+FOLIO_TEST_SET_FLAG(has_hwpoisoned, FOLIO_SECOND_PAGE)\n+FOLIO_TEST_CLEAR_FLAG(has_hwpoisoned, FOLIO_SECOND_PAGE)\n #else\n FOLIO_FLAG_FALSE(has_hwpoisoned)\n #endif\n@@ -1041,8 +1046,29 @@ PAGE_TYPE_OPS(Slab, slab, slab)\n \n #ifdef CONFIG_HUGETLB_PAGE\n FOLIO_TYPE_OPS(hugetlb, hugetlb)\n+\n+#ifdef CONFIG_MEMORY_FAILURE\n+static inline bool folio_test_huge_poison(const struct folio *folio)\n+{\n+\treturn (READ_ONCE(folio-\u003epage.page_type) \u003e\u003e 23) ==\n+\t\t((PGTY_hugetlb \u003c\u003c 1) | 1);\n+}\n+\n+static inline void folio_set_huge_poison(struct folio *folio)\n+{\n+\tfolio-\u003epage.page_type |= (1 \u003c\u003c 23);\n+}\n+\n+static inline void folio_clear_huge_poison(struct folio *folio)\n+{\n+\tfolio-\u003epage.page_type \u0026= ~(1 \u003c\u003c 23);\n+}\n+#else\n+FOLIO_TEST_FLAG_FALSE(huge_poison)\n+#endif\n #else\n FOLIO_TEST_FLAG_FALSE(hugetlb)\n+FOLIO_TEST_FLAG_FALSE(huge_poison)\n #endif\n \n PAGE_TYPE_OPS(Zsmalloc, zsmalloc, zsmalloc)\n@@ -1068,10 +1094,14 @@ static inline bool PageHuge(const struct page *page)\n \treturn folio_test_hugetlb(page_folio(page));\n }\n \n+bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page);\n+bool hugetlb_unref_page_hwpoison(const struct page *page);\n+\n /*\n- * Check if a page is currently marked HWPoisoned. Note that this check is\n- * best effort only and inherently racy: there is no way to synchronize with\n- * failing hardware.\n+ * Check if a page is currently marked HWPoisoned.  This check is best\n+ * effort only and inherently racy: there is no way to synchronize with\n+ * failing hardware.  The caller may not have a refcount on the folio\n+ * containing the page, so we must be careful to not trip any assertions.\n  */\n static inline bool is_page_hwpoison(const struct page *page)\n {\n@@ -1080,13 +1110,26 @@ static inline bool is_page_hwpoison(const struct page *page)\n \tif (PageHWPoison(page))\n \t\treturn true;\n \tfolio = page_folio(page);\n-\treturn folio_test_hugetlb(folio) \u0026\u0026 PageHWPoison(\u0026folio-\u003epage);\n+\tif (folio_test_huge_poison(folio))\n+\t\treturn hugetlb_unref_page_hwpoison(page);\n+\t/* In case we raced with hugetlb transferring flags */\n+\treturn PageHWPoison(page);\n+}\n+\n+static inline bool is_ref_page_hwpoison(const struct folio *folio,\n+\t\tconst struct page *page)\n+{\n+\tif (PageHWPoison(page))\n+\t\treturn true;\n+\tif (folio_test_hugetlb(folio))\n+\t\treturn hugetlb_page_hwpoison(folio, page);\n+\treturn false;\n }\n \n-static inline bool folio_contain_hwpoisoned_page(struct folio *folio)\n+static inline bool folio_has_hwpoisoned_page(const struct folio *folio)\n {\n-\treturn folio_test_hwpoison(folio) ||\n-\t    (folio_test_large(folio) \u0026\u0026 folio_test_has_hwpoisoned(folio));\n+\treturn PageHWPoison(\u0026folio-\u003epage) ||\n+\t       (folio_test_large(folio) \u0026\u0026 folio_test_has_hwpoisoned(folio));\n }\n \n bool is_free_buddy_page(const struct page *page);\ndiff --git a/include/linux/pagemap.h b/include/linux/pagemap.h\nindex 2c3718d592d6d..22d48935ffda7 100644\n--- a/include/linux/pagemap.h\n+++ b/include/linux/pagemap.h\n@@ -210,6 +210,7 @@ enum mapping_flags {\n \tAS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM = 9,\n \tAS_KERNEL_FILE = 10,\t/* mapping for a fake kernel file that shouldn't\n \t\t\t\t   account usage to user cgroups */\n+\tAS_AUTHORITATIVE = 11,\t/* If we miss in the page cache, it's a hole */\n \t/* Bits 16-25 are used for FOLIO_ORDER */\n \tAS_FOLIO_ORDER_BITS = 5,\n \tAS_FOLIO_ORDER_MIN = 16,\n@@ -345,6 +346,16 @@ static inline bool mapping_writeback_may_deadlock_on_reclaim(const struct addres\n \treturn test_bit(AS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM, \u0026mapping-\u003eflags);\n }\n \n+static inline void mapping_set_authoritative(struct address_space *mapping)\n+{\n+\tset_bit(AS_AUTHORITATIVE, \u0026mapping-\u003eflags);\n+}\n+\n+static inline bool mapping_is_authoritative(const struct address_space *mapping)\n+{\n+\treturn test_bit(AS_AUTHORITATIVE, \u0026mapping-\u003eflags);\n+}\n+\n static inline gfp_t mapping_gfp_mask(const struct address_space *mapping)\n {\n \treturn mapping-\u003egfp_mask;\n@@ -373,7 +384,7 @@ static inline void mapping_set_gfp_mask(struct address_space *m, gfp_t mask)\n  * assumptions about maximum order if THP are disabled, but 8 seems like\n  * a good order (that's 1MB if you're using 4kB pages)\n  */\n-#ifdef CONFIG_TRANSPARENT_HUGEPAGE\n+#ifdef CONFIG_LARGE_FOLIO\n #define PREFERRED_MAX_PAGECACHE_ORDER\tHPAGE_PMD_ORDER\n #else\n #define PREFERRED_MAX_PAGECACHE_ORDER\t8\n@@ -394,7 +405,7 @@ static inline void mapping_set_gfp_mask(struct address_space *m, gfp_t mask)\n  */\n static inline size_t mapping_max_folio_size_supported(void)\n {\n-\tif (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))\n+\tif (IS_ENABLED(CONFIG_LARGE_FOLIO))\n \t\treturn 1U \u003c\u003c (PAGE_SHIFT + MAX_PAGECACHE_ORDER);\n \treturn PAGE_SIZE;\n }\n@@ -405,29 +416,23 @@ static inline size_t mapping_max_folio_size_supported(void)\n  * @min: Minimum folio order (between 0-MAX_PAGECACHE_ORDER inclusive).\n  * @max: Maximum folio order (between @min-MAX_PAGECACHE_ORDER inclusive).\n  *\n- * The filesystem should call this function in its inode constructor to\n- * indicate which base size (min) and maximum size (max) of folio the VFS\n- * can use to cache the contents of the file.  This should only be used\n- * if the filesystem needs special handling of folio sizes (ie there is\n- * something the core cannot know).\n+ * The filesystem should call this function in its inode constructor\n+ * to indicate which size folios can be used to cache the contents of\n+ * the inode.  This should only be used if the filesystem needs special\n+ * handling of folio sizes (ie there is something the core cannot know).\n  * Do not tune it based on, eg, i_size.\n  *\n+ * hugetlb calls this with orders larger than MAX_PAGECACHE_ORDER.\n+ * Normal filesystems should not do this.\n+ *\n  * Context: This should not be called while the inode is active as it\n  * is non-atomic.\n  */\n static inline void mapping_set_folio_order_range(struct address_space *mapping,\n-\t\t\t\t\t\t unsigned int min,\n-\t\t\t\t\t\t unsigned int max)\n+\t\tunsigned int min, unsigned int max)\n {\n-\tif (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))\n+\tif (!IS_ENABLED(CONFIG_LARGE_FOLIO))\n \t\treturn;\n-\n-\tif (min \u003e MAX_PAGECACHE_ORDER)\n-\t\tmin = MAX_PAGECACHE_ORDER;\n-\n-\tif (max \u003e MAX_PAGECACHE_ORDER)\n-\t\tmax = MAX_PAGECACHE_ORDER;\n-\n \tif (max \u003c min)\n \t\tmax = min;\n \n@@ -460,7 +465,7 @@ static inline void mapping_set_large_folios(struct address_space *mapping)\n static inline unsigned int\n mapping_max_folio_order(const struct address_space *mapping)\n {\n-\tif (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))\n+\tif (!IS_ENABLED(CONFIG_LARGE_FOLIO))\n \t\treturn 0;\n \treturn (mapping-\u003eflags \u0026 AS_FOLIO_ORDER_MAX_MASK) \u003e\u003e AS_FOLIO_ORDER_MAX;\n }\n@@ -468,7 +473,7 @@ mapping_max_folio_order(const struct address_space *mapping)\n static inline unsigned int\n mapping_min_folio_order(const struct address_space *mapping)\n {\n-\tif (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))\n+\tif (!IS_ENABLED(CONFIG_LARGE_FOLIO))\n \t\treturn 0;\n \treturn (mapping-\u003eflags \u0026 AS_FOLIO_ORDER_MIN_MASK) \u003e\u003e AS_FOLIO_ORDER_MIN;\n }\n@@ -524,7 +529,7 @@ static inline bool mapping_large_folio_support(const struct address_space *mappi\n  *\n  * Return: True if PMD-sized folios are supported, otherwise false.\n  */\n-#ifdef CONFIG_TRANSPARENT_HUGEPAGE\n+#ifdef CONFIG_LARGE_FOLIO\n static inline bool mapping_pmd_folio_support(const struct address_space *mapping)\n {\n \t/* AS_FOLIO_ORDER is only reasonable for pagecache folios */\ndiff --git a/mm/Kconfig b/mm/Kconfig\nindex 9e0ca48249054..e666dd14ca0c3 100644\n--- a/mm/Kconfig\n+++ b/mm/Kconfig\n@@ -843,11 +843,15 @@ config PERSISTENT_HUGE_ZERO_FOLIO\n config MM_ID\n \tdef_bool n\n \n+config LARGE_FOLIO\n+\tdef_bool n\n+\tselect XARRAY_MULTI\n+\n menuconfig TRANSPARENT_HUGEPAGE\n \tbool \"Transparent Hugepage Support\"\n \tdepends on HAVE_ARCH_TRANSPARENT_HUGEPAGE \u0026\u0026 !PREEMPT_RT\n \tselect COMPACTION\n-\tselect XARRAY_MULTI\n+\tselect LARGE_FOLIO\n \tselect MM_ID\n \thelp\n \t  Transparent Hugepages allows the kernel to use huge pages and\ndiff --git a/mm/filemap.c b/mm/filemap.c\nindex 58eb9d2406434..5a8cc20e624e4 100644\n--- a/mm/filemap.c\n+++ b/mm/filemap.c\n@@ -2483,6 +2483,8 @@ static void filemap_get_read_batch(struct address_space *mapping,\n \n \t\tif (!folio_batch_add(fbatch, folio))\n \t\t\tbreak;\n+\t\tif (folio_has_hwpoisoned_page(folio))\n+\t\t\tbreak;\n \t\tif (!folio_test_uptodate(folio))\n \t\t\tbreak;\n \t\tif (folio_test_readahead(folio))\n@@ -2695,6 +2697,8 @@ static int filemap_get_pages(struct kiocb *iocb, size_t count,\n \tif (!folio_batch_count(fbatch)) {\n \t\tDEFINE_READAHEAD(ractl, filp, \u0026filp-\u003ef_ra, mapping, index);\n \n+\t\tif (mapping_is_authoritative(mapping))\n+\t\t\treturn 0;\n \t\tif (iocb-\u003eki_flags \u0026 IOCB_NOIO)\n \t\t\treturn -EAGAIN;\n \t\tif (iocb-\u003eki_flags \u0026 IOCB_NOWAIT)\n@@ -2749,6 +2753,29 @@ static inline bool pos_same_folio(loff_t pos1, loff_t pos2, struct folio *folio)\n \treturn (pos1 \u003e\u003e shift == pos2 \u003e\u003e shift);\n }\n \n+static size_t adjust_range_hwpoison(const struct folio *folio, size_t offset,\n+\t\tsize_t bytes)\n+{\n+\tconst struct page *page = folio_page(folio, offset / PAGE_SIZE);\n+\tsize_t safe_bytes;\n+\n+\tif (!folio_has_hwpoisoned_page(folio))\n+\t\treturn bytes;\n+\tif (is_ref_page_hwpoison(folio, page))\n+\t\treturn 0;\n+\n+\t/* Safe to read the remaining bytes in this page. */\n+\tsafe_bytes = PAGE_SIZE - (offset % PAGE_SIZE);\n+\tpage++;\n+\n+\t/* Check each remaining page as long as we are not done yet. */\n+\tfor (; safe_bytes \u003c bytes; safe_bytes += PAGE_SIZE, page++)\n+\t\tif (is_ref_page_hwpoison(folio, page))\n+\t\t\tbreak;\n+\n+\treturn min(safe_bytes, bytes);\n+}\n+\n static void filemap_end_dropbehind_read(struct folio *folio)\n {\n \tif (!folio_test_dropbehind(folio))\n@@ -2828,6 +2855,22 @@ ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,\n \t\t\tgoto put_folios;\n \t\tend_offset = min_t(loff_t, isize, iocb-\u003eki_pos + iter-\u003ecount);\n \n+\t\tif (!folio_batch_count(\u0026fbatch)) {\n+\t\t\tsize_t fsize = mapping_min_folio_nrbytes(mapping);\n+\t\t\tsize_t offset = iocb-\u003eki_pos \u0026 (fsize - 1);\n+\t\t\tsize_t bytes = min_t(loff_t, end_offset - iocb-\u003eki_pos,\n+\t\t\t\t\t     fsize - offset);\n+\t\t\tsize_t copied = iov_iter_zero(bytes, iter);\n+\n+\t\t\talready_read += copied;\n+\t\t\tiocb-\u003eki_pos += copied;\n+\t\t\tlast_pos = iocb-\u003eki_pos;\n+\n+\t\t\tif (copied \u003c bytes)\n+\t\t\t\terror = -EFAULT;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\t/*\n \t\t * Once we start copying data, we don't want to be touching any\n \t\t * cachelines that might be contended:\n@@ -2862,14 +2905,18 @@ ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,\n \t\t\tif (writably_mapped)\n \t\t\t\tflush_dcache_folio(folio);\n \n-\t\t\tcopied = copy_folio_to_iter(folio, offset, bytes, iter);\n+\t\t\tcopied = adjust_range_hwpoison(folio, offset, bytes);\n+\t\t\tif (copied \u003c bytes)\n+\t\t\t\terror = -EIO;\n+\t\t\tcopied = copy_folio_to_iter(folio, offset, copied, iter);\n \n \t\t\talready_read += copied;\n \t\t\tiocb-\u003eki_pos += copied;\n \t\t\tlast_pos = iocb-\u003eki_pos;\n \n \t\t\tif (copied \u003c bytes) {\n-\t\t\t\terror = -EFAULT;\n+\t\t\t\tif (!error)\n+\t\t\t\t\terror = -EFAULT;\n \t\t\t\tbreak;\n \t\t\t}\n \t\t}\ndiff --git a/mm/huge_memory.c b/mm/huge_memory.c\nindex 2bccb0a53a0a6..3533403091074 100644\n--- a/mm/huge_memory.c\n+++ b/mm/huge_memory.c\n@@ -4413,7 +4413,7 @@ static bool thp_underused(struct folio *folio)\n \tif (khugepaged_max_ptes_none == HPAGE_PMD_NR - 1)\n \t\treturn false;\n \n-\tif (folio_contain_hwpoisoned_page(folio))\n+\tif (folio_has_hwpoisoned_page(folio))\n \t\treturn false;\n \n \tfor (i = 0; i \u003c folio_nr_pages(folio); i++) {\ndiff --git a/mm/hugetlb.c b/mm/hugetlb.c\nindex 571212b80835e..40ae967b9eccc 100644\n--- a/mm/hugetlb.c\n+++ b/mm/hugetlb.c\n@@ -1255,7 +1255,7 @@ static struct folio *dequeue_hugetlb_folio_node_exact(struct hstate *h,\n \t\tif (pin \u0026\u0026 !folio_is_longterm_pinnable(folio))\n \t\t\tcontinue;\n \n-\t\tif (folio_test_hwpoison(folio))\n+\t\tif (folio_test_has_hwpoisoned(folio))\n \t\t\tcontinue;\n \n \t\tif (is_migrate_isolate_page(\u0026folio-\u003epage))\n@@ -1381,6 +1381,18 @@ static struct folio *alloc_gigantic_frozen_folio(int order, gfp_t gfp_mask, int\n }\n #endif\n \n+static void folio_clear_hugetlb(struct folio *folio)\n+{\n+\t/*\n+\t * Move HWPoison flag to each error page\n+\t * which makes any healthy pages reusable.\n+\t */\n+\tif (unlikely(folio_test_has_hwpoisoned(folio)))\n+\t\tfolio_clear_hugetlb_hwpoison(folio);\n+\n+\t__folio_clear_hugetlb(folio);\n+}\n+\n /*\n  * Remove hugetlb folio from lists.\n  * If vmemmap exists for the folio, clear the hugetlb flag so that the\n@@ -1419,7 +1431,7 @@ void remove_hugetlb_folio(struct hstate *h, struct folio *folio,\n \t * to tail struct pages.\n \t */\n \tif (!folio_test_hugetlb_vmemmap_optimized(folio))\n-\t\t__folio_clear_hugetlb(folio);\n+\t\tfolio_clear_hugetlb(folio);\n \n \th-\u003enr_huge_pages--;\n \th-\u003enr_huge_pages_node[nid]--;\n@@ -1493,17 +1505,10 @@ static void __update_and_free_hugetlb_folio(struct hstate *h,\n \t */\n \tif (folio_test_hugetlb(folio)) {\n \t\tspin_lock_irq(\u0026hugetlb_lock);\n-\t\t__folio_clear_hugetlb(folio);\n+\t\tfolio_clear_hugetlb(folio);\n \t\tspin_unlock_irq(\u0026hugetlb_lock);\n \t}\n \n-\t/*\n-\t * Move PageHWPoison flag from head page to the raw error pages,\n-\t * which makes any healthy subpages reusable.\n-\t */\n-\tif (unlikely(folio_test_hwpoison(folio)))\n-\t\tfolio_clear_hugetlb_hwpoison(folio);\n-\n \tVM_BUG_ON_FOLIO(folio_ref_count(folio), folio);\n \tif (folio_test_hugetlb_cma(folio))\n \t\thugetlb_cma_free_frozen_folio(folio);\n@@ -1595,7 +1600,7 @@ static void bulk_vmemmap_restore_error(struct hstate *h,\n \t\tlist_for_each_entry_safe(folio, t_folio, non_hvo_folios, lru) {\n \t\t\tlist_del(\u0026folio-\u003elru);\n \t\t\tspin_lock_irq(\u0026hugetlb_lock);\n-\t\t\t__folio_clear_hugetlb(folio);\n+\t\t\tfolio_clear_hugetlb(folio);\n \t\t\tspin_unlock_irq(\u0026hugetlb_lock);\n \t\t\tupdate_and_free_hugetlb_folio(h, folio, false);\n \t\t\tcond_resched();\n@@ -1620,7 +1625,7 @@ static void bulk_vmemmap_restore_error(struct hstate *h,\n \t\t\t} else {\n \t\t\t\tlist_del(\u0026folio-\u003elru);\n \t\t\t\tspin_lock_irq(\u0026hugetlb_lock);\n-\t\t\t\t__folio_clear_hugetlb(folio);\n+\t\t\t\tfolio_clear_hugetlb(folio);\n \t\t\t\tspin_unlock_irq(\u0026hugetlb_lock);\n \t\t\t\tupdate_and_free_hugetlb_folio(h, folio, false);\n \t\t\t\tcond_resched();\n@@ -1653,14 +1658,14 @@ static void update_and_free_pages_bulk(struct hstate *h,\n \t * should only be pages on the non_hvo_folios list.\n \t * Do note that the non_hvo_folios list could be empty.\n \t * Without HVO enabled, ret will be 0 and there is no need to call\n-\t * __folio_clear_hugetlb as this was done previously.\n+\t * folio_clear_hugetlb as this was done previously.\n \t */\n \tVM_WARN_ON(!list_empty(folio_list));\n \tVM_WARN_ON(ret \u003c 0);\n \tif (!list_empty(\u0026non_hvo_folios) \u0026\u0026 ret) {\n \t\tspin_lock_irq(\u0026hugetlb_lock);\n \t\tlist_for_each_entry(folio, \u0026non_hvo_folios, lru)\n-\t\t\t__folio_clear_hugetlb(folio);\n+\t\t\tfolio_clear_hugetlb(folio);\n \t\tspin_unlock_irq(\u0026hugetlb_lock);\n \t}\n \n@@ -1775,7 +1780,7 @@ void init_new_hugetlb_folio(struct folio *folio)\n  * stable.  Due to locking order, we can only trylock_write.  If we can\n  * not get the lock, simply return NULL to caller.\n  */\n-struct address_space *hugetlb_folio_mapping_lock_write(struct folio *folio)\n+struct address_space *hugetlb_folio_mapping_lock_write(const struct folio *folio)\n {\n \tstruct address_space *mapping = folio_mapping(folio);\n \n@@ -3998,7 +4003,7 @@ long demote_pool_huge_page(struct hstate *src, nodemask_t *nodes_allowed,\n \t\tstruct folio *folio, *next;\n \n \t\tlist_for_each_entry_safe(folio, next, \u0026src-\u003ehugepage_freelists[node], lru) {\n-\t\t\tif (folio_test_hwpoison(folio))\n+\t\t\tif (folio_test_has_hwpoisoned(folio))\n \t\t\t\tcontinue;\n \n \t\t\tremove_hugetlb_folio(src, folio, false);\n@@ -5809,7 +5814,7 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,\n \t\t * don't have hwpoisoned swap entry for errored virtual address.\n \t\t * So we need to block hugepage fault by PG_hwpoison bit check.\n \t\t */\n-\t\tif (unlikely(folio_test_hwpoison(folio))) {\n+\t\tif (unlikely(folio_test_has_hwpoisoned(folio))) {\n \t\t\tret = VM_FAULT_HWPOISON_LARGE |\n \t\t\t\tVM_FAULT_SET_HINDEX(hstate_index(h));\n \t\t\tgoto backout_unlocked;\n@@ -6318,7 +6323,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,\n \tptl = huge_pte_lock(h, dst_mm, dst_pte);\n \n \tret = -EIO;\n-\tif (folio_test_hwpoison(folio))\n+\tif (folio_test_has_hwpoisoned(folio))\n \t\tgoto out_release_unlock;\n \n \tret = -EEXIST;\ndiff --git a/mm/memory-failure.c b/mm/memory-failure.c\nindex 51508a55c4055..ab2094ed320cc 100644\n--- a/mm/memory-failure.c\n+++ b/mm/memory-failure.c\n@@ -1808,63 +1808,89 @@ EXPORT_SYMBOL_GPL(mf_dax_kill_procs);\n \n #ifdef CONFIG_HUGETLB_PAGE\n \n+/* Protects all lists of hwp_pages */\n+static DEFINE_SPINLOCK(hwp_page_lock);\n+\n /*\n- * Struct raw_hwp_page represents information about \"raw error page\",\n- * constructing singly linked list from -\u003e_hugetlb_hwpoison field of folio.\n+ * hwp_page represents information about \"error page\",\n+ * constructing singly linked list from folio-\u003ehugetlb_hwpoison field.\n  */\n-struct raw_hwp_page {\n-\tstruct llist_node node;\n+struct hwp_page {\n+\tstruct hwp_page *next;\n \tstruct page *page;\n };\n \n-static inline struct llist_head *raw_hwp_list_head(struct folio *folio)\n+static bool precise_page_poisoned(const struct folio *folio,\n+\t\tconst struct page *page)\n {\n-\treturn (struct llist_head *)\u0026folio-\u003e_hugetlb_hwpoison;\n-}\n+\tconst struct hwp_page *p;\n+\tunsigned long flags;\n \n-bool is_raw_hwpoison_page_in_hugepage(struct page *page)\n-{\n-\tstruct llist_head *raw_hwp_head;\n-\tstruct raw_hwp_page *p;\n-\tstruct folio *folio = page_folio(page);\n-\tbool ret = false;\n-\n-\tif (!folio_test_hwpoison(folio))\n-\t\treturn false;\n-\n-\tif (!folio_test_hugetlb(folio))\n-\t\treturn PageHWPoison(page);\n+\tspin_lock_irqsave(\u0026hwp_page_lock, flags);\n \n \t/*\n-\t * When RawHwpUnreliable is set, kernel lost track of which subpages\n-\t * are HWPOISON. So return as if ALL subpages are HWPOISONed.\n+\t * When RawHwpUnreliable is set, kernel lost track of which pages\n+\t * are HWPOISON. So return as if ALL pages are HWPOISONed.\n \t */\n-\tif (folio_test_hugetlb_raw_hwp_unreliable(folio))\n+\tif (folio_test_hugetlb_raw_hwp_unreliable(folio)) {\n+\t\tspin_unlock_irqrestore(\u0026hwp_page_lock, flags);\n \t\treturn true;\n+\t}\n \n-\tmutex_lock(\u0026mf_mutex);\n-\n-\traw_hwp_head = raw_hwp_list_head(folio);\n-\tllist_for_each_entry(p, raw_hwp_head-\u003efirst, node) {\n-\t\tif (page == p-\u003epage) {\n-\t\t\tret = true;\n+\tfor (p = folio-\u003ehugetlb_hwpoison; p; p = p-\u003enext) {\n+\t\tif (page == p-\u003epage)\n \t\t\tbreak;\n-\t\t}\n \t}\n+\tspin_unlock_irqrestore(\u0026hwp_page_lock, flags);\n \n-\tmutex_unlock(\u0026mf_mutex);\n+\treturn p != NULL;\n+}\n \n+/*\n+ * Check if a given @page in a hugetlb folio is HWPOISON.\n+ */\n+bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page)\n+{\n+\tif (!folio_test_has_hwpoisoned(folio))\n+\t\treturn false;\n+\n+\treturn precise_page_poisoned(folio, page);\n+}\n+\n+/*\n+ * We have no reference on the folio containing this page.\n+ * The hugetlb_lock keeps hugetlb folios from being freed.\n+ */\n+bool hugetlb_unref_page_hwpoison(const struct page *page)\n+{\n+\tconst struct folio *folio;\n+\tunsigned long flags;\n+\tbool ret;\n+\n+\tspin_lock_irqsave(\u0026hugetlb_lock, flags);\n+\tfolio = page_folio(page);\n+\tif (!folio_test_huge_poison(folio)) {\n+\t\tret = PageHWPoison(page);\n+\t\tgoto unlock;\n+\t}\n+\n+\tret = precise_page_poisoned(folio, page);\n+unlock:\n+\tspin_unlock_irqrestore(\u0026hugetlb_lock, flags);\n \treturn ret;\n }\n \n static unsigned long __folio_free_raw_hwp(struct folio *folio, bool move_flag)\n {\n-\tstruct llist_node *head;\n-\tstruct raw_hwp_page *p, *next;\n+\tstruct hwp_page *p, *next;\n \tunsigned long count = 0;\n \n-\thead = llist_del_all(raw_hwp_list_head(folio));\n-\tllist_for_each_entry_safe(p, next, head, node) {\n+\tnext = folio-\u003ehugetlb_hwpoison;\n+\tfolio-\u003ehugetlb_hwpoison = NULL;\n+\n+\twhile (next) {\n+\t\tp = next;\n+\t\tnext = p-\u003enext;\n \t\tif (move_flag)\n \t\t\tSetPageHWPoison(p-\u003epage);\n \t\telse\n@@ -1881,34 +1907,58 @@ static unsigned long __folio_free_raw_hwp(struct folio *folio, bool move_flag)\n #define\tMF_HUGETLB_FOLIO_PRE_POISONED\t3\t/* folio already poisoned */\n #define\tMF_HUGETLB_PAGE_PRE_POISONED\t4\t/* exact page already poisoned */\n #define\tMF_HUGETLB_RETRY\t\t5\t/* hugepage is busy, retry */\n+\n+static inline int hugetlb_set_poison(struct folio *folio)\n+{\n+\tif (folio_test_set_has_hwpoisoned(folio))\n+\t\treturn MF_HUGETLB_FOLIO_PRE_POISONED;\n+\tfolio_set_huge_poison(folio);\n+\treturn 0;\n+}\n+\n+static inline int hugetlb_clear_poison(struct folio *folio)\n+{\n+\tif (!folio_test_has_hwpoisoned(folio))\n+\t\treturn -EBUSY;\n+\tfolio_clear_huge_poison(folio);\n+\tfolio_clear_has_hwpoisoned(folio);\n+\treturn 0;\n+}\n+\n /*\n  * Set hugetlb folio as hwpoisoned, update folio private raw hwpoison list\n  * to keep track of the poisoned pages.\n  */\n static int hugetlb_update_hwpoison(struct folio *folio, struct page *page)\n {\n-\tstruct llist_head *head;\n-\tstruct raw_hwp_page *raw_hwp;\n-\tstruct raw_hwp_page *p;\n-\tint ret = folio_test_set_hwpoison(folio) ? MF_HUGETLB_FOLIO_PRE_POISONED : 0;\n+\tstruct hwp_page *p;\n+\tunsigned long flags;\n+\tint ret = hugetlb_set_poison(folio);\n \n \t/*\n \t * Once the hwpoison hugepage has lost reliable raw error info,\n-\t * there is little meaning to keep additional error info precisely,\n-\t * so skip to add additional raw error info.\n+\t * there is no point in keeping additional error info precisely,\n+\t * so skip adding additional raw error info.\n \t */\n \tif (folio_test_hugetlb_raw_hwp_unreliable(folio))\n \t\treturn MF_HUGETLB_FOLIO_PRE_POISONED;\n-\thead = raw_hwp_list_head(folio);\n-\tllist_for_each_entry(p, head-\u003efirst, node) {\n+\n+\tspin_lock_irqsave(\u0026hwp_page_lock, flags);\n+\tfor (p = folio-\u003ehugetlb_hwpoison; p; p = p-\u003enext) {\n \t\tif (p-\u003epage == page)\n-\t\t\treturn MF_HUGETLB_PAGE_PRE_POISONED;\n+\t\t\tbreak;\n \t}\n \n-\traw_hwp = kmalloc_obj(struct raw_hwp_page, GFP_ATOMIC);\n-\tif (raw_hwp) {\n-\t\traw_hwp-\u003epage = page;\n-\t\tllist_add(\u0026raw_hwp-\u003enode, head);\n+\tif (p) {\n+\t\tspin_unlock_irqrestore(\u0026hwp_page_lock, flags);\n+\t\treturn MF_HUGETLB_PAGE_PRE_POISONED;\n+\t}\n+\n+\tp = kmalloc_obj(*p, GFP_ATOMIC);\n+\tif (p) {\n+\t\tp-\u003epage = page;\n+\t\tp-\u003enext = folio-\u003ehugetlb_hwpoison;\n+\t\tfolio-\u003ehugetlb_hwpoison = p;\n \t} else {\n \t\t/*\n \t\t * Failed to save raw error info.  We no longer trace all\n@@ -1917,16 +1967,20 @@ static int hugetlb_update_hwpoison(struct folio *folio, struct page *page)\n \t\t */\n \t\tfolio_set_hugetlb_raw_hwp_unreliable(folio);\n \t\t/*\n-\t\t * Once hugetlb_raw_hwp_unreliable is set, raw_hwp_page is not\n+\t\t * Once hugetlb_raw_hwp_unreliable is set, hwp_page is not\n \t\t * used any more, so free it.\n \t\t */\n \t\t__folio_free_raw_hwp(folio, false);\n \t}\n+\tspin_unlock_irqrestore(\u0026hwp_page_lock, flags);\n \treturn ret;\n }\n \n static unsigned long folio_free_raw_hwp(struct folio *folio, bool move_flag)\n {\n+\tunsigned long count;\n+\tunsigned long flags;\n+\n \t/*\n \t * hugetlb_vmemmap_optimized hugepages can't be freed because struct\n \t * pages for tail pages are required but they don't exist.\n@@ -1941,7 +1995,11 @@ static unsigned long folio_free_raw_hwp(struct folio *folio, bool move_flag)\n \tif (folio_test_hugetlb_raw_hwp_unreliable(folio))\n \t\treturn 0;\n \n-\treturn __folio_free_raw_hwp(folio, move_flag);\n+\tspin_lock_irqsave(\u0026hwp_page_lock, flags);\n+\tcount = __folio_free_raw_hwp(folio, move_flag);\n+\tspin_unlock_irqrestore(\u0026hwp_page_lock, flags);\n+\n+\treturn count;\n }\n \n void folio_clear_hugetlb_hwpoison(struct folio *folio)\n@@ -1950,8 +2008,8 @@ void folio_clear_hugetlb_hwpoison(struct folio *folio)\n \t\treturn;\n \tif (folio_test_hugetlb_vmemmap_optimized(folio))\n \t\treturn;\n-\tfolio_clear_hwpoison(folio);\n \tfolio_free_raw_hwp(folio, true);\n+\tfolio_clear_has_hwpoisoned(folio);\n }\n \n static int get_huge_page_for_hwpoison(unsigned long pfn, int flags,\n@@ -2104,6 +2162,11 @@ static inline unsigned long folio_free_raw_hwp(struct folio *folio, bool flag)\n {\n \treturn 0;\n }\n+\n+static inline int hugetlb_clear_poison(struct folio *folio)\n+{\n+\treturn 0;\n+}\n #endif\t/* CONFIG_HUGETLB_PAGE */\n \n /* Drop the extra refcount in case we come from madvise() */\n@@ -2695,8 +2758,8 @@ int unpoison_memory(unsigned long pfn)\n \t\tgoto unlock_mutex;\n \t}\n \n-\tif (!PageHWPoison(p)) {\n-\t\tunpoison_pr_info(\"%#lx: page was already unpoisoned\\n\",\n+\tif (!is_page_hwpoison(p)) {\n+\t\tunpoison_pr_info(\"%#lx: page is not poisoned\\n\",\n \t\t\t\t pfn, \u0026unpoison_rs);\n \t\tgoto unlock_mutex;\n \t}\n@@ -2725,13 +2788,19 @@ int unpoison_memory(unsigned long pfn)\n \n \tghp = get_hwpoison_page(p, MF_UNPOISON);\n \tif (!ghp) {\n+\t\tspin_lock_irq(\u0026hugetlb_lock);\n \t\tif (folio_test_hugetlb(folio)) {\n \t\t\thuge = true;\n \t\t\tcount = folio_free_raw_hwp(folio, false);\n-\t\t\tif (count == 0)\n+\t\t\tif (count == 0) {\n+\t\t\t\tspin_unlock_irq(\u0026hugetlb_lock);\n \t\t\t\tgoto unlock_mutex;\n+\t\t\t}\n+\t\t\tret = hugetlb_clear_poison(folio);\n+\t\t} else {\n+\t\t\tret = TestClearPageHWPoison(p) ? 0 : -EBUSY;\n \t\t}\n-\t\tret = folio_test_clear_hwpoison(folio) ? 0 : -EBUSY;\n+\t\tspin_unlock_irq(\u0026hugetlb_lock);\n \t} else if (ghp \u003c 0) {\n \t\tif (ghp == -EHWPOISON) {\n \t\t\tret = put_page_back_buddy(p) ? 0 : -EBUSY;\n@@ -2740,16 +2809,18 @@ int unpoison_memory(unsigned long pfn)\n \t\t\tunpoison_pr_info(\"%#lx: failed to grab page\\n\",\n \t\t\t\t\t pfn, \u0026unpoison_rs);\n \t\t}\n-\t} else {\n-\t\tif (folio_test_hugetlb(folio)) {\n-\t\t\thuge = true;\n-\t\t\tcount = folio_free_raw_hwp(folio, false);\n-\t\t\tif (count == 0) {\n-\t\t\t\tfolio_put(folio);\n-\t\t\t\tgoto unlock_mutex;\n-\t\t\t}\n+\t} else if (folio_test_hugetlb(folio)) {\n+\t\thuge = true;\n+\t\tcount = folio_free_raw_hwp(folio, false);\n+\t\tif (count == 0) {\n+\t\t\tfolio_put(folio);\n+\t\t\tgoto unlock_mutex;\n \t\t}\n-\n+\t\tret = hugetlb_clear_poison(folio);\n+\t\tfolio_put(folio);\n+\t\tif (!ret)\n+\t\t\tfolio_put(folio);\n+\t} else {\n \t\tfolio_put(folio);\n \t\tif (TestClearPageHWPoison(p)) {\n \t\t\tfolio_put(folio);\ndiff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c\nindex 7ac19fab22632..33007e8e546a0 100644\n--- a/mm/memory_hotplug.c\n+++ b/mm/memory_hotplug.c\n@@ -1813,7 +1813,7 @@ static void do_migrate_range(unsigned long start_pfn, unsigned long end_pfn)\n \t\tif (folio_test_large(folio))\n \t\t\tpfn = folio_pfn(folio) + folio_nr_pages(folio) - 1;\n \n-\t\tif (folio_contain_hwpoisoned_page(folio)) {\n+\t\tif (folio_has_hwpoisoned_page(folio)) {\n \t\t\t/*\n \t\t\t * unmap_poisoned_folio() cannot handle large folios\n \t\t\t * in all cases yet.\ndiff --git a/mm/rmap.c b/mm/rmap.c\nindex 1c77d5dc06e9f..fd19a0bfbfe76 100644\n--- a/mm/rmap.c\n+++ b/mm/rmap.c\n@@ -1978,6 +1978,22 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio,\n \t\t\t\t     FPB_RESPECT_WRITE | FPB_RESPECT_SOFT_DIRTY);\n }\n \n+/*\n+ * Since we cannot split a hugetlb folio, we want to insert a poison\n+ * entry into the page table for the whole folio even if only one page\n+ * is poisoned.  Otherwise, we've split down to the PTE level and we only\n+ * want to poison the precise page\n+ */\n+static bool ttu_create_hwpoison(const struct folio *folio,\n+\t\tconst struct page *page, enum ttu_flags flags)\n+{\n+\tif (!(flags \u0026 TTU_HWPOISON))\n+\t\treturn false;\n+\tif (folio_test_hugetlb(folio))\n+\t\treturn folio_test_has_hwpoisoned(folio);\n+\treturn PageHWPoison(page);\n+}\n+\n /*\n  * @arg: enum ttu_flags will be passed to this argument\n  */\n@@ -1993,7 +2009,6 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,\n \tenum ttu_flags flags = (enum ttu_flags)(long)arg;\n \tunsigned long nr_pages = 1, end_addr;\n \tunsigned long pfn;\n-\tunsigned long hsz = 0;\n \tint ptes = 0;\n \n \t/*\n@@ -2023,9 +2038,6 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,\n \t\t */\n \t\tadjust_range_if_pmd_sharing_possible(vma, \u0026range.start,\n \t\t\t\t\t\t     \u0026range.end);\n-\n-\t\t/* We need the huge page size for set_huge_pte_at() */\n-\t\thsz = huge_page_size(hstate_vma(vma));\n \t}\n \tmmu_notifier_invalidate_range_start(\u0026range);\n \n@@ -2121,7 +2133,8 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,\n \t\t\t * The try_to_unmap() is only passed a hugetlb page\n \t\t\t * in the case where the hugetlb page is poisoned.\n \t\t\t */\n-\t\t\tVM_BUG_ON_PAGE(!PageHWPoison(subpage), subpage);\n+\t\t\tVM_BUG_ON_FOLIO(!folio_has_hwpoisoned_page(folio),\n+\t\t\t\t\tfolio);\n \t\t\t/*\n \t\t\t * huge_pmd_unshare may unmap an entire PMD page.\n \t\t\t * There is no way of knowing exactly which PMDs may\n@@ -2200,12 +2213,12 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,\n \t\t/* Update high watermark before we lower rss */\n \t\tupdate_hiwater_rss(mm);\n \n-\t\tif (PageHWPoison(subpage) \u0026\u0026 (flags \u0026 TTU_HWPOISON)) {\n+\t\tif (ttu_create_hwpoison(folio, subpage, flags)) {\n \t\t\tpteval = swp_entry_to_pte(make_hwpoison_entry(subpage));\n \t\t\tif (folio_test_hugetlb(folio)) {\n \t\t\t\thugetlb_count_sub(folio_nr_pages(folio), mm);\n \t\t\t\tset_huge_pte_at(mm, address, pvmw.pte, pteval,\n-\t\t\t\t\t\thsz);\n+\t\t\t\t\t\tfolio_size(folio));\n \t\t\t} else {\n \t\t\t\tdec_mm_counter(mm, mm_counter(folio));\n \t\t\t\tset_pte_at(mm, address, pvmw.pte, pteval);\n@@ -2423,7 +2436,6 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,\n \tstruct mmu_notifier_range range;\n \tenum ttu_flags flags = (enum ttu_flags)(long)arg;\n \tunsigned long pfn;\n-\tunsigned long hsz = 0;\n \n \t/*\n \t * When racing against e.g. zap_pte_range() on another cpu,\n@@ -2452,9 +2464,6 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,\n \t\t */\n \t\tadjust_range_if_pmd_sharing_possible(vma, \u0026range.start,\n \t\t\t\t\t\t     \u0026range.end);\n-\n-\t\t/* We need the huge page size for set_huge_pte_at() */\n-\t\thsz = huge_page_size(hstate_vma(vma));\n \t}\n \tmmu_notifier_invalidate_range_start(\u0026range);\n \n@@ -2607,14 +2616,14 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,\n \t\t/* Update high watermark before we lower rss */\n \t\tupdate_hiwater_rss(mm);\n \n-\t\tif (PageHWPoison(subpage)) {\n+\t\tif (ttu_create_hwpoison(folio, subpage, TTU_HWPOISON)) {\n \t\t\tVM_WARN_ON_FOLIO(folio_is_device_private(folio), folio);\n \n \t\t\tpteval = swp_entry_to_pte(make_hwpoison_entry(subpage));\n \t\t\tif (folio_test_hugetlb(folio)) {\n \t\t\t\thugetlb_count_sub(folio_nr_pages(folio), mm);\n \t\t\t\tset_huge_pte_at(mm, address, pvmw.pte, pteval,\n-\t\t\t\t\t\thsz);\n+\t\t\t\t\t\tfolio_size(folio));\n \t\t\t} else {\n \t\t\t\tdec_mm_counter(mm, mm_counter(folio));\n \t\t\t\tset_pte_at(mm, address, pvmw.pte, pteval);\n@@ -2644,7 +2653,8 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,\n \t\t\tif (arch_unmap_one(mm, vma, address, pteval) \u003c 0) {\n \t\t\t\tif (folio_test_hugetlb(folio))\n \t\t\t\t\tset_huge_pte_at(mm, address, pvmw.pte,\n-\t\t\t\t\t\t\tpteval, hsz);\n+\t\t\t\t\t\t\tpteval,\n+\t\t\t\t\t\t\tfolio_size(folio));\n \t\t\t\telse\n \t\t\t\t\tset_pte_at(mm, address, pvmw.pte, pteval);\n \t\t\t\tret = false;\n@@ -2657,7 +2667,8 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,\n \t\t\t\tif (anon_exclusive \u0026\u0026\n \t\t\t\t    hugetlb_try_share_anon_rmap(folio)) {\n \t\t\t\t\tset_huge_pte_at(mm, address, pvmw.pte,\n-\t\t\t\t\t\t\tpteval, hsz);\n+\t\t\t\t\t\t\tpteval,\n+\t\t\t\t\t\t\tfolio_size(folio));\n \t\t\t\t\tret = false;\n \t\t\t\t\tpage_vma_mapped_walk_done(\u0026pvmw);\n \t\t\t\t\tbreak;\n@@ -2703,7 +2714,7 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,\n \t\t\t}\n \t\t\tif (folio_test_hugetlb(folio))\n \t\t\t\tset_huge_pte_at(mm, address, pvmw.pte, swp_pte,\n-\t\t\t\t\t\thsz);\n+\t\t\t\t\t\tfolio_size(folio));\n \t\t\telse\n \t\t\t\tset_pte_at(mm, address, pvmw.pte, swp_pte);\n \t\t\ttrace_set_migration_pte(address, pte_val(swp_pte),\ndiff --git a/mm/shmem.c b/mm/shmem.c\nindex b51f83c970bb3..492c9bef36207 100644\n--- a/mm/shmem.c\n+++ b/mm/shmem.c\n@@ -3240,7 +3240,7 @@ shmem_write_begin(const struct kiocb *iocb, struct address_space *mapping,\n \tif (ret)\n \t\treturn ret;\n \n-\tif (folio_contain_hwpoisoned_page(folio)) {\n+\tif (folio_has_hwpoisoned_page(folio)) {\n \t\tfolio_unlock(folio);\n \t\tfolio_put(folio);\n \t\treturn -EIO;\ndiff --git a/mm/vmscan.c b/mm/vmscan.c\nindex 35c3bb15ae96a..70e54ace0e741 100644\n--- a/mm/vmscan.c\n+++ b/mm/vmscan.c\n@@ -1086,7 +1086,7 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,\n \t\tif (!folio_trylock(folio))\n \t\t\tgoto keep;\n \n-\t\tif (folio_contain_hwpoisoned_page(folio)) {\n+\t\tif (folio_has_hwpoisoned_page(folio)) {\n \t\t\t/*\n \t\t\t * unmap_poisoned_folio() can't handle large\n \t\t\t * folio, just skip it. memory_failure() will\ndiff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c\nindex db57c5766ab60..94eedcc7f1a9e 100644\n--- a/virt/kvm/guest_memfd.c\n+++ b/virt/kvm/guest_memfd.c\n@@ -774,7 +774,7 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,\n \tif (IS_ERR(folio))\n \t\treturn folio;\n \n-\tif (folio_test_hwpoison(folio)) {\n+\tif (folio_has_hwpoisoned_page(folio)) {\n \t\tfolio_unlock(folio);\n \t\tfolio_put(folio);\n \t\treturn ERR_PTR(-EHWPOISON);\n"
}

2/1 2026/07/30 02:36 agent patch-evaluator
2m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "HUGETLBFS",
    "MEMORY_FAILURE"
  ],
  "FocusSymbols": [
    "hugetlb_page_hwpoison",
    "hugetlb_unref_page_hwpoison",
    "unpoison_memory",
    "folio_clear_hugetlb_hwpoison"
  ],
  "Reasoning": "The patch changes how hwpoison is handled for large folios and hugetlbfs. It modifies `filemap_read` to handle hwpoisoned folios and updates memory failure recovery logic. These are functional changes in core MM and hugetlbfs that should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 4320ddc1fa86f1285b57efbad741b30f5189a375
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Jul 30 02:36:37 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/Kconfig b/fs/Kconfig
index cf6ae64776e62..eddac4ed214b1 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 216e1a0dd0b23..cef7e61b827d7 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 (is_raw_hwpoison_page_in_hugepage(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_raw_hwpoison_page_in_hugepage(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_hwpoison(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,
@@ -883,6 +776,17 @@ 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);
+	mapping_set_authoritative(inode->i_mapping);
+}
+
 /*
  * Hugetlbfs is not reclaimable; therefore its i_mmap_rwsem will never
  * be taken from reclaim -- unlike regular filesystems. This needs an
@@ -926,8 +830,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;
@@ -1206,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,
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 2abaf99321e90..1878642e75ec5 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));
@@ -1070,11 +1069,6 @@ 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);
-
 static inline unsigned long huge_page_mask_align(struct file *file)
 {
 	return PAGE_MASK & ~huge_page_mask(hstate_file(file));
@@ -1088,7 +1082,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 +1151,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/include/linux/mm_types.h b/include/linux/mm_types.h
index b18c2b2e7d2c6..86a2d0fee557b 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 hwp_page *hugetlb_hwpoison;
 	/* private: the union with struct page is transitional */
 		};
 		struct page __page_3;
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 7223f6f4e2b40..07cc7854b2de1 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,29 @@ PAGE_TYPE_OPS(Slab, slab, slab)
 
 #ifdef CONFIG_HUGETLB_PAGE
 FOLIO_TYPE_OPS(hugetlb, hugetlb)
+
+#ifdef CONFIG_MEMORY_FAILURE
+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(huge_poison)
+#endif
 #else
 FOLIO_TEST_FLAG_FALSE(hugetlb)
+FOLIO_TEST_FLAG_FALSE(huge_poison)
 #endif
 
 PAGE_TYPE_OPS(Zsmalloc, zsmalloc, zsmalloc)
@@ -1068,10 +1094,14 @@ 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);
+
 /*
- * 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,13 +1110,26 @@ 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 hugetlb_unref_page_hwpoison(page);
+	/* In case we raced with hugetlb transferring flags */
+	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_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));
+	return PageHWPoison(&folio->page) ||
+	       (folio_test_large(folio) && folio_test_has_hwpoisoned(folio));
 }
 
 bool is_free_buddy_page(const struct page *page);
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 2c3718d592d6d..22d48935ffda7 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;
@@ -373,7 +384,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_LARGE_FOLIO
 #define PREFERRED_MAX_PAGECACHE_ORDER	HPAGE_PMD_ORDER
 #else
 #define PREFERRED_MAX_PAGECACHE_ORDER	8
@@ -394,7 +405,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_LARGE_FOLIO))
 		return 1U << (PAGE_SHIFT + MAX_PAGECACHE_ORDER);
 	return PAGE_SIZE;
 }
@@ -405,29 +416,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_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 +465,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_LARGE_FOLIO))
 		return 0;
 	return (mapping->flags & AS_FOLIO_ORDER_MAX_MASK) >> AS_FOLIO_ORDER_MAX;
 }
@@ -468,7 +473,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_LARGE_FOLIO))
 		return 0;
 	return (mapping->flags & AS_FOLIO_ORDER_MIN_MASK) >> AS_FOLIO_ORDER_MIN;
 }
@@ -524,7 +529,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_LARGE_FOLIO
 static inline bool mapping_pmd_folio_support(const struct address_space *mapping)
 {
 	/* AS_FOLIO_ORDER is only reasonable for pagecache folios */
diff --git a/mm/Kconfig b/mm/Kconfig
index 9e0ca48249054..e666dd14ca0c3 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/filemap.c b/mm/filemap.c
index 58eb9d2406434..5a8cc20e624e4 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))
@@ -2695,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)
@@ -2749,6 +2753,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))
@@ -2828,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:
@@ -2862,14 +2905,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;
 			}
 		}
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2bccb0a53a0a6..3533403091074 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/hugetlb.c b/mm/hugetlb.c
index 571212b80835e..40ae967b9eccc 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))
@@ -1381,6 +1381,18 @@ static struct folio *alloc_gigantic_frozen_folio(int order, gfp_t gfp_mask, int
 }
 #endif
 
+static void folio_clear_hugetlb(struct folio *folio)
+{
+	/*
+	 * Move HWPoison flag to each error page
+	 * which makes any healthy pages reusable.
+	 */
+	if (unlikely(folio_test_has_hwpoisoned(folio)))
+		folio_clear_hugetlb_hwpoison(folio);
+
+	__folio_clear_hugetlb(folio);
+}
+
 /*
  * Remove hugetlb folio from lists.
  * If vmemmap exists for the folio, clear the hugetlb flag so that the
@@ -1419,7 +1431,7 @@ void remove_hugetlb_folio(struct hstate *h, struct folio *folio,
 	 * to tail struct pages.
 	 */
 	if (!folio_test_hugetlb_vmemmap_optimized(folio))
-		__folio_clear_hugetlb(folio);
+		folio_clear_hugetlb(folio);
 
 	h->nr_huge_pages--;
 	h->nr_huge_pages_node[nid]--;
@@ -1493,17 +1505,10 @@ static void __update_and_free_hugetlb_folio(struct hstate *h,
 	 */
 	if (folio_test_hugetlb(folio)) {
 		spin_lock_irq(&hugetlb_lock);
-		__folio_clear_hugetlb(folio);
+		folio_clear_hugetlb(folio);
 		spin_unlock_irq(&hugetlb_lock);
 	}
 
-	/*
-	 * Move PageHWPoison flag from head page to the raw error pages,
-	 * which makes any healthy subpages reusable.
-	 */
-	if (unlikely(folio_test_hwpoison(folio)))
-		folio_clear_hugetlb_hwpoison(folio);
-
 	VM_BUG_ON_FOLIO(folio_ref_count(folio), folio);
 	if (folio_test_hugetlb_cma(folio))
 		hugetlb_cma_free_frozen_folio(folio);
@@ -1595,7 +1600,7 @@ static void bulk_vmemmap_restore_error(struct hstate *h,
 		list_for_each_entry_safe(folio, t_folio, non_hvo_folios, lru) {
 			list_del(&folio->lru);
 			spin_lock_irq(&hugetlb_lock);
-			__folio_clear_hugetlb(folio);
+			folio_clear_hugetlb(folio);
 			spin_unlock_irq(&hugetlb_lock);
 			update_and_free_hugetlb_folio(h, folio, false);
 			cond_resched();
@@ -1620,7 +1625,7 @@ static void bulk_vmemmap_restore_error(struct hstate *h,
 			} else {
 				list_del(&folio->lru);
 				spin_lock_irq(&hugetlb_lock);
-				__folio_clear_hugetlb(folio);
+				folio_clear_hugetlb(folio);
 				spin_unlock_irq(&hugetlb_lock);
 				update_and_free_hugetlb_folio(h, folio, false);
 				cond_resched();
@@ -1653,14 +1658,14 @@ static void update_and_free_pages_bulk(struct hstate *h,
 	 * should only be pages on the non_hvo_folios list.
 	 * Do note that the non_hvo_folios list could be empty.
 	 * Without HVO enabled, ret will be 0 and there is no need to call
-	 * __folio_clear_hugetlb as this was done previously.
+	 * folio_clear_hugetlb as this was done previously.
 	 */
 	VM_WARN_ON(!list_empty(folio_list));
 	VM_WARN_ON(ret < 0);
 	if (!list_empty(&non_hvo_folios) && ret) {
 		spin_lock_irq(&hugetlb_lock);
 		list_for_each_entry(folio, &non_hvo_folios, lru)
-			__folio_clear_hugetlb(folio);
+			folio_clear_hugetlb(folio);
 		spin_unlock_irq(&hugetlb_lock);
 	}
 
@@ -1775,7 +1780,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);
 
@@ -3998,7 +4003,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 +5814,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 +6323,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 51508a55c4055..ab2094ed320cc 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1808,63 +1808,89 @@ EXPORT_SYMBOL_GPL(mf_dax_kill_procs);
 
 #ifdef CONFIG_HUGETLB_PAGE
 
+/* Protects all lists of hwp_pages */
+static DEFINE_SPINLOCK(hwp_page_lock);
+
 /*
- * Struct raw_hwp_page represents information about "raw error page",
- * constructing singly linked list from ->_hugetlb_hwpoison field of folio.
+ * hwp_page represents information about "error page",
+ * constructing singly linked list from folio->hugetlb_hwpoison field.
  */
-struct raw_hwp_page {
-	struct llist_node node;
+struct hwp_page {
+	struct hwp_page *next;
 	struct page *page;
 };
 
-static inline struct llist_head *raw_hwp_list_head(struct folio *folio)
+static bool precise_page_poisoned(const struct folio *folio,
+		const struct page *page)
 {
-	return (struct llist_head *)&folio->_hugetlb_hwpoison;
-}
+	const struct hwp_page *p;
+	unsigned long flags;
 
-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;
-
-	if (!folio_test_hwpoison(folio))
-		return false;
-
-	if (!folio_test_hugetlb(folio))
-		return PageHWPoison(page);
+	spin_lock_irqsave(&hwp_page_lock, flags);
 
 	/*
-	 * 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))
+	if (folio_test_hugetlb_raw_hwp_unreliable(folio)) {
+		spin_unlock_irqrestore(&hwp_page_lock, flags);
 		return true;
+	}
 
-	mutex_lock(&mf_mutex);
-
-	raw_hwp_head = raw_hwp_list_head(folio);
-	llist_for_each_entry(p, raw_hwp_head->first, node) {
-		if (page == p->page) {
-			ret = true;
+	for (p = folio->hugetlb_hwpoison; p; p = p->next) {
+		if (page == p->page)
 			break;
-		}
 	}
+	spin_unlock_irqrestore(&hwp_page_lock, flags);
 
-	mutex_unlock(&mf_mutex);
+	return p != NULL;
+}
 
+/*
+ * 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;
-	struct raw_hwp_page *p, *next;
+	struct hwp_page *p, *next;
 	unsigned long count = 0;
 
-	head = llist_del_all(raw_hwp_list_head(folio));
-	llist_for_each_entry_safe(p, next, head, node) {
+	next = folio->hugetlb_hwpoison;
+	folio->hugetlb_hwpoison = NULL;
+
+	while (next) {
+		p = next;
+		next = p->next;
 		if (move_flag)
 			SetPageHWPoison(p->page);
 		else
@@ -1881,34 +1907,58 @@ 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_has_hwpoisoned(folio))
+		return -EBUSY;
+	folio_clear_huge_poison(folio);
+	folio_clear_has_hwpoisoned(folio);
+	return 0;
+}
+
 /*
  * Set hugetlb folio as hwpoisoned, update folio private raw hwpoison list
  * to keep track of the poisoned pages.
  */
 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;
+	struct hwp_page *p;
+	unsigned long flags;
+	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;
-	head = raw_hwp_list_head(folio);
-	llist_for_each_entry(p, head->first, node) {
+
+	spin_lock_irqsave(&hwp_page_lock, flags);
+	for (p = folio->hugetlb_hwpoison; p; p = p->next) {
 		if (p->page == page)
-			return MF_HUGETLB_PAGE_PRE_POISONED;
+			break;
 	}
 
-	raw_hwp = kmalloc_obj(struct raw_hwp_page, GFP_ATOMIC);
-	if (raw_hwp) {
-		raw_hwp->page = page;
-		llist_add(&raw_hwp->node, head);
+	if (p) {
+		spin_unlock_irqrestore(&hwp_page_lock, flags);
+		return MF_HUGETLB_PAGE_PRE_POISONED;
+	}
+
+	p = kmalloc_obj(*p, GFP_ATOMIC);
+	if (p) {
+		p->page = page;
+		p->next = folio->hugetlb_hwpoison;
+		folio->hugetlb_hwpoison = p;
 	} else {
 		/*
 		 * Failed to save raw error info.  We no longer trace all
@@ -1917,16 +1967,20 @@ static int hugetlb_update_hwpoison(struct folio *folio, struct page *page)
 		 */
 		folio_set_hugetlb_raw_hwp_unreliable(folio);
 		/*
-		 * Once hugetlb_raw_hwp_unreliable is set, raw_hwp_page is not
+		 * Once hugetlb_raw_hwp_unreliable is set, hwp_page is not
 		 * used any more, so free it.
 		 */
 		__folio_free_raw_hwp(folio, false);
 	}
+	spin_unlock_irqrestore(&hwp_page_lock, flags);
 	return ret;
 }
 
 static unsigned long folio_free_raw_hwp(struct folio *folio, bool move_flag)
 {
+	unsigned long count;
+	unsigned long flags;
+
 	/*
 	 * hugetlb_vmemmap_optimized hugepages can't be freed because struct
 	 * pages for tail pages are required but they don't exist.
@@ -1941,7 +1995,11 @@ static unsigned long folio_free_raw_hwp(struct folio *folio, bool move_flag)
 	if (folio_test_hugetlb_raw_hwp_unreliable(folio))
 		return 0;
 
-	return __folio_free_raw_hwp(folio, move_flag);
+	spin_lock_irqsave(&hwp_page_lock, flags);
+	count = __folio_free_raw_hwp(folio, move_flag);
+	spin_unlock_irqrestore(&hwp_page_lock, flags);
+
+	return count;
 }
 
 void folio_clear_hugetlb_hwpoison(struct folio *folio)
@@ -1950,8 +2008,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,
@@ -2104,6 +2162,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() */
@@ -2695,8 +2758,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;
 	}
@@ -2725,13 +2788,19 @@ int unpoison_memory(unsigned long pfn)
 
 	ghp = get_hwpoison_page(p, MF_UNPOISON);
 	if (!ghp) {
+		spin_lock_irq(&hugetlb_lock);
 		if (folio_test_hugetlb(folio)) {
 			huge = true;
 			count = folio_free_raw_hwp(folio, false);
-			if (count == 0)
+			if (count == 0) {
+				spin_unlock_irq(&hugetlb_lock);
 				goto unlock_mutex;
+			}
+			ret = hugetlb_clear_poison(folio);
+		} else {
+			ret = TestClearPageHWPoison(p) ? 0 : -EBUSY;
 		}
-		ret = folio_test_clear_hwpoison(folio) ? 0 : -EBUSY;
+		spin_unlock_irq(&hugetlb_lock);
 	} else if (ghp < 0) {
 		if (ghp == -EHWPOISON) {
 			ret = put_page_back_buddy(p) ? 0 : -EBUSY;
@@ -2740,16 +2809,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;
-			}
+	} else if (folio_test_hugetlb(folio)) {
+		huge = true;
+		count = folio_free_raw_hwp(folio, false);
+		if (count == 0) {
+			folio_put(folio);
+			goto unlock_mutex;
 		}
-
+		ret = hugetlb_clear_poison(folio);
+		folio_put(folio);
+		if (!ret)
+			folio_put(folio);
+	} else {
 		folio_put(folio);
 		if (TestClearPageHWPoison(p)) {
 			folio_put(folio);
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 7ac19fab22632..33007e8e546a0 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/rmap.c b/mm/rmap.c
index 1c77d5dc06e9f..fd19a0bfbfe76 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -1978,6 +1978,22 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio,
 				     FPB_RESPECT_WRITE | FPB_RESPECT_SOFT_DIRTY);
 }
 
+/*
+ * Since we cannot split a hugetlb folio, we want to insert a poison
+ * entry into the page table for the whole folio even if only one page
+ * is poisoned.  Otherwise, we've split down to the PTE level and we only
+ * want to poison the precise page
+ */
+static bool ttu_create_hwpoison(const struct folio *folio,
+		const struct page *page, enum ttu_flags flags)
+{
+	if (!(flags & TTU_HWPOISON))
+		return false;
+	if (folio_test_hugetlb(folio))
+		return folio_test_has_hwpoisoned(folio);
+	return PageHWPoison(page);
+}
+
 /*
  * @arg: enum ttu_flags will be passed to this argument
  */
@@ -1993,7 +2009,6 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,
 	enum ttu_flags flags = (enum ttu_flags)(long)arg;
 	unsigned long nr_pages = 1, end_addr;
 	unsigned long pfn;
-	unsigned long hsz = 0;
 	int ptes = 0;
 
 	/*
@@ -2023,9 +2038,6 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,
 		 */
 		adjust_range_if_pmd_sharing_possible(vma, &range.start,
 						     &range.end);
-
-		/* We need the huge page size for set_huge_pte_at() */
-		hsz = huge_page_size(hstate_vma(vma));
 	}
 	mmu_notifier_invalidate_range_start(&range);
 
@@ -2121,7 +2133,8 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,
 			 * The try_to_unmap() is only passed a hugetlb page
 			 * in the case where the hugetlb page is poisoned.
 			 */
-			VM_BUG_ON_PAGE(!PageHWPoison(subpage), subpage);
+			VM_BUG_ON_FOLIO(!folio_has_hwpoisoned_page(folio),
+					folio);
 			/*
 			 * huge_pmd_unshare may unmap an entire PMD page.
 			 * There is no way of knowing exactly which PMDs may
@@ -2200,12 +2213,12 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,
 		/* Update high watermark before we lower rss */
 		update_hiwater_rss(mm);
 
-		if (PageHWPoison(subpage) && (flags & TTU_HWPOISON)) {
+		if (ttu_create_hwpoison(folio, subpage, flags)) {
 			pteval = swp_entry_to_pte(make_hwpoison_entry(subpage));
 			if (folio_test_hugetlb(folio)) {
 				hugetlb_count_sub(folio_nr_pages(folio), mm);
 				set_huge_pte_at(mm, address, pvmw.pte, pteval,
-						hsz);
+						folio_size(folio));
 			} else {
 				dec_mm_counter(mm, mm_counter(folio));
 				set_pte_at(mm, address, pvmw.pte, pteval);
@@ -2423,7 +2436,6 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
 	struct mmu_notifier_range range;
 	enum ttu_flags flags = (enum ttu_flags)(long)arg;
 	unsigned long pfn;
-	unsigned long hsz = 0;
 
 	/*
 	 * When racing against e.g. zap_pte_range() on another cpu,
@@ -2452,9 +2464,6 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
 		 */
 		adjust_range_if_pmd_sharing_possible(vma, &range.start,
 						     &range.end);
-
-		/* We need the huge page size for set_huge_pte_at() */
-		hsz = huge_page_size(hstate_vma(vma));
 	}
 	mmu_notifier_invalidate_range_start(&range);
 
@@ -2607,14 +2616,14 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
 		/* Update high watermark before we lower rss */
 		update_hiwater_rss(mm);
 
-		if (PageHWPoison(subpage)) {
+		if (ttu_create_hwpoison(folio, subpage, TTU_HWPOISON)) {
 			VM_WARN_ON_FOLIO(folio_is_device_private(folio), folio);
 
 			pteval = swp_entry_to_pte(make_hwpoison_entry(subpage));
 			if (folio_test_hugetlb(folio)) {
 				hugetlb_count_sub(folio_nr_pages(folio), mm);
 				set_huge_pte_at(mm, address, pvmw.pte, pteval,
-						hsz);
+						folio_size(folio));
 			} else {
 				dec_mm_counter(mm, mm_counter(folio));
 				set_pte_at(mm, address, pvmw.pte, pteval);
@@ -2644,7 +2653,8 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
 			if (arch_unmap_one(mm, vma, address, pteval) < 0) {
 				if (folio_test_hugetlb(folio))
 					set_huge_pte_at(mm, address, pvmw.pte,
-							pteval, hsz);
+							pteval,
+							folio_size(folio));
 				else
 					set_pte_at(mm, address, pvmw.pte, pteval);
 				ret = false;
@@ -2657,7 +2667,8 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
 				if (anon_exclusive &&
 				    hugetlb_try_share_anon_rmap(folio)) {
 					set_huge_pte_at(mm, address, pvmw.pte,
-							pteval, hsz);
+							pteval,
+							folio_size(folio));
 					ret = false;
 					page_vma_mapped_walk_done(&pvmw);
 					break;
@@ -2703,7 +2714,7 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
 			}
 			if (folio_test_hugetlb(folio))
 				set_huge_pte_at(mm, address, pvmw.pte, swp_pte,
-						hsz);
+						folio_size(folio));
 			else
 				set_pte_at(mm, address, pvmw.pte, swp_pte);
 			trace_set_migration_pte(address, pte_val(swp_pte),
diff --git a/mm/shmem.c b/mm/shmem.c
index b51f83c970bb3..492c9bef36207 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 35c3bb15ae96a..70e54ace0e741 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
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);

3/2 2026/07/30 02:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18503
							output: 19
							thoughts: 589
						

Thoughts:
**Fuzzing Target Identification: Large Folios and Hugetlbfs HWPoisoning**

Okay, let's break this down. My initial assessment is that this patch series *definitely* introduces functional changes that warrant fuzzing. The modification of core memory management components (`fs/hugetlbfs/inode.c`, various include files, and a slew of `mm/` files) is a clear indication. The key focus here is the handling of hardware poisoning (`hwpoison`) in the context of large folios and hugetlbfs.

Specifically, it looks like they're reworking how `hwpoison` is managed for large pages. The move away from `hugetlbfs_read_iter` to `generic_file_read_iter` is a red flag on its own. The introduction of new functions like `folio_has_hwpoisoned_page`, `hugetlb_page_hwpoison`, and `hugetlb_unref_page_hwpoison`, signals a significant shift in how these page types are handled when hardware errors are detected. The patch also touches `filemap_read` to account for this.

So, where do we start? Well, `filemap_read` is a hot path, so I'd probably be a little cautious about focusing *too* heavily there, though it's certainly relevant. The new functions, especially `hugetlb_page_hwpoison` and `hugetlb_unref_page_hwpoison`, look like prime targets. The changes to `mm/memory-failure.c` are also crucial. `unpoison_memory` is obviously a function of interest.

Let's dive deeper into `mm/memory-failure.c`. The mentioned functions (`hugetlb_page_hwpoison`, `hugetlb_unref_page_hwpoison`, and `unpoison_memory`) are where the rubber meets the road. In addition, I need to check functions like `hugetlb_update_hwpoison`, `folio_clear_hugetlb_hwpoison`, `folio_free_raw_hwp` in `mm/memory-failure.c`.

Similarly, in `mm/filemap.c`, I'll need to scrutinize the `filemap_read` modifications and the newly added `adjust_range_hwpoison`. I'll want to review the code and understand how it interacts with the HWPoisoning.

Of course, the relevant Kconfig options are crucial context: `CONFIG_HUGETLBFS`, `CONFIG_HUGETLB_PAGE`, `CONFIG_MEMORY_FAILURE`, and, crucially, `CONFIG_LARGE_FOLIO`. I'll need to verify the dependencies, particularly how `CONFIG_HUGETLB_PAGE` and `TRANSPARENT_HUGEPAGE` select `CONFIG_LARGE_FOLIO`. And I'll double-check `mm/Kconfig` to confirm the specific implications of `MEMORY_FAILURE`.

Ultimately, the goal is to identify the critical code paths related to hardware poisoning of large folios and hugetlbfs pages and design fuzzing strategies accordingly.



4/2 2026/07/30 02:36 tool grepper
0m Args:
{
  "Expression": "config MEMORY_FAILURE"
}

Results:
{
  "Output": "mm/Kconfig=774=config ARCH_SUPPORTS_MEMORY_FAILURE\n--\nmm/Kconfig-776-\nmm/Kconfig:777:config MEMORY_FAILURE\nmm/Kconfig-778-\tdepends on MMU\n"
}

5/2 2026/07/30 02:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19847
							output: 32
							thoughts: 18
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach that query, as if it were my thought process:

**Diving into MEMORY_FAILURE Dependencies**

Right, so we need to see what's connected to `MEMORY_FAILURE` in the kernel configuration. The most logical place to start is always the `mm/Kconfig` file. That's the central configuration file for memory management options, so it's a safe bet that any dependencies for `MEMORY_FAILURE` will be defined, or at least referenced, there. Let's start digging in and see what the relationships are. I need to understand what other options enable or disable it, and what features might depend on it being set. This is probably going to involve tracing through the logic to see how it's defined and how other options might influence its visibility. I expect to be able to understand the overall picture quickly. I need a clear mental model of how this feature integrates with the rest of the memory management subsystem.



6/2 2026/07/30 02:36 tool read-file
0m Args:
{
  "File": "mm/Kconfig",
  "FirstLine": 770,
  "LineCount": 20
}

Results:
{
  "Contents": " 770:\t\n 771:\t\t  This value can be changed after boot using the\n 772:\t\t  /proc/sys/vm/mmap_min_addr tunable.\n 773:\t\n 774:\tconfig ARCH_SUPPORTS_MEMORY_FAILURE\n 775:\t\tbool\n 776:\t\n 777:\tconfig MEMORY_FAILURE\n 778:\t\tdepends on MMU\n 779:\t\tdepends on ARCH_SUPPORTS_MEMORY_FAILURE\n 780:\t\tbool \"Enable recovery from hardware memory errors\"\n 781:\t\tselect INTERVAL_TREE\n 782:\t\thelp\n 783:\t\t  Enables code to recover from some memory failures on systems\n 784:\t\t  with MCA recovery. This allows a system to continue running\n 785:\t\t  even when some of its memory has uncorrected errors. This requires\n 786:\t\t  special hardware support and typically ECC memory.\n 787:\t\n 788:\tconfig HWPOISON_INJECT\n 789:\t\ttristate \"HWPoison pages injector\"\n"
}

7/2 2026/07/30 02:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20395
							output: 31
							thoughts: 53
						

Thoughts:
Okay, here's my take on those configurations and my initial thought process diving into the kernel code:

**Diving into Memory Failure Handling**

Right, so I'm thinking about bolstering our memory error handling. Based on past experience, enabling `MEMORY_FAILURE` and `HWPOISON_INJECT` seems like a solid starting point. Those are practically essential for robust fault detection and injection testing, respectively. And, while I'm at it, I should be looking at `HUGETLBFS` and `HUGETLB_PAGE`. They can be useful for performance and resilience with large memory pages.

Now, to understand exactly *how* these options play out, I really need to delve into the implementation. I'm going to start by taking a close look at `mm/memory-failure.c`. That file should give me the core logic behind memory failure handling. I want to see how the kernel detects, isolates, and responds to errors. Time to see what kind of interesting functions are defined in there...



8/2 2026/07/30 02:36 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool|unsigned long).*hugetlb_.*hwpoison"
}

Results:
{
  "Output": "include/linux/page-flags.h=1092=static inline bool PageHuge(const struct page *page)\n--\ninclude/linux/page-flags.h-1096-\ninclude/linux/page-flags.h:1097:bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page);\ninclude/linux/page-flags.h:1098:bool hugetlb_unref_page_hwpoison(const struct page *page);\ninclude/linux/page-flags.h-1099-\n--\nmm/memory-failure.c=1823=static bool precise_page_poisoned(const struct folio *folio,\n--\nmm/memory-failure.c-1851- */\nmm/memory-failure.c:1852:bool hugetlb_page_hwpoison(const struct folio *folio, const struct page *page)\nmm/memory-failure.c-1853-{\n--\nmm/memory-failure.c-1863- */\nmm/memory-failure.c:1864:bool hugetlb_unref_page_hwpoison(const struct page *page)\nmm/memory-failure.c-1865-{\n--\nmm/memory-failure.c=1979=static unsigned long folio_free_raw_hwp(struct folio *folio, bool move_flag)\n--\nmm/memory-failure.c-2004-\nmm/memory-failure.c:2005:void folio_clear_hugetlb_hwpoison(struct folio *folio)\nmm/memory-failure.c-2006-{\n"
}

9/2 2026/07/30 02:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21101
							output: 22
							thoughts: 24
						

Thoughts:
Here's a summary of my thought process, as though I were considering these points:

**Analyzing Potential Memory Corruption in HugeTLB Environments**

Okay, so we've got a potential memory issue, and the context suggests it's related to HugeTLB pages. Right away, my instinct is to dig into the core logic. Specifically, I need to understand the interaction of hardware poisoning and HugeTLB updates. The function name itself, `hugetlb_update_hwpoison`, implies this is where the critical handling occurs. Therefore, my first step is clear: I *must* examine the code within `mm/memory-failure.c`, targeting that specific function. This is where the magic (or the potential problem!) lies in terms of how hardware-detected errors are propagated and handled within the HugeTLB framework. That's the logical place to start my investigation.



10/2 2026/07/30 02:37 tool grepper
0m Args:
{
  "Expression": "hugetlb_update_hwpoison"
}

Results:
{
  "Output": "mm/memory-failure.c=1919=static inline int hugetlb_clear_poison(struct folio *folio)\n--\nmm/memory-failure.c-1931- */\nmm/memory-failure.c:1932:static int hugetlb_update_hwpoison(struct folio *folio, struct page *page)\nmm/memory-failure.c-1933-{\n--\nmm/memory-failure.c=2015=static int get_huge_page_for_hwpoison(unsigned long pfn, int flags,\n--\nmm/memory-failure.c-2045-\nmm/memory-failure.c:2046:\trc = hugetlb_update_hwpoison(folio, page);\nmm/memory-failure.c-2047-\tif (rc \u003e= MF_HUGETLB_FOLIO_PRE_POISONED) {\n"
}

11/2 2026/07/30 02:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21557
							output: 20
							thoughts: 92
						

Thoughts:
Okay, here's my breakdown of what I'm looking at, from my perspective as someone who understands memory management:

**Analyzing Poisoned Memory Handling in the HugeTLB and Filemap Contexts**

Alright, I'm digging into the source code related to handling hardware poisoning within the HugeTLB and, I suspect, how it interfaces with the filemap system. I'm focusing specifically on how these functions are declared and their scope. First, I noticed something interesting: `hugetlb_update_hwpoison` is declared `static`, which means its visibility and use are restricted to its source file. This likely means it's an internal helper function within that specific HugeTLB module.

Conversely, I see that `folio_clear_hugetlb_hwpoison`, `hugetlb_page_hwpoison`, and `hugetlb_unref_page_hwpoison` are *not* static. That implies they are designed to be callable from elsewhere, likely as part of a broader interface for managing poisoned HugeTLB pages. They're probably part of the core poisoning mechanisms, and therefore need to be accessible. `unpoison_memory` is also not static, which is what I would expect for a function that deals with the general act of clearing memory.

Now, I'm thinking about how all of this ties together. The next logical step is to check `mm/filemap.c`, because I know that's where the filemap subsystem lives. Specifically, I'm interested in the function `adjust_range_hwpoison`. I'm going to look at how that function interacts with or utilizes the other functions I've examined. I anticipate seeing if the filemap system is involved in propagating or responding to hardware poisoning signals that originate in the HugeTLB context. I'm curious to see how the code handles the interaction between these two distinct memory management areas.



12/2 2026/07/30 02:37 tool grepper
0m Args:
{
  "Expression": "adjust_range_hwpoison"
}

Results:
{
  "Output": "mm/filemap.c=2749=static inline bool pos_same_folio(loff_t pos1, loff_t pos2, struct folio *folio)\n--\nmm/filemap.c-2755-\nmm/filemap.c:2756:static size_t adjust_range_hwpoison(const struct folio *folio, size_t offset,\nmm/filemap.c-2757-\t\tsize_t bytes)\n--\nmm/filemap.c=2804=ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,\n--\nmm/filemap.c-2907-\nmm/filemap.c:2908:\t\t\tcopied = adjust_range_hwpoison(folio, offset, bytes);\nmm/filemap.c-2909-\t\t\tif (copied \u003c bytes)\n"
}

13/2 2026/07/30 02:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22292
							output: 21
							thoughts: 35
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's inner monologue:

**Investigating `adjust_range_hwpoison` and `folio_clear_hugetlb`**

Alright, so `adjust_range_hwpoison` is static, which is good. That likely means it's limited in scope and probably specific to a particular module or functionality. Knowing that helps me narrow down where to look for its usage and its relationship to other code.

Now, let's see... the mention of `folio_clear_hugetlb` immediately makes me think of huge pages. Given the context, I'm betting that `adjust_range_hwpoison` is related to handling hardware poisoning within the context of hugetlbfs. A quick look at `mm/hugetlb.c` should provide the necessary details. Let's see how they interact.



14/2 2026/07/30 02:37 tool grepper
0m Args:
{
  "Expression": "folio_clear_hugetlb"
}

Results:
{
  "Output": "include/linux/hugetlb.h=610=static __always_inline\t\t\t\t\t\t\\\ninclude/linux/hugetlb.h:611:void folio_clear_hugetlb_##flname(struct folio *folio)\t\t\\\ninclude/linux/hugetlb.h-612-\t{\tvoid *private = \u0026folio-\u003eprivate;\t\t\\\n--\ninclude/linux/hugetlb.h=627=static inline void\t\t\t\t\t\t\\\ninclude/linux/hugetlb.h:628:folio_clear_hugetlb_##flname(struct folio *folio)\t\t\\\ninclude/linux/hugetlb.h-629-\t{ }\n--\ninclude/linux/hugetlb.h=876=int dissolve_free_hugetlb_folios(unsigned long start_pfn,\n--\ninclude/linux/hugetlb.h-879-#ifdef CONFIG_MEMORY_FAILURE\ninclude/linux/hugetlb.h:880:extern void folio_clear_hugetlb_hwpoison(struct folio *folio);\ninclude/linux/hugetlb.h-881-#else\ninclude/linux/hugetlb.h:882:static inline void folio_clear_hugetlb_hwpoison(struct folio *folio)\ninclude/linux/hugetlb.h-883-{\n--\nmm/hugetlb.c=1247=static struct folio *dequeue_hugetlb_folio_node_exact(struct hstate *h,\n--\nmm/hugetlb.c-1265-\t\tfolio_ref_unfreeze(folio, 1);\nmm/hugetlb.c:1266:\t\tfolio_clear_hugetlb_freed(folio);\nmm/hugetlb.c-1267-\t\th-\u003efree_huge_pages--;\n--\nmm/hugetlb.c=1377=static struct folio *alloc_gigantic_frozen_folio(int order, gfp_t gfp_mask, int nid,\n--\nmm/hugetlb.c-1383-\nmm/hugetlb.c:1384:static void folio_clear_hugetlb(struct folio *folio)\nmm/hugetlb.c-1385-{\n--\nmm/hugetlb.c-1390-\tif (unlikely(folio_test_has_hwpoisoned(folio)))\nmm/hugetlb.c:1391:\t\tfolio_clear_hugetlb_hwpoison(folio);\nmm/hugetlb.c-1392-\nmm/hugetlb.c:1393:\t__folio_clear_hugetlb(folio);\nmm/hugetlb.c-1394-}\n--\nmm/hugetlb.c=1404=void remove_hugetlb_folio(struct hstate *h, struct folio *folio,\n--\nmm/hugetlb.c-1418-\tif (folio_test_hugetlb_freed(folio)) {\nmm/hugetlb.c:1419:\t\tfolio_clear_hugetlb_freed(folio);\nmm/hugetlb.c-1420-\t\th-\u003efree_huge_pages--;\n--\nmm/hugetlb.c-1433-\tif (!folio_test_hugetlb_vmemmap_optimized(folio))\nmm/hugetlb.c:1434:\t\tfolio_clear_hugetlb(folio);\nmm/hugetlb.c-1435-\n--\nmm/hugetlb.c=1470=static void __update_and_free_hugetlb_folio(struct hstate *h,\n--\nmm/hugetlb.c-1507-\t\tspin_lock_irq(\u0026hugetlb_lock);\nmm/hugetlb.c:1508:\t\tfolio_clear_hugetlb(folio);\nmm/hugetlb.c-1509-\t\tspin_unlock_irq(\u0026hugetlb_lock);\n--\nmm/hugetlb.c=1586=static void bulk_vmemmap_restore_error(struct hstate *h,\n--\nmm/hugetlb.c-1602-\t\t\tspin_lock_irq(\u0026hugetlb_lock);\nmm/hugetlb.c:1603:\t\t\tfolio_clear_hugetlb(folio);\nmm/hugetlb.c-1604-\t\t\tspin_unlock_irq(\u0026hugetlb_lock);\n--\nmm/hugetlb.c-1627-\t\t\t\tspin_lock_irq(\u0026hugetlb_lock);\nmm/hugetlb.c:1628:\t\t\t\tfolio_clear_hugetlb(folio);\nmm/hugetlb.c-1629-\t\t\t\tspin_unlock_irq(\u0026hugetlb_lock);\n--\nmm/hugetlb.c=1637=static void update_and_free_pages_bulk(struct hstate *h,\n--\nmm/hugetlb.c-1660-\t * Without HVO enabled, ret will be 0 and there is no need to call\nmm/hugetlb.c:1661:\t * folio_clear_hugetlb as this was done previously.\nmm/hugetlb.c-1662-\t */\n--\nmm/hugetlb.c-1667-\t\tlist_for_each_entry(folio, \u0026non_hvo_folios, lru)\nmm/hugetlb.c:1668:\t\t\tfolio_clear_hugetlb(folio);\nmm/hugetlb.c-1669-\t\tspin_unlock_irq(\u0026hugetlb_lock);\n--\nmm/hugetlb.c=1689=void free_huge_folio(struct folio *folio)\n--\nmm/hugetlb.c-1708-\trestore_reserve = folio_test_hugetlb_restore_reserve(folio);\nmm/hugetlb.c:1709:\tfolio_clear_hugetlb_restore_reserve(folio);\nmm/hugetlb.c-1710-\n--\nmm/hugetlb.c-1730-\tspin_lock_irqsave(\u0026hugetlb_lock, flags);\nmm/hugetlb.c:1731:\tfolio_clear_hugetlb_migratable(folio);\nmm/hugetlb.c-1732-\thugetlb_cgroup_uncharge_folio(hstate_index(h),\n--\nmm/hugetlb.c=2565=void restore_reserve_on_error(struct hstate *h, struct vm_area_struct *vma,\n--\nmm/hugetlb.c-2582-\t\t\t */\nmm/hugetlb.c:2583:\t\t\tfolio_clear_hugetlb_restore_reserve(folio);\nmm/hugetlb.c-2584-\t\telse if (rc)\n--\nmm/hugetlb.c=5146=void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,\n--\nmm/hugetlb.c-5283-\t\t\t\t */\nmm/hugetlb.c:5284:\t\t\t\tfolio_clear_hugetlb_restore_reserve(folio);\nmm/hugetlb.c-5285-\t\t\telse if (rc)\n--\nmm/hugetlb.c=5633=int hugetlb_add_to_page_cache(struct folio *folio, struct address_space *mapping,\n--\nmm/hugetlb.c-5647-\t}\nmm/hugetlb.c:5648:\tfolio_clear_hugetlb_restore_reserve(folio);\nmm/hugetlb.c-5649-\n--\nmm/hugetlb.c=7133=bool folio_isolate_hugetlb(struct folio *folio, struct list_head *list)\n--\nmm/hugetlb.c-7143-\t}\nmm/hugetlb.c:7144:\tfolio_clear_hugetlb_migratable(folio);\nmm/hugetlb.c-7145-\tlist_move_tail(\u0026folio-\u003elru, list);\n--\nmm/hugetlb.c=7190=void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio, int reason)\n--\nmm/hugetlb.c-7211-\t\tfolio_set_hugetlb_temporary(old_folio);\nmm/hugetlb.c:7212:\t\tfolio_clear_hugetlb_temporary(new_folio);\nmm/hugetlb.c-7213-\n--\nmm/hugetlb_vmemmap.c=395=static int __hugetlb_vmemmap_restore_folio(const struct hstate *h,\n--\nmm/hugetlb_vmemmap.c-419-\tif (!ret)\nmm/hugetlb_vmemmap.c:420:\t\tfolio_clear_hugetlb_vmemmap_optimized(folio);\nmm/hugetlb_vmemmap.c-421-\n--\nmm/hugetlb_vmemmap.c=522=static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,\n--\nmm/hugetlb_vmemmap.c-577-\tif (ret)\nmm/hugetlb_vmemmap.c:578:\t\tfolio_clear_hugetlb_vmemmap_optimized(folio);\nmm/hugetlb_vmemmap.c-579-\n--\nmm/memory-failure.c=1979=static unsigned long folio_free_raw_hwp(struct folio *folio, bool move_flag)\n--\nmm/memory-failure.c-2004-\nmm/memory-failure.c:2005:void folio_clear_hugetlb_hwpoison(struct folio *folio)\nmm/memory-failure.c-2006-{\n--\nmm/memory-failure.c=2015=static int get_huge_page_for_hwpoison(unsigned long pfn, int flags,\n--\nmm/memory-failure.c-2056-\tif (count_increased \u0026\u0026 folio_test_hugetlb_migratable(folio)) {\nmm/memory-failure.c:2057:\t\tfolio_clear_hugetlb_migratable(folio);\nmm/memory-failure.c-2058-\t\t*migratable_cleared = true;\n--\nmm/memory-failure.c=2083=static int try_memory_failure_hugetlb(unsigned long pfn, int flags)\n--\nmm/memory-failure.c-2120-\tif (hwpoison_filter(p)) {\nmm/memory-failure.c:2121:\t\tfolio_clear_hugetlb_hwpoison(folio);\nmm/memory-failure.c-2122-\t\tif (migratable_cleared)\n--\nmm/rmap.c=3153=void hugetlb_add_new_anon_rmap(struct folio *folio,\n--\nmm/rmap.c-3161-\tatomic_set(\u0026folio-\u003e_large_mapcount, 0);\nmm/rmap.c:3162:\tfolio_clear_hugetlb_restore_reserve(folio);\nmm/rmap.c-3163-\t__folio_set_anon(folio, vma, address, true);\n"
}

15/2 2026/07/30 02:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25364
							output: 20
							thoughts: 442
						

Thoughts:
**Analyzing HugeTLB and Memory Poisoning Interactions**

Okay, so I'm looking into how HugeTLB interacts with memory poisoning and I've got a good starting point. I've noted that `folio_clear_hugetlb` is static, which is interesting, but `folio_clear_hugetlb_hwpoison` isn't, so that's something to keep in mind.

Now, I'm going through the relevant function calls to establish the call chain. I'm focusing on `mm/memory-failure.c` - `hugetlb_page_hwpoison`, `hugetlb_unref_page_hwpoison`, `unpoison_memory`, and `folio_clear_hugetlb_hwpoison`. I'm also looking at `mm/filemap.c` to see if `filemap_read` is called, and `fs/hugetlbfs/inode.c`, but only noting `hugetlbfs_init_regular_inode` and `hugetlbfs_get_inode` being static. I also note that `hugetlbfs_file_operations` uses `generic_file_read_iter`.

So, the key functions that really stand out and that I need to investigate are `hugetlb_page_hwpoison`, `hugetlb_unref_page_hwpoison`, `unpoison_memory`, and `folio_clear_hugetlb_hwpoison`.

Let's see if `hugetlb_page_hwpoison` is accessible. Aha! It's called from `is_ref_page_hwpoison` in `include/linux/page-flags.h`, which in turn is called from `adjust_range_hwpoison` in `mm/filemap.c`, which is then called from `filemap_read`. That's a solid call chain; `filemap_read` is definitely reachable.

`hugetlb_unref_page_hwpoison` looks good. I found that it's called from `is_page_hwpoison` in `include/linux/page-flags.h`. That's then called by `unpoison_memory` in `mm/memory-failure.c`.

Okay, I'm checking `mm/memory-failure.c` to see if `unpoison_memory` is exported or not. Yes, it is exported. That's good to know.

Lastly, just to be thorough, I'm checking `mm/rmap.c` for `ttu_create_hwpoison`.



16/2 2026/07/30 02:37 tool grepper
0m Args:
{
  "Expression": "ttu_create_hwpoison"
}

Results:
{
  "Output": "mm/rmap.c=1944=static inline unsigned int folio_unmap_pte_batch(struct folio *folio,\n--\nmm/rmap.c-1986- */\nmm/rmap.c:1987:static bool ttu_create_hwpoison(const struct folio *folio,\nmm/rmap.c-1988-\t\tconst struct page *page, enum ttu_flags flags)\n--\nmm/rmap.c=2000=static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,\n--\nmm/rmap.c-2215-\nmm/rmap.c:2216:\t\tif (ttu_create_hwpoison(folio, subpage, flags)) {\nmm/rmap.c-2217-\t\t\tpteval = swp_entry_to_pte(make_hwpoison_entry(subpage));\n--\nmm/rmap.c=2428=static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,\n--\nmm/rmap.c-2618-\nmm/rmap.c:2619:\t\tif (ttu_create_hwpoison(folio, subpage, TTU_HWPOISON)) {\nmm/rmap.c-2620-\t\t\tVM_WARN_ON_FOLIO(folio_is_device_private(folio), folio);\n"
}

17/2 2026/07/30 02:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26729
							output: 23
							thoughts: 37
						

Thoughts:
Okay, here's my take on those thoughts, framed from an expert's perspective:

**Investigating Potential Hardware Poisoning within the Huge Pages Code**

Alright, so `ttu_create_hwpoison` is a static function. That immediately makes me think about its scope and potential impact. My mind jumps to where it might be used and how it interacts with the rest of the system. Given the nature of hardware poisoning, I'm going to start by checking `mm/huge_memory.c`. That's the obvious place to begin, focusing specifically on looking for `folio_has_hwpoisoned_page`. Huge pages are a likely target for this kind of low-level error detection, so that function name is a key lead. I'm essentially trying to trace the path from the function that creates the hardware poison condition to the logic that identifies and handles it within the huge page management framework. I want to see how the system is designed to respond when a hardware error affects a huge page.



18/2 2026/07/30 02:37 tool grepper
0m Args:
{
  "Expression": "folio_has_hwpoisoned_page"
}

Results:
{
  "Output": "include/linux/page-flags.h=893=TESTPAGEFLAG_FALSE(TransCompound, transcompound)\n--\ninclude/linux/page-flags.h-901- * scheme used in that case).  You probably don't want to call this directly;\ninclude/linux/page-flags.h:902: * use folio_has_hwpoisoned_page() instead.\ninclude/linux/page-flags.h-903- *\n--\ninclude/linux/page-flags.h=1119=static inline bool is_ref_page_hwpoison(const struct folio *folio,\n--\ninclude/linux/page-flags.h-1128-\ninclude/linux/page-flags.h:1129:static inline bool folio_has_hwpoisoned_page(const struct folio *folio)\ninclude/linux/page-flags.h-1130-{\n--\nmm/filemap.c=2464=static void filemap_get_read_batch(struct address_space *mapping,\n--\nmm/filemap.c-2485-\t\t\tbreak;\nmm/filemap.c:2486:\t\tif (folio_has_hwpoisoned_page(folio))\nmm/filemap.c-2487-\t\t\tbreak;\n--\nmm/filemap.c=2756=static size_t adjust_range_hwpoison(const struct folio *folio, size_t offset,\n--\nmm/filemap.c-2761-\nmm/filemap.c:2762:\tif (!folio_has_hwpoisoned_page(folio))\nmm/filemap.c-2763-\t\treturn bytes;\n--\nmm/huge_memory.c=4408=static bool thp_underused(struct folio *folio)\n--\nmm/huge_memory.c-4415-\nmm/huge_memory.c:4416:\tif (folio_has_hwpoisoned_page(folio))\nmm/huge_memory.c-4417-\t\treturn false;\n--\nmm/memory_hotplug.c=1793=static void do_migrate_range(unsigned long start_pfn, unsigned long end_pfn)\n--\nmm/memory_hotplug.c-1815-\nmm/memory_hotplug.c:1816:\t\tif (folio_has_hwpoisoned_page(folio)) {\nmm/memory_hotplug.c-1817-\t\t\t/*\n--\nmm/rmap.c=2000=static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,\n--\nmm/rmap.c-2135-\t\t\t */\nmm/rmap.c:2136:\t\t\tVM_BUG_ON_FOLIO(!folio_has_hwpoisoned_page(folio),\nmm/rmap.c-2137-\t\t\t\t\tfolio);\n--\nmm/shmem.c=3216=shmem_write_begin(const struct kiocb *iocb, struct address_space *mapping,\n--\nmm/shmem.c-3242-\nmm/shmem.c:3243:\tif (folio_has_hwpoisoned_page(folio)) {\nmm/shmem.c-3244-\t\tfolio_unlock(folio);\n--\nmm/vmscan.c=1055=static unsigned int shrink_folio_list(struct list_head *folio_list,\n--\nmm/vmscan.c-1088-\nmm/vmscan.c:1089:\t\tif (folio_has_hwpoisoned_page(folio)) {\nmm/vmscan.c-1090-\t\t\t/*\n--\nvirt/kvm/guest_memfd.c=754=static struct folio *__kvm_gmem_get_pfn(struct file *file,\n--\nvirt/kvm/guest_memfd.c-776-\nvirt/kvm/guest_memfd.c:777:\tif (folio_has_hwpoisoned_page(folio)) {\nvirt/kvm/guest_memfd.c-778-\t\tfolio_unlock(folio);\n"
}

19/2 2026/07/30 02:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28027
							output: 19
							thoughts: 226
						

Thoughts:
**My Analysis of Hardware Poisoning Functions**

Okay, so I'm digging into the hardware poisoning functions within this code. The core concern, as I see it, revolves around these functions: `hugetlb_page_hwpoison`, `hugetlb_unref_page_hwpoison`, `unpoison_memory`, and `folio_clear_hugetlb_hwpoison`. From what I can tell, `folio_has_hwpoisoned_page`, `mapping_set_authoritative`, `mapping_is_authoritative` and `mapping_set_folio_order_range` are static inline, so they don't seem to be where the primary logic is located. That streamlines the investigation significantly; I can concentrate on those four.

Now, I need to understand the dependencies and how the configurations play into this. I'll need to check the Kconfig settings—specifically, `HUGETLBFS`, `HUGETLB_PAGE`, `MEMORY_FAILURE` and `LARGE_FOLIO`– to see what triggers the inclusion or behaviour of these core functions. I'll need to locate `mm/Kconfig` to determine how `LARGE_FOLIO` is defined and used. Understanding the config options will be critical to determine under what situations this poison/unpoison logic is invoked.



20/2 2026/07/30 02:37 tool grepper
0m Args:
{
  "Expression": "config LARGE_FOLIO"
}

Results:
{
  "Output": "mm/Kconfig=843=config MM_ID\n--\nmm/Kconfig-845-\nmm/Kconfig:846:config LARGE_FOLIO\nmm/Kconfig-847-\tdef_bool n\n"
}

21/2 2026/07/30 02:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28616
							output: 19
							thoughts: 93
						

Thoughts:
**My Kconfig Musings**

Okay, so `LARGE_FOLIO` is a dependency, a `def_bool`, and it's automatically handled when `HUGETLB_PAGE` or `TRANSPARENT_HUGEPAGE` are selected. That means I don't need to go out of my way to explicitly enable it. It's really more of an internal detail. Still, it's good practice to note it, just for clarity's sake.

But, thinking about what really *drives* its inclusion, I need to focus on the key players here. `HUGETLBFS` and `MEMORY_FAILURE` are the important ones that trigger it. I should make sure my understanding of their configuration and dependencies is solid.

Right, time to dig into `mm/Kconfig` and refresh my memory on the ins and outs of `HUGETLBFS`.



22/2 2026/07/30 02:38 tool grepper
0m Args:
{
  "Expression": "config HUGETLBFS"
}

Results:
{
  "Output": "fs/Kconfig=247=config ARCH_SUPPORTS_HUGETLBFS\n--\nfs/Kconfig-249-\nfs/Kconfig:250:menuconfig HUGETLBFS\nfs/Kconfig-251-\tbool \"HugeTLB file system support\"\n"
}

23/2 2026/07/30 02:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28997
							output: 32
							thoughts: 34
						

Thoughts:
Okay, here's my take on those thoughts, framed from my perspective as a seasoned Linux kernel developer:

**Delving into the HUGETLBFS Configuration**

Right, so `HUGETLBFS`... I remember seeing it listed in `fs/Kconfig`. Time to get down to brass tacks and figure out what other configurations hinge on it. I need to understand its dependencies. It's time to crack open `fs/Kconfig` and trace through the logic. I need to see what other features or options need to be enabled for `HUGETLBFS` to even be considered. This will inform my decisions later, depending on what I'm working on. Let's see what the Kconfig says.



24/2 2026/07/30 02:38 tool read-file
0m Args:
{
  "File": "fs/Kconfig",
  "FirstLine": 245,
  "LineCount": 20
}

Results:
{
  "Contents": " 245:\t\t  If unsure, say N.\n 246:\t\n 247:\tconfig ARCH_SUPPORTS_HUGETLBFS\n 248:\t\tdef_bool n\n 249:\t\n 250:\tmenuconfig HUGETLBFS\n 251:\t\tbool \"HugeTLB file system support\"\n 252:\t\tdepends on ARCH_SUPPORTS_HUGETLBFS\n 253:\t\tselect MEMFD_CREATE\n 254:\t\tselect PADATA if SMP\n 255:\t\thelp\n 256:\t\t  hugetlbfs is a filesystem backing for HugeTLB pages, based on\n 257:\t\t  ramfs. For architectures that support it, say Y here and read\n 258:\t\t  \u003cfile:Documentation/admin-guide/mm/hugetlbpage.rst\u003e for details.\n 259:\t\n 260:\t\t  If unsure, say N.\n 261:\t\n 262:\tif HUGETLBFS\n 263:\tconfig HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON\n 264:\t\tbool \"HugeTLB Vmemmap Optimization (HVO) defaults to on\"\n"
}

25/2 2026/07/30 02:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29527
							output: 26
							thoughts: 125
						

Thoughts:
**Checking Dependencies and Key Functions**

Okay, so I'm looking at this configuration. It's clear that `HUGETLBFS` is intrinsically linked to `ARCH_SUPPORTS_HUGETLBFS`, that's a given. And `MEMORY_FAILURE`, similarly, is tied to `ARCH_SUPPORTS_MEMORY_FAILURE`. Makes sense; we can't enable those features if the architecture doesn't support them. So, the core configs in play here are `HUGETLBFS` and `MEMORY_FAILURE`.

Now, I need to focus on the key functions that interact with this. I've got `hugetlb_page_hwpoison`, `hugetlb_unref_page_hwpoison`, `unpoison_memory`, and `folio_clear_hugetlb_hwpoison`. These are clearly the critical pieces dealing with hardware poisoning in the hugetlb context. The next logical step is to quickly confirm the visibility of `hugetlb_page_hwpoison` – I need to ensure it's exported and accessible where it's needed within the broader framework. This check is crucial for understanding how it interfaces with other parts of the system and its integration.



26/2 2026/07/30 02:38 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*hugetlb_page_hwpoison"
}

Results:
{
  "error": "no matches"
}

27/2 2026/07/30 02:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29945
							output: 22
							thoughts: 37
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating a Memory Failure**

Alright, so the failure isn't stemming from something I've *explicitly* exported, which is good. That eliminates a whole class of easily-debugged external causes. But it's also not a statically-defined thing, meaning it's likely part of some dynamic process. That's a bit more involved, but manageable. The clue is that it's in `mm/memory-failure.c`. That gives me a very focused area to start my investigation. Now, based on the context of 'memory-failure', I should turn my attention to the `unpoison_memory` function. That seems like the most logical next step.



28/2 2026/07/30 02:38 tool grepper
0m Args:
{
  "Expression": "EXPORT_SYMBOL.*unpoison_memory"
}

Results:
{
  "Output": "mm/kmsan/hooks.c=398=void kmsan_unpoison_memory(const void *address, size_t size)\n--\nmm/kmsan/hooks.c-410-}\nmm/kmsan/hooks.c:411:EXPORT_SYMBOL(kmsan_unpoison_memory);\nmm/kmsan/hooks.c-412-\n--\nmm/memory-failure.c=2730=int unpoison_memory(unsigned long pfn)\n--\nmm/memory-failure.c-2840-}\nmm/memory-failure.c:2841:EXPORT_SYMBOL(unpoison_memory);\nmm/memory-failure.c-2842-\n"
}

29/2 2026/07/30 02:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30351
							output: 28
							thoughts: 28
						

Thoughts:
Okay, here's my summarized thought process, presented as if I'm the one thinking it:

**Verifying Memory Unpoisoning Functionality**

Alright, confirmed. The `unpoison_memory` function is definitely exported. That's a good starting point, and what I expected. Now, to dig deeper, the next logical step is to examine the function that clears hardware-poisoned huge pages. Let me go take a look at `folio_clear_hugetlb_hwpoison`. This seems like the most relevant area to understand how the system handles memory errors and, critically, how it attempts to recover from them, considering the context. I need to understand how these pieces fit together.



30/2 2026/07/30 02:38 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*folio_clear_hugetlb_hwpoison"
}

Results:
{
  "error": "no matches"
}

31/2 2026/07/30 02:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30568
							output: 146
							thoughts: 78
						

Thoughts:
Okay, here's my take on those thoughts, framed from my own expert perspective:

**Reflecting on the Patch and its Implications**

So, this isn't a simple static update, but rather something more dynamic, something that's being actively *managed* internally. I'm pleased with the direction, particularly the emphasis on the focus functions. They seem well-designed. The changes are clearly functional and, importantly, the new functionality is readily accessible.

The crux of this is how we're adapting the hardware poison handling for these larger memory structures: folios and hugetlbfs. This patch really digs into how we're dealing with poisoned pages when we're reading them through `filemap_read` and, crucially, how we are properly unpoisoning them. That's a critical area, especially with the increased use of large pages these days.

I think 'set-results' is the right next logical step. It seems like this would be the natural function call to make at this point in the process.



32/2 2026/07/30 02:38 tool set-results
0m Args:
{
  "EnableConfigs": [
    "HUGETLBFS",
    "MEMORY_FAILURE"
  ],
  "FocusSymbols": [
    "hugetlb_page_hwpoison",
    "hugetlb_unref_page_hwpoison",
    "unpoison_memory",
    "folio_clear_hugetlb_hwpoison"
  ],
  "Reasoning": "The patch changes how hwpoison is handled for large folios and hugetlbfs. It modifies `filemap_read` to handle hwpoisoned folios and updates memory failure recovery logic. These are functional changes in core MM and hugetlbfs that should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "HUGETLBFS",
    "MEMORY_FAILURE"
  ],
  "FocusSymbols": [
    "hugetlb_page_hwpoison",
    "hugetlb_unref_page_hwpoison",
    "unpoison_memory",
    "folio_clear_hugetlb_hwpoison"
  ],
  "Reasoning": "The patch changes how hwpoison is handled for large folios and hugetlbfs. It modifies `filemap_read` to handle hwpoisoned folios and updates memory failure recovery logic. These are functional changes in core MM and hugetlbfs that should be fuzzed.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)