blob: af517a1f2936cec58a7d7db5ec8a9431490424b7 [file] [log] [blame]
/* SPDX-License-Identifier: MIT */
/*
* Description: Test that registered waits work
*/
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <linux/mman.h>
#include "liburing.h"
#include "helpers.h"
#include "test.h"
#include "../src/syscall.h"
static const struct io_uring_reg_wait brief_wait = {
.flags = IORING_REG_WAIT_TS,
.ts.tv_sec = 0,
.ts.tv_nsec = 1000,
};
static bool has_kernel_regions;
static int test_wait_reg_offset(struct io_uring *ring,
unsigned wait_nr, unsigned long offset)
{
return __sys_io_uring_enter2(ring->ring_fd, 0, wait_nr,
IORING_ENTER_GETEVENTS |
IORING_ENTER_EXT_ARG |
IORING_ENTER_EXT_ARG_REG,
(void *)offset,
sizeof(struct io_uring_reg_wait));
}
static int __init_ring_with_region(struct io_uring *ring, unsigned ring_flags,
struct io_uring_mem_region_reg *pr,
bool disabled)
{
int flags = disabled ? IORING_SETUP_R_DISABLED : 0;
int ret;
ret = io_uring_queue_init(8, ring, flags);
if (ret) {
if (ret != -EINVAL)
fprintf(stderr, "ring setup failed: %d\n", ret);
return ret;
}
ret = io_uring_register_region(ring, pr);
if (ret)
goto err;
if (disabled) {
ret = io_uring_enable_rings(ring);
if (ret) {
fprintf(stderr, "io_uring_enable_rings failure %i\n", ret);
goto err;
}
}
return 0;
err:
io_uring_queue_exit(ring);
return ret;
}
static int page_size;
static struct io_uring_reg_wait *reg;
static int test_invalid_sig(struct io_uring *ring)
{
struct io_uring_cqe *cqe;
sigset_t sig;
int ret;
memset(reg, 0, sizeof(*reg));
reg->ts.tv_sec = 1;
reg->ts.tv_nsec = 0;
reg->sigmask = (unsigned long) &sig;
reg->sigmask_sz = 1;
ret = io_uring_submit_and_wait_reg(ring, &cqe, 1, 0);
if (ret != -EINVAL) {
fprintf(stderr, "sigmask_sz failed: %d\n", ret);
return T_EXIT_FAIL;
}
memset(reg, 0, sizeof(*reg));
reg->ts.tv_sec = 1;
reg->ts.tv_nsec = 0;
reg->sigmask = 100;
reg->sigmask_sz = 8;
ret = io_uring_submit_and_wait_reg(ring, &cqe, 1, 0);
if (ret != -EFAULT) {
fprintf(stderr, "sigmask invalid failed: %d\n", ret);
return T_EXIT_FAIL;
}
return T_EXIT_PASS;
}
static int test_offsets(struct io_uring *ring, struct io_uring_reg_wait *base,
size_t size, bool overallocated)
{
struct io_uring_cqe *cqe;
int max_index = size / sizeof(struct io_uring_reg_wait);
struct io_uring_reg_wait *rw;
unsigned long offset;
int copy_size;
void *rw_ptr;
int ret;
rw = base;
memcpy(rw, &brief_wait, sizeof(brief_wait));
ret = io_uring_submit_and_wait_reg(ring, &cqe, 1, 0);
if (ret != -ETIME) {
fprintf(stderr, "0 index failed: %d\n", ret);
return T_EXIT_FAIL;
}
if (overallocated) {
rw = base + max_index;
memcpy(rw, &brief_wait, sizeof(brief_wait));
}
ret = io_uring_submit_and_wait_reg(ring, &cqe, 1, max_index);
if (ret != -EFAULT) {
fprintf(stderr, "max+1 index failed: %d\n", ret);
return T_EXIT_FAIL;
}
rw = base + max_index - 1;
memcpy(rw, &brief_wait, sizeof(brief_wait));
ret = io_uring_submit_and_wait_reg(ring, &cqe, 1, max_index - 1);
if (ret != -ETIME) {
fprintf(stderr, "last index failed: %d\n", ret);
return T_EXIT_FAIL;
}
offset = 0UL - sizeof(long);
ret = test_wait_reg_offset(ring, 1, offset);
if (ret != -EFAULT) {
fprintf(stderr, "overflow offset failed: %d\n", ret);
return T_EXIT_FAIL;
}
offset = size - sizeof(long);
rw = (void *)base + offset;
copy_size = overallocated ? sizeof(brief_wait) : sizeof(long);
memcpy(rw, &brief_wait, copy_size);
ret = test_wait_reg_offset(ring, 1, offset);
if (ret != -EFAULT) {
fprintf(stderr, "OOB offset failed: %d\n", ret);
return T_EXIT_FAIL;
}
offset = 1;
rw_ptr = (void *) base + offset;
memcpy(rw_ptr, &brief_wait, sizeof(brief_wait));
/* undefined behaviour, check the kernel doesn't crash */
(void)test_wait_reg_offset(ring, 1, offset);
return 0;
}
static int test_basic(struct io_uring *ring)
{
struct io_uring_cqe *cqe;
struct timeval tv;
int ret;
memset(reg, 0, sizeof(*reg));
reg->ts.tv_sec = 1;
reg->ts.tv_nsec = 100000000ULL;
reg->flags = IORING_REG_WAIT_TS;
gettimeofday(&tv, NULL);
ret = io_uring_submit_and_wait_reg(ring, &cqe, 2, 0);
if (ret != -ETIME) {
fprintf(stderr, "submit_and_wait_reg: %d\n", ret);
goto err;
}
ret = mtime_since_now(&tv);
/* allow some slack, should be around 1.1s */
if (ret < 1000 || ret > 1200) {
fprintf(stderr, "wait too long or short: %d\n", ret);
goto err;
}
return T_EXIT_PASS;
err:
return T_EXIT_FAIL;
}
static int test_wait_arg(void)
{
struct io_uring_region_desc rd = {};
struct io_uring_mem_region_reg mr = {};
struct io_uring ring;
void *buffer;
int ret;
ret = io_uring_queue_init(8, &ring, IORING_SETUP_R_DISABLED);
if (ret) {
if (ret == -EINVAL) {
printf("IORING_SETUP_R_DISABLED not supported, skip\n");
return 0;
}
fprintf(stderr, "ring setup failed: %d\n", ret);
return T_EXIT_FAIL;
}
buffer = t_aligned_alloc(page_size, page_size * 4);
if (!buffer) {
fprintf(stderr, "allocation failed\n");
return T_EXIT_FAIL;
}
rd.user_addr = uring_ptr_to_u64(buffer);
rd.size = page_size;
rd.flags = IORING_MEM_REGION_TYPE_USER;
mr.region_uptr = uring_ptr_to_u64(&rd);
mr.flags = IORING_MEM_REGION_REG_WAIT_ARG;
ret = io_uring_register_region(&ring, &mr);
if (ret) {
fprintf(stderr, "region reg failed %i\n", ret);
return 1;
}
ret = io_uring_enable_rings(&ring);
if (ret) {
fprintf(stderr, "io_uring_enable_rings failure %i\n", ret);
return T_EXIT_FAIL;
}
reg = buffer;
ret = test_basic(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test failed\n");
goto err;
}
ret = test_invalid_sig(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_invalid sig failed\n");
goto err;
}
ret = test_offsets(&ring, buffer, page_size, true);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_offsets failed\n");
goto err;
}
err:
free(buffer);
io_uring_queue_exit(&ring);
return ret;
}
static int test_try_register_region(struct io_uring_mem_region_reg *pr,
bool disabled)
{
struct io_uring ring;
int ret;
ret = __init_ring_with_region(&ring, 0, pr, disabled);
if (!ret)
io_uring_queue_exit(&ring);
return ret;
}
static int test_regions(void)
{
struct io_uring_region_desc rd = {};
struct io_uring_mem_region_reg mr = {};
void *buffer;
int ret;
buffer = t_aligned_alloc(page_size, page_size * 4);
if (!buffer) {
fprintf(stderr, "allocation failed\n");
return T_EXIT_FAIL;
}
rd.user_addr = uring_ptr_to_u64(buffer);
rd.size = page_size;
rd.flags = IORING_MEM_REGION_TYPE_USER;
mr.region_uptr = uring_ptr_to_u64(&rd);
mr.flags = IORING_MEM_REGION_REG_WAIT_ARG;
ret = test_try_register_region(&mr, true);
if (ret == -EINVAL) {
free(buffer);
return T_EXIT_SKIP;
}
if (ret) {
fprintf(stderr, "region: register normal fail %i\n", ret);
return T_EXIT_FAIL;
}
ret = test_try_register_region(&mr, false);
if (ret != -EINVAL) {
fprintf(stderr, "region: register with !R_DISABLED fail %i\n", ret);
return T_EXIT_FAIL;
}
rd.size = page_size * 4;
ret = test_try_register_region(&mr, true);
if (ret) {
fprintf(stderr, "test_try_register_region() 16KB fail %i\n", ret);
return T_EXIT_FAIL;
}
rd.size = page_size;
rd.user_addr = 0;
ret = test_try_register_region(&mr, true);
if (ret != -EFAULT) {
fprintf(stderr, "test_try_register_region() null uptr fail %i\n", ret);
return T_EXIT_FAIL;
}
rd.user_addr = uring_ptr_to_u64(buffer);
rd.flags = 0;
ret = test_try_register_region(&mr, true);
if (!ret) {
fprintf(stderr, "test_try_register_region() kernel alloc with uptr fail %i\n", ret);
return T_EXIT_FAIL;
}
rd.flags = IORING_MEM_REGION_TYPE_USER;
rd.size = 0;
ret = test_try_register_region(&mr, true);
if (!ret) {
fprintf(stderr, "test_try_register_region() 0-size fail %i\n", ret);
return T_EXIT_FAIL;
}
rd.size = page_size;
mr.region_uptr = 0;
ret = test_try_register_region(&mr, true);
if (!ret) {
fprintf(stderr, "test_try_register_region() NULL region %i\n", ret);
return T_EXIT_FAIL;
}
mr.region_uptr = uring_ptr_to_u64(&rd);
rd.user_addr += 16;
ret = test_try_register_region(&mr, true);
if (!ret) {
fprintf(stderr, "test_try_register_region() misaligned region %i\n", ret);
return T_EXIT_FAIL;
}
rd.user_addr = 0x1000;
ret = test_try_register_region(&mr, true);
if (!ret) {
fprintf(stderr, "test_try_register_region() bogus uptr %i\n", ret);
return T_EXIT_FAIL;
}
rd.user_addr = uring_ptr_to_u64(buffer);
free(buffer);
buffer = mmap(NULL, page_size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buffer == MAP_FAILED) {
fprintf(stderr, "mmap alloc failed\n");
return 1;
}
rd.user_addr = uring_ptr_to_u64(buffer);
ret = test_try_register_region(&mr, true);
if (ret != -EFAULT) {
fprintf(stderr, "test_try_register_region() RO uptr %i\n", ret);
return T_EXIT_FAIL;
}
rd.flags = 0;
rd.user_addr = 0;
ret = test_try_register_region(&mr, true);
if (ret == -EINVAL) {
has_kernel_regions = false;
goto out;
}
if (ret) {
fprintf(stderr, "test_try_register_region() failed kernel alloc %i\n", ret);
return T_EXIT_FAIL;
}
has_kernel_regions = true;
rd.flags = 0;
rd.user_addr = uring_ptr_to_u64(buffer);
ret = test_try_register_region(&mr, true);
if (!ret) {
fprintf(stderr, "test_try_register_region() failed uptr w kernel alloc %i\n", ret);
return T_EXIT_FAIL;
}
out:
munmap(buffer, page_size);
return 0;
}
struct t_region {
void *ptr;
bool user_mem;
size_t size;
};
static void t_region_free(struct t_region *r)
{
if (r->ptr)
munmap(r->ptr, r->size);
}
static int t_region_create_kernel(struct t_region *r,
struct io_uring *ring)
{
struct io_uring_region_desc rd = { .size = r->size, };
struct io_uring_mem_region_reg mr = {
.region_uptr = uring_ptr_to_u64(&rd),
.flags = IORING_MEM_REGION_REG_WAIT_ARG,
};
void *p;
int ret;
ret = io_uring_register_region(ring, &mr);
if (ret)
return ret;
p = mmap(NULL, r->size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE, ring->ring_fd, rd.mmap_offset);
if (p == MAP_FAILED)
return -EFAULT;
r->ptr = p;
r->user_mem = false;
return 0;
}
static int t_region_create_user(struct t_region *r,
struct io_uring *ring,
bool huge)
{
struct io_uring_region_desc rd = {};
struct io_uring_mem_region_reg mr = {};
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
void *p;
int ret;
if (huge)
flags |= MAP_HUGETLB | MAP_HUGE_2MB;
p = mmap(NULL, r->size, PROT_READ | PROT_WRITE, flags, -1, 0);
if (p == MAP_FAILED)
return -ENOMEM;
mr.region_uptr = uring_ptr_to_u64(&rd);
mr.flags = IORING_MEM_REGION_REG_WAIT_ARG;
rd.user_addr = uring_ptr_to_u64(p);
rd.flags = IORING_MEM_REGION_TYPE_USER;
rd.size = r->size;
ret = io_uring_register_region(ring, &mr);
if (ret) {
munmap(p, r->size);
return ret;
}
r->ptr = p;
r->user_mem = true;
return 0;
}
struct test_param {
size_t size;
bool huge_page;
bool kern_buf;
};
static int test_region_buffer_types(void)
{
const size_t huge_size = 1024 * 1024 * 2;
struct test_param params[] = {
{ .size = page_size },
/* forcing vmap */
{ .size = page_size * 2 },
{ .size = page_size * 16 },
/* huge page w/o vmap */
{ .size = huge_size, .huge_page = true },
/* huge page w/ vmap */
{ .size = huge_size * 2, .huge_page = true },
{ .size = page_size, .kern_buf = true },
/* likely to be a compound page */
{ .size = page_size * 2, .kern_buf = true },
{ .size = page_size * 8, .kern_buf = true },
/* kernel allocation + vmap */
{ .size = page_size * 512, .kern_buf = true },
};
struct io_uring ring;
int i, ret;
for (i = 0; i < ARRAY_SIZE(params); i++) {
struct t_region r = { .size = params[i].size, };
ret = io_uring_queue_init(8, &ring, IORING_SETUP_R_DISABLED);
if (ret) {
fprintf(stderr, "ring setup failed: %d\n", ret);
return ret;
}
if (params[i].kern_buf)
ret = t_region_create_kernel(&r, &ring);
else
ret = t_region_create_user(&r, &ring, params[i].huge_page);
if (ret) {
io_uring_queue_exit(&ring);
if (ret == -ENOMEM || ret == -EINVAL)
continue;
fprintf(stderr, "t_region_create_user failed, idx %i\n", i);
return 1;
}
ret = io_uring_enable_rings(&ring);
if (ret) {
fprintf(stderr, "io_uring_enable_rings failure %i\n", ret);
return ret;
}
ret = test_offsets(&ring, r.ptr, r.size, false);
if (ret) {
fprintf(stderr, "test_offsets failed, idx %i\n", i);
return 1;
}
t_region_free(&r);
io_uring_queue_exit(&ring);
}
return 0;
}
int main(int argc, char *argv[])
{
int ret;
if (argc > 1)
return 0;
page_size = sysconf(_SC_PAGESIZE);
if (page_size < 0) {
perror("sysconf(_SC_PAGESIZE)");
return 1;
}
ret = test_regions();
if (ret == T_EXIT_SKIP) {
printf("regions are not supported, skip\n");
return 0;
} else if (ret) {
fprintf(stderr, "test_region failed\n");
return 1;
}
ret = test_wait_arg();
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_wait_arg failed\n");
return 1;
}
ret = test_region_buffer_types();
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_region_buffer_types failed\n");
return 1;
}
return 0;
}