test/large-resize: test resizing with pending large CQEs/SQEs

Test SETUP_SQE128 and SETUP_CQE32 with pending entries in the SQ
and CQ rings during resize, and ensure that everything is copied
correctly.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/test/Makefile b/test/Makefile
index d16c7bd..10927d9 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -157,6 +157,7 @@
 	io-wq-unused-exit.c \
 	iowait.c \
 	kallsyms.c \
+	large-resize.c \
 	lfs-openat.c \
 	lfs-openat-write.c \
 	link.c \
diff --git a/test/large-resize.c b/test/large-resize.c
new file mode 100644
index 0000000..b5fbd8b
--- /dev/null
+++ b/test/large-resize.c
@@ -0,0 +1,475 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: test ring resizing with fixed large sqes/cqes, and with
+ *		mixed mode sqes/cqes.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "liburing.h"
+#include "helpers.h"
+
+static int test_cqe32_resize(void)
+{
+	struct io_uring ring;
+	struct io_uring_params p = { }, new_p = { };
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	int ret, i;
+
+	p.flags = IORING_SETUP_CQE32 | IORING_SETUP_DEFER_TASKRUN |
+		  IORING_SETUP_SINGLE_ISSUER;
+	p.sq_entries = 8;
+	p.cq_entries = 8;
+
+	ret = io_uring_queue_init_params(8, &ring, &p);
+	if (ret) {
+		if (ret == -EINVAL)
+			return T_EXIT_SKIP;
+		fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+		return T_EXIT_FAIL;
+	}
+
+	for (i = 0; i < 4; i++) {
+		sqe = io_uring_get_sqe(&ring);
+		io_uring_prep_nop(sqe);
+		sqe->user_data = 0xAAAA0000ULL + i;
+	}
+
+	ret = io_uring_submit(&ring);
+	if (ret != 4) {
+		fprintf(stderr, "submit: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	/* Resize the ring */
+	new_p.sq_entries = 8;
+	new_p.cq_entries = 16;
+	new_p.flags = IORING_SETUP_CQSIZE;
+
+	ret = io_uring_resize_rings(&ring, &new_p);
+	if (ret) {
+		if (ret == -EINVAL)
+			return T_EXIT_SKIP;
+		fprintf(stderr, "resize failed: %s\n", strerror(-ret));
+		return T_EXIT_FAIL;
+	}
+
+	for (i = 0; i < 4; i++) {
+		ret = io_uring_wait_cqe(&ring, &cqe);
+		if (ret) {
+			fprintf(stderr, "wait_cqe: %s\n", strerror(-ret));
+			return T_EXIT_FAIL;
+		}
+
+		if (cqe->user_data != (0xAAAA0000ULL + i)) {
+			fprintf(stderr, "  *** CQE32 CORRUPTION DETECTED ***\n");
+			return T_EXIT_FAIL;
+		}
+
+		io_uring_cqe_seen(&ring, cqe);
+	}
+
+	io_uring_queue_exit(&ring);
+	return T_EXIT_PASS;
+}
+
+static int test_cqe_mixed_resize(void)
+{
+	struct io_uring ring;
+	struct io_uring_params p = { }, new_p = { };
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	int ret, i;
+
+	/* Setup ring with CQE_MIXED, DEFER_TASKRUN, SINGLE_ISSUER */
+	p.flags = IORING_SETUP_CQE_MIXED | IORING_SETUP_DEFER_TASKRUN |
+		  IORING_SETUP_SINGLE_ISSUER;
+	p.sq_entries = 8;
+	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_init: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	for (i = 0; i < 4; i++) {
+		sqe = io_uring_get_sqe(&ring);
+		if (!sqe) {
+			fprintf(stderr, "get_sqe failed\n");
+			return T_EXIT_FAIL;
+		}
+
+		if (i % 2 == 0) {
+			io_uring_prep_nop(sqe);
+			sqe->user_data = 0xBBBB0000ULL + i;
+		} else {
+			io_uring_prep_nop(sqe);  /* Still NOP but different pattern */
+			sqe->user_data = 0xCCCC0000ULL + i;
+		}
+	}
+
+	ret = io_uring_submit(&ring);
+	if (ret != 4) {
+		fprintf(stderr, "submit: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	/* Resize the ring */
+	new_p.sq_entries = 8;
+	new_p.cq_entries = 16;
+	new_p.flags = IORING_SETUP_CQSIZE;
+
+	ret = io_uring_resize_rings(&ring, &new_p);
+	if (ret) {
+		fprintf(stderr, "resize failed: %s\n", strerror(-ret));
+		return T_EXIT_FAIL;
+	}
+
+	/* Reap and verify CQEs */
+	for (i = 0; i < 4; i++) {
+		uint64_t expected;
+
+		ret = io_uring_wait_cqe(&ring, &cqe);
+		if (ret) {
+			fprintf(stderr, "wait_cqe: %s\n", strerror(-ret));
+			return T_EXIT_FAIL;
+		}
+
+		expected = (i % 2 == 0) ? (0xBBBB0000ULL + i) : (0xCCCC0000ULL + i);
+		if (cqe->user_data != expected) {
+			fprintf(stderr, "  *** CQE_MIXED CORRUPTION DETECTED ***\n");
+			return T_EXIT_FAIL;
+		}
+
+		io_uring_cqe_seen(&ring, cqe);
+	}
+
+	io_uring_queue_exit(&ring);
+	return T_EXIT_PASS;
+}
+
+static int test_ring_wrapping(void)
+{
+	struct io_uring ring;
+	struct io_uring_params p = { }, new_p = { };
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	int ret, i, consumed_count;
+	int remaining_expected[] = {6, 7};
+	int remaining_count = 0;
+
+	/* Setup small ring to force wrapping */
+	p.flags = IORING_SETUP_CQE32 | IORING_SETUP_DEFER_TASKRUN |
+		  IORING_SETUP_SINGLE_ISSUER;
+	p.sq_entries = 4;
+	p.cq_entries = 4;
+
+	ret = io_uring_queue_init_params(4, &ring, &p);
+	if (ret) {
+		if (ret == -EINVAL)
+			return T_EXIT_SKIP;
+		fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+		return T_EXIT_FAIL;
+	}
+
+	/* Submit more entries to force head/tail wrapping */
+	consumed_count = 0;
+	for (i = 0; i < 8; i++) {
+		sqe = io_uring_get_sqe(&ring);
+		if (!sqe) {
+			fprintf(stderr, "get_sqe failed at %d\n", i);
+			return T_EXIT_FAIL;
+		}
+		io_uring_prep_nop(sqe);
+		sqe->user_data = 0xDDDD0000ULL + i;
+
+		ret = io_uring_submit(&ring);
+		if (ret != 1) {
+			fprintf(stderr, "submit failed: %d\n", ret);
+			return T_EXIT_FAIL;
+		}
+
+		/* Consume some to create wrap scenario */
+		if (i >= 2) {
+			uint64_t expected;
+
+			ret = io_uring_wait_cqe(&ring, &cqe);
+			if (ret) {
+				fprintf(stderr, "wait_cqe: %s\n", strerror(-ret));
+				return T_EXIT_FAIL;
+			}
+
+			/* Verify early consumed CQE */
+			expected = 0xDDDD0000ULL + consumed_count;
+
+			if (cqe->user_data != expected) {
+				fprintf(stderr, "  *** EARLY CQE CORRUPTION DETECTED ***\n");
+				return T_EXIT_FAIL;
+			}
+
+			io_uring_cqe_seen(&ring, cqe);
+			consumed_count++;
+		}
+	}
+
+	/* Now resize with pending wrapped entries */
+	new_p.sq_entries = 4;
+	new_p.cq_entries = 16;
+
+	ret = io_uring_resize_rings(&ring, &new_p);
+	if (ret) {
+		fprintf(stderr, "resize failed: %s\n", strerror(-ret));
+		return T_EXIT_FAIL;
+	}
+
+	/* Consume remaining entries (should be operations 6 and 7) */
+	while (true) {
+		uint64_t expected;
+
+		ret = io_uring_peek_cqe(&ring, &cqe);
+		if (ret == -EAGAIN)
+			break;
+		if (ret) {
+			fprintf(stderr, "peek_cqe: %s\n", strerror(-ret));
+			return T_EXIT_FAIL;
+		}
+
+		if (remaining_count >= 2) {
+			printf("  *** TOO MANY REMAINING CQES ***\n");
+			return T_EXIT_FAIL;
+		}
+
+		expected = 0xDDDD0000ULL + remaining_expected[remaining_count];
+		if (cqe->user_data != expected) {
+			fprintf(stderr, "  *** REMAINING CQE CORRUPTION DETECTED ***\n");
+			return T_EXIT_FAIL;
+		}
+
+		io_uring_cqe_seen(&ring, cqe);
+		remaining_count++;
+	}
+
+	if (remaining_count != 2) {
+		fprintf(stderr, "  *** WRONG NUMBER OF REMAINING CQES: got %d, expected 2 ***\n", remaining_count);
+		return T_EXIT_FAIL;
+	}
+
+	io_uring_queue_exit(&ring);
+	return T_EXIT_PASS;
+}
+
+static int test_sqe128_resize(void)
+{
+	struct io_uring ring;
+	struct io_uring_params p = { }, new_p = { };
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	int ret, i;
+
+	p.flags = IORING_SETUP_SQE128 | IORING_SETUP_DEFER_TASKRUN |
+		  IORING_SETUP_SINGLE_ISSUER;
+	p.sq_entries = 8;
+	p.cq_entries = 8;
+
+	ret = io_uring_queue_init_params(8, &ring, &p);
+	if (ret) {
+		if (ret == -EINVAL)
+			return T_EXIT_SKIP;
+		fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+		return T_EXIT_FAIL;
+	}
+
+	/* Prepare 4 large SQEs but don't submit them yet */
+	for (i = 0; i < 4; i++) {
+		sqe = io_uring_get_sqe(&ring);
+		if (!sqe) {
+			fprintf(stderr, "get_sqe failed\n");
+			return 1;
+		}
+		io_uring_prep_nop(sqe);
+		sqe->user_data = 0xEEEE0000ULL + i;
+
+		/* Fill some extended SQE data to ensure 128-byte copy works */
+		if (ring.sq.ring_ptr) {
+			/* Access extended part of SQE128 if available */
+			memset(sqe->cmd, 0x55 + i, sizeof(sqe->cmd));
+		}
+	}
+
+	__io_uring_flush_sq(&ring);
+
+	/* Resize the ring while SQEs are pending */
+	new_p.sq_entries = 16;
+	new_p.cq_entries = 16;
+	new_p.flags = IORING_SETUP_CQSIZE;
+
+	ret = io_uring_resize_rings(&ring, &new_p);
+	if (ret) {
+		fprintf(stderr, "resize failed: %s\n", strerror(-ret));
+		return 1;
+	}
+
+	/* Now submit the pending SQEs */
+	ret = io_uring_submit(&ring);
+	if (ret != 4) {
+		fprintf(stderr, "submit after resize: %d (expected 4)\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	/* Reap and verify CQEs */
+	for (i = 0; i < 4; i++) {
+		ret = io_uring_wait_cqe(&ring, &cqe);
+		if (ret) {
+			fprintf(stderr, "wait_cqe: %s\n", strerror(-ret));
+			return T_EXIT_FAIL;
+		}
+
+		if (cqe->user_data != (0xEEEE0000ULL + i)) {
+			printf("  *** SQE128 CORRUPTION DETECTED ***\n");
+			return T_EXIT_FAIL;
+		}
+
+		io_uring_cqe_seen(&ring, cqe);
+	}
+
+	io_uring_queue_exit(&ring);
+	return T_EXIT_PASS;
+}
+
+static int test_sqe_mixed_resize(void)
+{
+	struct io_uring ring;
+	struct io_uring_params p = { }, new_p = { };
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	int ret, i;
+
+	p.flags = IORING_SETUP_SQE_MIXED | IORING_SETUP_DEFER_TASKRUN |
+		  IORING_SETUP_SINGLE_ISSUER;
+	p.sq_entries = 8;
+	p.cq_entries = 8;
+
+	ret = io_uring_queue_init_params(8, &ring, &p);
+	if (ret) {
+		if (ret == -EINVAL)
+			return T_EXIT_SKIP;
+		return T_EXIT_FAIL;
+	}
+
+	/* Prepare mix of regular and large SQEs but don't submit them yet */
+	for (i = 0; i < 4; i++) {
+		sqe = io_uring_get_sqe(&ring);
+		if (!sqe) {
+			fprintf(stderr, "get_sqe failed\n");
+			return T_EXIT_FAIL;
+		}
+
+		/* Alternate between regular NOPs and operations that might use large SQEs */
+		if (i % 2 == 0) {
+			io_uring_prep_nop(sqe);
+			sqe->user_data = 0xFFFF0000ULL + i;
+		} else {
+			io_uring_prep_nop(sqe);  /* Still NOP but different pattern */
+			sqe->user_data = 0x11110000ULL + i;
+			/* Fill extended data for mixed SQE */
+			memset(sqe->cmd, 0xAA + i, sizeof(sqe->cmd));
+		}
+	}
+
+	__io_uring_flush_sq(&ring);
+
+	/* Resize the ring while mixed SQEs are pending */
+	new_p.sq_entries = 16;
+	new_p.cq_entries = 16;
+	new_p.flags = IORING_SETUP_CQSIZE;
+
+	ret = io_uring_resize_rings(&ring, &new_p);
+	if (ret) {
+		fprintf(stderr, "resize failed: %s\n", strerror(-ret));
+		return T_EXIT_FAIL;
+	}
+
+	/* Now submit the pending mixed SQEs */
+	ret = io_uring_submit(&ring);
+	if (ret != 4) {
+		fprintf(stderr, "submit after resize: %d (expected 4)\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	/* Reap and verify CQEs */
+	for (i = 0; i < 4; i++) {
+		uint64_t expected;
+
+		ret = io_uring_wait_cqe(&ring, &cqe);
+		if (ret) {
+			fprintf(stderr, "wait_cqe: %s\n", strerror(-ret));
+			return T_EXIT_FAIL;
+		}
+
+		expected = (i % 2 == 0) ? (0xFFFF0000ULL + i) : (0x11110000ULL + i);
+		if (cqe->user_data != expected) {
+			fprintf(stderr, "  *** SQE_MIXED CORRUPTION DETECTED ***\n");
+			return T_EXIT_FAIL;
+		}
+
+		io_uring_cqe_seen(&ring, cqe);
+	}
+
+	io_uring_queue_exit(&ring);
+	return T_EXIT_PASS;
+}
+
+int main(void)
+{
+	int ret;
+
+	ret = test_cqe32_resize();
+	if (ret == T_EXIT_FAIL) {
+		fprintf(stderr, "cqe32_resize failed\n");
+		return T_EXIT_FAIL;
+	} else if (ret == T_EXIT_SKIP) {
+		return T_EXIT_SKIP;
+	}
+
+	ret = test_cqe_mixed_resize();
+	if (ret == T_EXIT_FAIL) {
+		fprintf(stderr, "cqe_mixed_resize failed\n");
+		return T_EXIT_FAIL;
+	} else if (ret == T_EXIT_SKIP) {
+		return T_EXIT_SKIP;
+	}
+
+	ret = test_sqe128_resize();
+	if (ret == T_EXIT_FAIL) {
+		fprintf(stderr, "sqe128_resize failed\n");
+		return T_EXIT_FAIL;
+	} else if (ret == T_EXIT_SKIP) {
+		return T_EXIT_SKIP;
+	}
+
+	ret = test_sqe_mixed_resize();
+	if (ret == T_EXIT_FAIL) {
+		fprintf(stderr, "sqe_mixed_resize failed\n");
+		return T_EXIT_FAIL;
+	} else if (ret == T_EXIT_SKIP) {
+		return T_EXIT_SKIP;
+	}
+
+	ret = test_ring_wrapping();
+	if (ret == T_EXIT_FAIL) {
+		fprintf(stderr, "ring_wrapping failed\n");
+		return T_EXIT_FAIL;
+	} else if (ret == T_EXIT_SKIP) {
+		return T_EXIT_SKIP;
+	}
+
+	return T_EXIT_PASS;
+}