/* SPDX-License-Identifier: MIT */ /* * Description: run various shared buffer ring sanity checks * */ #include #include #include #include #include #include #include #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; }