diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-20 13:47:56 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-20 13:47:56 -0700 |
| commit | f5437ff7299e47e76e52d37a2937a4b0f04e399f (patch) | |
| tree | 29c5fec2e09c24835ede4c62a5a5edd4113d27f8 | |
| parent | c84d3e3130dfe1058cb27dc78e7ad8bd36f0545a (diff) | |
| parent | 881dc9dd66d2367e7fe1be0f953a8837cb3b26a9 (diff) | |
| download | linux-f5437ff7299e47e76e52d37a2937a4b0f04e399f.tar.gz linux-f5437ff7299e47e76e52d37a2937a4b0f04e399f.zip | |
Merge tag 'for-7.3/io_uring-20260819' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux
Pull io_uring update from Jens Axboe:
"On top of the usual cleanups and fixes, the bigger items in here are:
- zcrx work, most of it centered around adding dynamic area
provisioning, plus a bunch of prep and cleanups leading up to it:
scale refilling with large pages, coalesce same-niov RQEs on
refill, separate the RQ head/tail cache lines and cache the RQ
tail, and rework the area creation locking.
- Fix the futex inflight accounting so that only private futex waits
are marked inflight, and don't mark wake requests as inflight at
all.
- Drop the custom iov copy in the buffer select prep and msg header
copy paths, using the generic helpers instead.
- Fix a folio size overflow in io_vec_fill_bvec(), and account the
pages a compound region really uses in the memmap path.
- Fix an iovec leak in uring_cmd when the async cmd isn't recycled,
skip the blocking task work for io_uring_cmd_issue_blocking(), and
don't skip completion for a synchronous multishot cmd
- Defer eventfd signaling when queued from a wakeup handler
- Fix io-wq worker accounting when canceling creation callbacks
- Annotate remote tasks for kcoverage"
* tag 'for-7.3/io_uring-20260819' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: (30 commits)
io_uring: Add missing include for ITER_SOURCE and ITER_DEST
io_uring/uring_cmd: don't skip completion for a synchronous multishot cmd
io_uring/memmap: account the pages a compound region really uses
io_uring/zcrx: add dynamic area provisioning
io_uring/zcrx: lock area creation with pp_lock
io_uring/zcrx: keep array of areas
io_uring/zcrx: move freelist lock to struct zcrx
io_uring/zcrx: unmap under netdev lock
io_uring/zcrx: split dmabuf unmap and release
io_uring/zcrx: don't pass ifq_reg to area creation
io_uring/zcrx: add helper for deriving area token
io_uring/zcrx: don't reload skb_shinfo
io_urint/zcrx: narrow var scope in io_zcrx_recv_skb()
io_uring/zcrx: constify area_reg on import
io_uring/zcrx: coalesce same-niov RQEs on refill
io_uring/zcrx: cache RQ tail
io_uring/zcrx: add RQ iterator
io_uring/zcrx: move RQ head/tail to separate cache lines
io_uring/zcrx: scale refilling with large pages
io_uring/io-wq: fix worker accounting when canceling creation callbacks
...
| -rw-r--r-- | include/linux/io_uring_types.h | 11 | ||||
| -rw-r--r-- | include/uapi/linux/io_uring/zcrx.h | 7 | ||||
| -rw-r--r-- | io_uring/eventfd.c | 8 | ||||
| -rw-r--r-- | io_uring/eventfd.h | 2 | ||||
| -rw-r--r-- | io_uring/futex.c | 32 | ||||
| -rw-r--r-- | io_uring/futex.h | 1 | ||||
| -rw-r--r-- | io_uring/io-wq.c | 14 | ||||
| -rw-r--r-- | io_uring/io_uring.c | 17 | ||||
| -rw-r--r-- | io_uring/io_uring.h | 2 | ||||
| -rw-r--r-- | io_uring/memmap.c | 37 | ||||
| -rw-r--r-- | io_uring/net.c | 75 | ||||
| -rw-r--r-- | io_uring/opdef.c | 2 | ||||
| -rw-r--r-- | io_uring/poll.c | 23 | ||||
| -rw-r--r-- | io_uring/query.c | 2 | ||||
| -rw-r--r-- | io_uring/rsrc.c | 2 | ||||
| -rw-r--r-- | io_uring/rw.c | 24 | ||||
| -rw-r--r-- | io_uring/sqpoll.c | 7 | ||||
| -rw-r--r-- | io_uring/tw.c | 2 | ||||
| -rw-r--r-- | io_uring/uring_cmd.c | 10 | ||||
| -rw-r--r-- | io_uring/waitid.c | 2 | ||||
| -rw-r--r-- | io_uring/zcrx.c | 398 | ||||
| -rw-r--r-- | io_uring/zcrx.h | 15 |
22 files changed, 448 insertions, 245 deletions
diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index 87151a5b62c1..94936ee297ef 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -6,6 +6,7 @@ #include <linux/task_work.h> #include <linux/bitmap.h> #include <linux/llist.h> +#include <linux/uio.h> #include <uapi/linux/io_uring.h> struct iou_loop_params; @@ -20,6 +21,14 @@ enum { * It's also ignored unless IORING_SETUP_DEFER_TASKRUN is set. */ IOU_F_TWQ_LAZY_WAKE = 1, + + /* + * Set when task_work is queued from a waitqueue wakeup handler, where + * an arbitrary provider waitqueue lock is held. Signaling the CQ ring + * eventfd inline from there can recurse back into that lock through + * epoll, so the eventfd signal must be deferred. + */ + IOU_F_TWQ_IN_WAKE = 2, }; enum io_uring_cmd_flags { @@ -534,6 +543,8 @@ struct io_ring_ctx { struct io_mapped_region ring_region; /* used for optimised request parameter and wait argument passing */ struct io_mapped_region param_region; + + struct kcov_common_handle_id kcov_handle; }; /* diff --git a/include/uapi/linux/io_uring/zcrx.h b/include/uapi/linux/io_uring/zcrx.h index e01bc0e34b24..99b8636fdabb 100644 --- a/include/uapi/linux/io_uring/zcrx.h +++ b/include/uapi/linux/io_uring/zcrx.h @@ -116,6 +116,7 @@ enum zcrx_ctrl_op { ZCRX_CTRL_FLUSH_RQ, ZCRX_CTRL_EXPORT, ZCRX_CTRL_ARM_EVENT, + ZCRX_CTRL_ADD_AREA, __ZCRX_CTRL_LAST, }; @@ -134,6 +135,11 @@ struct zcrx_ctrl_arm_event { __u32 __resv[11]; }; +struct zcrx_ctrl_add_area { + __u64 area_ptr; /* pointer to struct io_uring_zcrx_area_reg */ + __u64 __resv[5]; +}; + struct zcrx_ctrl { __u32 zcrx_id; __u32 op; /* see enum zcrx_ctrl_op */ @@ -143,6 +149,7 @@ struct zcrx_ctrl { struct zcrx_ctrl_export zc_export; struct zcrx_ctrl_flush_rq zc_flush; struct zcrx_ctrl_arm_event zc_arm_event; + struct zcrx_ctrl_add_area zc_area; }; }; diff --git a/io_uring/eventfd.c b/io_uring/eventfd.c index d656cc2a0b9b..63fe6e5d79ba 100644 --- a/io_uring/eventfd.c +++ b/io_uring/eventfd.c @@ -51,9 +51,9 @@ static void io_eventfd_do_signal(struct rcu_head *rcu) /* * Returns true if the caller should put the ev_fd reference, false if not. */ -static bool __io_eventfd_signal(struct io_ev_fd *ev_fd) +static bool __io_eventfd_signal(struct io_ev_fd *ev_fd, bool defer) { - if (eventfd_signal_allowed()) { + if (!defer && eventfd_signal_allowed()) { eventfd_signal_mask(ev_fd->cq_ev_fd, EPOLL_URING_WAKE); return true; } @@ -73,7 +73,7 @@ static bool io_eventfd_trigger(struct io_ev_fd *ev_fd) return !ev_fd->eventfd_async || io_wq_current_is_worker(); } -void io_eventfd_signal(struct io_ring_ctx *ctx, bool cqe_event) +void io_eventfd_signal(struct io_ring_ctx *ctx, bool cqe_event, bool defer) { bool skip = false; struct io_ev_fd *ev_fd; @@ -113,7 +113,7 @@ void io_eventfd_signal(struct io_ring_ctx *ctx, bool cqe_event) spin_unlock(&ctx->completion_lock); } - if (skip || __io_eventfd_signal(ev_fd)) + if (skip || __io_eventfd_signal(ev_fd, defer)) io_eventfd_put(ev_fd); } diff --git a/io_uring/eventfd.h b/io_uring/eventfd.h index 400eda4a4165..e965d80d9fdc 100644 --- a/io_uring/eventfd.h +++ b/io_uring/eventfd.h @@ -5,4 +5,4 @@ int io_eventfd_register(struct io_ring_ctx *ctx, void __user *arg, unsigned int eventfd_async); int io_eventfd_unregister(struct io_ring_ctx *ctx); -void io_eventfd_signal(struct io_ring_ctx *ctx, bool cqe_event); +void io_eventfd_signal(struct io_ring_ctx *ctx, bool cqe_event, bool defer); diff --git a/io_uring/futex.c b/io_uring/futex.c index 906701b3c5c6..eaee14242a3a 100644 --- a/io_uring/futex.c +++ b/io_uring/futex.c @@ -149,8 +149,21 @@ int io_futex_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) !futex_validate_input(iof->futex_flags, iof->futex_mask)) return -EINVAL; - /* Mark as inflight, so file exit cancelation will find it */ - io_req_track_inflight(req); + return 0; +} + +int io_futex_wait_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) +{ + struct io_futex *iof = io_kiocb_to_cmd(req, struct io_futex); + int ret; + + ret = io_futex_prep(req, sqe); + if (unlikely(ret)) + return ret; + + /* inflight tracking only needed for mm private hash */ + if (!(iof->futex_flags & FLAGS_SHARED)) + io_req_track_inflight(req); return 0; } @@ -168,13 +181,14 @@ static void io_futex_wakev_fn(struct wake_q_head *wake_q, struct futex_q *q) io_req_set_res(req, 0, 0); req->io_task_work.func = io_futexv_complete; - io_req_task_work_add(req); + __io_req_task_work_add(req, IOU_F_TWQ_IN_WAKE); } int io_futexv_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) { struct io_futex *iof = io_kiocb_to_cmd(req, struct io_futex); struct io_futexv_data *ifd; + unsigned int i; int ret; /* No flags or mask supported for waitv */ @@ -199,8 +213,14 @@ int io_futexv_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) return ret; } - /* Mark as inflight, so file exit cancelation will find it */ - io_req_track_inflight(req); + /* inflight tracking only needed for mm private hash */ + for (i = 0; i < iof->futex_nr; i++) { + if (!(ifd->futexv[i].w.flags & FLAGS_SHARED)) { + io_req_track_inflight(req); + break; + } + } + iof->futexv_unqueued = 0; req->flags |= REQ_F_ASYNC_DATA; req->async_data = ifd; @@ -217,7 +237,7 @@ static void io_futex_wake_fn(struct wake_q_head *wake_q, struct futex_q *q) io_req_set_res(req, 0, 0); req->io_task_work.func = io_futex_complete; - io_req_task_work_add(req); + __io_req_task_work_add(req, IOU_F_TWQ_IN_WAKE); } int io_futexv_wait(struct io_kiocb *req, unsigned int issue_flags) diff --git a/io_uring/futex.h b/io_uring/futex.h index d789fcf715e3..987db3f2c6d9 100644 --- a/io_uring/futex.h +++ b/io_uring/futex.h @@ -3,6 +3,7 @@ #include "cancel.h" int io_futex_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); +int io_futex_wait_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); int io_futexv_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); int io_futex_wait(struct io_kiocb *req, unsigned int issue_flags); int io_futexv_wait(struct io_kiocb *req, unsigned int issue_flags); diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 2e14880eef92..2ca223e47d41 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -19,6 +19,7 @@ #include <linux/mmu_context.h> #include <linux/sched/sysctl.h> #include <uapi/linux/io_uring.h> +#include <linux/kcov.h> #include "io-wq.h" #include "slist.h" @@ -211,9 +212,12 @@ static void io_worker_cancel_cb(struct io_worker *worker) struct io_wq *wq = worker->wq; atomic_dec(&acct->nr_running); - raw_spin_lock(&acct->workers_lock); - acct->nr_workers--; - raw_spin_unlock(&acct->workers_lock); + /* create_worker_cb() has not reserved a worker slot yet. */ + if (worker->create_work.func != create_worker_cb) { + raw_spin_lock(&acct->workers_lock); + acct->nr_workers--; + raw_spin_unlock(&acct->workers_lock); + } io_worker_ref_put(wq); clear_bit_unlock(0, &worker->create_state); io_worker_release(worker); @@ -643,13 +647,17 @@ static void io_worker_handle_work(struct io_wq_acct *acct, unsigned int hash = __io_wq_is_hashed(work_flags) ? __io_get_work_hash(work_flags) : -1U; + struct io_kiocb *req; next_hashed = wq_next_work(work); if (do_kill && (work_flags & IO_WQ_WORK_UNBOUND)) atomic_or(IO_WQ_WORK_CANCEL, &work->flags); + req = container_of(work, struct io_kiocb, work); + kcov_remote_start_common(req->ctx->kcov_handle); io_wq_submit_work(work); + kcov_remote_stop(); io_assign_current_work(worker, NULL); linked = io_wq_free_work(work); diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 1ea2fca34a36..76f049e29aa2 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -59,6 +59,7 @@ #include <linux/audit.h> #include <linux/security.h> #include <linux/jump_label.h> +#include <linux/kcov.h> #define CREATE_TRACE_POINTS #include <trace/events/io_uring.h> @@ -293,6 +294,7 @@ static __cold struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p) INIT_HLIST_HEAD(&ctx->cancelable_uring_cmd); io_napi_init(ctx); mutex_init(&ctx->mmap_lock); + ctx->kcov_handle = kcov_common_handle(); return ctx; @@ -405,7 +407,7 @@ static void io_prep_async_link(struct io_kiocb *req) } } -static void io_queue_iowq(struct io_kiocb *req) +void io_queue_iowq(struct io_kiocb *req) { struct io_uring_task *tctx = req->tctx; @@ -433,17 +435,6 @@ static void io_queue_iowq(struct io_kiocb *req) io_wq_enqueue(tctx->io_wq, &req->work); } -static void io_req_queue_iowq_tw(struct io_tw_req tw_req, io_tw_token_t tw) -{ - io_queue_iowq(tw_req.req); -} - -void io_req_queue_iowq(struct io_kiocb *req) -{ - req->io_task_work.func = io_req_queue_iowq_tw; - io_req_task_work_add(req); -} - unsigned io_linked_nr(struct io_kiocb *req) { struct io_kiocb *tmp; @@ -484,7 +475,7 @@ void __io_commit_cqring_flush(struct io_ring_ctx *ctx) if (ctx->int_flags & IO_RING_F_OFF_TIMEOUT_USED) io_flush_timeouts(ctx); if (ctx->int_flags & IO_RING_F_HAS_EVFD) - io_eventfd_signal(ctx, true); + io_eventfd_signal(ctx, true, false); } static inline void __io_cq_lock(struct io_ring_ctx *ctx) diff --git a/io_uring/io_uring.h b/io_uring/io_uring.h index cb736b815422..dfe26a9c21bf 100644 --- a/io_uring/io_uring.h +++ b/io_uring/io_uring.h @@ -195,7 +195,7 @@ __cold void io_uring_drop_tctx_refs(struct task_struct *task); int io_ring_add_registered_file(struct io_uring_task *tctx, struct file *file, int start, int end); -void io_req_queue_iowq(struct io_kiocb *req); +void io_queue_iowq(struct io_kiocb *req); int io_poll_issue(struct io_kiocb *req, io_tw_token_t tw); int io_submit_sqes(struct io_ring_ctx *ctx, unsigned int nr); diff --git a/io_uring/memmap.c b/io_uring/memmap.c index 23e8a85111bc..48c0eb012412 100644 --- a/io_uring/memmap.c +++ b/io_uring/memmap.c @@ -16,8 +16,10 @@ #include "zcrx.h" static bool io_mem_alloc_compound(struct page **pages, int nr_pages, - size_t size, gfp_t gfp) + size_t size, gfp_t gfp, + struct user_struct *user) { + unsigned long nr_compound, extra; struct page *page; int i, order; @@ -27,9 +29,22 @@ static bool io_mem_alloc_compound(struct page **pages, int nr_pages, else if (order) gfp |= __GFP_COMP; + /* + * get_order() rounds a non power of two size up, so the allocation + * can hold more pages than the region exposes. Account those too, + * and leave the compound allocation alone if they do not fit. + */ + nr_compound = 1UL << order; + extra = nr_compound - nr_pages; + if (extra && user && __io_account_mem(user, extra)) + return false; + page = alloc_pages(gfp, order); - if (!page) + if (!page) { + if (extra && user) + __io_unaccount_mem(user, extra); return false; + } for (i = 0; i < nr_pages; i++) pages[i] = page + i; @@ -105,8 +120,15 @@ void io_free_region(struct user_struct *user, struct io_mapped_region *mr) } if ((mr->flags & IO_REGION_F_VMAP) && mr->ptr) vunmap(mr->ptr); - if (mr->nr_pages && user) - __io_unaccount_mem(user, mr->nr_pages); + if (mr->nr_pages && user) { + unsigned long nr_accounted = mr->nr_pages; + + /* a compound region was accounted for the whole allocation */ + if (mr->flags & IO_REGION_F_SINGLE_REF) + nr_accounted = 1UL << get_order(io_region_size(mr)); + + __io_unaccount_mem(user, nr_accounted); + } memset(mr, 0, sizeof(*mr)); } @@ -151,7 +173,8 @@ static int io_region_pin_pages(struct io_mapped_region *mr, static int io_region_allocate_pages(struct io_mapped_region *mr, struct io_uring_region_desc *reg, - unsigned long mmap_offset) + unsigned long mmap_offset, + struct user_struct *user) { gfp_t gfp = GFP_KERNEL_ACCOUNT | __GFP_ZERO | __GFP_NOWARN; size_t size = io_region_size(mr); @@ -162,7 +185,7 @@ static int io_region_allocate_pages(struct io_mapped_region *mr, if (!pages) return -ENOMEM; - if (io_mem_alloc_compound(pages, mr->nr_pages, size, gfp)) { + if (io_mem_alloc_compound(pages, mr->nr_pages, size, gfp, user)) { mr->flags |= IO_REGION_F_SINGLE_REF; goto done; } @@ -217,7 +240,7 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, if (reg->flags & IORING_MEM_REGION_TYPE_USER) ret = io_region_pin_pages(mr, reg); else - ret = io_region_allocate_pages(mr, reg, mmap_offset); + ret = io_region_allocate_pages(mr, reg, mmap_offset, ctx->user); if (ret) goto out_free; diff --git a/io_uring/net.c b/io_uring/net.c index a74d15f7b7d2..3f04aa1054c6 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -236,39 +236,6 @@ static int io_net_import_vec(struct io_kiocb *req, struct io_async_msghdr *iomsg return 0; } -static int io_compat_msg_copy_hdr(struct io_kiocb *req, - struct io_async_msghdr *iomsg, - struct compat_msghdr *msg, int ddir, - struct sockaddr __user **save_addr) -{ - struct io_sr_msg *sr = io_kiocb_to_cmd(req, struct io_sr_msg); - struct compat_iovec __user *uiov; - int ret; - - if (copy_from_user(msg, sr->umsg_compat, sizeof(*msg))) - return -EFAULT; - - ret = __get_compat_msghdr(&iomsg->msg, msg, save_addr); - if (ret) - return ret; - - uiov = compat_ptr(msg->msg_iov); - if (req->flags & REQ_F_BUFFER_SELECT) { - if (msg->msg_iovlen == 0) { - sr->len = 0; - } else if (msg->msg_iovlen > 1) { - return -EINVAL; - } else { - struct compat_iovec tmp_iov; - - if (copy_from_user(&tmp_iov, uiov, sizeof(tmp_iov))) - return -EFAULT; - sr->len = tmp_iov.iov_len; - } - } - return 0; -} - static int io_copy_msghdr_from_user(struct user_msghdr *msg, struct user_msghdr __user *umsg) { @@ -288,11 +255,10 @@ ua_end: } static int io_msg_copy_hdr(struct io_kiocb *req, struct io_async_msghdr *iomsg, - struct user_msghdr *msg, int ddir, + struct user_msghdr *msg, struct sockaddr __user **save_addr) { struct io_sr_msg *sr = io_kiocb_to_cmd(req, struct io_sr_msg); - struct user_msghdr __user *umsg = sr->umsg; int ret; iomsg->msg.msg_name = &iomsg->addr; @@ -301,7 +267,10 @@ static int io_msg_copy_hdr(struct io_kiocb *req, struct io_async_msghdr *iomsg, if (io_is_compat(req->ctx)) { struct compat_msghdr cmsg; - ret = io_compat_msg_copy_hdr(req, iomsg, &cmsg, ddir, save_addr); + if (copy_from_user(&cmsg, sr->umsg_compat, sizeof(cmsg))) + return -EFAULT; + + ret = __get_compat_msghdr(&iomsg->msg, &cmsg, save_addr); if (ret) return ret; @@ -310,18 +279,17 @@ static int io_msg_copy_hdr(struct io_kiocb *req, struct io_async_msghdr *iomsg, msg->msg_controllen = cmsg.msg_controllen; msg->msg_iov = compat_ptr(cmsg.msg_iov); msg->msg_iovlen = cmsg.msg_iovlen; - return 0; - } - - ret = io_copy_msghdr_from_user(msg, umsg); - if (unlikely(ret)) - return ret; + } else { + ret = io_copy_msghdr_from_user(msg, sr->umsg); + if (unlikely(ret)) + return ret; - msg->msg_flags = 0; + msg->msg_flags = 0; - ret = __copy_msghdr(&iomsg->msg, msg, save_addr); - if (ret) - return ret; + ret = __copy_msghdr(&iomsg->msg, msg, save_addr); + if (ret) + return ret; + } if (req->flags & REQ_F_BUFFER_SELECT) { if (msg->msg_iovlen == 0) { @@ -329,12 +297,13 @@ static int io_msg_copy_hdr(struct io_kiocb *req, struct io_async_msghdr *iomsg, } else if (msg->msg_iovlen > 1) { return -EINVAL; } else { - struct iovec __user *uiov = msg->msg_iov; - struct iovec tmp_iov; + struct iovec fast_iov, *iov; - if (copy_from_user(&tmp_iov, uiov, sizeof(tmp_iov))) - return -EFAULT; - sr->len = tmp_iov.iov_len; + iov = iovec_from_user(msg->msg_iov, 1, 1, &fast_iov, + io_is_compat(req->ctx)); + if (IS_ERR(iov)) + return PTR_ERR(iov); + sr->len = iov->iov_len; } } return 0; @@ -402,7 +371,7 @@ static int io_sendmsg_setup(struct io_kiocb *req, const struct io_uring_sqe *sqe sr->flags |= IORING_SEND_VECTORIZED; sr->umsg = u64_to_user_ptr(READ_ONCE(sqe->addr)); - ret = io_msg_copy_hdr(req, kmsg, &msg, ITER_SOURCE, NULL); + ret = io_msg_copy_hdr(req, kmsg, &msg, NULL); if (unlikely(ret)) return ret; /* save msg_control as sys_sendmsg() overwrites it */ @@ -761,7 +730,7 @@ static int io_recvmsg_copy_hdr(struct io_kiocb *req, struct user_msghdr msg; int ret; - ret = io_msg_copy_hdr(req, iomsg, &msg, ITER_DEST, &iomsg->uaddr); + ret = io_msg_copy_hdr(req, iomsg, &msg, &iomsg->uaddr); if (unlikely(ret)) return ret; diff --git a/io_uring/opdef.c b/io_uring/opdef.c index 4e58eb1344ea..cf3aa2242cd7 100644 --- a/io_uring/opdef.c +++ b/io_uring/opdef.c @@ -467,7 +467,7 @@ const struct io_issue_def io_issue_defs[] = { }, [IORING_OP_FUTEX_WAIT] = { #if defined(CONFIG_FUTEX) - .prep = io_futex_prep, + .prep = io_futex_wait_prep, .issue = io_futex_wait, #else .prep = io_eopnotsupp_prep, diff --git a/io_uring/poll.c b/io_uring/poll.c index 0204affdc308..5447a7c24dce 100644 --- a/io_uring/poll.c +++ b/io_uring/poll.c @@ -208,9 +208,9 @@ enum { IOU_POLL_REQUEUE = 4, }; -static void __io_poll_execute(struct io_kiocb *req, int mask) +static void __io_poll_execute(struct io_kiocb *req, int mask, unsigned tw_flags) { - unsigned flags = 0; + unsigned flags = tw_flags; io_req_set_res(req, mask, 0); req->io_task_work.func = io_poll_task_func; @@ -218,14 +218,15 @@ static void __io_poll_execute(struct io_kiocb *req, int mask) trace_io_uring_task_add(req, mask); if (!(req->flags & REQ_F_POLL_NO_LAZY)) - flags = IOU_F_TWQ_LAZY_WAKE; + flags |= IOU_F_TWQ_LAZY_WAKE; __io_req_task_work_add(req, flags); } -static inline void io_poll_execute(struct io_kiocb *req, int res) +static inline void io_poll_execute(struct io_kiocb *req, int res, + unsigned tw_flags) { if (io_poll_get_ownership(req)) - __io_poll_execute(req, res); + __io_poll_execute(req, res, tw_flags); } /* @@ -344,7 +345,7 @@ void io_poll_task_func(struct io_tw_req tw_req, io_tw_token_t tw) if (ret == IOU_POLL_NO_ACTION) { return; } else if (ret == IOU_POLL_REQUEUE) { - __io_poll_execute(req, 0); + __io_poll_execute(req, 0, 0); return; } io_poll_remove_entries(req); @@ -383,7 +384,7 @@ static void io_poll_cancel_req(struct io_kiocb *req) { io_poll_mark_cancelled(req); /* kick tw, which should complete the request */ - io_poll_execute(req, 0); + io_poll_execute(req, 0, 0); } #define IO_ASYNC_POLL_COMMON (EPOLLONESHOT | EPOLLPRI) @@ -392,7 +393,7 @@ static __cold int io_pollfree_wake(struct io_kiocb *req, struct io_poll *poll) { io_poll_mark_cancelled(req); /* we have to kick tw in case it's not already */ - io_poll_execute(req, 0); + io_poll_execute(req, 0, IOU_F_TWQ_IN_WAKE); io_poll_remove_waitq(poll); return 1; } @@ -430,7 +431,7 @@ static int io_poll_wake(struct wait_queue_entry *wait, unsigned mode, int sync, else req->flags &= ~REQ_F_SINGLE_POLL; } - __io_poll_execute(req, mask); + __io_poll_execute(req, mask, IOU_F_TWQ_IN_WAKE); } return 1; } @@ -618,7 +619,7 @@ static int __io_arm_poll_handler(struct io_kiocb *req, if (mask && (poll->events & EPOLLET) && io_poll_can_finish_inline(req, ipt)) { - __io_poll_execute(req, mask); + __io_poll_execute(req, mask, 0); return 0; } io_napi_add(req); @@ -629,7 +630,7 @@ static int __io_arm_poll_handler(struct io_kiocb *req, * poll was waken up, queue up a tw, it'll deal with it. */ if (atomic_cmpxchg(&req->poll_refs, 1, 0) != 1) - __io_poll_execute(req, 0); + __io_poll_execute(req, 0, 0); } return 0; } diff --git a/io_uring/query.c b/io_uring/query.c index 88a325736992..4321f0b94b4b 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -38,7 +38,7 @@ static ssize_t io_query_zcrx(union io_query_data *data) e->register_flags = ZCRX_SUPPORTED_REG_FLAGS; e->area_flags = IORING_ZCRX_AREA_DMABUF; e->nr_ctrl_opcodes = __ZCRX_CTRL_LAST; - e->rq_hdr_size = sizeof(struct io_uring); + e->rq_hdr_size = sizeof(struct zcrx_rq_hdr); e->rq_hdr_alignment = L1_CACHE_BYTES; e->features = ZCRX_FEATURES; e->__resv2 = 0; diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c index 98dccefd801b..174f74cbbf60 100644 --- a/io_uring/rsrc.c +++ b/io_uring/rsrc.c @@ -1477,7 +1477,7 @@ static int io_vec_fill_bvec(int ddir, struct iov_iter *iter, struct iovec *iovec, unsigned nr_iovs, struct iou_vec *vec) { - unsigned long folio_size = 1 << imu->folio_shift; + unsigned long folio_size = 1UL << imu->folio_shift; unsigned long folio_mask = folio_size - 1; struct bio_vec *res_bvec = vec->bvec; size_t total_len = 0; diff --git a/io_uring/rw.c b/io_uring/rw.c index 95038cfda615..95106dd1d7eb 100644 --- a/io_uring/rw.c +++ b/io_uring/rw.c @@ -9,7 +9,6 @@ #include <linux/fsnotify.h> #include <linux/poll.h> #include <linux/nospec.h> -#include <linux/compat.h> #include <linux/io_uring/cmd.h> #include <linux/indirect_call_wrapper.h> @@ -50,33 +49,20 @@ static bool io_file_supports_nowait(struct io_kiocb *req, __poll_t mask) return false; } -static int io_iov_compat_buffer_select_prep(struct io_rw *rw) -{ - struct compat_iovec __user *uiov = u64_to_user_ptr(rw->addr); - struct compat_iovec iov; - - if (copy_from_user(&iov, uiov, sizeof(iov))) - return -EFAULT; - rw->len = iov.iov_len; - return 0; -} - static int io_iov_buffer_select_prep(struct io_kiocb *req) { struct iovec __user *uiov; - struct iovec iov; + struct iovec fast_iov, *iov; struct io_rw *rw = io_kiocb_to_cmd(req, struct io_rw); if (rw->len != 1) return -EINVAL; - if (io_is_compat(req->ctx)) - return io_iov_compat_buffer_select_prep(rw); - uiov = u64_to_user_ptr(rw->addr); - if (copy_from_user(&iov, uiov, sizeof(*uiov))) - return -EFAULT; - rw->len = iov.iov_len; + iov = iovec_from_user(uiov, 1, 1, &fast_iov, io_is_compat(req->ctx)); + if (IS_ERR(iov)) + return PTR_ERR(iov); + rw->len = iov->iov_len; return 0; } diff --git a/io_uring/sqpoll.c b/io_uring/sqpoll.c index 2460bd605266..ad42e8eb1002 100644 --- a/io_uring/sqpoll.c +++ b/io_uring/sqpoll.c @@ -13,6 +13,7 @@ #include <linux/cpuset.h> #include <linux/sched/cputime.h> #include <linux/io_uring.h> +#include <linux/kcov.h> #include <uapi/linux/io_uring.h> @@ -332,10 +333,14 @@ static int io_sq_thread(void *data) cap_entries = !list_is_singular(&sqd->ctx_list); list_for_each_entry(ctx, &sqd->ctx_list, sqd_list) { - int ret = __io_sq_thread(ctx, sqd, cap_entries, &ist); + int ret; + + kcov_remote_start_common(ctx->kcov_handle); + ret = __io_sq_thread(ctx, sqd, cap_entries, &ist); if (!sqt_spin && (ret > 0 || !list_empty(&ctx->iopoll_list))) sqt_spin = true; + kcov_remote_stop(); } if (io_sq_tw(IORING_TW_CAP_ENTRIES_VALUE)) sqt_spin = true; diff --git a/io_uring/tw.c b/io_uring/tw.c index e6ee15571e85..f573bcc3af6a 100644 --- a/io_uring/tw.c +++ b/io_uring/tw.c @@ -173,7 +173,7 @@ void io_req_local_work_add(struct io_kiocb *req, unsigned flags) if (mpscq_push(&ctx->work_list, &req->io_task_work.node)) { io_ctx_mark_taskrun(ctx); if (data_race(ctx->int_flags) & IO_RING_F_HAS_EVFD) - io_eventfd_signal(ctx, false); + io_eventfd_signal(ctx, false, flags & IOU_F_TWQ_IN_WAKE); } /* diff --git a/io_uring/uring_cmd.c b/io_uring/uring_cmd.c index c14c22cff49e..726a659f38c3 100644 --- a/io_uring/uring_cmd.c +++ b/io_uring/uring_cmd.c @@ -38,6 +38,8 @@ static void io_req_uring_cleanup(struct io_kiocb *req, unsigned int issue_flags) if (io_alloc_cache_put(&req->ctx->cmd_cache, ac)) { ioucmd->sqe = NULL; io_req_async_data_clear(req, REQ_F_NEED_CLEANUP); + } else { + io_vec_free(&ac->vec); } } @@ -208,6 +210,8 @@ int io_uring_cmd_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) ac = io_uring_alloc_async_data(&req->ctx->cmd_cache, req); if (!ac) return -ENOMEM; + if (ac->vec.iovec) + req->flags |= REQ_F_NEED_CLEANUP; ioucmd->sqe = sqe; return 0; } @@ -269,10 +273,6 @@ int io_uring_cmd(struct io_kiocb *req, unsigned int issue_flags) } ret = file->f_op->uring_cmd(ioucmd, issue_flags); - if (ioucmd->flags & IORING_URING_CMD_MULTISHOT) { - if (ret >= 0) - return IOU_ISSUE_SKIP_COMPLETE; - } if (ret == -EAGAIN) { ioucmd->flags |= IORING_URING_CMD_REISSUE; return ret; @@ -326,7 +326,7 @@ void io_uring_cmd_issue_blocking(struct io_uring_cmd *ioucmd) { struct io_kiocb *req = cmd_to_io_kiocb(ioucmd); - io_req_queue_iowq(req); + io_queue_iowq(req); } int io_cmd_poll_multishot(struct io_uring_cmd *cmd, diff --git a/io_uring/waitid.c b/io_uring/waitid.c index 32f68fd7fcdd..76af129ba8ca 100644 --- a/io_uring/waitid.c +++ b/io_uring/waitid.c @@ -253,7 +253,7 @@ static int io_waitid_wait(struct wait_queue_entry *wait, unsigned mode, return 1; req->io_task_work.func = io_waitid_cb; - io_req_task_work_add(req); + __io_req_task_work_add(req, IOU_F_TWQ_IN_WAKE); return 1; } diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index f1464ea8ca64..1b3b11405dac 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -28,10 +28,28 @@ #include "zcrx.h" #include "rsrc.h" +#define ZCRX_MAX_FRAGS_PER_PAGE MAX(PAGE_SIZE / 1024, 1) +/* + * We need a reasonable limit to be able to fill in 64 entries on average + * for 1500 byte MTU. Over-estimate it to keep it pow2. + */ +#define ZCRX_REFILL_CAP MIN(64 * ZCRX_MAX_FRAGS_PER_PAGE, 1024) + #define IO_ZCRX_AREA_SUPPORTED_FLAGS (IORING_ZCRX_AREA_DMABUF) +#define ZCRX_MAX_AREAS 1024 #define IO_DMA_ATTR (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING) +static inline u32 zcrx_next_area_id(struct io_zcrx_ifq *zcrx) +{ + return zcrx->nr_areas; +} + +static inline u64 zcrx_area_id_to_token(u32 area_id) +{ + return (u64)area_id << IORING_ZCRX_AREA_SHIFT; +} + static inline struct io_zcrx_ifq *io_pp_to_ifq(struct page_pool *pp) { return pp->mp_priv; @@ -109,27 +127,31 @@ static int io_populate_area_dma(struct io_zcrx_ifq *ifq, return 0; } -static void io_release_dmabuf(struct io_zcrx_mem *mem) +static void io_unmap_dmabuf(struct io_zcrx_mem *mem) { if (!IS_ENABLED(CONFIG_DMA_SHARED_BUFFER)) return; - if (mem->sgt) dma_buf_unmap_attachment_unlocked(mem->attach, mem->sgt, DMA_FROM_DEVICE); if (mem->attach) dma_buf_detach(mem->dmabuf, mem->attach); - if (mem->dmabuf) - dma_buf_put(mem->dmabuf); - mem->sgt = NULL; mem->attach = NULL; +} + +static void io_release_dmabuf(struct io_zcrx_mem *mem) +{ + if (!IS_ENABLED(CONFIG_DMA_SHARED_BUFFER)) + return; + if (mem->dmabuf) + dma_buf_put(mem->dmabuf); mem->dmabuf = NULL; } static int io_import_dmabuf(struct io_zcrx_ifq *ifq, struct io_zcrx_mem *mem, - struct io_uring_zcrx_area_reg *area_reg) + const struct io_uring_zcrx_area_reg *area_reg) { unsigned long off = (unsigned long)area_reg->addr; unsigned long len = (unsigned long)area_reg->len; @@ -178,6 +200,7 @@ static int io_import_dmabuf(struct io_zcrx_ifq *ifq, mem->size = len; return 0; err: + io_unmap_dmabuf(mem); io_release_dmabuf(mem); return ret; } @@ -201,7 +224,7 @@ static unsigned long io_count_account_pages(struct page **pages, unsigned nr_pag static int io_import_umem(struct io_zcrx_ifq *ifq, struct io_zcrx_mem *mem, - struct io_uring_zcrx_area_reg *area_reg) + const struct io_uring_zcrx_area_reg *area_reg) { struct page **pages; int nr_pages, ret; @@ -267,7 +290,7 @@ static void io_release_area_mem(struct io_zcrx_mem *mem) static int io_import_area(struct io_zcrx_ifq *ifq, struct io_zcrx_mem *mem, - struct io_uring_zcrx_area_reg *area_reg) + const struct io_uring_zcrx_area_reg *area_reg) { int ret; @@ -289,13 +312,14 @@ static int io_import_area(struct io_zcrx_ifq *ifq, return io_import_umem(ifq, mem, area_reg); } -static void io_zcrx_unmap_area(struct io_zcrx_ifq *ifq, +static void __io_zcrx_unmap_area(struct io_zcrx_ifq *ifq, struct io_zcrx_area *area) { int i; - guard(mutex)(&ifq->pp_lock); - if (!area->is_mapped) + lockdep_assert_held(&ifq->pp_lock); + + if (!area || !area->is_mapped) return; area->is_mapped = false; @@ -305,13 +329,23 @@ static void io_zcrx_unmap_area(struct io_zcrx_ifq *ifq, } if (area->mem.is_dmabuf) { - io_release_dmabuf(&area->mem); + io_unmap_dmabuf(&area->mem); } else { dma_unmap_sgtable(ifq->dev, &area->mem.page_sg_table, DMA_FROM_DEVICE, IO_DMA_ATTR); } } +static void io_zcrx_unmap_areas(struct io_zcrx_ifq *ifq) +{ + unsigned area_idx; + + lockdep_assert_held(&ifq->pp_lock); + + for (area_idx = 0; area_idx < ifq->nr_areas; area_idx++) + __io_zcrx_unmap_area(ifq, ifq->areas[area_idx]); +} + static void zcrx_sync_for_device(struct page_pool *pp, struct io_zcrx_ifq *zcrx, netmem_ref *netmems, unsigned nr) { @@ -351,16 +385,16 @@ static inline atomic_t *io_get_user_counter(struct net_iov *niov) return &area->user_refs[net_iov_idx(niov)]; } -static bool io_zcrx_put_niov_uref(struct net_iov *niov) +static bool io_zcrx_put_niov_uref(struct net_iov *niov, unsigned refs) { atomic_t *uref = io_get_user_counter(niov); int old; old = atomic_read(uref); do { - if (unlikely(old == 0)) + if (unlikely(old < refs)) return false; - } while (!atomic_try_cmpxchg(uref, &old, old - 1)); + } while (!atomic_try_cmpxchg(uref, &old, old - refs)); return true; } @@ -372,9 +406,9 @@ static void io_zcrx_get_niov_uref(struct net_iov *niov) static void io_fill_zcrx_offsets(struct io_uring_zcrx_offsets *offsets) { - offsets->head = offsetof(struct io_uring, head); - offsets->tail = offsetof(struct io_uring, tail); - offsets->rqes = ALIGN(sizeof(struct io_uring), L1_CACHE_BYTES); + offsets->head = offsetof(struct zcrx_rq_hdr, head); + offsets->tail = offsetof(struct zcrx_rq_hdr, tail); + offsets->rqes = ALIGN(sizeof(struct zcrx_rq_hdr), L1_CACHE_BYTES); } static int io_allocate_rbuf_ring(struct io_ring_ctx *ctx, @@ -402,7 +436,7 @@ static int io_allocate_rbuf_ring(struct io_ring_ctx *ctx, return ret; ptr = io_region_get_ptr(&ifq->rq_region); - ifq->rq.ring = (struct io_uring *)ptr; + ifq->rq.ring = (struct zcrx_rq_hdr *)ptr; ifq->rq.rqes = (struct io_uring_zcrx_rqe *)(ptr + off); memset(ifq->rq.ring, 0, sizeof(*ifq->rq.ring)); @@ -420,7 +454,8 @@ static void io_free_rbuf_ring(struct io_zcrx_ifq *ifq) static void io_zcrx_free_area(struct io_zcrx_ifq *ifq, struct io_zcrx_area *area) { - io_zcrx_unmap_area(ifq, area); + if (WARN_ON_ONCE(area->is_mapped)) + return; io_release_area_mem(&area->mem); if (area->mem.account_pages) @@ -437,31 +472,52 @@ static int io_zcrx_append_area(struct io_zcrx_ifq *ifq, struct io_zcrx_area *area) { bool kern_readable = !area->mem.is_dmabuf; + struct io_zcrx_area **areas, **old_areas; + unsigned old_nr; - if (WARN_ON_ONCE(ifq->area)) + if (ifq->kern_readable != kern_readable) return -EINVAL; - if (WARN_ON_ONCE(ifq->kern_readable != kern_readable)) + if (ifq->nr_areas + 1 > ZCRX_MAX_AREAS) return -EINVAL; - ifq->area = area; + old_areas = ifq->areas; + old_nr = ifq->nr_areas; + + areas = kmalloc_array(old_nr + 1, sizeof(areas[0]), + GFP_KERNEL_ACCOUNT | __GFP_ZERO); + if (!areas) + return -ENOMEM; + if (old_areas) + memcpy(areas, old_areas, old_nr * sizeof(areas[0])); + areas[old_nr] = area; + + scoped_guard(spinlock_bh, &ifq->rq.lock) { + guard(spinlock_bh)(&ifq->alloc_lock); + ifq->areas = areas; + ifq->nr_areas = old_nr + 1; + } + kfree(old_areas); return 0; } -static int io_zcrx_create_area(struct io_zcrx_ifq *ifq, +static int __zcrx_create_area(struct io_zcrx_ifq *ifq, struct io_uring_zcrx_area_reg *area_reg, - struct io_uring_zcrx_ifq_reg *reg) + u32 rx_buf_len) { int buf_size_shift = PAGE_SHIFT; struct io_zcrx_area *area; unsigned nr_iovs; int i, ret; - if (reg->rx_buf_len) { - if (!is_power_of_2(reg->rx_buf_len) || - reg->rx_buf_len < PAGE_SIZE) + lockdep_assert_held(&ifq->pp_lock); + + if (rx_buf_len) { + if (!is_power_of_2(rx_buf_len) || rx_buf_len < PAGE_SIZE) return -EINVAL; - buf_size_shift = ilog2(reg->rx_buf_len); + buf_size_shift = ilog2(rx_buf_len); } + if (ifq->niov_shift && ifq->niov_shift != buf_size_shift) + return -EINVAL; if (!ifq->dev && buf_size_shift != PAGE_SHIFT) return -EOPNOTSUPP; @@ -518,19 +574,28 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq, area->free_count = nr_iovs; /* we're only supporting one area per ifq for now */ - area->area_id = 0; - area_reg->rq_area_token = (u64)area->area_id << IORING_ZCRX_AREA_SHIFT; - spin_lock_init(&area->freelist_lock); + area->area_id = zcrx_next_area_id(ifq); + area_reg->rq_area_token = zcrx_area_id_to_token(area->area_id); ret = io_zcrx_append_area(ifq, area); if (!ret) return 0; err: - if (area) + if (area) { + __io_zcrx_unmap_area(ifq, area); io_zcrx_free_area(ifq, area); + } return ret; } +static int io_zcrx_create_area(struct io_zcrx_ifq *ifq, + struct io_uring_zcrx_area_reg *area_reg, + struct io_uring_zcrx_ifq_reg *reg) +{ + guard(mutex)(&ifq->pp_lock); + return __zcrx_create_area(ifq, area_reg, reg->rx_buf_len); +} + static struct io_zcrx_ifq *io_zcrx_ifq_alloc(struct io_ring_ctx *ctx) { struct io_zcrx_ifq *ifq; @@ -542,6 +607,7 @@ static struct io_zcrx_ifq *io_zcrx_ifq_alloc(struct io_ring_ctx *ctx) ifq->if_rxq = -1; spin_lock_init(&ifq->ctx_lock); spin_lock_init(&ifq->rq.lock); + spin_lock_init(&ifq->alloc_lock); mutex_init(&ifq->pp_lock); refcount_set(&ifq->refs, 1); refcount_set(&ifq->user_refs, 1); @@ -550,7 +616,7 @@ static struct io_zcrx_ifq *io_zcrx_ifq_alloc(struct io_ring_ctx *ctx) static void io_zcrx_drop_netdev(struct io_zcrx_ifq *ifq) { - guard(mutex)(&ifq->pp_lock); + lockdep_assert_held(&ifq->pp_lock); if (!ifq->netdev) return; @@ -574,11 +640,13 @@ static void io_close_queue(struct io_zcrx_ifq *ifq) } if (netdev) { - if (ifq->if_rxq != -1) { - netdev_lock(netdev); + netdev_lock(netdev); + if (ifq->if_rxq != -1) netif_mp_close_rxq(netdev, ifq->if_rxq, &p); - netdev_unlock(netdev); - } + + scoped_guard(mutex, &ifq->pp_lock) + io_zcrx_unmap_areas(ifq); + netdev_unlock(netdev); netdev_put(netdev, &netdev_tracker); } ifq->if_rxq = -1; @@ -586,6 +654,8 @@ static void io_close_queue(struct io_zcrx_ifq *ifq) static void io_zcrx_ifq_free(struct io_zcrx_ifq *ifq) { + int i; + if (WARN_ON_ONCE(ifq->if_rxq != -1)) return; if (WARN_ON_ONCE(ifq->netdev != NULL)) @@ -593,8 +663,8 @@ static void io_zcrx_ifq_free(struct io_zcrx_ifq *ifq) if (WARN_ON_ONCE(ifq->master_ctx)) return; - if (ifq->area) - io_zcrx_free_area(ifq, ifq->area); + for (i = 0; i < ifq->nr_areas; i++) + io_zcrx_free_area(ifq, ifq->areas[i]); if (ifq->mm_account) mmdrop(ifq->mm_account); if (ifq->dev) @@ -603,6 +673,7 @@ static void io_zcrx_ifq_free(struct io_zcrx_ifq *ifq) io_free_rbuf_ring(ifq); free_uid(ifq->user); mutex_destroy(&ifq->pp_lock); + kfree(ifq->areas); kfree(ifq); } @@ -615,8 +686,9 @@ static void io_put_zcrx_ifq(struct io_zcrx_ifq *ifq) static void io_zcrx_return_niov_freelist(struct net_iov *niov) { struct io_zcrx_area *area = io_zcrx_iov_to_area(niov); + struct io_zcrx_ifq *ifq = area->ifq; - guard(spinlock_bh)(&area->freelist_lock); + guard(spinlock_bh)(&ifq->alloc_lock); if (WARN_ON_ONCE(area->free_count >= area->nia.num_niovs)) return; area->freelist[area->free_count++] = net_iov_idx(niov); @@ -626,7 +698,7 @@ static struct net_iov *zcrx_get_free_niov(struct io_zcrx_area *area) { unsigned niov_idx; - lockdep_assert_held(&area->freelist_lock); + lockdep_assert_held(&area->ifq->alloc_lock); if (unlikely(!area->free_count)) return NULL; @@ -647,14 +719,10 @@ static void io_zcrx_return_niov(struct net_iov *niov) page_pool_put_unrefed_netmem(niov->desc.pp, netmem, -1, false); } -static void io_zcrx_scrub(struct io_zcrx_ifq *ifq) +static void io_zcrx_scrub_area(struct io_zcrx_ifq *ifq, struct io_zcrx_area *area) { - struct io_zcrx_area *area = ifq->area; int i; - if (!area) - return; - /* Reclaim back all buffers given to the user space. */ for (i = 0; i < area->nia.num_niovs; i++) { struct net_iov *niov = &area->nia.niovs[i]; @@ -668,6 +736,15 @@ static void io_zcrx_scrub(struct io_zcrx_ifq *ifq) } } +static void io_zcrx_scrub(struct io_zcrx_ifq *ifq) +{ + int i; + + guard(mutex)(&ifq->pp_lock); + for (i = 0; i < ifq->nr_areas; i++) + io_zcrx_scrub_area(ifq, ifq->areas[i]); +} + static void zcrx_unregister_user(struct io_zcrx_ifq *ifq, struct io_ring_ctx *ctx) { scoped_guard(spinlock_bh, &ifq->ctx_lock) { @@ -927,6 +1004,8 @@ int io_register_zcrx(struct io_ring_ctx *ctx, if (copy_from_user(&area, u64_to_user_ptr(reg.area_ptr), sizeof(area))) return -EFAULT; + if (area.rq_area_token) + return -EINVAL; memset(¬if, 0, sizeof(notif)); if (reg.event_desc && copy_from_user(¬if, u64_to_user_ptr(reg.event_desc), @@ -989,6 +1068,8 @@ int io_register_zcrx(struct io_ring_ctx *ctx, goto err; } + WARN_ON_ONCE(!ifq->niov_shift); + reg.zcrx_id = id; scoped_guard(mutex, &ctx->mmap_lock) { @@ -1081,14 +1162,24 @@ void io_unregister_zcrx(struct io_ring_ctx *ctx) xa_destroy(&ctx->zcrx_ctxs); } -static inline u32 zcrx_rq_entries(struct zcrx_rq *rq) +struct zcrx_rq_iter { + int rqes_left; + bool flushed; +}; + +static inline u32 __zcrx_rq_entries(struct zcrx_rq *rq) { - u32 entries; + u32 entries = rq->cached_tail - rq->cached_head; - entries = smp_load_acquire(&rq->ring->tail) - rq->cached_head; return min(entries, rq->nr_entries); } +static inline u32 zcrx_rq_entries(struct zcrx_rq *rq) +{ + rq->cached_tail = smp_load_acquire(&rq->ring->tail); + return __zcrx_rq_entries(rq); +} + static struct io_uring_zcrx_rqe *zcrx_next_rqe(struct zcrx_rq *rq, unsigned mask) { unsigned int idx = rq->cached_head++ & mask; @@ -1096,6 +1187,33 @@ static struct io_uring_zcrx_rqe *zcrx_next_rqe(struct zcrx_rq *rq, unsigned mask return &rq->rqes[idx]; } +static inline void zcrx_rq_iter_init(struct zcrx_rq_iter *it, + struct zcrx_rq *rq) +{ + it->rqes_left = min_t(unsigned, __zcrx_rq_entries(rq), ZCRX_REFILL_CAP); + it->flushed = false; +} + +static inline bool zcrx_rq_iter_next(struct zcrx_rq_iter *it, + struct zcrx_rq *rq, + struct io_uring_zcrx_rqe **rqe) +{ + it->rqes_left--; + if (unlikely(it->rqes_left < 0)) { + if (it->flushed) + return false; + rq->cached_tail = smp_load_acquire(&rq->ring->tail); + it->rqes_left = min_t(unsigned, __zcrx_rq_entries(rq), + ZCRX_REFILL_CAP); + it->flushed = true; + if (--it->rqes_left < 0) + return false; + } + + *rqe = zcrx_next_rqe(rq, rq->nr_entries - 1); + return true; +} + static inline bool io_parse_rqe(struct io_uring_zcrx_rqe *rqe, struct io_zcrx_ifq *ifq, struct net_iov **ret_niov) @@ -1104,12 +1222,15 @@ static inline bool io_parse_rqe(struct io_uring_zcrx_rqe *rqe, unsigned niov_idx, area_idx; struct io_zcrx_area *area; + lockdep_assert_held(&ifq->rq.lock); + area_idx = off >> IORING_ZCRX_AREA_SHIFT; niov_idx = (off & ~IORING_ZCRX_AREA_MASK) >> ifq->niov_shift; - if (unlikely(rqe->__pad || area_idx)) + if (unlikely(rqe->__pad || area_idx >= ifq->nr_areas)) return false; - area = ifq->area; + area_idx = array_index_nospec(area_idx, ifq->nr_areas); + area = ifq->areas[area_idx]; if (unlikely(niov_idx >= area->nia.num_niovs)) return false; @@ -1119,44 +1240,58 @@ static inline bool io_parse_rqe(struct io_uring_zcrx_rqe *rqe, return true; } +static bool zcrx_put_refill_niov(struct net_iov *niov, struct page_pool *pp, + unsigned refs) +{ + netmem_ref netmem = net_iov_to_netmem(niov); + + if (!io_zcrx_put_niov_uref(niov, refs)) + return false; + if (page_pool_unref_netmem(netmem, refs) != 0) + return false; + if (unlikely(niov->desc.pp != pp)) { + io_zcrx_return_niov(niov); + return false; + } + return true; +} + static unsigned io_zcrx_ring_refill(struct page_pool *pp, struct io_zcrx_ifq *ifq, netmem_ref *netmems, unsigned to_alloc) { struct zcrx_rq *rq = &ifq->rq; - unsigned int mask = rq->nr_entries - 1; - unsigned int entries; + struct io_uring_zcrx_rqe *rqe; + struct zcrx_rq_iter it; + struct net_iov *niov = NULL; + unsigned niov_refs = 0; unsigned allocated = 0; guard(spinlock_bh)(&rq->lock); - entries = zcrx_rq_entries(rq); - entries = min_t(unsigned, entries, to_alloc); - if (unlikely(!entries)) - return 0; + zcrx_rq_iter_init(&it, rq); - do { - struct io_uring_zcrx_rqe *rqe = zcrx_next_rqe(rq, mask); - struct net_iov *niov; - netmem_ref netmem; + while (allocated < to_alloc - 1 && zcrx_rq_iter_next(&it, rq, &rqe)) { + struct net_iov *next_niov; - if (!io_parse_rqe(rqe, ifq, &niov)) + if (!io_parse_rqe(rqe, ifq, &next_niov)) continue; - if (!io_zcrx_put_niov_uref(niov)) - continue; - - netmem = net_iov_to_netmem(niov); - if (!page_pool_unref_and_test(netmem)) - continue; - - if (unlikely(niov->desc.pp != pp)) { - io_zcrx_return_niov(niov); + if (niov == next_niov) { + niov_refs++; continue; } + if (niov && zcrx_put_refill_niov(niov, pp, niov_refs)) { + netmems[allocated] = net_iov_to_netmem(niov); + allocated++; + } + niov = next_niov; + niov_refs = 1; + } - netmems[allocated] = netmem; + if (niov && zcrx_put_refill_niov(niov, pp, niov_refs)) { + netmems[allocated] = net_iov_to_netmem(niov); allocated++; - } while (--entries); + } smp_store_release(&rq->ring->head, rq->cached_head); return allocated; @@ -1165,18 +1300,24 @@ static unsigned io_zcrx_ring_refill(struct page_pool *pp, static unsigned io_zcrx_refill_slow(struct page_pool *pp, struct io_zcrx_ifq *ifq, netmem_ref *netmems, unsigned to_alloc) { - struct io_zcrx_area *area = ifq->area; + unsigned area_idx = 0; unsigned allocated = 0; - guard(spinlock_bh)(&area->freelist_lock); + guard(spinlock_bh)(&ifq->alloc_lock); - for (allocated = 0; allocated < to_alloc; allocated++) { - struct net_iov *niov = zcrx_get_free_niov(area); + while (allocated < to_alloc) { + struct net_iov *niov = zcrx_get_free_niov(ifq->areas[area_idx]); + + if (!niov) { + area_idx++; + if (area_idx >= ifq->nr_areas) + break; + continue; + } - if (!niov) - break; net_mp_niov_set_page_pool(pp, niov); netmems[allocated] = net_iov_to_netmem(niov); + allocated++; } return allocated; } @@ -1320,9 +1461,9 @@ static void io_pp_uninstall(void *mp_priv, struct netdev_rx_queue *rxq) struct pp_memory_provider_params *p = &rxq->mp_params; struct io_zcrx_ifq *ifq = mp_priv; + guard(mutex)(&ifq->pp_lock); + io_zcrx_unmap_areas(ifq); io_zcrx_drop_netdev(ifq); - if (ifq->area) - io_zcrx_unmap_area(ifq, ifq->area); p->mp_ops = NULL; p->mp_priv = NULL; @@ -1367,7 +1508,7 @@ static void zcrx_return_buffers(netmem_ref *netmems, unsigned nr) netmem_ref netmem = netmems[i]; struct net_iov *niov = netmem_to_net_iov(netmem); - if (!io_zcrx_put_niov_uref(niov)) + if (!io_zcrx_put_niov_uref(niov, 1)) continue; if (!page_pool_unref_and_test(netmem)) continue; @@ -1423,6 +1564,34 @@ static int zcrx_arm_notif(struct io_ring_ctx *ctx, struct io_zcrx_ifq *zcrx, return 0; } +static int zcrx_ctrl_add_area(struct io_ring_ctx *ctx, struct io_zcrx_ifq *ifq, + struct zcrx_ctrl *ctrl) +{ + struct zcrx_ctrl_add_area *ctrl_add = &ctrl->zc_area; + struct io_uring_zcrx_area_reg __user *area_uptr; + struct io_uring_zcrx_area_reg area_reg; + + area_uptr = u64_to_user_ptr(ctrl_add->area_ptr); + if (copy_from_user(&area_reg, area_uptr, sizeof(area_reg))) + return -EFAULT; + if (!mem_is_zero(&ctrl_add->__resv, sizeof(ctrl_add->__resv))) + return -EINVAL; + if (area_reg.rq_area_token) + return -EINVAL; + + guard(mutex)(&ifq->pp_lock); + if (ifq->dev && !ifq->netdev) + return -EFAULT; + + /* we can't safely roll back area append, copy it out first */ + area_reg.rq_area_token = zcrx_area_id_to_token(zcrx_next_area_id(ifq)); + if (copy_to_user(area_uptr, &area_reg, sizeof(area_reg))) + return -EFAULT; + area_reg.rq_area_token = 0; + + return __zcrx_create_area(ifq, &area_reg, 1U << ifq->niov_shift); +} + int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) { struct zcrx_ctrl ctrl; @@ -1449,6 +1618,8 @@ int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) return zcrx_export(ctx, zcrx, &ctrl, arg); case ZCRX_CTRL_ARM_EVENT: return zcrx_arm_notif(ctx, zcrx, &ctrl); + case ZCRX_CTRL_ADD_AREA: + return zcrx_ctrl_add_area(ctx, zcrx, &ctrl); } return -EOPNOTSUPP; @@ -1475,25 +1646,30 @@ static bool io_zcrx_queue_cqe(struct io_kiocb *req, struct net_iov *niov, area = io_zcrx_iov_to_area(niov); offset = off + (net_iov_idx(niov) << ifq->niov_shift); rcqe = (struct io_uring_zcrx_cqe *)(cqe + 1); - rcqe->off = offset + ((u64)area->area_id << IORING_ZCRX_AREA_SHIFT); + rcqe->off = offset + zcrx_area_id_to_token(area->area_id); rcqe->__pad = 0; return true; } static struct net_iov *io_alloc_fallback_niov(struct io_zcrx_ifq *ifq) { - struct io_zcrx_area *area = ifq->area; struct net_iov *niov = NULL; + unsigned area_idx; if (!ifq->kern_readable) return NULL; - scoped_guard(spinlock_bh, &area->freelist_lock) - niov = zcrx_get_free_niov(area); + guard(spinlock_bh)(&ifq->alloc_lock); + + for (area_idx = 0; area_idx < ifq->nr_areas; area_idx++) { + niov = zcrx_get_free_niov(ifq->areas[area_idx]); + if (niov) { + page_pool_fragment_netmem(net_iov_to_netmem(niov), 1); + return niov; + } + } - if (niov) - page_pool_fragment_netmem(net_iov_to_netmem(niov), 1); - return niov; + return NULL; } struct io_copy_cache { @@ -1635,8 +1811,8 @@ io_zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb, struct io_kiocb *req = args->req; struct sk_buff *frag_iter; unsigned start, start_off = offset; - int i, copy, end, off; - int ret = 0; + struct skb_shared_info *shi; + int i, ret = 0; len = min_t(size_t, len, desc->count); /* @@ -1671,23 +1847,20 @@ io_zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb, } start = skb_headlen(skb); + shi = skb_shinfo(skb); - for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { - const skb_frag_t *frag; + for (i = 0; i < shi->nr_frags; i++) { + const skb_frag_t *frag = &shi->frags[i]; + unsigned frag_end = start + skb_frag_size(frag); if (WARN_ON(start > offset + len)) return -EFAULT; - frag = &skb_shinfo(skb)->frags[i]; - end = start + skb_frag_size(frag); - - if (offset < end) { - copy = end - offset; - if (copy > len) - copy = len; + if (offset < frag_end) { + unsigned copy = min(frag_end - offset, len); + unsigned frag_off = offset - start; - off = offset - start; - ret = io_zcrx_recv_frag(req, ifq, frag, off, copy); + ret = io_zcrx_recv_frag(req, ifq, frag, frag_off, copy); if (ret < 0) goto out; @@ -1696,24 +1869,23 @@ io_zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb, if (len == 0 || ret != copy) goto out; } - start = end; + start = frag_end; } skb_walk_frags(skb, frag_iter) { + unsigned frag_end; + if (WARN_ON(start > offset + len)) return -EFAULT; - end = start + frag_iter->len; - if (offset < end) { + frag_end = start + frag_iter->len; + if (offset < frag_end) { + unsigned copy = min(frag_end - offset, len); + unsigned frag_off = offset - start; size_t count; - copy = end - offset; - if (copy > len) - copy = len; - - off = offset - start; count = desc->count; - ret = io_zcrx_recv_skb(desc, frag_iter, off, copy); + ret = io_zcrx_recv_skb(desc, frag_iter, frag_off, copy); desc->count = count; if (ret < 0) goto out; @@ -1723,7 +1895,7 @@ io_zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb, if (len == 0 || ret != copy) goto out; } - start = end; + start = frag_end; } out: diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h index c1005f23caff..d4a54b4e17fd 100644 --- a/io_uring/zcrx.h +++ b/io_uring/zcrx.h @@ -37,29 +37,38 @@ struct io_zcrx_area { u16 area_id; /* freelist */ - spinlock_t freelist_lock ____cacheline_aligned_in_smp; u32 free_count; u32 *freelist; struct io_zcrx_mem mem; }; +struct zcrx_rq_hdr { + u32 head ____cacheline_aligned_in_smp; + u32 tail ____cacheline_aligned_in_smp; +}; + struct zcrx_rq { spinlock_t lock; - struct io_uring *ring; + struct zcrx_rq_hdr *ring; struct io_uring_zcrx_rqe *rqes; u32 cached_head; + u32 cached_tail; u32 nr_entries; }; struct io_zcrx_ifq { - struct io_zcrx_area *area; + /* read-protected by any of: ->pp_lock, ->alloc_lock, ->rq.lock */ + struct io_zcrx_area **areas; + unsigned nr_areas; + unsigned niov_shift; struct user_struct *user; struct mm_struct *mm_account; bool kern_readable; struct zcrx_rq rq ____cacheline_aligned_in_smp; + spinlock_t alloc_lock ____cacheline_aligned_in_smp; u32 if_rxq; struct device *dev; |
