Add nop testing for IORING_SETUP_CQE_MIXED

Add NOP testing for mixed sized CQEs.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h
index 4c8c9da..643514e 100644
--- a/src/include/liburing/io_uring.h
+++ b/src/include/liburing/io_uring.h
@@ -436,6 +436,7 @@
  * IORING_NOP_INJECT_RESULT	Inject result from sqe->result
  */
 #define IORING_NOP_INJECT_RESULT	(1U << 0)
+#define IORING_NOP_CQE32		(1U << 5)
 
 /*
  * IO completion data structure (Completion Queue Entry)
diff --git a/test/Makefile b/test/Makefile
index edfc0df..54251bf 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -148,6 +148,8 @@
 	no-mmap-inval.c \
 	nop-all-sizes.c \
 	nop.c \
+	nop32.c \
+	nop32-overflow.c \
 	ooo-file-unreg.c \
 	openat2.c \
 	open-close.c \
diff --git a/test/nop.c b/test/nop.c
index d3ab766..b02dcb8 100644
--- a/test/nop.c
+++ b/test/nop.c
@@ -83,7 +83,7 @@
 		goto err;
 	}
 	if (!cqe->user_data) {
-		fprintf(stderr, "Unexpected 0 user_data\n");
+		fprintf(stderr, "Unexpected 0 user_data: %ld\n", (long) cqe->user_data);
 		goto err;
 	}
 	if (cqe32) {
@@ -140,7 +140,7 @@
 			goto err;
 		}
 		if (!cqe->user_data) {
-			fprintf(stderr, "Unexpected 0 user_data\n");
+			fprintf(stderr, "Unexpected 0 user_data: %ld\n", (long) cqe->user_data);
 			goto err;
 		}
 		if (cqe32) {
diff --git a/test/nop32-overflow.c b/test/nop32-overflow.c
new file mode 100644
index 0000000..72beac2
--- /dev/null
+++ b/test/nop32-overflow.c
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: run various nop tests
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "liburing.h"
+#include "helpers.h"
+#include "test.h"
+
+static int seq;
+
+static int fail_cqe(struct io_uring_cqe *cqe, int off, const char *msg)
+{
+	fprintf(stderr, "Bad cqe at off %d: %s\n", off, msg);
+	fprintf(stderr, "CQE ud=%lu, res=%u, flags=%x\n", (long) cqe->user_data, cqe->res, cqe->flags);
+	return T_EXIT_FAIL;
+}
+
+static int test_ring(unsigned flags)
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	struct io_uring ring;
+	struct io_uring_params p = { };
+	unsigned head;
+	int ret, i, cqe_nr;
+
+	p.flags = flags | IORING_SETUP_CQSIZE;
+	p.cq_entries = 8;
+	ret = io_uring_queue_init_params(8, &ring, &p);
+	if (ret) {
+		if (ret == -EINVAL)
+			return T_EXIT_SKIP;
+		fprintf(stderr, "ring setup failed: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	/*
+	 * Prep 8 NOP requests, with the first being a 16b one to
+	 * unalign the ring. The 4 latter ones will hit overflow,
+	 * and after the first 4 we should see a SKIP cqe.
+	 */
+	for (i = 0; i < 8; i++) {
+		sqe = io_uring_get_sqe(&ring);
+		io_uring_prep_nop(sqe);
+		if (i)
+			sqe->nop_flags = IORING_NOP_CQE32;
+		sqe->user_data = ++seq;
+	}
+
+	io_uring_submit(&ring);
+
+	/*
+	 * Should find the following CQEs:
+	 *
+	 * 1: 16b with ud 1
+	 * 2: 32b with ud 2
+	 * 3: 32b with ud 3
+	 * 4: 32b with ud 4
+	 * 5: 16b skip CQE
+	 *
+	 * And then we should have overflow pending to flush the rest
+	 */
+	i = cqe_nr = 0;
+	io_uring_for_each_cqe(&ring, head, cqe) {
+		switch (i) {
+		case 0:
+			if (cqe->user_data != 1)
+				return fail_cqe(cqe, i, "Non-overflow UD");
+			if (cqe->flags & IORING_CQE_F_32)
+				return fail_cqe(cqe, i, "Non-overflow flags");
+			break;
+		case 1:
+		case 2:
+		case 3:
+			if (cqe->user_data != i + 1)
+				return fail_cqe(cqe, i, "Non-overflow 32b UD");
+			if (!(cqe->flags & IORING_CQE_F_32))
+				return fail_cqe(cqe, i, "Non-overflow 32b flags");
+			break;
+		case 4:
+			if (!(cqe->flags & IORING_CQE_F_SKIP))
+				return fail_cqe(cqe, i, "Non-overflow skip");
+			break;
+		default:
+			return fail_cqe(cqe, i, "Bogus CQE");
+		}
+		i++;
+		cqe_nr += io_uring_cqe_nr(cqe);
+	}
+	io_uring_cq_advance(&ring, cqe_nr);
+
+	/*
+	 * This should flush overflow, and we should see:
+	 *
+	 * 1: 32b with ud 5
+	 * 2: 32b with ud 6
+	 * 3: 32b with ud 7
+	 * 4: 32b with ud 8
+	 */
+	for (i = 0; i < 4; i++) {
+		ret = io_uring_wait_cqe(&ring, &cqe);
+		if (ret) {
+			fprintf(stderr, "wait ret %d\n", ret);
+			return T_EXIT_FAIL;
+		}
+		switch (i) {
+		case 0:
+		case 1:
+		case 2:
+		case 3:
+			if (cqe->user_data != i + 5)
+				return fail_cqe(cqe, i + 5, "Overflow UD");
+			if (!(cqe->flags & IORING_CQE_F_32))
+				return fail_cqe(cqe, i + 5, "Overflow flags");
+			break;
+		default:
+			return fail_cqe(cqe, i + 5, "Bogus CQE");
+		}
+		io_uring_cqe_seen(&ring, cqe);
+	}
+
+	io_uring_queue_exit(&ring);
+	return ret;
+}
+
+int main(int argc, char *argv[])
+{
+	int ret;
+
+	if (argc > 1)
+		return T_EXIT_SKIP;
+
+	ret = test_ring(IORING_SETUP_CQE_MIXED);
+	if (ret == T_EXIT_SKIP) {
+		return T_EXIT_SKIP;
+	} else if (ret != T_EXIT_PASS) {
+		fprintf(stderr, "Mixed ring test failed\n");
+		return ret;
+	}
+
+	return T_EXIT_PASS;
+}
diff --git a/test/nop32.c b/test/nop32.c
new file mode 100644
index 0000000..7eee506
--- /dev/null
+++ b/test/nop32.c
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: run various nop tests
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "liburing.h"
+#include "helpers.h"
+#include "test.h"
+
+static int seq;
+static int extra1 = 100, extra2 = 200;
+static int want_extra1 = 100, want_extra2 = 200;
+
+static int test_single_nop(struct io_uring *ring, unsigned req_flags, bool cqe32)
+{
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	int ret;
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "get sqe failed\n");
+		goto err;
+	}
+
+	io_uring_prep_nop(sqe);
+	sqe->user_data = ++seq;
+	sqe->flags |= req_flags;
+	if (cqe32) {
+		sqe->nop_flags = IORING_NOP_CQE32;
+		sqe->off = extra1++;
+		sqe->addr = extra2++;
+	}
+
+	ret = io_uring_submit(ring);
+	if (ret <= 0) {
+		fprintf(stderr, "sqe submit failed: %d\n", ret);
+		goto err;
+	}
+
+	ret = io_uring_wait_cqe(ring, &cqe);
+	if (ret < 0) {
+		fprintf(stderr, "wait completion %d\n", ret);
+		goto err;
+	}
+	if (!cqe->user_data) {
+		fprintf(stderr, "Unexpected 0 user_data: %ld\n", (long) cqe->user_data);
+		goto err;
+	}
+	if (cqe32) {
+		if (!(cqe->flags & IORING_CQE_F_32)) {
+			fprintf(stderr, "CQE_F_32 not set\n");
+			goto err;
+		}
+		if (cqe->big_cqe[0] != want_extra1) {
+			fprintf(stderr, "Unexpected extra1: %ld, want %d\n", (long) cqe->big_cqe[0], want_extra1);
+			goto err;
+
+		}
+		want_extra1++;
+		if (cqe->big_cqe[1] != want_extra2) {
+			fprintf(stderr, "Unexpected extra2: %ld, want %d\n", (long) cqe->big_cqe[1], want_extra2);
+			goto err;
+		}
+		want_extra2++;
+	}
+	io_uring_cqe_seen(ring, cqe);
+	return T_EXIT_PASS;
+err:
+	return T_EXIT_FAIL;
+}
+
+static int test_ring(unsigned flags)
+{
+	struct io_uring ring;
+	struct io_uring_params p = { };
+	int ret, i;
+
+	p.flags = flags;
+	ret = io_uring_queue_init_params(8, &ring, &p);
+	if (ret) {
+		if (ret == -EINVAL)
+			return T_EXIT_SKIP;
+		fprintf(stderr, "ring setup failed: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	test_single_nop(&ring, 0, 0);
+
+	for (i = 0; i < 16; i++) {
+		ret = test_single_nop(&ring, 0, 1);
+		if (ret) {
+			printf("fail off %d\n", i);
+			fprintf(stderr, "test_single_nop failed\n");
+			goto err;
+		}
+	}
+err:
+	io_uring_queue_exit(&ring);
+	return ret;
+}
+
+int main(int argc, char *argv[])
+{
+	int ret;
+
+	if (argc > 1)
+		return T_EXIT_SKIP;
+
+	ret = test_ring(IORING_SETUP_CQE_MIXED);
+	if (ret == T_EXIT_SKIP) {
+		return T_EXIT_SKIP;
+	} else if (ret != T_EXIT_PASS) {
+		fprintf(stderr, "Mixed ring test failed\n");
+		return ret;
+	}
+
+	return T_EXIT_PASS;
+}