AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/12 20:14 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "SWAP"
  ],
  "FocusSymbols": [
    "__split_huge_page_to_list_to_order",
    "folio_split_unmapped",
    "folio_check_splittable",
    "__swap_cache_replace_folio"
  ],
  "KMSANReasoning": "The patch refactors the folio splitting logic in the memory management subsystem (specifically `mm/huge_memory.c` and `mm/swap_state.c`), introducing separate paths for anonymous/swapcache folios and file-backed folios. The changes involve page reference counting, locking, LRU list management, and page cache/swap cache updates.\n\nThese modifications do not introduce new data structures sent to user space, nor do they allocate uninitialized memory that is subsequently read or used in control flow. Any bugs introduced by this patch would likely manifest as use-after-free, double free, out-of-bounds accesses, or locking issues (deadlocks/race conditions). Such bugs are effectively detected by KASAN, LOCKDEP, and standard kernel bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch refactors and modifies the logic for splitting folios, particularly for anonymous and swapcache folios. It introduces new functions to handle splitting and modifies conditions in folio_check_splittable. These are core memory management changes affecting Transparent Huge Pages (THP) and swap, which are reachable from userspace and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/08/12 20:14 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 5ae70f7a267937b5eabcd196fff52e2479d0cd2e\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Aug 12 20:14:22 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/mm/huge_memory.c b/mm/huge_memory.c\nindex ced400f72d43a..1c61b7d39cd03 100644\n--- a/mm/huge_memory.c\n+++ b/mm/huge_memory.c\n@@ -3589,9 +3589,6 @@ static void remap_page(struct folio *folio, unsigned long nr, int flags)\n {\n \tint i = 0;\n \n-\t/* If unmap_folio() uses try_to_migrate() on file, remove this check */\n-\tif (!folio_test_anon(folio))\n-\t\treturn;\n \tfor (;;) {\n \t\tremove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);\n \t\ti += folio_nr_pages(folio);\n@@ -3634,6 +3631,18 @@ static bool page_range_has_hwpoisoned(struct page *page, long nr_pages)\n \treturn false;\n }\n \n+/**\n+ * for_each_folio_safe - iterate over contiguous folios safe against folio free\n+ * @start: the first folio to iterate\n+ * @end: sentinel, folio_next() of the last folio to iterate\n+ * @sub_folio: struct folio * to use as the loop cursor\n+ * @next: struct folio * used as temporary storage\n+ */\n+#define for_each_folio_safe(start, end, sub_folio, next) \\\n+\tfor (sub_folio = (start), next = folio_next(sub_folio); \\\n+\t     sub_folio != (end); \\\n+\t     sub_folio = next, next = folio_next(next))\n+\n /*\n  * It splits @folio into @new_order folios and copies the @folio metadata to\n  * all the resulting folios.\n@@ -3755,8 +3764,8 @@ static void __split_folio_to_order(struct folio *folio, int old_order,\n }\n \n /**\n- * __split_unmapped_folio() - splits an unmapped @folio to lower order folios in\n- * two ways: uniform split or non-uniform split.\n+ * __split_frozen_folio() - splits a frozen @folio to lower order folios\n+ * in two ways: uniform split or non-uniform split.\n  * @folio: the to-be-split folio\n  * @new_order: the smallest order of the after split folios (since buddy\n  *             allocator like split generates folios with orders from @folio's\n@@ -3795,11 +3804,12 @@ static void __split_folio_to_order(struct folio *folio, int old_order,\n  * Return: 0 - successful, \u003c0 - failed (if -ENOMEM is returned, @folio might be\n  * split but not to @new_order, the caller needs to check)\n  */\n-static int __split_unmapped_folio(struct folio *folio, int new_order,\n+static int __split_frozen_folio(struct folio *folio, int new_order,\n \t\tstruct page *split_at, struct xa_state *xas,\n \t\tstruct address_space *mapping, enum split_type split_type)\n {\n \tconst bool is_anon = folio_test_anon(folio);\n+\tconst bool is_swapcache = folio_test_swapcache(folio);\n \tint old_order = folio_order(folio);\n \tint start_order = split_type == SPLIT_TYPE_UNIFORM ? new_order : old_order - 1;\n \tstruct folio *old_folio = folio;\n@@ -3814,8 +3824,8 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,\n \t     split_order--) {\n \t\tint nr_new_folios = 1UL \u003c\u003c (old_order - split_order);\n \n-\t\t/* order-1 anonymous folio is not supported */\n-\t\tif (is_anon \u0026\u0026 split_order == 1)\n+\t\t/* order-1 anonymous or swapcache folio is not supported */\n+\t\tif ((is_anon || is_swapcache) \u0026\u0026 split_order == 1)\n \t\t\tcontinue;\n \n \t\tif (mapping) {\n@@ -3878,32 +3888,26 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,\n int folio_check_splittable(struct folio *folio, unsigned int new_order,\n \t\t\t   enum split_type split_type)\n {\n+\tbool is_anon = folio_test_anon(folio);\n+\tbool is_swapcache = folio_test_swapcache(folio);\n+\n \tVM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);\n \t/*\n \t * Folios that just got truncated cannot get split. Signal to the\n-\t * caller that there was a race.\n-\t *\n-\t * TODO: this will also currently refuse folios without a mapping in the\n-\t * swapcache (shmem or to-be-anon folios).\n+\t * caller that there was a race. A mappingless swap cache folio\n+\t * has no page cache entries to update, so it is fine to split.\n \t */\n-\tif (!folio-\u003emapping \u0026\u0026 !folio_test_anon(folio))\n+\tif (!folio-\u003emapping \u0026\u0026 !is_swapcache)\n \t\treturn -EBUSY;\n \n-\t/* order-1 is not supported for anonymous THP. */\n-\tif (folio_test_anon(folio) \u0026\u0026 new_order == 1)\n-\t\treturn -EINVAL;\n-\n \t/*\n-\t * swapcache folio could only be split to order 0\n-\t *\n-\t * non-uniform split creates after-split folios with orders from\n-\t * folio_order(folio) - 1 to new_order, making it not suitable for any\n-\t * swapcache folio split. Only uniform split to order-0 can be used\n-\t * here.\n+\t * Order-1 is unsupported: anon folios need subpage 2 for the\n+\t * deferred split list, hybrid shmem \u0026 swap cache folios are not\n+\t * splittable, and a splittable mappingless swap cache folio could\n+\t * be either anon or shmem, which we cannot tell apart.\n \t */\n-\tif ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) \u0026\u0026 folio_test_swapcache(folio)) {\n+\tif ((is_anon || is_swapcache) \u0026\u0026 new_order == 1)\n \t\treturn -EINVAL;\n-\t}\n \n \tif (is_huge_zero_folio(folio))\n \t\treturn -EINVAL;\n@@ -3911,39 +3915,97 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,\n \tif (folio_test_writeback(folio))\n \t\treturn -EBUSY;\n \n+\t/*\n+\t * A non-anon swapcache folio that still has a mapping can only be a\n+\t * shmem folio under SWAP IO, it's removed from either swap cache or\n+\t * shmem mapping afterward. There is little benefit in splitting them\n+\t * hence reject it here up front before touching anything.\n+\t */\n+\tif (!is_anon \u0026\u0026 is_swapcache \u0026\u0026 folio-\u003emapping)\n+\t\treturn -EBUSY;\n+\n \treturn 0;\n }\n \n-/* Number of folio references from the pagecache or the swapcache. */\n-static unsigned int folio_cache_ref_count(const struct folio *folio)\n+/* Number of folio references from the swapcache. */\n+static unsigned int folio_swapcache_ref_count(const struct folio *folio)\n {\n-\tif (folio_test_anon(folio) \u0026\u0026 !folio_test_swapcache(folio))\n+\tif (!folio_test_swapcache(folio))\n \t\treturn 0;\n \treturn folio_nr_pages(folio);\n }\n \n-static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order,\n-\t\t\t\t\t     struct page *split_at, struct xa_state *xas,\n-\t\t\t\t\t     struct address_space *mapping, bool do_lru,\n-\t\t\t\t\t     struct list_head *list, enum split_type split_type,\n-\t\t\t\t\t     pgoff_t end, int *nr_shmem_dropped)\n+/**\n+ * __folio_split_unmap_and_freeze() - split an anon or swap cache folio\n+ * @folio: folio to split, must be locked\n+ * @new_order: the order of the after-split folios (uniform split), or the\n+ *             smallest order of the after-split folios (non-uniform split)\n+ * @split_at: in non-uniform split, the folio containing @split_at is split\n+ *            until its order becomes @new_order\n+ * @do_lru: if true, add after-split folios to @list if non NULL, otherwise to\n+ *          the LRU list\n+ * @anon_unmap: if true, unmap @folio before the split and remap it after\n+ * @list: after-split folios will be put on it if non NULL\n+ * @split_type: perform uniform split or not (non-uniform split)\n+ *\n+ * Helper for splitting an anon or swap cache folio. It unmaps @folio (unless\n+ * @anon_unmap is false), freezes its refcount, and performs the split, updates\n+ * the swap cache entries. Split folios are unfrozen and remapped.\n+ *\n+ * Return: 0 on success, otherwise an error number is returned.\n+ */\n+static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_order,\n+\t\t\t\t\t  struct page *split_at, bool do_lru, bool anon_unmap,\n+\t\t\t\t\t  struct list_head *list, enum split_type split_type)\n {\n \tstruct folio *end_folio = folio_next(folio);\n+\tbool is_anon = folio_test_anon(folio);\n+\tstruct swap_cluster_info *ci = NULL;\n \tstruct folio *new_folio, *next;\n \tint old_order = folio_order(folio);\n+\tstruct anon_vma *anon_vma = NULL;\n+\tenum ttu_flags ttu_flags = 0;\n \tstruct list_lru_one *lru;\n+\tstruct lruvec *lruvec;\n \tbool dequeue_deferred;\n \tint ret = 0;\n \n-\tVM_WARN_ON_ONCE(!mapping \u0026\u0026 end);\n+\t/*\n+\t * Unmap/remap needs the anon_vma. The caller does not necessarily\n+\t * hold an mmap_lock that would prevent the anon_vma from\n+\t * disappearing, so we first take a reference and lock it. This is\n+\t * similar to folio_lock_anon_vma_read() except the write lock is\n+\t * taken to serialize against parallel split or collapse.\n+\t */\n+\tif (anon_unmap) {\n+\t\tanon_vma = folio_get_anon_vma(folio);\n+\t\tif (!anon_vma)\n+\t\t\treturn -EBUSY;\n+\t\tanon_vma_lock_write(anon_vma);\n+\t}\n+\n+\t/* Racy check if we can split the page, before the optional unmap. */\n+\tif (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {\n+\t\tret = -EAGAIN;\n+\t\tgoto out_unlock;\n+\t}\n+\n+\tif (anon_unmap)\n+\t\tunmap_folio(folio);\n+\n+\tlocal_irq_disable();\n+\n \t/*\n \t * If this folio can be on the deferred split queue, lock out\n \t * the shrinker before freezing the ref. If the shrinker sees\n \t * a 0-ref folio, it assumes it beat folio_put() to the list\n \t * lock and must clean up the LRU state - the same dequeue we\n \t * will do below as part of the split.\n+\t *\n+\t * Only anon folios are ever queued on the deferred split list,\n+\t * so non-anon folios (mappingless swapcache) never need dequeuing.\n \t */\n-\tdequeue_deferred = folio_test_anon(folio) \u0026\u0026 old_order \u003e 1;\n+\tdequeue_deferred = old_order \u003e 1 \u0026\u0026 is_anon;\n \tif (dequeue_deferred) {\n \t\tstruct mem_cgroup *memcg;\n \n@@ -3952,128 +4014,252 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n\n \t\tlru = list_lru_lock(\u0026deferred_split_lru,\n \t\t\t\t    folio_nid(folio), \u0026memcg);\n \t}\n-\tif (folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {\n-\t\tstruct swap_cluster_info *ci = NULL;\n-\t\tstruct lruvec *lruvec;\n \n+\tif (!folio_ref_freeze(folio, folio_swapcache_ref_count(folio) + 1)) {\n \t\tif (dequeue_deferred) {\n-\t\t\t__list_lru_del(\u0026deferred_split_lru, lru,\n-\t\t\t\t       \u0026folio-\u003e_deferred_list, folio_nid(folio));\n-\t\t\tif (folio_test_partially_mapped(folio)) {\n-\t\t\t\tfolio_clear_partially_mapped(folio);\n-\t\t\t\tmod_mthp_stat(old_order,\n-\t\t\t\t\tMTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);\n-\t\t\t}\n \t\t\tlist_lru_unlock(lru);\n \t\t\trcu_read_unlock();\n \t\t}\n+\t\tret = -EAGAIN;\n+\t\tgoto out_no_split;\n+\t}\n \n-\t\tif (mapping) {\n-\t\t\tint nr = folio_nr_pages(folio);\n-\n-\t\t\tif (folio_test_pmd_mappable(folio) \u0026\u0026\n-\t\t\t    new_order \u003c HPAGE_PMD_ORDER) {\n-\t\t\t\tif (folio_test_swapbacked(folio)) {\n-\t\t\t\t\tlruvec_stat_mod_folio(folio,\n-\t\t\t\t\t\t\tNR_SHMEM_THPS, -nr);\n-\t\t\t\t} else {\n-\t\t\t\t\tlruvec_stat_mod_folio(folio,\n-\t\t\t\t\t\t\tNR_FILE_THPS, -nr);\n-\t\t\t\t}\n-\t\t\t}\n+\tif (dequeue_deferred) {\n+\t\t__list_lru_del(\u0026deferred_split_lru, lru,\n+\t\t\t       \u0026folio-\u003e_deferred_list, folio_nid(folio));\n+\t\tif (folio_test_partially_mapped(folio)) {\n+\t\t\tfolio_clear_partially_mapped(folio);\n+\t\t\tmod_mthp_stat(old_order,\n+\t\t\t\t      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);\n \t\t}\n+\t\tlist_lru_unlock(lru);\n+\t\trcu_read_unlock();\n+\t}\n \n-\t\tif (folio_test_swapcache(folio)) {\n-\t\t\tif (mapping) {\n-\t\t\t\tVM_WARN_ON_ONCE_FOLIO(mapping, folio);\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n+\tif (folio_test_swapcache(folio))\n+\t\tci = swap_cluster_get_and_lock(folio);\n \n-\t\t\tci = swap_cluster_get_and_lock(folio);\n-\t\t}\n+\tif (do_lru)\n+\t\tlruvec = folio_lruvec_lock(folio);\n+\n+\tret = __split_frozen_folio(folio, new_order, split_at, NULL,\n+\t\t\t\t   NULL, split_type);\n \n-\t\t/* lock lru list/PageCompound, ref frozen by page_ref_freeze */\n+\t/*\n+\t * Unfreeze the post-split folios and put them back to the right\n+\t * place. Keep the head @folio frozen until the end: sub entries\n+\t * in swap cache must be updated first, so a concurrent\n+\t * swap_cache_get_folio() cannot return the head folio for a sub\n+\t * entry (folio_try_get() will fail on the head @folio until unfreeze).\n+\t */\n+\tfor_each_folio_safe(folio_next(folio), end_folio, new_folio, next) {\n+\t\tzone_device_private_split_cb(folio, new_folio);\n+\t\tfolio_ref_unfreeze(new_folio,\n+\t\t\t\t   folio_swapcache_ref_count(new_folio) + 1);\n \t\tif (do_lru)\n-\t\t\tlruvec = folio_lruvec_lock(folio);\n+\t\t\tlru_add_split_folio(folio, new_folio, lruvec, list);\n+\t\tif (ci)\n+\t\t\t__swap_cache_replace_folio(ci, folio, new_folio);\n+\t}\n \n-\t\tret = __split_unmapped_folio(folio, new_order, split_at, xas,\n-\t\t\t\t\t     mapping, split_type);\n+\tzone_device_private_split_cb(folio, NULL);\n+\tfolio_ref_unfreeze(folio, folio_swapcache_ref_count(folio) + 1);\n \n-\t\t/*\n-\t\t * Unfreeze after-split folios and put them back to the right\n-\t\t * list. @folio should be kept frozon until page cache\n-\t\t * entries are updated with all the other after-split folios\n-\t\t * to prevent others seeing stale page cache entries.\n-\t\t * As a result, new_folio starts from the next folio of\n-\t\t * @folio.\n-\t\t */\n-\t\tfor (new_folio = folio_next(folio); new_folio != end_folio;\n-\t\t     new_folio = next) {\n-\t\t\tunsigned long nr_pages = folio_nr_pages(new_folio);\n+\tif (do_lru)\n+\t\tlruvec_unlock(lruvec);\n+\tif (ci)\n+\t\tswap_cluster_unlock(ci);\n+out_no_split:\n+\tlocal_irq_enable();\n+\tif (anon_unmap) {\n+\t\tif (!ret \u0026\u0026 !folio_is_device_private(folio))\n+\t\t\tttu_flags = TTU_USE_SHARED_ZEROPAGE;\n+\t\tremap_page(folio, 1 \u003c\u003c old_order, ttu_flags);\n+\t}\n+out_unlock:\n+\tif (anon_unmap) {\n+\t\tanon_vma_unlock_write(anon_vma);\n+\t\tput_anon_vma(anon_vma);\n+\t}\n \n-\t\t\tnext = folio_next(new_folio);\n+\treturn ret;\n+}\n \n-\t\t\tzone_device_private_split_cb(folio, new_folio);\n+/**\n+ * __folio_split_unmap_and_freeze_file() - split a file-backed folio\n+ * @folio: folio to split, must be locked and file-backed\n+ * @new_order: the order of the after-split folios (uniform split), or the\n+ *             smallest order of the after-split folios (non-uniform split)\n+ * @split_at: in non-uniform split, the folio containing @split_at is split\n+ *            until its order becomes @new_order\n+ * @list: after-split folios will be put on it if non NULL\n+ * @split_type: perform uniform split or not (non-uniform split)\n+ *\n+ * Helper for splitting a file-backed folio. It unmaps @folio, freezes its\n+ * refcount, and perform the split, updates the page cache entries. Split\n+ * folios are unfrozen but not remapped, they are faulted back in on demand.\n+ *\n+ * Return: 0 on success, otherwise an error number is returned. (if -ENOMEM\n+ * is returned, @folio might be split but not to @new_order)\n+ */\n+static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int new_order,\n+\t\t\t\t\t       struct page *split_at, struct list_head *list,\n+\t\t\t\t\t       enum split_type split_type)\n+{\n+\tstruct address_space *mapping = folio-\u003emapping;\n+\tXA_STATE(xas, \u0026mapping-\u003ei_pages, folio-\u003eindex);\n+\tstruct folio *end_folio = folio_next(folio);\n+\tlong old_nr_pages = folio_nr_pages(folio);\n+\tstruct mem_cgroup *memcg, *old_memcg;\n+\tstruct folio *new_folio, *next;\n+\tint nr_shmem_dropped = 0;\n+\tunsigned int min_order;\n+\tstruct lruvec *lruvec;\n+\tpgoff_t end = 0;\n+\tgfp_t gfp;\n+\tint ret = 0;\n \n-\t\t\tfolio_ref_unfreeze(new_folio,\n-\t\t\t\t\t   folio_cache_ref_count(new_folio) + 1);\n+\tmin_order = mapping_min_folio_order(mapping);\n+\tif (new_order \u003c min_order)\n+\t\treturn -EINVAL;\n \n-\t\t\tif (do_lru)\n-\t\t\t\tlru_add_split_folio(folio, new_folio, lruvec, list);\n+\t/*\n+\t * Switch to folio's memcg as xarray node allocation can happen and\n+\t * needs to charge to it.\n+\t */\n+\tmemcg = get_mem_cgroup_from_folio(folio);\n+\told_memcg = set_active_memcg(memcg);\n \n-\t\t\t/*\n-\t\t\t * Anonymous folio with swap cache.\n-\t\t\t * NOTE: shmem in swap cache is not supported yet.\n-\t\t\t */\n-\t\t\tif (ci) {\n-\t\t\t\t__swap_cache_replace_folio(ci, folio, new_folio);\n-\t\t\t\tcontinue;\n-\t\t\t}\n+\tgfp = current_gfp_context(mapping_gfp_mask(mapping) \u0026 GFP_RECLAIM_MASK);\n+\tif (!filemap_release_folio(folio, gfp)) {\n+\t\tret = -EBUSY;\n+\t\tgoto fail_free;\n+\t}\n \n-\t\t\t/* Anonymous folio without swap cache */\n-\t\t\tif (!mapping)\n-\t\t\t\tcontinue;\n+\tmapping_set_update(\u0026xas, mapping);\n \n-\t\t\t/* Add the new folio to the page cache. */\n-\t\t\tif (new_folio-\u003eindex \u003c end) {\n-\t\t\t\t__xa_store(\u0026mapping-\u003ei_pages, new_folio-\u003eindex,\n-\t\t\t\t\t   new_folio, 0);\n-\t\t\t\tcontinue;\n-\t\t\t}\n+\tif (split_type == SPLIT_TYPE_UNIFORM) {\n+\t\tint old_order = folio_order(folio);\n \n-\t\t\tVM_WARN_ON_ONCE(!nr_shmem_dropped);\n-\t\t\t/* Drop folio beyond EOF: -\u003eindex \u003e= end */\n-\t\t\tif (shmem_mapping(mapping) \u0026\u0026 nr_shmem_dropped)\n-\t\t\t\t*nr_shmem_dropped += nr_pages;\n-\t\t\telse if (folio_test_clear_dirty(new_folio))\n-\t\t\t\tfolio_account_cleaned(\n-\t\t\t\t\tnew_folio, inode_to_wb(mapping-\u003ehost));\n-\t\t\t__filemap_remove_folio(new_folio, NULL);\n-\t\t\tfolio_put_refs(new_folio, nr_pages);\n+\t\txas_set_order(\u0026xas, folio-\u003eindex, new_order);\n+\t\txas_split_alloc(\u0026xas, folio, old_order, gfp);\n+\t\tif (xas_error(\u0026xas)) {\n+\t\t\tret = xas_error(\u0026xas);\n+\t\t\tgoto fail_free;\n \t\t}\n+\t}\n \n-\t\tzone_device_private_split_cb(folio, NULL);\n-\t\t/*\n-\t\t * Unfreeze @folio only after all page cache entries, which\n-\t\t * used to point to it, have been updated with new folios.\n-\t\t * Otherwise, a parallel folio_try_get() can grab @folio\n-\t\t * and its caller can see stale page cache entries.\n-\t\t */\n-\t\tfolio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);\n+\ti_mmap_lock_read(mapping);\n \n-\t\tif (do_lru)\n-\t\t\tlruvec_unlock(lruvec);\n+\t/* Racy check if we can split the page, before unmap_folio() */\n+\tif (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {\n+\t\tret = -EAGAIN;\n+\t\tgoto fail_mmap_unlock;\n+\t}\n \n-\t\tif (ci)\n-\t\t\tswap_cluster_unlock(ci);\n-\t} else {\n-\t\tif (dequeue_deferred) {\n-\t\t\tlist_lru_unlock(lru);\n-\t\t\trcu_read_unlock();\n+\t/*\n+\t * __split_frozen_folio() may need to trim off pages beyond\n+\t * EOF: but on 32-bit, i_size_read() takes an irq-unsafe\n+\t * seqlock, which cannot be nested inside the page tree lock.\n+\t * So note end now: i_size itself may be changed at any moment,\n+\t * but folio lock is good enough to serialize the trimming.\n+\t */\n+\tend = DIV_ROUND_UP(i_size_read(mapping-\u003ehost), PAGE_SIZE);\n+\tif (shmem_mapping(mapping))\n+\t\tend = shmem_fallocend(mapping-\u003ehost, end);\n+\n+\tunmap_folio(folio);\n+\n+\txas_lock_irq(\u0026xas);\n+\n+\t/*\n+\t * Check if the folio is present in page cache.\n+\t * We assume all tail are present too, if folio is there.\n+\t */\n+\tif (xas_load(\u0026xas) != folio) {\n+\t\tret = -EAGAIN;\n+\t\tgoto fail;\n+\t}\n+\n+\tif (!folio_ref_freeze(folio, old_nr_pages + 1)) {\n+\t\tret = -EAGAIN;\n+\t\tgoto fail;\n+\t}\n+\n+\tif (folio_test_pmd_mappable(folio) \u0026\u0026 new_order \u003c HPAGE_PMD_ORDER) {\n+\t\tif (folio_test_swapbacked(folio))\n+\t\t\tlruvec_stat_mod_folio(folio, NR_SHMEM_THPS, -old_nr_pages);\n+\t\telse\n+\t\t\tlruvec_stat_mod_folio(folio, NR_FILE_THPS, -old_nr_pages);\n+\t}\n+\n+\t/* lock lru list/PageCompound, ref frozen by page_ref_freeze */\n+\tlruvec = folio_lruvec_lock(folio);\n+\tret = __split_frozen_folio(folio, new_order, split_at, \u0026xas,\n+\t\t\t\t   mapping, split_type);\n+\n+\t/*\n+\t * Unfreeze after-split folios and put them back to the right\n+\t * list. @folio should be kept frozen until page cache\n+\t * entries are updated with all the other after-split folios\n+\t * to prevent others seeing stale page cache entries.\n+\t * As a result, new_folio starts from the next folio of\n+\t * @folio.\n+\t */\n+\tfor_each_folio_safe(folio_next(folio), end_folio, new_folio, next) {\n+\t\tunsigned long nr_pages = folio_nr_pages(new_folio);\n+\n+\t\tfolio_ref_unfreeze(new_folio,\n+\t\t\t\t   folio_nr_pages(new_folio) + 1);\n+\n+\t\tlru_add_split_folio(folio, new_folio, lruvec, list);\n+\n+\t\t/* Add the new folio to the page cache. */\n+\t\tif (new_folio-\u003eindex \u003c end) {\n+\t\t\t__xa_store(\u0026mapping-\u003ei_pages, new_folio-\u003eindex,\n+\t\t\t\t   new_folio, 0);\n+\t\t\tcontinue;\n \t\t}\n-\t\treturn -EAGAIN;\n+\n+\t\t/* Drop folio beyond EOF: -\u003eindex \u003e= end */\n+\t\tif (shmem_mapping(mapping))\n+\t\t\tnr_shmem_dropped += nr_pages;\n+\t\telse if (folio_test_clear_dirty(new_folio))\n+\t\t\tfolio_account_cleaned(new_folio,\n+\t\t\t\t\t      inode_to_wb(mapping-\u003ehost));\n+\t\t__filemap_remove_folio(new_folio, NULL);\n+\t\tfolio_put_refs(new_folio, nr_pages);\n \t}\n \n+\t/*\n+\t * Unfreeze @folio only after all page cache entries, which\n+\t * used to point to it, have been updated with new folios.\n+\t * Otherwise, a parallel folio_try_get() can grab @folio\n+\t * and its caller can see stale page cache entries.\n+\t */\n+\tfolio_ref_unfreeze(folio, folio_nr_pages(folio) + 1);\n+\tlruvec_unlock(lruvec);\n+fail:\n+\t/*\n+\t * If we want to use try_to_migrate() on file in unmap_folio,\n+\t * remember to add remap_page() and adapt it.\n+\t */\n+\txas_unlock_irq(\u0026xas);\n+fail_mmap_unlock:\n+\tif (nr_shmem_dropped)\n+\t\tshmem_uncharge(mapping-\u003ehost, nr_shmem_dropped);\n+\t/*\n+\t * Drop the mapping while the inode is still pinned. @folio stays\n+\t * locked and present in the page cache, so eviction cannot free\n+\t * the inode yet, nothing past this point may touch the inode or\n+\t * the mapping.\n+\t */\n+\ti_mmap_unlock_read(mapping);\n+fail_free:\n+\t/* Restore the previously active memcg */\n+\tset_active_memcg(old_memcg);\n+\tmem_cgroup_put(memcg);\n+\txas_destroy(\u0026xas);\n \treturn ret;\n }\n \n@@ -4086,9 +4272,9 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n\n  * @list: after-split folios will be put on it if non NULL\n  * @split_type: perform uniform split or not (non-uniform split)\n  *\n- * It calls __split_unmapped_folio() to perform uniform and non-uniform split.\n+ * It calls __split_frozen_folio() to perform uniform and non-uniform split.\n  * It is in charge of checking whether the split is supported or not and\n- * preparing @folio for __split_unmapped_folio().\n+ * preparing @folio for __split_frozen_folio().\n  *\n  * After splitting, the after-split folio containing @lock_at remains locked\n  * and others are unlocked:\n@@ -4102,17 +4288,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,\n \t\tstruct page *split_at, struct page *lock_at,\n \t\tstruct list_head *list, enum split_type split_type)\n {\n-\tXA_STATE(xas, \u0026folio-\u003emapping-\u003ei_pages, folio-\u003eindex);\n+\tbool is_swapcache = folio_test_swapcache(folio);\n \tstruct folio *end_folio = folio_next(folio);\n \tbool is_anon = folio_test_anon(folio);\n-\tstruct mem_cgroup *memcg, *old_memcg;\n-\tstruct address_space *mapping = NULL;\n-\tstruct anon_vma *anon_vma = NULL;\n \tint old_order = folio_order(folio);\n \tstruct folio *new_folio, *next;\n-\tint nr_shmem_dropped = 0;\n-\tenum ttu_flags ttu_flags = 0;\n-\tpgoff_t end = 0;\n \tint ret;\n \n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);\n@@ -4120,141 +4300,29 @@ static int __folio_split(struct folio *folio, unsigned int new_order,\n \n \tif (folio != page_folio(split_at) || folio != page_folio(lock_at)) {\n \t\tret = -EINVAL;\n-\t\tgoto out_no_memcg;\n+\t\tgoto out;\n \t}\n \n \tif (new_order \u003e= old_order) {\n \t\tret = -EINVAL;\n-\t\tgoto out_no_memcg;\n+\t\tgoto out;\n \t}\n \n \tret = folio_check_splittable(folio, new_order, split_type);\n \tif (ret) {\n \t\tVM_WARN_ONCE(ret == -EINVAL, \"Tried to split an unsplittable folio\");\n-\t\tgoto out_no_memcg;\n-\t}\n-\n-\t/*\n-\t * switch to folio's memcg as xarray node allocation can happen and\n-\t * needs to charge to it.\n-\t */\n-\tmemcg = get_mem_cgroup_from_folio(folio);\n-\told_memcg = set_active_memcg(memcg);\n-\n-\tif (is_anon) {\n-\t\t/*\n-\t\t * The caller does not necessarily hold an mmap_lock that would\n-\t\t * prevent the anon_vma disappearing so we first we take a\n-\t\t * reference to it and then lock the anon_vma for write. This\n-\t\t * is similar to folio_lock_anon_vma_read except the write lock\n-\t\t * is taken to serialise against parallel split or collapse\n-\t\t * operations.\n-\t\t */\n-\t\tanon_vma = folio_get_anon_vma(folio);\n-\t\tif (!anon_vma) {\n-\t\t\tret = -EBUSY;\n-\t\t\tgoto out;\n-\t\t}\n-\t\tanon_vma_lock_write(anon_vma);\n-\t\tmapping = NULL;\n-\t} else {\n-\t\tunsigned int min_order;\n-\t\tgfp_t gfp;\n-\n-\t\tmapping = folio-\u003emapping;\n-\t\tmin_order = mapping_min_folio_order(mapping);\n-\t\tif (new_order \u003c min_order) {\n-\t\t\tret = -EINVAL;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tgfp = current_gfp_context(mapping_gfp_mask(mapping) \u0026\n-\t\t\t\t\t\t\tGFP_RECLAIM_MASK);\n-\n-\t\tif (!filemap_release_folio(folio, gfp)) {\n-\t\t\tret = -EBUSY;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tmapping_set_update(\u0026xas, mapping);\n-\n-\t\tif (split_type == SPLIT_TYPE_UNIFORM) {\n-\t\t\txas_set_order(\u0026xas, folio-\u003eindex, new_order);\n-\t\t\txas_split_alloc(\u0026xas, folio, old_order, gfp);\n-\t\t\tif (xas_error(\u0026xas)) {\n-\t\t\t\tret = xas_error(\u0026xas);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\t\t}\n-\n-\t\tanon_vma = NULL;\n-\t\ti_mmap_lock_read(mapping);\n-\n-\t\t/*\n-\t\t *__split_unmapped_folio() may need to trim off pages beyond\n-\t\t * EOF: but on 32-bit, i_size_read() takes an irq-unsafe\n-\t\t * seqlock, which cannot be nested inside the page tree lock.\n-\t\t * So note end now: i_size itself may be changed at any moment,\n-\t\t * but folio lock is good enough to serialize the trimming.\n-\t\t */\n-\t\tend = DIV_ROUND_UP(i_size_read(mapping-\u003ehost), PAGE_SIZE);\n-\t\tif (shmem_mapping(mapping))\n-\t\t\tend = shmem_fallocend(mapping-\u003ehost, end);\n-\t}\n-\n-\t/*\n-\t * Racy check if we can split the page, before unmap_folio() will\n-\t * split PMDs\n-\t */\n-\tif (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {\n-\t\tret = -EAGAIN;\n-\t\tgoto out_unlock;\n-\t}\n-\n-\tunmap_folio(folio);\n-\n-\t/* block interrupt reentry in xa_lock and spinlock */\n-\tlocal_irq_disable();\n-\tif (mapping) {\n-\t\t/*\n-\t\t * Check if the folio is present in page cache.\n-\t\t * We assume all tail are present too, if folio is there.\n-\t\t */\n-\t\txas_lock(\u0026xas);\n-\t\txas_reset(\u0026xas);\n-\t\tif (xas_load(\u0026xas) != folio) {\n-\t\t\tret = -EAGAIN;\n-\t\t\tgoto fail;\n-\t\t}\n+\t\tgoto out;\n \t}\n \n-\tret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, \u0026xas, mapping,\n-\t\t\t\t\t\ttrue, list, split_type, end, \u0026nr_shmem_dropped);\n-fail:\n-\tif (mapping)\n-\t\txas_unlock(\u0026xas);\n-\n-\tlocal_irq_enable();\n-\n-\tif (nr_shmem_dropped)\n-\t\tshmem_uncharge(mapping-\u003ehost, nr_shmem_dropped);\n-\n-\tif (!ret \u0026\u0026 is_anon \u0026\u0026 !folio_is_device_private(folio))\n-\t\tttu_flags = TTU_USE_SHARED_ZEROPAGE;\n-\n-\tremap_page(folio, 1 \u003c\u003c old_order, ttu_flags);\n-\n-\t/*\n-\t * Drop the mapping while the inode is still pinned. @folio stays\n-\t * locked and present in the page cache until the loop below, so\n-\t * eviction cannot free the inode yet; @lock_at is not enough, it may\n-\t * be a tail beyond EOF that the split already dropped from the page\n-\t * cache. Nothing past this point may touch the inode or the mapping.\n-\t */\n-\tif (mapping) {\n-\t\ti_mmap_unlock_read(mapping);\n-\t\tmapping = NULL;\n-\t}\n+\tif (is_anon)\n+\t\tret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,\n+\t\t\t\t\t\t     true, list, split_type);\n+\telse if (is_swapcache)\n+\t\tret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,\n+\t\t\t\t\t\t     false, list, split_type);\n+\telse\n+\t\tret = __folio_split_unmap_and_freeze_file(folio, new_order, split_at,\n+\t\t\t\t\t\t\t  list, split_type);\n \n \t/*\n \t * Unlock all after-split folios except the one containing\n@@ -4265,29 +4333,19 @@ static int __folio_split(struct folio *folio, unsigned int new_order,\n \t\tif (new_folio == page_folio(lock_at))\n \t\t\tcontinue;\n \n-\t\tfolio_unlock(new_folio);\n \t\t/*\n \t\t * Subpages whose mapping has been zapped may be freed\n \t\t * earlier, but freeing them requires taking the\n-\t\t * lru_lock, so we defer put_page() on tail pages until\n+\t\t * lru_lock, so we defer folio_put() on tail pages until\n \t\t * after the split completes.\n \t\t */\n-\t\tfree_folio_and_swap_cache(new_folio);\n+\t\tif (is_swapcache)\n+\t\t\tfolio_free_swap(new_folio);\n+\t\tfolio_unlock(new_folio);\n+\t\tfolio_put(new_folio);\n \t}\n \n-out_unlock:\n-\tif (anon_vma) {\n-\t\tanon_vma_unlock_write(anon_vma);\n-\t\tput_anon_vma(anon_vma);\n-\t}\n-\tif (mapping)\n-\t\ti_mmap_unlock_read(mapping);\n out:\n-\t/* restore to caller's old_memcg */\n-\tset_active_memcg(old_memcg);\n-\tmem_cgroup_put(memcg);\n-out_no_memcg:\n-\txas_destroy(\u0026xas);\n \tif (is_pmd_order(old_order))\n \t\tcount_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);\n \tcount_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);\n@@ -4311,29 +4369,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,\n  * isolated from LRU (if applicable)\n  *\n  * Upon return, the folio is not remapped, split folios are not added to LRU,\n- * free_folio_and_swap_cache() is not called, and new folios remain locked.\n+ * folio_free_swap() is not called, and new folios remain locked.\n  *\n  * Return: 0 on success, -EAGAIN if the folio cannot be split (e.g., due to\n  *         insufficient reference count or extra pins).\n  */\n int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n {\n-\tint ret = 0;\n-\n \tVM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);\n \n-\tif (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)\n-\t\treturn -EAGAIN;\n-\n-\tlocal_irq_disable();\n-\tret = __folio_freeze_and_split_unmapped(folio, new_order, \u0026folio-\u003epage, NULL,\n-\t\t\t\t\t\tNULL, false, NULL, SPLIT_TYPE_UNIFORM,\n-\t\t\t\t\t\t0, NULL);\n-\tlocal_irq_enable();\n-\treturn ret;\n+\treturn __folio_split_unmap_and_freeze(folio, new_order, \u0026folio-\u003epage, false,\n+\t\t\t\t\t      false, NULL, SPLIT_TYPE_UNIFORM);\n }\n \n /*\n@@ -4352,10 +4401,9 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n  *    GUP pins, will result in the folio not getting split; instead, the caller\n  *    will receive an -EAGAIN.\n  *\n- * 4) @new_order \u003e 1, usually. Splitting to order-1 anonymous folios is not\n- *    supported for non-file-backed folios, because folio-\u003e_deferred_list, which\n- *    is used by partially mapped folios, is stored in subpage 2, but an order-1\n- *    folio only has subpages 0 and 1. File-backed order-1 folios are supported,\n+ * 4) @new_order != 1 for anon or swapcache. Anon folios need subpage 2 for\n+ *    _deferred_list, which order-1 folios lack, and a swapcache folio may\n+ *    become anon once faulted in. File-backed order-1 folios are supported,\n  *    since they do not use _deferred_list.\n  *\n  * After splitting, the caller's folio reference will be transferred to @page,\n@@ -4384,7 +4432,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n  * with the folio. Splitting to order 0 is compatible with all folios.\n  */\n int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\n-\t\t\t\t     unsigned int new_order)\n+\t\t\t\t       unsigned int new_order)\n {\n \tstruct folio *folio = page_folio(page);\n \ndiff --git a/mm/swap_state.c b/mm/swap_state.c\nindex 5be825911e645..f1405e5b813ee 100644\n--- a/mm/swap_state.c\n+++ b/mm/swap_state.c\n@@ -388,8 +388,9 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,\n \t    folio_order(old) != folio_order(new)) {\n \t\tci_off = swp_cluster_offset(old-\u003eswap);\n \t\tci_end = ci_off + folio_nr_pages(old);\n-\t\twhile (ci_off++ \u003c ci_end)\n+\t\tdo {\n \t\t\tWARN_ON_ONCE(swp_tb_to_folio(__swap_table_get(ci, ci_off)) != old);\n+\t\t} while (++ci_off \u003c ci_end);\n \t}\n }\n \n"
}

2/1 2026/08/12 20:14 agent patch-evaluator
1m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "SWAP"
  ],
  "FocusSymbols": [
    "__split_huge_page_to_list_to_order",
    "folio_split_unmapped",
    "folio_check_splittable",
    "__swap_cache_replace_folio"
  ],
  "Reasoning": "The patch refactors and modifies the logic for splitting folios, particularly for anonymous and swapcache folios. It introduces new functions to handle splitting and modifies conditions in folio_check_splittable. These are core memory management changes affecting Transparent Huge Pages (THP) and swap, which are reachable from userspace and 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 5ae70f7a267937b5eabcd196fff52e2479d0cd2e
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Aug 12 20:14:22 2026 +0000

    syz-cluster: applied patch under review

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ced400f72d43a..1c61b7d39cd03 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3589,9 +3589,6 @@ static void remap_page(struct folio *folio, unsigned long nr, int flags)
 {
 	int i = 0;
 
-	/* If unmap_folio() uses try_to_migrate() on file, remove this check */
-	if (!folio_test_anon(folio))
-		return;
 	for (;;) {
 		remove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);
 		i += folio_nr_pages(folio);
@@ -3634,6 +3631,18 @@ static bool page_range_has_hwpoisoned(struct page *page, long nr_pages)
 	return false;
 }
 
+/**
+ * for_each_folio_safe - iterate over contiguous folios safe against folio free
+ * @start: the first folio to iterate
+ * @end: sentinel, folio_next() of the last folio to iterate
+ * @sub_folio: struct folio * to use as the loop cursor
+ * @next: struct folio * used as temporary storage
+ */
+#define for_each_folio_safe(start, end, sub_folio, next) \
+	for (sub_folio = (start), next = folio_next(sub_folio); \
+	     sub_folio != (end); \
+	     sub_folio = next, next = folio_next(next))
+
 /*
  * It splits @folio into @new_order folios and copies the @folio metadata to
  * all the resulting folios.
@@ -3755,8 +3764,8 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
 }
 
 /**
- * __split_unmapped_folio() - splits an unmapped @folio to lower order folios in
- * two ways: uniform split or non-uniform split.
+ * __split_frozen_folio() - splits a frozen @folio to lower order folios
+ * in two ways: uniform split or non-uniform split.
  * @folio: the to-be-split folio
  * @new_order: the smallest order of the after split folios (since buddy
  *             allocator like split generates folios with orders from @folio's
@@ -3795,11 +3804,12 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
  * Return: 0 - successful, <0 - failed (if -ENOMEM is returned, @folio might be
  * split but not to @new_order, the caller needs to check)
  */
-static int __split_unmapped_folio(struct folio *folio, int new_order,
+static int __split_frozen_folio(struct folio *folio, int new_order,
 		struct page *split_at, struct xa_state *xas,
 		struct address_space *mapping, enum split_type split_type)
 {
 	const bool is_anon = folio_test_anon(folio);
+	const bool is_swapcache = folio_test_swapcache(folio);
 	int old_order = folio_order(folio);
 	int start_order = split_type == SPLIT_TYPE_UNIFORM ? new_order : old_order - 1;
 	struct folio *old_folio = folio;
@@ -3814,8 +3824,8 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 	     split_order--) {
 		int nr_new_folios = 1UL << (old_order - split_order);
 
-		/* order-1 anonymous folio is not supported */
-		if (is_anon && split_order == 1)
+		/* order-1 anonymous or swapcache folio is not supported */
+		if ((is_anon || is_swapcache) && split_order == 1)
 			continue;
 
 		if (mapping) {
@@ -3878,32 +3888,26 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 int folio_check_splittable(struct folio *folio, unsigned int new_order,
 			   enum split_type split_type)
 {
+	bool is_anon = folio_test_anon(folio);
+	bool is_swapcache = folio_test_swapcache(folio);
+
 	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
 	/*
 	 * Folios that just got truncated cannot get split. Signal to the
-	 * caller that there was a race.
-	 *
-	 * TODO: this will also currently refuse folios without a mapping in the
-	 * swapcache (shmem or to-be-anon folios).
+	 * caller that there was a race. A mappingless swap cache folio
+	 * has no page cache entries to update, so it is fine to split.
 	 */
-	if (!folio->mapping && !folio_test_anon(folio))
+	if (!folio->mapping && !is_swapcache)
 		return -EBUSY;
 
-	/* order-1 is not supported for anonymous THP. */
-	if (folio_test_anon(folio) && new_order == 1)
-		return -EINVAL;
-
 	/*
-	 * swapcache folio could only be split to order 0
-	 *
-	 * non-uniform split creates after-split folios with orders from
-	 * folio_order(folio) - 1 to new_order, making it not suitable for any
-	 * swapcache folio split. Only uniform split to order-0 can be used
-	 * here.
+	 * Order-1 is unsupported: anon folios need subpage 2 for the
+	 * deferred split list, hybrid shmem & swap cache folios are not
+	 * splittable, and a splittable mappingless swap cache folio could
+	 * be either anon or shmem, which we cannot tell apart.
 	 */
-	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && folio_test_swapcache(folio)) {
+	if ((is_anon || is_swapcache) && new_order == 1)
 		return -EINVAL;
-	}
 
 	if (is_huge_zero_folio(folio))
 		return -EINVAL;
@@ -3911,39 +3915,97 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	if (folio_test_writeback(folio))
 		return -EBUSY;
 
+	/*
+	 * A non-anon swapcache folio that still has a mapping can only be a
+	 * shmem folio under SWAP IO, it's removed from either swap cache or
+	 * shmem mapping afterward. There is little benefit in splitting them
+	 * hence reject it here up front before touching anything.
+	 */
+	if (!is_anon && is_swapcache && folio->mapping)
+		return -EBUSY;
+
 	return 0;
 }
 
-/* Number of folio references from the pagecache or the swapcache. */
-static unsigned int folio_cache_ref_count(const struct folio *folio)
+/* Number of folio references from the swapcache. */
+static unsigned int folio_swapcache_ref_count(const struct folio *folio)
 {
-	if (folio_test_anon(folio) && !folio_test_swapcache(folio))
+	if (!folio_test_swapcache(folio))
 		return 0;
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order,
-					     struct page *split_at, struct xa_state *xas,
-					     struct address_space *mapping, bool do_lru,
-					     struct list_head *list, enum split_type split_type,
-					     pgoff_t end, int *nr_shmem_dropped)
+/**
+ * __folio_split_unmap_and_freeze() - split an anon or swap cache folio
+ * @folio: folio to split, must be locked
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @do_lru: if true, add after-split folios to @list if non NULL, otherwise to
+ *          the LRU list
+ * @anon_unmap: if true, unmap @folio before the split and remap it after
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting an anon or swap cache folio. It unmaps @folio (unless
+ * @anon_unmap is false), freezes its refcount, and performs the split, updates
+ * the swap cache entries. Split folios are unfrozen and remapped.
+ *
+ * Return: 0 on success, otherwise an error number is returned.
+ */
+static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_order,
+					  struct page *split_at, bool do_lru, bool anon_unmap,
+					  struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
+	bool is_anon = folio_test_anon(folio);
+	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio, *next;
 	int old_order = folio_order(folio);
+	struct anon_vma *anon_vma = NULL;
+	enum ttu_flags ttu_flags = 0;
 	struct list_lru_one *lru;
+	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
-	VM_WARN_ON_ONCE(!mapping && end);
+	/*
+	 * Unmap/remap needs the anon_vma. The caller does not necessarily
+	 * hold an mmap_lock that would prevent the anon_vma from
+	 * disappearing, so we first take a reference and lock it. This is
+	 * similar to folio_lock_anon_vma_read() except the write lock is
+	 * taken to serialize against parallel split or collapse.
+	 */
+	if (anon_unmap) {
+		anon_vma = folio_get_anon_vma(folio);
+		if (!anon_vma)
+			return -EBUSY;
+		anon_vma_lock_write(anon_vma);
+	}
+
+	/* Racy check if we can split the page, before the optional unmap. */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	if (anon_unmap)
+		unmap_folio(folio);
+
+	local_irq_disable();
+
 	/*
 	 * If this folio can be on the deferred split queue, lock out
 	 * the shrinker before freezing the ref. If the shrinker sees
 	 * a 0-ref folio, it assumes it beat folio_put() to the list
 	 * lock and must clean up the LRU state - the same dequeue we
 	 * will do below as part of the split.
+	 *
+	 * Only anon folios are ever queued on the deferred split list,
+	 * so non-anon folios (mappingless swapcache) never need dequeuing.
 	 */
-	dequeue_deferred = folio_test_anon(folio) && old_order > 1;
+	dequeue_deferred = old_order > 1 && is_anon;
 	if (dequeue_deferred) {
 		struct mem_cgroup *memcg;
 
@@ -3952,128 +4014,252 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		lru = list_lru_lock(&deferred_split_lru,
 				    folio_nid(folio), &memcg);
 	}
-	if (folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
-		struct swap_cluster_info *ci = NULL;
-		struct lruvec *lruvec;
 
+	if (!folio_ref_freeze(folio, folio_swapcache_ref_count(folio) + 1)) {
 		if (dequeue_deferred) {
-			__list_lru_del(&deferred_split_lru, lru,
-				       &folio->_deferred_list, folio_nid(folio));
-			if (folio_test_partially_mapped(folio)) {
-				folio_clear_partially_mapped(folio);
-				mod_mthp_stat(old_order,
-					MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
-			}
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
+		ret = -EAGAIN;
+		goto out_no_split;
+	}
 
-		if (mapping) {
-			int nr = folio_nr_pages(folio);
-
-			if (folio_test_pmd_mappable(folio) &&
-			    new_order < HPAGE_PMD_ORDER) {
-				if (folio_test_swapbacked(folio)) {
-					lruvec_stat_mod_folio(folio,
-							NR_SHMEM_THPS, -nr);
-				} else {
-					lruvec_stat_mod_folio(folio,
-							NR_FILE_THPS, -nr);
-				}
-			}
+	if (dequeue_deferred) {
+		__list_lru_del(&deferred_split_lru, lru,
+			       &folio->_deferred_list, folio_nid(folio));
+		if (folio_test_partially_mapped(folio)) {
+			folio_clear_partially_mapped(folio);
+			mod_mthp_stat(old_order,
+				      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
 		}
+		list_lru_unlock(lru);
+		rcu_read_unlock();
+	}
 
-		if (folio_test_swapcache(folio)) {
-			if (mapping) {
-				VM_WARN_ON_ONCE_FOLIO(mapping, folio);
-				return -EINVAL;
-			}
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
 
-			ci = swap_cluster_get_and_lock(folio);
-		}
+	if (do_lru)
+		lruvec = folio_lruvec_lock(folio);
+
+	ret = __split_frozen_folio(folio, new_order, split_at, NULL,
+				   NULL, split_type);
 
-		/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	/*
+	 * Unfreeze the post-split folios and put them back to the right
+	 * place. Keep the head @folio frozen until the end: sub entries
+	 * in swap cache must be updated first, so a concurrent
+	 * swap_cache_get_folio() cannot return the head folio for a sub
+	 * entry (folio_try_get() will fail on the head @folio until unfreeze).
+	 */
+	for_each_folio_safe(folio_next(folio), end_folio, new_folio, next) {
+		zone_device_private_split_cb(folio, new_folio);
+		folio_ref_unfreeze(new_folio,
+				   folio_swapcache_ref_count(new_folio) + 1);
 		if (do_lru)
-			lruvec = folio_lruvec_lock(folio);
+			lru_add_split_folio(folio, new_folio, lruvec, list);
+		if (ci)
+			__swap_cache_replace_folio(ci, folio, new_folio);
+	}
 
-		ret = __split_unmapped_folio(folio, new_order, split_at, xas,
-					     mapping, split_type);
+	zone_device_private_split_cb(folio, NULL);
+	folio_ref_unfreeze(folio, folio_swapcache_ref_count(folio) + 1);
 
-		/*
-		 * Unfreeze after-split folios and put them back to the right
-		 * list. @folio should be kept frozon until page cache
-		 * entries are updated with all the other after-split folios
-		 * to prevent others seeing stale page cache entries.
-		 * As a result, new_folio starts from the next folio of
-		 * @folio.
-		 */
-		for (new_folio = folio_next(folio); new_folio != end_folio;
-		     new_folio = next) {
-			unsigned long nr_pages = folio_nr_pages(new_folio);
+	if (do_lru)
+		lruvec_unlock(lruvec);
+	if (ci)
+		swap_cluster_unlock(ci);
+out_no_split:
+	local_irq_enable();
+	if (anon_unmap) {
+		if (!ret && !folio_is_device_private(folio))
+			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
+		remap_page(folio, 1 << old_order, ttu_flags);
+	}
+out_unlock:
+	if (anon_unmap) {
+		anon_vma_unlock_write(anon_vma);
+		put_anon_vma(anon_vma);
+	}
 
-			next = folio_next(new_folio);
+	return ret;
+}
 
-			zone_device_private_split_cb(folio, new_folio);
+/**
+ * __folio_split_unmap_and_freeze_file() - split a file-backed folio
+ * @folio: folio to split, must be locked and file-backed
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting a file-backed folio. It unmaps @folio, freezes its
+ * refcount, and perform the split, updates the page cache entries. Split
+ * folios are unfrozen but not remapped, they are faulted back in on demand.
+ *
+ * Return: 0 on success, otherwise an error number is returned. (if -ENOMEM
+ * is returned, @folio might be split but not to @new_order)
+ */
+static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int new_order,
+					       struct page *split_at, struct list_head *list,
+					       enum split_type split_type)
+{
+	struct address_space *mapping = folio->mapping;
+	XA_STATE(xas, &mapping->i_pages, folio->index);
+	struct folio *end_folio = folio_next(folio);
+	long old_nr_pages = folio_nr_pages(folio);
+	struct mem_cgroup *memcg, *old_memcg;
+	struct folio *new_folio, *next;
+	int nr_shmem_dropped = 0;
+	unsigned int min_order;
+	struct lruvec *lruvec;
+	pgoff_t end = 0;
+	gfp_t gfp;
+	int ret = 0;
 
-			folio_ref_unfreeze(new_folio,
-					   folio_cache_ref_count(new_folio) + 1);
+	min_order = mapping_min_folio_order(mapping);
+	if (new_order < min_order)
+		return -EINVAL;
 
-			if (do_lru)
-				lru_add_split_folio(folio, new_folio, lruvec, list);
+	/*
+	 * Switch to folio's memcg as xarray node allocation can happen and
+	 * needs to charge to it.
+	 */
+	memcg = get_mem_cgroup_from_folio(folio);
+	old_memcg = set_active_memcg(memcg);
 
-			/*
-			 * Anonymous folio with swap cache.
-			 * NOTE: shmem in swap cache is not supported yet.
-			 */
-			if (ci) {
-				__swap_cache_replace_folio(ci, folio, new_folio);
-				continue;
-			}
+	gfp = current_gfp_context(mapping_gfp_mask(mapping) & GFP_RECLAIM_MASK);
+	if (!filemap_release_folio(folio, gfp)) {
+		ret = -EBUSY;
+		goto fail_free;
+	}
 
-			/* Anonymous folio without swap cache */
-			if (!mapping)
-				continue;
+	mapping_set_update(&xas, mapping);
 
-			/* Add the new folio to the page cache. */
-			if (new_folio->index < end) {
-				__xa_store(&mapping->i_pages, new_folio->index,
-					   new_folio, 0);
-				continue;
-			}
+	if (split_type == SPLIT_TYPE_UNIFORM) {
+		int old_order = folio_order(folio);
 
-			VM_WARN_ON_ONCE(!nr_shmem_dropped);
-			/* Drop folio beyond EOF: ->index >= end */
-			if (shmem_mapping(mapping) && nr_shmem_dropped)
-				*nr_shmem_dropped += nr_pages;
-			else if (folio_test_clear_dirty(new_folio))
-				folio_account_cleaned(
-					new_folio, inode_to_wb(mapping->host));
-			__filemap_remove_folio(new_folio, NULL);
-			folio_put_refs(new_folio, nr_pages);
+		xas_set_order(&xas, folio->index, new_order);
+		xas_split_alloc(&xas, folio, old_order, gfp);
+		if (xas_error(&xas)) {
+			ret = xas_error(&xas);
+			goto fail_free;
 		}
+	}
 
-		zone_device_private_split_cb(folio, NULL);
-		/*
-		 * Unfreeze @folio only after all page cache entries, which
-		 * used to point to it, have been updated with new folios.
-		 * Otherwise, a parallel folio_try_get() can grab @folio
-		 * and its caller can see stale page cache entries.
-		 */
-		folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+	i_mmap_lock_read(mapping);
 
-		if (do_lru)
-			lruvec_unlock(lruvec);
+	/* Racy check if we can split the page, before unmap_folio() */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto fail_mmap_unlock;
+	}
 
-		if (ci)
-			swap_cluster_unlock(ci);
-	} else {
-		if (dequeue_deferred) {
-			list_lru_unlock(lru);
-			rcu_read_unlock();
+	/*
+	 * __split_frozen_folio() may need to trim off pages beyond
+	 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
+	 * seqlock, which cannot be nested inside the page tree lock.
+	 * So note end now: i_size itself may be changed at any moment,
+	 * but folio lock is good enough to serialize the trimming.
+	 */
+	end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
+	if (shmem_mapping(mapping))
+		end = shmem_fallocend(mapping->host, end);
+
+	unmap_folio(folio);
+
+	xas_lock_irq(&xas);
+
+	/*
+	 * Check if the folio is present in page cache.
+	 * We assume all tail are present too, if folio is there.
+	 */
+	if (xas_load(&xas) != folio) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (!folio_ref_freeze(folio, old_nr_pages + 1)) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (folio_test_pmd_mappable(folio) && new_order < HPAGE_PMD_ORDER) {
+		if (folio_test_swapbacked(folio))
+			lruvec_stat_mod_folio(folio, NR_SHMEM_THPS, -old_nr_pages);
+		else
+			lruvec_stat_mod_folio(folio, NR_FILE_THPS, -old_nr_pages);
+	}
+
+	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	lruvec = folio_lruvec_lock(folio);
+	ret = __split_frozen_folio(folio, new_order, split_at, &xas,
+				   mapping, split_type);
+
+	/*
+	 * Unfreeze after-split folios and put them back to the right
+	 * list. @folio should be kept frozen until page cache
+	 * entries are updated with all the other after-split folios
+	 * to prevent others seeing stale page cache entries.
+	 * As a result, new_folio starts from the next folio of
+	 * @folio.
+	 */
+	for_each_folio_safe(folio_next(folio), end_folio, new_folio, next) {
+		unsigned long nr_pages = folio_nr_pages(new_folio);
+
+		folio_ref_unfreeze(new_folio,
+				   folio_nr_pages(new_folio) + 1);
+
+		lru_add_split_folio(folio, new_folio, lruvec, list);
+
+		/* Add the new folio to the page cache. */
+		if (new_folio->index < end) {
+			__xa_store(&mapping->i_pages, new_folio->index,
+				   new_folio, 0);
+			continue;
 		}
-		return -EAGAIN;
+
+		/* Drop folio beyond EOF: ->index >= end */
+		if (shmem_mapping(mapping))
+			nr_shmem_dropped += nr_pages;
+		else if (folio_test_clear_dirty(new_folio))
+			folio_account_cleaned(new_folio,
+					      inode_to_wb(mapping->host));
+		__filemap_remove_folio(new_folio, NULL);
+		folio_put_refs(new_folio, nr_pages);
 	}
 
+	/*
+	 * Unfreeze @folio only after all page cache entries, which
+	 * used to point to it, have been updated with new folios.
+	 * Otherwise, a parallel folio_try_get() can grab @folio
+	 * and its caller can see stale page cache entries.
+	 */
+	folio_ref_unfreeze(folio, folio_nr_pages(folio) + 1);
+	lruvec_unlock(lruvec);
+fail:
+	/*
+	 * If we want to use try_to_migrate() on file in unmap_folio,
+	 * remember to add remap_page() and adapt it.
+	 */
+	xas_unlock_irq(&xas);
+fail_mmap_unlock:
+	if (nr_shmem_dropped)
+		shmem_uncharge(mapping->host, nr_shmem_dropped);
+	/*
+	 * Drop the mapping while the inode is still pinned. @folio stays
+	 * locked and present in the page cache, so eviction cannot free
+	 * the inode yet, nothing past this point may touch the inode or
+	 * the mapping.
+	 */
+	i_mmap_unlock_read(mapping);
+fail_free:
+	/* Restore the previously active memcg */
+	set_active_memcg(old_memcg);
+	mem_cgroup_put(memcg);
+	xas_destroy(&xas);
 	return ret;
 }
 
@@ -4086,9 +4272,9 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
  * @list: after-split folios will be put on it if non NULL
  * @split_type: perform uniform split or not (non-uniform split)
  *
- * It calls __split_unmapped_folio() to perform uniform and non-uniform split.
+ * It calls __split_frozen_folio() to perform uniform and non-uniform split.
  * It is in charge of checking whether the split is supported or not and
- * preparing @folio for __split_unmapped_folio().
+ * preparing @folio for __split_frozen_folio().
  *
  * After splitting, the after-split folio containing @lock_at remains locked
  * and others are unlocked:
@@ -4102,17 +4288,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		struct page *split_at, struct page *lock_at,
 		struct list_head *list, enum split_type split_type)
 {
-	XA_STATE(xas, &folio->mapping->i_pages, folio->index);
+	bool is_swapcache = folio_test_swapcache(folio);
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
-	struct mem_cgroup *memcg, *old_memcg;
-	struct address_space *mapping = NULL;
-	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
-	int nr_shmem_dropped = 0;
-	enum ttu_flags ttu_flags = 0;
-	pgoff_t end = 0;
 	int ret;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
@@ -4120,141 +4300,29 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	if (folio != page_folio(split_at) || folio != page_folio(lock_at)) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	if (new_order >= old_order) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	ret = folio_check_splittable(folio, new_order, split_type);
 	if (ret) {
 		VM_WARN_ONCE(ret == -EINVAL, "Tried to split an unsplittable folio");
-		goto out_no_memcg;
-	}
-
-	/*
-	 * switch to folio's memcg as xarray node allocation can happen and
-	 * needs to charge to it.
-	 */
-	memcg = get_mem_cgroup_from_folio(folio);
-	old_memcg = set_active_memcg(memcg);
-
-	if (is_anon) {
-		/*
-		 * The caller does not necessarily hold an mmap_lock that would
-		 * prevent the anon_vma disappearing so we first we take a
-		 * reference to it and then lock the anon_vma for write. This
-		 * is similar to folio_lock_anon_vma_read except the write lock
-		 * is taken to serialise against parallel split or collapse
-		 * operations.
-		 */
-		anon_vma = folio_get_anon_vma(folio);
-		if (!anon_vma) {
-			ret = -EBUSY;
-			goto out;
-		}
-		anon_vma_lock_write(anon_vma);
-		mapping = NULL;
-	} else {
-		unsigned int min_order;
-		gfp_t gfp;
-
-		mapping = folio->mapping;
-		min_order = mapping_min_folio_order(mapping);
-		if (new_order < min_order) {
-			ret = -EINVAL;
-			goto out;
-		}
-
-		gfp = current_gfp_context(mapping_gfp_mask(mapping) &
-							GFP_RECLAIM_MASK);
-
-		if (!filemap_release_folio(folio, gfp)) {
-			ret = -EBUSY;
-			goto out;
-		}
-
-		mapping_set_update(&xas, mapping);
-
-		if (split_type == SPLIT_TYPE_UNIFORM) {
-			xas_set_order(&xas, folio->index, new_order);
-			xas_split_alloc(&xas, folio, old_order, gfp);
-			if (xas_error(&xas)) {
-				ret = xas_error(&xas);
-				goto out;
-			}
-		}
-
-		anon_vma = NULL;
-		i_mmap_lock_read(mapping);
-
-		/*
-		 *__split_unmapped_folio() may need to trim off pages beyond
-		 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
-		 * seqlock, which cannot be nested inside the page tree lock.
-		 * So note end now: i_size itself may be changed at any moment,
-		 * but folio lock is good enough to serialize the trimming.
-		 */
-		end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
-		if (shmem_mapping(mapping))
-			end = shmem_fallocend(mapping->host, end);
-	}
-
-	/*
-	 * Racy check if we can split the page, before unmap_folio() will
-	 * split PMDs
-	 */
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
-		ret = -EAGAIN;
-		goto out_unlock;
-	}
-
-	unmap_folio(folio);
-
-	/* block interrupt reentry in xa_lock and spinlock */
-	local_irq_disable();
-	if (mapping) {
-		/*
-		 * Check if the folio is present in page cache.
-		 * We assume all tail are present too, if folio is there.
-		 */
-		xas_lock(&xas);
-		xas_reset(&xas);
-		if (xas_load(&xas) != folio) {
-			ret = -EAGAIN;
-			goto fail;
-		}
+		goto out;
 	}
 
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, &xas, mapping,
-						true, list, split_type, end, &nr_shmem_dropped);
-fail:
-	if (mapping)
-		xas_unlock(&xas);
-
-	local_irq_enable();
-
-	if (nr_shmem_dropped)
-		shmem_uncharge(mapping->host, nr_shmem_dropped);
-
-	if (!ret && is_anon && !folio_is_device_private(folio))
-		ttu_flags = TTU_USE_SHARED_ZEROPAGE;
-
-	remap_page(folio, 1 << old_order, ttu_flags);
-
-	/*
-	 * Drop the mapping while the inode is still pinned. @folio stays
-	 * locked and present in the page cache until the loop below, so
-	 * eviction cannot free the inode yet; @lock_at is not enough, it may
-	 * be a tail beyond EOF that the split already dropped from the page
-	 * cache. Nothing past this point may touch the inode or the mapping.
-	 */
-	if (mapping) {
-		i_mmap_unlock_read(mapping);
-		mapping = NULL;
-	}
+	if (is_anon)
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     true, list, split_type);
+	else if (is_swapcache)
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     false, list, split_type);
+	else
+		ret = __folio_split_unmap_and_freeze_file(folio, new_order, split_at,
+							  list, split_type);
 
 	/*
 	 * Unlock all after-split folios except the one containing
@@ -4265,29 +4333,19 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		if (new_folio == page_folio(lock_at))
 			continue;
 
-		folio_unlock(new_folio);
 		/*
 		 * Subpages whose mapping has been zapped may be freed
 		 * earlier, but freeing them requires taking the
-		 * lru_lock, so we defer put_page() on tail pages until
+		 * lru_lock, so we defer folio_put() on tail pages until
 		 * after the split completes.
 		 */
-		free_folio_and_swap_cache(new_folio);
+		if (is_swapcache)
+			folio_free_swap(new_folio);
+		folio_unlock(new_folio);
+		folio_put(new_folio);
 	}
 
-out_unlock:
-	if (anon_vma) {
-		anon_vma_unlock_write(anon_vma);
-		put_anon_vma(anon_vma);
-	}
-	if (mapping)
-		i_mmap_unlock_read(mapping);
 out:
-	/* restore to caller's old_memcg */
-	set_active_memcg(old_memcg);
-	mem_cgroup_put(memcg);
-out_no_memcg:
-	xas_destroy(&xas);
 	if (is_pmd_order(old_order))
 		count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);
 	count_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);
@@ -4311,29 +4369,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
  * isolated from LRU (if applicable)
  *
  * Upon return, the folio is not remapped, split folios are not added to LRU,
- * free_folio_and_swap_cache() is not called, and new folios remain locked.
+ * folio_free_swap() is not called, and new folios remain locked.
  *
  * Return: 0 on success, -EAGAIN if the folio cannot be split (e.g., due to
  *         insufficient reference count or extra pins).
  */
 int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 {
-	int ret = 0;
-
 	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
 
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
-		return -EAGAIN;
-
-	local_irq_disable();
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, &folio->page, NULL,
-						NULL, false, NULL, SPLIT_TYPE_UNIFORM,
-						0, NULL);
-	local_irq_enable();
-	return ret;
+	return __folio_split_unmap_and_freeze(folio, new_order, &folio->page, false,
+					      false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*
@@ -4352,10 +4401,9 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  *    GUP pins, will result in the folio not getting split; instead, the caller
  *    will receive an -EAGAIN.
  *
- * 4) @new_order > 1, usually. Splitting to order-1 anonymous folios is not
- *    supported for non-file-backed folios, because folio->_deferred_list, which
- *    is used by partially mapped folios, is stored in subpage 2, but an order-1
- *    folio only has subpages 0 and 1. File-backed order-1 folios are supported,
+ * 4) @new_order != 1 for anon or swapcache. Anon folios need subpage 2 for
+ *    _deferred_list, which order-1 folios lack, and a swapcache folio may
+ *    become anon once faulted in. File-backed order-1 folios are supported,
  *    since they do not use _deferred_list.
  *
  * After splitting, the caller's folio reference will be transferred to @page,
@@ -4384,7 +4432,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  * with the folio. Splitting to order 0 is compatible with all folios.
  */
 int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
-				     unsigned int new_order)
+				       unsigned int new_order)
 {
 	struct folio *folio = page_folio(page);
 
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 5be825911e645..f1405e5b813ee 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -388,8 +388,9 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
 	    folio_order(old) != folio_order(new)) {
 		ci_off = swp_cluster_offset(old->swap);
 		ci_end = ci_off + folio_nr_pages(old);
-		while (ci_off++ < ci_end)
+		do {
 			WARN_ON_ONCE(swp_tb_to_folio(__swap_table_get(ci, ci_off)) != old);
+		} while (++ci_off < ci_end);
 	}
 }
 

3/2 2026/08/12 20:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12881
							output: 40
							thoughts: 363
						

Thoughts:
**My Assessment of the Patch Series: Fuzzing Target Identification**

Okay, let's break this down. First, this patch series is clearly touching crucial kernel memory management code, specifically the `mm/huge_memory.c` and `mm/swap_state.c` files. It's refactoring folio splitting logic, particularly for anonymous and swapcache folios. The introduction of these new functions, `__folio_split_unmap_and_freeze`, `__folio_split_unmap_and_freeze_file` and changes to `folio_check_splittable` strongly suggest a functional change impacting THP and folio splitting. This isn't just a trivial refactor. Given that this area is already fairly complex and prone to subtle race conditions, it's a prime candidate for fuzzing.

My immediate next step is always to identify the focus symbols. Let's see... the patch introduces:

*   `__folio_split_unmap_and_freeze` (static) - Likely an internal helper
*   `__folio_split_unmap_and_freeze_file` (static) - Similar to above
*   `folio_split_unmapped` (exported/global) - A likely starting point
*   `__split_huge_page_to_list_to_order` (exported/global) - Another potential entry point.
*   `folio_check_splittable` (global) - Important for triggering the new code paths.

Wait a second, `folio_split_unmapped` is *not* static, so that's definitely a starting point. `__split_huge_page_to_list_to_order` is global, which is good. Now, is `split_huge_page_to_list_to_order` a macro or a function? Let's quickly double-check `mm/huge_memory.c` to be sure. That's a classic gotcha. Always verify these assumptions.



4/2 2026/08/12 20:14 tool grepper
0m Args:
{
  "Expression": "split_huge_page_to_list_to_order",
  "PathPrefix": "mm/huge_memory.c"
}

Results:
{
  "Output": "mm/huge_memory.c=4377=int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n--\nmm/huge_memory.c-4433- */\nmm/huge_memory.c:4434:int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\nmm/huge_memory.c-4435-\t\t\t\t       unsigned int new_order)\n--\nmm/huge_memory.c-4451- * It has the same prerequisites and returns as\nmm/huge_memory.c:4452: * split_huge_page_to_list_to_order().\nmm/huge_memory.c-4453- *\n--\nmm/huge_memory.c=4501=int split_folio_to_list(struct folio *folio, struct list_head *list)\nmm/huge_memory.c-4502-{\nmm/huge_memory.c:4503:\treturn split_huge_page_to_list_to_order(\u0026folio-\u003epage, list, 0);\nmm/huge_memory.c-4504-}\n--\nmm/huge_memory.c=4787=static int split_huge_pages_pid(int pid, unsigned long vaddr_start,\n--\nmm/huge_memory.c-4856-\t\t/*\nmm/huge_memory.c:4857:\t\t * For folios with private, split_huge_page_to_list_to_order()\nmm/huge_memory.c-4858-\t\t * will try to drop it before split and then check if the folio\n"
}

5/2 2026/08/12 20:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14130
							output: 42
							thoughts: 31
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with Linux kernel memory management:

**Hunting Down the Huge Page Split Logic**

Alright, the problem I'm working on suggests there's a breakdown in how huge pages are handled, specifically when they need to be split. My instinct is to trace the execution path that handles this split operation. The naming convention strongly suggests the function I need is in the `include/linux/huge_mm.h` header file. The specific function I need to look at will be `split_huge_page_to_list_to_order`. This function name, `split_huge_page_to_list_to_order`, almost certainly means it splits a huge page, adds the smaller pages to a freelist, and then likely adjusts the allocation order. I need to understand the details of how it's splitting, what freelist it's using, and how it's managing the allocation order after the split. I need to understand this to determine if the logic here is where the problem is originating.



6/2 2026/08/12 20:14 tool grepper
0m Args:
{
  "Expression": "split_huge_page_to_list_to_order",
  "PathPrefix": "include/linux/huge_mm.h"
}

Results:
{
  "Output": "include/linux/huge_mm.h=398=enum split_type {\n--\ninclude/linux/huge_mm.h-402-\ninclude/linux/huge_mm.h:403:int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-404-\t\tunsigned int new_order);\n--\ninclude/linux/huge_mm.h=410=int folio_split(struct folio *folio, unsigned int new_order, struct page *page,\n--\ninclude/linux/huge_mm.h-412-\ninclude/linux/huge_mm.h:413:static inline int split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-414-\t\tunsigned int new_order)\ninclude/linux/huge_mm.h-415-{\ninclude/linux/huge_mm.h:416:\treturn __split_huge_page_to_list_to_order(page, list, new_order);\ninclude/linux/huge_mm.h-417-}\ninclude/linux/huge_mm.h=418=static inline int split_huge_page_to_order(struct page *page, unsigned int new_order)\ninclude/linux/huge_mm.h-419-{\ninclude/linux/huge_mm.h:420:\treturn split_huge_page_to_list_to_order(page, NULL, new_order);\ninclude/linux/huge_mm.h-421-}\n--\ninclude/linux/huge_mm.h=423=static inline int split_huge_page(struct page *page)\ninclude/linux/huge_mm.h-424-{\ninclude/linux/huge_mm.h:425:\treturn split_huge_page_to_list_to_order(page, NULL, 0);\ninclude/linux/huge_mm.h-426-}\n--\ninclude/linux/huge_mm.h=645=static inline int\ninclude/linux/huge_mm.h:646:split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-647-\t\tunsigned int new_order)\n--\ninclude/linux/huge_mm.h=826=static inline int split_folio_to_order(struct folio *folio, int new_order)\ninclude/linux/huge_mm.h-827-{\ninclude/linux/huge_mm.h:828:\treturn split_huge_page_to_list_to_order(\u0026folio-\u003epage, NULL, new_order);\ninclude/linux/huge_mm.h-829-}\n"
}

7/2 2026/08/12 20:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15135
							output: 35
							thoughts: 80
						

Thoughts:
Okay, here's my interpretation of this technical snippet, put in the first person, as requested:

**Deconstructing This Patch's Focus**

Alright, so this patch is clearly touching some core memory management and swapping logic. My initial assessment is that it's honing in on a few specific functions related to huge page splitting and folio operations. Specifically, I see it's poking around `__split_huge_page_to_list_to_order`, `folio_split_unmapped`, and `folio_check_splittable`. Those all scream "huge page management" to me.

But there's something else... it also mentions `__swap_cache_replace_folio` within `mm/swap_state.c`. That function's involved in swapping, but it's *static* within that file, which makes me curious. I'm going to quickly check that function's definition, because the fact that it's static is potentially significant. It could mean this patch is more targeted than it initially seems. I want to understand *why* it's touching a static function within a swapping context alongside huge page splitting, because there might be something very interesting happening. Is there a connection I'm missing? That function's design could be important here.



8/2 2026/08/12 20:14 tool grepper
0m Args:
{
  "Expression": "__swap_cache_replace_folio",
  "PathPrefix": "mm/swap_state.c"
}

Results:
{
  "Output": "mm/swap_state.c=335=void swap_cache_del_folio(struct folio *folio)\n--\nmm/swap_state.c-347-/**\nmm/swap_state.c:348: * __swap_cache_replace_folio - Replace a folio in the swap cache.\nmm/swap_state.c-349- * @ci: The locked swap cluster.\n--\nmm/swap_state.c-360- */\nmm/swap_state.c:361:void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap_state.c-362-\t\t\t\tstruct folio *old, struct folio *new)\n"
}

9/2 2026/08/12 20:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15701
							output: 35
							thoughts: 42
						

Thoughts:
Here's my summary, as if I were thinking it:

**Digging into Swap Cache Replacement**

Okay, so I'm dealing with `__swap_cache_replace_folio` and I'm pretty sure it's a global function, but I don't recall seeing it exported anywhere. That makes me think it's likely internal to the swap cache implementation, which is fair enough. To confirm this and to understand its use, I should probably take a look at the source code for the swap state management. Specifically, I need to check `mm/swap_state.c` for the definition and any related calls to `__swap_cache_replace_folio`. That's where I'll get the real story.



10/2 2026/08/12 20:14 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "__swap_cache_replace_folio",
  "PathPrefix": "include/linux/swap.h"
}

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

11/2 2026/08/12 20:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15948
							output: 33
							thoughts: 11
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Diving into `mm/swap.h`**

Alright, time to dig into the swap implementation. My initial focus is always on the header files—`mm/swap.h` in this case. I want to quickly refresh my understanding of the data structures and core defines involved. I'm looking for the foundational building blocks: the `swap_entry_t` perhaps, any relevant page flags, and potentially the interfaces for interacting with the swap space itself. Are there any new APIs introduced since the last time I really dissected this code? Any recent changes in how swap entries are managed, or how the backing store is defined? I need a clear mental model of the key abstractions before I delve deeper into the implementation details. Let's see what `mm/swap.h` reveals.



12/2 2026/08/12 20:14 tool grepper
0m Args:
{
  "Expression": "__swap_cache_replace_folio",
  "PathPrefix": "mm/swap.h"
}

Results:
{
  "Output": "mm/swap.h=342=void __swap_cache_del_folio(struct swap_cluster_info *ci,\nmm/swap.h-343-\t\t\t    struct folio *folio, swp_entry_t entry, void *shadow);\nmm/swap.h:344:void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap.h-345-\t\t\t\tstruct folio *old, struct folio *new);\n--\nmm/swap.h=476=static inline void __swap_cache_del_folio(struct swap_cluster_info *ci,\n--\nmm/swap.h-480-\nmm/swap.h:481:static inline void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap.h-482-\t\tstruct folio *old, struct folio *new)\n"
}

13/2 2026/08/12 20:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16400
							output: 28
							thoughts: 136
						

Thoughts:
**Analyzing the Swap Cache Replacement Folio Function**

Okay, so I'm diving into this `__swap_cache_replace_folio` function. I see it's defined in `mm/swap.h`. My immediate focus is on the core logic, so I'm going to zero in on these functions: `__split_huge_page_to_list_to_order`, `folio_split_unmapped`, `folio_check_splittable`, and naturally, `__swap_cache_replace_folio` itself. I need to understand how these pieces fit together.

My next step is to see if any specific kernel configurations are prerequisites. I'm noting that the code for this functionality is in `mm/huge_memory.c`, which, from memory, is compiled when `CONFIG_TRANSPARENT_HUGEPAGE` is enabled. I should double check this by referencing `mm/Makefile` to confirm the build dependencies and ensure I fully understand the conditional compilation. Gotta make sure I'm not missing anything.



14/2 2026/08/12 20:14 tool grepper
0m Args:
{
  "Expression": "huge_memory.o",
  "PathPrefix": "mm/Makefile"
}

Results:
{
  "Output": "mm/Makefile=100=obj-$(CONFIG_DEVICE_MIGRATION) += migrate_device.o\nmm/Makefile:101:obj-$(CONFIG_TRANSPARENT_HUGEPAGE) += huge_memory.o khugepaged.o\nmm/Makefile-102-obj-$(CONFIG_PAGE_COUNTER) += page_counter.o\n"
}

15/2 2026/08/12 20:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16882
							output: 28
							thoughts: 42
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own:

**Analyzing Kernel Configuration Dependencies**

Alright, so the discussion has clearly established that `TRANSPARENT_HUGEPAGE` is non-negotiable here. That's a given. But now, I need to delve a bit deeper and look at the dependencies more comprehensively. My next thought is around `mm/swap_state.c`. I know it's compiled when `CONFIG_SWAP` is enabled, but I need to understand *why* and what its potential relevance is in this specific scenario. It's time to dig into the relationship between transparent hugepages and swap, and ensure nothing gets missed. This feels like a core piece I need to get a firm grasp on, to be sure I am not missing any critical pieces in the design.



16/2 2026/08/12 20:14 tool grepper
0m Args:
{
  "Expression": "swap_state.o",
  "PathPrefix": "mm/Makefile"
}

Results:
{
  "Output": "mm/Makefile=76=endif\nmm/Makefile-77-\nmm/Makefile:78:obj-$(CONFIG_SWAP)\t+= page_io.o swap_state.o swapfile.o\nmm/Makefile-79-obj-$(CONFIG_ZSWAP)\t+= zswap.o\n"
}

17/2 2026/08/12 20:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17211
							output: 26
							thoughts: 91
						

Thoughts:
Okay, here's my understanding, thinking through this like I would:

**Examining Configuration Dependencies for Folio-Related Code**

Alright, so I see we're dealing with `SWAP` again, just like I suspected. Gotta make sure that's accounted for. I'm going to quickly scan for other potential configuration dependencies that this patch might touch. The code references `folio_test_swapcache(folio)` and `folio_test_anon(folio)`. That narrows it down pretty clearly, it looks like `CONFIG_TRANSPARENT_HUGEPAGE` and `CONFIG_SWAP` are the primary configurations in play here. I'm confident that these are the two main drivers of the changes. Now, let's just do a quick cross-reference to be certain: I'm going to look for places where `folio_split_unmapped` is called. That should either confirm the dependency or maybe show us if there is anything else we need to include. I just want to be thorough.



18/2 2026/08/12 20:15 tool grepper
0m Args:
{
  "Expression": "folio_split_unmapped",
  "PathPrefix": ""
}

Results:
{
  "Output": "include/linux/huge_mm.h=403=int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-404-\t\tunsigned int new_order);\ninclude/linux/huge_mm.h:405:int folio_split_unmapped(struct folio *folio, unsigned int new_order);\ninclude/linux/huge_mm.h-406-unsigned int min_order_for_split(struct folio *folio);\n--\nmm/huge_memory.c=4287=static int __folio_split(struct folio *folio, unsigned int new_order,\n--\nmm/huge_memory.c-4355-/**\nmm/huge_memory.c:4356: * folio_split_unmapped() - split a large anon folio that is already unmapped\nmm/huge_memory.c-4357- * @folio: folio to split\n--\nmm/huge_memory.c-4376- */\nmm/huge_memory.c:4377:int folio_split_unmapped(struct folio *folio, unsigned int new_order)\nmm/huge_memory.c-4378-{\n--\nmm/migrate_device.c=912=static int migrate_vma_split_unmapped_folio(struct migrate_vma *migrate,\n--\nmm/migrate_device.c-926-\tsplit_huge_pmd_address(migrate-\u003evma, addr, true);\nmm/migrate_device.c:927:\tret = folio_split_unmapped(folio, 0);\nmm/migrate_device.c-928-\tif (ret)\n"
}

19/2 2026/08/12 20:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17972
							output: 27
							thoughts: 86
						

Thoughts:
Here's my understanding of the code snippet, distilled into a first-person perspective, as it relates to my expertise:

**My Understanding of Folio Splitting Functions**

Okay, so I see a clear relationship emerging between these folio splitting functions. It looks like `folio_split_unmapped` is a specific implementation of a folio split, invoked within the broader context of device migration (`migrate_vma_split_unmapped_folio`) in the `mm/migrate_device.c` file. This tells me this split is for unmapped folios involved in device migration, likely ensuring data integrity and correct handling of page ownership during the process, given the migration context.

Then, there's `__split_huge_page_to_list_to_order`. This one is interesting because it's called from a wide array of places. That makes me think it's the core, low-level function for actually doing the splitting, potentially handling the physical page manipulations and list management involved in transforming a huge page into a set of smaller, ordered folios. The "to_list_to_order" part definitely hints at keeping track of the smaller folios in the correct order for later reassembly or other operations, so it's a critical piece of the puzzle.

Finally, `folio_check_splittable` appears to be a prerequisite check. It's called before any actual splitting is done, from both the general splitting function `__folio_split` and from the process-specific huge page splitting function `split_huge_pages_pid`. That points to this check being about determining if a folio *can* be split. This could be checking things like whether it's locked, whether it's a valid candidate for splitting given its current state, and potentially other constraints. The fact that it's called from those two functions means it is universally applicable and likely enforces necessary invariants before attempting to break a huge page down.



20/2 2026/08/12 20:15 tool grepper
0m Args:
{
  "Expression": "folio_check_splittable",
  "PathPrefix": ""
}

Results:
{
  "Output": "include/linux/huge_mm.h=407=int split_folio_to_list(struct folio *folio, struct list_head *list);\ninclude/linux/huge_mm.h:408:int folio_check_splittable(struct folio *folio, unsigned int new_order,\ninclude/linux/huge_mm.h-409-\t\t\t   enum split_type split_type);\n--\nmm/huge_memory.c=3807=static int __split_frozen_folio(struct folio *folio, int new_order,\n--\nmm/huge_memory.c-3872-/**\nmm/huge_memory.c:3873: * folio_check_splittable() - check if a folio can be split to a given order\nmm/huge_memory.c-3874- * @folio: folio to be split\n--\nmm/huge_memory.c-3879- *\nmm/huge_memory.c:3880: * folio_check_splittable() checks if @folio can be split to @new_order using\nmm/huge_memory.c-3881- * @split_type method. The truncated folio check must come first.\n--\nmm/huge_memory.c-3887- */\nmm/huge_memory.c:3888:int folio_check_splittable(struct folio *folio, unsigned int new_order,\nmm/huge_memory.c-3889-\t\t\t   enum split_type split_type)\n--\nmm/huge_memory.c=4287=static int __folio_split(struct folio *folio, unsigned int new_order,\n--\nmm/huge_memory.c-4310-\nmm/huge_memory.c:4311:\tret = folio_check_splittable(folio, new_order, split_type);\nmm/huge_memory.c-4312-\tif (ret) {\n"
}

21/2 2026/08/12 20:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18972
							output: 28
							thoughts: 117
						

Thoughts:
Okay, here's my take on those code interactions.

**Thinking Through Folio Splitting**

Right, so `folio_check_splittable` gets invoked by `__folio_split`, which makes perfect sense. Now I need to zero in on the critical functions related to this splitting process. I'm focusing on these key symbols: `__split_huge_page_to_list_to_order`, `folio_split_unmapped`, `folio_check_splittable`, and `__swap_cache_replace_folio`. Those are the usual suspects.

Hang on a second… I'm just recalling that `__swap_cache_replace_folio`'s call graph might be more limited than I initially thought. It looks like it's primarily called from `__folio_split_unmap_and_freeze`. I should probably double-check if there are any other call sites, just to be sure. It pays to be thorough when you are dealing with memory management.



22/2 2026/08/12 20:15 tool grepper
0m Args:
{
  "Expression": "__swap_cache_replace_folio",
  "PathPrefix": ""
}

Results:
{
  "Output": "mm/huge_memory.c=3957=static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_order,\n--\nmm/huge_memory.c-4061-\t\tif (ci)\nmm/huge_memory.c:4062:\t\t\t__swap_cache_replace_folio(ci, folio, new_folio);\nmm/huge_memory.c-4063-\t}\n--\nmm/migrate.c=583=static int __folio_migrate_mapping(struct address_space *mapping,\n--\nmm/migrate.c-656-\tif (folio_test_swapcache(folio))\nmm/migrate.c:657:\t\t__swap_cache_replace_folio(ci, folio, newfolio);\nmm/migrate.c-658-\telse\n--\nmm/shmem.c=2088=static int shmem_replace_folio(struct folio **foliop, gfp_t gfp,\n--\nmm/shmem.c-2125-\tci = swap_cluster_get_and_lock_irq(old);\nmm/shmem.c:2126:\t__swap_cache_replace_folio(ci, old, new);\nmm/shmem.c-2127-\tmem_cgroup_replace_folio(old, new);\n--\nmm/swap.h=342=void __swap_cache_del_folio(struct swap_cluster_info *ci,\nmm/swap.h-343-\t\t\t    struct folio *folio, swp_entry_t entry, void *shadow);\nmm/swap.h:344:void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap.h-345-\t\t\t\tstruct folio *old, struct folio *new);\n--\nmm/swap.h=476=static inline void __swap_cache_del_folio(struct swap_cluster_info *ci,\n--\nmm/swap.h-480-\nmm/swap.h:481:static inline void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap.h-482-\t\tstruct folio *old, struct folio *new)\n--\nmm/swap_state.c=335=void swap_cache_del_folio(struct folio *folio)\n--\nmm/swap_state.c-347-/**\nmm/swap_state.c:348: * __swap_cache_replace_folio - Replace a folio in the swap cache.\nmm/swap_state.c-349- * @ci: The locked swap cluster.\n--\nmm/swap_state.c-360- */\nmm/swap_state.c:361:void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap_state.c-362-\t\t\t\tstruct folio *old, struct folio *new)\n"
}

23/2 2026/08/12 20:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20056
							output: 32
							thoughts: 264
						

Thoughts:
**My Analysis of Folio Splitting and Swap Operations**

Okay, so I'm looking at `__swap_cache_replace_folio` – that's definitely a global symbol, exported from `mm/swap_state.c`. Good to know. Now, let's see what else we need to consider in terms of configurations.

Hmm, `CONFIG_DEVICE_MIGRATION` is tied to `migrate_vma_split_unmapped_folio`, which in turn calls `folio_split_unmapped`. But `folio_split_unmapped` resides within `mm/huge_memory.c`, and that's compiled with `CONFIG_TRANSPARENT_HUGEPAGE`. So, it looks like `TRANSPARENT_HUGEPAGE` and `SWAP` are the big ones for this functionality. Makes sense.

Alright, let's quickly review the internals. `__folio_split_unmap_and_freeze` and `__folio_split_unmap_and_freeze_file` are static functions – not very helpful as direct entry points for analysis. However, `__split_huge_page_to_list_to_order` seems to be the core entry point for splitting. Then, we have `folio_split_unmapped` as another potential entry point.

I like `folio_check_splittable` as a focus symbol; that seems like a good place to dig in. And, as a reminder, `__swap_cache_replace_folio` is also a solid option, given its global nature.

Just going to double-check those configs again...always a good idea to confirm.



24/2 2026/08/12 20:15 tool grepper
0m Args:
{
  "Expression": "config TRANSPARENT_HUGEPAGE",
  "PathPrefix": "mm/Kconfig"
}

Results:
{
  "Output": "mm/Kconfig=831=config MM_ID\n--\nmm/Kconfig-833-\nmm/Kconfig:834:menuconfig TRANSPARENT_HUGEPAGE\nmm/Kconfig-835-\tbool \"Transparent Hugepage Support\"\n--\nmm/Kconfig=852=choice\n--\nmm/Kconfig-857-\nmm/Kconfig:858:\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\nmm/Kconfig-859-\t\tbool \"always\"\n--\nmm/Kconfig-864-\nmm/Kconfig:865:\tconfig TRANSPARENT_HUGEPAGE_MADVISE\nmm/Kconfig-866-\t\tbool \"madvise\"\n--\nmm/Kconfig-873-\nmm/Kconfig:874:\tconfig TRANSPARENT_HUGEPAGE_NEVER\nmm/Kconfig-875-\t\tbool \"never\"\n--\nmm/Kconfig=881=choice\n--\nmm/Kconfig-890-\nmm/Kconfig:891:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_NEVER\nmm/Kconfig-892-\t\tbool \"never\"\n--\nmm/Kconfig-900-\nmm/Kconfig:901:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ALWAYS\nmm/Kconfig-902-\t\tbool \"always\"\n--\nmm/Kconfig-908-\nmm/Kconfig:909:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_WITHIN_SIZE\nmm/Kconfig-910-\t\tbool \"within_size\"\n--\nmm/Kconfig-916-\nmm/Kconfig:917:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ADVISE\nmm/Kconfig-918-\t\tbool \"advise\"\n--\nmm/Kconfig=926=choice\n--\nmm/Kconfig-935-\nmm/Kconfig:936:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_NEVER\nmm/Kconfig-937-\t\tbool \"never\"\n--\nmm/Kconfig-944-\nmm/Kconfig:945:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ALWAYS\nmm/Kconfig-946-\t\tbool \"always\"\n--\nmm/Kconfig-952-\nmm/Kconfig:953:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_WITHIN_SIZE\nmm/Kconfig-954-\t\tbool \"within_size\"\n--\nmm/Kconfig-960-\nmm/Kconfig:961:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ADVISE\nmm/Kconfig-962-\t\tbool \"advise\"\n"
}

25/2 2026/08/12 20:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21419
							output: 27
							thoughts: 25
						

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

**Quick Configuration Check**

Alright, let's see, going over the system setup. I see `TRANSPARENT_HUGEPAGE` is definitely flagged in the config. Okay, that's expected, but always good to confirm. Then, there's `SWAP`, which is another key parameter. Need to consider how that plays into everything. I'll need to keep that in mind when examining the other settings.



26/2 2026/08/12 20:15 tool grepper
0m Args:
{
  "Expression": "config SWAP",
  "PathPrefix": "mm/Kconfig"
}

Results:
{
  "Output": "mm/Kconfig=9=config ARCH_NO_SWAP\n--\nmm/Kconfig-11-\nmm/Kconfig:12:menuconfig SWAP\nmm/Kconfig-13-\tbool \"Support for paging of anonymous memory (swap)\"\n"
}

27/2 2026/08/12 20:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21655
							output: 163
							thoughts: 241
						

Thoughts:
**My Assessment of This Patch**

Okay, so I see that `TRANSPARENT_HUGEPAGE` and `SWAP` are definitely relevant here; those are the key configuration flags we need to keep in mind. Let's break down the critical symbols that jump out to me: `__split_huge_page_to_list_to_order`, `folio_split_unmapped`, `folio_check_splittable`, and `__swap_cache_replace_folio`. Those are telling.

The changes are, without a doubt, functional and drilling down into the heart of memory management. That's a good starting point. The fact that userspace can trigger these changes through `madvise`, `mbind`, or even just plain old memory allocation and swapping seals the deal. We're talking about a substantial attack surface.

This patch refactors the splitting of folios, especially anonymous and swapcache folios. I'm seeing the introduction of the new functions `__folio_split_unmap_and_freeze` and `__folio_split_unmap_and_freeze_file`, which tells me it's handling the splitting process directly, and changes to the conditions in `folio_check_splittable`. The fact that these are touching the core memory management with THP and swap makes this critical. Given the userspace reach, fuzzing this is absolutely justified. It's a clear candidate for `WorthFuzzing=true`.

Time to call `set-results`.



28/2 2026/08/12 20:15 tool set-results
0m Args:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "SWAP"
  ],
  "FocusSymbols": [
    "__split_huge_page_to_list_to_order",
    "folio_split_unmapped",
    "folio_check_splittable",
    "__swap_cache_replace_folio"
  ],
  "Reasoning": "The patch refactors and modifies the logic for splitting folios, particularly for anonymous and swapcache folios. It introduces new functions to handle splitting and modifies conditions in folio_check_splittable. These are core memory management changes affecting Transparent Huge Pages (THP) and swap, which are reachable from userspace and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "SWAP"
  ],
  "FocusSymbols": [
    "__split_huge_page_to_list_to_order",
    "folio_split_unmapped",
    "folio_check_splittable",
    "__swap_cache_replace_folio"
  ],
  "Reasoning": "The patch refactors and modifies the logic for splitting folios, particularly for anonymous and swapcache folios. It introduces new functions to handle splitting and modifies conditions in folio_check_splittable. These are core memory management changes affecting Transparent Huge Pages (THP) and swap, which are reachable from userspace and should be fuzzed.",
  "WorthFuzzing": true
}

29/1 2026/08/12 20:15 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

30/2 2026/08/12 20:15 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch refactors the folio splitting logic in the memory management subsystem (specifically `mm/huge_memory.c` and `mm/swap_state.c`), introducing separate paths for anonymous/swapcache folios and file-backed folios. The changes involve page reference counting, locking, LRU list management, and page cache/swap cache updates.\n\nThese modifications do not introduce new data structures sent to user space, nor do they allocate uninitialized memory that is subsequently read or used in control flow. Any bugs introduced by this patch would likely manifest as use-after-free, double free, out-of-bounds accesses, or locking issues (deadlocks/race conditions). Such bugs are effectively detected by KASAN, LOCKDEP, and standard kernel bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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 5ae70f7a267937b5eabcd196fff52e2479d0cd2e
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Aug 12 20:14:22 2026 +0000

    syz-cluster: applied patch under review

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ced400f72d43a..1c61b7d39cd03 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3589,9 +3589,6 @@ static void remap_page(struct folio *folio, unsigned long nr, int flags)
 {
 	int i = 0;
 
-	/* If unmap_folio() uses try_to_migrate() on file, remove this check */
-	if (!folio_test_anon(folio))
-		return;
 	for (;;) {
 		remove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);
 		i += folio_nr_pages(folio);
@@ -3634,6 +3631,18 @@ static bool page_range_has_hwpoisoned(struct page *page, long nr_pages)
 	return false;
 }
 
+/**
+ * for_each_folio_safe - iterate over contiguous folios safe against folio free
+ * @start: the first folio to iterate
+ * @end: sentinel, folio_next() of the last folio to iterate
+ * @sub_folio: struct folio * to use as the loop cursor
+ * @next: struct folio * used as temporary storage
+ */
+#define for_each_folio_safe(start, end, sub_folio, next) \
+	for (sub_folio = (start), next = folio_next(sub_folio); \
+	     sub_folio != (end); \
+	     sub_folio = next, next = folio_next(next))
+
 /*
  * It splits @folio into @new_order folios and copies the @folio metadata to
  * all the resulting folios.
@@ -3755,8 +3764,8 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
 }
 
 /**
- * __split_unmapped_folio() - splits an unmapped @folio to lower order folios in
- * two ways: uniform split or non-uniform split.
+ * __split_frozen_folio() - splits a frozen @folio to lower order folios
+ * in two ways: uniform split or non-uniform split.
  * @folio: the to-be-split folio
  * @new_order: the smallest order of the after split folios (since buddy
  *             allocator like split generates folios with orders from @folio's
@@ -3795,11 +3804,12 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
  * Return: 0 - successful, <0 - failed (if -ENOMEM is returned, @folio might be
  * split but not to @new_order, the caller needs to check)
  */
-static int __split_unmapped_folio(struct folio *folio, int new_order,
+static int __split_frozen_folio(struct folio *folio, int new_order,
 		struct page *split_at, struct xa_state *xas,
 		struct address_space *mapping, enum split_type split_type)
 {
 	const bool is_anon = folio_test_anon(folio);
+	const bool is_swapcache = folio_test_swapcache(folio);
 	int old_order = folio_order(folio);
 	int start_order = split_type == SPLIT_TYPE_UNIFORM ? new_order : old_order - 1;
 	struct folio *old_folio = folio;
@@ -3814,8 +3824,8 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 	     split_order--) {
 		int nr_new_folios = 1UL << (old_order - split_order);
 
-		/* order-1 anonymous folio is not supported */
-		if (is_anon && split_order == 1)
+		/* order-1 anonymous or swapcache folio is not supported */
+		if ((is_anon || is_swapcache) && split_order == 1)
 			continue;
 
 		if (mapping) {
@@ -3878,32 +3888,26 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 int folio_check_splittable(struct folio *folio, unsigned int new_order,
 			   enum split_type split_type)
 {
+	bool is_anon = folio_test_anon(folio);
+	bool is_swapcache = folio_test_swapcache(folio);
+
 	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
 	/*
 	 * Folios that just got truncated cannot get split. Signal to the
-	 * caller that there was a race.
-	 *
-	 * TODO: this will also currently refuse folios without a mapping in the
-	 * swapcache (shmem or to-be-anon folios).
+	 * caller that there was a race. A mappingless swap cache folio
+	 * has no page cache entries to update, so it is fine to split.
 	 */
-	if (!folio->mapping && !folio_test_anon(folio))
+	if (!folio->mapping && !is_swapcache)
 		return -EBUSY;
 
-	/* order-1 is not supported for anonymous THP. */
-	if (folio_test_anon(folio) && new_order == 1)
-		return -EINVAL;
-
 	/*
-	 * swapcache folio could only be split to order 0
-	 *
-	 * non-uniform split creates after-split folios with orders from
-	 * folio_order(folio) - 1 to new_order, making it not suitable for any
-	 * swapcache folio split. Only uniform split to order-0 can be used
-	 * here.
+	 * Order-1 is unsupported: anon folios need subpage 2 for the
+	 * deferred split list, hybrid shmem & swap cache folios are not
+	 * splittable, and a splittable mappingless swap cache folio could
+	 * be either anon or shmem, which we cannot tell apart.
 	 */
-	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && folio_test_swapcache(folio)) {
+	if ((is_anon || is_swapcache) && new_order == 1)
 		return -EINVAL;
-	}
 
 	if (is_huge_zero_folio(folio))
 		return -EINVAL;
@@ -3911,39 +3915,97 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	if (folio_test_writeback(folio))
 		return -EBUSY;
 
+	/*
+	 * A non-anon swapcache folio that still has a mapping can only be a
+	 * shmem folio under SWAP IO, it's removed from either swap cache or
+	 * shmem mapping afterward. There is little benefit in splitting them
+	 * hence reject it here up front before touching anything.
+	 */
+	if (!is_anon && is_swapcache && folio->mapping)
+		return -EBUSY;
+
 	return 0;
 }
 
-/* Number of folio references from the pagecache or the swapcache. */
-static unsigned int folio_cache_ref_count(const struct folio *folio)
+/* Number of folio references from the swapcache. */
+static unsigned int folio_swapcache_ref_count(const struct folio *folio)
 {
-	if (folio_test_anon(folio) && !folio_test_swapcache(folio))
+	if (!folio_test_swapcache(folio))
 		return 0;
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order,
-					     struct page *split_at, struct xa_state *xas,
-					     struct address_space *mapping, bool do_lru,
-					     struct list_head *list, enum split_type split_type,
-					     pgoff_t end, int *nr_shmem_dropped)
+/**
+ * __folio_split_unmap_and_freeze() - split an anon or swap cache folio
+ * @folio: folio to split, must be locked
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @do_lru: if true, add after-split folios to @list if non NULL, otherwise to
+ *          the LRU list
+ * @anon_unmap: if true, unmap @folio before the split and remap it after
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting an anon or swap cache folio. It unmaps @folio (unless
+ * @anon_unmap is false), freezes its refcount, and performs the split, updates
+ * the swap cache entries. Split folios are unfrozen and remapped.
+ *
+ * Return: 0 on success, otherwise an error number is returned.
+ */
+static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_order,
+					  struct page *split_at, bool do_lru, bool anon_unmap,
+					  struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
+	bool is_anon = folio_test_anon(folio);
+	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio, *next;
 	int old_order = folio_order(folio);
+	struct anon_vma *anon_vma = NULL;
+	enum ttu_flags ttu_flags = 0;
 	struct list_lru_one *lru;
+	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
-	VM_WARN_ON_ONCE(!mapping && end);
+	/*
+	 * Unmap/remap needs the anon_vma. The caller does not necessarily
+	 * hold an mmap_lock that would prevent the anon_vma from
+	 * disappearing, so we first take a reference and lock it. This is
+	 * similar to folio_lock_anon_vma_read() except the write lock is
+	 * taken to serialize against parallel split or collapse.
+	 */
+	if (anon_unmap) {
+		anon_vma = folio_get_anon_vma(folio);
+		if (!anon_vma)
+			return -EBUSY;
+		anon_vma_lock_write(anon_vma);
+	}
+
+	/* Racy check if we can split the page, before the optional unmap. */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	if (anon_unmap)
+		unmap_folio(folio);
+
+	local_irq_disable();
+
 	/*
 	 * If this folio can be on the deferred split queue, lock out
 	 * the shrinker before freezing the ref. If the shrinker sees
 	 * a 0-ref folio, it assumes it beat folio_put() to the list
 	 * lock and must clean up the LRU state - the same dequeue we
 	 * will do below as part of the split.
+	 *
+	 * Only anon folios are ever queued on the deferred split list,
+	 * so non-anon folios (mappingless swapcache) never need dequeuing.
 	 */
-	dequeue_deferred = folio_test_anon(folio) && old_order > 1;
+	dequeue_deferred = old_order > 1 && is_anon;
 	if (dequeue_deferred) {
 		struct mem_cgroup *memcg;
 
@@ -3952,128 +4014,252 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		lru = list_lru_lock(&deferred_split_lru,
 				    folio_nid(folio), &memcg);
 	}
-	if (folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
-		struct swap_cluster_info *ci = NULL;
-		struct lruvec *lruvec;
 
+	if (!folio_ref_freeze(folio, folio_swapcache_ref_count(folio) + 1)) {
 		if (dequeue_deferred) {
-			__list_lru_del(&deferred_split_lru, lru,
-				       &folio->_deferred_list, folio_nid(folio));
-			if (folio_test_partially_mapped(folio)) {
-				folio_clear_partially_mapped(folio);
-				mod_mthp_stat(old_order,
-					MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
-			}
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
+		ret = -EAGAIN;
+		goto out_no_split;
+	}
 
-		if (mapping) {
-			int nr = folio_nr_pages(folio);
-
-			if (folio_test_pmd_mappable(folio) &&
-			    new_order < HPAGE_PMD_ORDER) {
-				if (folio_test_swapbacked(folio)) {
-					lruvec_stat_mod_folio(folio,
-							NR_SHMEM_THPS, -nr);
-				} else {
-					lruvec_stat_mod_folio(folio,
-							NR_FILE_THPS, -nr);
-				}
-			}
+	if (dequeue_deferred) {
+		__list_lru_del(&deferred_split_lru, lru,
+			       &folio->_deferred_list, folio_nid(folio));
+		if (folio_test_partially_mapped(folio)) {
+			folio_clear_partially_mapped(folio);
+			mod_mthp_stat(old_order,
+				      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
 		}
+		list_lru_unlock(lru);
+		rcu_read_unlock();
+	}
 
-		if (folio_test_swapcache(folio)) {
-			if (mapping) {
-				VM_WARN_ON_ONCE_FOLIO(mapping, folio);
-				return -EINVAL;
-			}
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
 
-			ci = swap_cluster_get_and_lock(folio);
-		}
+	if (do_lru)
+		lruvec = folio_lruvec_lock(folio);
+
+	ret = __split_frozen_folio(folio, new_order, split_at, NULL,
+				   NULL, split_type);
 
-		/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	/*
+	 * Unfreeze the post-split folios and put them back to the right
+	 * place. Keep the head @folio frozen until the end: sub entries
+	 * in swap cache must be updated first, so a concurrent
+	 * swap_cache_get_folio() cannot return the head folio for a sub
+	 * entry (folio_try_get() will fail on the head @folio until unfreeze).
+	 */
+	for_each_folio_safe(folio_next(folio), end_folio, new_folio, next) {
+		zone_device_private_split_cb(folio, new_folio);
+		folio_ref_unfreeze(new_folio,
+				   folio_swapcache_ref_count(new_folio) + 1);
 		if (do_lru)
-			lruvec = folio_lruvec_lock(folio);
+			lru_add_split_folio(folio, new_folio, lruvec, list);
+		if (ci)
+			__swap_cache_replace_folio(ci, folio, new_folio);
+	}
 
-		ret = __split_unmapped_folio(folio, new_order, split_at, xas,
-					     mapping, split_type);
+	zone_device_private_split_cb(folio, NULL);
+	folio_ref_unfreeze(folio, folio_swapcache_ref_count(folio) + 1);
 
-		/*
-		 * Unfreeze after-split folios and put them back to the right
-		 * list. @folio should be kept frozon until page cache
-		 * entries are updated with all the other after-split folios
-		 * to prevent others seeing stale page cache entries.
-		 * As a result, new_folio starts from the next folio of
-		 * @folio.
-		 */
-		for (new_folio = folio_next(folio); new_folio != end_folio;
-		     new_folio = next) {
-			unsigned long nr_pages = folio_nr_pages(new_folio);
+	if (do_lru)
+		lruvec_unlock(lruvec);
+	if (ci)
+		swap_cluster_unlock(ci);
+out_no_split:
+	local_irq_enable();
+	if (anon_unmap) {
+		if (!ret && !folio_is_device_private(folio))
+			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
+		remap_page(folio, 1 << old_order, ttu_flags);
+	}
+out_unlock:
+	if (anon_unmap) {
+		anon_vma_unlock_write(anon_vma);
+		put_anon_vma(anon_vma);
+	}
 
-			next = folio_next(new_folio);
+	return ret;
+}
 
-			zone_device_private_split_cb(folio, new_folio);
+/**
+ * __folio_split_unmap_and_freeze_file() - split a file-backed folio
+ * @folio: folio to split, must be locked and file-backed
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting a file-backed folio. It unmaps @folio, freezes its
+ * refcount, and perform the split, updates the page cache entries. Split
+ * folios are unfrozen but not remapped, they are faulted back in on demand.
+ *
+ * Return: 0 on success, otherwise an error number is returned. (if -ENOMEM
+ * is returned, @folio might be split but not to @new_order)
+ */
+static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int new_order,
+					       struct page *split_at, struct list_head *list,
+					       enum split_type split_type)
+{
+	struct address_space *mapping = folio->mapping;
+	XA_STATE(xas, &mapping->i_pages, folio->index);
+	struct folio *end_folio = folio_next(folio);
+	long old_nr_pages = folio_nr_pages(folio);
+	struct mem_cgroup *memcg, *old_memcg;
+	struct folio *new_folio, *next;
+	int nr_shmem_dropped = 0;
+	unsigned int min_order;
+	struct lruvec *lruvec;
+	pgoff_t end = 0;
+	gfp_t gfp;
+	int ret = 0;
 
-			folio_ref_unfreeze(new_folio,
-					   folio_cache_ref_count(new_folio) + 1);
+	min_order = mapping_min_folio_order(mapping);
+	if (new_order < min_order)
+		return -EINVAL;
 
-			if (do_lru)
-				lru_add_split_folio(folio, new_folio, lruvec, list);
+	/*
+	 * Switch to folio's memcg as xarray node allocation can happen and
+	 * needs to charge to it.
+	 */
+	memcg = get_mem_cgroup_from_folio(folio);
+	old_memcg = set_active_memcg(memcg);
 
-			/*
-			 * Anonymous folio with swap cache.
-			 * NOTE: shmem in swap cache is not supported yet.
-			 */
-			if (ci) {
-				__swap_cache_replace_folio(ci, folio, new_folio);
-				continue;
-			}
+	gfp = current_gfp_context(mapping_gfp_mask(mapping) & GFP_RECLAIM_MASK);
+	if (!filemap_release_folio(folio, gfp)) {
+		ret = -EBUSY;
+		goto fail_free;
+	}
 
-			/* Anonymous folio without swap cache */
-			if (!mapping)
-				continue;
+	mapping_set_update(&xas, mapping);
 
-			/* Add the new folio to the page cache. */
-			if (new_folio->index < end) {
-				__xa_store(&mapping->i_pages, new_folio->index,
-					   new_folio, 0);
-				continue;
-			}
+	if (split_type == SPLIT_TYPE_UNIFORM) {
+		int old_order = folio_order(folio);
 
-			VM_WARN_ON_ONCE(!nr_shmem_dropped);
-			/* Drop folio beyond EOF: ->index >= end */
-			if (shmem_mapping(mapping) && nr_shmem_dropped)
-				*nr_shmem_dropped += nr_pages;
-			else if (folio_test_clear_dirty(new_folio))
-				folio_account_cleaned(
-					new_folio, inode_to_wb(mapping->host));
-			__filemap_remove_folio(new_folio, NULL);
-			folio_put_refs(new_folio, nr_pages);
+		xas_set_order(&xas, folio->index, new_order);
+		xas_split_alloc(&xas, folio, old_order, gfp);
+		if (xas_error(&xas)) {
+			ret = xas_error(&xas);
+			goto fail_free;
 		}
+	}
 
-		zone_device_private_split_cb(folio, NULL);
-		/*
-		 * Unfreeze @folio only after all page cache entries, which
-		 * used to point to it, have been updated with new folios.
-		 * Otherwise, a parallel folio_try_get() can grab @folio
-		 * and its caller can see stale page cache entries.
-		 */
-		folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+	i_mmap_lock_read(mapping);
 
-		if (do_lru)
-			lruvec_unlock(lruvec);
+	/* Racy check if we can split the page, before unmap_folio() */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto fail_mmap_unlock;
+	}
 
-		if (ci)
-			swap_cluster_unlock(ci);
-	} else {
-		if (dequeue_deferred) {
-			list_lru_unlock(lru);
-			rcu_read_unlock();
+	/*
+	 * __split_frozen_folio() may need to trim off pages beyond
+	 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
+	 * seqlock, which cannot be nested inside the page tree lock.
+	 * So note end now: i_size itself may be changed at any moment,
+	 * but folio lock is good enough to serialize the trimming.
+	 */
+	end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
+	if (shmem_mapping(mapping))
+		end = shmem_fallocend(mapping->host, end);
+
+	unmap_folio(folio);
+
+	xas_lock_irq(&xas);
+
+	/*
+	 * Check if the folio is present in page cache.
+	 * We assume all tail are present too, if folio is there.
+	 */
+	if (xas_load(&xas) != folio) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (!folio_ref_freeze(folio, old_nr_pages + 1)) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (folio_test_pmd_mappable(folio) && new_order < HPAGE_PMD_ORDER) {
+		if (folio_test_swapbacked(folio))
+			lruvec_stat_mod_folio(folio, NR_SHMEM_THPS, -old_nr_pages);
+		else
+			lruvec_stat_mod_folio(folio, NR_FILE_THPS, -old_nr_pages);
+	}
+
+	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	lruvec = folio_lruvec_lock(folio);
+	ret = __split_frozen_folio(folio, new_order, split_at, &xas,
+				   mapping, split_type);
+
+	/*
+	 * Unfreeze after-split folios and put them back to the right
+	 * list. @folio should be kept frozen until page cache
+	 * entries are updated with all the other after-split folios
+	 * to prevent others seeing stale page cache entries.
+	 * As a result, new_folio starts from the next folio of
+	 * @folio.
+	 */
+	for_each_folio_safe(folio_next(folio), end_folio, new_folio, next) {
+		unsigned long nr_pages = folio_nr_pages(new_folio);
+
+		folio_ref_unfreeze(new_folio,
+				   folio_nr_pages(new_folio) + 1);
+
+		lru_add_split_folio(folio, new_folio, lruvec, list);
+
+		/* Add the new folio to the page cache. */
+		if (new_folio->index < end) {
+			__xa_store(&mapping->i_pages, new_folio->index,
+				   new_folio, 0);
+			continue;
 		}
-		return -EAGAIN;
+
+		/* Drop folio beyond EOF: ->index >= end */
+		if (shmem_mapping(mapping))
+			nr_shmem_dropped += nr_pages;
+		else if (folio_test_clear_dirty(new_folio))
+			folio_account_cleaned(new_folio,
+					      inode_to_wb(mapping->host));
+		__filemap_remove_folio(new_folio, NULL);
+		folio_put_refs(new_folio, nr_pages);
 	}
 
+	/*
+	 * Unfreeze @folio only after all page cache entries, which
+	 * used to point to it, have been updated with new folios.
+	 * Otherwise, a parallel folio_try_get() can grab @folio
+	 * and its caller can see stale page cache entries.
+	 */
+	folio_ref_unfreeze(folio, folio_nr_pages(folio) + 1);
+	lruvec_unlock(lruvec);
+fail:
+	/*
+	 * If we want to use try_to_migrate() on file in unmap_folio,
+	 * remember to add remap_page() and adapt it.
+	 */
+	xas_unlock_irq(&xas);
+fail_mmap_unlock:
+	if (nr_shmem_dropped)
+		shmem_uncharge(mapping->host, nr_shmem_dropped);
+	/*
+	 * Drop the mapping while the inode is still pinned. @folio stays
+	 * locked and present in the page cache, so eviction cannot free
+	 * the inode yet, nothing past this point may touch the inode or
+	 * the mapping.
+	 */
+	i_mmap_unlock_read(mapping);
+fail_free:
+	/* Restore the previously active memcg */
+	set_active_memcg(old_memcg);
+	mem_cgroup_put(memcg);
+	xas_destroy(&xas);
 	return ret;
 }
 
@@ -4086,9 +4272,9 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
  * @list: after-split folios will be put on it if non NULL
  * @split_type: perform uniform split or not (non-uniform split)
  *
- * It calls __split_unmapped_folio() to perform uniform and non-uniform split.
+ * It calls __split_frozen_folio() to perform uniform and non-uniform split.
  * It is in charge of checking whether the split is supported or not and
- * preparing @folio for __split_unmapped_folio().
+ * preparing @folio for __split_frozen_folio().
  *
  * After splitting, the after-split folio containing @lock_at remains locked
  * and others are unlocked:
@@ -4102,17 +4288,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		struct page *split_at, struct page *lock_at,
 		struct list_head *list, enum split_type split_type)
 {
-	XA_STATE(xas, &folio->mapping->i_pages, folio->index);
+	bool is_swapcache = folio_test_swapcache(folio);
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
-	struct mem_cgroup *memcg, *old_memcg;
-	struct address_space *mapping = NULL;
-	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
-	int nr_shmem_dropped = 0;
-	enum ttu_flags ttu_flags = 0;
-	pgoff_t end = 0;
 	int ret;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
@@ -4120,141 +4300,29 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	if (folio != page_folio(split_at) || folio != page_folio(lock_at)) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	if (new_order >= old_order) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	ret = folio_check_splittable(folio, new_order, split_type);
 	if (ret) {
 		VM_WARN_ONCE(ret == -EINVAL, "Tried to split an unsplittable folio");
-		goto out_no_memcg;
-	}
-
-	/*
-	 * switch to folio's memcg as xarray node allocation can happen and
-	 * needs to charge to it.
-	 */
-	memcg = get_mem_cgroup_from_folio(folio);
-	old_memcg = set_active_memcg(memcg);
-
-	if (is_anon) {
-		/*
-		 * The caller does not necessarily hold an mmap_lock that would
-		 * prevent the anon_vma disappearing so we first we take a
-		 * reference to it and then lock the anon_vma for write. This
-		 * is similar to folio_lock_anon_vma_read except the write lock
-		 * is taken to serialise against parallel split or collapse
-		 * operations.
-		 */
-		anon_vma = folio_get_anon_vma(folio);
-		if (!anon_vma) {
-			ret = -EBUSY;
-			goto out;
-		}
-		anon_vma_lock_write(anon_vma);
-		mapping = NULL;
-	} else {
-		unsigned int min_order;
-		gfp_t gfp;
-
-		mapping = folio->mapping;
-		min_order = mapping_min_folio_order(mapping);
-		if (new_order < min_order) {
-			ret = -EINVAL;
-			goto out;
-		}
-
-		gfp = current_gfp_context(mapping_gfp_mask(mapping) &
-							GFP_RECLAIM_MASK);
-
-		if (!filemap_release_folio(folio, gfp)) {
-			ret = -EBUSY;
-			goto out;
-		}
-
-		mapping_set_update(&xas, mapping);
-
-		if (split_type == SPLIT_TYPE_UNIFORM) {
-			xas_set_order(&xas, folio->index, new_order);
-			xas_split_alloc(&xas, folio, old_order, gfp);
-			if (xas_error(&xas)) {
-				ret = xas_error(&xas);
-				goto out;
-			}
-		}
-
-		anon_vma = NULL;
-		i_mmap_lock_read(mapping);
-
-		/*
-		 *__split_unmapped_folio() may need to trim off pages beyond
-		 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
-		 * seqlock, which cannot be nested inside the page tree lock.
-		 * So note end now: i_size itself may be changed at any moment,
-		 * but folio lock is good enough to serialize the trimming.
-		 */
-		end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
-		if (shmem_mapping(mapping))
-			end = shmem_fallocend(mapping->host, end);
-	}
-
-	/*
-	 * Racy check if we can split the page, before unmap_folio() will
-	 * split PMDs
-	 */
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
-		ret = -EAGAIN;
-		goto out_unlock;
-	}
-
-	unmap_folio(folio);
-
-	/* block interrupt reentry in xa_lock and spinlock */
-	local_irq_disable();
-	if (mapping) {
-		/*
-		 * Check if the folio is present in page cache.
-		 * We assume all tail are present too, if folio is there.
-		 */
-		xas_lock(&xas);
-		xas_reset(&xas);
-		if (xas_load(&xas) != folio) {
-			ret = -EAGAIN;
-			goto fail;
-		}
+		goto out;
 	}
 
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, &xas, mapping,
-						true, list, split_type, end, &nr_shmem_dropped);
-fail:
-	if (mapping)
-		xas_unlock(&xas);
-
-	local_irq_enable();
-
-	if (nr_shmem_dropped)
-		shmem_uncharge(mapping->host, nr_shmem_dropped);
-
-	if (!ret && is_anon && !folio_is_device_private(folio))
-		ttu_flags = TTU_USE_SHARED_ZEROPAGE;
-
-	remap_page(folio, 1 << old_order, ttu_flags);
-
-	/*
-	 * Drop the mapping while the inode is still pinned. @folio stays
-	 * locked and present in the page cache until the loop below, so
-	 * eviction cannot free the inode yet; @lock_at is not enough, it may
-	 * be a tail beyond EOF that the split already dropped from the page
-	 * cache. Nothing past this point may touch the inode or the mapping.
-	 */
-	if (mapping) {
-		i_mmap_unlock_read(mapping);
-		mapping = NULL;
-	}
+	if (is_anon)
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     true, list, split_type);
+	else if (is_swapcache)
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     false, list, split_type);
+	else
+		ret = __folio_split_unmap_and_freeze_file(folio, new_order, split_at,
+							  list, split_type);
 
 	/*
 	 * Unlock all after-split folios except the one containing
@@ -4265,29 +4333,19 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		if (new_folio == page_folio(lock_at))
 			continue;
 
-		folio_unlock(new_folio);
 		/*
 		 * Subpages whose mapping has been zapped may be freed
 		 * earlier, but freeing them requires taking the
-		 * lru_lock, so we defer put_page() on tail pages until
+		 * lru_lock, so we defer folio_put() on tail pages until
 		 * after the split completes.
 		 */
-		free_folio_and_swap_cache(new_folio);
+		if (is_swapcache)
+			folio_free_swap(new_folio);
+		folio_unlock(new_folio);
+		folio_put(new_folio);
 	}
 
-out_unlock:
-	if (anon_vma) {
-		anon_vma_unlock_write(anon_vma);
-		put_anon_vma(anon_vma);
-	}
-	if (mapping)
-		i_mmap_unlock_read(mapping);
 out:
-	/* restore to caller's old_memcg */
-	set_active_memcg(old_memcg);
-	mem_cgroup_put(memcg);
-out_no_memcg:
-	xas_destroy(&xas);
 	if (is_pmd_order(old_order))
 		count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);
 	count_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);
@@ -4311,29 +4369,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
  * isolated from LRU (if applicable)
  *
  * Upon return, the folio is not remapped, split folios are not added to LRU,
- * free_folio_and_swap_cache() is not called, and new folios remain locked.
+ * folio_free_swap() is not called, and new folios remain locked.
  *
  * Return: 0 on success, -EAGAIN if the folio cannot be split (e.g., due to
  *         insufficient reference count or extra pins).
  */
 int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 {
-	int ret = 0;
-
 	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
 
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
-		return -EAGAIN;
-
-	local_irq_disable();
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, &folio->page, NULL,
-						NULL, false, NULL, SPLIT_TYPE_UNIFORM,
-						0, NULL);
-	local_irq_enable();
-	return ret;
+	return __folio_split_unmap_and_freeze(folio, new_order, &folio->page, false,
+					      false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*
@@ -4352,10 +4401,9 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  *    GUP pins, will result in the folio not getting split; instead, the caller
  *    will receive an -EAGAIN.
  *
- * 4) @new_order > 1, usually. Splitting to order-1 anonymous folios is not
- *    supported for non-file-backed folios, because folio->_deferred_list, which
- *    is used by partially mapped folios, is stored in subpage 2, but an order-1
- *    folio only has subpages 0 and 1. File-backed order-1 folios are supported,
+ * 4) @new_order != 1 for anon or swapcache. Anon folios need subpage 2 for
+ *    _deferred_list, which order-1 folios lack, and a swapcache folio may
+ *    become anon once faulted in. File-backed order-1 folios are supported,
  *    since they do not use _deferred_list.
  *
  * After splitting, the caller's folio reference will be transferred to @page,
@@ -4384,7 +4432,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  * with the folio. Splitting to order 0 is compatible with all folios.
  */
 int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
-				     unsigned int new_order)
+				       unsigned int new_order)
 {
 	struct folio *folio = page_folio(page);
 
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 5be825911e645..f1405e5b813ee 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -388,8 +388,9 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
 	    folio_order(old) != folio_order(new)) {
 		ci_off = swp_cluster_offset(old->swap);
 		ci_end = ci_off + folio_nr_pages(old);
-		while (ci_off++ < ci_end)
+		do {
 			WARN_ON_ONCE(swp_tb_to_folio(__swap_table_get(ci, ci_off)) != old);
+		} while (++ci_off < ci_end);
 	}
 }
 

31/3 2026/08/12 20:15 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12878
							output: 210
							thoughts: 569
						

Thoughts:
**My Assessment: KMSAN Not Needed**

Okay, let's break this down. This patch is touching core memory management, specifically folio splitting, in `mm/huge_memory.c` and `mm/swap_state.c`. It's refactoring the existing code, splitting things into functions like `__folio_split_unmap_and_freeze` and variants, dealing with anon, swapcache, and file-backed folios. It's essentially about how we handle splitting folios and their associated data structures.

The key thing I need to check is if this introduces *any* risk of uninitialized memory being exposed. That's what KMSAN is for, after all. From what I see, it doesn't. No new data structures are being sent up to user space, which is a common area for leaks. No new memory allocations are happening that *aren't* initialized before use. The code uses `folio_ref_freeze`, `unmap_folio`, `__split_frozen_folio`, and so on. These are all well-established functions.

I'm focusing specifically on uninitialized reads. Is there a scenario where this patch *could* lead to a kernel variable being read before it's been assigned a value, potentially leaking sensitive information, or creating a control flow vulnerability? No. The patch looks safe in this respect. It only modifies existing memory management structures and logic, it does not create any new code paths where uninitialized memory access is possible.

Therefore, the potential bugs would be classic memory corruption bugs: use-after-free, double frees, memory leaks, out-of-bounds accesses (because of incorrect page counts), or even deadlocks. But all of these are caught by KASAN, LOCKDEP, and the usual kernel memory bug detectors. KMSAN is for a different class of problem, which isn't present here.

So, I'm confident in saying that `NeedsKMSAN` should be false for this patch. I see no risk of uninitialized memory reads or information leaks.



32/3 2026/08/12 20:15 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch refactors the folio splitting logic in the memory management subsystem (specifically `mm/huge_memory.c` and `mm/swap_state.c`), introducing separate paths for anonymous/swapcache folios and file-backed folios. The changes involve page reference counting, locking, LRU list management, and page cache/swap cache updates.\n\nThese modifications do not introduce new data structures sent to user space, nor do they allocate uninitialized memory that is subsequently read or used in control flow. Any bugs introduced by this patch would likely manifest as use-after-free, double free, out-of-bounds accesses, or locking issues (deadlocks/race conditions). Such bugs are effectively detected by KASAN, LOCKDEP, and standard kernel bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch refactors the folio splitting logic in the memory management subsystem (specifically `mm/huge_memory.c` and `mm/swap_state.c`), introducing separate paths for anonymous/swapcache folios and file-backed folios. The changes involve page reference counting, locking, LRU list management, and page cache/swap cache updates.\n\nThese modifications do not introduce new data structures sent to user space, nor do they allocate uninitialized memory that is subsequently read or used in control flow. Any bugs introduced by this patch would likely manifest as use-after-free, double free, out-of-bounds accesses, or locking issues (deadlocks/race conditions). Such bugs are effectively detected by KASAN, LOCKDEP, and standard kernel bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

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)