• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* SPDX-License-Identifier: MIT */
2 /*
3  * io_uring_enter.c
4  *
5  * Description: Unit tests for the io_uring_enter system call.
6  *
7  * Copyright 2019, Red Hat, Inc.
8  * Author: Jeff Moyer <jmoyer@redhat.com>
9  */
10 #include <stdio.h>
11 #include <fcntl.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include <sys/sysinfo.h>
17 #include <poll.h>
18 #include <assert.h>
19 #include <sys/uio.h>
20 #include <sys/mman.h>
21 #include <linux/mman.h>
22 #include <sys/time.h>
23 #include <sys/resource.h>
24 #include <limits.h>
25 #include <sys/time.h>
26 
27 #include "helpers.h"
28 #include "liburing.h"
29 #include "liburing/barrier.h"
30 #include "../src/syscall.h"
31 
32 #define IORING_MAX_ENTRIES 4096
33 
34 int
expect_failed_submit(struct io_uring * ring,int error)35 expect_failed_submit(struct io_uring *ring, int error)
36 {
37 	int ret;
38 
39 	ret = io_uring_submit(ring);
40 	if (ret == 1) {
41 		printf("expected failure, but io_uring_submit succeeded.\n");
42 		return 1;
43 	}
44 
45 	if (errno != error) {
46 		printf("expected %d, got %d\n", error, errno);
47 		return 1;
48 	}
49 
50 	return 0;
51 }
52 
53 int
expect_fail(int fd,unsigned int to_submit,unsigned int min_complete,unsigned int flags,sigset_t * sig,int error)54 expect_fail(int fd, unsigned int to_submit, unsigned int min_complete,
55 	    unsigned int flags, sigset_t *sig, int error)
56 {
57 	int ret;
58 
59 	ret = __sys_io_uring_enter(fd, to_submit, min_complete, flags, sig);
60 	if (ret != -1) {
61 		printf("expected %s, but call succeeded\n", strerror(error));
62 		return 1;
63 	}
64 
65 	if (errno != error) {
66 		printf("expected %d, got %d\n", error, errno);
67 		return 1;
68 	}
69 
70 	return 0;
71 }
72 
73 int
try_io_uring_enter(int fd,unsigned int to_submit,unsigned int min_complete,unsigned int flags,sigset_t * sig,int expect,int error)74 try_io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete,
75 		   unsigned int flags, sigset_t *sig, int expect, int error)
76 {
77 	int ret;
78 
79 	printf("io_uring_enter(%d, %u, %u, %u, %p)\n", fd, to_submit,
80 	       min_complete, flags, sig);
81 
82 	if (expect == -1)
83 		return expect_fail(fd, to_submit, min_complete,
84 				   flags, sig, error);
85 
86 	ret = __sys_io_uring_enter(fd, to_submit, min_complete, flags, sig);
87 	if (ret != expect) {
88 		printf("Expected %d, got %d\n", expect, errno);
89 		return 1;
90 	}
91 
92 	return 0;
93 }
94 
95 /*
96  * prep a read I/O.  index is treated like a block number.
97  */
98 int
setup_file(char * template,off_t len)99 setup_file(char *template, off_t len)
100 {
101 	int fd, ret;
102 	char buf[4096];
103 
104 	fd = mkstemp(template);
105 	if (fd < 0) {
106 		perror("mkstemp");
107 		exit(1);
108 	}
109 	ret = ftruncate(fd, len);
110 	if (ret < 0) {
111 		perror("ftruncate");
112 		exit(1);
113 	}
114 
115 	ret = read(fd, buf, 4096);
116 	if (ret != 4096) {
117 		printf("read returned %d, expected 4096\n", ret);
118 		exit(1);
119 	}
120 
121 	return fd;
122 }
123 
124 void
io_prep_read(struct io_uring_sqe * sqe,int fd,off_t offset,size_t len)125 io_prep_read(struct io_uring_sqe *sqe, int fd, off_t offset, size_t len)
126 {
127 	struct iovec *iov;
128 
129 	iov = t_malloc(sizeof(*iov));
130 	assert(iov);
131 
132 	iov->iov_base = t_malloc(len);
133 	assert(iov->iov_base);
134 	iov->iov_len = len;
135 
136 	io_uring_prep_readv(sqe, fd, iov, 1, offset);
137 	io_uring_sqe_set_data(sqe, iov); // free on completion
138 }
139 
140 void
reap_events(struct io_uring * ring,unsigned nr)141 reap_events(struct io_uring *ring, unsigned nr)
142 {
143 	int ret;
144 	unsigned left = nr;
145 	struct io_uring_cqe *cqe;
146 	struct iovec *iov;
147 	struct timeval start, now, elapsed;
148 
149 	printf("Reaping %u I/Os\n", nr);
150 	gettimeofday(&start, NULL);
151 	while (left) {
152 		ret = io_uring_wait_cqe(ring, &cqe);
153 		if (ret < 0) {
154 			printf("io_uring_wait_cqe returned %d\n", ret);
155 			printf("expected success\n");
156 			exit(1);
157 		}
158 		if (cqe->res != 4096)
159 			printf("cqe->res: %d, expected 4096\n", cqe->res);
160 		iov = io_uring_cqe_get_data(cqe);
161 		free(iov->iov_base);
162 		free(iov);
163 		left--;
164 		io_uring_cqe_seen(ring, cqe);
165 
166 		gettimeofday(&now, NULL);
167 		timersub(&now, &start, &elapsed);
168 		if (elapsed.tv_sec > 10) {
169 			printf("Timed out waiting for I/Os to complete.\n");
170 			printf("%u expected, %u completed\n", nr, left);
171 			break;
172 		}
173 	}
174 }
175 
176 void
submit_io(struct io_uring * ring,unsigned nr)177 submit_io(struct io_uring *ring, unsigned nr)
178 {
179 	int fd, ret;
180 	off_t file_len;
181 	unsigned i;
182 	static char template[32] = "/tmp/io_uring_enter-test.XXXXXX";
183 	struct io_uring_sqe *sqe;
184 
185 	printf("Allocating %u sqes\n", nr);
186 	file_len = nr * 4096;
187 	fd = setup_file(template, file_len);
188 	for (i = 0; i < nr; i++) {
189 		/* allocate an sqe */
190 		sqe = io_uring_get_sqe(ring);
191 		/* fill it in */
192 		io_prep_read(sqe, fd, i * 4096, 4096);
193 	}
194 
195 	/* submit the I/Os */
196 	printf("Submitting %u I/Os\n", nr);
197 	ret = io_uring_submit(ring);
198 	unlink(template);
199 	if (ret < 0) {
200 		perror("io_uring_enter");
201 		exit(1);
202 	}
203 	printf("Done\n");
204 }
205 
206 int
main(int argc,char ** argv)207 main(int argc, char **argv)
208 {
209 	int ret;
210 	unsigned int status = 0;
211 	struct io_uring ring;
212 	struct io_uring_sq *sq = &ring.sq;
213 	unsigned ktail, mask, index;
214 	unsigned sq_entries;
215 	unsigned completed, dropped;
216 
217 	if (argc > 1)
218 		return 0;
219 
220 	ret = io_uring_queue_init(IORING_MAX_ENTRIES, &ring, 0);
221 	if (ret < 0) {
222 		perror("io_uring_queue_init");
223 		exit(1);
224 	}
225 	mask = *sq->kring_mask;
226 
227 	/* invalid flags */
228 	status |= try_io_uring_enter(ring.ring_fd, 1, 0, ~0U, NULL, -1, EINVAL);
229 
230 	/* invalid fd, EBADF */
231 	status |= try_io_uring_enter(-1, 0, 0, 0, NULL, -1, EBADF);
232 
233 	/* valid, non-ring fd, EOPNOTSUPP */
234 	status |= try_io_uring_enter(0, 0, 0, 0, NULL, -1, EOPNOTSUPP);
235 
236 	/* to_submit: 0, flags: 0;  should get back 0. */
237 	status |= try_io_uring_enter(ring.ring_fd, 1, 0, 0, NULL, 0, 0);
238 
239 	/* fill the sq ring */
240 	sq_entries = *ring.sq.kring_entries;
241 	submit_io(&ring, sq_entries);
242 	printf("Waiting for %u events\n", sq_entries);
243 	ret = __sys_io_uring_enter(ring.ring_fd, 0, sq_entries,
244 					IORING_ENTER_GETEVENTS, NULL);
245 	if (ret < 0) {
246 		perror("io_uring_enter");
247 		status = 1;
248 	} else {
249 		/*
250 		 * This is a non-IOPOLL ring, which means that io_uring_enter
251 		 * should not return until min_complete events are available
252 		 * in the completion queue.
253 		 */
254 		completed = *ring.cq.ktail - *ring.cq.khead;
255 		if (completed != sq_entries) {
256 			printf("Submitted %u I/Os, but only got %u completions\n",
257 			       sq_entries, completed);
258 			status = 1;
259 		}
260 		reap_events(&ring, sq_entries);
261 	}
262 
263 	/*
264 	 * Add an invalid index to the submission queue.  This should
265 	 * result in the dropped counter increasing.
266 	 */
267 	printf("Submitting invalid sqe index.\n");
268 	index = *sq->kring_entries + 1; // invalid index
269 	dropped = *sq->kdropped;
270 	ktail = *sq->ktail;
271 	sq->array[ktail & mask] = index;
272 	++ktail;
273 	/*
274 	 * Ensure that the kernel sees the SQE update before it sees the tail
275 	 * update.
276 	 */
277 	io_uring_smp_store_release(sq->ktail, ktail);
278 
279 	ret = __sys_io_uring_enter(ring.ring_fd, 1, 0, 0, NULL);
280 	/* now check to see if our sqe was dropped */
281 	if (*sq->kdropped == dropped) {
282 		printf("dropped counter did not increase\n");
283 		status = 1;
284 	}
285 
286 	if (!status) {
287 		printf("PASS\n");
288 		return 0;
289 	}
290 
291 	printf("FAIL\n");
292 	return -1;
293 }
294