Add the core infrastructure for ghost swapfile backend switching: - alloc_phys_swap_slot() - free_phys_swap_slot() - ghost_redirect_to_phys() Initialize redirect_xa in read_swap_header() when a ghost swapfile is detected (file size == PAGE_SIZE). Handle Redirect entries in __swap_cluster_free_entries(): when a ghost entry with a Redirect is freed (swap count reaches zero), erase the xarray mapping and cascade the free to the physical swap slot. Also skip zero-flag clearing for Redirect entries (they carry no zero flag). Signed-off-by: Baoquan He --- mm/swapfile.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/mm/swapfile.c b/mm/swapfile.c index 148ceb2d812b..c1686c2b2521 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1975,7 +1976,14 @@ void __swap_cluster_free_entries(struct swap_info_struct *si, * means zswap has a compressed copy; the xarray still * holds the entry and zswap_invalidate will free it. */ - if (swp_tb_is_pointer(old_tb)) { + if (swp_tb_is_redirect(old_tb)) { + swp_entry_t phys_entry; + + phys_entry = redirect_to_swp_entry(old_tb); + xa_erase(&si->redirect_xa, + ci_off + cluster_offset(si, ci)); + free_phys_swap_slot(phys_entry); + } else if (swp_tb_is_pointer(old_tb)) { VM_WARN_ON(zswap_swp_tb_get_count(old_tb) > 1); /* * Free the zswap entry now under ci->lock while @@ -1990,7 +1998,8 @@ void __swap_cluster_free_entries(struct swap_info_struct *si, /* Resetting the slot to NULL also clears the inline flags. */ __swap_table_set(ci, ci_off, null_to_swp_tb()); - if (!SWAP_TABLE_HAS_ZEROFLAG && !swp_tb_is_pointer(old_tb)) + if (!SWAP_TABLE_HAS_ZEROFLAG && !swp_tb_is_pointer(old_tb) && + !swp_tb_is_redirect(old_tb)) __swap_table_clear_zero(ci, ci_off); /* @@ -3593,6 +3602,7 @@ static unsigned long read_swap_header(struct swap_info_struct *si, /* Ghost swapfile */ si->bdev = NULL; si->flags |= SWP_GHOST | SWP_SOLIDSTATE; + xa_init(&si->redirect_xa); return maxpages; } @@ -4006,6 +4016,99 @@ void __folio_throttle_swaprate(struct folio *folio, gfp_t gfp) } #endif +#ifdef CONFIG_ZSWAP +/* + * Ghost swapfile redirect: physical swap slot allocation for writeback. + * + * Allocates a single order-0 swap slot from any real (non-ghost, writable) + * swap device. Used by zswap writeback to obtain a physical destination + * for ghost-backed entries. + */ +swp_entry_t alloc_phys_swap_slot(void) +{ + struct swap_info_struct *si; + unsigned long offset; + swp_entry_t entry = {}; + + spin_lock(&swap_avail_lock); + plist_for_each_entry(si, &swap_avail_head, avail_list) { + /* Skip ghost or non-writable devices */ + if (si->flags & SWP_GHOST) + continue; + if (!(si->flags & SWP_WRITEOK)) + continue; + if (!get_swap_device_info(si)) + continue; + + /* Try to allocate one slot */ + offset = cluster_alloc_swap_entry(si, NULL); + put_swap_device(si); + if (offset && offset != SWAP_ENTRY_INVALID) { + entry = swp_entry(si->type, offset); + break; + } + } + spin_unlock(&swap_avail_lock); + + return entry; +} + +void free_phys_swap_slot(swp_entry_t phys_entry) +{ + struct swap_info_struct *si; + unsigned long offset; + struct swap_cluster_info *ci; + + si = get_swap_device(phys_entry); + if (!si) + return; + offset = swp_offset(phys_entry); + ci = swap_cluster_lock(si, offset); + if (ci) { + /* + * Clear the physical slot. The swap count should be 0 at + * this point (the ghost entry's count has been transferred + * or freed). + */ + __swap_table_set(ci, offset % SWAPFILE_CLUSTER, + null_to_swp_tb()); + swap_cluster_unlock(ci); + swap_range_free(si, offset, 1); + } + put_swap_device(si); +} + +/* + * Resolve a ghost swap offset to its physical backing entry. + * Checks the swap table first (for Redirect entries), then falls + * back to the xarray (for entries that have a swap-cache folio). + */ +swp_entry_t ghost_redirect_to_phys(struct swap_info_struct *si, pgoff_t offset) +{ + struct swap_cluster_info *ci; + unsigned long swp_tb; + void *val; + + /* Fast path: check the swap table for a Redirect entry */ + ci = __swap_offset_to_cluster(si, offset); + spin_lock(&ci->lock); + swp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER); + if (swp_tb_is_redirect(swp_tb)) { + swp_entry_t phys = redirect_to_swp_entry(swp_tb); + spin_unlock(&ci->lock); + return phys; + } + spin_unlock(&ci->lock); + + /* Slow path: look up the persistent xarray mapping */ + val = xa_load(&si->redirect_xa, offset); + if (val && !xa_is_err(val)) + return (swp_entry_t){ .val = xa_to_value(val) }; + + return (swp_entry_t){}; +} +#endif /* CONFIG_ZSWAP */ + static int __init swapfile_init(void) { swapfile_maximum_size = arch_max_swapfile_size(); -- 2.54.0