android13/external/ltp/testcases/kernel/syscalls/io_uring/io_uring01.c

269 lines
6.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2020 ARM Ltd. All rights reserved.
* Author: Vikas Kumar <vikas.kumar2@arm.com>
*
* Copyright (C) 2020 Cyril Hrubis <chrubis@suse.cz>
*
* Tests for asynchronous I/O raw API i.e io_uring_setup(), io_uring_register()
* and io_uring_enter(). This tests validate basic API operation by creating a
* submission queue and a completion queue using io_uring_setup(). User buffer
* registered in the kernel for long term operation using io_uring_register().
* This tests initiates I/O operations with the help of io_uring_enter().
*/
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include "config.h"
#include "tst_test.h"
#include "lapi/io_uring.h"
#define TEST_FILE "test_file"
#define QUEUE_DEPTH 1
#define BLOCK_SZ 1024
static struct tcase {
unsigned int setup_flags;
unsigned int register_opcode;
unsigned int enter_flags;
} tcases[] = {
{0, IORING_REGISTER_BUFFERS, IORING_OP_READ_FIXED},
};
struct io_sq_ring {
unsigned int *head;
unsigned int *tail;
unsigned int *ring_mask;
unsigned int *ring_entries;
unsigned int *flags;
unsigned int *array;
};
struct io_cq_ring {
unsigned int *head;
unsigned int *tail;
unsigned int *ring_mask;
unsigned int *ring_entries;
struct io_uring_cqe *cqes;
};
struct submitter {
int ring_fd;
struct io_sq_ring sq_ring;
struct io_uring_sqe *sqes;
struct io_cq_ring cq_ring;
};
static struct submitter sub_ring;
static struct submitter *s = &sub_ring;
static sigset_t sig;
static struct iovec *iov;
static void *sptr;
static size_t sptr_size;
static void *cptr;
static size_t cptr_size;
static int setup_io_uring_test(struct submitter *s, struct tcase *tc)
{
struct io_sq_ring *sring = &s->sq_ring;
struct io_cq_ring *cring = &s->cq_ring;
struct io_uring_params p;
memset(&p, 0, sizeof(p));
p.flags |= tc->setup_flags;
s->ring_fd = io_uring_setup(QUEUE_DEPTH, &p);
if (s->ring_fd != -1) {
tst_res(TPASS, "io_uring_setup() passed");
} else {
tst_res(TFAIL | TERRNO, "io_uring_setup() failed");
return 1;
}
sptr_size = p.sq_off.array + p.sq_entries * sizeof(unsigned int);
/* Submission queue ring buffer mapping */
sptr = SAFE_MMAP(0, sptr_size,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE,
s->ring_fd, IORING_OFF_SQ_RING);
/* Save global submission queue struct info */
sring->head = sptr + p.sq_off.head;
sring->tail = sptr + p.sq_off.tail;
sring->ring_mask = sptr + p.sq_off.ring_mask;
sring->ring_entries = sptr + p.sq_off.ring_entries;
sring->flags = sptr + p.sq_off.flags;
sring->array = sptr + p.sq_off.array;
/* Submission queue entries ring buffer mapping */
s->sqes = SAFE_MMAP(0, p.sq_entries *
sizeof(struct io_uring_sqe),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE,
s->ring_fd, IORING_OFF_SQES);
cptr_size = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe);
/* Completion queue ring buffer mapping */
cptr = SAFE_MMAP(0, cptr_size,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE,
s->ring_fd, IORING_OFF_CQ_RING);
/* Save global completion queue struct info */
cring->head = cptr + p.cq_off.head;
cring->tail = cptr + p.cq_off.tail;
cring->ring_mask = cptr + p.cq_off.ring_mask;
cring->ring_entries = cptr + p.cq_off.ring_entries;
cring->cqes = cptr + p.cq_off.cqes;
return 0;
}
static void check_buffer(char *buffer, size_t len)
{
size_t i;
for (i = 0; i < len; i++) {
if (buffer[i] != 'a') {
tst_res(TFAIL, "Wrong data at offset %zu", i);
break;
}
}
if (i == len)
tst_res(TPASS, "Buffer filled in correctly");
}
static void drain_uring_cq(struct submitter *s, unsigned int exp_events)
{
struct io_cq_ring *cring = &s->cq_ring;
unsigned int head = *cring->head;
unsigned int events = 0;
for (head = *cring->head; head != *cring->tail; head++) {
struct io_uring_cqe *cqe = &cring->cqes[head & *s->cq_ring.ring_mask];
events++;
if (cqe->res < 0) {
tst_res(TFAIL, "CQE result %s", tst_strerrno(-cqe->res));
} else {
struct iovec *iovecs = (void*)cqe->user_data;
if (cqe->res == BLOCK_SZ)
tst_res(TPASS, "CQE result %i", cqe->res);
else
tst_res(TFAIL, "CQE result %i expected %i", cqe->res, BLOCK_SZ);
check_buffer(iovecs[0].iov_base, cqe->res);
}
}
*cring->head = head;
if (exp_events == events) {
tst_res(TPASS, "Got %u completion events", events);
return;
}
tst_res(TFAIL, "Got %u completion events expected %u",
events, exp_events);
}
static int submit_to_uring_sq(struct submitter *s, struct tcase *tc)
{
unsigned int index = 0, tail = 0, next_tail = 0;
struct io_sq_ring *sring = &s->sq_ring;
struct io_uring_sqe *sqe;
int ret;
memset(iov->iov_base, 0, iov->iov_len);
ret = io_uring_register(s->ring_fd, tc->register_opcode,
iov, QUEUE_DEPTH);
if (ret == 0) {
tst_res(TPASS, "io_uring_register() passed");
} else {
tst_res(TFAIL | TERRNO, "io_uring_register() failed");
return 1;
}
int fd = SAFE_OPEN(TEST_FILE, O_RDONLY);
/* Submission queue entry addition to SQE ring buffer tail */
tail = *sring->tail;
next_tail = tail + 1;
index = tail & *s->sq_ring.ring_mask;
sqe = &s->sqes[index];
sqe->flags = 0;
sqe->fd = fd;
sqe->opcode = tc->enter_flags;
sqe->addr = (unsigned long)iov->iov_base;
sqe->len = BLOCK_SZ;
sqe->off = 0;
sqe->user_data = (unsigned long long)iov;
sring->array[index] = index;
tail = next_tail;
/* Kernel to notice the tail update */
if (*sring->tail != tail)
*sring->tail = tail;
ret = io_uring_enter(s->ring_fd, 1, 1, IORING_ENTER_GETEVENTS, &sig);
if (ret == 1) {
tst_res(TPASS, "io_uring_enter() waited for 1 event");
} else {
tst_res(TFAIL | TERRNO, "io_uring_enter() returned %i", ret);
SAFE_CLOSE(fd);
return 1;
}
SAFE_CLOSE(fd);
return 0;
}
static void cleanup_io_uring_test(void)
{
io_uring_register(s->ring_fd, IORING_UNREGISTER_BUFFERS,
NULL, QUEUE_DEPTH);
SAFE_MUNMAP(s->sqes, sizeof(struct io_uring_sqe));
SAFE_MUNMAP(cptr, cptr_size);
SAFE_MUNMAP(sptr, sptr_size);
SAFE_CLOSE(s->ring_fd);
}
static void run(unsigned int n)
{
struct tcase *tc = &tcases[n];
if (setup_io_uring_test(s, tc))
return;
if (!submit_to_uring_sq(s, tc))
drain_uring_cq(s, 1);
cleanup_io_uring_test();
}
static void setup(void)
{
io_uring_setup_supported_by_kernel();
tst_fill_file(TEST_FILE, 'a', 1024, 1);
}
static struct tst_test test = {
.setup = setup,
.test = run,
.needs_tmpdir = 1,
.tcnt = ARRAY_SIZE(tcases),
.bufs = (struct tst_buffers []) {
{&iov, .iov_sizes = (int[]){BLOCK_SZ, -1}},
{}
}
};