libfuse
fuse_uring.c
1/*
2 * FUSE: Filesystem in Userspace
3 * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com>
4 *
5 * Implementation of (most of) FUSE-over-io-uring.
6 *
7 * This program can be distributed under the terms of the GNU LGPLv2.
8 * See the file LGPL2.txt
9 */
10
11#define _GNU_SOURCE
12
13#include "fuse_i.h"
14#include "fuse_kernel.h"
15#include "fuse_uring_i.h"
16
17#include <stdlib.h>
18#include <liburing.h>
19#include <sys/sysinfo.h>
20#include <stdint.h>
21#include <inttypes.h>
22#include <stdbool.h>
23#include <string.h>
24#include <unistd.h>
25#include <numa.h>
26#include <pthread.h>
27#include <stdio.h>
28#include <linux/sched.h>
29#include <poll.h>
30#include <sys/eventfd.h>
31
32/* Size of command data area in SQE when IORING_SETUP_SQE128 is used */
33#define FUSE_URING_MAX_SQE128_CMD_DATA 80
34
35struct fuse_ring_ent {
36 struct fuse_ring_queue *ring_queue; /* back pointer */
37 struct fuse_req req;
38
39 struct fuse_uring_req_header *req_header;
40 void *op_payload;
41 size_t req_payload_sz;
42
43 /* commit id of a fuse request */
44 uint64_t req_commit_id;
45
46 enum fuse_uring_cmd last_cmd;
47
48 /* header and payload */
49 struct iovec iov[2];
50};
51
52struct fuse_ring_queue {
53 /* back pointer */
54 struct fuse_ring_pool *ring_pool;
55 int qid;
56 int numa_node;
57 pthread_t tid;
58 int eventfd;
59 size_t req_header_sz;
60 struct io_uring ring;
61
62 pthread_mutex_t ring_lock;
63 bool cqe_processing;
64
65 /* size depends on queue depth */
66 struct fuse_ring_ent ent[];
67};
68
73 struct fuse_session *se;
74
75 /* number of queues */
76 size_t nr_queues;
77
78 /* number of per queue entries */
79 size_t queue_depth;
80
81 /* max payload size for fuse requests*/
82 size_t max_req_payload_sz;
83
84 /* size of a single queue */
85 size_t queue_mem_size;
86
87 unsigned int started_threads;
88 unsigned int failed_threads;
89
90 /* Avoid sending queue entries before FUSE_INIT reply*/
91 sem_t init_sem;
92
93 pthread_cond_t thread_start_cond;
94 pthread_mutex_t thread_start_mutex;
95
96 /* pointer to the first queue */
97 struct fuse_ring_queue *queues;
98};
99
100static size_t
101fuse_ring_queue_size(const size_t q_depth)
102{
103 const size_t req_size = sizeof(struct fuse_ring_ent) * q_depth;
104
105 return sizeof(struct fuse_ring_queue) + req_size;
106}
107
108static struct fuse_ring_queue *
109fuse_uring_get_queue(struct fuse_ring_pool *fuse_ring, int qid)
110{
111 void *ptr =
112 ((char *)fuse_ring->queues) + (qid * fuse_ring->queue_mem_size);
113
114 return ptr;
115}
116
120static void *fuse_uring_get_sqe_cmd(struct io_uring_sqe *sqe)
121{
122 return (void *)&sqe->cmd[0];
123}
124
125static void fuse_uring_sqe_set_req_data(struct fuse_uring_cmd_req *req,
126 const unsigned int qid,
127 const uint64_t commit_id)
128{
129 req->qid = qid;
130 req->commit_id = commit_id;
131 req->flags = 0;
132}
133
134static void
135fuse_uring_sqe_prepare(struct io_uring_sqe *sqe, struct fuse_ring_ent *req,
136 __u32 cmd_op)
137{
138 /* These fields should be written once, never change */
139 sqe->opcode = IORING_OP_URING_CMD;
140
141 /*
142 * IOSQE_FIXED_FILE: fd is the index to the fd *array*
143 * given to io_uring_register_files()
144 */
145 sqe->flags = IOSQE_FIXED_FILE;
146 sqe->fd = 0;
147
148 sqe->rw_flags = 0;
149 sqe->ioprio = 0;
150 sqe->off = 0;
151
152 io_uring_sqe_set_data(sqe, req);
153
154 sqe->cmd_op = cmd_op;
155 sqe->__pad1 = 0;
156}
157
158static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool,
159 struct fuse_ring_queue *queue,
160 struct fuse_ring_ent *ring_ent)
161{
162 bool locked = false;
163 struct fuse_session *se = ring_pool->se;
164 struct fuse_uring_req_header *rrh = ring_ent->req_header;
165 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
166 struct fuse_uring_ent_in_out *ent_in_out =
167 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
168 struct io_uring_sqe *sqe;
169
170 if (pthread_self() != queue->tid) {
171 pthread_mutex_lock(&queue->ring_lock);
172 locked = true;
173 }
174
175 sqe = io_uring_get_sqe(&queue->ring);
176
177 if (sqe == NULL) {
178 /* This is an impossible condition, unless there is a bug.
179 * The kernel sent back an SQEs, which is assigned to a request.
180 * There is no way to get out of SQEs, as the number of
181 * SQEs matches the number tof requests.
182 */
183
184 se->error = -EIO;
185 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
186
187 return -EIO;
188 }
189
190 ring_ent->last_cmd = FUSE_IO_URING_CMD_COMMIT_AND_FETCH;
191 fuse_uring_sqe_prepare(sqe, ring_ent, ring_ent->last_cmd);
192 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid,
193 ring_ent->req_commit_id);
194
195 if (se->debug) {
196 fuse_log(FUSE_LOG_DEBUG, " unique: %" PRIu64 ", result=%d\n",
197 out->unique, ent_in_out->payload_sz);
198 }
199
200 if (!queue->cqe_processing)
201 io_uring_submit(&queue->ring);
202
203 if (locked)
204 pthread_mutex_unlock(&queue->ring_lock);
205
206 return 0;
207}
208
209int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
210 void **mr)
211{
212 struct fuse_ring_ent *ring_ent;
213
214 /* Not possible without io-uring interface */
215 if (!req->flags.is_uring)
216 return -EINVAL;
217
218 ring_ent = container_of(req, struct fuse_ring_ent, req);
219
220 *payload = ring_ent->op_payload;
221 *payload_sz = ring_ent->req_payload_sz;
222
223 /*
224 * For now unused, but will be used later when the application can
225 * allocate the buffers itself and register them for rdma.
226 */
227 if (mr)
228 *mr = NULL;
229
230 return 0;
231}
232
233int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize)
234{
235 int res;
236 struct fuse_ring_ent *ring_ent =
237 container_of(req, struct fuse_ring_ent, req);
238 struct fuse_uring_req_header *rrh = ring_ent->req_header;
239 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
240 struct fuse_uring_ent_in_out *ent_in_out =
241 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
242
243 struct fuse_ring_queue *queue = ring_ent->ring_queue;
244 struct fuse_ring_pool *ring_pool = queue->ring_pool;
245 size_t max_payload_sz = ring_pool->max_req_payload_sz;
246
247 if (argsize > max_payload_sz) {
248 fuse_log(FUSE_LOG_ERR, "argsize %zu exceeds buffer size %zu",
249 argsize, max_payload_sz);
250 error = -EINVAL;
251 } else if (argsize) {
252 if (arg != ring_ent->op_payload)
253 memcpy(ring_ent->op_payload, arg, argsize);
254 }
255 ent_in_out->payload_sz = argsize;
256
257 out->error = error;
258 out->unique = req->unique;
259
260 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
261
262 fuse_free_req(req);
263
264 return res;
265}
266
267int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv,
268 enum fuse_buf_copy_flags flags)
269{
270 struct fuse_ring_ent *ring_ent =
271 container_of(req, struct fuse_ring_ent, req);
272
273 struct fuse_ring_queue *queue = ring_ent->ring_queue;
274 struct fuse_ring_pool *ring_pool = queue->ring_pool;
275 struct fuse_uring_req_header *rrh = ring_ent->req_header;
276 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
277 struct fuse_uring_ent_in_out *ent_in_out =
278 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
279 size_t max_payload_sz = ring_ent->req_payload_sz;
280 struct fuse_bufvec dest_vec = FUSE_BUFVEC_INIT(max_payload_sz);
281 int res;
282
283 dest_vec.buf[0].mem = ring_ent->op_payload;
284 dest_vec.buf[0].size = max_payload_sz;
285
286 res = fuse_buf_copy(&dest_vec, bufv, flags);
287
288 out->error = res < 0 ? res : 0;
289 out->unique = req->unique;
290
291 ent_in_out->payload_sz = res > 0 ? res : 0;
292
293 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
294
295 fuse_free_req(req);
296
297 return res;
298}
299
303int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count)
304{
305 struct fuse_ring_ent *ring_ent =
306 container_of(req, struct fuse_ring_ent, req);
307
308 struct fuse_ring_queue *queue = ring_ent->ring_queue;
309 struct fuse_ring_pool *ring_pool = queue->ring_pool;
310 struct fuse_uring_req_header *rrh = ring_ent->req_header;
311 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
312 struct fuse_uring_ent_in_out *ent_in_out =
313 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
314 size_t max_buf = ring_pool->max_req_payload_sz;
315 size_t len = 0;
316 int res = 0;
317
318 /* copy iov into the payload, idx=0 is the header section */
319 for (int idx = 1; idx < count; idx++) {
320 struct iovec *cur = &iov[idx];
321
322 if (len + cur->iov_len > max_buf) {
323 fuse_log(FUSE_LOG_ERR,
324 "iov[%d] exceeds buffer size %zu",
325 idx, max_buf);
326 res = -EINVAL; /* Gracefully handle this? */
327 break;
328 }
329
330 memcpy(ring_ent->op_payload + len, cur->iov_base, cur->iov_len);
331 len += cur->iov_len;
332 }
333
334 ent_in_out->payload_sz = len;
335
336 out->error = res;
337 out->unique = req->unique;
338 out->len = len;
339
340 return fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
341}
342
343static int fuse_queue_setup_io_uring(struct io_uring *ring, size_t qid,
344 size_t depth, int fd, int evfd)
345{
346 int rc;
347 struct io_uring_params params = {0};
348 int files[2] = { fd, evfd };
349
350 depth += 1; /* for the eventfd poll SQE */
351
352 params.flags = IORING_SETUP_SQE128;
353
354 /* Replies are batched and flushed in one io_uring_enter; don't let a
355 * single failing commit SQE stall submission of the rest of the batch.
356 */
357 params.flags |= IORING_SETUP_SUBMIT_ALL;
358
359 /* Avoid cq overflow */
360 params.flags |= IORING_SETUP_CQSIZE;
361 params.cq_entries = depth * 2;
362
363 /* These flags should help to increase performance, but actually
364 * make it a bit slower - reason should get investigated.
365 */
366 if (0) {
367 /* Has the main slow down effect */
368 params.flags |= IORING_SETUP_SINGLE_ISSUER;
369
370 // params.flags |= IORING_SETUP_DEFER_TASKRUN;
371 params.flags |= IORING_SETUP_TASKRUN_FLAG;
372
373 /* Second main effect to make it slower */
374 params.flags |= IORING_SETUP_COOP_TASKRUN;
375 }
376
377 rc = io_uring_queue_init_params(depth, ring, &params);
378 if (rc != 0) {
379 fuse_log(FUSE_LOG_ERR, "Failed to setup qid %zu: %d (%s)\n",
380 qid, rc, strerror(-rc));
381 return rc;
382 }
383
384 rc = io_uring_register_files(ring, files, 1);
385 if (rc != 0) {
386 rc = -errno;
387 fuse_log(FUSE_LOG_ERR,
388 "Failed to register files for ring idx %zu: %s",
389 qid, strerror(errno));
390 return rc;
391 }
392
393 return 0;
394}
395
396static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring)
397{
398 for (size_t qid = 0; qid < fuse_ring->nr_queues; qid++) {
399 struct fuse_ring_queue *queue =
400 fuse_uring_get_queue(fuse_ring, qid);
401
402 if (queue->tid != 0) {
403 uint64_t value = 1ULL;
404 int rc;
405
406 rc = write(queue->eventfd, &value, sizeof(value));
407 if (rc != sizeof(value))
408 fprintf(stderr,
409 "Wrote to eventfd=%d err=%s: rc=%d\n",
410 queue->eventfd, strerror(errno), rc);
411 pthread_cancel(queue->tid);
412 pthread_join(queue->tid, NULL);
413 queue->tid = 0;
414 }
415
416 if (queue->eventfd >= 0) {
417 close(queue->eventfd);
418 queue->eventfd = -1;
419 }
420
421 if (queue->ring.ring_fd != -1)
422 io_uring_queue_exit(&queue->ring);
423
424 for (size_t idx = 0; idx < fuse_ring->queue_depth; idx++) {
425 struct fuse_ring_ent *ent = &queue->ent[idx];
426
427 numa_free(ent->op_payload, ent->req_payload_sz);
428 numa_free(ent->req_header, queue->req_header_sz);
429 }
430
431 pthread_mutex_destroy(&queue->ring_lock);
432 }
433
434 free(fuse_ring->queues);
435 pthread_cond_destroy(&fuse_ring->thread_start_cond);
436 pthread_mutex_destroy(&fuse_ring->thread_start_mutex);
437 free(fuse_ring);
438}
439
440static int fuse_uring_register_ent(struct fuse_ring_queue *queue,
441 struct fuse_ring_ent *ent)
442{
443 struct io_uring_sqe *sqe;
444
445 sqe = io_uring_get_sqe(&queue->ring);
446 if (sqe == NULL) {
447 /*
448 * All SQEs are idle here - no good reason this
449 * could fail
450 */
451 fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs");
452 return -EIO;
453 }
454
455 ent->last_cmd = FUSE_IO_URING_CMD_REGISTER;
456 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
457
458 /* only needed for fetch */
459 ent->iov[0].iov_base = ent->req_header;
460 ent->iov[0].iov_len = queue->req_header_sz;
461
462 ent->iov[1].iov_base = ent->op_payload;
463 ent->iov[1].iov_len = ent->req_payload_sz;
464
465 sqe->addr = (uint64_t)(ent->iov);
466 sqe->len = 2;
467
468 /* this is a fetch, kernel does not read commit id */
469 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, 0);
470
471 return 0;
472
473}
474
475static int fuse_uring_register_queue(struct fuse_ring_queue *queue)
476{
477 struct fuse_ring_pool *ring_pool = queue->ring_pool;
478 unsigned int sq_ready;
479 struct io_uring_sqe *sqe;
480 int res;
481
482 for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) {
483 struct fuse_ring_ent *ent = &queue->ent[idx];
484
485 res = fuse_uring_register_ent(queue, ent);
486 if (res != 0)
487 return res;
488 }
489
490 sq_ready = io_uring_sq_ready(&queue->ring);
491 if (sq_ready != ring_pool->queue_depth) {
492 fuse_log(FUSE_LOG_ERR,
493 "SQE ready mismatch, expected %zu got %u\n",
494 ring_pool->queue_depth, sq_ready);
495 return -EINVAL;
496 }
497
498 /* Poll SQE for the eventfd to wake up on teardown */
499 sqe = io_uring_get_sqe(&queue->ring);
500 if (sqe == NULL) {
501 fuse_log(FUSE_LOG_ERR, "Failed to get eventfd SQE");
502 return -EIO;
503 }
504
505 io_uring_prep_poll_add(sqe, queue->eventfd, POLLIN);
506 io_uring_sqe_set_data(sqe, (void *)(uintptr_t)queue->eventfd);
507
508 /* Only preparation until here, no submission yet */
509
510 return 0;
511}
512
513static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se)
514{
515 struct fuse_ring_pool *fuse_ring = NULL;
516 const size_t nr_queues = get_nprocs_conf();
517 size_t payload_sz = se->bufsize - FUSE_BUFFER_HEADER_SIZE;
518 size_t queue_sz;
519
520 if (se->debug)
521 fuse_log(FUSE_LOG_DEBUG, "starting io-uring q-depth=%d\n",
522 se->uring.q_depth);
523
524 fuse_ring = calloc(1, sizeof(*fuse_ring));
525 if (fuse_ring == NULL) {
526 fuse_log(FUSE_LOG_ERR, "Allocating the ring failed\n");
527 goto err;
528 }
529
530 queue_sz = fuse_ring_queue_size(se->uring.q_depth);
531 fuse_ring->queues = calloc(1, queue_sz * nr_queues);
532 if (fuse_ring->queues == NULL) {
533 fuse_log(FUSE_LOG_ERR, "Allocating the queues failed\n");
534 goto err;
535 }
536
537 fuse_ring->se = se;
538 fuse_ring->nr_queues = nr_queues;
539 fuse_ring->queue_depth = se->uring.q_depth;
540 fuse_ring->max_req_payload_sz = payload_sz;
541 fuse_ring->queue_mem_size = queue_sz;
542
543 /*
544 * very basic queue initialization, that cannot fail and will
545 * allow easy cleanup if something (like mmap) fails in the middle
546 * below
547 */
548 for (size_t qid = 0; qid < nr_queues; qid++) {
549 struct fuse_ring_queue *queue =
550 fuse_uring_get_queue(fuse_ring, qid);
551
552 queue->ring.ring_fd = -1;
553 queue->numa_node = numa_node_of_cpu(qid);
554 queue->qid = qid;
555 queue->ring_pool = fuse_ring;
556 queue->eventfd = -1;
557 pthread_mutex_init(&queue->ring_lock, NULL);
558 }
559
560 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
561 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
562 sem_init(&fuse_ring->init_sem, 0, 0);
563
564 return fuse_ring;
565
566err:
567 if (fuse_ring)
568 fuse_session_destruct_uring(fuse_ring);
569
570 return NULL;
571}
572
573static void fuse_uring_resubmit(struct fuse_ring_queue *queue,
574 struct fuse_ring_ent *ent)
575{
576 struct io_uring_sqe *sqe;
577
578 sqe = io_uring_get_sqe(&queue->ring);
579 if (sqe == NULL) {
580 /* This is an impossible condition, unless there is a bug.
581 * The kernel sent back an SQEs, which is assigned to a request.
582 * There is no way to get out of SQEs, as the number of
583 * SQEs matches the number tof requests.
584 */
585
586 queue->ring_pool->se->error = -EIO;
587 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
588
589 return;
590 }
591
592 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
593
594 switch (ent->last_cmd) {
595 case FUSE_IO_URING_CMD_REGISTER:
596 sqe->addr = (uint64_t)(ent->iov);
597 sqe->len = 2;
598 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
599 queue->qid, 0);
600 break;
601 case FUSE_IO_URING_CMD_COMMIT_AND_FETCH:
602 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
603 queue->qid, ent->req_commit_id);
604 break;
605 default:
606 fuse_log(FUSE_LOG_ERR, "Unknown command type: %d\n",
607 ent->last_cmd);
608 queue->ring_pool->se->error = -EINVAL;
609 break;
610 }
611
612 /* caller submits */
613}
614
615static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue,
616 struct io_uring_cqe *cqe)
617{
618 struct fuse_ring_ent *ent = io_uring_cqe_get_data(cqe);
619
620 if (!ent) {
621 fuse_log(FUSE_LOG_ERR,
622 "cqe=%p io_uring_cqe_get_data returned NULL\n", cqe);
623 return;
624 }
625
626 struct fuse_req *req = &ent->req;
627 struct fuse_ring_pool *fuse_ring = queue->ring_pool;
628 struct fuse_uring_req_header *rrh = ent->req_header;
629
630 struct fuse_in_header *in = (struct fuse_in_header *)&rrh->in_out;
631 struct fuse_uring_ent_in_out *ent_in_out = &rrh->ring_ent_in_out;
632
633 ent->req_commit_id = ent_in_out->commit_id;
634 if (unlikely(ent->req_commit_id == 0)) {
635 /*
636 * If this happens kernel will not find the response - it will
637 * be stuck forever - better to abort immediately.
638 */
639 fuse_log(FUSE_LOG_ERR, "Received invalid commit_id=0\n");
640 abort();
641 }
642
643 memset(&req->flags, 0, sizeof(req->flags));
644 memset(&req->u, 0, sizeof(req->u));
645 req->flags.is_uring = 1;
646 req->ref_cnt++;
647 req->ch = NULL; /* not needed for uring */
648 req->interrupted = 0;
649 list_init_req(req);
650
651 fuse_session_process_uring_cqe(fuse_ring->se, req, in, &rrh->op_in,
652 ent->op_payload, ent_in_out->payload_sz);
653}
654
655static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue)
656{
657 struct fuse_ring_pool *ring_pool = queue->ring_pool;
658 struct fuse_session *se = ring_pool->se;
659 size_t num_completed = 0;
660 struct io_uring_cqe *cqe;
661 unsigned int head;
662 struct fuse_ring_ent *ent;
663 int ret = 0;
664
665 io_uring_for_each_cqe(&queue->ring, head, cqe) {
666 int err = 0;
667
668 num_completed++;
669
670 err = cqe->res;
671 if (unlikely(err != 0)) {
672 if (err > 0 && ((uintptr_t)io_uring_cqe_get_data(cqe) ==
673 (unsigned int)queue->eventfd)) {
674 /* teardown from eventfd */
675 return -ENOTCONN;
676 }
677
678
679 switch (err) {
680 case -EAGAIN:
681 fallthrough;
682 case -EINTR:
683 ent = io_uring_cqe_get_data(cqe);
684 fuse_uring_resubmit(queue, ent);
685 continue;
686 default:
687 break;
688 }
689
690 /* -ENOTCONN is ok on umount */
691 if (err != -ENOTCONN) {
692 se->error = cqe->res;
693
694 /* return first error */
695 if (ret == 0)
696 ret = err;
697 }
698
699 } else {
700 fuse_uring_handle_cqe(queue, cqe);
701 }
702 }
703
704 if (num_completed)
705 io_uring_cq_advance(&queue->ring, num_completed);
706
707 return ret == 0 ? 0 : num_completed;
708}
709
714static void fuse_uring_set_thread_core(int qid)
715{
716 cpu_set_t mask;
717 int rc;
718
719 CPU_ZERO(&mask);
720 CPU_SET(qid, &mask);
721 rc = sched_setaffinity(0, sizeof(cpu_set_t), &mask);
722 if (rc != 0)
723 fuse_log(FUSE_LOG_ERR, "Failed to bind qid=%d to its core: %s\n",
724 qid, strerror(errno));
725
726 if (0) {
727 const int policy = SCHED_IDLE;
728 const struct sched_param param = {
729 .sched_priority = sched_get_priority_min(policy),
730 };
731
732 /* Set the lowest possible priority, so that the application
733 * submitting requests is not moved away from the current core.
734 */
735 rc = sched_setscheduler(0, policy, &param);
736 if (rc != 0)
737 fuse_log(FUSE_LOG_ERR, "Failed to set scheduler: %s\n",
738 strerror(errno));
739 }
740}
741
742/*
743 * @return negative error code or io-uring file descriptor
744 */
745static int fuse_uring_init_queue(struct fuse_ring_queue *queue)
746{
747 struct fuse_ring_pool *ring = queue->ring_pool;
748 struct fuse_session *se = ring->se;
749 int res;
750 size_t page_sz = sysconf(_SC_PAGESIZE);
751
752 queue->eventfd = eventfd(0, EFD_CLOEXEC);
753 if (queue->eventfd < 0) {
754 res = -errno;
755 fuse_log(FUSE_LOG_ERR,
756 "Failed to create eventfd for qid %d: %s\n",
757 queue->qid, strerror(errno));
758 return res;
759 }
760
761 res = fuse_queue_setup_io_uring(&queue->ring, queue->qid,
762 ring->queue_depth, se->fd,
763 queue->eventfd);
764 if (res != 0) {
765 fuse_log(FUSE_LOG_ERR, "qid=%d io_uring init failed\n",
766 queue->qid);
767 return res;
768 }
769
770 queue->req_header_sz = ROUND_UP(sizeof(struct fuse_uring_req_header),
771 page_sz);
772
773 for (size_t idx = 0; idx < ring->queue_depth; idx++) {
774 struct fuse_ring_ent *ring_ent = &queue->ent[idx];
775 struct fuse_req *req = &ring_ent->req;
776
777 ring_ent->ring_queue = queue;
778
779 /*
780 * Also allocate the header to have it page aligned, which
781 * is a requirement for page pinning
782 */
783 ring_ent->req_header =
784 numa_alloc_local(queue->req_header_sz);
785 if (!ring_ent->req_header)
786 return -ENOMEM;
787 ring_ent->req_payload_sz = ring->max_req_payload_sz;
788
789 ring_ent->op_payload =
790 numa_alloc_local(ring_ent->req_payload_sz);
791 if (!ring_ent->op_payload)
792 return -ENOMEM;
793
794 req->se = se;
795 pthread_mutex_init(&req->lock, NULL);
796 req->flags.is_uring = 1;
797 req->ref_cnt = 1; /* extra ref to avoid destruction */
798 list_init_req(req);
799 }
800
801 res = fuse_uring_register_queue(queue);
802 if (res != 0) {
803 fuse_log(
804 FUSE_LOG_ERR,
805 "Grave fuse-uring error on preparing SQEs, aborting\n");
806 se->error = -EIO;
808 return res;
809 }
810
811 return queue->ring.ring_fd;
812}
813
814static void *fuse_uring_thread(void *arg)
815{
816 struct fuse_ring_queue *queue = arg;
817 struct fuse_ring_pool *ring_pool = queue->ring_pool;
818 struct fuse_session *se = ring_pool->se;
819 int err;
820 char thread_name[16] = { 0 };
821
822 snprintf(thread_name, 16, "fuse-ring-%d", queue->qid);
823 thread_name[15] = '\0';
824 fuse_set_thread_name(thread_name);
825
826 fuse_uring_set_thread_core(queue->qid);
827
828 err = fuse_uring_init_queue(queue);
829 pthread_mutex_lock(&ring_pool->thread_start_mutex);
830 if (err < 0)
831 ring_pool->failed_threads++;
832 ring_pool->started_threads++;
833 pthread_cond_broadcast(&ring_pool->thread_start_cond);
834 pthread_mutex_unlock(&ring_pool->thread_start_mutex);
835
836 if (err < 0) {
837 fuse_log(FUSE_LOG_ERR, "qid=%d queue setup failed\n",
838 queue->qid);
839 goto err_non_fatal;
840 }
841
842 sem_wait(&ring_pool->init_sem);
843
844 /* Not using fuse_session_exited(se), as that cannot be inlined */
845 while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) {
846 io_uring_submit_and_wait(&queue->ring, 1);
847
848 pthread_mutex_lock(&queue->ring_lock);
849 queue->cqe_processing = true;
850 err = fuse_uring_queue_handle_cqes(queue);
851 queue->cqe_processing = false;
852 pthread_mutex_unlock(&queue->ring_lock);
853 if (err < 0)
854 goto err;
855 }
856
857 return NULL;
858
859err:
861err_non_fatal:
862 return NULL;
863}
864
865static int fuse_uring_start_ring_threads(struct fuse_ring_pool *ring)
866{
867 int rc = 0;
868
869 for (size_t qid = 0; qid < ring->nr_queues; qid++) {
870 struct fuse_ring_queue *queue = fuse_uring_get_queue(ring, qid);
871
872 rc = pthread_create(&queue->tid, NULL, fuse_uring_thread, queue);
873 if (rc != 0)
874 break;
875 }
876
877 return rc;
878}
879
880static int fuse_uring_sanity_check(struct fuse_session *se)
881{
882 if (se->uring.q_depth == 0) {
883 fuse_log(FUSE_LOG_ERR, "io-uring queue depth must be > 0\n");
884 return -EINVAL;
885 }
886
887 _Static_assert(sizeof(struct fuse_uring_cmd_req) <=
888 FUSE_URING_MAX_SQE128_CMD_DATA,
889 "SQE128_CMD_DATA has 80B cmd data");
890
891 return 0;
892}
893
894int fuse_uring_start(struct fuse_session *se)
895{
896 int err = 0;
897 struct fuse_ring_pool *fuse_ring;
898
899 fuse_uring_sanity_check(se);
900
901 fuse_ring = fuse_create_ring(se);
902 if (fuse_ring == NULL) {
903 err = -EADDRNOTAVAIL;
904 goto err;
905 }
906
907 se->uring.pool = fuse_ring;
908
909 /* Hold off threads from send fuse ring entries (SQEs) */
910 sem_init(&fuse_ring->init_sem, 0, 0);
911 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
912 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
913
914 err = fuse_uring_start_ring_threads(fuse_ring);
915 if (err)
916 goto err;
917
918 /*
919 * Wait for all threads to start or to fail
920 */
921 pthread_mutex_lock(&fuse_ring->thread_start_mutex);
922 while (fuse_ring->started_threads < fuse_ring->nr_queues)
923 pthread_cond_wait(&fuse_ring->thread_start_cond,
924 &fuse_ring->thread_start_mutex);
925
926 if (fuse_ring->failed_threads != 0)
927 err = -EADDRNOTAVAIL;
928 pthread_mutex_unlock(&fuse_ring->thread_start_mutex);
929
930err:
931 if (err) {
932 /* Note all threads need to have been started */
933 if (fuse_ring)
934 fuse_session_destruct_uring(fuse_ring);
935 se->uring.pool = NULL;
936 }
937 return err;
938}
939
940int fuse_uring_stop(struct fuse_session *se)
941{
942 struct fuse_ring_pool *ring = se->uring.pool;
943
944 if (ring == NULL)
945 return 0;
946
947 fuse_session_destruct_uring(ring);
948
949 return 0;
950}
951
952void fuse_uring_wake_ring_threads(struct fuse_session *se)
953{
954 struct fuse_ring_pool *ring = se->uring.pool;
955
956 /* Wake up the threads to let them send SQEs */
957 for (size_t qid = 0; qid < ring->nr_queues; qid++)
958 sem_post(&ring->init_sem);
959}
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
fuse_buf_copy_flags
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
void fuse_session_exit(struct fuse_session *se)
struct fuse_req * fuse_req_t
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
void * mem
size_t size
struct fuse_buf buf[1]