swapin_walk_pmd_entry() walks PTEs and skips non-present PMDs, so MADV_WILLNEED is a no-op on a PMD swap entry. Handle PMD swap entries under pmd_trans_huge_lock(). If the covered swap-cache range already has a PMD-sized folio, there is nothing left to prefetch. If the range has split cache state, or any covered slot currently has a zswap entry, split the PMD swap entry and ask the walker to retry so the PTE path can handle the individual slots. Otherwise pin the swap device and read the folio in at PMD order via swapin_sync(BIT(HPAGE_PMD_ORDER)). This keeps the subsequent fault on the do_huge_pmd_swap_page() path and avoids order-0 readahead needlessly splitting the PMD swap entry. Any failure of the PMD-order swapin splits the entry and retries through the PTE path. That covers losing a race with per-slot swap-cache population (-EBUSY) after dropping the PMD lock, but also the -ENOMEM that a PMD-order allocation can easily hit: leaving the entry alone would make MADV_WILLNEED prefetch nothing at all for the range, while the PTE path can still read the 512 slots at order 0. If per-page zswap state reappears during the read, remove the failed clean PMD-sized folio from swap cache before splitting so the PTE path can load each slot. This uses folio_trylock(): the lock is only free once the read has completed, so MADV_WILLNEED never blocks on in-flight I/O, and unlike testing folio_test_locked() directly it cannot race with an unrelated lock holder. Signed-off-by: Usama Arif --- mm/madvise.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/mm/madvise.c b/mm/madvise.c index 16b39a06b038f..3ef1af1e76daa 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -185,6 +186,93 @@ static int madvise_update_vma(vm_flags_t new_flags, } #ifdef CONFIG_SWAP +/* + * Prefetch a whole PMD swap entry as one PMD-order folio. + * + * Called with the PMD lock held; always drops it. Returns true when the + * caller should ask the walker to retry so the PTE path can handle the + * covered slots individually. + */ +static bool swapin_pmd_swap_entry(struct vm_area_struct *vma, pmd_t *pmd, + unsigned long addr, softleaf_t entry, + spinlock_t *ptl) +{ + struct vm_fault vmf = { + .vma = vma, + .address = addr, + .real_address = addr, + .pmd = pmd, + }; + enum swap_pmd_cache cache_state; + struct swap_info_struct *si; + struct folio *folio; + bool split = false; + + cache_state = swap_pmd_cache_lookup(entry, &folio); + if (cache_state == SWAP_PMD_CACHE_HUGE) { + /* Already cached as one PMD-sized folio, nothing to do. */ + folio_put(folio); + spin_unlock(ptl); + return false; + } + if (cache_state == SWAP_PMD_CACHE_SPLIT || + zswap_is_present(entry, HPAGE_PMD_NR)) { + spin_unlock(ptl); + return true; + } + + /* + * Pin the swap device under the PMD lock so the PMD-swap-entry + * observation keeps the entry valid for swapin_sync(). + */ + si = get_swap_device(entry); + spin_unlock(ptl); + if (!si) + return false; + + folio = swapin_sync(entry, GFP_HIGHUSER_MOVABLE, BIT(HPAGE_PMD_ORDER), + &vmf, NULL, 0); + + /* + * Fall back to PTE-order swapin: a PMD-order failure does not mean + * that individual slots cannot be read. + */ + if (IS_ERR_OR_NULL(folio)) { + split = true; + goto out; + } + + if (folio_nr_pages(folio) != HPAGE_PMD_NR) { + split = true; + goto out_put; + } + + /* + * A trylock only succeeds once the read has completed, so this never + * blocks MADV_WILLNEED on in-flight I/O. A failed PMD-order zswap load + * leaves the folio clean and not uptodate; drop it from the swap cache + * so the PTE retry can load the per-page state. Another thread may + * have removed it already, so revalidate the association first. + */ + if (!folio_trylock(folio)) + goto out_put; + + if (!folio_test_uptodate(folio) && + zswap_is_present(entry, HPAGE_PMD_NR)) { + if (folio_matches_swap_entry(folio, entry)) + swap_cache_del_folio(folio); + split = true; + } + folio_unlock(folio); + +out_put: + folio_put(folio); +out: + /* Keep the device pinned until the last use of @entry. */ + put_swap_device(si); + return split; +} + static int swapin_walk_pmd_entry(pmd_t *pmd, unsigned long start, unsigned long end, struct mm_walk *walk) { @@ -194,6 +282,23 @@ static int swapin_walk_pmd_entry(pmd_t *pmd, unsigned long start, spinlock_t *ptl; unsigned long addr; + ptl = pmd_trans_huge_lock(pmd, vma); + if (ptl) { + pmd_t pmdval = *pmd; + + if (pmd_is_swap_entry(pmdval)) { + /* swapin_pmd_swap_entry() always drops the PMD lock. */ + if (swapin_pmd_swap_entry(vma, pmd, start, + softleaf_from_pmd(pmdval), + ptl)) { + __split_huge_pmd(vma, pmd, start, false); + walk->action = ACTION_AGAIN; + } + goto ret; + } + spin_unlock(ptl); + } + for (addr = start; addr < end; addr += PAGE_SIZE) { pte_t pte; softleaf_t entry; @@ -222,6 +327,7 @@ static int swapin_walk_pmd_entry(pmd_t *pmd, unsigned long start, if (ptep) pte_unmap_unlock(ptep, ptl); swap_read_submit(&ctx); +ret: cond_resched(); return 0; -- 2.53.0-Meta