[RFC PATCH 11/11] mm, swap: serialize xswap map/unmap with a mutex

From: Baoquan He

Date: Mon Jul 27 2026 - 10:19:02 EST


Concurrent xswap grow operations race on vm_area_map_pages(),
triggering WARN_ON(!pte_none) in the vmap page table walk when
one thread sees a PTE already set by another. The -EBUSY safety
net catches it but the warning is noisy and unavoidable without
serialization.

Add a per-device mutex (xswap_lock) held across xswap_map_clusters
and xswap_unmap_clusters so that only one thread can modify the
VM_SPARSE page table at a time. The shrink path is already
deferred to a workqueue so it does not contend with itself.

Signed-off-by: Baoquan He <baoquan.he@xxxxxxxxx>
---
include/linux/swap.h | 1 +
mm/swapfile.c | 17 +++++++++++++++--
2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/include/linux/swap.h b/include/linux/swap.h
index ebae01fbd100..b0fe0475bba7 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -256,6 +256,7 @@ struct swap_info_struct {
unsigned long nr_free_tail; /* contiguous free clusters at tail */
struct dentry *debugfs_entry; /* debugfs: type<N>_max_clusters */
struct work_struct xswap_shrink_work; /* deferred shrink trigger */
+ struct mutex xswap_lock; /* serialize map/unmap operations */
#endif
struct list_head free_clusters; /* free clusters list */
struct list_head full_clusters; /* full clusters list */
diff --git a/mm/swapfile.c b/mm/swapfile.c
index b41ff2c594fc..89c6a6b03a9d 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -3773,11 +3773,14 @@ static int xswap_map_clusters(struct swap_info_struct *si,
struct page **pages;
unsigned long i;

+ mutex_lock(&si->xswap_lock);
+
if (vm_start >= vm_end) {
/* All requested clusters fall within already-mapped pages. */
for (i = start_idx; i < start_idx + nr; i++)
spin_lock_init(&si->cluster_info[i].lock);
WRITE_ONCE(si->nr_clusters_mapped, start_idx + nr);
+ mutex_unlock(&si->xswap_lock);
return 0;
}

@@ -3842,6 +3845,7 @@ static int xswap_map_clusters(struct swap_info_struct *si,
* Pairs with READ_ONCE() in shrink/grow paths.
*/
WRITE_ONCE(si->nr_clusters_mapped, start_idx + nr);
+ mutex_unlock(&si->xswap_lock);
return 0;

fail_nounmap:
@@ -3862,6 +3866,7 @@ static int xswap_map_clusters(struct swap_info_struct *si,
spin_lock_init(&si->cluster_info[i].lock);

WRITE_ONCE(si->nr_clusters_mapped, start_idx + nr);
+ mutex_unlock(&si->xswap_lock);
return 0;

fail:
@@ -3871,6 +3876,7 @@ static int xswap_map_clusters(struct swap_info_struct *si,
__free_page(pages[i]);
}
kfree(pages);
+ mutex_unlock(&si->xswap_lock);
return -ENOMEM;
}

@@ -3910,8 +3916,12 @@ static void xswap_unmap_clusters(struct swap_info_struct *si,
struct xswap_page_data xpd;
int i;

- if (vm_start >= vm_end)
+ mutex_lock(&si->xswap_lock);
+
+ if (vm_start >= vm_end) {
+ mutex_unlock(&si->xswap_lock);
goto out;
+ }

size = vm_end - vm_start;
npages = size >> PAGE_SHIFT;
@@ -3937,10 +3947,12 @@ static void xswap_unmap_clusters(struct swap_info_struct *si,
__free_page(xpd.pages[i]);
kfree(xpd.pages);
}
+
+ mutex_unlock(&si->xswap_lock);
+out:
/*
* Pairs with READ_ONCE() in shrink/grow paths.
*/
- out:
WRITE_ONCE(si->nr_clusters_mapped, start_idx);
}

@@ -4142,6 +4154,7 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
/* All mapped clusters except cluster 0 are free at the tail */
si->nr_free_tail = si->nr_clusters_mapped - 1;

+ mutex_init(&si->xswap_lock);
INIT_WORK(&si->xswap_shrink_work, xswap_shrink_work_fn);
xswap_debugfs_add(si);
return 0;
--
2.54.0