public inbox for io-uring@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] extra io_uring BPF examples
@ 2026-02-23 14:11 Pavel Begunkov
  2026-02-23 14:12 ` [PATCH 1/3] io_uring/selftests: add BPF'ed cp example Pavel Begunkov
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Pavel Begunkov @ 2026-02-23 14:11 UTC (permalink / raw)
  To: io-uring; +Cc: asml.silence, bpf, axboe, Alexei Starovoitov

NOT FOR INCLUSION

Alexei asked for extra more realistic examples. This this series is
based on top of v9 of the io_uring BPF series and implemented as
selftests. There could be more, but I stopped with 3 that should
give an idea how it'll be used:

1. A QD=1 file copy program that show cases state machine handling.

2. A BPF program rate-limiting the number of inflight io_uring request.
   That's a good example of what users asked for before but seemed to
   be too niche to be plumbed into the main io_uring path.

3. A toy example of how BPF can interact with registered buffers.

Pavel Begunkov (3):
  io_uring/selftests: add BPF'ed cp example
  io_uring/selftests: add rate limiter BPF program
  io_uring/selftests: add regbuf xor example

 io_uring/bpf-ops.c                            |  29 +++
 tools/testing/selftests/io_uring/Makefile     |   2 +-
 .../testing/selftests/io_uring/common-defs.h  |  27 +++
 tools/testing/selftests/io_uring/cp.bpf.c     | 134 ++++++++++++++
 tools/testing/selftests/io_uring/cp.c         | 130 +++++++++++++
 tools/testing/selftests/io_uring/helpers.h    |  15 +-
 .../selftests/io_uring/rate_limiter.bpf.c     |  84 +++++++++
 .../testing/selftests/io_uring/rate_limiter.c | 172 ++++++++++++++++++
 tools/testing/selftests/io_uring/xor.bpf.c    |  31 ++++
 tools/testing/selftests/io_uring/xor.c        |  83 +++++++++
 10 files changed, 702 insertions(+), 5 deletions(-)
 create mode 100644 tools/testing/selftests/io_uring/cp.bpf.c
 create mode 100644 tools/testing/selftests/io_uring/cp.c
 create mode 100644 tools/testing/selftests/io_uring/rate_limiter.bpf.c
 create mode 100644 tools/testing/selftests/io_uring/rate_limiter.c
 create mode 100644 tools/testing/selftests/io_uring/xor.bpf.c
 create mode 100644 tools/testing/selftests/io_uring/xor.c

-- 
2.52.0


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

* [PATCH 1/3] io_uring/selftests: add BPF'ed cp example
  2026-02-23 14:11 [PATCH 0/3] extra io_uring BPF examples Pavel Begunkov
@ 2026-02-23 14:12 ` Pavel Begunkov
  2026-02-23 14:12 ` [PATCH 2/3] io_uring/selftests: add rate limiter BPF program Pavel Begunkov
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 8+ messages in thread
From: Pavel Begunkov @ 2026-02-23 14:12 UTC (permalink / raw)
  To: io-uring; +Cc: asml.silence, bpf, axboe, Alexei Starovoitov

Implement a simple QD=1 cp using BPF io_uring as an example of a state
machine handling.

Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
 tools/testing/selftests/io_uring/Makefile     |   2 +-
 .../testing/selftests/io_uring/common-defs.h  |  10 ++
 tools/testing/selftests/io_uring/cp.bpf.c     | 134 ++++++++++++++++++
 tools/testing/selftests/io_uring/cp.c         | 130 +++++++++++++++++
 4 files changed, 275 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/io_uring/cp.bpf.c
 create mode 100644 tools/testing/selftests/io_uring/cp.c

diff --git a/tools/testing/selftests/io_uring/Makefile b/tools/testing/selftests/io_uring/Makefile
index 37f50acdba37..26e4cf721b86 100644
--- a/tools/testing/selftests/io_uring/Makefile
+++ b/tools/testing/selftests/io_uring/Makefile
@@ -3,7 +3,7 @@ include ../../../build/Build.include
 include ../../../scripts/Makefile.arch
 include ../../../scripts/Makefile.include
 
-TEST_GEN_PROGS := nops_loop overflow unreg
+TEST_GEN_PROGS := nops_loop overflow unreg cp
 
 # override lib.mk's default rules
 OVERRIDE_TARGETS := 1
diff --git a/tools/testing/selftests/io_uring/common-defs.h b/tools/testing/selftests/io_uring/common-defs.h
index 9a44e0687436..20b59adbe703 100644
--- a/tools/testing/selftests/io_uring/common-defs.h
+++ b/tools/testing/selftests/io_uring/common-defs.h
@@ -28,4 +28,14 @@ struct unreg_state {
 	unsigned	times_invoked;
 };
 
+struct cp_state {
+	int input_fd;
+	int output_fd;
+	void *buffer;
+	unsigned nr_infligt;
+	unsigned cur_offset;
+	size_t buffer_size;
+	int res;
+};
+
 #endif /* IOU_TOOLS_COMMON_DEFS_H */
diff --git a/tools/testing/selftests/io_uring/cp.bpf.c b/tools/testing/selftests/io_uring/cp.bpf.c
new file mode 100644
index 000000000000..7c19b687ab37
--- /dev/null
+++ b/tools/testing/selftests/io_uring/cp.bpf.c
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "vmlinux.h"
+#include "common-defs.h"
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
+
+enum {
+	REQ_TOKEN_READ = 1,
+	REQ_TOKEN_WRITE
+};
+
+const volatile struct ring_info ri;
+
+#define t_min(a, b) ((a) < (b) ? (a) : (b))
+
+static inline void sqe_prep_rw(struct io_uring_sqe *sqe, unsigned opcode,
+				   int fd, void *addr,
+				   __u32 len, __u64 offset)
+{
+	*sqe = (struct io_uring_sqe){};
+	sqe->opcode = opcode;
+	sqe->fd = fd;
+	sqe->off = offset;
+	sqe->addr = (__u64)(unsigned long)addr;
+	sqe->len = len;
+}
+
+static int issue_next_req(struct io_ring_ctx *ring, struct io_uring_sqe *sqes,
+			  struct cp_state *cs, int type, size_t size)
+{
+	struct io_uring_sqe *sqe = sqes;
+	__u8 req_type;
+	int fd, ret;
+
+	if (type == REQ_TOKEN_READ) {
+		req_type = IORING_OP_READ;
+		fd = cs->input_fd;
+	} else {
+		req_type = IORING_OP_WRITE;
+		fd = cs->output_fd;
+	}
+
+	sqe_prep_rw(sqes, req_type, fd, cs->buffer, size, cs->cur_offset);
+	sqe->user_data = type;
+
+	ret = bpf_io_uring_submit_sqes(ring, 1);
+	if (ret != 1) {
+		cs->res = ret;
+		return ret < 0 ? ret : -EFAULT;
+	}
+	return 0;
+}
+
+SEC("struct_ops.s/cp_loop_step")
+int BPF_PROG(cp_loop_step, struct io_ring_ctx *ring, struct iou_loop_params *ls)
+{
+	struct io_uring_sqe *sqes;
+	struct io_uring_cqe *cqes;
+	struct io_uring *cq_hdr;
+	struct cp_state *cs;
+	void *rings;
+	int ret;
+
+	sqes = (void *)bpf_io_uring_get_region(ring, IOU_REGION_SQ,
+				ri.sq_entries * sizeof(struct io_uring_sqe));
+	rings = (void *)bpf_io_uring_get_region(ring, IOU_REGION_CQ,
+				ri.cqes_offset + ri.cq_entries * sizeof(struct io_uring_cqe));
+	cs = (void *)bpf_io_uring_get_region(ring, IOU_REGION_MEM, sizeof(*cs));
+	if (!rings || !sqes || !cs)
+		return IOU_LOOP_STOP;
+	cq_hdr = rings + ri.cq_hdr_offset;
+	cqes = rings + ri.cqes_offset;
+
+	if (!cs->nr_infligt) {
+		cs->nr_infligt++;
+		ret = issue_next_req(ring, sqes, cs, REQ_TOKEN_READ,
+				     cs->buffer_size);
+		if (ret)
+			return IOU_LOOP_STOP;
+	}
+
+	if (cq_hdr->tail != cq_hdr->head) {
+		struct io_uring_cqe *cqe;
+
+		if (cq_hdr->tail - cq_hdr->head != 1) {
+			cs->res = -ERANGE;
+			return IOU_LOOP_STOP;
+		}
+
+		cqe = &cqes[cq_hdr->head & (ri.cq_entries - 1)];
+		if (cqe->res < 0) {
+			cs->res = cqe->res;
+			return IOU_LOOP_STOP;
+		}
+
+		switch (cqe->user_data) {
+		case REQ_TOKEN_READ:
+			if (cqe->res == 0) {
+				cs->res = 0;
+				return IOU_LOOP_STOP;
+			}
+			ret = issue_next_req(ring, sqes, cs, REQ_TOKEN_WRITE,
+					     cqe->res);
+			if (ret)
+				return IOU_LOOP_STOP;
+			break;
+		case REQ_TOKEN_WRITE:
+			cs->cur_offset += cqe->res;
+			ret = issue_next_req(ring, sqes, cs, REQ_TOKEN_READ,
+					     cs->buffer_size);
+			if (ret)
+				return IOU_LOOP_STOP;
+			break;
+		default:
+			bpf_printk("invalid token\n");
+			cs->res = -EINVAL;
+			return IOU_LOOP_STOP;
+		};
+
+		cq_hdr->head++;
+	}
+
+	ls->cq_wait_idx = cq_hdr->head + 1;
+	return IOU_LOOP_CONTINUE;
+}
+
+SEC(".struct_ops.link")
+struct io_uring_bpf_ops cp_ops = {
+	.loop_step = (void *)cp_loop_step,
+};
diff --git a/tools/testing/selftests/io_uring/cp.c b/tools/testing/selftests/io_uring/cp.c
new file mode 100644
index 000000000000..333b3f2a370d
--- /dev/null
+++ b/tools/testing/selftests/io_uring/cp.c
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/stddef.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <bpf/libbpf.h>
+#include <io_uring/mini_liburing.h>
+
+#include "common-defs.h"
+#include "helpers.h"
+#include "cp.bpf.skel.h"
+
+static struct cp *skel;
+static struct bpf_link *cp_link;
+
+static char *in_fname;
+static char *out_fname;
+
+struct ring_ctx {
+	struct io_uring ring;
+	struct ring_info ri;
+
+	void *region;
+	size_t region_size;
+};
+
+static void setup_bpf_prog(struct ring_ctx *ctx)
+{
+	int ret;
+
+	skel = cp__open();
+	if (!skel) {
+		fprintf(stderr, "can't generate skeleton\n");
+		exit(1);
+	}
+
+	skel->struct_ops.cp_ops->ring_fd = ctx->ring.ring_fd;
+	skel->rodata->ri = ctx->ri;
+
+	ret = cp__load(skel);
+	if (ret) {
+		fprintf(stderr, "failed to load skeleton\n");
+		exit(1);
+	}
+
+	cp_link = bpf_map__attach_struct_ops(skel->maps.cp_ops);
+	if (!cp_link) {
+		fprintf(stderr, "failed to attach ops\n");
+		exit(1);
+	}
+}
+
+static void do_cp(struct ring_ctx *ctx)
+{
+	size_t page_size = sysconf(_SC_PAGE_SIZE);
+	struct cp_state *cs = ctx->region;
+	unsigned input_fd, output_fd;
+	size_t buffer_size = 4096;
+	size_t file_size;
+	struct stat st;
+	void *buffer;
+	int ret;
+
+	input_fd = open(in_fname, O_RDONLY | O_DIRECT);
+	output_fd = open(out_fname, O_WRONLY | O_DIRECT | O_CREAT, 0644);
+	if (input_fd < 0 || output_fd < 0) {
+		fprintf(stderr, "can't open files");
+		exit(1);
+	}
+	if (fstat(input_fd, &st) == -1) {
+		fprintf(stderr, "stat failed\n");
+		exit(1);
+	}
+	file_size = st.st_size;
+
+	if (ftruncate(output_fd, file_size) == -1) {
+		fprintf(stderr, "ftruncate failed\n");
+		exit(1);
+	}
+
+	buffer = aligned_alloc(page_size, buffer_size);
+	if (!buffer) {
+		fprintf(stderr, "can't allocate buffer\n");
+		exit(1);
+	}
+
+	cs->input_fd = input_fd;
+	cs->output_fd = output_fd;
+	cs->buffer = buffer;
+	cs->buffer_size = buffer_size;
+	cs->res = -EBUSY;
+
+	ret = ring_ctx_run(ctx);
+	if (ret) {
+		fprintf(stderr, "run failed %i\n", ret);
+		exit(1);
+	}
+
+	if (cs->res)
+		fprintf(stderr, "run failed: %i\n", cs->res);
+
+	free(buffer);
+	close(input_fd);
+	close(output_fd);
+}
+
+int main(int argc, char *argv[])
+{
+	struct ring_ctx ctx;
+
+	if (argc != 3)
+		return 0;
+
+	in_fname = argv[1];
+	out_fname = argv[2];
+
+	ring_ctx_create(&ctx, sizeof(struct nops_state));
+	setup_bpf_prog(&ctx);
+
+	do_cp(&ctx);
+
+	bpf_link__destroy(cp_link);
+	cp__destroy(skel);
+	ring_ctx_destroy(&ctx);
+	return 0;
+}
-- 
2.52.0


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

* [PATCH 2/3] io_uring/selftests: add rate limiter BPF program
  2026-02-23 14:11 [PATCH 0/3] extra io_uring BPF examples Pavel Begunkov
  2026-02-23 14:12 ` [PATCH 1/3] io_uring/selftests: add BPF'ed cp example Pavel Begunkov
@ 2026-02-23 14:12 ` Pavel Begunkov
  2026-02-23 14:12 ` [PATCH 3/3] io_uring/selftests: add regbuf xor example Pavel Begunkov
  2026-02-23 14:14 ` [PATCH 0/3] extra io_uring BPF examples Jens Axboe
  3 siblings, 0 replies; 8+ messages in thread
From: Pavel Begunkov @ 2026-02-23 14:12 UTC (permalink / raw)
  To: io-uring; +Cc: asml.silence, bpf, axboe, Alexei Starovoitov

Show how io_uring can be extended with features using BPF, which
would've otherwise required extra complexity and overhead in the common
io_uring for somewhat niche users. The rate limiter program caps how
many requests can be in flight at the same time. If the user queues up
more SQEs than the cap, their execution will be delayed until previous
requests has completed. It'll automatically handle submitting deferred
SQEs, as well will correct the waiting parameters based. E.g. if the
user submits 8 SQEs and wants to wait for CQEs but limits it at 2
requests at a time, the program will wait for 2 CQEs multiple times
until there are all 8 CQEs.

The test executes timeout requests and measures the total time to make
sure they're not executed in parallel.

Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
 tools/testing/selftests/io_uring/Makefile     |   2 +-
 .../testing/selftests/io_uring/common-defs.h  |  11 ++
 tools/testing/selftests/io_uring/helpers.h    |  15 +-
 .../selftests/io_uring/rate_limiter.bpf.c     |  84 +++++++++
 .../testing/selftests/io_uring/rate_limiter.c | 172 ++++++++++++++++++
 5 files changed, 279 insertions(+), 5 deletions(-)
 create mode 100644 tools/testing/selftests/io_uring/rate_limiter.bpf.c
 create mode 100644 tools/testing/selftests/io_uring/rate_limiter.c

diff --git a/tools/testing/selftests/io_uring/Makefile b/tools/testing/selftests/io_uring/Makefile
index 26e4cf721b86..e0581f96d98a 100644
--- a/tools/testing/selftests/io_uring/Makefile
+++ b/tools/testing/selftests/io_uring/Makefile
@@ -3,7 +3,7 @@ include ../../../build/Build.include
 include ../../../scripts/Makefile.arch
 include ../../../scripts/Makefile.include
 
-TEST_GEN_PROGS := nops_loop overflow unreg cp
+TEST_GEN_PROGS := nops_loop overflow unreg cp rate_limiter
 
 # override lib.mk's default rules
 OVERRIDE_TARGETS := 1
diff --git a/tools/testing/selftests/io_uring/common-defs.h b/tools/testing/selftests/io_uring/common-defs.h
index 20b59adbe703..dae3b0fe8588 100644
--- a/tools/testing/selftests/io_uring/common-defs.h
+++ b/tools/testing/selftests/io_uring/common-defs.h
@@ -38,4 +38,15 @@ struct cp_state {
 	int res;
 };
 
+struct rate_limiter_state {
+	unsigned inflight;
+	unsigned max_inflight;
+	unsigned cached_cq_tail;
+	unsigned target_cq_tail;
+	bool waitiing;
+
+	unsigned to_wait;
+	int res;
+};
+
 #endif /* IOU_TOOLS_COMMON_DEFS_H */
diff --git a/tools/testing/selftests/io_uring/helpers.h b/tools/testing/selftests/io_uring/helpers.h
index b6d1b8ca64b8..6894e081e3c0 100644
--- a/tools/testing/selftests/io_uring/helpers.h
+++ b/tools/testing/selftests/io_uring/helpers.h
@@ -31,7 +31,9 @@ static inline void ring_ctx_destroy(struct ring_ctx *ctx)
 	free(ctx->region);
 }
 
-static inline void ring_ctx_create(struct ring_ctx *ctx, size_t region_size)
+static inline void ring_ctx_create_flags(struct ring_ctx *ctx,
+					 size_t region_size,
+					 unsigned ring_flags)
 {
 	struct io_uring_mem_region_reg mr;
 	struct io_uring_region_desc rd;
@@ -47,11 +49,11 @@ static inline void ring_ctx_create(struct ring_ctx *ctx, size_t region_size)
 
 	memset(&params, 0, sizeof(params));
 	params.cq_entries = cq_entries;
-	params.flags = IORING_SETUP_SINGLE_ISSUER |
+	params.flags = ring_flags |
+			IORING_SETUP_SINGLE_ISSUER |
 			IORING_SETUP_DEFER_TASKRUN |
 			IORING_SETUP_NO_SQARRAY |
-			IORING_SETUP_CQSIZE |
-			IORING_SETUP_SQ_REWIND;
+			IORING_SETUP_CQSIZE;
 
 	ret = io_uring_queue_init_params(sq_entries, &ctx->ring, &params);
 	if (ret) {
@@ -92,4 +94,9 @@ static inline void ring_ctx_create(struct ring_ctx *ctx, size_t region_size)
 	ri->cq_entries = cq_entries;
 }
 
+static inline void ring_ctx_create(struct ring_ctx *ctx, size_t region_size)
+{
+	ring_ctx_create_flags(ctx, region_size, IORING_SETUP_SQ_REWIND);
+}
+
 #endif /* IOU_TOOLS_HELPERS_H */
diff --git a/tools/testing/selftests/io_uring/rate_limiter.bpf.c b/tools/testing/selftests/io_uring/rate_limiter.bpf.c
new file mode 100644
index 000000000000..c4fb74ee6a62
--- /dev/null
+++ b/tools/testing/selftests/io_uring/rate_limiter.bpf.c
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Limit the number of inflight requests, and automatically submits more
+ * when previous requests complete. The user can wait for more CQEs than
+ * the maximum inflight number, in which case it will loop submitting
+ * and waiting until the specified to_wait is satisfied. It's a simple
+ * example and doesn't handle lots of edge cases.
+ */
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "vmlinux.h"
+#include "common-defs.h"
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
+
+const volatile struct ring_info ri;
+
+#define t_min(a, b) ((a) < (b) ? (a) : (b))
+
+SEC("struct_ops.s/rate_limiter_loop_step")
+int BPF_PROG(rate_limiter_loop_step, struct io_ring_ctx *ring, struct iou_loop_params *ls)
+{
+	struct io_uring *cq_hdr, *sq_hdr;
+	unsigned nr_new_cqes, extra_wait;
+	struct rate_limiter_state *rls;
+	unsigned to_submit;
+	unsigned cq_tail;
+	int ret, to_wait;
+	void *rings;
+
+	rings = (void *)bpf_io_uring_get_region(ring, IOU_REGION_CQ,
+				ri.cqes_offset + ri.cq_entries * sizeof(struct io_uring_cqe));
+	rls = (void *)bpf_io_uring_get_region(ring, IOU_REGION_MEM, sizeof(*rls));
+	if (!rings || !rls)
+		return IOU_LOOP_STOP;
+	cq_hdr = rings + ri.cq_hdr_offset;
+	sq_hdr = rings + ri.sq_hdr_offset;
+
+	/* Recalculate the inflight count, we only consider new CQEs */
+	cq_tail = cq_hdr->tail;
+	nr_new_cqes = cq_tail - rls->cached_cq_tail;
+	if (nr_new_cqes > ri.cq_entries) {
+		rls->res = -ERANGE;
+		return IOU_LOOP_STOP;
+	}
+	rls->cached_cq_tail = cq_tail;
+	rls->inflight -= nr_new_cqes;
+
+	/* Submit SQEs if we're under the QD limit */
+	to_submit = t_min(sq_hdr->tail - sq_hdr->head, ri.sq_entries);
+	to_submit = t_min(to_submit, rls->max_inflight - rls->inflight);
+	if (to_submit) {
+		ret = bpf_io_uring_submit_sqes(ring, to_submit);
+		if (ret != to_submit) {
+			rls->res = ret;
+			return IOU_LOOP_STOP;
+		}
+		rls->inflight += to_submit;
+	}
+
+	to_wait = rls->to_wait;
+	if (to_wait) {
+		rls->to_wait = 0;
+		rls->target_cq_tail = cq_hdr->head + to_wait;
+		rls->waitiing = true;
+	}
+	if (!rls->waitiing)
+		return IOU_LOOP_STOP;
+
+	if ((int)(cq_hdr->tail - rls->target_cq_tail) >= 0)
+		return IOU_LOOP_STOP;
+
+	extra_wait = rls->target_cq_tail - cq_hdr->tail;
+	extra_wait = t_min(extra_wait, rls->inflight);
+	ls->cq_wait_idx = cq_hdr->tail + extra_wait;
+	return IOU_LOOP_CONTINUE;
+}
+
+SEC(".struct_ops.link")
+struct io_uring_bpf_ops rate_limiter_ops = {
+	.loop_step = (void *)rate_limiter_loop_step,
+};
diff --git a/tools/testing/selftests/io_uring/rate_limiter.c b/tools/testing/selftests/io_uring/rate_limiter.c
new file mode 100644
index 000000000000..4b9bdd4d6b44
--- /dev/null
+++ b/tools/testing/selftests/io_uring/rate_limiter.c
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/stddef.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include <bpf/libbpf.h>
+#include <io_uring/mini_liburing.h>
+
+#include "common-defs.h"
+#include "helpers.h"
+#include "rate_limiter.bpf.skel.h"
+
+static struct rate_limiter *skel;
+static struct bpf_link *rate_limiter_link;
+
+static void setup_bpf_prog(struct ring_ctx *ctx)
+{
+	int ret;
+
+	skel = rate_limiter__open();
+	if (!skel) {
+		fprintf(stderr, "can't generate skeleton\n");
+		exit(1);
+	}
+
+	skel->struct_ops.rate_limiter_ops->ring_fd = ctx->ring.ring_fd;
+	skel->rodata->ri = ctx->ri;
+
+	ret = rate_limiter__load(skel);
+	if (ret) {
+		fprintf(stderr, "failed to load skeleton\n");
+		exit(1);
+	}
+
+	rate_limiter_link = bpf_map__attach_struct_ops(skel->maps.rate_limiter_ops);
+	if (!rate_limiter_link) {
+		fprintf(stderr, "failed to attach ops\n");
+		exit(1);
+	}
+}
+
+static void t_io_uring_prep_timeout(struct io_uring_sqe *sqe,
+				    struct __kernel_timespec *ts)
+{
+	memset(sqe, 0, sizeof(*sqe));
+	sqe->opcode = (__u8)IORING_OP_TIMEOUT;
+	sqe->fd = -1;
+	sqe->addr = (unsigned long)ts;
+	sqe->len = 1;
+}
+
+static unsigned long long mtime_since(const struct timeval *s,
+				      const struct timeval *e)
+{
+	long long sec, usec;
+
+	sec = e->tv_sec - s->tv_sec;
+	usec = (e->tv_usec - s->tv_usec);
+	if (sec > 0 && usec < 0) {
+		sec--;
+		usec += 1000000;
+	}
+
+	sec *= 1000;
+	usec /= 1000;
+	return sec + usec;
+}
+
+static inline struct io_uring_cqe *io_uring_peek_cqe(struct io_uring *ring)
+{
+	struct io_uring_cq *cq = &ring->cq;
+	const unsigned int mask = *cq->kring_mask;
+	unsigned int head = *cq->khead;
+
+	read_barrier();
+	if (head != *cq->ktail)
+		return &cq->cqes[head & mask];
+
+	return NULL;
+}
+
+static void run_ring(struct ring_ctx *ctx)
+{
+	struct rate_limiter_state *rls = ctx->region;
+	const unsigned long ms_per_req = 500;
+	const unsigned max_inflight = 2;
+	const unsigned nr_reqs = 8;
+	struct io_uring *ring = &ctx->ring;
+	struct timeval tv_start, tv_end;
+	struct __kernel_timespec ts;
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	unsigned nr_cqes = 0;
+	unsigned long dt;
+	int i, ret;
+
+	rls->max_inflight = max_inflight;
+	ts.tv_sec = 0;
+	ts.tv_nsec = ms_per_req * 1000000;
+
+	for (i = 0; i < nr_reqs; i++) {
+		sqe = io_uring_get_sqe(ring);
+		if (!sqe) {
+			fprintf(stderr, "get sqe failed\n");
+			exit(1);
+		}
+
+		t_io_uring_prep_timeout(sqe, &ts);
+	}
+
+	gettimeofday(&tv_start, NULL);
+
+	ret = io_uring_submit(ring);
+	if (ret || rls->res) {
+		fprintf(stderr, "sqe submit failed: %d %d\n", ret, rls->res);
+		exit(1);
+	}
+
+	rls->to_wait = nr_reqs;
+	ret = io_uring_enter(ring->ring_fd, 0, 0, 0, NULL);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "wait failed %i\n", ret);
+		exit(1);
+	}
+	if (rls->res) {
+		fprintf(stderr, "bpf wait failed %i\n", rls->res);
+		exit(1);
+	}
+
+	while (1) {
+		cqe = io_uring_peek_cqe(ring);
+		if (!cqe)
+			break;
+		if (cqe->res != -ETIME) {
+			fprintf(stderr, "invalid cqe %d\n", ret);
+			exit(1);
+		}
+		io_uring_cqe_seen(ring);
+		nr_cqes++;
+	}
+
+	if (nr_cqes != nr_reqs) {
+		fprintf(stderr, "CQEs missing\n");
+		exit(1);
+	}
+
+	gettimeofday(&tv_end, NULL);
+	dt = mtime_since(&tv_start, &tv_end);
+
+	if (dt < nr_reqs / max_inflight * ms_per_req) {
+		fprintf(stderr, "timeout fired too fast %lu\n", dt);
+		exit(1);
+	}
+}
+
+int main()
+{
+	struct ring_ctx ctx;
+
+	ring_ctx_create_flags(&ctx, sizeof(struct nops_state), 0);
+	setup_bpf_prog(&ctx);
+
+	run_ring(&ctx);
+
+	bpf_link__destroy(rate_limiter_link);
+	rate_limiter__destroy(skel);
+	ring_ctx_destroy(&ctx);
+	return 0;
+}
-- 
2.52.0


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

* [PATCH 3/3] io_uring/selftests: add regbuf xor example
  2026-02-23 14:11 [PATCH 0/3] extra io_uring BPF examples Pavel Begunkov
  2026-02-23 14:12 ` [PATCH 1/3] io_uring/selftests: add BPF'ed cp example Pavel Begunkov
  2026-02-23 14:12 ` [PATCH 2/3] io_uring/selftests: add rate limiter BPF program Pavel Begunkov
@ 2026-02-23 14:12 ` Pavel Begunkov
  2026-02-23 14:14 ` [PATCH 0/3] extra io_uring BPF examples Jens Axboe
  3 siblings, 0 replies; 8+ messages in thread
From: Pavel Begunkov @ 2026-02-23 14:12 UTC (permalink / raw)
  To: io-uring; +Cc: asml.silence, bpf, axboe, Alexei Starovoitov

Add a kfunc and an example of calculating xor for a registered buffer.
That's useful for cases where the registered buffer is not visible by
user space and exists in-kernel only. The rest depends on the BPF
writer, and one way to use it could be to execute all jobs like xor'ing
at the beginning and the do normal request processing (the example
program just stops). Another approach would be to implement it as a part
of BPF CQE processing.

We'll need to think through the registered buffer kfunc API before
merging anything similar (not to mention implementing it properly).

Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
 io_uring/bpf-ops.c                            | 29 +++++++
 tools/testing/selftests/io_uring/Makefile     |  2 +-
 .../testing/selftests/io_uring/common-defs.h  |  6 ++
 tools/testing/selftests/io_uring/xor.bpf.c    | 31 +++++++
 tools/testing/selftests/io_uring/xor.c        | 83 +++++++++++++++++++
 5 files changed, 150 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/io_uring/xor.bpf.c
 create mode 100644 tools/testing/selftests/io_uring/xor.c

diff --git a/io_uring/bpf-ops.c b/io_uring/bpf-ops.c
index 1ffe7ba73b89..51c239c7cb34 100644
--- a/io_uring/bpf-ops.c
+++ b/io_uring/bpf-ops.c
@@ -9,6 +9,7 @@
 #include "memmap.h"
 #include "bpf-ops.h"
 #include "loop.h"
+#include "rsrc.h"
 
 static DEFINE_MUTEX(io_bpf_ctrl_mutex);
 static const struct btf_type *loop_params_type;
@@ -47,11 +48,39 @@ __u8 *bpf_io_uring_get_region(struct io_ring_ctx *ctx, __u32 region_id,
 	return io_region_get_ptr(r);
 }
 
+__bpf_kfunc
+__u8 bpf_io_uring_xor_regbuf(struct io_ring_ctx *ctx, unsigned idx,
+				size_t size)
+{
+	const struct io_mapped_ubuf *imu;
+	struct io_rsrc_node *node;
+	struct iov_iter iter;
+	__u8 xor_res = 0;
+	__u8 buffer[512];
+	int i, ret;
+
+	node = io_rsrc_node_lookup(&ctx->buf_table, idx);
+	if (!node)
+		return 0;
+	imu = node->buf;
+	if (size > imu->len || size > sizeof(buffer))
+		return 0;
+
+	iov_iter_bvec(&iter, ITER_SOURCE, imu->bvec, imu->nr_bvecs, size);
+	ret = copy_from_iter(buffer, size, &iter);
+	if (ret != size)
+		return 0;
+	for (i = 0; i < size; i++)
+		xor_res ^= buffer[i];
+	return xor_res;
+}
+
 __bpf_kfunc_end_defs();
 
 BTF_KFUNCS_START(io_uring_kfunc_set)
 BTF_ID_FLAGS(func, bpf_io_uring_submit_sqes, KF_SLEEPABLE);
 BTF_ID_FLAGS(func, bpf_io_uring_get_region, KF_RET_NULL);
+BTF_ID_FLAGS(func, bpf_io_uring_xor_regbuf, KF_SLEEPABLE);
 BTF_KFUNCS_END(io_uring_kfunc_set)
 
 static const struct btf_kfunc_id_set bpf_io_uring_kfunc_set = {
diff --git a/tools/testing/selftests/io_uring/Makefile b/tools/testing/selftests/io_uring/Makefile
index e0581f96d98a..3b139964f537 100644
--- a/tools/testing/selftests/io_uring/Makefile
+++ b/tools/testing/selftests/io_uring/Makefile
@@ -3,7 +3,7 @@ include ../../../build/Build.include
 include ../../../scripts/Makefile.arch
 include ../../../scripts/Makefile.include
 
-TEST_GEN_PROGS := nops_loop overflow unreg cp rate_limiter
+TEST_GEN_PROGS := nops_loop overflow unreg cp rate_limiter xor
 
 # override lib.mk's default rules
 OVERRIDE_TARGETS := 1
diff --git a/tools/testing/selftests/io_uring/common-defs.h b/tools/testing/selftests/io_uring/common-defs.h
index dae3b0fe8588..ee6c36eae426 100644
--- a/tools/testing/selftests/io_uring/common-defs.h
+++ b/tools/testing/selftests/io_uring/common-defs.h
@@ -49,4 +49,10 @@ struct rate_limiter_state {
 	int res;
 };
 
+struct xor_state {
+	unsigned regbuf_idx;
+	unsigned xor_size;
+	__u8 xor_res;
+};
+
 #endif /* IOU_TOOLS_COMMON_DEFS_H */
diff --git a/tools/testing/selftests/io_uring/xor.bpf.c b/tools/testing/selftests/io_uring/xor.bpf.c
new file mode 100644
index 000000000000..c7c3b46ca1bf
--- /dev/null
+++ b/tools/testing/selftests/io_uring/xor.bpf.c
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "vmlinux.h"
+#include "common-defs.h"
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
+
+const volatile struct ring_info ri;
+
+SEC("struct_ops.s/xor_step")
+int BPF_PROG(xor_step, struct io_ring_ctx *ring, struct iou_loop_params *ls)
+{
+	struct xor_state *xs;
+
+	xs = (void *)bpf_io_uring_get_region(ring, IOU_REGION_MEM,
+				sizeof(*xs));
+	if (!xs)
+		return IOU_LOOP_STOP;
+
+	xs->xor_res = bpf_io_uring_xor_regbuf(ring, xs->regbuf_idx,
+						xs->xor_size);
+	return IOU_LOOP_STOP;
+}
+
+SEC(".struct_ops.link")
+struct io_uring_bpf_ops xor_ops = {
+	.loop_step = (void *)xor_step,
+};
diff --git a/tools/testing/selftests/io_uring/xor.c b/tools/testing/selftests/io_uring/xor.c
new file mode 100644
index 000000000000..c2e7d6af2c92
--- /dev/null
+++ b/tools/testing/selftests/io_uring/xor.c
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/stddef.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <bpf/libbpf.h>
+#include <io_uring/mini_liburing.h>
+
+#include "helpers.h"
+#include "xor.bpf.skel.h"
+
+int main()
+{
+	struct xor_state *xs;
+	struct bpf_link *link;
+	struct xor *skel;
+	struct ring_ctx ctx;
+	__u8 *buffer;
+	size_t buf_size = 512;
+	struct iovec iov;
+	__u8 xorv = 0;
+	int ret, i;
+
+	ring_ctx_create(&ctx, sizeof(*xs));
+
+	skel = xor__open();
+	if (!skel) {
+		fprintf(stderr, "can't generate skeleton\n");
+		exit(1);
+	}
+	skel->struct_ops.xor_ops->ring_fd = ctx.ring.ring_fd;
+	skel->rodata->ri = ctx.ri;
+
+	ret = xor__load(skel);
+	if (ret) {
+		fprintf(stderr, "failed to load skeleton\n");
+		exit(1);
+	}
+	link = bpf_map__attach_struct_ops(skel->maps.xor_ops);
+	if (!link) {
+		fprintf(stderr, "failed to attach ops\n");
+		return 1;
+	}
+
+
+	buffer = aligned_alloc(4096, buf_size);
+	if (!buffer) {
+		fprintf(stderr, "allocation failed\n");
+		return 1;
+	}
+
+	srand((unsigned)time(NULL));
+	for (i = 0; i < buf_size; i++) {
+		buffer[i] = rand() % 256;
+		xorv ^= buffer[i];
+	}
+
+	iov.iov_len = buf_size;
+	iov.iov_base = buffer;
+	ret = io_uring_register_buffers(&ctx.ring, &iov, 1);
+	if (ret < 0) {
+		fprintf(stderr, "can't register buffer\n");
+		return 1;
+	}
+
+	xs = ctx.region;
+	xs->regbuf_idx = 0;
+	xs->xor_size = buf_size;
+	ring_ctx_run(&ctx);
+
+	if (xs->xor_res != xorv) {
+		fprintf(stderr, "invalid result, %i %i\n", xorv, xs->xor_res);
+		return 1;
+	}
+
+	bpf_link__destroy(link);
+	xor__destroy(skel);
+	ring_ctx_destroy(&ctx);
+	return 0;
+}
-- 
2.52.0


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

* Re: [PATCH 0/3] extra io_uring BPF examples
  2026-02-23 14:11 [PATCH 0/3] extra io_uring BPF examples Pavel Begunkov
                   ` (2 preceding siblings ...)
  2026-02-23 14:12 ` [PATCH 3/3] io_uring/selftests: add regbuf xor example Pavel Begunkov
@ 2026-02-23 14:14 ` Jens Axboe
  2026-02-23 14:32   ` Pavel Begunkov
  3 siblings, 1 reply; 8+ messages in thread
From: Jens Axboe @ 2026-02-23 14:14 UTC (permalink / raw)
  To: Pavel Begunkov, io-uring; +Cc: bpf, Alexei Starovoitov

On 2/23/26 7:11 AM, Pavel Begunkov wrote:
> NOT FOR INCLUSION
> 
> Alexei asked for extra more realistic examples. This this series is
> based on top of v9 of the io_uring BPF series and implemented as
> selftests. There could be more, but I stopped with 3 that should
> give an idea how it'll be used:
> 
> 1. A QD=1 file copy program that show cases state machine handling.
> 
> 2. A BPF program rate-limiting the number of inflight io_uring request.
>    That's a good example of what users asked for before but seemed to
>    be too niche to be plumbed into the main io_uring path.
> 
> 3. A toy example of how BPF can interact with registered buffers.

Let's please keep examples in the liburing side, where they can be
with the documentation too.

-- 
Jens Axboe


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

* Re: [PATCH 0/3] extra io_uring BPF examples
  2026-02-23 14:14 ` [PATCH 0/3] extra io_uring BPF examples Jens Axboe
@ 2026-02-23 14:32   ` Pavel Begunkov
  2026-02-23 14:39     ` Jens Axboe
  0 siblings, 1 reply; 8+ messages in thread
From: Pavel Begunkov @ 2026-02-23 14:32 UTC (permalink / raw)
  To: Jens Axboe, io-uring; +Cc: bpf, Alexei Starovoitov

On 2/23/26 14:14, Jens Axboe wrote:
> On 2/23/26 7:11 AM, Pavel Begunkov wrote:
>> NOT FOR INCLUSION
>>
>> Alexei asked for extra more realistic examples. This this series is
>> based on top of v9 of the io_uring BPF series and implemented as
>> selftests. There could be more, but I stopped with 3 that should
>> give an idea how it'll be used:
>>
>> 1. A QD=1 file copy program that show cases state machine handling.
>>
>> 2. A BPF program rate-limiting the number of inflight io_uring request.
>>     That's a good example of what users asked for before but seemed to
>>     be too niche to be plumbed into the main io_uring path.
>>
>> 3. A toy example of how BPF can interact with registered buffers.
> 
> Let's please keep examples in the liburing side, where they can be
> with the documentation too.

I got you a liburing example

https://github.com/isilence/liburing/tree/bpf-ops-example

but need to sync with the selftest. I think I'll rather keep the rest
into a separate repository instead of adding all helpers to liburing,
which will inevitably do similar things but deviating in API and
internal details. And it's simpler on dependency management instead
of requiring libbpf / etc. for liburing. I wanted a space for
misc io_uring bits that doesn't make sense to keep as core liburing
anyway.

-- 
Pavel Begunkov


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

* Re: [PATCH 0/3] extra io_uring BPF examples
  2026-02-23 14:32   ` Pavel Begunkov
@ 2026-02-23 14:39     ` Jens Axboe
  2026-02-23 15:06       ` Pavel Begunkov
  0 siblings, 1 reply; 8+ messages in thread
From: Jens Axboe @ 2026-02-23 14:39 UTC (permalink / raw)
  To: Pavel Begunkov, io-uring; +Cc: bpf, Alexei Starovoitov

On 2/23/26 7:32 AM, Pavel Begunkov wrote:
> On 2/23/26 14:14, Jens Axboe wrote:
>> On 2/23/26 7:11 AM, Pavel Begunkov wrote:
>>> NOT FOR INCLUSION
>>>
>>> Alexei asked for extra more realistic examples. This this series is
>>> based on top of v9 of the io_uring BPF series and implemented as
>>> selftests. There could be more, but I stopped with 3 that should
>>> give an idea how it'll be used:
>>>
>>> 1. A QD=1 file copy program that show cases state machine handling.
>>>
>>> 2. A BPF program rate-limiting the number of inflight io_uring request.
>>>     That's a good example of what users asked for before but seemed to
>>>     be too niche to be plumbed into the main io_uring path.
>>>
>>> 3. A toy example of how BPF can interact with registered buffers.
>>
>> Let's please keep examples in the liburing side, where they can be
>> with the documentation too.
> 
> I got you a liburing example
> 
> https://github.com/isilence/liburing/tree/bpf-ops-example

Right, but that's just the nop thing, which isn't really interesting
outside of doing basic verification of yes indeed the kernel side works.

> but need to sync with the selftest. I think I'll rather keep the rest
> into a separate repository instead of adding all helpers to liburing,
> which will inevitably do similar things but deviating in API and
> internal details. And it's simpler on dependency management instead
> of requiring libbpf / etc. for liburing. I wanted a space for
> misc io_uring bits that doesn't make sense to keep as core liburing
> anyway.

liburing should have support and documentation. It's not that hard to
check for libbpf in configure and either build the support or not. Once
various feature support ends up being fragmented in the ecosystem THAT
is a mess for users, I'd much rather have it consolidated and deal with
it on the liburing side.

-- 
Jens Axboe

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

* Re: [PATCH 0/3] extra io_uring BPF examples
  2026-02-23 14:39     ` Jens Axboe
@ 2026-02-23 15:06       ` Pavel Begunkov
  0 siblings, 0 replies; 8+ messages in thread
From: Pavel Begunkov @ 2026-02-23 15:06 UTC (permalink / raw)
  To: Jens Axboe, io-uring; +Cc: bpf, Alexei Starovoitov

On 2/23/26 14:39, Jens Axboe wrote:
> On 2/23/26 7:32 AM, Pavel Begunkov wrote:
>> On 2/23/26 14:14, Jens Axboe wrote:
>>> On 2/23/26 7:11 AM, Pavel Begunkov wrote:
>>>> NOT FOR INCLUSION
>>>>
>>>> Alexei asked for extra more realistic examples. This this series is
>>>> based on top of v9 of the io_uring BPF series and implemented as
>>>> selftests. There could be more, but I stopped with 3 that should
>>>> give an idea how it'll be used:
>>>>
>>>> 1. A QD=1 file copy program that show cases state machine handling.
>>>>
>>>> 2. A BPF program rate-limiting the number of inflight io_uring request.
>>>>      That's a good example of what users asked for before but seemed to
>>>>      be too niche to be plumbed into the main io_uring path.
>>>>
>>>> 3. A toy example of how BPF can interact with registered buffers.
>>>
>>> Let's please keep examples in the liburing side, where they can be
>>> with the documentation too.
>>
>> I got you a liburing example
>>
>> https://github.com/isilence/liburing/tree/bpf-ops-example
> 
> Right, but that's just the nop thing, which isn't really interesting
> outside of doing basic verification of yes indeed the kernel side works.
> 
>> but need to sync with the selftest. I think I'll rather keep the rest
>> into a separate repository instead of adding all helpers to liburing,
>> which will inevitably do similar things but deviating in API and
>> internal details. And it's simpler on dependency management instead
>> of requiring libbpf / etc. for liburing. I wanted a space for
>> misc io_uring bits that doesn't make sense to keep as core liburing
>> anyway.
> 
> liburing should have support and documentation. It's not that hard to
> check for libbpf in configure and either build the support or not. Once
> various feature support ends up being fragmented in the ecosystem THAT
> is a mess for users, I'd much rather have it consolidated and deal with
> it on the liburing side.

What kind of support do you mean? Installation, removal, other BPF
management is all on the libbpf side, e.g. skeleton open/load/etc.,
and with it generating new structures for each of them, I'm not sure
how to make it into some generic API whether it's liburing or not
instead of making users to fill in the gaps, nor I think we should.
As for figuring out right helpers and abstractions for BPF program
writing, it'll be a gradual process, and I'd rather have it
separately, it's not like I can reuse liburing for that.

-- 
Pavel Begunkov


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

end of thread, other threads:[~2026-02-23 15:06 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-23 14:11 [PATCH 0/3] extra io_uring BPF examples Pavel Begunkov
2026-02-23 14:12 ` [PATCH 1/3] io_uring/selftests: add BPF'ed cp example Pavel Begunkov
2026-02-23 14:12 ` [PATCH 2/3] io_uring/selftests: add rate limiter BPF program Pavel Begunkov
2026-02-23 14:12 ` [PATCH 3/3] io_uring/selftests: add regbuf xor example Pavel Begunkov
2026-02-23 14:14 ` [PATCH 0/3] extra io_uring BPF examples Jens Axboe
2026-02-23 14:32   ` Pavel Begunkov
2026-02-23 14:39     ` Jens Axboe
2026-02-23 15:06       ` Pavel Begunkov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox