All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] fuse: add folio_max_order and folio_min_order controls in fusectl
@ 2026-07-03  9:28 Vadlakonda Swetha
  2026-07-06 21:10 ` Joanne Koong
  0 siblings, 1 reply; 3+ messages in thread
From: Vadlakonda Swetha @ 2026-07-03  9:28 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: fuse-devel, linux-fsdevel, linux-kernel, Vadlakonda Swetha

Expose `folio_max_order` and `folio_min_order` entries under the FUSE
control filesystem (`/sys/fs/fuse/connections/<connection_id>/`).

Motivation:
For fuse based systems like GCSFuse (Google Cloud Storage Fuse), large
folios gives a significant boost to the read performance.
- Without large folios, single file read perf is capped at 3GiB/s
- With large folios, we are able to achieve 12GiB/s, 1MB blocksize
- Writes:2GiB/s with large folios vs 1.6GiB/s without folios for 1MB bs

We understand that subfolio dirty page tracking is not available for
writeback cache with large folios. However this is not a blocker for
GCSFuse and similar cloud/network-based filesystems for the following
reasons:

1. We generally recommend applications to use larger io sizes (>1MB)
   for writes because of the latency involved with the network calls.
2. Random writes & small overwrites are also not recommended for cloud
   based filesystems.
3. Beneficial when write back cache is explicitly disabled. Incase of
   GCSFuse, write back cache is disabled for different other reasons.

Providing these controls allow individual filesystems to explicitly opt
into large folios once they evaluate their specific workload patterns.

Why sysfs (/sys/fs/fuse/connections/):
Exposing the folio order range via `fusectl` provides a simple interface
that avoids needing to bump the FUSE UAPI protocol version or update
libfuse, while allowing dynamic tuning of active FUSE mounts.

Testing:
- Verified folio order range assignment on regular inodes.
- Range validation checks (min > max and order > MAX_PAGECACHE_ORDER)
  correctly return -EINVAL.
- Added automated selftest to tools/testing/selftests/filesystems/fuse/.

Signed-off-by: Vadlakonda Swetha <swethav0411@gmail.com>
---
 fs/fuse/control.c                                         | 109 +++++++++++++-
 fs/fuse/fuse_i.h                                          |   6 +
 fs/fuse/inode.c                                           |  13 ++
 tools/testing/selftests/filesystems/fuse/fusectl_test.c  |  33 +++++
 4 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/fs/fuse/control.c b/fs/fuse/control.c
index 21ffde596..49144a04d 100644
--- a/fs/fuse/control.c
+++ b/fs/fuse/control.c
@@ -11,6 +11,7 @@
 #include <linux/module.h>
 #include <linux/fs_context.h>
 #include <linux/namei.h>
+#include <linux/pagemap.h>
 
 #define FUSE_CTL_SUPER_MAGIC 0x65735543
 
@@ -173,6 +174,94 @@ static ssize_t fuse_conn_congestion_threshold_write(struct file *file,
 	return ret;
 }
 
+static ssize_t fuse_conn_folio_max_order_read(struct file *file,
+					       char __user *buf, size_t len,
+					       loff_t *ppos)
+{
+	struct fuse_conn *fc;
+	unsigned int val;
+
+	fc = fuse_ctl_file_conn_get(file);
+	if (!fc)
+		return 0;
+
+	val = READ_ONCE(fc->folio_max_order);
+	fuse_conn_put(fc);
+
+	return fuse_conn_limit_read(file, buf, len, ppos, val);
+}
+
+static ssize_t fuse_conn_folio_max_order_write(struct file *file,
+						const char __user *buf,
+						size_t count, loff_t *ppos)
+{
+	unsigned int val = 0;
+	struct fuse_conn *fc;
+	ssize_t ret;
+
+	ret = fuse_conn_limit_write(file, buf, count, ppos, &val, MAX_PAGECACHE_ORDER);
+	if (ret <= 0)
+		goto out;
+
+	fc = fuse_ctl_file_conn_get(file);
+	if (!fc)
+		goto out;
+
+	if (val != 0 && val < READ_ONCE(fc->folio_min_order)) {
+		fuse_conn_put(fc);
+		return -EINVAL;
+	}
+
+	WRITE_ONCE(fc->folio_max_order, val);
+	fuse_conn_put(fc);
+out:
+	return ret;
+}
+
+static ssize_t fuse_conn_folio_min_order_read(struct file *file,
+					       char __user *buf, size_t len,
+					       loff_t *ppos)
+{
+	struct fuse_conn *fc;
+	unsigned int val;
+
+	fc = fuse_ctl_file_conn_get(file);
+	if (!fc)
+		return 0;
+
+	val = READ_ONCE(fc->folio_min_order);
+	fuse_conn_put(fc);
+
+	return fuse_conn_limit_read(file, buf, len, ppos, val);
+}
+
+static ssize_t fuse_conn_folio_min_order_write(struct file *file,
+						const char __user *buf,
+						size_t count, loff_t *ppos)
+{
+	unsigned int val = 0;
+	struct fuse_conn *fc;
+	ssize_t ret;
+
+	ret = fuse_conn_limit_write(file, buf, count, ppos, &val, MAX_PAGECACHE_ORDER);
+	if (ret <= 0)
+		goto out;
+
+	fc = fuse_ctl_file_conn_get(file);
+	if (!fc)
+		goto out;
+
+	if (READ_ONCE(fc->folio_max_order) != 0 && val > READ_ONCE(fc->folio_max_order)) {
+		fuse_conn_put(fc);
+		return -EINVAL;
+	}
+
+	WRITE_ONCE(fc->folio_min_order, val);
+	fuse_conn_put(fc);
+out:
+	return ret;
+}
+
 static const struct file_operations fuse_ctl_abort_ops = {
 	.open = nonseekable_open,
 	.write = fuse_conn_abort_write,
@@ -195,6 +284,18 @@ static const struct file_operations fuse_conn_congestion_threshold_ops = {
 	.write = fuse_conn_congestion_threshold_write,
 };
 
+static const struct file_operations fuse_conn_folio_max_order_ops = {
+	.open = nonseekable_open,
+	.read = fuse_conn_folio_max_order_read,
+	.write = fuse_conn_folio_max_order_write,
+};
+
+static const struct file_operations fuse_conn_folio_min_order_ops = {
+	.open = nonseekable_open,
+	.read = fuse_conn_folio_min_order_read,
+	.write = fuse_conn_folio_min_order_write,
+};
+
 static struct dentry *fuse_ctl_add_dentry(struct dentry *parent,
 					  struct fuse_conn *fc,
 					  const char *name, int mode,
@@ -267,7 +368,13 @@ int fuse_ctl_add_conn(struct fuse_conn *fc)
 				 NULL, &fuse_conn_max_background_ops) ||
 	    !fuse_ctl_add_dentry(parent, fc, "congestion_threshold",
 				 S_IFREG | 0600, NULL,
-				 &fuse_conn_congestion_threshold_ops))
+				 &fuse_conn_congestion_threshold_ops) ||
+	    !fuse_ctl_add_dentry(parent, fc, "folio_max_order",
+				 S_IFREG | 0600, NULL,
+				 &fuse_conn_folio_max_order_ops) ||
+	    !fuse_ctl_add_dentry(parent, fc, "folio_min_order",
+				 S_IFREG | 0600, NULL,
+				 &fuse_conn_folio_min_order_ops))
 		goto err;
 
 	return 0;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 85f738c53..e57f52931 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -450,6 +450,12 @@ struct fuse_conn {
 	/** @max_read: Maximum read size */
 	unsigned max_read;
 
+	/** @folio_max_order: Maximum folio order */
+	unsigned int folio_max_order;
+
+	/** @folio_min_order: Minimum folio order */
+	unsigned int folio_min_order;
+
 	/** @max_write: Maximum write size */
 	unsigned max_write;
 
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index d975073c6..c093a7d45 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -9,6 +9,7 @@
 
 #include <linux/dax.h>
 #include <linux/pagemap.h>
+#include <linux/log2.h>
 #include <linux/slab.h>
 #include <linux/file.h>
 #include <linux/seq_file.h>
@@ -413,6 +414,18 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr,
 	if (S_ISREG(inode->i_mode)) {
 		fuse_init_common(inode);
 		fuse_init_file_inode(inode, attr->flags);
+		if (fc->folio_max_order || fc->folio_min_order) {
+			unsigned int max_order;
+
+			max_order = fc->folio_max_order ?
+				    fc->folio_max_order : MAX_PAGECACHE_ORDER;
+			mapping_set_folio_order_range(inode->i_mapping,
+						      fc->folio_min_order,
+						      max_order);
+			pr_info("fuse: inode %llu folio order range set to min=%u, max=%u\n",
+				(unsigned long long)inode->i_ino,
+				fc->folio_min_order, max_order);
+		}
 	} else if (S_ISDIR(inode->i_mode))
 		fuse_init_dir(inode);
 	else if (S_ISLNK(inode->i_mode))
diff --git a/tools/testing/selftests/filesystems/fuse/fusectl_test.c b/tools/testing/selftests/filesystems/fuse/fusectl_test.c
index 0d1d012c3..56c849c3f 100644
--- a/tools/testing/selftests/filesystems/fuse/fusectl_test.c
+++ b/tools/testing/selftests/filesystems/fuse/fusectl_test.c
@@ -137,4 +137,37 @@ TEST_F(fusectl, abort)
 	ASSERT_EQ(errno, ENOTCONN);
 }
 
+TEST_F(fusectl, folio_orders)
+{
+	char max_path[PATH_MAX];
+	char min_path[PATH_MAX];
+	int fd;
+
+	sprintf(max_path, "/sys/fs/fuse/connections/%d/folio_max_order", self->connection);
+	sprintf(min_path, "/sys/fs/fuse/connections/%d/folio_min_order", self->connection);
+
+	/* 1. Verify sysfs files exist */
+	ASSERT_EQ(0, access(max_path, F_OK));
+	ASSERT_EQ(0, access(min_path, F_OK));
+
+	/* 2. Set valid folio_max_order = 8 */
+	write_file(_metadata, max_path, "8");
+	/* 3. Set valid folio_min_order = 4 */
+	write_file(_metadata, min_path, "4");
+
+	/* 4. Reject invalid write: min_order > max_order (e.g. min=10, max=8) */
+	fd = open(min_path, O_WRONLY);
+	ASSERT_GE(fd, 0);
+	ASSERT_LT(write(fd, "10", 2), 0);
+	ASSERT_EQ(errno, EINVAL);
+	close(fd);
+
+	/* 5. Reject invalid write: max_order < min_order (e.g. max=2, min=4) */
+	fd = open(max_path, O_WRONLY);
+	ASSERT_GE(fd, 0);
+	ASSERT_LT(write(fd, "2", 1), 0);
+	ASSERT_EQ(errno, EINVAL);
+	close(fd);
+}
+
 TEST_HARNESS_MAIN
-- 
2.45.2

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-07  1:06 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-03  9:28 [PATCH] fuse: add folio_max_order and folio_min_order controls in fusectl Vadlakonda Swetha
2026-07-06 21:10 ` Joanne Koong
2026-07-07  1:06   ` Swetha Vadlakonda

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.