[RESEND v7 11/29] mm: split PMD swap entries into PTE swap entries
From: Usama Arif
Date: Mon Sep 14 2026 - 08:55:27 EST
Once a PMD can hold a swap entry, everything that splits a PMD - mprotect()
or munmap() over part of the range, MADV_FREE, a pagewalk with no PMD
handler - has to be able to split that entry too, or the callers that rely
on split_huge_pmd() to hand them a PTE table would find the PMD unchanged.
No reference counting is needed: a swap entry pins no folio, and swap_map
is already one per slot, so the PTEs simply take over what the PMD held.
The migration-only entry point cannot reach the new branch, because
page_vma_mapped_walk() never hands back a swap PMD for the folio being
migrated. Warn if that ever changes, and force the regular split anyway,
since the branch leaves folio and page uninitialised.
Test the pre-split old_pmd rather than re-reading *pmd in the trailing
folio_remove_rmap_pmd() gate, so every entry-type test in the function
interrogates the same snapshot. That part is cosmetic: pmdp_invalidate()
leaves the PMD present as far as software is concerned.
Signed-off-by: Usama Arif <usama.arif@xxxxxxxxx>
---
mm/huge_memory.c | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 873887aed0bc2..0e347a545588c 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3304,6 +3304,21 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
folio_add_anon_rmap_ptes(folio, page, HPAGE_PMD_NR,
vma, haddr, rmap_flags);
}
+ } else if (pmd_is_swap_entry(*pmd)) {
+ /*
+ * A PMD swap entry has no page, so it cannot be turned into
+ * PTE migration entries. page_vma_mapped_walk() never hands
+ * one back for the folio being migrated, so this should not
+ * happen; warn, but also force the regular split so that a
+ * broken invariant cannot make the code below dereference the
+ * uninitialised folio and page.
+ */
+ VM_WARN_ON_ONCE(use_migration_entries);
+ use_migration_entries = false;
+ old_pmd = *pmd;
+ soft_dirty = pmd_swp_soft_dirty(old_pmd);
+ uffd_wp = pmd_swp_uffd(old_pmd);
+ anon_exclusive = pmd_swp_exclusive(old_pmd);
} else {
/*
* Up to this point the pmd is present and huge and userland has
@@ -3440,6 +3455,25 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
VM_WARN_ON(!pte_none(ptep_get(pte + i)));
set_pte_at(mm, addr, pte + i, entry);
}
+ } else if (pmd_is_swap_entry(old_pmd)) {
+ const softleaf_t old_entry = softleaf_from_pmd(old_pmd);
+ pte_t pte_swp_entry;
+ swp_entry_t entry;
+
+ for (i = 0, addr = haddr; i < HPAGE_PMD_NR;
+ i++, addr += PAGE_SIZE) {
+ entry = swp_entry(swp_type(old_entry),
+ swp_offset(old_entry) + i);
+ pte_swp_entry = swp_entry_to_pte(entry);
+ if (soft_dirty)
+ pte_swp_entry = pte_swp_mksoft_dirty(pte_swp_entry);
+ if (uffd_wp)
+ pte_swp_entry = pte_swp_mkuffd(pte_swp_entry);
+ if (anon_exclusive)
+ pte_swp_entry = pte_swp_mkexclusive(pte_swp_entry);
+ VM_WARN_ON(!pte_none(ptep_get(pte + i)));
+ set_pte_at(mm, addr, pte + i, pte_swp_entry);
+ }
} else {
pte_t entry;
@@ -3467,7 +3501,7 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
}
pte_unmap(pte);
- if (!pmd_is_migration_entry(*pmd))
+ if (!pmd_is_migration_entry(old_pmd) && !pmd_is_swap_entry(old_pmd))
folio_remove_rmap_pmd(folio, page, vma);
if (use_migration_entries)
put_page(page);
--
2.53.0-Meta