Re: [PATCH v6 06/12] mm: swap in PMD swap entries as whole THPs during swapoff
From: Lance Yang
Date: Wed Aug 19 2026 - 01:38:22 EST
+Cc Miaohe
On Tue, Aug 18, 2026 at 06:09:47AM -0700, Usama Arif wrote:
[...]
>+#ifdef CONFIG_THP_SWAP
>+/*
>+ * unuse_pmd - Map a locked folio at PMD granularity during swapoff.
>+ *
>+ * The caller provides a locked, swapped-in folio. Returns 0 on success
>+ * (PMD was mapped). Returns -EAGAIN if the swap cache folio no longer
>+ * matches the entry or the PMD changed under the lock (try_to_unuse will
>+ * rescan). Returns -EIO if the folio is not uptodate or contains a poisoned
>+ * subpage; in that case the PMD is split so unuse_pte_range() can handle
>+ * individual pages.
>+ */
>+static int unuse_pmd(struct vm_area_struct *vma, pmd_t *pmd,
>+ unsigned long addr, softleaf_t entry,
>+ struct folio *folio)
>+{
>+ struct mm_struct *mm = vma->vm_mm;
>+ struct page *page;
>+ pmd_t new_pmd, old_pmd;
>+ spinlock_t *ptl;
>+ rmap_t rmap_flags = RMAP_NONE;
>+ bool exclusive;
>+
>+ if (unlikely(!folio_matches_swap_entry(folio, entry)))
>+ return -EAGAIN;
>+
>+ if (unlikely(!folio_test_uptodate(folio))) {
>+ /* Let PTE fallback reread each slot independently. */
>+ swap_cache_del_folio(folio);
>+ __split_huge_pmd(vma, pmd, addr, false);
>+ return -EIO;
>+ }
>+
>+ if (unlikely(folio_contain_hwpoisoned_page(folio))) {
>+ /* Let PTE fallback isolate the poisoned subpages. */
>+ __split_huge_pmd(vma, pmd, addr, false);
>+ return -EIO;
>+ }
Hmm ... can this miss a poisoned tail?
memory_failure() sets PageHWPoison(p) before taking folio_lock(), but
PG_has_hwpoisoned is only set later. Since unuse_pmd_entry() holds folio
lock across this check, memory_failure() can be blocked on folio_lock()
with a tail already poisoned, IIUC ...
folio_contain_hwpoisoned_page() then sees neither a poisoned head nor
PG_has_hwpoisoned, and set_pmd_at() maps that tail through a normal PMD.
Note that unuse_pte() checks PageHWPoison(page) directly. Should we check
each subpage here as well before installing the PMD?
>+
>+ page = folio_page(folio, 0);
>+
>+ ptl = pmd_lock(mm, pmd);
>+ old_pmd = pmdp_get(pmd);
>+
>+ if (!pmd_is_swap_entry(old_pmd) ||
>+ softleaf_from_pmd(old_pmd).val != entry.val) {
>+ spin_unlock(ptl);
>+ return -EAGAIN;
>+ }
>+
>+ exclusive = pmd_swp_exclusive(old_pmd);
>+
>+ /*
>+ * Some architectures may have to restore extra metadata to the folio
>+ * when reading from swap. This metadata may be indexed by swap entry
>+ * so this must be called before folio_put_swap().
>+ */
>+ arch_swap_restore(folio_swap(entry, folio), folio);
>+
>+ add_mm_counter(mm, MM_ANONPAGES, HPAGE_PMD_NR);
>+ add_mm_counter(mm, MM_SWAPENTS, -HPAGE_PMD_NR);
>+
>+ new_pmd = folio_mk_pmd(folio, vma->vm_page_prot);
>+ new_pmd = pmd_mkold(new_pmd);
>+ if (pmd_swp_soft_dirty(old_pmd))
>+ new_pmd = pmd_mksoft_dirty(new_pmd);
>+ if (pmd_swp_uffd(old_pmd))
>+ new_pmd = pmd_mkuffd(new_pmd);
>+ if (pmd_swp_uffd(old_pmd) && userfaultfd_rwp(vma))
>+ new_pmd = pmd_modify(new_pmd, PAGE_NONE);
>+
>+ if (exclusive)
>+ rmap_flags |= RMAP_EXCLUSIVE;
>+
>+ folio_get(folio);
>+ if (!folio_test_anon(folio))
>+ folio_add_new_anon_rmap(folio, vma, addr, rmap_flags);
>+ else
>+ folio_add_anon_rmap_pmd(folio, page, vma, addr, rmap_flags);
>+
>+ set_pmd_at(mm, addr, pmd, new_pmd);
>+ folio_put_swap(folio, NULL);
>+
>+ spin_unlock(ptl);
>+
>+ folio_free_swap(folio);
>+ return 0;
>+}
[...]
Cheers, Lance