summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-25 07:59:44 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-25 07:59:44 -0700
commit9cebfe6504488198b012e746bc6b313f88b95439 (patch)
tree8fc504cea938827aba411494141c266f448c39bd /fs
parentce14fe4cd756d2ad75a1f5b53816872b4e69f7cc (diff)
parent34b5c4a6e4fb9dbb3f9d87f3b0fb0372105c8302 (diff)
downloadlinux-9cebfe6504488198b012e746bc6b313f88b95439.tar.gz
linux-9cebfe6504488198b012e746bc6b313f88b95439.zip
Merge tag 'fuse-update-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse
Pull fuse updates from Miklos Szeredi: - Improve performance of the io-uring transport by introducing buffer pools and zero-copy (Joanne) - Fix lots of bugs (Baokun Li) - Fix io-uring initialization issues (Joanne, Bernd) - More prep work for large folios (Joanne) - Don't limit buffered read to 128k (Jim Harris) - Fix zeroing of page end (dirtied with mmap) on file size extension (Jimmy Zuber) - Improve performance in certain cases with wake_up_sync() when queuing request (Xuewen Yan) - Misc fixes and cleanups (Xuewen Yan) * tag 'fuse-update-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse: (35 commits) fuse: zero the partial EOF page when extending a file io_uring: Add missing include for ITER_SOURCE and ITER_DEST fuse: Fix the condition to enable over-io-uring fuse: invalidate the correct range after O_APPEND direct write selftests/fuse: test post-EOF page zeroing when a file is extended fuse: wake one waiter per freed slot when raising max_background fuse: use min_not_zero() in fuse_init_server_timeout() fuse: copy request headers via a stack buffer for io-uring fuse: give wakeup hints to the scheduler for synchronous requests fuse: check for NULL root inode in fuse_fill_super_submount fuse: reject a duplicate fd= mount option cuse: wait for pending RCU callbacks on module exit fuse: fix invalidate lock leak on open O_TRUNC DAX failure fuse: fix invalidate lock leak on setattr writeback failure fuse: wait for FR_FINISHED on abort_on_kill to prevent use-after-free fuse: make dentry_tree_work static docs: fuse: document io-uring buffer pool and zero-copy uapi fuse: add zero-copy over io-uring fuse: support registered buffer pools in io-uring fuse: add io-uring buffer pools ...
Diffstat (limited to 'fs')
-rw-r--r--fs/fuse/args.h2
-rw-r--r--fs/fuse/cuse.c8
-rw-r--r--fs/fuse/dev.c104
-rw-r--r--fs/fuse/dev.h2
-rw-r--r--fs/fuse/dev_uring.c633
-rw-r--r--fs/fuse/dev_uring_i.h56
-rw-r--r--fs/fuse/dir.c37
-rw-r--r--fs/fuse/file.c20
-rw-r--r--fs/fuse/fuse_dev_i.h5
-rw-r--r--fs/fuse/fuse_i.h2
-rw-r--r--fs/fuse/inode.c16
-rw-r--r--fs/fuse/readdir.c2
-rw-r--r--fs/fuse/req_timeout.c12
13 files changed, 734 insertions, 165 deletions
diff --git a/fs/fuse/args.h b/fs/fuse/args.h
index ecfe51a192af..5173264a1261 100644
--- a/fs/fuse/args.h
+++ b/fs/fuse/args.h
@@ -42,6 +42,8 @@ struct fuse_args {
bool is_pinned:1;
bool invalidate_vmap:1;
bool abort_on_kill:1;
+ /* server requested io-uring zero-copy for this op */
+ bool zero_copy:1;
struct fuse_in_arg in_args[4];
struct fuse_arg out_args[2];
void (*end)(struct fuse_args *args, int error);
diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
index 3c15b5ba16d7..4079cf8e5974 100644
--- a/fs/fuse/cuse.c
+++ b/fs/fuse/cuse.c
@@ -530,7 +530,8 @@ static int cuse_channel_open(struct inode *inode, struct file *file)
INIT_LIST_HEAD(&cc->list);
- cc->fc.chan->initialized = 1;
+ /* Pairs with smp_load_acquire() readers of fch->initialized */
+ smp_store_release(&cc->fc.chan->initialized, 1);
rc = cuse_send_init(cc);
if (rc) {
fuse_dev_put(fud);
@@ -653,6 +654,11 @@ static void __exit cuse_exit(void)
{
misc_deregister(&cuse_miscdev);
class_destroy(cuse_class);
+ /*
+ * Wait for pending call_rcu() callbacks that call back into
+ * this module via fc->release (cuse_fc_release).
+ */
+ rcu_barrier();
}
module_init(cuse_init);
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 5763a7cd3b37..4fec31fc0b84 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -75,17 +75,23 @@ void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *pa
fch->minor = param->minor;
fch->max_write = param->max_write;
fch->max_pages = param->max_pages;
+
+ if (param->io_uring_enabled)
+ fuse_uring_conn_init(fch);
}
- /* Make sure stores before this are seen on another CPU */
- smp_wmb();
- fch->initialized = 1;
+ /* Pairs with smp_load_acquire() readers of fch->initialized */
+ smp_store_release(&fch->initialized, 1);
wake_up_all(&fch->blocked_waitq);
}
static bool fuse_block_alloc(struct fuse_chan *fch, bool for_background)
{
- return !fch->initialized || (for_background && fch->blocked) ||
+ /* Pairs with smp_store_release() in fuse_chan_set_initialized() */
+ if (!smp_load_acquire(&fch->initialized))
+ return true;
+
+ return (for_background && fch->blocked) ||
(fch->io_uring && fch->connected && !fuse_uring_ready(fch));
}
@@ -120,9 +126,6 @@ static struct fuse_req *fuse_get_req(struct fuse_chan *fch, bool for_background)
goto out;
}
- /* Matches smp_wmb() in fuse_chan_set_initialized() */
- smp_rmb();
-
err = -ENOTCONN;
if (!fch->connected)
goto out;
@@ -210,10 +213,13 @@ EXPORT_SYMBOL_GPL(fuse_req_hash);
/*
* A new request is available, wake fiq->waitq
*/
-static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq)
+static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq, bool sync)
__releases(fiq->lock)
{
- wake_up(&fiq->waitq);
+ if (sync)
+ wake_up_sync(&fiq->waitq);
+ else
+ wake_up(&fiq->waitq);
kill_fasync(&fiq->fasync, SIGIO, POLL_IN);
spin_unlock(&fiq->lock);
}
@@ -230,7 +236,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq,
if (fiq->connected) {
fiq->forget_list_tail->next = forget;
fiq->forget_list_tail = forget;
- fuse_dev_wake_and_unlock(fiq);
+ fuse_dev_wake_and_unlock(fiq, false);
} else {
kfree(forget);
spin_unlock(&fiq->lock);
@@ -240,7 +246,8 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq,
void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req)
{
spin_lock(&fiq->lock);
- if (list_empty(&req->intr_entry)) {
+ /* Repeat FR_SENT test after obtaining the lock to prevent race with fuse_resend() */
+ if (list_empty(&req->intr_entry) && test_bit(FR_SENT, &req->flags)) {
list_add_tail(&req->intr_entry, &fiq->interrupts);
/*
* Pairs with smp_mb() implied by test_and_set_bit()
@@ -251,7 +258,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req)
list_del_init(&req->intr_entry);
spin_unlock(&fiq->lock);
} else {
- fuse_dev_wake_and_unlock(fiq);
+ fuse_dev_wake_and_unlock(fiq, false);
}
} else {
spin_unlock(&fiq->lock);
@@ -281,11 +288,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique);
static void fuse_dev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req)
{
+ bool sync = test_and_clear_bit(FR_SYNC_WAKEUP, &req->flags);
+
spin_lock(&fiq->lock);
if (fiq->connected) {
fuse_request_assign_unique_locked(fiq, req);
list_add_tail(&req->list, &fiq->pending);
- fuse_dev_wake_and_unlock(fiq);
+ fuse_dev_wake_and_unlock(fiq, sync);
} else {
spin_unlock(&fiq->lock);
req->out.h.error = -ENOTCONN;
@@ -397,7 +406,8 @@ void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val)
fch->max_background = val;
fch->blocked = fch->num_background >= fch->max_background;
if (!fch->blocked)
- wake_up(&fch->blocked_waitq);
+ wake_up_nr(&fch->blocked_waitq,
+ fch->max_background - fch->num_background);
spin_unlock(&fch->bg_lock);
}
@@ -411,11 +421,6 @@ void fuse_chan_set_fc(struct fuse_chan *fch, struct fuse_conn *fc)
fch->conn = fc;
}
-void fuse_chan_io_uring_enable(struct fuse_chan *fch)
-{
- fch->io_uring = 1;
-}
-
void fuse_pqueue_init(struct fuse_pqueue *fpq)
{
spin_lock_init(&fpq->lock);
@@ -725,7 +730,7 @@ static void request_wait_answer(struct fuse_req *req)
if (req->args->abort_on_kill) {
fuse_chan_abort(fch, false);
- return;
+ goto wait_for_finish;
}
if (test_bit(FR_URING, &req->flags))
@@ -736,6 +741,7 @@ static void request_wait_answer(struct fuse_req *req)
return;
}
+wait_for_finish:
/*
* Either request is already in userspace, or it was forced.
* Wait it out.
@@ -752,6 +758,11 @@ static void __fuse_request_send(struct fuse_req *req)
/* acquire extra reference, since request is still needed after
fuse_request_end() */
__fuse_get_request(req);
+ /*
+ * This is a synchronous request: the caller will block waiting for
+ * the answer. Hint the scheduler via wake_up_sync().
+ */
+ set_bit(FR_SYNC_WAKEUP, &req->flags);
fuse_send_one(fiq, req);
request_wait_answer(req);
@@ -1249,11 +1260,25 @@ int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop,
if (folio) {
size = folio_size(folio);
- if (zeroing && count < size)
- folio_zero_range(folio, 0, size);
+ if (zeroing && count < size) {
+ /*
+ * When the copy is skipped the folio already holds the
+ * payload, so only the bytes outside [offset, offset +
+ * count) may be zeroed.
+ *
+ * Otherwise, the whole folio is cleared first so that a
+ * failed copy leaves zeros rather than stale folio
+ * contents.
+ */
+ if (cs->skip_folio_copy)
+ folio_zero_segments(folio, 0, offset,
+ offset + count, size);
+ else
+ folio_zero_range(folio, 0, size);
+ }
}
- while (count) {
+ while (!cs->skip_folio_copy && count) {
if (cs->write && cs->pipebufs && folio) {
/*
* Can't control lifetime of pipe buffers, so always
@@ -1346,6 +1371,10 @@ int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs,
for (i = 0; !err && i < numargs; i++) {
struct fuse_arg *arg = &args[i];
if (i == numargs - 1 && argpages)
+ /*
+ * if cs->skip_folio_copy is set, this just does any
+ * needed zeroing. No copying is involved.
+ */
err = fuse_copy_folios(cs, arg->size, zeroing);
else
err = fuse_copy_one(cs, arg->value, arg->size);
@@ -1760,7 +1789,7 @@ out:
void fuse_chan_resend(struct fuse_chan *fch)
{
struct fuse_dev *fud;
- struct fuse_req *req, *next;
+ struct fuse_req *req;
struct fuse_iqueue *fiq = &fch->iq;
LIST_HEAD(to_queue);
unsigned int i;
@@ -1775,24 +1804,20 @@ void fuse_chan_resend(struct fuse_chan *fch)
struct fuse_pqueue *fpq = &fud->pq;
spin_lock(&fpq->lock);
- for (i = 0; i < FUSE_PQ_HASH_SIZE; i++)
- list_splice_tail_init(&fpq->processing[i], &to_queue);
+ for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) {
+ struct list_head *this_queue = &fpq->processing[i];
+
+ list_for_each_entry(req, this_queue, list)
+ clear_bit(FR_SENT, &req->flags);
+ list_splice_tail_init(this_queue, &to_queue);
+ }
spin_unlock(&fpq->lock);
}
spin_unlock(&fch->lock);
- list_for_each_entry_safe(req, next, &to_queue, list) {
- set_bit(FR_PENDING, &req->flags);
- clear_bit(FR_SENT, &req->flags);
- /* mark the request as resend request */
- req->in.h.unique |= FUSE_UNIQUE_RESEND;
- }
-
spin_lock(&fiq->lock);
if (!fiq->connected) {
spin_unlock(&fiq->lock);
- list_for_each_entry(req, &to_queue, list)
- clear_bit(FR_PENDING, &req->flags);
fuse_dev_end_requests(&to_queue);
return;
}
@@ -1801,12 +1826,16 @@ void fuse_chan_resend(struct fuse_chan *fch)
* intr_entry on fiq->interrupts after the request is re-queued.
*/
list_for_each_entry(req, &to_queue, list) {
+ set_bit(FR_PENDING, &req->flags);
+ /* mark the request as resend request */
+ req->in.h.unique |= FUSE_UNIQUE_RESEND;
+
if (test_bit(FR_INTERRUPTED, &req->flags))
list_del_init(&req->intr_entry);
}
/* iq and pq requests are both oldest to newest */
list_splice(&to_queue, &fiq->pending);
- fuse_dev_wake_and_unlock(fiq);
+ fuse_dev_wake_and_unlock(fiq, false);
}
/* Look up request on processing list by unique ID */
@@ -1888,7 +1917,8 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud,
* initialized and connected state
*/
err = -EINVAL;
- if (!fch->initialized || !fch->connected)
+ /* Pairs with smp_store_release() in fuse_chan_set_initialized() */
+ if (!smp_load_acquire(&fch->initialized) || !fch->connected)
goto copy_finish;
/* Don't try to move folios (yet) */
diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h
index aed69fd14c41..8d25378c0918 100644
--- a/fs/fuse/dev.h
+++ b/fs/fuse/dev.h
@@ -22,6 +22,7 @@ struct fuse_chan_param {
unsigned int minor;
unsigned int max_write;
unsigned int max_pages;
+ bool io_uring_enabled;
};
struct fuse_chan *fuse_chan_new(void);
@@ -34,7 +35,6 @@ void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val);
unsigned int fuse_chan_num_waiting(struct fuse_chan *fch);
void fuse_chan_set_fc(struct fuse_chan *fch, struct fuse_conn *fc);
void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *param);
-void fuse_chan_io_uring_enable(struct fuse_chan *fch);
ssize_t fuse_chan_send(struct fuse_chan *fch, struct fuse_args *args);
int fuse_chan_send_bg(struct fuse_chan *fch, struct fuse_args *args, gfp_t gfp_flags);
int fuse_chan_send_notify_reply(struct fuse_chan *fch, struct fuse_args *args, u64 unique);
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 77c8cec43d9c..c6dd420c4034 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -9,6 +9,7 @@
#include "dev_uring_i.h"
#include "fuse_trace.h"
+#include <linux/bitmap.h>
#include <linux/fs.h>
#include <linux/io_uring/cmd.h>
@@ -21,6 +22,8 @@ MODULE_PARM_DESC(enable_uring,
#define FUSE_URING_IOV_HEADERS 0
#define FUSE_URING_IOV_PAYLOAD 1
+#define FUSE_URING_ADD_QUEUE_FLAGS (FUSE_URING_ZERO_COPY)
+
bool fuse_uring_enabled(void)
{
return enable_uring;
@@ -30,6 +33,11 @@ struct fuse_uring_pdu {
struct fuse_ring_ent *ent;
};
+struct fuse_zero_copy_bvs {
+ unsigned int nr_bvs;
+ struct bio_vec bvs[];
+};
+
static const struct fuse_iqueue_ops fuse_io_uring_ops;
enum fuse_uring_header_type {
@@ -41,6 +49,32 @@ enum fuse_uring_header_type {
FUSE_URING_HEADER_RING_ENT,
};
+static inline bool bufpool_enabled(struct fuse_ring_queue *queue)
+{
+ return queue->payload_mode == FUSE_PAYLOAD_BUFPOOL;
+}
+
+static inline bool bufpool_registered(struct fuse_ring_queue *queue)
+{
+ return queue->bufpool && queue->bufpool->registered;
+}
+
+/*
+ * For a registered bufpool, every sqe that drives a payload import (REGISTER,
+ * COMMIT_AND_FETCH) must carry the registered buffer index of the pool.
+ * This also must be called from the command's issue handler, where cmd->sqe is
+ * still valid
+ */
+static inline bool fuse_uring_cmd_index_ok(struct io_uring_cmd *cmd,
+ struct fuse_ring_queue *queue)
+{
+ if (!bufpool_registered(queue))
+ return true;
+
+ return (cmd->flags & IORING_URING_CMD_FIXED) &&
+ READ_ONCE(cmd->sqe->buf_index) == queue->bufpool->registered_index;
+}
+
static void uring_cmd_set_ring_ent(struct io_uring_cmd *cmd,
struct fuse_ring_ent *ring_ent)
{
@@ -86,8 +120,36 @@ static void fuse_uring_flush_bg(struct fuse_ring_queue *queue)
}
}
+static bool can_zero_copy_req(struct fuse_ring_ent *ent, struct fuse_req *req)
+{
+ struct fuse_args *args = req->args;
+
+ if (!ent->queue->zero_copy || !args->zero_copy)
+ return false;
+
+ if (args->opcode != FUSE_READ && args->opcode != FUSE_WRITE)
+ return false;
+
+ return args->in_pages || args->out_pages;
+}
+
+static void zero_copy_unregister(struct io_uring_cmd *cmd,
+ struct fuse_ring_ent *ent,
+ unsigned int issue_flags)
+{
+ if (ent->zero_copied) {
+ int err = io_buffer_unregister(cmd, ent->zero_copy_index,
+ issue_flags);
+
+ if (err)
+ pr_warn_ratelimited("qid=%d zero-copy unregister failed: %d\n",
+ ent->queue->qid, err);
+ ent->zero_copied = false;
+ }
+}
+
static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req,
- int error)
+ int error, unsigned int issue_flags)
{
struct fuse_ring_queue *queue = ent->queue;
struct fuse_ring *ring = queue->ring;
@@ -107,6 +169,8 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req,
spin_unlock(&queue->lock);
+ zero_copy_unregister(ent->cmd, ent, issue_flags);
+
if (error)
req->out.h.error = error;
@@ -204,7 +268,7 @@ void fuse_uring_destruct(struct fuse_chan *fch)
return;
for (qid = 0; qid < ring->nr_queues; qid++) {
- struct fuse_ring_queue *queue = ring->queues[qid];
+ struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);
struct fuse_ring_ent *ent, *next;
if (!queue)
@@ -222,8 +286,9 @@ void fuse_uring_destruct(struct fuse_chan *fch)
}
kfree(queue->fpq.processing);
+ kfree(queue->bufpool);
kfree(queue);
- ring->queues[qid] = NULL;
+ WRITE_ONCE(ring->queues[qid], NULL);
}
kfree(ring->queues);
@@ -238,7 +303,6 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch)
{
struct fuse_ring *ring;
size_t nr_queues = num_possible_cpus();
- struct fuse_ring *res = NULL;
size_t max_payload_size;
ring = kzalloc_obj(*ring, GFP_KERNEL_ACCOUNT);
@@ -258,12 +322,6 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch)
spin_unlock(&fch->lock);
goto out_err;
}
- if (fch->ring) {
- /* race, another thread created the ring in the meantime */
- spin_unlock(&fch->lock);
- res = fch->ring;
- goto out_err;
- }
init_waitqueue_head(&ring->stop_waitq);
@@ -278,11 +336,18 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch)
out_err:
kfree(ring->queues);
kfree(ring);
- return res;
+ return NULL;
+}
+
+void fuse_uring_conn_init(struct fuse_chan *fch)
+{
+ if (fuse_uring_create(fch))
+ fch->io_uring = 1;
}
static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
- int qid)
+ int qid, bool zero_copy,
+ bool fail_if_exists)
{
struct fuse_chan *fch = ring->chan;
struct fuse_ring_queue *queue;
@@ -290,16 +355,17 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
queue = kzalloc_obj(*queue, GFP_KERNEL_ACCOUNT);
if (!queue)
- return NULL;
+ return ERR_PTR(-ENOMEM);
pq = fuse_pqueue_alloc();
if (!pq) {
kfree(queue);
- return NULL;
+ return ERR_PTR(-ENOMEM);
}
queue->qid = qid;
queue->ring = ring;
spin_lock_init(&queue->lock);
+ queue->zero_copy = zero_copy;
INIT_LIST_HEAD(&queue->ent_avail_queue);
INIT_LIST_HEAD(&queue->ent_commit_queue);
@@ -316,14 +382,17 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
if (ring->queues[qid]) {
spin_unlock(&fch->lock);
kfree(queue->fpq.processing);
+ kfree(queue->bufpool);
kfree(queue);
- return ring->queues[qid];
+ return fail_if_exists ? ERR_PTR(-EEXIST) : ring->queues[qid];
}
/*
- * write_once and lock as the caller mostly doesn't take the lock at all
+ * fch->lock serializes concurrent creators for this qid.
+ * smp_store_release() are for the lockless readers who must see a
+ * fully initialized queue after &ring->queues[qid] is set
*/
- WRITE_ONCE(ring->queues[qid], queue);
+ smp_store_release(&ring->queues[qid], queue);
spin_unlock(&fch->lock);
return queue;
@@ -434,7 +503,7 @@ static void fuse_uring_log_ent_state(struct fuse_ring *ring)
struct fuse_ring_ent *ent;
for (qid = 0; qid < ring->nr_queues; qid++) {
- struct fuse_ring_queue *queue = ring->queues[qid];
+ struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);
if (!queue)
continue;
@@ -643,30 +712,57 @@ static int copy_header_from_ring(struct fuse_ring_ent *ent,
return 0;
}
+static int fuse_uring_import_payload(struct fuse_ring_ent *ent, int dir,
+ struct iov_iter *iter,
+ unsigned int issue_flags)
+{
+ void __user *base = ent->payload.iov_base;
+ size_t len = ent->payload.iov_len;
+ int err = 0;
+
+ if (!base) {
+ memset(iter, 0, sizeof(*iter));
+ return 0;
+ }
+
+ if (bufpool_registered(ent->queue))
+ err = io_uring_cmd_import_fixed((u64)(uintptr_t)base, len, dir,
+ iter, ent->cmd, issue_flags);
+ else
+ err = import_ubuf(dir, base, len, iter);
+
+ if (err)
+ pr_info_ratelimited("fuse: Import of user buffer failed\n");
+
+ return err;
+}
+
static int setup_fuse_copy_state(struct fuse_copy_state *cs,
- struct fuse_ring *ring, struct fuse_req *req,
+ struct fuse_req *req,
struct fuse_ring_ent *ent, int dir,
- struct iov_iter *iter)
+ struct iov_iter *iter,
+ unsigned int issue_flags)
{
int err;
- err = import_ubuf(dir, ent->payload, ring->max_payload_sz, iter);
- if (err) {
- pr_info_ratelimited("fuse: Import of user buffer failed\n");
+ err = fuse_uring_import_payload(ent, dir, iter, issue_flags);
+ if (err)
return err;
- }
fuse_copy_init(cs, dir == ITER_DEST, iter);
+ if (ent->zero_copied)
+ cs->skip_folio_copy = true;
+
cs->is_uring = true;
cs->req = req;
return 0;
}
-static int fuse_uring_copy_from_ring(struct fuse_ring *ring,
- struct fuse_req *req,
- struct fuse_ring_ent *ent)
+static int fuse_uring_copy_from_ring(struct fuse_req *req,
+ struct fuse_ring_ent *ent,
+ unsigned int issue_flags)
{
struct fuse_copy_state cs;
struct fuse_args *args = req->args;
@@ -679,7 +775,8 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring,
if (err)
return err;
- err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_SOURCE, &iter);
+ err = setup_fuse_copy_state(&cs, req, ent, ITER_SOURCE, &iter,
+ issue_flags);
if (err)
return err;
@@ -688,11 +785,68 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring,
return err;
}
+static void fuse_zero_copy_release(void *priv)
+{
+ struct fuse_zero_copy_bvs *zc_bvs = priv;
+ unsigned int i;
+
+ for (i = 0; i < zc_bvs->nr_bvs; i++)
+ folio_put(page_folio(zc_bvs->bvs[i].bv_page));
+
+ kvfree(zc_bvs);
+}
+
+static int fuse_uring_set_up_zero_copy(struct fuse_ring_ent *ent,
+ struct fuse_req *req,
+ unsigned int issue_flags)
+{
+ struct fuse_args_pages *ap;
+ int err, i, ddir = 0;
+ struct fuse_zero_copy_bvs *zc_bvs;
+ struct bio_vec *bvs;
+
+ /* out_pages indicates a read, in_pages indicates a write */
+ if (req->args->out_pages)
+ ddir |= IO_BUF_DEST;
+ if (req->args->in_pages)
+ ddir |= IO_BUF_SOURCE;
+
+ ap = container_of(req->args, typeof(*ap), args);
+
+ zc_bvs = kvmalloc_flex(*zc_bvs, bvs, ap->num_folios,
+ GFP_KERNEL_ACCOUNT);
+ if (!zc_bvs)
+ return -ENOMEM;
+
+ zc_bvs->nr_bvs = ap->num_folios;
+ bvs = zc_bvs->bvs;
+ for (i = 0; i < ap->num_folios; i++) {
+ bvs[i].bv_page = folio_page(ap->folios[i], 0);
+ bvs[i].bv_offset = ap->descs[i].offset;
+ bvs[i].bv_len = ap->descs[i].length;
+ folio_get(ap->folios[i]);
+ }
+
+ err = io_buffer_register_bvec(ent->cmd, bvs, ap->num_folios,
+ fuse_zero_copy_release, zc_bvs,
+ ddir, ent->zero_copy_index,
+ issue_flags);
+ if (err) {
+ fuse_zero_copy_release(zc_bvs);
+ return err;
+ }
+
+ ent->zero_copied = true;
+
+ return 0;
+}
+
/*
* Copy data from the req to the ring buffer
*/
-static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req,
- struct fuse_ring_ent *ent)
+static int fuse_uring_args_to_ring(struct fuse_req *req,
+ struct fuse_ring_ent *ent,
+ unsigned int issue_flags)
{
struct fuse_copy_state cs;
struct fuse_args *args = req->args;
@@ -705,7 +859,15 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req,
.commit_id = req->in.h.unique,
};
- err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_DEST, &iter);
+ if (can_zero_copy_req(ent, req)) {
+ ent_in_out.flags |= FUSE_URING_ENT_ZERO_COPY;
+ err = fuse_uring_set_up_zero_copy(ent, req, issue_flags);
+ if (err)
+ return err;
+ }
+
+ err = setup_fuse_copy_state(&cs, req, ent, ITER_DEST, &iter,
+ issue_flags);
if (err)
return err;
@@ -735,15 +897,32 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req,
}
ent_in_out.payload_sz = cs.ring.copied_sz;
+ /*
+ * on a zero-copied write the pages are registered for the server to
+ * read via a fixed-buffer op rather than copied into the payload
+ * buffer, so copied_sz does not account for it. The server still needs
+ * the total inbound size to know how many bytes to read from the
+ * registered buffer, so add the page arg (always the last in-arg) back
+ * in
+ */
+ if (cs.skip_folio_copy && args->in_pages)
+ ent_in_out.payload_sz +=
+ args->in_args[args->in_numargs - 1].size;
+
+ if (bufpool_enabled(ent->queue) && ent->payload.iov_base)
+ ent_in_out.offset =
+ (uintptr_t)ent->payload.iov_base - ent->queue->bufpool->base_uaddr;
+
return copy_header_to_ring(ent, FUSE_URING_HEADER_RING_ENT,
&ent_in_out, sizeof(ent_in_out));
}
static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent,
- struct fuse_req *req)
+ struct fuse_req *req,
+ unsigned int issue_flags)
{
struct fuse_ring_queue *queue = ent->queue;
- struct fuse_ring *ring = queue->ring;
+ struct fuse_in_header in_header;
int err;
err = -EIO;
@@ -758,23 +937,124 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent,
return err;
/* copy the request */
- err = fuse_uring_args_to_ring(ring, req, ent);
+ err = fuse_uring_args_to_ring(req, ent, issue_flags);
if (unlikely(err)) {
pr_info_ratelimited("Copy to ring failed: %d\n", err);
return err;
}
/* copy fuse_in_header */
- return copy_header_to_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->in.h,
- sizeof(req->in.h));
+ in_header = req->in.h;
+ return copy_header_to_ring(ent, FUSE_URING_HEADER_IN_OUT, &in_header,
+ sizeof(in_header));
+}
+
+static bool fuse_uring_req_has_copyable_payload(struct fuse_ring_ent *ent,
+ struct fuse_req *req)
+{
+ struct fuse_args *args = req->args;
+
+ if (!can_zero_copy_req(ent, req))
+ return args->in_numargs > 1 || args->out_numargs;
+
+ /*
+ * the asymmetry between in_numargs > 2 and out_numargs > 1 is because
+ * the per-op header is extracted before fuse_copy_args() for inargs but
+ * not for outargs
+ */
+ if ((args->in_numargs > 1) && (!args->in_pages || args->in_numargs > 2))
+ return true;
+ if (args->out_numargs && (!args->out_pages || args->out_numargs > 1))
+ return true;
+
+ return false;
+}
+
+static int fuse_uring_select_buffer(struct fuse_ring_ent *ent)
+{
+ struct fuse_ring_queue *queue = ent->queue;
+ struct fuse_bufpool *pool = queue->bufpool;
+ unsigned int id;
+
+ lockdep_assert_held(&queue->lock);
+
+ id = find_first_bit(pool->free_map, pool->nr_bufs);
+ if (id >= pool->nr_bufs)
+ return -ENOBUFS;
+
+ WARN_ON_ONCE(ent->payload.iov_base);
+ __clear_bit(id, pool->free_map);
+
+ ent->buf_id = id;
+ ent->payload.iov_base =
+ (void __user *)(pool->base_uaddr + id * pool->buf_size);
+ ent->payload.iov_len = pool->buf_size;
+
+ return 0;
+}
+
+static void fuse_uring_recycle_buffer(struct fuse_ring_ent *ent)
+{
+ struct iovec *ent_payload = &ent->payload;
+ struct fuse_ring_queue *queue = ent->queue;
+ struct fuse_bufpool *pool;
+
+ lockdep_assert_held(&queue->lock);
+
+ if (!bufpool_enabled(queue) || !ent_payload->iov_base)
+ return;
+
+ pool = queue->bufpool;
+
+ /* a buffer should never be recycled twice */
+ WARN_ON_ONCE(test_bit(ent->buf_id, pool->free_map));
+ __set_bit(ent->buf_id, pool->free_map);
+
+ memset(ent_payload, 0, sizeof(*ent_payload));
+ ent->buf_id = 0;
+}
+
+static int fuse_uring_next_req_update_buffer(struct fuse_ring_ent *ent,
+ struct fuse_req *req)
+{
+ bool buffer_selected;
+ bool has_payload;
+
+ if (!bufpool_enabled(ent->queue))
+ return 0;
+
+ buffer_selected = !!ent->payload.iov_base;
+ has_payload = fuse_uring_req_has_copyable_payload(ent, req);
+
+ if (has_payload && !buffer_selected)
+ return fuse_uring_select_buffer(ent);
+
+ if (!has_payload && buffer_selected)
+ fuse_uring_recycle_buffer(ent);
+
+ return 0;
+}
+
+static int fuse_uring_prep_buffer(struct fuse_ring_ent *ent,
+ struct fuse_req *req)
+{
+ if (!bufpool_enabled(ent->queue))
+ return 0;
+
+ /* no payload to copy, can skip selecting a buffer */
+ if (!fuse_uring_req_has_copyable_payload(ent, req))
+ return 0;
+
+ return fuse_uring_select_buffer(ent);
}
static int fuse_uring_prepare_send(struct fuse_ring_ent *ent,
- struct fuse_req *req)
+ struct fuse_req *req,
+ unsigned int issue_flags)
{
int err;
- err = fuse_uring_copy_to_ring(ent, req);
+ err = fuse_uring_copy_to_ring(ent, req, issue_flags);
if (!err) {
set_bit(FR_SENT, &req->flags);
trace_fuse_request_sent(req);
@@ -788,7 +1068,7 @@ static int fuse_uring_prepare_send(struct fuse_ring_ent *ent,
ent->state = FRRS_INVALID;
spin_unlock(&ent->queue->lock);
- fuse_uring_req_end(ent, req, err);
+ fuse_uring_req_end(ent, req, err, issue_flags);
}
return err;
@@ -856,9 +1136,12 @@ static struct fuse_req *fuse_uring_ent_assign_req(struct fuse_ring_ent *ent)
/* get and assign the next entry while it is still holding the lock */
req = list_first_entry_or_null(req_queue, struct fuse_req, list);
- if (req)
- fuse_uring_add_req_to_ring_ent(ent, req);
+ if (!req || fuse_uring_next_req_update_buffer(ent, req)) {
+ fuse_uring_recycle_buffer(ent);
+ return NULL;
+ }
+ fuse_uring_add_req_to_ring_ent(ent, req);
return req;
}
@@ -870,12 +1153,13 @@ static struct fuse_req *fuse_uring_ent_assign_req(struct fuse_ring_ent *ent)
static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req,
unsigned int issue_flags)
{
- struct fuse_ring *ring = ent->queue->ring;
+ struct fuse_out_header out_header;
ssize_t err = -EFAULT;
- if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->out.h,
- sizeof(req->out.h)))
+ if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &out_header,
+ sizeof(out_header)))
goto out;
+ req->out.h = out_header;
err = fuse_uring_out_header_has_err(&req->out.h, req);
if (err) {
@@ -883,9 +1167,9 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req,
goto out;
}
- err = fuse_uring_copy_from_ring(ring, req, ent);
+ err = fuse_uring_copy_from_ring(req, ent, issue_flags);
out:
- fuse_uring_req_end(ent, req, err);
+ fuse_uring_req_end(ent, req, err, issue_flags);
}
/*
@@ -895,7 +1179,8 @@ out:
* Else, there is no next fuse request and this returns false.
*/
static bool fuse_uring_get_next_fuse_req(struct fuse_ring_ent *ent,
- struct fuse_ring_queue *queue)
+ struct fuse_ring_queue *queue,
+ unsigned int issue_flags)
{
int err;
struct fuse_req *req;
@@ -907,7 +1192,7 @@ retry:
spin_unlock(&queue->lock);
if (req) {
- err = fuse_uring_prepare_send(ent, req);
+ err = fuse_uring_prepare_send(ent, req, issue_flags);
if (err)
goto retry;
}
@@ -967,7 +1252,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
if (qid >= ring->nr_queues)
return -EINVAL;
- queue = ring->queues[qid];
+ queue = READ_ONCE(ring->queues[qid]);
if (!queue)
return err;
fpq = &queue->fpq;
@@ -981,6 +1266,11 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
return err;
}
+ if (!fuse_uring_cmd_index_ok(cmd, queue)) {
+ spin_unlock(&queue->lock);
+ return -EINVAL;
+ }
+
/* Find a request based on the unique ID of the fuse request
* This should get revised, as it needs a hash calculation and list
* search. And full struct fuse_pqueue is needed (memory overhead).
@@ -1002,8 +1292,14 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
if (err != 0) {
pr_info_ratelimited("qid=%d commit_id %llu state %d",
queue->qid, commit_id, ent->state);
+ fuse_uring_recycle_buffer(ent);
spin_unlock(&queue->lock);
- fuse_uring_req_end(ent, req, err);
+ /*
+ * Unregister any zero copyable pages since ent->cmd is null
+ * when it hits fuse_uring_req_end() in this path
+ */
+ zero_copy_unregister(cmd, ent, issue_flags);
+ fuse_uring_req_end(ent, req, err, issue_flags);
return err;
}
@@ -1019,8 +1315,13 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
* fuse requests would otherwise not get processed - committing
* and fetching is done in one step vs legacy fuse, which has separated
* read (fetch request) and write (commit result).
+ *
+ * If there is no next request or if all buffers are busy (if using a
+ * bufpool), the cmd is not returned to userspace. The entry is left
+ * available and the cmd only returns to userspace when there's a
+ * next request and an available buffer.
*/
- if (fuse_uring_get_next_fuse_req(ent, queue))
+ if (fuse_uring_get_next_fuse_req(ent, queue, issue_flags))
fuse_uring_send(ent, cmd, 0, issue_flags);
return 0;
}
@@ -1035,7 +1336,7 @@ static bool is_ring_ready(struct fuse_ring *ring, int current_qid)
if (current_qid == qid)
continue;
- queue = ring->queues[qid];
+ queue = READ_ONCE(ring->queues[qid]);
if (!queue) {
ready = false;
break;
@@ -1122,10 +1423,14 @@ static struct fuse_ring_ent *
fuse_uring_create_ring_ent(struct io_uring_cmd *cmd,
struct fuse_ring_queue *queue)
{
+ const struct fuse_uring_cmd_req *cmd_req =
+ io_uring_sqe128_cmd(cmd->sqe, struct fuse_uring_cmd_req);
struct fuse_ring *ring = queue->ring;
struct fuse_ring_ent *ent;
struct iovec iov[FUSE_URING_IOV_SEGS];
struct iovec *headers, *payload;
+ unsigned int zero_copy_index;
+
int err;
err = fuse_uring_get_iovec_from_sqe(cmd->sqe, iov);
@@ -1135,6 +1440,10 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd,
return ERR_PTR(err);
}
+ zero_copy_index = READ_ONCE(cmd_req->ent_zero_copy_buf_index);
+ if (zero_copy_index && !queue->zero_copy)
+ return ERR_PTR(-EINVAL);
+
err = -EINVAL;
headers = &iov[FUSE_URING_IOV_HEADERS];
if (headers->iov_len < sizeof(struct fuse_uring_req_header)) {
@@ -1143,11 +1452,29 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd,
}
payload = &iov[FUSE_URING_IOV_PAYLOAD];
- if (payload->iov_len < ring->max_payload_sz) {
- pr_info_ratelimited("Invalid req payload len %zu\n",
- payload->iov_len);
- return ERR_PTR(err);
+
+ spin_lock(&queue->lock);
+ if (bufpool_enabled(queue)) {
+ if (payload->iov_base || payload->iov_len ||
+ !fuse_uring_cmd_index_ok(cmd, queue)) {
+ spin_unlock(&queue->lock);
+ return ERR_PTR(err);
+ }
+ } else {
+ if (payload->iov_len < ring->max_payload_sz) {
+ spin_unlock(&queue->lock);
+ pr_info_ratelimited("Invalid req payload len %zu\n",
+ payload->iov_len);
+ return ERR_PTR(err);
+ }
+ if (queue->zero_copy) {
+ spin_unlock(&queue->lock);
+ pr_info_ratelimited("Can only use zero copy with bufpools\n");
+ return ERR_PTR(err);
+ }
+ queue->payload_mode = FUSE_PAYLOAD_PER_ENT;
}
+ spin_unlock(&queue->lock);
err = -ENOMEM;
ent = kzalloc_obj(*ent, GFP_KERNEL_ACCOUNT);
@@ -1158,7 +1485,9 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd,
ent->queue = queue;
ent->headers = headers->iov_base;
- ent->payload = payload->iov_base;
+ if (queue->payload_mode == FUSE_PAYLOAD_PER_ENT)
+ ent->payload = *payload;
+ ent->zero_copy_index = zero_copy_index;
atomic_inc(&ring->queue_refs);
return ent;
@@ -1176,26 +1505,21 @@ static int fuse_uring_register(struct io_uring_cmd *cmd,
struct fuse_ring *ring = smp_load_acquire(&fch->ring);
struct fuse_ring_queue *queue;
struct fuse_ring_ent *ent;
- int err;
unsigned int qid = READ_ONCE(cmd_req->qid);
- err = -ENOMEM;
- if (!ring) {
- ring = fuse_uring_create(fch);
- if (!ring)
- return err;
- }
+ if (!ring)
+ return -EINVAL;
if (qid >= ring->nr_queues) {
pr_info_ratelimited("fuse: Invalid ring qid %u\n", qid);
return -EINVAL;
}
- queue = ring->queues[qid];
+ queue = READ_ONCE(ring->queues[qid]);
if (!queue) {
- queue = fuse_uring_create_queue(ring, qid);
- if (!queue)
- return err;
+ queue = fuse_uring_create_queue(ring, qid, false, false);
+ if (IS_ERR(queue))
+ return PTR_ERR(queue);
}
/*
@@ -1210,6 +1534,110 @@ static int fuse_uring_register(struct io_uring_cmd *cmd,
return fuse_uring_do_register(ent, cmd, issue_flags);
}
+static int fuse_uring_add_queue(struct io_uring_cmd *cmd, struct fuse_chan *fch)
+{
+ const struct fuse_uring_cmd_req *cmd_req =
+ io_uring_sqe128_cmd(cmd->sqe, struct fuse_uring_cmd_req);
+ struct fuse_ring *ring = smp_load_acquire(&fch->ring);
+ unsigned int qid = READ_ONCE(cmd_req->qid);
+ uint64_t flags = READ_ONCE(cmd_req->flags);
+ struct fuse_ring_queue *queue;
+ bool zero_copy = flags & FUSE_URING_ZERO_COPY;
+
+ if (!ring)
+ return -EINVAL;
+
+ if (qid >= ring->nr_queues) {
+ pr_info_ratelimited("fuse: Invalid ring qid %u\n", qid);
+ return -EINVAL;
+ }
+
+ if (flags & ~FUSE_URING_ADD_QUEUE_FLAGS)
+ return -EINVAL;
+
+ if (zero_copy && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ queue = fuse_uring_create_queue(ring, qid, zero_copy, true);
+ if (IS_ERR(queue))
+ return PTR_ERR(queue);
+
+ return 0;
+}
+
+static int fuse_uring_add_bufpool(struct io_uring_cmd *cmd,
+ struct fuse_chan *fch)
+{
+ const struct fuse_uring_cmd_req *cmd_req =
+ io_uring_sqe128_cmd(cmd->sqe, struct fuse_uring_cmd_req);
+ unsigned int qid = READ_ONCE(cmd_req->qid);
+ uint64_t flags = READ_ONCE(cmd_req->flags);
+ /* paired with the smp_store_release() in fuse_uring_create */
+ struct fuse_ring *ring = smp_load_acquire(&fch->ring);
+ struct fuse_ring_queue *queue;
+ struct fuse_bufpool *pool;
+ uintptr_t pool_uaddr;
+ unsigned int pool_len, nr_bufs;
+ size_t pool_size, buf_size;
+ bool registered = cmd->flags & IORING_URING_CMD_FIXED;
+
+ if (!ring || qid >= ring->nr_queues || flags)
+ return -EINVAL;
+
+ /* reserved for future use, must be zero */
+ if (READ_ONCE(cmd_req->bufpool.reserved))
+ return -EINVAL;
+
+ /* Pairs with smp_store_release() in fuse_uring_create_queue() */
+ queue = smp_load_acquire(&ring->queues[qid]);
+ if (!queue)
+ return -EINVAL;
+
+ pool_uaddr = READ_ONCE(cmd_req->bufpool.uaddr);
+ pool_len = READ_ONCE(cmd_req->bufpool.len);
+
+ /* each buffer holds the max payload size */
+ buf_size = queue->ring->max_payload_sz;
+
+ nr_bufs = pool_len / buf_size;
+ if (!nr_bufs)
+ return -EINVAL;
+
+ pool_size = struct_size(pool, free_map, BITS_TO_LONGS(nr_bufs));
+ pool = kzalloc(pool_size, GFP_KERNEL_ACCOUNT);
+ if (!pool)
+ return -ENOMEM;
+
+ pool->base_uaddr = pool_uaddr;
+ pool->buf_size = buf_size;
+ pool->nr_bufs = nr_bufs;
+ /* all buffers are free */
+ bitmap_set(pool->free_map, 0, nr_bufs);
+
+ /*
+ * A registered bufpool is reached through an io_uring fixed buffer, so
+ * the pool is registered iff this command was submitted with
+ * IORING_URING_CMD_FIXED. The registered buffer index is taken from
+ * sqe->buf_index.
+ */
+ if (registered) {
+ pool->registered = true;
+ pool->registered_index = READ_ONCE(cmd->sqe->buf_index);
+ }
+
+ spin_lock(&queue->lock);
+ if (queue->payload_mode != FUSE_PAYLOAD_UNSET) {
+ spin_unlock(&queue->lock);
+ kfree(pool);
+ return -EINVAL;
+ }
+ queue->bufpool = pool;
+ queue->payload_mode = FUSE_PAYLOAD_BUFPOOL;
+ spin_unlock(&queue->lock);
+
+ return 0;
+}
+
/*
* Entry function from io_uring to handle the given passthrough command
* (op code IORING_OP_URING_CMD)
@@ -1237,23 +1665,30 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
}
fch = fud->chan;
- /* Once a connection has io-uring enabled on it, it can't be disabled */
- if (!enable_uring && !fch->io_uring) {
- pr_info_ratelimited("fuse-io-uring is disabled\n");
- return -EOPNOTSUPP;
- }
+ /*
+ * The ring is sized from values negotiated by FUSE_INIT
+ *
+ * Pairs with smp_store_release() in fuse_chan_set_initialized()
+ */
+ if (!smp_load_acquire(&fch->initialized))
+ return -EAGAIN;
if (fch->abort_with_err)
return -ECONNABORTED;
if (!fch->connected)
return -ENOTCONN;
- /*
- * fuse_uring_register() needs the ring to be initialized,
- * we need to know the max payload size
- */
- if (!fch->initialized)
- return -EAGAIN;
+ /* Once a connection has io-uring enabled on it, it can't be disabled */
+ if (!enable_uring && !fch->io_uring) {
+ pr_info_ratelimited("fuse-io-uring is disabled by module parameter\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!fch->io_uring) {
+ pr_info_ratelimited(
+ "fuse-io-uring not enabled on this connection\n");
+ return -EOPNOTSUPP;
+ }
switch (cmd_op) {
case FUSE_IO_URING_CMD_REGISTER:
@@ -1274,6 +1709,18 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
return err;
}
break;
+ case FUSE_IO_URING_CMD_ADD_QUEUE:
+ err = fuse_uring_add_queue(cmd, fch);
+ if (err)
+ pr_info_once("FUSE_IO_URING_CMD_ADD_QUEUE failed err=%d\n",
+ err);
+ return err;
+ case FUSE_IO_URING_CMD_ADD_BUFPOOL:
+ err = fuse_uring_add_bufpool(cmd, fch);
+ if (err)
+ pr_info_once("FUSE_IO_URING_ADD_BUFPOOL failed err=%d\n",
+ err);
+ return err;
default:
return -EINVAL;
}
@@ -1295,9 +1742,10 @@ static void fuse_uring_send_in_task(struct io_tw_req tw_req, io_tw_token_t tw)
int err;
if (!tw.cancel) {
- err = fuse_uring_prepare_send(ent, ent->fuse_req);
+ err = fuse_uring_prepare_send(ent, ent->fuse_req, issue_flags);
if (err) {
- if (!fuse_uring_get_next_fuse_req(ent, queue))
+ if (!fuse_uring_get_next_fuse_req(ent, queue,
+ issue_flags))
return;
err = 0;
}
@@ -1307,11 +1755,12 @@ static void fuse_uring_send_in_task(struct io_tw_req tw_req, io_tw_token_t tw)
spin_lock(&queue->lock);
list_del_init(&ent->list);
+ fuse_uring_recycle_buffer(ent);
spin_unlock(&queue->lock);
io_uring_cmd_done(cmd, err, issue_flags);
- fuse_uring_req_end(ent, ent->fuse_req, err);
+ fuse_uring_req_end(ent, ent->fuse_req, err, issue_flags);
kfree(ent);
if (atomic_dec_and_test(&queue->ring->queue_refs))
wake_up_all(&queue->ring->stop_waitq);
@@ -1330,7 +1779,7 @@ static struct fuse_ring_queue *fuse_uring_task_to_queue(struct fuse_ring *ring)
ring->nr_queues))
qid = 0;
- queue = ring->queues[qid];
+ queue = READ_ONCE(ring->queues[qid]);
WARN_ONCE(!queue, "Missing queue for qid %d\n", qid);
return queue;
@@ -1368,15 +1817,16 @@ void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req)
req->ring_queue = queue;
ent = list_first_entry_or_null(&queue->ent_avail_queue,
struct fuse_ring_ent, list);
- if (ent)
- fuse_uring_add_req_to_ring_ent(ent, req);
- else
- list_add_tail(&req->list, &queue->fuse_req_queue);
- spin_unlock(&queue->lock);
- if (ent)
- fuse_uring_dispatch_ent(ent);
+ if (!ent || fuse_uring_prep_buffer(ent, req)) {
+ list_add_tail(&req->list, &queue->fuse_req_queue);
+ spin_unlock(&queue->lock);
+ return;
+ }
+ fuse_uring_add_req_to_ring_ent(ent, req);
+ spin_unlock(&queue->lock);
+ fuse_uring_dispatch_ent(ent);
return;
err_unlock:
@@ -1424,10 +1874,9 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req)
*/
req = list_first_entry_or_null(&queue->fuse_req_queue, struct fuse_req,
list);
- if (ent && req) {
+ if (ent && req && !fuse_uring_prep_buffer(ent, req)) {
fuse_uring_add_req_to_ring_ent(ent, req);
spin_unlock(&queue->lock);
-
fuse_uring_dispatch_ent(ent);
} else {
spin_unlock(&queue->lock);
diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h
index 55f8d04e4b0b..263d0f8b9714 100644
--- a/fs/fuse/dev_uring_i.h
+++ b/fs/fuse/dev_uring_i.h
@@ -7,6 +7,8 @@
#ifndef _FS_FUSE_DEV_URING_I_H
#define _FS_FUSE_DEV_URING_I_H
+#include <linux/uio.h>
+
#include "fuse_dev_i.h"
#ifdef CONFIG_FUSE_IO_URING
@@ -36,11 +38,50 @@ enum fuse_ring_req_state {
FRRS_RELEASED,
};
+/* how a queue's payload buffers are provided */
+enum fuse_queue_payload_mode {
+ /* not yet committed (a bufpool may still be added) */
+ FUSE_PAYLOAD_UNSET = 0,
+ /* each entry registers its own payload buffer */
+ FUSE_PAYLOAD_PER_ENT,
+ /* each entry's payload buffer is assigned from a bufpool */
+ FUSE_PAYLOAD_BUFPOOL,
+};
+
+struct fuse_bufpool {
+ bool registered;
+
+ /*
+ * io_uring registered buffer table index for this pool, bound at
+ * ADD_BUFPOOL time. Only valid if the bufpool is registered
+ */
+ u16 registered_index;
+
+ /* starting uaddr of the bufpool */
+ uintptr_t base_uaddr;
+
+ /* size of each buffer in the pool */
+ size_t buf_size;
+
+ /* total number of buffers in the pool */
+ unsigned int nr_bufs;
+
+ /* bitmap tracking which buffers are free */
+ unsigned long free_map[];
+};
+
/** A fuse ring entry, part of the ring queue */
struct fuse_ring_ent {
/* userspace buffer */
struct fuse_uring_req_header __user *headers;
- void __user *payload;
+ struct iovec payload;
+
+ /* buffer id in the pool, if bufpools are used. ignored otherwise */
+ unsigned int buf_id;
+
+ /* true if the request's pages are being zero-copied */
+ bool zero_copied;
+ unsigned int zero_copy_index;
/* the ring queue that owns the request */
struct fuse_ring_queue *queue;
@@ -99,6 +140,14 @@ struct fuse_ring_queue {
unsigned int active_background;
bool stopped;
+
+ /* how this queue's payload buffers are provided */
+ enum fuse_queue_payload_mode payload_mode;
+
+ /* only allocated when payload_mode == FUSE_PAYLOAD_BUFPOOL */
+ struct fuse_bufpool *bufpool;
+
+ bool zero_copy;
};
/*
@@ -135,6 +184,7 @@ struct fuse_ring {
bool ready;
};
+void fuse_uring_conn_init(struct fuse_chan *fch);
void fuse_uring_stop_queues(struct fuse_ring *ring);
void fuse_uring_abort_end_requests(struct fuse_ring *ring);
int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags);
@@ -174,6 +224,10 @@ static inline bool fuse_uring_ready(struct fuse_chan *fch)
#else /* CONFIG_FUSE_IO_URING */
+static inline void fuse_uring_conn_init(struct fuse_chan *fch)
+{
+}
+
static inline void fuse_uring_abort(struct fuse_chan *fch)
{
}
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index d4e0029810c0..e49b4e874b15 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -34,7 +34,7 @@ struct dentry_bucket {
#define FUSE_HASH_BITS 5
#define FUSE_HASH_SIZE (1 << FUSE_HASH_BITS)
static struct dentry_bucket dentry_hash[FUSE_HASH_SIZE];
-struct delayed_work dentry_tree_work;
+static struct delayed_work dentry_tree_work;
/* Minimum invalidation work queue frequency */
#define FUSE_DENTRY_INVAL_FREQ_MIN 5
@@ -96,6 +96,7 @@ static void fuse_advise_use_readdirplus(struct inode *dir)
struct fuse_dentry {
u64 time;
+ u64 epoch;
union {
struct rcu_head rcu;
struct rb_node node;
@@ -236,6 +237,13 @@ void fuse_dentry_tree_cleanup(void)
WARN_ON_ONCE(!RB_EMPTY_ROOT(&dentry_hash[i].tree));
}
+void fuse_dentry_set_epoch(struct dentry *dentry, u64 epoch)
+{
+ struct fuse_dentry *fd = dentry->d_fsdata;
+
+ fd->epoch = epoch;
+}
+
static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time)
{
((struct fuse_dentry *) dentry->d_fsdata)->time = time;
@@ -387,10 +395,11 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
struct fuse_mount *fm;
struct fuse_conn *fc;
struct fuse_inode *fi;
+ struct fuse_dentry *fd = entry->d_fsdata;
int ret;
fc = get_fuse_conn_super(dir->i_sb);
- if (entry->d_time < atomic_read(&fc->epoch))
+ if (fd->epoch < atomic_read(&fc->epoch))
goto invalid;
inode = d_inode_rcu(entry);
@@ -480,10 +489,10 @@ static int fuse_dentry_init(struct dentry *dentry)
RB_CLEAR_NODE(&fd->node);
dentry->d_fsdata = fd;
/*
- * Initialising d_time (epoch) to '0' ensures the dentry is invalid
+ * Initialising epoch to '0' ensures the dentry is invalid
* if compared to fc->epoch, which is initialized to '1'.
*/
- dentry->d_time = 0;
+ fuse_dentry_set_epoch(dentry, 0);
return 0;
}
@@ -641,7 +650,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
goto out_err;
entry = newent ? newent : entry;
- entry->d_time = epoch;
+ fuse_dentry_set_epoch(entry, epoch);
if (outarg_valid)
fuse_change_entry_timeout(entry, &outarg);
else
@@ -898,7 +907,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
}
kfree(forget);
d_instantiate(entry, inode);
- entry->d_time = epoch;
+ fuse_dentry_set_epoch(entry, epoch);
fuse_change_entry_timeout(entry, &outentry);
fuse_dir_changed(dir);
err = generic_file_open(inode, file);
@@ -1028,10 +1037,10 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
return d;
if (d) {
- d->d_time = epoch;
+ fuse_dentry_set_epoch(d, epoch);
fuse_change_entry_timeout(d, &outarg);
} else {
- entry->d_time = epoch;
+ fuse_dentry_set_epoch(entry, epoch);
fuse_change_entry_timeout(entry, &outarg);
}
fuse_dir_changed(dir);
@@ -2169,10 +2178,8 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
filemap_invalidate_lock(mapping);
fault_blocked = true;
err = fuse_dax_break_layouts(inode, 0, -1);
- if (err) {
- filemap_invalidate_unlock(mapping);
- return err;
- }
+ if (err)
+ goto unlock;
}
if (attr->ia_valid & ATTR_OPEN) {
@@ -2199,7 +2206,7 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
ATTR_TIMES_SET)) {
err = write_inode_now(inode, true);
if (err)
- return err;
+ goto unlock;
fuse_set_nowrite(inode);
fuse_release_nowrite(inode);
@@ -2290,6 +2297,9 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
*/
if ((is_truncate || !is_wb) &&
S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) {
+ if (outarg.attr.size > oldsize)
+ truncate_pagecache_range(inode, oldsize,
+ outarg.attr.size - 1);
truncate_pagecache(inode, outarg.attr.size);
invalidate_inode_pages2(mapping);
}
@@ -2307,6 +2317,7 @@ error:
clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
+unlock:
if (fault_blocked)
filemap_invalidate_unlock(mapping);
return err;
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index f2c081f09791..8d6135a6108a 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -272,7 +272,7 @@ static int fuse_open(struct inode *inode, struct file *file)
filemap_invalidate_lock(inode->i_mapping);
err = fuse_dax_break_layouts(inode, 0, -1);
if (err)
- goto out_inode_unlock;
+ goto out_unlock;
}
if (is_wb_truncate || dax_truncate)
@@ -296,9 +296,9 @@ static int fuse_open(struct inode *inode, struct file *file)
else if (!(ff->open_flags & FOPEN_KEEP_CACHE))
invalidate_inode_pages2(inode->i_mapping);
}
+out_unlock:
if (dax_truncate)
filemap_invalidate_unlock(inode->i_mapping);
-out_inode_unlock:
if (is_wb_truncate || dax_truncate)
inode_unlock(inode);
@@ -605,6 +605,7 @@ void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos,
args->out_argvar = true;
args->out_numargs = 1;
args->out_args[0].size = count;
+ args->zero_copy = ff->open_flags & FOPEN_IO_URING_ZERO_COPY;
}
static void fuse_release_user_pages(struct fuse_args_pages *ap, ssize_t nres,
@@ -1153,6 +1154,7 @@ static void fuse_write_args_fill(struct fuse_io_args *ia, struct fuse_file *ff,
args->out_numargs = 1;
args->out_args[0].size = sizeof(ia->write.out);
args->out_args[0].value = &ia->write.out;
+ args->zero_copy = ff->open_flags & FOPEN_IO_URING_ZERO_COPY;
}
static unsigned int fuse_write_flags(struct kiocb *iocb)
@@ -1346,9 +1348,13 @@ static ssize_t fuse_perform_write(struct kiocb *iocb, struct iov_iter *ii)
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_inode *fi = get_fuse_inode(inode);
loff_t pos = iocb->ki_pos;
+ loff_t old_size = i_size_read(inode);
int err = 0;
ssize_t res = 0;
+ if (pos > old_size)
+ truncate_pagecache_range(inode, old_size, pos - 1);
+
if (inode->i_size < pos + iov_iter_count(ii))
set_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
@@ -1787,13 +1793,14 @@ static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct inode *inode = file_inode(iocb->ki_filp);
struct address_space *mapping = inode->i_mapping;
- loff_t pos = iocb->ki_pos;
ssize_t res;
bool exclusive;
fuse_dio_lock(iocb, from, &exclusive);
res = generic_write_checks(iocb, from);
if (res > 0) {
+ loff_t pos = iocb->ki_pos;
+
task_io_account_write(res);
if (!is_sync_kiocb(iocb)) {
res = fuse_direct_IO(iocb, from);
@@ -1808,7 +1815,7 @@ static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
/*
* As in generic_file_direct_write(), invalidate after
* write, to invalidate read-ahead cache that may have
- * with the write.
+ * competed with the write.
*/
invalidate_inode_pages2_range(mapping,
pos >> PAGE_SHIFT,
@@ -2894,6 +2901,11 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
/* we could have extended the file */
if (!(mode & FALLOC_FL_KEEP_SIZE)) {
+ loff_t oldsize = i_size_read(inode);
+
+ if (offset + length > oldsize)
+ truncate_pagecache_range(inode, oldsize,
+ offset + length - 1);
if (fuse_write_update_attr(inode, offset + length, length))
file_update_time(file);
}
diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h
index 668c8391d61c..4b412a76225f 100644
--- a/fs/fuse/fuse_dev_i.h
+++ b/fs/fuse/fuse_dev_i.h
@@ -38,6 +38,8 @@ struct fuse_iqueue;
* @FR_PRIVATE: request is on private list
* @FR_ASYNC: request is asynchronous
* @FR_URING: request is handled through fuse-io-uring
+ * @FR_SYNC_WAKEUP: use synchronous wakeup when queueing this request to
+ * give the scheduler a hint about the waker task
*/
enum fuse_req_flag {
FR_ISREPLY,
@@ -53,6 +55,7 @@ enum fuse_req_flag {
FR_PRIVATE,
FR_ASYNC,
FR_URING,
+ FR_SYNC_WAKEUP,
};
/**
@@ -325,6 +328,8 @@ struct fuse_copy_state {
bool write:1;
bool move_folios:1;
bool is_uring:1;
+ /* set when the payload is zero-copied. folios are filled in place */
+ bool skip_folio_copy:1;
struct {
unsigned int copied_sz; /* copied size into the user buffer */
} ring;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 85f738c53122..c8d4c5f3af7e 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1054,6 +1054,8 @@ u64 fuse_time_to_jiffies(u64 sec, u32 nsec);
void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o);
+void fuse_dentry_set_epoch(struct dentry *dentry, u64 epoch);
+
/*
* Initialize fuse_conn
*/
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index d975073c6029..e9552be3637b 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -791,6 +791,9 @@ static int fuse_opt_fd(struct fs_context *fsc, struct file *file)
{
struct fuse_fs_context *ctx = fsc->fs_private;
+ if (ctx->fud)
+ return invalfc(fsc, "Multiple fd specified");
+
if (file->f_op != &fuse_dev_operations)
return invalfc(fsc, "fd is not a fuse device");
/*
@@ -1272,6 +1275,7 @@ static void process_init_reply(struct fuse_args *args, int error)
struct fuse_mount *fm = ia->fm;
struct fuse_conn *fc = fm->fc;
struct fuse_init_out *arg = &ia->out;
+ bool io_uring_enabled = false;
bool ok = true;
if (error || arg->major != FUSE_KERNEL_VERSION)
@@ -1402,7 +1406,7 @@ static void process_init_reply(struct fuse_args *args, int error)
ok = false;
}
if (flags & FUSE_OVER_IO_URING && fuse_uring_enabled())
- fuse_chan_io_uring_enable(fc->chan);
+ io_uring_enabled = true;
if (flags & FUSE_REQUEST_TIMEOUT)
timeout = arg->request_timeout;
@@ -1416,6 +1420,7 @@ static void process_init_reply(struct fuse_args *args, int error)
fm->sb->s_bdi->ra_pages =
min(fm->sb->s_bdi->ra_pages, ra_pages);
+ fm->sb->s_bdi->io_pages = fc->max_pages;
fc->minor = arg->minor;
fc->max_write = arg->minor < 5 ? 4096 : arg->max_write;
fc->max_write = max_t(unsigned, 4096, fc->max_write);
@@ -1432,6 +1437,7 @@ static void process_init_reply(struct fuse_args *args, int error)
.minor = fc->minor,
.max_write = fc->max_write,
.max_pages = fc->max_pages,
+ .io_uring_enabled = io_uring_enabled,
};
fuse_chan_set_initialized(fc->chan, &cp);
}
@@ -1474,12 +1480,8 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
flags |= FUSE_PASSTHROUGH;
- /*
- * This is just an information flag for fuse server. No need to check
- * the reply - server is either sending IORING_OP_URING_CMD or not.
- */
if (fuse_uring_enabled())
- flags |= FUSE_OVER_IO_URING;
+ flags |= FUSE_OVER_IO_URING | FUSE_HAS_IO_URING_BUFPOOL;
ia->in.flags = flags;
ia->in.flags2 = flags >> 32;
@@ -1639,6 +1641,8 @@ static int fuse_fill_super_submount(struct super_block *sb,
fuse_fill_attr_from_inode(&root_attr, parent_fi);
root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0,
fuse_get_evict_ctr(fm->fc));
+ if (!root)
+ return -ENOMEM;
/*
* This inode is just a duplicate, so it is not looked up and
* its nlookup should not be incremented. fuse_iget() does
diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
index 0e1321491747..5ca87151d70d 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -260,7 +260,7 @@ retry:
}
if (fc->readdirplus_auto)
set_bit(FUSE_I_INIT_RDPLUS, &get_fuse_inode(inode)->state);
- dentry->d_time = epoch;
+ fuse_dentry_set_epoch(dentry, epoch);
fuse_change_entry_timeout(dentry, o);
dput(dentry);
diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c
index 6cc6fc491343..95a1acd7bc08 100644
--- a/fs/fuse/req_timeout.c
+++ b/fs/fuse/req_timeout.c
@@ -128,18 +128,12 @@ static void set_request_timeout(struct fuse_chan *fch, unsigned int timeout)
void fuse_init_server_timeout(struct fuse_chan *fch, unsigned int timeout)
{
- if (!timeout && !fuse_max_req_timeout && !fuse_default_req_timeout)
- return;
-
if (!timeout)
timeout = fuse_default_req_timeout;
- if (fuse_max_req_timeout) {
- if (timeout)
- timeout = min(fuse_max_req_timeout, timeout);
- else
- timeout = fuse_max_req_timeout;
- }
+ timeout = min_not_zero(timeout, fuse_max_req_timeout);
+ if (!timeout)
+ return;
timeout = max(FUSE_TIMEOUT_TIMER_FREQ, timeout);