blob: 030d0424caead23cbe48d979a976d9cad0cfe907 [file]
/* SPDX-License-Identifier: MIT */
/*
* Description: Test having a buffer group with legacy buffers, where those
* are consumed until the group is empty. Then when empty, the
* group is converted to a ring provided one, and finally the
* original buffer from the legacy group is recycled.
*/
#include <errno.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include "liburing.h"
#include "helpers.h"
#define BGID 0x444
#define BID0 0
#define RING_ENTRIES 1024
#define LEGACY_BUF_LEN 0x1000
#define TRY_ITERS 800000
#define POST_REG_ITERS 300000
static volatile sig_atomic_t g_stop;
struct shared_state {
volatile int stop;
volatile int reg_ok;
volatile int reg_err;
volatile int reg_no_br;
};
static void on_alarm(int sig)
{
g_stop = 1;
}
static void pin_cpu(int cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
sched_setaffinity(0, sizeof(set), &set);
}
static int sender_loop(int sfd, struct shared_state *sh)
{
pin_cpu(1);
while (!sh->stop && !g_stop) {
ssize_t n = send(sfd, "", 0, MSG_DONTWAIT);
if (n < 0 && errno == EAGAIN)
sched_yield();
}
return 0;
}
static int reg_loop(int ring_fd, struct io_uring_buf_reg *reg,
struct shared_state *sh)
{
pin_cpu(1);
while (!sh->stop && !g_stop) {
int ret;
ret = io_uring_register(ring_fd, IORING_REGISTER_PBUF_RING,
reg, 1);
if (ret == 0) {
sh->reg_ok = 1;
break;
}
if (ret == -EINVAL) {
sh->reg_no_br = 1;
break;
}
if (ret != -EEXIST && ret != -EBUSY && ret != -EINVAL)
sh->reg_err = -ret;
}
while (!sh->stop && !g_stop)
usleep(1000);
return 0;
}
static int run_poc(void)
{
struct io_uring ring;
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
struct io_uring_buf_reg reg;
struct io_uring_buf_ring *br = NULL;
struct shared_state *sh = NULL;
char *legacy_buf = NULL;
char *ring_data = NULL;
size_t ring_bytes;
pid_t regger = -1, sender = -1;
int sv[2] = { -1, -1 }, ret, i, st, reg_seen_iter = -1;
pin_cpu(0);
sh = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (sh == MAP_FAILED) {
perror("mmap sh");
return T_EXIT_FAIL;
}
memset(sh, 0, 4096);
legacy_buf = mmap(NULL, LEGACY_BUF_LEN, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (legacy_buf == MAP_FAILED) {
perror("mmap legacy_buf");
return T_EXIT_FAIL;
}
memset(legacy_buf, 0x41, LEGACY_BUF_LEN);
ring_data = mmap(NULL, LEGACY_BUF_LEN, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (ring_data == MAP_FAILED) {
perror("mmap ring_data");
return T_EXIT_FAIL;
}
memset(ring_data, 0x42, LEGACY_BUF_LEN);
ring_bytes = (sizeof(struct io_uring_buf_ring) +
RING_ENTRIES * sizeof(struct io_uring_buf) + 4095) & ~4095UL;
br = mmap(NULL, ring_bytes, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (br == MAP_FAILED) {
perror("mmap br");
return T_EXIT_FAIL;
}
memset(br, 0, ring_bytes);
for (i = 0; i < RING_ENTRIES; i++) {
br->bufs[i].addr = (uint64_t)(uintptr_t)ring_data;
br->bufs[i].len = LEGACY_BUF_LEN;
br->bufs[i].bid = (uint16_t)i;
}
br->tail = RING_ENTRIES;
ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, sv);
if (ret < 0) {
perror("socketpair");
return T_EXIT_FAIL;
}
ret = io_uring_queue_init(2, &ring, 0);
if (ret < 0) {
fprintf(stderr, "ring_init=%d\n", ret);
return T_EXIT_FAIL;
}
/* Provide a single legacy buffer for BGID */
sqe = io_uring_get_sqe(&ring);
io_uring_prep_provide_buffers(sqe, legacy_buf, LEGACY_BUF_LEN, 1,
BGID, BID0);
ret = io_uring_submit_and_wait(&ring, 1);
if (ret < 0) {
fprintf(stderr, "submit_and_wait: %d\n", ret);
return T_EXIT_FAIL;
}
ret = io_uring_wait_cqe(&ring, &cqe);
if (ret < 0) {
fprintf(stderr, "wait_cqe=%d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->res < 0) {
io_uring_cqe_seen(&ring, cqe);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(&ring, cqe);
memset(&reg, 0, sizeof(reg));
reg.ring_addr = (uint64_t)(uintptr_t)br;
reg.ring_entries = RING_ENTRIES;
reg.bgid = BGID;
sender = fork();
if (sender < 0)
return T_EXIT_FAIL;
if (sender == 0)
_exit(sender_loop(sv[1], sh));
regger = fork();
if (regger < 0)
return T_EXIT_FAIL;
if (regger == 0)
_exit(reg_loop(ring.ring_fd, &reg, sh));
for (i = 0; i < TRY_ITERS; i++) {
if (g_stop)
break;
sqe = io_uring_get_sqe(&ring);
io_uring_prep_recv(sqe, sv[0], NULL, 1, 0);
sqe->flags = IOSQE_ASYNC | IOSQE_BUFFER_SELECT;
sqe->buf_group = BGID;
ret = io_uring_submit_and_wait(&ring, 1);
if (ret < 0) {
fprintf(stderr, "wait_cqe %d\n", ret);
break;
}
ret = io_uring_wait_cqe(&ring, &cqe);
if (ret < 0) {
fprintf(stderr, "wait_cqe=%d at %u\n", ret, i);
break;
}
io_uring_cqe_seen(&ring, cqe);
if (sh->reg_no_br)
break;
if (sh->reg_ok && reg_seen_iter < 0)
reg_seen_iter = (int)i;
if (reg_seen_iter >= 0 &&
(int)i - reg_seen_iter > POST_REG_ITERS)
break;
}
sh->stop = 1;
if (sender > 0)
waitpid(sender, &st, 0);
if (regger > 0)
waitpid(regger, &st, 0);
io_uring_queue_exit(&ring);
close(sv[0]);
close(sv[1]);
return T_EXIT_PASS;
}
int main(int argc, char *argv[])
{
if (argc > 1)
return T_EXIT_SKIP;
signal(SIGALRM, on_alarm);
alarm(30);
return run_poc();
}