[PATCH v2 05/12] mm, swap: add sysfs create interface for xswap
From: Baoquan He
Date: Sun Sep 13 2026 - 03:53:16 EST
xswap devices have no backing storage, so there is no file to swapon.
Add a sysfs interface to create them directly:
/sys/kernel/mm/xswap/create write an optional priority to create
a device (empty = DEF_SWAP_PRIO)
A new device is created with si->max and nr_clusters_max both equal to
full RAM: the cluster_info array is a sparse VM_SPARSE area mapped
lazily, so an idle device costs nothing. The runtime size can be
lowered per device afterwards via
/sys/kernel/mm/xswap/type<N>/limit (added later in this series).
The optional priority follows swapon(2)'s -p semantics (default
DEF_SWAP_PRIO, valid range -1..SWAP_FLAG_PRIO_MASK). The device shows
up in /proc/swaps as "xswap<N>".
Hibernation device discovery skips xswap devices: they have no bdev
to carry a resume image.
Signed-off-by: Baoquan He <hebaoquan@xxxxxxxxxx>
---
mm/swapfile.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 160 insertions(+), 5 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 5140b3f846b2..4861e2d49e36 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -16,6 +16,8 @@
#include <linux/kernel_stat.h>
#include <linux/swap.h>
#include <linux/vmalloc.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
#include <linux/pagemap.h>
#include <linux/namei.h>
#include <linux/shmem_fs.h>
@@ -48,6 +50,7 @@
#include "swap_table.h"
#include "internal.h"
#include "swap.h"
+#define DEF_SWAP_PRIO -1
#ifdef CONFIG_XSWAP
/*
@@ -66,7 +69,70 @@ static int xswap_map_clusters(struct swap_info_struct *si,
static void xswap_unmap_clusters(struct swap_info_struct *si,
unsigned long start_idx, unsigned long nr);
static int xswap_mapped_end(pte_t *pte, unsigned long addr, void *data);
-#endif
+
+#ifdef CONFIG_SYSFS
+static int xswap_create(int prio);
+
+static ssize_t xswap_create_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int prio = DEF_SWAP_PRIO;
+ int err;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ /* "[<prio>]" is optional; empty means DEF_SWAP_PRIO, i.e. the same
+ * default as swapon(2) without SWAP_FLAG_PREFER.
+ */
+ if (*skip_spaces(buf)) {
+ err = kstrtoint(buf, 10, &prio);
+ if (err)
+ return err;
+ }
+
+ err = xswap_create(prio);
+ if (err < 0)
+ return err;
+
+ return count;
+}
+
+static struct kobj_attribute xswap_create_attr = __ATTR(create, 0200, NULL,
+ xswap_create_store);
+
+static struct attribute *xswap_attrs[] = {
+ &xswap_create_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group xswap_attr_group = {
+ .attrs = xswap_attrs,
+};
+
+static struct kobject *xswap_kobj;
+
+static void xswap_sysfs_init(void)
+{
+ xswap_kobj = kobject_create_and_add("xswap", mm_kobj);
+ if (!xswap_kobj) {
+ pr_err("xswap: failed to create sysfs kobject\n");
+ return;
+ }
+ if (sysfs_create_group(xswap_kobj, &xswap_attr_group))
+ pr_err("xswap: failed to create sysfs group\n");
+}
+#else
+static inline void xswap_sysfs_init(void)
+{
+}
+#endif /* CONFIG_SYSFS */
+#else /* !CONFIG_XSWAP */
+static inline void xswap_sysfs_init(void)
+{
+}
+#endif /* CONFIG_XSWAP */
static void swap_range_alloc(struct swap_info_struct *si,
unsigned int nr_entries);
@@ -94,7 +160,6 @@ atomic_t nr_real_swapfiles;
EXPORT_SYMBOL_GPL(nr_swap_pages);
/* protected with swap_lock. reading in vm_swap_full() doesn't need lock */
long total_swap_pages;
-#define DEF_SWAP_PRIO -1
unsigned long swapfile_maximum_size;
#ifdef CONFIG_MIGRATION
bool swap_migration_ad_supported;
@@ -2255,6 +2320,9 @@ static int __find_hibernation_swap_type(dev_t device, sector_t offset)
if (!(sis->flags & SWP_WRITEOK))
continue;
+ /* xswap has no bdev to match a resume device */
+ if (sis->flags & SWP_XSWAP)
+ continue;
if (device == sis->bdev->bd_dev) {
struct swap_extent *se = first_se(sis);
@@ -2381,6 +2449,8 @@ int find_first_swap(dev_t *device)
if (!(sis->flags & SWP_WRITEOK))
continue;
+ if (sis->flags & SWP_XSWAP)
+ continue;
*device = sis->bdev->bd_dev;
spin_unlock(&swap_lock);
return type;
@@ -3313,7 +3383,7 @@ static void *swap_start(struct seq_file *swap, loff_t *pos)
return SEQ_START_TOKEN;
for (type = 0; (si = swap_type_to_info(type)); type++) {
- if (!(si->swap_file))
+ if (!(si->swap_file) && !(si->flags & SWP_XSWAP))
continue;
if (!--l)
return si;
@@ -3334,7 +3404,7 @@ static void *swap_next(struct seq_file *swap, void *v, loff_t *pos)
++(*pos);
for (; (si = swap_type_to_info(type)); type++) {
- if (!(si->swap_file))
+ if (!(si->swap_file) && !(si->flags & SWP_XSWAP))
continue;
return si;
}
@@ -3376,7 +3446,14 @@ static int swap_show(struct seq_file *swap, void *v)
inuse = K(swap_usage_in_pages(si));
file = si->swap_file;
- len = seq_file_path(swap, file, " \t\n\\");
+ if (file)
+ len = seq_file_path(swap, file, " \t\n\\");
+ else {
+ char name[16];
+
+ len = scnprintf(name, sizeof(name), "xswap%d", si->type);
+ seq_puts(swap, name);
+ }
seq_printf(swap, "%*s%s\t%lu\t%s%lu\t%s%d\n",
len < 40 ? 40 - len : 1, " ",
swap_type_str(si),
@@ -3926,6 +4003,82 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
return err;
}
+#ifdef CONFIG_XSWAP
+#ifdef CONFIG_SYSFS
+/* Create a file-less xswap device. si->max and the initial nr_clusters
+ * ceiling both equal full RAM; the runtime size can be lowered afterwards
+ * via /sys/kernel/mm/xswap/type<N>/limit.
+ */
+static int xswap_create(int prio)
+{
+ struct swap_info_struct *si;
+ unsigned long ram, maxpages;
+ int error;
+
+ if (prio != DEF_SWAP_PRIO && (prio < 0 || prio > SWAP_FLAG_PRIO_MASK))
+ return -EINVAL;
+
+ si = alloc_swap_info();
+ if (IS_ERR(si))
+ return PTR_ERR(si);
+
+ INIT_WORK(&si->discard_work, swap_discard_work);
+ INIT_WORK(&si->reclaim_work, swap_reclaim_work);
+
+ ram = totalram_pages();
+ maxpages = min_t(unsigned long, ram, swapfile_maximum_size);
+ if ((unsigned int)maxpages == 0)
+ maxpages = UINT_MAX;
+ /* Cluster-aligned, so no cluster holds a slot past si->max. */
+ if (maxpages > SWAPFILE_CLUSTER)
+ maxpages = rounddown(maxpages, SWAPFILE_CLUSTER);
+ if (maxpages < 2)
+ maxpages = 2;
+
+ si->bdev = NULL;
+ si->flags |= SWP_XSWAP | SWP_SOLIDSTATE;
+ si->max = maxpages;
+ si->pages = maxpages - 1;
+ /* no backing file: mirror the xswap branch of setup_swap_extents() */
+ si->ops = &swap_bdev_ops;
+
+ error = setup_swap_clusters_info(si, NULL, maxpages);
+ if (error)
+ goto bad_swap;
+
+ error = zswap_swapon(si->type, si->max);
+ if (error)
+ goto bad_swap;
+
+ mutex_lock(&swapon_mutex);
+ si->prio = prio;
+ si->list.prio = -si->prio;
+ si->avail_list.prio = -si->prio;
+ /* si->swap_file stays NULL: this is a file-less device */
+ enable_swap_info(si);
+ mutex_unlock(&swapon_mutex);
+
+ pr_info("xswap: adding extendable swap type %d (prio %d, %u pages, max %lu)\n",
+ si->type, prio, si->pages, maxpages);
+ atomic_inc(&proc_poll_event);
+ wake_up_interruptible(&proc_poll_wait);
+
+ return si->type;
+
+bad_swap:
+ kfree(si->global_cluster);
+ si->global_cluster = NULL;
+ destroy_swap_extents(si, NULL); /* safe: xswap never sets SWP_ACTIVATED */
+ free_swap_cluster_info(si);
+ si->cluster_info = NULL;
+ spin_lock(&swap_lock);
+ si->flags = 0;
+ spin_unlock(&swap_lock);
+ return error;
+}
+#endif /* CONFIG_SYSFS */
+#endif /* CONFIG_XSWAP */
+
SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
{
struct swap_info_struct *si;
@@ -4269,6 +4422,8 @@ static int __init swapfile_init(void)
swap_migration_ad_supported = true;
#endif /* CONFIG_MIGRATION */
+ xswap_sysfs_init();
+
return 0;
}
subsys_initcall(swapfile_init);
--
2.54.0