forked from axboe/liburing
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
- Loading branch information
Showing
2 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
/* SPDX-License-Identifier: MIT */ | ||
|
||
#include <stdio.h> | ||
#include <assert.h> | ||
#include <string.h> | ||
#include <unistd.h> | ||
#include <stdlib.h> | ||
#include <sys/ioctl.h> | ||
|
||
#include "liburing.h" | ||
#include "helpers.h" | ||
|
||
#define MAX_TEST_LBAS 1024 | ||
|
||
static const char *filename; | ||
|
||
static int lba_size; | ||
static uint64_t bdev_size; | ||
static uint64_t bdev_size_lbas; | ||
|
||
#ifndef BLOCK_URING_CMD_DISCARD | ||
#define BLOCK_URING_CMD_DISCARD 0 | ||
#endif | ||
|
||
static void prep_cmd_discard(struct io_uring_sqe *sqe, int fd, | ||
uint64_t from, uint64_t len) | ||
{ | ||
io_uring_prep_nop(sqe); | ||
sqe->opcode = IORING_OP_URING_CMD; | ||
sqe->fd = fd; | ||
sqe->addr = from; | ||
sqe->addr3 = len; | ||
sqe->cmd_op = BLOCK_URING_CMD_DISCARD; | ||
} | ||
|
||
static int queue_discard_range(struct io_uring *ring, int bdev_fd, | ||
uint64_t from, uint64_t len) | ||
{ | ||
struct io_uring_sqe *sqe; | ||
struct io_uring_cqe *cqe; | ||
int err; | ||
|
||
sqe = io_uring_get_sqe(ring); | ||
assert(sqe != NULL); | ||
prep_cmd_discard(sqe, bdev_fd, from, len); | ||
|
||
err = io_uring_submit_and_wait(ring, 1); | ||
if (err != 1) { | ||
fprintf(stderr, "io_uring_submit_and_wait failed %d\n", err); | ||
exit(1); | ||
} | ||
|
||
err = io_uring_wait_cqe(ring, &cqe); | ||
if (err) { | ||
fprintf(stderr, "io_uring_wait_cqe failed %d\n", err); | ||
exit(1); | ||
} | ||
|
||
err = cqe->res; | ||
io_uring_cqe_seen(ring, cqe); | ||
return err; | ||
} | ||
|
||
static int queue_discard_lba(struct io_uring *ring, int bdev_fd, | ||
uint64_t from, uint64_t nr_lba) | ||
{ | ||
return queue_discard_range(ring, bdev_fd, | ||
from * lba_size, nr_lba * lba_size); | ||
} | ||
|
||
static int test_parallel_discards(struct io_uring *ring, int fd) | ||
{ | ||
struct io_uring_sqe *sqe; | ||
struct io_uring_cqe *cqe; | ||
int inflight = 0; | ||
int max_inflight = 16; | ||
int left = 1000; | ||
int ret; | ||
|
||
while (left || inflight) { | ||
int queued = 0; | ||
unsigned head, nr_cqes = 0; | ||
int lba_len = 8; | ||
|
||
while (inflight < max_inflight && left) { | ||
int off = rand() % (MAX_TEST_LBAS - lba_len); | ||
sqe = io_uring_get_sqe(ring); | ||
assert(sqe != NULL); | ||
|
||
prep_cmd_discard(sqe, fd, off * lba_size, | ||
lba_len * lba_size); | ||
if (rand() & 1) | ||
sqe->flags |= IOSQE_ASYNC; | ||
|
||
queued++; | ||
left--; | ||
inflight++; | ||
} | ||
if (queued) { | ||
ret = io_uring_submit(ring); | ||
if (ret != queued) { | ||
fprintf(stderr, "io_uring_submit failed %d\n", ret); | ||
return T_EXIT_FAIL; | ||
} | ||
} | ||
|
||
ret = io_uring_wait_cqe(ring, &cqe); | ||
if (ret) { | ||
fprintf(stderr, "io_uring_wait_cqe failed %d\n", ret); | ||
exit(1); | ||
} | ||
|
||
io_uring_for_each_cqe(ring, head, cqe) { | ||
nr_cqes++; | ||
inflight--; | ||
if (cqe->res != 0) { | ||
fprintf(stderr, "discard failed %i\n", cqe->res); | ||
return T_EXIT_FAIL; | ||
} | ||
} | ||
io_uring_cq_advance(ring, nr_cqes); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
|
||
static int basic_discard_test(struct io_uring *ring) | ||
{ | ||
int ret, fd; | ||
|
||
fd = open(filename, O_DIRECT | O_RDWR | O_EXCL); | ||
if (fd < 0) { | ||
fprintf(stderr, "open failed %i\n", errno); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = queue_discard_lba(ring, fd, 0, 1); | ||
if (ret == -EINVAL) { | ||
printf("discard cmds are not supported, skip\n"); | ||
return T_EXIT_SKIP; | ||
} else if (ret) { | ||
fprintf(stderr, "discard fail 0 1 %i\n", ret); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = queue_discard_lba(ring, fd, 7, 15); | ||
if (ret) { | ||
fprintf(stderr, "discard fail 7 15 %i\n", ret); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = queue_discard_lba(ring, fd, 1, MAX_TEST_LBAS - 1); | ||
if (ret) { | ||
fprintf(stderr, "large discard failed %i\n", ret); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = test_parallel_discards(ring, fd); | ||
if (ret) { | ||
fprintf(stderr, "test_parallel_discards() failed %i\n", ret); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
close(fd); | ||
return 0; | ||
} | ||
|
||
static int test_fail_edge_cases(struct io_uring *ring) | ||
{ | ||
int ret, fd; | ||
|
||
fd = open(filename, O_DIRECT | O_RDWR | O_EXCL); | ||
if (fd < 0) { | ||
fprintf(stderr, "open failed %i\n", errno); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = queue_discard_lba(ring, fd, bdev_size_lbas, 1); | ||
if (ret >= 0) { | ||
fprintf(stderr, "discarded beyond capacity %i\n", ret); | ||
return 1; | ||
} | ||
|
||
ret = queue_discard_lba(ring, fd, bdev_size_lbas - 1, 2); | ||
if (ret >= 0) { | ||
fprintf(stderr, "discarded beyond capacity with overlap %i\n", ret); | ||
return 1; | ||
} | ||
|
||
ret = queue_discard_range(ring, fd, (uint64_t)-lba_size, lba_size + 2); | ||
if (ret >= 0) { | ||
fprintf(stderr, "discard overflow %i\n", ret); | ||
return 1; | ||
} | ||
|
||
ret = queue_discard_range(ring, fd, lba_size / 2, lba_size); | ||
if (ret >= 0) { | ||
fprintf(stderr, "discard, unaligned offset %i\n", ret); | ||
return 1; | ||
} | ||
|
||
ret = queue_discard_range(ring, fd, 0, lba_size / 2); | ||
if (ret >= 0) { | ||
fprintf(stderr, "discard, unaligned size %i\n", ret); | ||
return 1; | ||
} | ||
|
||
close(fd); | ||
return 0; | ||
} | ||
|
||
static int test_rdonly(struct io_uring *ring) | ||
{ | ||
int ret, fd; | ||
int ro; | ||
|
||
fd = open(filename, O_DIRECT | O_RDONLY | O_EXCL); | ||
if (fd < 0) { | ||
fprintf(stderr, "open failed %i\n", errno); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = queue_discard_lba(ring, fd, 0, 1); | ||
if (ret >= 0) { | ||
fprintf(stderr, "discarded with O_RDONLY %i\n", ret); | ||
return 1; | ||
} | ||
close(fd); | ||
|
||
fd = open(filename, O_DIRECT | O_RDWR | O_EXCL); | ||
if (fd < 0) { | ||
fprintf(stderr, "open failed %i\n", errno); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ro = 1; | ||
ret = ioctl(fd, BLKROSET, &ro); | ||
if (ret) { | ||
fprintf(stderr, "BLKROSET 1 failed %i\n", errno); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = queue_discard_lba(ring, fd, 0, 1); | ||
if (ret >= 0) { | ||
fprintf(stderr, "discarded with O_RDONLY %i\n", ret); | ||
return 1; | ||
} | ||
|
||
ro = 0; | ||
ret = ioctl(fd, BLKROSET, &ro); | ||
if (ret) { | ||
fprintf(stderr, "BLKROSET 0 failed %i\n", errno); | ||
return T_EXIT_FAIL; | ||
} | ||
close(fd); | ||
return 0; | ||
} | ||
|
||
int main(int argc, char *argv[]) | ||
{ | ||
struct io_uring ring; | ||
int fd, ret; | ||
|
||
if (argc != 2) | ||
return T_SETUP_SKIP; | ||
filename = argv[1]; | ||
|
||
fd = open(filename, O_DIRECT | O_RDONLY | O_EXCL); | ||
if (fd < 0) { | ||
fprintf(stderr, "open failed %i\n", errno); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = ioctl(fd, BLKGETSIZE64, &bdev_size); | ||
if (ret < 0) { | ||
fprintf(stderr, "BLKGETSIZE64 failed %i\n", errno); | ||
return T_EXIT_FAIL; | ||
} | ||
ret = ioctl(fd, BLKSSZGET, &lba_size); | ||
if (ret < 0) { | ||
fprintf(stderr, "BLKSSZGET failed %i\n", errno); | ||
return T_EXIT_FAIL; | ||
} | ||
assert(bdev_size % lba_size == 0); | ||
bdev_size_lbas = bdev_size / lba_size; | ||
close(fd); | ||
|
||
if (bdev_size_lbas < MAX_TEST_LBAS) { | ||
fprintf(stderr, "the device is too small, skip\n"); | ||
return T_EXIT_SKIP; | ||
} | ||
|
||
ret = io_uring_queue_init(16, &ring, 0); | ||
if (ret) { | ||
fprintf(stderr, "queue init failed: %d\n", ret); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = basic_discard_test(&ring); | ||
if (ret) { | ||
if (ret == T_EXIT_SKIP) | ||
return T_EXIT_SKIP; | ||
fprintf(stderr, "basic_discard_test() failed\n"); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = test_rdonly(&ring); | ||
if (ret) { | ||
fprintf(stderr, "test_rdonly() failed\n"); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
ret = test_fail_edge_cases(&ring); | ||
if (ret) { | ||
fprintf(stderr, "test_fail_edge_cases() failed\n"); | ||
return T_EXIT_FAIL; | ||
} | ||
|
||
io_uring_queue_exit(&ring); | ||
return 0; | ||
} |