[PATCH 3/3] mm/swap: route slot free notifications through swap_ops
From: Jianyue Wu
Date: Sun Jun 14 2026 - 11:36:43 EST
Dispatch slot_free_notify through swap_ops instead of
block_device_operations. Zram keeps slot-free handling alongside its
other swap_ops methods.
Move slot_trylock into the CONFIG_SWAP block. With CONFIG_SWAP=n it
has no callers and the build fails on -Werror=unused-function.
Document the callback locking rules in include/linux/swap.h. Remove
the outdated locking.rst note for swap_slot_free_notify.
Signed-off-by: Jianyue Wu <wujianyue000@xxxxxxxxx>
---
Documentation/filesystems/locking.rst | 5 --
drivers/block/zram/zram_drv.c | 88 ++++++++++++++++++-----------------
include/linux/blkdev.h | 2 -
include/linux/swap.h | 7 +++
mm/swapfile.c | 13 ++----
rust/kernel/block/mq/gen_disk.rs | 1 -
6 files changed, 57 insertions(+), 59 deletions(-)
diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst
index 70481bdc031d..964c841bf917 100644
--- a/Documentation/filesystems/locking.rst
+++ b/Documentation/filesystems/locking.rst
@@ -443,7 +443,6 @@ prototypes::
unsigned long *);
void (*unlock_native_capacity) (struct gendisk *);
int (*getgeo)(struct gendisk *, struct hd_geometry *);
- void (*swap_slot_free_notify) (struct block_device *, unsigned long);
locking rules:
@@ -457,12 +456,8 @@ compat_ioctl: no
direct_access: no
unlock_native_capacity: no
getgeo: no
-swap_slot_free_notify: no (see below)
======================= ===================
-swap_slot_free_notify is called with swap_lock and sometimes the page lock
-held.
-
file_operations
===============
diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c
index 9b2bd0287402..b78246dc1746 100644
--- a/drivers/block/zram/zram_drv.c
+++ b/drivers/block/zram/zram_drv.c
@@ -72,31 +72,6 @@ static void slot_lock_init(struct zram *zram, u32 index)
&__key, 0);
}
-/*
- * entry locking rules:
- *
- * 1) Lock is exclusive
- *
- * 2) lock() function can sleep waiting for the lock
- *
- * 3) Lock owner can sleep
- *
- * 4) Use TRY lock variant when in atomic context
- * - must check return value and handle locking failers
- */
-static __must_check bool slot_trylock(struct zram *zram, u32 index)
-{
- unsigned long *lock = &zram->table[index].__lock;
-
- if (!test_and_set_bit_lock(ZRAM_ENTRY_LOCK, lock)) {
- mutex_acquire(slot_dep_map(zram, index), 0, 1, _RET_IP_);
- lock_acquired(slot_dep_map(zram, index), _RET_IP_);
- return true;
- }
-
- return false;
-}
-
static void slot_lock(struct zram *zram, u32 index)
{
unsigned long *lock = &zram->table[index].__lock;
@@ -2798,23 +2773,6 @@ static void zram_submit_bio(struct bio *bio)
}
}
-static void zram_slot_free_notify(struct block_device *bdev,
- unsigned long index)
-{
- struct zram *zram;
-
- zram = bdev->bd_disk->private_data;
-
- atomic64_inc(&zram->stats.notify_free);
- if (!slot_trylock(zram, index)) {
- atomic64_inc(&zram->stats.miss_free);
- return;
- }
-
- slot_free(zram, index);
- slot_unlock(zram, index);
-}
-
static void zram_comp_params_reset(struct zram *zram)
{
u32 prio;
@@ -3058,6 +3016,50 @@ static void zram_swap_submit_write(struct swap_io_ctx *ctx)
swap_write_end(sio, failed);
}
+/*
+ * entry locking rules:
+ *
+ * 1) Lock is exclusive
+ *
+ * 2) lock() function can sleep waiting for the lock
+ *
+ * 3) Lock owner can sleep
+ *
+ * 4) Use TRY lock variant when in atomic context
+ * - must check return value and handle locking failers
+ */
+static __must_check bool slot_trylock(struct zram *zram, u32 index)
+{
+ unsigned long *lock = &zram->table[index].__lock;
+
+ if (!test_and_set_bit_lock(ZRAM_ENTRY_LOCK, lock)) {
+ mutex_acquire(slot_dep_map(zram, index), 0, 1, _RET_IP_);
+ lock_acquired(slot_dep_map(zram, index), _RET_IP_);
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * swap_range_free() holds the swap cluster lock. Use slot_trylock() so
+ * we never block on a slot that is already locked elsewhere.
+ */
+static void zram_swap_slot_free_notify(struct swap_info_struct *sis,
+ unsigned long index)
+{
+ struct zram *zram = sis->bdev->bd_disk->private_data;
+
+ atomic64_inc(&zram->stats.notify_free);
+ if (!slot_trylock(zram, index)) {
+ atomic64_inc(&zram->stats.miss_free);
+ return;
+ }
+
+ slot_free(zram, index);
+ slot_unlock(zram, index);
+}
+
/*
* No ->can_merge: block rules exist to grow bios on contiguous sectors and
* matching blkcg. zram already batches through swap_iocb, and
@@ -3068,6 +3070,7 @@ static void zram_swap_submit_write(struct swap_io_ctx *ctx)
static const struct swap_ops zram_swap_ops = {
.submit_read = zram_swap_submit_read,
.submit_write = zram_swap_submit_write,
+ .slot_free_notify = zram_swap_slot_free_notify,
};
#endif /* CONFIG_SWAP */
@@ -3075,7 +3078,6 @@ static const struct swap_ops zram_swap_ops = {
static const struct block_device_operations zram_devops = {
.open = zram_open,
.submit_bio = zram_submit_bio,
- .swap_slot_free_notify = zram_slot_free_notify,
.owner = THIS_MODULE
};
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 890128cdea1c..f861ceed39eb 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -1669,8 +1669,6 @@ struct block_device_operations {
int (*getgeo)(struct gendisk *, struct hd_geometry *);
int (*set_read_only)(struct block_device *bdev, bool ro);
void (*free_disk)(struct gendisk *disk);
- /* this callback is with swap_lock and sometimes page table lock held */
- void (*swap_slot_free_notify) (struct block_device *, unsigned long);
int (*report_zones)(struct gendisk *, sector_t sector,
unsigned int nr_zones,
struct blk_report_zones_args *args);
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 70bf6f3f04dc..09640eb5a45d 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -40,6 +40,11 @@ struct swap_io_ctx {
* the iocb is full or the plug is flushed.
* @submit_write: flush the accumulated write ctx to the backend.
* @submit_read: flush the accumulated read ctx to the backend.
+ * @slot_free_notify: optional callback invoked when a swap slot
+ * becomes free. swap_range_free() calls it with the
+ * swap cluster lock held. The folio lock may also be
+ * held on swap-cache teardown paths. Must not sleep
+ * or block.
*/
struct swap_ops {
unsigned int flags;
@@ -49,6 +54,8 @@ struct swap_ops {
size_t prev_folio_size, int rw);
void (*submit_write)(struct swap_io_ctx *ctx);
void (*submit_read)(struct swap_io_ctx *ctx);
+ void (*slot_free_notify)(struct swap_info_struct *sis,
+ unsigned long offset);
};
int swap_register_block_ops(const struct block_device_operations *fops,
diff --git a/mm/swapfile.c b/mm/swapfile.c
index ebdc96092961..79a4166fb9bf 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1311,21 +1311,18 @@ static void swap_range_free(struct swap_info_struct *si, unsigned long offset,
unsigned int nr_entries)
{
unsigned long end = offset + nr_entries - 1;
- void (*swap_slot_free_notify)(struct block_device *, unsigned long);
+ void (*slot_free_notify)(struct swap_info_struct *sis,
+ unsigned long offset);
unsigned int i;
for (i = 0; i < nr_entries; i++)
zswap_invalidate(swp_entry(si->type, offset + i));
- if (si->flags & SWP_BLKDEV)
- swap_slot_free_notify =
- si->bdev->bd_disk->fops->swap_slot_free_notify;
- else
- swap_slot_free_notify = NULL;
+ slot_free_notify = si->ops->slot_free_notify;
while (offset <= end) {
arch_swap_invalidate_page(si->type, offset);
- if (swap_slot_free_notify)
- swap_slot_free_notify(si->bdev, offset);
+ if (slot_free_notify)
+ slot_free_notify(si, offset);
offset++;
}
diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
index 912cb805caf5..25552d69f711 100644
--- a/rust/kernel/block/mq/gen_disk.rs
+++ b/rust/kernel/block/mq/gen_disk.rs
@@ -135,7 +135,6 @@ pub fn build<T: Operations>(
unlock_native_capacity: None,
getgeo: None,
set_read_only: None,
- swap_slot_free_notify: None,
report_zones: None,
devnode: None,
alternative_gpt_sector: None,
--
2.43.0