From: Usama Arif <usama.arif@linux.dev>
To: Andrew Morton <akpm@linux-foundation.org>,
david@kernel.org, chrisl@kernel.org, kasong@tencent.com,
ljs@kernel.org, ziy@nvidia.com, linux-mm@kvack.org
Cc: ying.huang@linux.alibaba.com, Baoquan He <baoquan.he@linux.dev>,
willy@infradead.org, youngjun.park@lge.com, hannes@cmpxchg.org,
riel@surriel.com, shakeel.butt@linux.dev, alex@ghiti.fr,
kas@kernel.org, baohua@kernel.org, dev.jain@arm.com,
baolin.wang@linux.alibaba.com, Nico Pache <nico.pache@linux.dev>,
Liam R. Howlett <liam@infradead.org>,
ryan.roberts@arm.com, Vlastimil Babka <vbabka@kernel.org>,
lance.yang@linux.dev, linux-kernel@vger.kernel.org,
nphamcs@gmail.com, shikemeng@huaweicloud.com, yosry@kernel.org,
qi.zheng@linux.dev, luizcap@redhat.com, kernel-team@meta.com,
Usama Arif <usama.arif@linux.dev>
Subject: [RESEND v7 25/29] mm: handle PMD swap entries in UFFDIO_MOVE
Date: Mon, 14 Sep 2026 05:28:15 -0700 [thread overview]
Message-ID: <20260914122950.3283997-26-usama.arif@linux.dev> (raw)
In-Reply-To: <20260914122950.3283997-1-usama.arif@linux.dev>
move_pages_huge_pmd() returns -ENOENT for any PMD that is neither
trans_huge nor a migration entry, so an aligned UFFDIO_MOVE over a
swapped-out THP fails even though a PMD swap entry is a perfectly good
mapping to move. Falling back to the PTE path is no help either: splitting
yields PTE swap entries pointing at the same swap-cache folio, and
move_pages_ptes() refuses any swap-cache folio that is still large.
move_swap_pmd() is modelled on move_swap_pte(): it moves the entry under
both PMD locks, propagates soft-dirty, arms the UFFD marker for an
RWP-registered destination, carries the deposited page table across, and
requires pmd_swp_exclusive() for the same single-owner semantics.
The entry can only be moved whole while the covered swap cache is empty or
holds one PMD-sized folio. A cached folio is locked and revalidated, then
its anon rmap is re-anchored to the destination VMA; an empty cache is
re-checked slot by slot under both PMD locks, because a per-slot folio that
appeared meanwhile would need the PTE path to fix up its rmap metadata. A
range that is already split is split and retried through PTEs. Revalidation
failure just returns -EAGAIN: its usual cause is a racing fault that made
src_pmd a healthy present THP, which must not be shattered.
Finally, reject a PMD swap entry at the *destination* with -EEXIST. It is
not a hole, and unlike a migration entry it does not resolve on its own:
pte_alloc() skips a !pmd_none PMD, pte_offset_map_rw_nolock() then fails,
and the resulting -EAGAIN would be retried forever.
Signed-off-by: Usama Arif <usama.arif@linux.dev>
---
mm/huge_memory.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++-
mm/userfaultfd.c | 14 +++++
2 files changed, 171 insertions(+), 1 deletion(-)
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 5f3d620c64a94..497f677a3ef71 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2972,6 +2972,78 @@ int change_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,
#endif
#ifdef CONFIG_USERFAULTFD
+#ifdef CONFIG_THP_SWAP
+/*
+ * Move a PMD-level swap entry from src_pmd to dst_pmd. Both PMD locks are
+ * acquired here; src_folio (if present) must already be locked. The deposited
+ * page table backing the source THP is moved across with the entry.
+ */
+static int move_swap_pmd(struct mm_struct *mm, struct vm_area_struct *dst_vma,
+ unsigned long dst_addr, unsigned long src_addr,
+ pmd_t *dst_pmd, pmd_t *src_pmd,
+ pmd_t orig_dst_pmd, pmd_t orig_src_pmd,
+ spinlock_t *dst_ptl, spinlock_t *src_ptl,
+ struct folio *src_folio, swp_entry_t entry)
+{
+ pgtable_t src_pgtable;
+ pmd_t moved_pmd;
+
+ /*
+ * The folio may have been freed and reused for a different swap entry
+ * while it was unlocked. Re-verify the association.
+ */
+ if (src_folio && unlikely(!folio_matches_swap_entry(src_folio, entry) ||
+ folio_nr_pages(src_folio) != HPAGE_PMD_NR))
+ return -EAGAIN;
+
+ double_pt_lock(dst_ptl, src_ptl);
+
+ if (!pmd_same(*src_pmd, orig_src_pmd) ||
+ !pmd_same(*dst_pmd, orig_dst_pmd)) {
+ double_pt_unlock(dst_ptl, src_ptl);
+ return -EAGAIN;
+ }
+
+ /*
+ * If the folio is in the swap cache, re-anchor its anon rmap to the
+ * destination VMA so a future swap-in fault at dst_addr finds it.
+ * Otherwise, re-check the whole PMD swap range: a PMD swap entry is
+ * only a compact encoding for HPAGE_PMD_NR swap slots, and any per-slot
+ * cached folio would need the PTE move path to update its rmap
+ * metadata.
+ */
+ if (src_folio) {
+ folio_move_anon_rmap(src_folio, dst_vma);
+ src_folio->index = linear_anon_page_index(dst_vma, dst_addr);
+ } else {
+ unsigned int type = swp_type(entry);
+ pgoff_t offset = swp_offset(entry);
+ int i;
+
+ for (i = 0; i < HPAGE_PMD_NR; i++) {
+ if (swap_cache_has_folio(swp_entry(type, offset + i))) {
+ double_pt_unlock(dst_ptl, src_ptl);
+ return -EAGAIN;
+ }
+ }
+ }
+
+ moved_pmd = pmdp_huge_get_and_clear(mm, src_addr, src_pmd);
+ if (pgtable_supports_soft_dirty())
+ moved_pmd = pmd_swp_mksoft_dirty(moved_pmd);
+ /* Re-arm RWP on the moved swap entry if dst_vma is RWP-registered. */
+ if (userfaultfd_rwp(dst_vma))
+ moved_pmd = pmd_swp_mkuffd(moved_pmd);
+ set_pmd_at(mm, dst_addr, dst_pmd, moved_pmd);
+
+ src_pgtable = pgtable_trans_huge_withdraw(mm, src_pmd);
+ pgtable_trans_huge_deposit(mm, dst_pmd, src_pgtable);
+
+ double_pt_unlock(dst_ptl, src_ptl);
+ return 0;
+}
+#endif /* CONFIG_THP_SWAP */
+
/*
* The PT lock for src_pmd and dst_vma/src_vma (for reading) are locked by
* the caller, but it must return after releasing the page_table_lock.
@@ -3006,11 +3078,95 @@ int move_pages_huge_pmd(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd, pm
}
if (!pmd_trans_huge(src_pmdval)) {
- spin_unlock(src_ptl);
if (pmd_is_migration_entry(src_pmdval)) {
+ spin_unlock(src_ptl);
pmd_migration_entry_wait(mm, src_pmd);
return -EAGAIN;
}
+#ifdef CONFIG_THP_SWAP
+ if (pmd_is_swap_entry(src_pmdval)) {
+ swp_entry_t entry;
+ struct swap_info_struct *si;
+ enum swap_pmd_cache cache_state;
+
+ /*
+ * UFFDIO_MOVE on anon mappings requires single-owner
+ * semantics; refuse to move a shared swap entry.
+ */
+ if (!pmd_swp_exclusive(src_pmdval)) {
+ spin_unlock(src_ptl);
+ return -EBUSY;
+ }
+
+ entry = softleaf_from_pmd(src_pmdval);
+ spin_unlock(src_ptl);
+
+ /*
+ * Pin the swap device against a racing swapoff. NULL
+ * means swapoff is in progress, which resolves on its
+ * own, so ask the caller to retry. An error pointer
+ * means the entry names no swap device at all: that
+ * never resolves, so report it instead of spinning in
+ * the caller's -EAGAIN loop.
+ */
+ si = get_swap_device(entry);
+ if (!si)
+ return -EAGAIN;
+ if (IS_ERR(si))
+ return PTR_ERR(si);
+
+ src_folio = NULL;
+ cache_state = swap_pmd_cache_lookup(entry, &src_folio);
+ if (cache_state == SWAP_PMD_CACHE_SPLIT) {
+ put_swap_device(si);
+ __split_huge_pmd(src_vma, src_pmd, src_addr);
+ return -EAGAIN;
+ }
+
+ mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0,
+ mm, src_addr,
+ src_addr + HPAGE_PMD_SIZE);
+ mmu_notifier_invalidate_range_start(&range);
+
+ if (src_folio) {
+ folio_lock(src_folio);
+ /*
+ * Do not split on failure here. The usual cause
+ * is that a racing fault swapped the range back
+ * in and dropped the folio from the swap cache,
+ * so src_pmd is now a healthy present THP;
+ * splitting it would destroy the very mapping
+ * UFFDIO_MOVE is trying to move whole. The
+ * caller's -EAGAIN retry re-reads src_pmd and
+ * picks the right path, exactly as
+ * move_swap_pte() relies on for the PTE case.
+ */
+ if (!folio_matches_swap_entry(src_folio, entry) ||
+ folio_nr_pages(src_folio) != HPAGE_PMD_NR) {
+ folio_unlock(src_folio);
+ folio_put(src_folio);
+ mmu_notifier_invalidate_range_end(&range);
+ put_swap_device(si);
+ return -EAGAIN;
+ }
+ }
+
+ dst_ptl = pmd_lockptr(mm, dst_pmd);
+ err = move_swap_pmd(mm, dst_vma, dst_addr, src_addr,
+ dst_pmd, src_pmd, dst_pmdval,
+ src_pmdval, dst_ptl, src_ptl,
+ src_folio, entry);
+
+ mmu_notifier_invalidate_range_end(&range);
+ if (src_folio) {
+ folio_unlock(src_folio);
+ folio_put(src_folio);
+ }
+ put_swap_device(si);
+ return err;
+ }
+#endif /* CONFIG_THP_SWAP */
+ spin_unlock(src_ptl);
return -ENOENT;
}
diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index 79cc7b546f130..e9e1df254fd72 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -2053,6 +2053,20 @@ static ssize_t move_pages(struct userfaultfd_ctx *ctx, unsigned long dst_start,
break;
}
+ /*
+ * A PMD swap entry at dst is a swapped-out THP, not a hole,
+ * and unlike a PMD migration entry it will not resolve on its
+ * own. Nothing below faults it back in: pte_alloc() skips a
+ * !pmd_none PMD, pte_offset_map_rw_nolock() then fails on the
+ * non-present PMD, and the -EAGAIN that produces would be
+ * retried forever by the loop below. Be strict, exactly as for
+ * a present THP.
+ */
+ if (unlikely(pmd_is_swap_entry(dst_pmdval))) {
+ err = -EEXIST;
+ break;
+ }
+
ptl = pmd_trans_huge_lock(src_pmd, src_vma);
if (ptl) {
/* Check if we can move the pmd without splitting it. */
--
2.53.0-Meta
next prev parent reply other threads:[~2026-09-14 12:32 UTC|newest]
Thread overview: 73+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 12:27 [RESEND v7 00/29] mm: PMD-level swap entries for anonymous THPs Usama Arif
2026-09-14 12:27 ` [RESEND v7 01/29] mm: rename pmd_to_softleaf_folio() to pmd_softleaf_to_folio() Usama Arif
2026-09-14 21:17 ` Barry Song
2026-09-15 3:06 ` Baoquan He
2026-09-15 3:34 ` Qi Zheng
2026-09-14 12:27 ` [RESEND v7 02/29] arm64: mm: add PMD swap-exclusive helpers Usama Arif
2026-09-18 21:11 ` David Hildenbrand (Arm)
2026-09-22 12:34 ` Usama Arif
2026-09-22 14:45 ` David Hildenbrand (Arm)
2026-09-22 15:03 ` Usama Arif
2026-09-14 12:27 ` [RESEND v7 03/29] loongarch: " Usama Arif
2026-09-18 21:18 ` David Hildenbrand (Arm)
2026-09-14 12:27 ` [RESEND v7 04/29] powerpc: " Usama Arif
2026-09-18 21:22 ` David Hildenbrand (Arm)
2026-09-22 12:48 ` Usama Arif
2026-09-14 12:27 ` [RESEND v7 05/29] riscv: " Usama Arif
2026-09-18 21:24 ` David Hildenbrand (Arm)
2026-09-14 12:27 ` [RESEND v7 06/29] s390: " Usama Arif
2026-09-18 21:41 ` David Hildenbrand (Arm)
2026-09-22 12:51 ` Usama Arif
2026-09-14 12:27 ` [RESEND v7 07/29] x86: " Usama Arif
2026-09-16 14:31 ` Kiryl Shutsemau
2026-09-22 12:52 ` Usama Arif
2026-09-18 21:43 ` David Hildenbrand (Arm)
2026-09-22 12:54 ` Usama Arif
2026-09-14 12:27 ` [RESEND v7 08/29] mm: recognize PMD swap entries in the softleaf layer Usama Arif
2026-09-16 14:47 ` Kiryl Shutsemau
2026-09-18 21:56 ` David Hildenbrand (Arm)
2026-09-22 13:08 ` Usama Arif
2026-09-22 14:48 ` David Hildenbrand (Arm)
2026-09-14 12:27 ` [RESEND v7 09/29] mm/debug_vm_pgtable: test PMD swap-exclusive helpers Usama Arif
2026-09-16 14:52 ` Kiryl Shutsemau
2026-09-18 21:57 ` David Hildenbrand (Arm)
2026-09-14 12:28 ` [RESEND v7 10/29] mm: make PMD migration-entry splitting explicit Usama Arif
2026-09-16 14:58 ` Kiryl Shutsemau
2026-09-18 22:10 ` David Hildenbrand (Arm)
2026-09-22 13:15 ` Usama Arif
2026-09-23 11:04 ` David Hildenbrand (Arm)
2026-09-24 16:12 ` Usama Arif
2026-09-14 12:28 ` [RESEND v7 11/29] mm: split PMD swap entries into PTE swap entries Usama Arif
2026-09-16 15:08 ` Kiryl Shutsemau
2026-09-22 11:46 ` David Hildenbrand (Arm)
2026-09-22 13:18 ` Usama Arif
2026-09-23 11:20 ` David Hildenbrand (Arm)
2026-09-24 17:34 ` Usama Arif
2026-09-24 19:57 ` David Hildenbrand (Arm)
2026-09-14 12:28 ` [RESEND v7 12/29] mm: handle PMD swap entries in fork path Usama Arif
2026-09-24 20:28 ` David Hildenbrand (Arm)
2026-09-25 11:16 ` Usama Arif
2026-09-14 12:28 ` [RESEND v7 13/29] mm: zswap: reject high-order swap cache allocations backed by zswap Usama Arif
2026-09-24 20:30 ` David Hildenbrand (Arm)
2026-09-14 12:28 ` [RESEND v7 14/29] mm: swap in PMD swap entries as whole THPs during swapoff Usama Arif
2026-09-14 12:28 ` [RESEND v7 15/29] fs/proc: account PMD swap entries in smaps Usama Arif
2026-09-24 20:37 ` David Hildenbrand (Arm)
2026-09-25 13:28 ` Usama Arif
2026-09-14 12:28 ` [RESEND v7 16/29] mm: handle soft-dirty and uffd-wp on PMD swap entries Usama Arif
2026-09-14 12:28 ` [RESEND v7 17/29] mm/hmm: fault PMD swap entries on demand Usama Arif
2026-09-14 12:28 ` [RESEND v7 18/29] mm: free PMD swap entries in zap_huge_pmd() Usama Arif
2026-09-14 12:28 ` [RESEND v7 19/29] mm/madvise: free PMD swap entries with MADV_FREE Usama Arif
2026-09-14 12:28 ` [RESEND v7 20/29] mm/madvise: skip PMD swap entries for MADV_COLD and MADV_PAGEOUT Usama Arif
2026-09-14 12:28 ` [RESEND v7 21/29] mm/madvise: keep PMD swap entries whole for MADV_GUARD_INSTALL/REMOVE Usama Arif
2026-09-14 12:28 ` [RESEND v7 22/29] mm/mincore: report PMD swap-cache residency Usama Arif
2026-09-14 12:28 ` [RESEND v7 23/29] mm/khugepaged: treat PMD swap entries as mapped THPs Usama Arif
2026-09-14 12:28 ` [RESEND v7 24/29] mm: handle PMD swap entries in MADV_WILLNEED Usama Arif
2026-09-14 12:28 ` Usama Arif [this message]
2026-09-14 12:28 ` [RESEND v7 26/29] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage Usama Arif
2026-09-14 12:28 ` [RESEND v7 27/29] mm: handle PMD swap entry faults on swap-in Usama Arif
2026-09-14 12:28 ` [RESEND v7 28/29] mm: install PMD swap entries on swap-out Usama Arif
2026-09-14 12:28 ` [RESEND v7 29/29] selftests/mm: add PMD swap entry tests Usama Arif
2026-09-15 3:32 ` [RESEND v7 00/29] mm: PMD-level swap entries for anonymous THPs Andrew Morton
2026-09-15 14:09 ` Usama Arif
2026-09-16 0:00 ` Andrew Morton
2026-09-16 10:22 ` Usama Arif
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260914122950.3283997-26-usama.arif@linux.dev \
--to=usama.arif@linux.dev \
--cc=akpm@linux-foundation.org \
--cc=alex@ghiti.fr \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=baoquan.he@linux.dev \
--cc=chrisl@kernel.org \
--cc=david@kernel.org \
--cc=dev.jain@arm.com \
--cc=hannes@cmpxchg.org \
--cc=kas@kernel.org \
--cc=kasong@tencent.com \
--cc=kernel-team@meta.com \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=luizcap@redhat.com \
--cc=nico.pache@linux.dev \
--cc=nphamcs@gmail.com \
--cc=qi.zheng@linux.dev \
--cc=riel@surriel.com \
--cc=ryan.roberts@arm.com \
--cc=shakeel.butt@linux.dev \
--cc=shikemeng@huaweicloud.com \
--cc=vbabka@kernel.org \
--cc=willy@infradead.org \
--cc=ying.huang@linux.alibaba.com \
--cc=yosry@kernel.org \
--cc=youngjun.park@lge.com \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.