When cluster_alloc_swap_entry() fails to find a free cluster and the xswap device still has room to grow, expand the mapped range by XSWAP_GROW_CLUSTERS clusters. Since xswap is always SWP_SOLIDSTATE, global_cluster_lock is never held on this path. This makes the xswap cluster space grow transparently as swap usage increases, without any userspace intervention. Growing maps pages into the VM_SPARSE area, which can sleep. The caller holds local_lock(&percpu_swap_cluster.lock) across the whole slow path, so drop it around xswap_map_clusters() and take it again afterwards; it only protects the per-cpu cluster cache, which this path does not touch. Signed-off-by: Baoquan He --- mm/swapfile.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/mm/swapfile.c b/mm/swapfile.c index b0edf5421fc5..17e482059d32 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -1273,6 +1273,47 @@ static unsigned long cluster_alloc_swap_entry(struct swap_info_struct *si, if (found) goto done; } + +#ifdef CONFIG_XSWAP + /* For xswap: grow the cluster_info array, then retry. */ + if (!found && (si->flags & SWP_XSWAP) && + READ_ONCE(si->nr_clusters_mapped) < READ_ONCE(si->nr_clusters_max) && + list_empty(&si->free_clusters)) { + unsigned long nr_new = min(READ_ONCE(si->nr_clusters_max) - + READ_ONCE(si->nr_clusters_mapped), + XSWAP_GROW_CLUSTERS); + unsigned long start = READ_ONCE(si->nr_clusters_mapped); + unsigned long i; + int ret; + + /* + * Mapping pages can sleep. The lock only guards the per-cpu + * cluster cache, which this path does not touch. + */ + local_unlock(&percpu_swap_cluster.lock); + ret = xswap_map_clusters(si, start, nr_new); + local_lock(&percpu_swap_cluster.lock); + + if (!ret) { + for (i = start; i < start + nr_new; i++) { + struct swap_cluster_info *ci = &si->cluster_info[i]; + + /* + * A concurrent grower may have taken these already; + * only add the off-list ones. + */ + spin_lock(&ci->lock); + if (ci->flags == CLUSTER_FLAG_NONE) + move_cluster(si, ci, &si->free_clusters, + CLUSTER_FLAG_FREE); + spin_unlock(&ci->lock); + } + + found = alloc_swap_scan_list(si, &si->free_clusters, + folio, false); + } + } +#endif done: if (!(si->flags & SWP_SOLIDSTATE)) spin_unlock(&si->global_cluster_lock); -- 2.54.0