mirror of
https://github.com/openharmony/third_party_liburing.git
synced 2026-07-20 22:58:41 -04:00
4b8f3098a5
Co-authored-by: Pavel Begunkov<asml.silence@gmail.com> Co-authored-by: Guillem Jover<guillem@hadrons.org> Co-authored-by: Jens Axboe<axboe@kernel.dk> Co-authored-by: Khem Raj<raj.khem@gmail.com> Co-authored-by: Michael de Lang<kingoipo@gmail.com> Co-authored-by: David Disseldorp<ddiss@suse.de> Co-authored-by: Ming Lei<ming.lei@redhat.com>
425 lines
9.6 KiB
C
425 lines
9.6 KiB
C
/* SPDX-License-Identifier: MIT */
|
|
/*
|
|
* Description: test timestamp retrieving
|
|
*
|
|
*/
|
|
#include <time.h>
|
|
#include <arpa/inet.h>
|
|
#include <inttypes.h>
|
|
#include <linux/errqueue.h>
|
|
#include <linux/ipv6.h>
|
|
#include <linux/net_tstamp.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/udp.h>
|
|
#include <netinet/tcp.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
|
|
#include "liburing.h"
|
|
#include "helpers.h"
|
|
|
|
/*
|
|
* Only sparc/hppa should have "non-standard" values for SCM_TS_OPT_ID
|
|
*/
|
|
#ifndef SCM_TS_OPT_ID
|
|
#if defined(__sparc__) || defined(__sparc64__)
|
|
#define SCM_TS_OPT_ID 0x005a
|
|
#elif defined(__hppa__)
|
|
#define SCM_TS_OPT_ID 0x404C
|
|
#else
|
|
#define SCM_TS_OPT_ID 81
|
|
#endif
|
|
#endif
|
|
|
|
static const int cfg_payload_len = 10;
|
|
static uint16_t dest_port = 9000;
|
|
static uint32_t ts_opt_id = 81;
|
|
static bool cfg_use_cmsg_opt_id = false;
|
|
static char buffer[128];
|
|
static const bool verbose = false;
|
|
|
|
static struct sockaddr_in6 daddr6;
|
|
|
|
static int saved_tskey = -1;
|
|
static int saved_tskey_type = -1;
|
|
|
|
static int listen_fd;
|
|
|
|
struct ctx {
|
|
int family;
|
|
int proto;
|
|
int report_opt;
|
|
int num_pkts;
|
|
int cqe_mixed;
|
|
};
|
|
|
|
static int validate_key(int tskey, int tstype, struct ctx *ctx)
|
|
{
|
|
int stepsize;
|
|
|
|
/* compare key for each subsequent request
|
|
* must only test for one type, the first one requested
|
|
*/
|
|
if (saved_tskey == -1 || cfg_use_cmsg_opt_id)
|
|
saved_tskey_type = tstype;
|
|
else if (saved_tskey_type != tstype)
|
|
return 0;
|
|
|
|
stepsize = ctx->proto == SOCK_STREAM ? cfg_payload_len : 1;
|
|
stepsize = cfg_use_cmsg_opt_id ? 0 : stepsize;
|
|
if (tskey != saved_tskey + stepsize) {
|
|
fprintf(stderr, "ERROR: key %d, expected %d\n",
|
|
tskey, saved_tskey + stepsize);
|
|
return -EINVAL;
|
|
}
|
|
|
|
saved_tskey = tskey;
|
|
return 0;
|
|
}
|
|
|
|
static int test_prep_sock(int family, int proto, unsigned report_opt)
|
|
{
|
|
int ipproto = proto == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP;
|
|
unsigned int sock_opt;
|
|
int fd, val = 1;
|
|
|
|
fd = socket(family, proto, ipproto);
|
|
if (fd < 0)
|
|
t_error(1, errno, "socket");
|
|
|
|
if (proto == SOCK_STREAM) {
|
|
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
|
|
(char*) &val, sizeof(val)))
|
|
t_error(1, 0, "setsockopt no nagle");
|
|
|
|
if (connect(fd, (void *) &daddr6, sizeof(daddr6)))
|
|
t_error(1, errno, "connect ipv6");
|
|
}
|
|
|
|
sock_opt = SOF_TIMESTAMPING_SOFTWARE |
|
|
SOF_TIMESTAMPING_OPT_CMSG |
|
|
SOF_TIMESTAMPING_OPT_ID;
|
|
sock_opt |= report_opt;
|
|
sock_opt |= SOF_TIMESTAMPING_OPT_TSONLY;
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING,
|
|
(char *) &sock_opt, sizeof(sock_opt)))
|
|
t_error(1, 0, "setsockopt timestamping");
|
|
|
|
return fd;
|
|
}
|
|
|
|
#define MAX_PACKETS 32
|
|
|
|
struct send_req {
|
|
struct msghdr msg;
|
|
struct iovec iov;
|
|
char control[CMSG_SPACE(sizeof(uint32_t))];
|
|
};
|
|
|
|
static void queue_ts_cmd(struct io_uring *ring, int fd)
|
|
{
|
|
struct io_uring_sqe *sqe;
|
|
|
|
sqe = io_uring_get_sqe(ring);
|
|
io_uring_prep_rw(IORING_OP_URING_CMD, sqe, fd, NULL, 0, 0);
|
|
sqe->cmd_op = SOCKET_URING_OP_TX_TIMESTAMP;
|
|
sqe->user_data = 43;
|
|
}
|
|
|
|
static void queue_send(struct io_uring *ring, int fd, void *buf, struct send_req *r,
|
|
int proto)
|
|
{
|
|
struct io_uring_sqe *sqe;
|
|
|
|
r->iov.iov_base = buf;
|
|
r->iov.iov_len = cfg_payload_len;
|
|
|
|
memset(&r->msg, 0, sizeof(r->msg));
|
|
r->msg.msg_iov = &r->iov;
|
|
r->msg.msg_iovlen = 1;
|
|
if (proto == SOCK_STREAM) {
|
|
r->msg.msg_name = (void *)&daddr6;
|
|
r->msg.msg_namelen = sizeof(daddr6);
|
|
}
|
|
|
|
if (cfg_use_cmsg_opt_id) {
|
|
struct cmsghdr *cmsg;
|
|
|
|
memset(r->control, 0, sizeof(r->control));
|
|
r->msg.msg_control = r->control;
|
|
r->msg.msg_controllen = CMSG_SPACE(sizeof(uint32_t));
|
|
|
|
cmsg = CMSG_FIRSTHDR(&r->msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_TS_OPT_ID;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(uint32_t));
|
|
|
|
*((uint32_t *)CMSG_DATA(cmsg)) = ts_opt_id;
|
|
saved_tskey = ts_opt_id;
|
|
}
|
|
|
|
sqe = io_uring_get_sqe(ring);
|
|
io_uring_prep_sendmsg(sqe, fd, &r->msg, 0);
|
|
sqe->user_data = 0;
|
|
}
|
|
|
|
static const char *get_tstype_name(int tstype)
|
|
{
|
|
if (tstype == SCM_TSTAMP_SCHED)
|
|
return "ENQ";
|
|
if (tstype == SCM_TSTAMP_SND)
|
|
return "SND";
|
|
if (tstype == SCM_TSTAMP_ACK)
|
|
return "ACK";
|
|
return "unknown";
|
|
}
|
|
|
|
static int do_test(struct ctx *ctx)
|
|
{
|
|
struct send_req reqs[MAX_PACKETS];
|
|
struct io_uring_cqe *cqe;
|
|
struct io_uring ring;
|
|
unsigned long head;
|
|
int cqes_seen = 0;
|
|
int i, fd, ret;
|
|
int ts_expected = 0, ts_got = 0;
|
|
unsigned ring_flags;
|
|
|
|
ts_expected += !!(ctx->report_opt & SOF_TIMESTAMPING_TX_SCHED);
|
|
ts_expected += !!(ctx->report_opt & SOF_TIMESTAMPING_TX_SOFTWARE);
|
|
ts_expected += !!(ctx->report_opt & SOF_TIMESTAMPING_TX_ACK);
|
|
|
|
if (ctx->cqe_mixed)
|
|
ring_flags = IORING_SETUP_CQE_MIXED;
|
|
else
|
|
ring_flags = IORING_SETUP_CQE32;
|
|
|
|
ret = t_create_ring(32, &ring, ring_flags);
|
|
if (ret == T_SETUP_SKIP)
|
|
return T_EXIT_SKIP;
|
|
else if (ret)
|
|
t_error(1, ret, "queue init\n");
|
|
|
|
assert(ctx->num_pkts <= MAX_PACKETS);
|
|
|
|
fd = test_prep_sock(ctx->family, ctx->proto, ctx->report_opt);
|
|
if (fd < 0)
|
|
t_error(1, fd, "can't create socket\n");
|
|
|
|
memset(buffer, 'a', cfg_payload_len);
|
|
saved_tskey = -1;
|
|
|
|
if (cfg_use_cmsg_opt_id)
|
|
saved_tskey = ts_opt_id;
|
|
|
|
for (i = 0; i < ctx->num_pkts; i++) {
|
|
queue_send(&ring, fd, buffer, &reqs[i], ctx->proto);
|
|
ret = io_uring_submit(&ring);
|
|
if (ret != 1)
|
|
t_error(1, ret, "submit failed");
|
|
|
|
ret = io_uring_wait_cqe(&ring, &cqe);
|
|
if (ret || cqe->res != cfg_payload_len) {
|
|
fprintf(stderr, "wait send cqe, %d %d, expected %d\n",
|
|
ret, cqe->res, cfg_payload_len);
|
|
return T_EXIT_FAIL;
|
|
}
|
|
io_uring_cqe_seen(&ring, cqe);
|
|
}
|
|
|
|
usleep(200000);
|
|
|
|
queue_ts_cmd(&ring, fd);
|
|
ret = io_uring_submit(&ring);
|
|
if (ret != 1)
|
|
t_error(1, ret, "submit failed");
|
|
|
|
ret = io_uring_wait_cqe(&ring, &cqe);
|
|
if (ret) {
|
|
fprintf(stderr, "wait_cqe failed %d\n", ret);
|
|
return T_EXIT_FAIL;
|
|
}
|
|
|
|
io_uring_for_each_cqe(&ring, head, cqe) {
|
|
struct io_timespec *ts;
|
|
int tskey, tstype;
|
|
bool hwts;
|
|
|
|
cqes_seen++;
|
|
if (cqe->flags & IORING_CQE_F_SKIP) {
|
|
if (!ctx->cqe_mixed) {
|
|
t_error(1, 0, "SKIP set for non-mixed");
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!(cqe->flags & IORING_CQE_F_MORE)) {
|
|
if (cqe->res == -EINVAL || cqe->res == -EOPNOTSUPP)
|
|
return T_EXIT_SKIP;
|
|
if (cqe->res)
|
|
t_error(1, 0, "failed cqe %i", cqe->res);
|
|
break;
|
|
}
|
|
if (ctx->cqe_mixed && !(cqe->flags & IORING_CQE_F_32)) {
|
|
t_error(1, 0, "CQE_F_32 not set");
|
|
break;
|
|
}
|
|
cqes_seen++;
|
|
|
|
ts = (void *)(cqe + 1);
|
|
tstype = cqe->flags >> IORING_TIMESTAMP_TYPE_SHIFT;
|
|
tskey = cqe->res;
|
|
hwts = cqe->flags & IORING_CQE_F_TSTAMP_HW;
|
|
|
|
ts_got++;
|
|
if (verbose)
|
|
fprintf(stderr, "ts: key %x, type %i (%s), is hw %i, sec %lu, nsec %lu\n",
|
|
tskey, tstype, get_tstype_name(tstype), hwts,
|
|
(unsigned long)ts->tv_sec,
|
|
(unsigned long)ts->tv_nsec);
|
|
|
|
ret = validate_key(tskey, tstype, ctx);
|
|
if (ret)
|
|
return T_EXIT_FAIL;
|
|
}
|
|
|
|
if (ts_got != ts_expected) {
|
|
fprintf(stderr, "expected %i timestamps, got %i\n",
|
|
ts_expected, ts_got);
|
|
return -EINVAL;
|
|
}
|
|
|
|
close(fd);
|
|
io_uring_cq_advance(&ring, cqes_seen);
|
|
io_uring_queue_exit(&ring);
|
|
return T_EXIT_PASS;
|
|
}
|
|
|
|
static void resolve_hostname(const char *name, int port)
|
|
{
|
|
memset(&daddr6, 0, sizeof(daddr6));
|
|
daddr6.sin6_family = AF_INET6;
|
|
daddr6.sin6_port = htons(port);
|
|
if (inet_pton(AF_INET6, name, &daddr6.sin6_addr) != 1)
|
|
t_error(1, 0, "ipv6 parse error: %s", name);
|
|
}
|
|
|
|
static void do_listen(int family, int type, void *addr, int alen)
|
|
{
|
|
listen_fd = socket(family, type, 0);
|
|
if (listen_fd == -1)
|
|
t_error(1, errno, "socket rx");
|
|
|
|
if (bind(listen_fd, addr, alen))
|
|
t_error(1, errno, "bind rx");
|
|
|
|
if (type == SOCK_STREAM && listen(listen_fd, 10))
|
|
t_error(1, errno, "listen rx");
|
|
|
|
/* leave fd open, will be closed on process exit.
|
|
* this enables connect() to succeed and avoids icmp replies
|
|
*/
|
|
}
|
|
|
|
static int do_main(int family, int proto, int cqe_mixed)
|
|
{
|
|
struct ctx ctx;
|
|
int ret;
|
|
|
|
ctx.num_pkts = 1;
|
|
ctx.family = family;
|
|
ctx.proto = proto;
|
|
ctx.cqe_mixed = cqe_mixed;
|
|
|
|
if (verbose)
|
|
fprintf(stderr, "test SND\n");
|
|
ctx.report_opt = SOF_TIMESTAMPING_TX_SOFTWARE;
|
|
ret = do_test(&ctx);
|
|
if (ret) {
|
|
if (ret == T_EXIT_SKIP)
|
|
fprintf(stderr, "no timestamp cmd, skip\n");
|
|
return ret;
|
|
}
|
|
|
|
if (verbose)
|
|
fprintf(stderr, "test ENQ\n");
|
|
ctx.report_opt = SOF_TIMESTAMPING_TX_SCHED;
|
|
ret = do_test(&ctx);
|
|
if (ret)
|
|
return T_EXIT_FAIL;
|
|
|
|
if (verbose)
|
|
fprintf(stderr, "test ENQ + SND\n");
|
|
ctx.report_opt = SOF_TIMESTAMPING_TX_SCHED | SOF_TIMESTAMPING_TX_SOFTWARE;
|
|
ret = do_test(&ctx);
|
|
if (ret)
|
|
return T_EXIT_FAIL;
|
|
|
|
if (proto == SOCK_STREAM) {
|
|
if (verbose)
|
|
fprintf(stderr, "test ACK\n");
|
|
ctx.report_opt = SOF_TIMESTAMPING_TX_ACK;
|
|
ret = do_test(&ctx);
|
|
if (ret)
|
|
return T_EXIT_FAIL;
|
|
|
|
if (verbose)
|
|
fprintf(stderr, "test SND + ACK\n");
|
|
ctx.report_opt = SOF_TIMESTAMPING_TX_SOFTWARE |
|
|
SOF_TIMESTAMPING_TX_ACK;
|
|
ret = do_test(&ctx);
|
|
if (ret)
|
|
return T_EXIT_FAIL;
|
|
|
|
if (verbose)
|
|
fprintf(stderr, "test ENQ + SND + ACK\n");
|
|
ctx.report_opt = SOF_TIMESTAMPING_TX_SCHED |
|
|
SOF_TIMESTAMPING_TX_SOFTWARE |
|
|
SOF_TIMESTAMPING_TX_ACK;
|
|
ret = do_test(&ctx);
|
|
if (ret)
|
|
return T_EXIT_FAIL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *hostname = "::1";
|
|
int ret;
|
|
|
|
if (argc > 1)
|
|
return T_EXIT_SKIP;
|
|
|
|
resolve_hostname(hostname, dest_port);
|
|
do_listen(PF_INET6, SOCK_STREAM, &daddr6, sizeof(daddr6));
|
|
ret = do_main(PF_INET6, SOCK_STREAM, 0);
|
|
if (ret == T_EXIT_SKIP) {
|
|
return T_EXIT_SKIP;
|
|
} else if (ret != T_EXIT_PASS) {
|
|
fprintf(stderr, "CQE32 test failed\n");
|
|
return ret;
|
|
}
|
|
|
|
close(listen_fd);
|
|
do_listen(PF_INET6, SOCK_STREAM, &daddr6, sizeof(daddr6));
|
|
ret = do_main(PF_INET6, SOCK_STREAM, 1);
|
|
if (ret == T_EXIT_SKIP) {
|
|
return T_EXIT_PASS;
|
|
} else if (ret != T_EXIT_PASS) {
|
|
fprintf(stderr, "CQE32 test failed\n");
|
|
return ret;
|
|
}
|
|
|
|
return T_EXIT_PASS;
|
|
}
|