| /* SPDX-License-Identifier: MIT */ |
| /* |
| * Description: run various shared buffer ring sanity checks |
| * |
| */ |
| #include <errno.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <sys/mman.h> |
| |
| #include "liburing.h" |
| #include "helpers.h" |
| |
| static int no_buf_ring; |
| static int pagesize; |
| |
| /* test trying to register classic group when ring group exists */ |
| static int test_mixed_reg2(int bgid) |
| { |
| struct io_uring_buf_ring *br; |
| struct io_uring_sqe *sqe; |
| struct io_uring_cqe *cqe; |
| struct io_uring ring; |
| void *bufs; |
| int ret; |
| |
| ret = t_create_ring(1, &ring, 0); |
| if (ret == T_SETUP_SKIP) |
| return 0; |
| else if (ret != T_SETUP_OK) |
| return 1; |
| |
| br = io_uring_setup_buf_ring(&ring, 32, bgid, 0, &ret); |
| if (!br) { |
| fprintf(stderr, "Buffer ring register failed %d\n", ret); |
| return 1; |
| } |
| |
| /* provide classic buffers, group 1 */ |
| bufs = malloc(8 * 1024); |
| sqe = io_uring_get_sqe(&ring); |
| io_uring_prep_provide_buffers(sqe, bufs, 1024, 8, bgid, 0); |
| io_uring_submit(&ring); |
| ret = io_uring_wait_cqe(&ring, &cqe); |
| if (ret) { |
| fprintf(stderr, "wait_cqe %d\n", ret); |
| return 1; |
| } |
| if (cqe->res != -EEXIST && cqe->res != -EINVAL) { |
| fprintf(stderr, "cqe res %d\n", cqe->res); |
| return 1; |
| } |
| io_uring_cqe_seen(&ring, cqe); |
| |
| io_uring_free_buf_ring(&ring, br, 32, bgid); |
| io_uring_queue_exit(&ring); |
| free(bufs); |
| return 0; |
| } |
| |
| /* test trying to register ring group when classic group exists */ |
| static int test_mixed_reg(int bgid) |
| { |
| struct io_uring_buf_ring *br; |
| struct io_uring_sqe *sqe; |
| struct io_uring_cqe *cqe; |
| struct io_uring ring; |
| void *bufs; |
| int ret; |
| |
| ret = t_create_ring(1, &ring, 0); |
| if (ret == T_SETUP_SKIP) |
| return 0; |
| else if (ret != T_SETUP_OK) |
| return 1; |
| |
| /* provide classic buffers, group 1 */ |
| bufs = malloc(8 * 1024); |
| sqe = io_uring_get_sqe(&ring); |
| io_uring_prep_provide_buffers(sqe, bufs, 1024, 8, bgid, 0); |
| io_uring_submit(&ring); |
| ret = io_uring_wait_cqe(&ring, &cqe); |
| if (ret) { |
| fprintf(stderr, "wait_cqe %d\n", ret); |
| return 1; |
| } |
| if (cqe->res) { |
| fprintf(stderr, "cqe res %d\n", cqe->res); |
| return 1; |
| } |
| io_uring_cqe_seen(&ring, cqe); |
| |
| br = io_uring_setup_buf_ring(&ring, 32, bgid, 0, &ret); |
| if (br) { |
| fprintf(stderr, "Buffer ring setup succeeded unexpectedly %d\n", ret); |
| return 1; |
| } |
| |
| io_uring_queue_exit(&ring); |
| free(bufs); |
| return 0; |
| } |
| |
| static int test_double_reg_unreg(int bgid) |
| { |
| struct io_uring_buf_reg reg = { }; |
| struct io_uring_buf_ring *br; |
| struct io_uring ring; |
| int ret; |
| |
| ret = t_create_ring(1, &ring, 0); |
| if (ret == T_SETUP_SKIP) |
| return 0; |
| else if (ret != T_SETUP_OK) |
| return 1; |
| |
| br = io_uring_setup_buf_ring(&ring, 32, bgid, 0, &ret); |
| if (!br) { |
| fprintf(stderr, "Buffer ring register failed %d\n", ret); |
| return 1; |
| } |
| |
| /* check that 2nd register with same bgid fails */ |
| reg.ring_addr = (unsigned long) br; |
| reg.ring_entries = 32; |
| reg.bgid = bgid; |
| |
| ret = io_uring_register_buf_ring(&ring, ®, 0); |
| if (ret != -EEXIST) { |
| fprintf(stderr, "Buffer ring register failed %d\n", ret); |
| return 1; |
| } |
| |
| ret = io_uring_free_buf_ring(&ring, br, 32, bgid); |
| if (ret) { |
| fprintf(stderr, "Buffer ring register failed %d\n", ret); |
| return 1; |
| } |
| |
| ret = io_uring_unregister_buf_ring(&ring, bgid); |
| if (ret != -EINVAL && ret != -ENOENT) { |
| fprintf(stderr, "Buffer ring register failed %d\n", ret); |
| return 1; |
| } |
| |
| io_uring_queue_exit(&ring); |
| return 0; |
| } |
| |
| static int test_reg_unreg(int bgid) |
| { |
| struct io_uring_buf_ring *br; |
| struct io_uring ring; |
| int ret; |
| |
| ret = t_create_ring(1, &ring, 0); |
| if (ret == T_SETUP_SKIP) |
| return 0; |
| else if (ret != T_SETUP_OK) |
| return 1; |
| |
| br = io_uring_setup_buf_ring(&ring, 32, bgid, 0, &ret); |
| if (!br) { |
| if (ret == -EINVAL) { |
| no_buf_ring = 1; |
| return 0; |
| } |
| fprintf(stderr, "Buffer ring register failed %d\n", ret); |
| return 1; |
| } |
| |
| ret = io_uring_free_buf_ring(&ring, br, 32, bgid); |
| if (ret) { |
| fprintf(stderr, "Buffer ring unregister failed %d\n", ret); |
| return 1; |
| } |
| |
| io_uring_queue_exit(&ring); |
| return 0; |
| } |
| |
| static int test_bad_reg(int bgid) |
| { |
| struct io_uring ring; |
| int ret; |
| struct io_uring_buf_reg reg = { }; |
| |
| ret = t_create_ring(1, &ring, 0); |
| if (ret == T_SETUP_SKIP) |
| return 0; |
| else if (ret != T_SETUP_OK) |
| return 1; |
| |
| reg.ring_addr = 4096; |
| reg.ring_entries = 32; |
| reg.bgid = bgid; |
| |
| ret = io_uring_register_buf_ring(&ring, ®, 0); |
| if (!ret) |
| fprintf(stderr, "Buffer ring register worked unexpectedly\n"); |
| |
| io_uring_queue_exit(&ring); |
| return !ret; |
| } |
| |
| static int test_full_page_reg(int bgid) |
| { |
| #if defined(__hppa__) |
| return T_EXIT_SKIP; |
| #else |
| struct io_uring ring; |
| int ret; |
| void *ptr; |
| struct io_uring_buf_reg reg = { }; |
| int entries = pagesize / sizeof(struct io_uring_buf); |
| |
| ret = io_uring_queue_init(1, &ring, 0); |
| if (ret) { |
| fprintf(stderr, "queue init failed %d\n", ret); |
| return T_EXIT_FAIL; |
| } |
| |
| ret = posix_memalign(&ptr, pagesize, pagesize * 2); |
| if (ret) { |
| fprintf(stderr, "posix_memalign failed %d\n", ret); |
| goto err; |
| } |
| |
| ret = mprotect(ptr + pagesize, pagesize, PROT_NONE); |
| if (ret) { |
| fprintf(stderr, "mprotect failed %d\n", errno); |
| goto err1; |
| } |
| |
| reg.ring_addr = (unsigned long) ptr; |
| reg.ring_entries = entries; |
| reg.bgid = bgid; |
| |
| ret = io_uring_register_buf_ring(&ring, ®, 0); |
| if (ret) |
| fprintf(stderr, "register buf ring failed %d\n", ret); |
| |
| if (mprotect(ptr + pagesize, pagesize, PROT_READ | PROT_WRITE)) |
| fprintf(stderr, "reverting mprotect failed %d\n", errno); |
| |
| err1: |
| free(ptr); |
| err: |
| io_uring_queue_exit(&ring); |
| return ret ? T_EXIT_FAIL : T_EXIT_PASS; |
| #endif |
| } |
| |
| static int test_one_read(int fd, int bgid, struct io_uring *ring) |
| { |
| int ret; |
| struct io_uring_cqe *cqe; |
| struct io_uring_sqe *sqe; |
| |
| sqe = io_uring_get_sqe(ring); |
| if (!sqe) { |
| fprintf(stderr, "get sqe failed\n"); |
| return -1; |
| } |
| |
| io_uring_prep_read(sqe, fd, NULL, 1, 0); |
| sqe->flags |= IOSQE_BUFFER_SELECT; |
| sqe->buf_group = bgid; |
| ret = io_uring_submit(ring); |
| if (ret <= 0) { |
| fprintf(stderr, "sqe submit failed: %d\n", ret); |
| return -1; |
| } |
| |
| ret = io_uring_wait_cqe(ring, &cqe); |
| if (ret < 0) { |
| fprintf(stderr, "wait completion %d\n", ret); |
| return -1; |
| } |
| ret = cqe->res; |
| io_uring_cqe_seen(ring, cqe); |
| |
| if (ret == -ENOBUFS) |
| return ret; |
| |
| if (ret != 1) { |
| fprintf(stderr, "read result %d\n", ret); |
| return -1; |
| } |
| |
| return cqe->flags >> 16; |
| } |
| |
| static int test_running(int bgid, int entries, int loops, int use_mmap) |
| { |
| int ring_mask = io_uring_buf_ring_mask(entries); |
| struct io_uring_buf_ring *br; |
| int ret, loop, idx, read_fd; |
| struct io_uring ring; |
| char buffer[8]; |
| bool *buffers; |
| |
| ret = t_create_ring(1, &ring, 0); |
| if (ret == T_SETUP_SKIP) |
| return T_EXIT_SKIP; |
| else if (ret != T_SETUP_OK) |
| return T_EXIT_FAIL; |
| |
| if (!use_mmap) { |
| br = io_uring_setup_buf_ring(&ring, entries, bgid, 0, &ret); |
| if (!br) { |
| /* by now should have checked if this is supported or not */ |
| fprintf(stderr, "Buffer ring register failed %d\n", ret); |
| return T_EXIT_FAIL; |
| } |
| } else { |
| struct io_uring_buf_reg reg = { |
| .ring_entries = entries, |
| .bgid = bgid, |
| .flags = IOU_PBUF_RING_MMAP, |
| }; |
| size_t ring_size; |
| off_t off; |
| |
| ret = io_uring_register_buf_ring(&ring, ®, 0); |
| if (ret) { |
| if (ret == -EINVAL) |
| return T_EXIT_SKIP; |
| fprintf(stderr, "mmap ring register failed %d\n", ret); |
| return T_EXIT_FAIL; |
| } |
| |
| off = IORING_OFF_PBUF_RING | |
| (unsigned long long) bgid << IORING_OFF_PBUF_SHIFT; |
| ring_size = sizeof(struct io_uring_buf) * entries; |
| br = mmap(NULL, ring_size, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_POPULATE, ring.ring_fd, off); |
| if (br == MAP_FAILED) { |
| perror("mmap"); |
| return T_EXIT_FAIL; |
| } |
| } |
| |
| buffers = malloc(sizeof(bool) * entries); |
| if (!buffers) |
| return T_EXIT_SKIP; |
| |
| read_fd = open("/dev/zero", O_RDONLY); |
| if (read_fd < 0) |
| return T_EXIT_SKIP; |
| |
| for (loop = 0; loop < loops; loop++) { |
| memset(buffers, 0, sizeof(bool) * entries); |
| for (idx = 0; idx < entries; idx++) |
| io_uring_buf_ring_add(br, buffer, sizeof(buffer), idx, ring_mask, idx); |
| io_uring_buf_ring_advance(br, entries); |
| |
| for (idx = 0; idx < entries; idx++) { |
| memset(buffer, 1, sizeof(buffer)); |
| ret = test_one_read(read_fd, bgid, &ring); |
| if (ret < 0) { |
| fprintf(stderr, "bad run %d/%d = %d\n", loop, idx, ret); |
| return T_EXIT_FAIL; |
| } |
| if (buffers[ret]) { |
| fprintf(stderr, "reused buffer %d/%d = %d!\n", loop, idx, ret); |
| return T_EXIT_FAIL; |
| } |
| if (buffer[0] != 0) { |
| fprintf(stderr, "unexpected read %d %d/%d = %d!\n", |
| (int)buffer[0], loop, idx, ret); |
| return T_EXIT_FAIL; |
| } |
| if (buffer[1] != 1) { |
| fprintf(stderr, "unexpected spilled read %d %d/%d = %d!\n", |
| (int)buffer[1], loop, idx, ret); |
| return T_EXIT_FAIL; |
| } |
| buffers[ret] = true; |
| } |
| ret = test_one_read(read_fd, bgid, &ring); |
| if (ret != -ENOBUFS) { |
| fprintf(stderr, "expected enobufs run %d = %d\n", loop, ret); |
| return T_EXIT_FAIL; |
| } |
| |
| } |
| |
| ret = io_uring_unregister_buf_ring(&ring, bgid); |
| if (ret) { |
| fprintf(stderr, "Buffer ring register failed %d\n", ret); |
| return T_EXIT_FAIL; |
| } |
| |
| close(read_fd); |
| io_uring_queue_exit(&ring); |
| free(buffers); |
| return T_EXIT_PASS; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int bgids[] = { 1, 127, -1 }; |
| int entries[] = {1, 32768, 4096, -1 }; |
| int ret, i; |
| |
| if (argc > 1) |
| return T_EXIT_SKIP; |
| |
| pagesize = getpagesize(); |
| |
| for (i = 0; bgids[i] != -1; i++) { |
| ret = test_reg_unreg(bgids[i]); |
| if (ret) { |
| fprintf(stderr, "test_reg_unreg failed\n"); |
| return T_EXIT_FAIL; |
| } |
| if (no_buf_ring) |
| break; |
| |
| ret = test_bad_reg(bgids[i]); |
| if (ret) { |
| fprintf(stderr, "test_bad_reg failed\n"); |
| return T_EXIT_FAIL; |
| } |
| |
| ret = test_double_reg_unreg(bgids[i]); |
| if (ret) { |
| fprintf(stderr, "test_double_reg_unreg failed\n"); |
| return T_EXIT_FAIL; |
| } |
| |
| ret = test_mixed_reg(bgids[i]); |
| if (ret) { |
| fprintf(stderr, "test_mixed_reg failed\n"); |
| return T_EXIT_FAIL; |
| } |
| |
| ret = test_mixed_reg2(bgids[i]); |
| if (ret) { |
| fprintf(stderr, "test_mixed_reg2 failed\n"); |
| return T_EXIT_FAIL; |
| } |
| |
| ret = test_full_page_reg(bgids[i]); |
| if (ret == T_EXIT_FAIL) { |
| fprintf(stderr, "test_full_page_reg failed\n"); |
| return T_EXIT_FAIL; |
| } |
| } |
| |
| for (i = 0; !no_buf_ring && entries[i] != -1; i++) { |
| ret = test_running(2, entries[i], 3, 0); |
| if (ret) { |
| fprintf(stderr, "test_running(%d) failed\n", entries[i]); |
| return T_EXIT_FAIL; |
| } |
| } |
| |
| for (i = 0; !no_buf_ring && entries[i] != -1; i++) { |
| ret = test_running(2, entries[i], 3, 1); |
| if (ret == T_EXIT_SKIP) { |
| break; |
| } else if (ret != T_EXIT_PASS) { |
| fprintf(stderr, "test_running(%d) mmap failed\n", entries[i]); |
| return T_EXIT_FAIL; |
| } |
| } |
| |
| |
| return T_EXIT_PASS; |
| } |