• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* SPDX-License-Identifier: MIT */
2 
3 #include <stdio.h>
4 #include <assert.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <stdlib.h>
8 #include <sys/ioctl.h>
9 #include <linux/fs.h>
10 
11 #include "liburing.h"
12 #include "helpers.h"
13 
14 #define MAX_TEST_LBAS		1024
15 
16 static const char *filename;
17 struct opcode {
18 	int op;
19 	bool test;
20 	bool not_supported;
21 };
22 
23 #define TEST_BLOCK_URING_CMD_MAX		3
24 
25 static struct opcode opcodes[TEST_BLOCK_URING_CMD_MAX] = {
26 	{ .op = BLOCK_URING_CMD_DISCARD, .test = true, },
27 	{ .test = false, },
28 	{ .test = false, },
29 };
30 
31 static int lba_size;
32 static uint64_t bdev_size;
33 static uint64_t bdev_size_lbas;
34 static char *buffer;
35 
prep_blk_cmd(struct io_uring_sqe * sqe,int fd,uint64_t from,uint64_t len,int cmd_op)36 static void prep_blk_cmd(struct io_uring_sqe *sqe, int fd,
37 			 uint64_t from, uint64_t len,
38 			 int cmd_op)
39 {
40 	assert(cmd_op == BLOCK_URING_CMD_DISCARD);
41 
42 	io_uring_prep_cmd_discard(sqe, fd, from, len);
43 }
44 
queue_cmd_range(struct io_uring * ring,int bdev_fd,uint64_t from,uint64_t len,int cmd_op)45 static int queue_cmd_range(struct io_uring *ring, int bdev_fd,
46 			   uint64_t from, uint64_t len, int cmd_op)
47 {
48 	struct io_uring_sqe *sqe;
49 	struct io_uring_cqe *cqe;
50 	int err;
51 
52 	sqe = io_uring_get_sqe(ring);
53 	assert(sqe != NULL);
54 	prep_blk_cmd(sqe, bdev_fd, from, len, cmd_op);
55 
56 	err = io_uring_submit_and_wait(ring, 1);
57 	if (err != 1) {
58 		fprintf(stderr, "io_uring_submit_and_wait failed %d\n", err);
59 		exit(1);
60 	}
61 
62 	err = io_uring_wait_cqe(ring, &cqe);
63 	if (err) {
64 		fprintf(stderr, "io_uring_wait_cqe failed %d (op %i)\n",
65 				err, cmd_op);
66 		exit(1);
67 	}
68 
69 	err = cqe->res;
70 	io_uring_cqe_seen(ring, cqe);
71 	return err;
72 }
73 
queue_cmd_lba(struct io_uring * ring,int bdev_fd,uint64_t from,uint64_t nr_lba,int cmd_op)74 static int queue_cmd_lba(struct io_uring *ring, int bdev_fd,
75 			 uint64_t from, uint64_t nr_lba, int cmd_op)
76 {
77 	return queue_cmd_range(ring, bdev_fd, from * lba_size,
78 				nr_lba * lba_size, cmd_op);
79 }
80 
queue_discard_lba(struct io_uring * ring,int bdev_fd,uint64_t from,uint64_t nr_lba)81 static int queue_discard_lba(struct io_uring *ring, int bdev_fd,
82 			     uint64_t from, uint64_t nr_lba)
83 {
84 	return queue_cmd_lba(ring, bdev_fd, from, nr_lba,
85 				BLOCK_URING_CMD_DISCARD);
86 }
87 
test_parallel(struct io_uring * ring,int fd,int cmd_op)88 static int test_parallel(struct io_uring *ring, int fd, int cmd_op)
89 {
90 	struct io_uring_sqe *sqe;
91 	struct io_uring_cqe *cqe;
92 	int inflight = 0;
93 	int max_inflight = 16;
94 	int left = 1000;
95 	int ret;
96 
97 	while (left || inflight) {
98 		int queued = 0;
99 		unsigned head, nr_cqes = 0;
100 		int lba_len = 8;
101 
102 		while (inflight < max_inflight && left) {
103 			int off = rand() % (MAX_TEST_LBAS - lba_len);
104 			sqe = io_uring_get_sqe(ring);
105 			assert(sqe != NULL);
106 
107 			prep_blk_cmd(sqe, fd, off * lba_size,
108 				     lba_len * lba_size, cmd_op);
109 			if (rand() & 1)
110 				sqe->flags |= IOSQE_ASYNC;
111 
112 			queued++;
113 			left--;
114 			inflight++;
115 		}
116 		if (queued) {
117 			ret = io_uring_submit(ring);
118 			if (ret != queued) {
119 				fprintf(stderr, "io_uring_submit failed %d\n", ret);
120 				return T_EXIT_FAIL;
121 			}
122 		}
123 
124 		ret = io_uring_wait_cqe(ring, &cqe);
125 		if (ret) {
126 			fprintf(stderr, "io_uring_wait_cqe failed %d\n", ret);
127 			exit(1);
128 		}
129 
130 		io_uring_for_each_cqe(ring, head, cqe) {
131 			nr_cqes++;
132 			inflight--;
133 			if (cqe->res != 0) {
134 				fprintf(stderr, "cmd %i failed %i\n", cmd_op,
135 						cqe->res);
136 				return T_EXIT_FAIL;
137 			}
138 		}
139 		io_uring_cq_advance(ring, nr_cqes);
140 	}
141 
142 	return 0;
143 }
144 
cmd_issue_verify(struct io_uring * ring,int fd,int lba,int len,int cmd_op)145 static int cmd_issue_verify(struct io_uring *ring, int fd, int lba, int len,
146 			    int cmd_op)
147 {
148 	int verify = (cmd_op != BLOCK_URING_CMD_DISCARD);
149 	int ret, i;
150 	ssize_t res;
151 
152 	if (verify) {
153 		for (i = 0; i < len; i++) {
154 			size_t off = (i + lba) * lba_size;
155 
156 			res = pwrite(fd, buffer, lba_size, off);
157 			if (res == -1) {
158 				fprintf(stderr, "pwrite failed\n");
159 				return T_EXIT_FAIL;
160 			}
161 		}
162 	}
163 
164 	ret = queue_cmd_lba(ring, fd, lba, len, cmd_op);
165 	if (ret) {
166 		if (ret == -EINVAL || ret == -EOPNOTSUPP)
167 			return T_EXIT_SKIP;
168 
169 		fprintf(stderr, "cmd_issue_verify %i fail lba %i len %i  ret %i\n",
170 				cmd_op, lba, len, ret);
171 		return T_EXIT_FAIL;
172 	}
173 
174 	if (verify) {
175 		for (i = 0; i < len; i++) {
176 			size_t off = (i + lba) * lba_size;
177 
178 			res = pread(fd, buffer, lba_size, off);
179 			if (res == -1) {
180 				fprintf(stderr, "pread failed\n");
181 				return T_EXIT_FAIL;
182 			}
183 			if (!memchr(buffer, 0, lba_size)) {
184 				fprintf(stderr, "mem cmp failed, lba %i\n", lba + i);
185 				return T_EXIT_FAIL;
186 			}
187 		}
188 	}
189 	return 0;
190 }
191 
basic_cmd_test(struct io_uring * ring,int op)192 static int basic_cmd_test(struct io_uring *ring, int op)
193 {
194 	int cmd_op = opcodes[op].op;
195 	int ret, fd;
196 
197 	if (!opcodes[op].test)
198 		return T_EXIT_SKIP;
199 
200 	fd = open(filename, O_DIRECT | O_RDWR | O_EXCL);
201 	if (fd < 0) {
202 		fprintf(stderr, "open failed %i\n", errno);
203 		return T_EXIT_FAIL;
204 	}
205 
206 	ret = cmd_issue_verify(ring, fd, 0, 1, cmd_op);
207 	if (ret == T_EXIT_SKIP) {
208 		printf("cmd %i not supported, skip\n", cmd_op);
209 		opcodes[op].not_supported = 1;
210 		close(fd);
211 		return T_EXIT_SKIP;
212 	} else if (ret) {
213 		fprintf(stderr, "cmd %i fail 0 1\n", cmd_op);
214 		return T_EXIT_FAIL;
215 	}
216 
217 	ret = cmd_issue_verify(ring, fd, 7, 15, cmd_op);
218 	if (ret) {
219 		fprintf(stderr, "cmd %i fail 7 15 %i\n", cmd_op, ret);
220 		return T_EXIT_FAIL;
221 	}
222 
223 	ret = cmd_issue_verify(ring, fd, 1, MAX_TEST_LBAS - 1, cmd_op);
224 	if (ret) {
225 		fprintf(stderr, "large cmd %i failed %i\n", cmd_op, ret);
226 		return T_EXIT_FAIL;
227 	}
228 
229 	ret = test_parallel(ring, fd, cmd_op);
230 	if (ret) {
231 		fprintf(stderr, "test_parallel() %i failed %i\n", cmd_op, ret);
232 		return T_EXIT_FAIL;
233 	}
234 
235 	close(fd);
236 	return 0;
237 }
238 
test_fail_edge_cases(struct io_uring * ring,int op)239 static int test_fail_edge_cases(struct io_uring *ring, int op)
240 {
241 	int cmd_op = opcodes[op].op;
242 	int ret, fd;
243 
244 	if (!opcodes[op].test)
245 		return T_EXIT_SKIP;
246 
247 	fd = open(filename, O_DIRECT | O_RDWR | O_EXCL);
248 	if (fd < 0) {
249 		fprintf(stderr, "open failed %i\n", errno);
250 		return T_EXIT_FAIL;
251 	}
252 
253 	ret = queue_cmd_lba(ring, fd, bdev_size_lbas, 1, cmd_op);
254 	if (ret >= 0) {
255 		fprintf(stderr, "cmd %i beyond capacity %i\n",
256 				cmd_op, ret);
257 		return 1;
258 	}
259 
260 	ret = queue_cmd_lba(ring, fd, bdev_size_lbas - 1, 2, cmd_op);
261 	if (ret >= 0) {
262 		fprintf(stderr, "cmd %i beyond capacity with overlap %i\n",
263 				cmd_op, ret);
264 		return 1;
265 	}
266 
267 	ret = queue_cmd_range(ring, fd, (uint64_t)-lba_size, lba_size + 2,
268 			      cmd_op);
269 	if (ret >= 0) {
270 		fprintf(stderr, "cmd %i range overflow %i\n",
271 				cmd_op, ret);
272 		return 1;
273 	}
274 
275 	ret = queue_cmd_range(ring, fd, lba_size / 2, lba_size, cmd_op);
276 	if (ret >= 0) {
277 		fprintf(stderr, "cmd %i unaligned offset %i\n",
278 				cmd_op, ret);
279 		return 1;
280 	}
281 
282 	ret = queue_cmd_range(ring, fd, 0, lba_size / 2, cmd_op);
283 	if (ret >= 0) {
284 		fprintf(stderr, "cmd %i unaligned size %i\n",
285 				cmd_op, ret);
286 		return 1;
287 	}
288 
289 	close(fd);
290 	return 0;
291 }
292 
test_rdonly(struct io_uring * ring,int op)293 static int test_rdonly(struct io_uring *ring, int op)
294 {
295 	int ret, fd;
296 	int ro;
297 
298 	if (!opcodes[op].test)
299 		return T_EXIT_SKIP;
300 
301 	fd = open(filename, O_DIRECT | O_RDONLY | O_EXCL);
302 	if (fd < 0) {
303 		fprintf(stderr, "open failed %i\n", errno);
304 		return T_EXIT_FAIL;
305 	}
306 
307 	ret = queue_discard_lba(ring, fd, 0, 1);
308 	if (ret >= 0) {
309 		fprintf(stderr, "discarded with O_RDONLY %i\n", ret);
310 		return 1;
311 	}
312 	close(fd);
313 
314 	fd = open(filename, O_DIRECT | O_RDWR | O_EXCL);
315 	if (fd < 0) {
316 		fprintf(stderr, "open failed %i\n", errno);
317 		return T_EXIT_FAIL;
318 	}
319 
320 	ro = 1;
321 	ret = ioctl(fd, BLKROSET, &ro);
322 	if (ret) {
323 		fprintf(stderr, "BLKROSET 1 failed %i\n", errno);
324 		return T_EXIT_FAIL;
325 	}
326 
327 	ret = queue_discard_lba(ring, fd, 0, 1);
328 	if (ret >= 0) {
329 		fprintf(stderr, "discarded with O_RDONLY %i\n", ret);
330 		return 1;
331 	}
332 
333 	ro = 0;
334 	ret = ioctl(fd, BLKROSET, &ro);
335 	if (ret) {
336 		fprintf(stderr, "BLKROSET 0 failed %i\n", errno);
337 		return T_EXIT_FAIL;
338 	}
339 	close(fd);
340 	return 0;
341 }
342 
main(int argc,char * argv[])343 int main(int argc, char *argv[])
344 {
345 	struct io_uring ring;
346 	int fd, ret, i, fret;
347 	int cmd_op;
348 
349 	if (argc != 2)
350 		return T_EXIT_SKIP;
351 	filename = argv[1];
352 
353 	fd = open(filename, O_DIRECT | O_RDONLY | O_EXCL);
354 	if (fd < 0) {
355 		fprintf(stderr, "open failed %i\n", errno);
356 		return T_EXIT_FAIL;
357 	}
358 
359 	ret = ioctl(fd, BLKGETSIZE64, &bdev_size);
360 	if (ret < 0) {
361 		fprintf(stderr, "BLKGETSIZE64 failed %i\n", errno);
362 		return T_EXIT_FAIL;
363 	}
364 	ret = ioctl(fd, BLKSSZGET, &lba_size);
365 	if (ret < 0) {
366 		fprintf(stderr, "BLKSSZGET failed %i\n", errno);
367 		return T_EXIT_FAIL;
368 	}
369 	assert(bdev_size % lba_size == 0);
370 	bdev_size_lbas = bdev_size / lba_size;
371 	close(fd);
372 
373 	buffer = aligned_alloc(lba_size, lba_size);
374 	if (!buffer) {
375 		fprintf(stderr, "aligned_alloc failed\n");
376 		return T_EXIT_FAIL;
377 	}
378 	for (i = 0; i < lba_size; i++)
379 		buffer[i] = i ^ 0xA7;
380 
381 	if (bdev_size_lbas < MAX_TEST_LBAS) {
382 		fprintf(stderr, "the device is too small, skip\n");
383 		return T_EXIT_SKIP;
384 	}
385 
386 	ret = io_uring_queue_init(16, &ring, 0);
387 	if (ret) {
388 		fprintf(stderr, "queue init failed: %d\n", ret);
389 		return T_EXIT_FAIL;
390 	}
391 
392 	fret = T_EXIT_SKIP;
393 	for (cmd_op = 0; cmd_op < TEST_BLOCK_URING_CMD_MAX; cmd_op++) {
394 		if (!opcodes[cmd_op].test)
395 			continue;
396 		ret = basic_cmd_test(&ring, cmd_op);
397 		if (ret) {
398 			if (ret == T_EXIT_SKIP)
399 				continue;
400 
401 			fprintf(stderr, "basic_cmd_test() failed, cmd %i\n",
402 					cmd_op);
403 			return T_EXIT_FAIL;
404 		}
405 
406 		ret = test_rdonly(&ring, cmd_op);
407 		if (ret) {
408 			fprintf(stderr, "test_rdonly() failed, cmd %i\n",
409 					cmd_op);
410 			return T_EXIT_FAIL;
411 		}
412 
413 		ret = test_fail_edge_cases(&ring, cmd_op);
414 		if (ret) {
415 			fprintf(stderr, "test_fail_edge_cases() failed, cmd %i\n",
416 					cmd_op);
417 			return T_EXIT_FAIL;
418 		}
419 		fret = T_EXIT_PASS;
420 	}
421 
422 	io_uring_queue_exit(&ring);
423 	free(buffer);
424 	return fret;
425 }
426