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 #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 = io_uring_enter(fd, to_submit, min_complete, flags, sig);
42 if (ret >= 0) {
43 fprintf(stderr, "expected %s, but call succeeded\n", strerror(-error));
44 return 1;
45 }
46
47 if (ret != error) {
48 fprintf(stderr, "expected %d, got %d\n", error, ret);
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)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)
58 {
59 int ret;
60
61 if (expect < 0)
62 return expect_fail(fd, to_submit, min_complete, flags, sig,
63 expect);
64
65 ret = io_uring_enter(fd, to_submit, min_complete, flags, sig);
66 if (ret != expect) {
67 fprintf(stderr, "Expected %d, got %d\n", expect, ret);
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 fprintf(stderr, "io_uring_queue_enter: %s\n", strerror(-ret));
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 struct io_uring_params p;
187
188 if (argc > 1)
189 return T_EXIT_SKIP;
190
191 memset(&p, 0, sizeof(p));
192 ret = t_io_uring_init_sqarray(IORING_MAX_ENTRIES, &ring, &p);
193 if (ret == -ENOMEM)
194 ret = t_io_uring_init_sqarray(IORING_MAX_ENTRIES_FALLBACK,
195 &ring, &p);
196 if (ret < 0) {
197 fprintf(stderr, "queue_init: %s\n", strerror(-ret));
198 exit(T_EXIT_FAIL);
199 }
200 mask = sq->ring_mask;
201
202 /* invalid flags */
203 status |= try_io_uring_enter(ring.ring_fd, 1, 0, ~0U, NULL, -EINVAL);
204
205 /* invalid fd, EBADF */
206 status |= try_io_uring_enter(-1, 0, 0, 0, NULL, -EBADF);
207
208 /* valid, non-ring fd, EOPNOTSUPP */
209 status |= try_io_uring_enter(0, 0, 0, 0, NULL, -EOPNOTSUPP);
210
211 /* to_submit: 0, flags: 0; should get back 0. */
212 status |= try_io_uring_enter(ring.ring_fd, 0, 0, 0, NULL, 0);
213
214 /* fill the sq ring */
215 sq_entries = ring.sq.ring_entries;
216 submit_io(&ring, sq_entries);
217 ret = io_uring_enter(ring.ring_fd, 0, sq_entries,
218 IORING_ENTER_GETEVENTS, NULL);
219 if (ret < 0) {
220 fprintf(stderr, "io_uring_enter: %s\n", strerror(-ret));
221 status = 1;
222 } else {
223 /*
224 * This is a non-IOPOLL ring, which means that io_uring_enter
225 * should not return until min_complete events are available
226 * in the completion queue.
227 */
228 completed = *ring.cq.ktail - *ring.cq.khead;
229 if (completed != sq_entries) {
230 fprintf(stderr, "Submitted %u I/Os, but only got %u completions\n",
231 sq_entries, completed);
232 status = 1;
233 }
234 reap_events(&ring, sq_entries);
235 }
236
237 /*
238 * Add an invalid index to the submission queue. This should
239 * result in the dropped counter increasing.
240 */
241 index = sq->ring_entries + 1; // invalid index
242 dropped = *sq->kdropped;
243 ktail = *sq->ktail;
244 sq->array[ktail & mask] = index;
245 ++ktail;
246 /*
247 * Ensure that the kernel sees the SQE update before it sees the tail
248 * update.
249 */
250 io_uring_smp_store_release(sq->ktail, ktail);
251
252 ret = io_uring_enter(ring.ring_fd, 1, 0, 0, NULL);
253 /* now check to see if our sqe was dropped */
254 if (*sq->kdropped == dropped) {
255 fprintf(stderr, "dropped counter did not increase\n");
256 status = 1;
257 }
258
259 if (!status)
260 return T_EXIT_PASS;
261
262 fprintf(stderr, "FAIL\n");
263 return T_EXIT_FAIL;
264 }
265