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 <[email protected]>
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 #define IORING_MAX_ENTRIES_FALLBACK 128
34
expect_fail(int fd,unsigned int to_submit,unsigned int min_complete,unsigned int flags,sigset_t * sig,int error)35 static int expect_fail(int fd, unsigned int to_submit,
36 unsigned int min_complete, unsigned int flags,
37 sigset_t *sig, int error)
38 {
39 int ret;
40
41 ret = __sys_io_uring_enter(fd, to_submit, min_complete, flags, sig);
42 if (ret != -1) {
43 fprintf(stderr, "expected %s, but call succeeded\n", strerror(error));
44 return 1;
45 }
46
47 if (errno != error) {
48 fprintf(stderr, "expected %d, got %d\n", error, errno);
49 return 1;
50 }
51
52 return 0;
53 }
54
try_io_uring_enter(int fd,unsigned int to_submit,unsigned int min_complete,unsigned int flags,sigset_t * sig,int expect,int error)55 static int try_io_uring_enter(int fd, unsigned int to_submit,
56 unsigned int min_complete, unsigned int flags,
57 sigset_t *sig, int expect, int error)
58 {
59 int ret;
60
61 if (expect == -1)
62 return expect_fail(fd, to_submit, min_complete,
63 flags, sig, error);
64
65 ret = __sys_io_uring_enter(fd, to_submit, min_complete, flags, sig);
66 if (ret != expect) {
67 fprintf(stderr, "Expected %d, got %d\n", expect, errno);
68 return 1;
69 }
70
71 return 0;
72 }
73
74 /*
75 * prep a read I/O. index is treated like a block number.
76 */
setup_file(char * template,off_t len)77 static int setup_file(char *template, off_t len)
78 {
79 int fd, ret;
80 char buf[4096];
81
82 fd = mkstemp(template);
83 if (fd < 0) {
84 perror("mkstemp");
85 exit(1);
86 }
87 ret = ftruncate(fd, len);
88 if (ret < 0) {
89 perror("ftruncate");
90 exit(1);
91 }
92
93 ret = read(fd, buf, 4096);
94 if (ret != 4096) {
95 fprintf(stderr, "read returned %d, expected 4096\n", ret);
96 exit(1);
97 }
98
99 return fd;
100 }
101
io_prep_read(struct io_uring_sqe * sqe,int fd,off_t offset,size_t len)102 static void io_prep_read(struct io_uring_sqe *sqe, int fd, off_t offset,
103 size_t len)
104 {
105 struct iovec *iov;
106
107 iov = t_malloc(sizeof(*iov));
108 assert(iov);
109
110 iov->iov_base = t_malloc(len);
111 assert(iov->iov_base);
112 iov->iov_len = len;
113
114 io_uring_prep_readv(sqe, fd, iov, 1, offset);
115 io_uring_sqe_set_data(sqe, iov); // free on completion
116 }
117
reap_events(struct io_uring * ring,unsigned nr)118 static void reap_events(struct io_uring *ring, unsigned nr)
119 {
120 int ret;
121 unsigned left = nr;
122 struct io_uring_cqe *cqe;
123 struct iovec *iov;
124 struct timeval start, now, elapsed;
125
126 gettimeofday(&start, NULL);
127 while (left) {
128 ret = io_uring_wait_cqe(ring, &cqe);
129 if (ret < 0) {
130 fprintf(stderr, "io_uring_wait_cqe returned %d\n", ret);
131 exit(1);
132 }
133 if (cqe->res != 4096)
134 fprintf(stderr, "cqe->res: %d, expected 4096\n", cqe->res);
135 iov = io_uring_cqe_get_data(cqe);
136 free(iov->iov_base);
137 free(iov);
138 left--;
139 io_uring_cqe_seen(ring, cqe);
140
141 gettimeofday(&now, NULL);
142 timersub(&now, &start, &elapsed);
143 if (elapsed.tv_sec > 10) {
144 fprintf(stderr, "Timed out waiting for I/Os to complete.\n");
145 fprintf(stderr, "%u expected, %u completed\n", nr, left);
146 break;
147 }
148 }
149 }
150
submit_io(struct io_uring * ring,unsigned nr)151 static void submit_io(struct io_uring *ring, unsigned nr)
152 {
153 int fd, ret;
154 off_t file_len;
155 unsigned i;
156 static char template[32] = "/tmp/io_uring_enter-test.XXXXXX";
157 struct io_uring_sqe *sqe;
158
159 file_len = nr * 4096;
160 fd = setup_file(template, file_len);
161 for (i = 0; i < nr; i++) {
162 /* allocate an sqe */
163 sqe = io_uring_get_sqe(ring);
164 /* fill it in */
165 io_prep_read(sqe, fd, i * 4096, 4096);
166 }
167
168 /* submit the I/Os */
169 ret = io_uring_submit(ring);
170 unlink(template);
171 if (ret < 0) {
172 perror("io_uring_enter");
173 exit(1);
174 }
175 }
176
main(int argc,char ** argv)177 int main(int argc, char **argv)
178 {
179 int ret;
180 unsigned int status = 0;
181 struct io_uring ring;
182 struct io_uring_sq *sq = &ring.sq;
183 unsigned ktail, mask, index;
184 unsigned sq_entries;
185 unsigned completed, dropped;
186
187 if (argc > 1)
188 return 0;
189
190 ret = io_uring_queue_init(IORING_MAX_ENTRIES, &ring, 0);
191 if (ret == -ENOMEM)
192 ret = io_uring_queue_init(IORING_MAX_ENTRIES_FALLBACK, &ring, 0);
193 if (ret < 0) {
194 perror("io_uring_queue_init");
195 exit(1);
196 }
197 mask = *sq->kring_mask;
198
199 /* invalid flags */
200 status |= try_io_uring_enter(ring.ring_fd, 1, 0, ~0U, NULL, -1, EINVAL);
201
202 /* invalid fd, EBADF */
203 status |= try_io_uring_enter(-1, 0, 0, 0, NULL, -1, EBADF);
204
205 /* valid, non-ring fd, EOPNOTSUPP */
206 status |= try_io_uring_enter(0, 0, 0, 0, NULL, -1, EOPNOTSUPP);
207
208 /* to_submit: 0, flags: 0; should get back 0. */
209 status |= try_io_uring_enter(ring.ring_fd, 0, 0, 0, NULL, 0, 0);
210
211 /* fill the sq ring */
212 sq_entries = *ring.sq.kring_entries;
213 submit_io(&ring, sq_entries);
214 ret = __sys_io_uring_enter(ring.ring_fd, 0, sq_entries,
215 IORING_ENTER_GETEVENTS, NULL);
216 if (ret < 0) {
217 perror("io_uring_enter");
218 status = 1;
219 } else {
220 /*
221 * This is a non-IOPOLL ring, which means that io_uring_enter
222 * should not return until min_complete events are available
223 * in the completion queue.
224 */
225 completed = *ring.cq.ktail - *ring.cq.khead;
226 if (completed != sq_entries) {
227 fprintf(stderr, "Submitted %u I/Os, but only got %u completions\n",
228 sq_entries, completed);
229 status = 1;
230 }
231 reap_events(&ring, sq_entries);
232 }
233
234 /*
235 * Add an invalid index to the submission queue. This should
236 * result in the dropped counter increasing.
237 */
238 index = *sq->kring_entries + 1; // invalid index
239 dropped = *sq->kdropped;
240 ktail = *sq->ktail;
241 sq->array[ktail & mask] = index;
242 ++ktail;
243 /*
244 * Ensure that the kernel sees the SQE update before it sees the tail
245 * update.
246 */
247 io_uring_smp_store_release(sq->ktail, ktail);
248
249 ret = __sys_io_uring_enter(ring.ring_fd, 1, 0, 0, NULL);
250 /* now check to see if our sqe was dropped */
251 if (*sq->kdropped == dropped) {
252 fprintf(stderr, "dropped counter did not increase\n");
253 status = 1;
254 }
255
256 if (!status)
257 return 0;
258
259 fprintf(stderr, "FAIL\n");
260 return -1;
261 }
262