summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-20 13:47:56 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-20 13:47:56 -0700
commitf5437ff7299e47e76e52d37a2937a4b0f04e399f (patch)
tree29c5fec2e09c24835ede4c62a5a5edd4113d27f8
parentc84d3e3130dfe1058cb27dc78e7ad8bd36f0545a (diff)
parent881dc9dd66d2367e7fe1be0f953a8837cb3b26a9 (diff)
downloadlinux-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.h11
-rw-r--r--include/uapi/linux/io_uring/zcrx.h7
-rw-r--r--io_uring/eventfd.c8
-rw-r--r--io_uring/eventfd.h2
-rw-r--r--io_uring/futex.c32
-rw-r--r--io_uring/futex.h1
-rw-r--r--io_uring/io-wq.c14
-rw-r--r--io_uring/io_uring.c17
-rw-r--r--io_uring/io_uring.h2
-rw-r--r--io_uring/memmap.c37
-rw-r--r--io_uring/net.c75
-rw-r--r--io_uring/opdef.c2
-rw-r--r--io_uring/poll.c23
-rw-r--r--io_uring/query.c2
-rw-r--r--io_uring/rsrc.c2
-rw-r--r--io_uring/rw.c24
-rw-r--r--io_uring/sqpoll.c7
-rw-r--r--io_uring/tw.c2
-rw-r--r--io_uring/uring_cmd.c10
-rw-r--r--io_uring/waitid.c2
-rw-r--r--io_uring/zcrx.c398
-rw-r--r--io_uring/zcrx.h15
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(&notif, 0, sizeof(notif));
if (reg.event_desc && copy_from_user(&notif, 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;