From: Pavel Begunkov <asml.silence@gmail.com>
To: io-uring@vger.kernel.org
Cc: asml.silence@gmail.com, bpf@vger.kernel.org, axboe@kernel.dk,
Alexei Starovoitov <alexei.starovoitov@gmail.com>
Subject: [PATCH 1/3] io_uring/selftests: add BPF'ed cp example
Date: Mon, 23 Feb 2026 14:12:00 +0000 [thread overview]
Message-ID: <3c300bb41da3004cba8924e1a6a8dbeb50c3468c.1771850496.git.asml.silence@gmail.com> (raw)
In-Reply-To: <cover.1771850496.git.asml.silence@gmail.com>
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
next prev parent reply other threads:[~2026-02-23 14:12 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-23 14:11 [PATCH 0/3] extra io_uring BPF examples Pavel Begunkov
2026-02-23 14:12 ` Pavel Begunkov [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=3c300bb41da3004cba8924e1a6a8dbeb50c3468c.1771850496.git.asml.silence@gmail.com \
--to=asml.silence@gmail.com \
--cc=alexei.starovoitov@gmail.com \
--cc=axboe@kernel.dk \
--cc=bpf@vger.kernel.org \
--cc=io-uring@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox