[PATCH v5 11/19] mm, swap: add CONFIG_XSWAP_DEFAULT_MAX

From: Baoquan He

Date: Mon Nov 09 2026 - 00:57:22 EST


From: Baoquan He <hebaoquan@xxxxxxxxxx>

Add a build-time default for the size of a new xswap device. The value
can be a percentage of RAM, a size in bytes, or "max". "max" means the
largest size the device can address. xswap.max= on the command line
overrides this default.

The build-time default is clamped to the device limit. An explicit
xswap.max= that is too large is refused.

If the build-time default cannot be parsed, or is a byte size below one
cluster, use 200% instead. A percentage default can still be below one
cluster after RAM is known. In that case, device creation fails.

Document the sysfs ABI, the stable id, and the synthetic /proc/swaps
name. Also document that zswap must stay enabled while xswap devices
exist.

Signed-off-by: Baoquan He <hebaoquan@xxxxxxxxxx>
---
.../ABI/testing/sysfs-kernel-mm-xswap | 38 +++++++++++++++++++
.../admin-guide/kernel-parameters.txt | 3 +-
Documentation/admin-guide/mm/zswap.rst | 9 +++++
MAINTAINERS | 1 +
mm/Kconfig | 11 ++++++
mm/swapfile.c | 22 +++++++++--
6 files changed, 79 insertions(+), 5 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-kernel-mm-xswap

diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-xswap b/Documentation/ABI/testing/sysfs-kernel-mm-xswap
new file mode 100644
index 000000000000..38eead0b1b31
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-kernel-mm-xswap
@@ -0,0 +1,38 @@
+What: /sys/kernel/mm/xswap/
+Date: October 2026
+Contact: Linux memory management mailing list <linux-mm@xxxxxxxxx>
+Description: File-less swap devices backed by zswap.
+
+ An xswap device has no backing file. Its slots are backed
+ by the zswap pool, and the device size is set at boot with
+ xswap.max= or CONFIG_XSWAP_DEFAULT_MAX. The size is fixed
+ for the lifetime of the device.
+
+ The name shown in /proc/swaps, xswap<id>, is synthetic and
+ is not a path. It cannot be passed to swapoff(2), so
+ swapoff -a reports an error for these entries. Use the
+ destroy attribute instead.
+
+ An xswap device requires zswap. Disabling zswap at runtime
+ (enabled=0, or max_pool_percent=0) while xswap devices
+ exist leaves them without backing: existing entries can
+ still be read, but new pages cannot be stored. Destroy the
+ xswap devices before disabling zswap.
+
+What: /sys/kernel/mm/xswap/create
+Date: October 2026
+Contact: Linux memory management mailing list <linux-mm@xxxxxxxxx>
+Description: Create an xswap device.
+
+ Write an optional priority (0..32767), or write nothing for
+ the default priority. The write fails if zswap is disabled
+ or if the configured size is smaller than one cluster.
+
+What: /sys/kernel/mm/xswap/destroy
+Date: October 2026
+Contact: Linux memory management mailing list <linux-mm@xxxxxxxxx>
+Description: Destroy an xswap device.
+
+ Write the device's stable id. The id is the number after
+ "xswap" in /proc/swaps, for example 1 for xswap1. It is
+ not a swap type and is never reused.
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index e92a1b9062a6..6e2d2747a26f 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -8743,5 +8743,6 @@ Kernel parameters
Maximum size of a new xswap device. The value is a
size in bytes with an optional KMGTPE suffix, a
percentage of RAM, or "max" for the largest size
- the device can address.
+ the device can address. The default is set by
+ CONFIG_XSWAP_DEFAULT_MAX.
Format: <size>[KMGTPE] | <percent>% | max
diff --git a/Documentation/admin-guide/mm/zswap.rst b/Documentation/admin-guide/mm/zswap.rst
index 2464425c783d..886b50ca90b1 100644
--- a/Documentation/admin-guide/mm/zswap.rst
+++ b/Documentation/admin-guide/mm/zswap.rst
@@ -136,3 +136,12 @@ selected.
A debugfs interface is provided for various statistic about pool size, number
of pages stored, same-value filled pages and various counters for the reasons
pages are rejected.
+
+xswap devices
+-------------
+
+An xswap device is a file-less swap device backed by zswap. While such a
+device exists, do not disable zswap at runtime (``enabled=0``) or set
+``max_pool_percent`` to 0. The device would have no backing: pages already
+in zswap can still be read, but new pages cannot be stored. Destroy the
+xswap devices first.
diff --git a/MAINTAINERS b/MAINTAINERS
index 0cf24eee20bb..66078761b746 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17910,6 +17910,7 @@ R: Youngjun Park <youngjun.park@xxxxxxx>
L: linux-mm@xxxxxxxxx
S: Maintained
F: Documentation/ABI/testing/sysfs-kernel-mm-swap
+F: Documentation/ABI/testing/sysfs-kernel-mm-xswap
F: Documentation/mm/swap-table.rst
F: include/linux/swap.h
F: include/linux/swap_ops.h
diff --git a/mm/Kconfig b/mm/Kconfig
index 9bdb905a66ef..902e3b1a6e35 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -131,6 +131,17 @@ config XSWAP
array is backed by a sparse vmalloc area that is mapped lazily
in chunks, avoiding static pre-allocation overhead.

+config XSWAP_DEFAULT_MAX
+ string "Default maximum size of a new xswap device"
+ depends on XSWAP
+ default "200%"
+ help
+ The maximum size of a newly created xswap device. It accepts a
+ percentage of RAM (for example "200%"), a size (for example "2T"),
+ or "max" for the largest size the device can address.
+
+ xswap.max= on the kernel command line overrides this value.
+
config ZSMALLOC
tristate

diff --git a/mm/swapfile.c b/mm/swapfile.c
index 498fbf6df1cd..cb53f9415de8 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -161,9 +161,12 @@ static bool xswap_size_max;
static bool xswap_size_set; /* xswap.max= was given */

/*
- * Parse "200%", "2T" or "max". @str is not modified.
+ * Parse "200%", "2T" or "max". @str is not modified. @cmdline tells an
+ * explicit xswap.max= from the build-time default, which is clamped
+ * instead of refused when it is too large.
*/
-static int __init xswap_parse_size(const char *str, const char *what)
+static int __init xswap_parse_size(const char *str, const char *what,
+ bool cmdline)
{
const char *pct;
char buf[16];
@@ -192,12 +195,16 @@ static int __init xswap_parse_size(const char *str, const char *what)
bytes = memparse(str, &end);
if (*end || bytes < PAGE_SIZE)
goto bad;
+ /* A default below one cluster would fail every create. */
+ if (!cmdline && bytes < (unsigned long)SWAPFILE_CLUSTER * PAGE_SIZE)
+ goto bad;
xswap_size_bytes = bytes;
xswap_size_pct = 0;
xswap_size_max = false;
}

- xswap_size_set = true;
+ if (cmdline)
+ xswap_size_set = true;
return 0;
bad:
pr_err("xswap: bad %s=%s\n", what, str);
@@ -206,7 +213,7 @@ static int __init xswap_parse_size(const char *str, const char *what)

static int __init xswap_max_setup(char *str)
{
- xswap_parse_size(str, "xswap.max");
+ xswap_parse_size(str, "xswap.max", true);
return 1;
}
__setup("xswap.max=", xswap_max_setup);
@@ -4830,6 +4837,13 @@ static int __init swapfile_init(void)
{
swapfile_maximum_size = arch_max_swapfile_size();

+#ifdef CONFIG_XSWAP
+ /* xswap.max= on the command line overrides the build-time default. */
+ if (!xswap_size_set)
+ xswap_parse_size(CONFIG_XSWAP_DEFAULT_MAX,
+ "CONFIG_XSWAP_DEFAULT_MAX", false);
+#endif
+
/*
* Once a cluster is freed, it's swap table content is read
* only, and all swap cache readers (swap_cache_*) verifies
--
2.54.0