Add zswap_writeback_ghost() to handle zswap writeback when the backing swap device is a ghost swapfile (SWP_GHOST). Previously, writeback for ghost entries was rejected with -EINVAL. The new writeback path: 1. Allocates a physical swap slot from any real swap device via alloc_phys_swap_slot(). 2. Creates a swap-cache folio attached to the ghost swap entry. 3. Decompresses the zswap entry into the folio. 4. Stores the ghost->physical mapping in the ghost's redirect_xa. 5. Temporarily redirects folio->swap to the physical entry and calls __swap_writepage() to write to the physical device. 6. On success, the xarray mapping persists. When the swap-cache folio is later removed, __swap_cache_do_del_folio() restores the Redirect entry from the xarray. This enables the backend switch from zswap (compressed in memory) to physical swap (on disk) without changing the PTE — the PTE continues to point to the ghost swap entry, and the swapin path forwards the I/O via the Redirect entry. Modify zswap_writeback_entry() to call zswap_writeback_ghost() for ghost-backed entries instead of returning -EINVAL. Signed-off-by: Baoquan He --- mm/zswap.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 5 deletions(-) diff --git a/mm/zswap.c b/mm/zswap.c index bdf8283fd880..2137e8f14413 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -978,6 +978,90 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio) * the swap cache, the compressed version stored by zswap can be * freed. */ +/* + * Ghost swapfile writeback: allocate a physical swap slot, decompress + * the zswap entry into a swap-cache folio, write it to the physical + * device via __swap_writepage, and set up a Redirect entry on the + * ghost swap table so future swapins are forwarded to the physical + * device. + */ +static int zswap_writeback_ghost(struct zswap_entry *entry, + swp_entry_t swpentry, + struct swap_info_struct *ghost_si, + pgoff_t offset) +{ + swp_entry_t phys_entry; + struct folio *folio; + struct mempolicy *mpol; + swp_entry_t saved_entry; + int ret = 0; + + /* Allocate a physical swap slot from a real device */ + phys_entry = alloc_phys_swap_slot(); + if (!phys_entry.val) + return -ENOMEM; + + /* Create a swap-cache folio attached to the ghost swap entry */ + mpol = get_task_policy(current); + folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol, + NO_INTERLEAVE_INDEX, NULL); + if (IS_ERR(folio)) { + ret = PTR_ERR(folio); + goto free_phys; + } + + /* Decompress zswap data into the folio */ + if (!zswap_decompress(entry, folio)) { + ret = -EIO; + goto del_folio; + } + + count_vm_event(ZSWPWB); + if (entry->objcg) + count_objcg_events(entry->objcg, ZSWPWB, 1); + + zswap_entry_free(entry); + + folio_mark_uptodate(folio); + folio_set_reclaim(folio); + + /* + * Store the redirect mapping in the xarray so it persists + * across swap-cache PFN cycles. The swap table currently + * holds a PFN (the swap cache folio); when the folio is + * eventually removed from swap cache, the Redirect entry + * will be restored from the xarray. + */ + xa_store(&ghost_si->redirect_xa, offset, + xa_mk_value(phys_entry.val), GFP_ATOMIC); + + /* + * Write the folio to the physical swap device. Temporarily + * redirect folio->swap to the physical entry so + * __swap_writepage uses the correct bdev and sector. + */ + saved_entry = folio->swap; + folio->swap = phys_entry; + ret = __swap_writepage(folio, NULL); + folio->swap = saved_entry; + + if (ret) { + xa_erase(&ghost_si->redirect_xa, offset); + goto del_folio; + } + + folio_put(folio); + return 0; + +del_folio: + swap_cache_del_folio(folio); + folio_unlock(folio); + folio_put(folio); +free_phys: + free_phys_swap_slot(phys_entry); + return ret; +} + static int zswap_writeback_entry(struct zswap_entry *entry, swp_entry_t swpentry) { @@ -994,11 +1078,6 @@ static int zswap_writeback_entry(struct zswap_entry *entry, if (!si) return -ENOENT; - if (si->flags & SWP_GHOST) { - put_swap_device(si); - return -EINVAL; - } - /* * Verify the swap table Pointer entry still references this * zswap_entry. If the entry was invalidated or replaced by a @@ -1015,6 +1094,13 @@ static int zswap_writeback_entry(struct zswap_entry *entry, } spin_unlock(&ci->lock); + /* Ghost swapfile: write back to a physical swap device */ + if (si->flags & SWP_GHOST) { + ret = zswap_writeback_ghost(entry, swpentry, si, offset); + put_swap_device(si); + return ret; + } + mpol = get_task_policy(current); folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol, NO_INTERLEAVE_INDEX, NULL); -- 2.54.0