cangen: add support for SO_TXTIME

Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
pull/389/head
Marc Kleine-Budde 2022-04-21 13:55:47 +02:00
parent 9dff4b6393
commit c6f2cf7c2f
1 changed files with 182 additions and 12 deletions

194
cangen.c
View File

@ -2,6 +2,8 @@
/* /*
* cangen.c - CAN frames generator * cangen.c - CAN frames generator
* *
* Copyright (c) 2022 Pengutronix,
* Marc Kleine-Budde <kernel@pengutronix.de>
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research * Copyright (c) 2002-2007 Volkswagen Group Electronic Research
* All rights reserved. * All rights reserved.
* *
@ -44,7 +46,9 @@
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <getopt.h>
#include <libgen.h> #include <libgen.h>
#include <limits.h>
#include <poll.h> #include <poll.h>
#include <signal.h> #include <signal.h>
#include <stdbool.h> #include <stdbool.h>
@ -64,11 +68,13 @@
#include <linux/can.h> #include <linux/can.h>
#include <linux/can/raw.h> #include <linux/can/raw.h>
#include <linux/net_tstamp.h>
#include "lib.h" #include "lib.h"
#define DEFAULT_GAP 200 /* ms */ #define DEFAULT_GAP 200 /* ms */
#define DEFAULT_BURST_COUNT 1 #define DEFAULT_BURST_COUNT 1
#define DEFAULT_SO_MARK_VAL 1
#define MODE_RANDOM 0 #define MODE_RANDOM 0
#define MODE_INCREMENT 1 #define MODE_INCREMENT 1
@ -81,6 +87,12 @@ extern int optind, opterr, optopt;
static volatile int running = 1; static volatile int running = 1;
static unsigned long long enobufs_count; static unsigned long long enobufs_count;
static bool ignore_enobufs; static bool ignore_enobufs;
static bool use_so_txtime;
static int clockid = CLOCK_TAI;
static int clock_nanosleep_flags;
static struct timespec ts, ts_gap;
static int so_mark_val = DEFAULT_SO_MARK_VAL;
#define NSEC_PER_SEC 1000000000LL #define NSEC_PER_SEC 1000000000LL
@ -151,6 +163,9 @@ static void print_usage(char *prg)
fprintf(stderr, "Options:\n"); fprintf(stderr, "Options:\n");
fprintf(stderr, " -g <ms> (gap in milli seconds - default: %d ms)\n", DEFAULT_GAP); fprintf(stderr, " -g <ms> (gap in milli seconds - default: %d ms)\n", DEFAULT_GAP);
fprintf(stderr, " -a (use absolute time for gap)"); fprintf(stderr, " -a (use absolute time for gap)");
fprintf(stderr, " -t (use SO_TXTIME)");
fprintf(stderr, " --start <ns> (start time (UTC nanoseconds))");
fprintf(stderr, " --mark <id> (set SO_MARK to <id>, default %u)", DEFAULT_SO_MARK_VAL);
fprintf(stderr, " -e (generate extended frame mode (EFF) CAN frames)\n"); fprintf(stderr, " -e (generate extended frame mode (EFF) CAN frames)\n");
fprintf(stderr, " -f (generate CAN FD CAN frames)\n"); fprintf(stderr, " -f (generate CAN FD CAN frames)\n");
fprintf(stderr, " -b (generate CAN FD CAN frames with bitrate switch (BRS))\n"); fprintf(stderr, " -b (generate CAN FD CAN frames with bitrate switch (BRS))\n");
@ -198,8 +213,78 @@ static void sigterm(int signo)
running = 0; running = 0;
} }
static int setsockopt_txtime(int fd)
{
const struct sock_txtime so_txtime_val = {
.clockid = clockid,
.flags = SOF_TXTIME_REPORT_ERRORS,
};
struct sock_txtime so_txtime_val_read;
int so_mark_val_read;
socklen_t vallen;
int ret;
/* SO_TXTIME */
ret = setsockopt(fd, SOL_SOCKET, SO_TXTIME,
&so_txtime_val, sizeof(so_txtime_val));
if (ret) {
int err = errno;
perror("setsockopt() SO_TXTIME");
if (err == EPERM)
fprintf(stderr, "Run with CAP_NET_ADMIN or as root.\n");
return -err;
};
vallen = sizeof(so_txtime_val_read);
ret = getsockopt(fd, SOL_SOCKET, SO_TXTIME,
&so_txtime_val_read, &vallen);
if (ret) {
perror("getsockopt() SO_TXTIME");
return -errno;
};
if (vallen != sizeof(so_txtime_val) ||
memcmp(&so_txtime_val, &so_txtime_val_read, vallen)) {
perror("getsockopt() SO_TXTIME: mismatch");
return -EINVAL;
}
/* SO_MARK */
ret = setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark_val, sizeof(so_mark_val));
if (ret) {
int err = errno;
perror("setsockopt() SO_MARK");
if (err == EPERM)
fprintf(stderr, "Run with CAP_NET_ADMIN or as root.\n");
return -err;
};
vallen = sizeof(so_mark_val_read);
ret = getsockopt(fd, SOL_SOCKET, SO_MARK,
&so_mark_val_read, &vallen);
if (ret) {
perror("getsockopt() SO_MARK");
return -errno;
};
if (vallen != sizeof(so_mark_val) ||
memcmp(&so_mark_val, &so_mark_val_read, vallen)) {
perror("getsockopt() SO_MARK: mismatch");
return -EINVAL;
}
return 0;
}
static int do_send_one(int fd, void *buf, size_t len, int timeout) static int do_send_one(int fd, void *buf, size_t len, int timeout)
{ {
uint8_t control[CMSG_SPACE(sizeof(uint64_t))] = { 0 };
struct iovec iov = { struct iovec iov = {
.iov_base = buf, .iov_base = buf,
.iov_len = len, .iov_len = len,
@ -211,6 +296,23 @@ static int do_send_one(int fd, void *buf, size_t len, int timeout)
ssize_t nbytes; ssize_t nbytes;
int ret; int ret;
if (use_so_txtime) {
struct cmsghdr *cm;
uint64_t tdeliver;
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
tdeliver = ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec;
ts = timespec_add(ts, ts_gap);
cm = CMSG_FIRSTHDR(&msg);
cm->cmsg_level = SOL_SOCKET;
cm->cmsg_type = SCM_TXTIME;
cm->cmsg_len = CMSG_LEN(sizeof(tdeliver));
memcpy(CMSG_DATA(cm), &tdeliver, sizeof(tdeliver));
}
resend: resend:
nbytes = sendmsg(fd, &msg, 0); nbytes = sendmsg(fd, &msg, 0);
if (nbytes < 0) { if (nbytes < 0) {
@ -249,6 +351,53 @@ static int do_send_one(int fd, void *buf, size_t len, int timeout)
return 0; return 0;
} }
static int setup_time(void)
{
int ret;
if (use_so_txtime) {
/* start time is defined */
if (ts.tv_sec || ts.tv_nsec)
return 0;
/* start time is now .... */
ret = clock_gettime(clockid, &ts);
if (ret) {
perror("clock_gettime");
return ret;
}
/* ... + gap */
ts = timespec_add(ts, ts_gap);
return 0;
}
if (ts.tv_sec || ts.tv_nsec) {
ret = clock_nanosleep(clockid, TIMER_ABSTIME, &ts, NULL);
if (ret != 0 && ret != EINTR) {
perror("clock_nanosleep");
return ret;
}
} else if (clock_nanosleep_flags == TIMER_ABSTIME) {
ret = clock_gettime(clockid, &ts);
if (ret)
perror("clock_gettime");
return ret;
}
if (clock_nanosleep_flags != TIMER_ABSTIME)
ts = ts_gap;
return 0;
}
enum {
OPT_MARK = UCHAR_MAX + 1,
OPT_START = UCHAR_MAX + 2,
};
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
double gap = DEFAULT_GAP; double gap = DEFAULT_GAP;
@ -283,9 +432,7 @@ int main(int argc, char **argv)
int i; int i;
struct ifreq ifr; struct ifreq ifr;
struct timespec ts, ts_gap;
struct timeval now; struct timeval now;
int clock_nanosleep_flags = 0;
int ret; int ret;
/* set seed value for pseudo random numbers */ /* set seed value for pseudo random numbers */
@ -296,7 +443,13 @@ int main(int argc, char **argv)
signal(SIGHUP, sigterm); signal(SIGHUP, sigterm);
signal(SIGINT, sigterm); signal(SIGINT, sigterm);
while ((opt = getopt(argc, argv, "g:aefbER8mI:L:D:p:n:ixc:vh?")) != -1) { const struct option long_options[] = {
{ "mark", required_argument, 0, OPT_MARK, },
{ "start", required_argument, 0, OPT_START, },
{ 0, 0, 0, 0 },
};
while ((opt = getopt_long(argc, argv, "g:atefbER8mI:L:D:p:n:ixc:vh?", long_options, NULL)) != -1) {
switch (opt) { switch (opt) {
case 'g': case 'g':
@ -307,6 +460,22 @@ int main(int argc, char **argv)
clock_nanosleep_flags = TIMER_ABSTIME; clock_nanosleep_flags = TIMER_ABSTIME;
break; break;
case 't':
clock_nanosleep_flags = TIMER_ABSTIME;
use_so_txtime = true;
break;
case OPT_START: {
int64_t start_time_ns;
start_time_ns = strtoll(optarg, NULL, 0);
ts = ns_to_timespec(start_time_ns);
break;
}
case OPT_MARK:
so_mark_val = strtoul(optarg, NULL, 0);
break;
case 'e': case 'e':
extended = 1; extended = 1;
break; break;
@ -501,16 +670,16 @@ int main(int argc, char **argv)
return 1; return 1;
} }
if (clock_nanosleep_flags == TIMER_ABSTIME) { if (use_so_txtime) {
ret = clock_gettime(CLOCK_MONOTONIC, &ts); ret = setsockopt_txtime(s);
if (ret) { if (ret)
perror("clock_gettime");
return 1; return 1;
}
} else {
ts = ts_gap;
} }
ret = setup_time();
if (ret)
return 1;
while (running) { while (running) {
frame.flags = 0; frame.flags = 0;
@ -587,12 +756,13 @@ int main(int argc, char **argv)
if (frame.len < maxdlen) if (frame.len < maxdlen)
memset(&frame.data[frame.len], 0, maxdlen - frame.len); memset(&frame.data[frame.len], 0, maxdlen - frame.len);
if ((ts.tv_sec || ts.tv_nsec) && if (!use_so_txtime &&
(ts.tv_sec || ts.tv_nsec) &&
burst_sent_count >= burst_count) { burst_sent_count >= burst_count) {
if (clock_nanosleep_flags == TIMER_ABSTIME) if (clock_nanosleep_flags == TIMER_ABSTIME)
ts = timespec_add(ts, ts_gap); ts = timespec_add(ts, ts_gap);
ret = clock_nanosleep(CLOCK_MONOTONIC, clock_nanosleep_flags, &ts, NULL); ret = clock_nanosleep(clockid, clock_nanosleep_flags, &ts, NULL);
if (ret != 0 && ret != EINTR) { if (ret != 0 && ret != EINTR) {
perror("clock_nanosleep"); perror("clock_nanosleep");
return 1; return 1;