From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from out-189.mta0.migadu.com (out-189.mta0.migadu.com [91.218.175.189]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C39C031328E for ; Tue, 7 Jul 2026 08:27:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.189 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783412826; cv=none; b=FiId9VEK7ALO/EmfhOKP73Wa7nSDk6WMIhH29yBBrLfUgKjgiF/7pX4bGvAESNeh1M8KKNjAQa9tx13JWze+LdKPCM4KWJcWy03OEkrX478v2EyvxwUXT+xUTtCM45Z/bXn+vGNmjwIKejE3OAd/EWSBnoEJwPJ+Ci/7aTnuHFc= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783412826; c=relaxed/simple; bh=VgYemv12BMLAQxWTMJBNU9EHAR4DmV4I0rNHmNZCYtY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-type; b=G849Uuxl+H26vIDS7Jzzt/sqbj8XTvxpV/ySELq3t0+A9Szu3DNBujih3BKBECZQqurL7pen6+eTGh+9jMAwpJLNTEIi61hyxcCMc0OnzlS5N3WYZbNCxnV8lSAy+BNVKwV+9PE61qKUcp3ROhaWfcJC33sLAOXaCdjFwxYNL44= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=nAaGzpYt; arc=none smtp.client-ip=91.218.175.189 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="nAaGzpYt" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1783412822; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=uQRdyB2exQMY8PjLdMUY7qkNzm0fh5pDFz3uMFxFwug=; b=nAaGzpYtXVbuVCg1xHEwqljL7JZSkxz5bpsy87hpaI4WHtkeVjevFYFzsP8SnOW2PEmE2V wsak8lJbptKJq15tIWoZ5QRS3T4LWoNgF2fPTRvmvs8mHe9pNAJHMbXcLvLkkQIm/Qx0t3 3bN8ERwhbXsL3lrpddbkdfWBz38v8pg= From: Baoquan He To: linux-mm@kvack.org Cc: akpm@linux-foundation.org, chrisl@kernel.org, kasong@tencent.com, shikemeng@huaweicloud.com, nphamcs@gmail.com, baohua@kernel.org, youngjun.park@lge.com, hannes@cmpxchg.org, yosry@kernel.org, chengming.zhou@linux.dev, linux-kernel@vger.kernel.org, Baoquan He Subject: [RFC PATCH 04/10] mm/swapfile: implement ghost redirect helpers and free-path cascade Date: Tue, 7 Jul 2026 16:26:06 +0800 Message-ID: <20260707082614.95030-5-baoquan.he@linux.dev> In-Reply-To: <20260707082614.95030-1-baoquan.he@linux.dev> References: <20260707082614.95030-1-baoquan.he@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-type: text/plain Content-Transfer-Encoding: 8bit X-Migadu-Flow: FLOW_OUT 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