[PATCH 1/3] io_uring: Redesign common helpers to follow liburing API conventions
Sebastian Chlad <[email protected]>
| Newsgroups | gmane.linux.ltp |
|---|---|
| Message-ID | <[email protected]> |
Replace coupled submit and wait helpers with a proper liburing-style API: - io_uring_get_sqe(): get next SQE slot from the submission ring - io_uring_prep_rw(): generic SQE fill for read/write perations - io_uring_prep_read/write/readv/writev(): operation-specific prep helpers - io_uring_sqe_set_data64(): set user_data explicitly on the SQE - io_uring_submit(): submit all pending SQEs to the kernel - io_uring_wait_cqe(): wait for next CQE, return pointer without consuming it - io_uring_cqe_seen(): advance the CQ head to mark a CQE as consumed The old io_uring_do_io_op() and io_uring_do_vec_io_op() helpers are removed. They coupled SQE submission with an immediate CQE wait, making tests effectively synchronous, and emitted tst_res(TPASS) from inside the helper, hiding where test results came from. They also set user_data to the opcode value, which is meaningless as a per-request tag and would cause spurious passes in any test with multiple in-flight SQEs of the same opcode. With the new API, each test controls SQE preparation, user_data assignment, submission, CQE validation and result reporting explicitly in its own body, matching how real io_uring applications are written. Signed-off-by: Sebastian Chlad <sebastian.chlad-IBi9RG/[email protected]> --- .../syscalls/io_uring/io_uring_common.h | 193 ++++++++---------- 1 file changed, 85 insertions(+), 108 deletions(-) diff --git a/testcases/kernel/syscalls/io_uring/io_uring_common.h b/testcases/kernel/syscalls/io_uring/io_uring_common.h index aa31339fb..18fdfc9b6 100644 --- a/testcases/kernel/syscalls/io_uring/io_uring_common.h +++ b/testcases/kernel/syscalls/io_uring/io_uring_common.h @@ -3,6 +3,8 @@ * Copyright (C) 2026 IBM * Author: Sachin Sant <[email protected]> * + * Copyright (C) 2026 Sebastian Chlad <sebastian.chlad-IBi9RG/[email protected]> + * * Common definitions and helper functions for io_uring tests */ @@ -43,6 +45,7 @@ struct io_uring_submit { size_t sq_ptr_size; void *cq_ptr; size_t cq_ptr_size; + unsigned int sq_pending; }; /* @@ -117,162 +120,136 @@ static inline void io_uring_cleanup_queue(struct io_uring_submit *s, } /* - * Internal helper to submit a single SQE to the submission queue - * Used by both vectored and non-vectored I/O operations + * Get the next available SQE slot from the submission ring. + * The SQE is zeroed and tracked as pending until io_uring_submit() is called. */ -static inline void io_uring_submit_sqe_internal(struct io_uring_submit *s, - int fd, int opcode, - unsigned long addr, - unsigned int len, - off_t offset, - unsigned long long user_data) +static inline struct io_uring_sqe *io_uring_get_sqe(struct io_uring_submit *s) { struct io_sq_ring *sring = &s->sq_ring; - unsigned int tail, index; - struct io_uring_sqe *sqe; - - tail = *sring->tail; - index = tail & *sring->ring_mask; - sqe = &s->sqes[index]; + unsigned int index = (*sring->tail + s->sq_pending) & *sring->ring_mask; + struct io_uring_sqe *sqe = &s->sqes[index]; memset(sqe, 0, sizeof(*sqe)); + sring->array[index] = index; + s->sq_pending++; + + return sqe; +} + +/* + * Generic SQE fill for read/write family operations. + * Does not touch user_data - caller sets it via io_uring_sqe_set_data64(). + */ +static inline void io_uring_prep_rw(struct io_uring_sqe *sqe, int opcode, + int fd, const void *addr, unsigned int len, + off_t offset) +{ sqe->opcode = opcode; sqe->fd = fd; - sqe->addr = addr; + sqe->addr = (unsigned long)addr; sqe->len = len; sqe->off = offset; - sqe->user_data = user_data; +} - sring->array[index] = index; - tail++; +static inline void io_uring_prep_read(struct io_uring_sqe *sqe, int fd, + void *buf, size_t len, off_t offset) +{ + io_uring_prep_rw(sqe, IORING_OP_READ, fd, buf, len, offset); +} - *sring->tail = tail; +static inline void io_uring_prep_write(struct io_uring_sqe *sqe, int fd, + const void *buf, size_t len, off_t offset) +{ + io_uring_prep_rw(sqe, IORING_OP_WRITE, fd, buf, len, offset); } -/* - * Submit a single SQE to the submission queue - * For basic read/write operations (non-vectored) - */ -static inline void io_uring_submit_sqe(struct io_uring_submit *s, int fd, - int opcode, void *buf, size_t len, +static inline void io_uring_prep_readv(struct io_uring_sqe *sqe, int fd, + struct iovec *iovs, int nr_vecs, off_t offset) { - io_uring_submit_sqe_internal(s, fd, opcode, (unsigned long)buf, - len, offset, opcode); + io_uring_prep_rw(sqe, IORING_OP_READV, fd, iovs, nr_vecs, offset); +} + +static inline void io_uring_prep_writev(struct io_uring_sqe *sqe, int fd, + struct iovec *iovs, int nr_vecs, + off_t offset) +{ + io_uring_prep_rw(sqe, IORING_OP_WRITEV, fd, iovs, nr_vecs, offset); } /* - * Submit a vectored SQE to the submission queue - * For readv/writev operations + * Set the user_data field of an SQE. user_data is returned verbatim in the + * corresponding CQE and must be unique per in-flight request to allow correct + * correlation of completions. */ -static inline void io_uring_submit_sqe_vec(struct io_uring_submit *s, int fd, - int opcode, struct iovec *iovs, - int nr_vecs, off_t offset) +static inline void io_uring_sqe_set_data64(struct io_uring_sqe *sqe, + uint64_t data) { - io_uring_submit_sqe_internal(s, fd, opcode, (unsigned long)iovs, - nr_vecs, offset, opcode); + sqe->user_data = data; } /* - * Map io_uring operation code to human-readable name + * Submit all pending SQEs to the kernel. */ -static inline const char *ioring_op_name(int op) +static inline void io_uring_submit(struct io_uring_submit *s) { - switch (op) { - case IORING_OP_READV: - return "IORING_OP_READV"; - case IORING_OP_WRITEV: - return "IORING_OP_WRITEV"; - case IORING_OP_READ: - return "IORING_OP_READ"; - case IORING_OP_WRITE: - return "IORING_OP_WRITE"; - default: - return "UNKNOWN"; - } + unsigned int pending = s->sq_pending; + + if (!pending) + return; + + *s->sq_ring.tail += pending; + s->sq_pending = 0; + + if (io_uring_enter(s->ring_fd, pending, 0, 0, NULL) < 0) + tst_brk(TBROK | TERRNO, "io_uring_enter() failed"); } /* - * Wait for and validate a completion queue entry - * Aborts test on failure using tst_brk() + * Wait for the next CQE and return a pointer to it. + * Does not advance the CQ head - call io_uring_cqe_seen() when done. */ -static inline void io_uring_wait_cqe(struct io_uring_submit *s, - int expected_res, int expected_opcode, - sigset_t *sig) +static inline struct io_uring_cqe *io_uring_wait_cqe(struct io_uring_submit *s, + sigset_t *sig) { struct io_cq_ring *cring = &s->cq_ring; - struct io_uring_cqe *cqe; - unsigned int head; int ret; - ret = io_uring_enter(s->ring_fd, 1, 1, IORING_ENTER_GETEVENTS, sig); + ret = io_uring_enter(s->ring_fd, 0, 1, IORING_ENTER_GETEVENTS, sig); if (ret < 0) tst_brk(TBROK | TERRNO, "io_uring_enter() failed"); - head = *cring->head; - if (head == *cring->tail) + if (*cring->head == *cring->tail) tst_brk(TBROK, "No completion event received"); - cqe = &cring->cqes[head & *cring->ring_mask]; - - if (cqe->user_data != (uint64_t)expected_opcode) { - *cring->head = head + 1; - tst_brk(TBROK, "Unexpected user_data: got %llu, expected %d", - (unsigned long long)cqe->user_data, expected_opcode); - } - - if (cqe->res != expected_res) { - *cring->head = head + 1; - tst_brk(TBROK, "Operation failed: res=%d, expected=%d", - cqe->res, expected_res); - } - - *cring->head = head + 1; + return &cring->cqes[*cring->head & *cring->ring_mask]; } /* - * Initialize buffer with a repeating character pattern - * Useful for creating test data with predictable patterns + * Mark the current CQE as consumed, advancing the CQ head. */ -static inline void io_uring_init_buffer_pattern(char *buf, size_t size, - char pattern) +static inline void io_uring_cqe_seen(struct io_uring_submit *s) { - size_t i; - - for (i = 0; i < size; i++) - buf[i] = pattern; + (*s->cq_ring.head)++; } /* - * Submit and wait for a non-vectored I/O operation - * Combines io_uring_submit_sqe() and io_uring_wait_cqe() with result reporting - */ -static inline void io_uring_do_io_op(struct io_uring_submit *s, int fd, - int op, void *buf, size_t len, - off_t offset, sigset_t *sig) -{ - io_uring_submit_sqe(s, fd, op, buf, len, offset); - io_uring_wait_cqe(s, len, op, sig); - tst_res(TPASS, "OP=%s (%02x) fd=%i buf=%p len=%zu offset=%jd", - ioring_op_name(op), op, fd, buf, len, (intmax_t)offset); -} - -/* - * Submit and wait for a vectored I/O operation - * Combines io_uring_submit_sqe_vec() and io_uring_wait_cqe() with - * result reporting + * Map io_uring operation code to human-readable name */ -static inline void io_uring_do_vec_io_op(struct io_uring_submit *s, int fd, - int op, struct iovec *iovs, - int nvecs, off_t offset, - int expected_size, sigset_t *sig) +static inline const char *ioring_op_name(int op) { - io_uring_submit_sqe_vec(s, fd, op, iovs, nvecs, offset); - io_uring_wait_cqe(s, expected_size, op, sig); - tst_res(TPASS, "OP=%s (%02x) fd=%i iovs=%p nvecs=%i offset=%jd " - "expected_size=%i", - ioring_op_name(op), op, fd, iovs, nvecs, (intmax_t)offset, - expected_size); + switch (op) { + case IORING_OP_READV: + return "IORING_OP_READV"; + case IORING_OP_WRITEV: + return "IORING_OP_WRITEV"; + case IORING_OP_READ: + return "IORING_OP_READ"; + case IORING_OP_WRITE: + return "IORING_OP_WRITE"; + default: + return "UNKNOWN"; + } } #endif /* IO_URING_COMMON_H */ -- 2.51.0 -- Mailing list info: https://lists.linux.it/listinfo/ltp