X-Git-Url: http://rtime.felk.cvut.cz/gitweb/can-utils.git/blobdiff_plain/f1c095d801520586a295d7221428bc5b0e155fe4..HEAD:/candump.c diff --git a/candump.c b/candump.c index 09f455c..c983b71 100644 --- a/candump.c +++ b/candump.c @@ -1,11 +1,7 @@ -/* - * $Id$ - */ - /* * candump.c * - * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * Copyright (c) 2002-2009 Volkswagen Group Electronic Research * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,18 +37,20 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * - * Send feedback to + * Send feedback to * */ #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -63,17 +61,26 @@ #include #include +#include #include "terminal.h" #include "lib.h" +#ifndef SIOCSHWTSTAMP +# define SIOCSHWTSTAMP 0x89b0 +#endif + #define MAXSOCK 16 /* max. number of CAN interfaces given on the cmdline */ -#define MAXFILTER 30 /* max. number of possible filters for each socket */ #define MAXIFNAMES 30 /* size of receive name index to omit ioctls */ #define MAXCOL 6 /* number of different colors for colorized output */ #define ANYDEV "any" /* name of interface to receive from any CAN interface */ #define ANL "\r\n" /* newline in ASC mode */ +#define SILENT_INI 42 /* detect user setting on commandline */ +#define SILENT_OFF 0 /* no silent mode */ +#define SILENT_ANI 1 /* silent mode with animation */ +#define SILENT_ON 2 /* silent mode (completely silent) */ + #define BOLD ATTBOLD #define RED ATTBOLD FGRED #define GREEN ATTBOLD FGGREEN @@ -85,12 +92,17 @@ const char col_on [MAXCOL][19] = {BLUE, RED, GREEN, BOLD, MAGENTA, CYAN}; const char col_off [] = ATTRESET; +static char *cmdlinename[MAXSOCK]; +static __u32 dropcnt[MAXSOCK]; +static __u32 last_dropcnt[MAXSOCK]; static char devname[MAXIFNAMES][IFNAMSIZ+1]; static int dindex[MAXIFNAMES]; static int max_devname_len; /* to prevent frazzled device name output */ +const int canfd_on = 1; #define MAXANI 4 const char anichar[MAXANI] = {'|', '/', '-', '\\'}; +const char extra_m_info[4][4] = {"- -", "B -", "- E", "B E"}; extern int optind, opterr, optopt; @@ -101,21 +113,31 @@ void print_usage(char *prg) fprintf(stderr, "\nUsage: %s [options] +\n", prg); fprintf(stderr, " (use CTRL-C to terminate %s)\n\n", prg); fprintf(stderr, "Options: -t (timestamp: (a)bsolute/(d)elta/(z)ero/(A)bsolute w date)\n"); + fprintf(stderr, " -H (use hardware timestamping)\n"); fprintf(stderr, " -c (increment color mode level)\n"); fprintf(stderr, " -i (binary output - may exceed 80 chars/line)\n"); fprintf(stderr, " -a (enable additional ASCII output)\n"); - fprintf(stderr, " -s (silent mode - 1: animation 2: completely silent)\n"); + fprintf(stderr, " -S (swap byte order in printed CAN data[] - marked with '%c' )\n", SWAP_DELIMITER); + fprintf(stderr, " -s (silent mode - %d: off (default) %d: animation %d: silent)\n", SILENT_OFF, SILENT_ANI, SILENT_ON); fprintf(stderr, " -b (bridge mode - send received frames to )\n"); fprintf(stderr, " -B (bridge mode - like '-b' with disabled loopback)\n"); - fprintf(stderr, " -l (log CAN-frames into file)\n"); + fprintf(stderr, " -u (delay bridge forwarding by microseconds)\n"); + fprintf(stderr, " -l (log CAN-frames into file. Sets '-s %d' by default)\n", SILENT_ON); fprintf(stderr, " -L (use log file format on stdout)\n"); + fprintf(stderr, " -n (terminate after receiption of CAN frames)\n"); + fprintf(stderr, " -r (set socket receive buffer to )\n"); + fprintf(stderr, " -d (monitor dropped CAN frames)\n"); + fprintf(stderr, " -e (dump CAN error frames in human-readable format)\n"); + fprintf(stderr, " -x (print extra message infos, rx/tx brs esi)\n"); + fprintf(stderr, " -T (terminate after without any reception)\n"); fprintf(stderr, "\n"); fprintf(stderr, "Up to %d CAN interfaces with optional filter sets can be specified\n", MAXSOCK); fprintf(stderr, "on the commandline in the form: [,filter]*\n"); - fprintf(stderr, "\nUp to %d comma separated filters can be specified for each given CAN interface:\n", MAXFILTER); + fprintf(stderr, "\nComma separated filters can be specified for each given CAN interface:\n"); fprintf(stderr, " : (matches when & mask == can_id & mask)\n"); fprintf(stderr, " ~ (matches when & mask != can_id & mask)\n"); fprintf(stderr, " # (set error frame filter, see include/linux/can/error.h)\n"); + fprintf(stderr, " [j|J] (join the given CAN filters - logical AND semantic)\n"); fprintf(stderr, "\nCAN IDs, masks and data content are given and expected in hexadecimal values.\n"); fprintf(stderr, "When can_id and can_mask are both 8 digits, they are assumed to be 29 bit EFF.\n"); fprintf(stderr, "Without any given filter all data frames are received ('0:0' default filter).\n"); @@ -124,8 +146,9 @@ void print_usage(char *prg) fprintf(stderr, "%s -c -c -ta can0,123:7FF,400:700,#000000FF can2,400~7F0 can3 can8\n", prg); fprintf(stderr, "%s -l any,0~0,#FFFFFFFF (log only error frames but no(!) data frames)\n", prg); fprintf(stderr, "%s -l any,0:0,#FFFFFFFF (log error frames and also all data frames)\n", prg); - fprintf(stderr, "%s vcan2,92345678:9FFFFFFF (match only for extended CAN ID 12345678)\n", prg); - fprintf(stderr, "%s vcan2,12345678:1FFFFFFF (analog to above due to 8 digit value length)\n", prg); + fprintf(stderr, "%s vcan2,92345678:DFFFFFFF (match only for extended CAN ID 12345678)\n", prg); + fprintf(stderr, "%s vcan2,123:7FF (matches CAN ID 123 - including EFF and RTR frames)\n", prg); + fprintf(stderr, "%s vcan2,123:C00007FF (matches CAN ID 123 - only SFF and non-RTR frames)\n", prg); fprintf(stderr, "\n"); } @@ -160,7 +183,7 @@ int idx2dindex(int ifidx, int socket) { break; if (i == MAXIFNAMES) { - printf("Interface index cache only supports %d interfaces.\n", + fprintf(stderr, "Interface index cache only supports %d interfaces.\n", MAXIFNAMES); exit(1); } @@ -183,44 +206,40 @@ int idx2dindex(int ifidx, int socket) { return i; } -canid_t checkeff(char *ptr, char *nptr) -{ - int len; - - if (nptr) - len = nptr - ptr; - else - len = strlen(ptr); - - if (len == 17 && (ptr[8] == ':' || ptr[8] == '~')) - return CAN_EFF_FLAG; - else - return 0; -} - int main(int argc, char **argv) { fd_set rdfs; int s[MAXSOCK]; int bridge = 0; + useconds_t bridge_delay = 0; unsigned char timestamp = 0; - unsigned char silent = 0; + int hw_timestamp = 0; + unsigned char dropmonitor = 0; + unsigned char extra_msg_info = 0; + unsigned char silent = SILENT_INI; unsigned char silentani = 0; unsigned char color = 0; unsigned char view = 0; unsigned char log = 0; unsigned char logfrmt = 0; + int count = 0; + int rcvbuf_size = 0; int opt, ret; int currmax, numfilter; + int join_filter; char *ptr, *nptr; - canid_t eff; struct sockaddr_can addr; - struct can_filter rfilter[MAXFILTER]; + char ctrlmsg[CMSG_SPACE(sizeof(struct timeval)) + CMSG_SPACE(sizeof(__u32))]; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + struct can_filter *rfilter; can_err_mask_t err_mask; - struct can_frame frame; - int nbytes, i; + struct canfd_frame frame; + int nbytes, i, maxdlen; struct ifreq ifr; struct timeval tv, last_tv; + struct timeval timeout, timeout_config = { 0, 0 }, *timeout_current = NULL; FILE *logfile = NULL; signal(SIGTERM, sigterm); @@ -230,18 +249,22 @@ int main(int argc, char **argv) last_tv.tv_sec = 0; last_tv.tv_usec = 0; - while ((opt = getopt(argc, argv, "t:cias:b:B:lLh?")) != -1) { + while ((opt = getopt(argc, argv, "t:ciaSs:b:B:u:ldxLn:r:heT:H?")) != -1) { switch (opt) { case 't': timestamp = optarg[0]; if ((timestamp != 'a') && (timestamp != 'A') && (timestamp != 'd') && (timestamp != 'z')) { - printf("%s: unknown timestamp mode '%c' - ignored\n", + fprintf(stderr, "%s: unknown timestamp mode '%c' - ignored\n", basename(argv[0]), optarg[0]); timestamp = 0; } break; + case 'H': + hw_timestamp = 1; + break; + case 'c': color++; break; @@ -254,14 +277,26 @@ int main(int argc, char **argv) view |= CANLIB_VIEW_ASCII; break; + case 'S': + view |= CANLIB_VIEW_SWAP; + break; + + case 'e': + view |= CANLIB_VIEW_ERROR; + break; + case 's': silent = atoi(optarg); + if (silent > SILENT_ON) { + print_usage(basename(argv[0])); + exit(1); + } break; case 'b': case 'B': if (strlen(optarg) >= IFNAMSIZ) { - printf("Name of CAN device '%s' is too long!\n\n", optarg); + fprintf(stderr, "Name of CAN device '%s' is too long!\n\n", optarg); return 1; } else { bridge = socket(PF_CAN, SOCK_RAW, CAN_RAW); @@ -280,8 +315,11 @@ int main(int argc, char **argv) return 1; } + /* disable default receive filter on this write-only RAW socket */ + setsockopt(bridge, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); + if (opt == 'B') { - int loopback = 0; + const int loopback = 0; setsockopt(bridge, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback)); @@ -294,14 +332,53 @@ int main(int argc, char **argv) } break; + case 'u': + bridge_delay = (useconds_t)strtoul(optarg, (char **)NULL, 10); + break; + case 'l': log = 1; break; + case 'd': + dropmonitor = 1; + break; + + case 'x': + extra_msg_info = 1; + break; + case 'L': logfrmt = 1; break; + case 'n': + count = atoi(optarg); + if (count < 1) { + print_usage(basename(argv[0])); + exit(1); + } + break; + + case 'r': + rcvbuf_size = atoi(optarg); + if (rcvbuf_size < 1) { + print_usage(basename(argv[0])); + exit(1); + } + break; + + case 'T': + errno = 0; + timeout_config.tv_usec = strtol(optarg, NULL, 0); + if (errno != 0) { + print_usage(basename(argv[0])); + exit(1); + } + timeout_config.tv_sec = timeout_config.tv_usec / 1000; + timeout_config.tv_usec = (timeout_config.tv_usec % 1000) * 1000; + timeout_current = &timeout; + break; default: print_usage(basename(argv[0])); exit(1); @@ -314,10 +391,23 @@ int main(int argc, char **argv) exit(0); } + if (logfrmt && view) { + fprintf(stderr, "Log file format selected: Please disable ASCII/BINARY/SWAP options!\n"); + exit(0); + } + + if (silent == SILENT_INI) { + if (log) { + fprintf(stderr, "Disabled standard output while logging.\n"); + silent = SILENT_ON; /* disable output on stdout */ + } else + silent = SILENT_OFF; /* default output */ + } + currmax = argc - optind; /* find real number of CAN devices */ if (currmax > MAXSOCK) { - printf("More than %d CAN devices given on commandline!\n", MAXSOCK); + fprintf(stderr, "More than %d CAN devices given on commandline!\n", MAXSOCK); return 1; } @@ -336,13 +426,15 @@ int main(int argc, char **argv) return 1; } + cmdlinename[i] = ptr; /* save pointer to cmdline name of this socket */ + if (nptr) nbytes = nptr - ptr; /* interface name is up the first ',' */ else nbytes = strlen(ptr); /* no ',' found => no filter definitions */ if (nbytes >= IFNAMSIZ) { - printf("name of CAN device '%s' is too long!\n", ptr); + fprintf(stderr, "name of CAN device '%s' is too long!\n", ptr); return 1; } @@ -371,43 +463,46 @@ int main(int argc, char **argv) /* found a ',' after the interface name => check for filters */ + /* determine number of filters to alloc the filter space */ + numfilter = 0; + ptr = nptr; + while (ptr) { + numfilter++; + ptr++; /* hop behind the ',' */ + ptr = strchr(ptr, ','); /* exit condition */ + } + + rfilter = malloc(sizeof(struct can_filter) * numfilter); + if (!rfilter) { + fprintf(stderr, "Failed to create filter space!\n"); + return 1; + } + numfilter = 0; err_mask = 0; + join_filter = 0; while (nptr) { ptr = nptr+1; /* hop behind the ',' */ nptr = strchr(ptr, ','); /* update exit condition */ - if (sscanf(ptr, "%lx:%lx", - (long unsigned int *) + if (sscanf(ptr, "%x:%x", &rfilter[numfilter].can_id, - (long unsigned int *) &rfilter[numfilter].can_mask) == 2) { - eff = checkeff(ptr, nptr); - rfilter[numfilter].can_id |= eff; - rfilter[numfilter].can_mask |= eff; + rfilter[numfilter].can_mask &= ~CAN_ERR_FLAG; numfilter++; - } else if (sscanf(ptr, "%lx~%lx", - (long unsigned int *) + } else if (sscanf(ptr, "%x~%x", &rfilter[numfilter].can_id, - (long unsigned int *) &rfilter[numfilter].can_mask) == 2) { rfilter[numfilter].can_id |= CAN_INV_FILTER; - eff = checkeff(ptr, nptr); - rfilter[numfilter].can_id |= eff; - rfilter[numfilter].can_mask |= eff; + rfilter[numfilter].can_mask &= ~CAN_ERR_FLAG; numfilter++; - } else if (sscanf(ptr, "#%lx", - (long unsigned int *)&err_mask) != 1) { - printf("Error in filter option parsing: '%s'\n", ptr); - exit(1); - } - - if (numfilter > MAXFILTER) { - printf("Too many filters specified for '%s'.\n", - ifr.ifr_name); - exit(1); + } else if (*ptr == 'j' || *ptr == 'J') { + join_filter = 1; + } else if (sscanf(ptr, "#%x", &err_mask) != 1) { + fprintf(stderr, "Error in filter option parsing: '%s'\n", ptr); + return 1; } } @@ -415,11 +510,99 @@ int main(int argc, char **argv) setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask)); + if (join_filter && setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_JOIN_FILTERS, + &join_filter, sizeof(join_filter)) < 0) { + perror("setsockopt CAN_RAW_JOIN_FILTERS not supported by your Linux Kernel"); + return 1; + } + if (numfilter) setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FILTER, - &rfilter, numfilter * sizeof(struct can_filter)); + rfilter, numfilter * sizeof(struct can_filter)); + + free(rfilter); + } /* if (nptr) */ + /* try to switch the socket into CAN FD mode */ + setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on)); + + if (rcvbuf_size) { + + int curr_rcvbuf_size; + socklen_t curr_rcvbuf_size_len = sizeof(curr_rcvbuf_size); + + /* try SO_RCVBUFFORCE first, if we run with CAP_NET_ADMIN */ + if (setsockopt(s[i], SOL_SOCKET, SO_RCVBUFFORCE, + &rcvbuf_size, sizeof(rcvbuf_size)) < 0) { +#ifdef DEBUG + printf("SO_RCVBUFFORCE failed so try SO_RCVBUF ...\n"); +#endif + if (setsockopt(s[i], SOL_SOCKET, SO_RCVBUF, + &rcvbuf_size, sizeof(rcvbuf_size)) < 0) { + perror("setsockopt SO_RCVBUF"); + return 1; + } + + if (getsockopt(s[i], SOL_SOCKET, SO_RCVBUF, + &curr_rcvbuf_size, &curr_rcvbuf_size_len) < 0) { + perror("getsockopt SO_RCVBUF"); + return 1; + } + + /* Only print a warning the first time we detect the adjustment */ + /* n.b.: The wanted size is doubled in Linux in net/sore/sock.c */ + if (!i && curr_rcvbuf_size < rcvbuf_size*2) + fprintf(stderr, "The socket receive buffer size was " + "adjusted due to /proc/sys/net/core/rmem_max.\n"); + } + } + + if (timestamp || log || logfrmt) { + if (hw_timestamp == 0) { + const int timestamp_on = 1; + + if (setsockopt(s[i], SOL_SOCKET, SO_TIMESTAMP, + ×tamp_on, sizeof(timestamp_on)) < 0) { + perror("setsockopt SO_TIMESTAMP"); + return 1; + } + } else { + struct ifreq hwtstamp; + struct hwtstamp_config hwconfig; + + memset(&hwtstamp, 0, sizeof(hwtstamp)); + strncpy(hwtstamp.ifr_name, ifr.ifr_name, sizeof(hwtstamp.ifr_name)); + hwtstamp.ifr_data = (void *)&hwconfig; + memset(&hwconfig, 0, sizeof(hwconfig)); + hwconfig.tx_type = HWTSTAMP_TX_OFF; + hwconfig.rx_filter = HWTSTAMP_FILTER_ALL; + + if (ioctl(s[i], SIOCSHWTSTAMP, &hwtstamp) < 0) { + perror("SIOCSHWTSTAMP"); + return 1; + } + + const int so_timestamping_flags = SOF_TIMESTAMPING_RAW_HARDWARE; + + if (setsockopt(s[i], SOL_SOCKET, SO_TIMESTAMPING, + &so_timestamping_flags, + sizeof(so_timestamping_flags)) < 0) + perror("setsockopt SO_TIMESTAMPING"); + } + } + + if (dropmonitor) { + + const int dropmonitor_on = 1; + + if (setsockopt(s[i], SOL_SOCKET, SO_RXQ_OVFL, + &dropmonitor_on, sizeof(dropmonitor_on)) < 0) { + perror("setsockopt SO_RXQ_OVFL not supported by your Linux Kernel"); + return 1; + } + } + if (bind(s[i], (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; @@ -446,7 +629,10 @@ int main(int argc, char **argv) now.tm_min, now.tm_sec); - printf("\nEnabling Logfile '%s'\n\n", fname); + if (silent != SILENT_ON) + printf("\nWarning: console output active while logging!"); + + fprintf(stderr, "\nEnabling Logfile '%s'\n\n", fname); logfile = fopen(fname, "w"); if (!logfile) { @@ -455,13 +641,23 @@ int main(int argc, char **argv) } } + /* these settings are static and can be held out of the hot path */ + iov.iov_base = &frame; + msg.msg_name = &addr; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &ctrlmsg; + while (running) { FD_ZERO(&rdfs); for (i=0; icmsg_level == SOL_SOCKET); + cmsg = CMSG_NXTHDR(&msg,cmsg)) { + if (cmsg->cmsg_type == SO_TIMESTAMP) + tv = *(struct timeval *)CMSG_DATA(cmsg); + else if (cmsg->cmsg_type == SO_RXQ_OVFL) + dropcnt[i] = *(__u32 *)CMSG_DATA(cmsg); + else if (cmsg->cmsg_type == SO_TIMESTAMPING) { + struct timespec *ts = (struct timespec *) CMSG_DATA(cmsg); + tv.tv_sec = ts[2].tv_sec; + tv.tv_usec = ts[2].tv_nsec / 1000; + } + } + + /* check for (unlikely) dropped frames on this specific socket */ + if (dropcnt[i] != last_dropcnt[i]) { + + __u32 frames = dropcnt[i] - last_dropcnt[i]; + + if (silent != SILENT_ON) + printf("DROPCOUNT: dropped %d CAN frame%s on '%s' socket (total drops %d)\n", + frames, (frames > 1)?"s":"", cmdlinename[i], dropcnt[i]); + + if (log) + fprintf(logfile, "DROPCOUNT: dropped %d CAN frame%s on '%s' socket (total drops %d)\n", + frames, (frames > 1)?"s":"", cmdlinename[i], dropcnt[i]); + + last_dropcnt[i] = dropcnt[i]; + } idx = idx2dindex(addr.can_ifindex, s[i]); + /* once we detected a EFF frame indent SFF frames accordingly */ + if (frame.can_id & CAN_EFF_FLAG) + view |= CANLIB_VIEW_INDENT_SFF; + if (log) { + char buf[CL_CFSZ]; /* max length */ + /* log CAN frame with absolute timestamp & device */ - fprintf(logfile, "(%ld.%06ld) ", tv.tv_sec, tv.tv_usec); - fprintf(logfile, "%*s ", max_devname_len, devname[idx]); - /* without seperator as logfile use-case is parsing */ - fprint_canframe(logfile, &frame, "\n", 0); + sprint_canframe(buf, &frame, 0, maxdlen); + fprintf(logfile, "(%010ld.%06ld) %*s %s\n", + tv.tv_sec, tv.tv_usec, + max_devname_len, devname[idx], buf); } if (logfrmt) { + char buf[CL_CFSZ]; /* max length */ + /* print CAN frame in log file style to stdout */ - printf("(%ld.%06ld) ", tv.tv_sec, tv.tv_usec); - printf("%*s ", max_devname_len, devname[idx]); - fprint_canframe(stdout, &frame, "\n", 0); + sprint_canframe(buf, &frame, 0, maxdlen); + printf("(%010ld.%06ld) %*s %s\n", + tv.tv_sec, tv.tv_usec, + max_devname_len, devname[idx], buf); goto out_fflush; /* no other output to stdout */ } - if (silent){ - if (silent == 1) { + if (silent != SILENT_OFF){ + if (silent == SILENT_ANI) { printf("%c\b", anichar[silentani%=MAXANI]); silentani++; } @@ -533,7 +778,7 @@ int main(int argc, char **argv) switch (timestamp) { case 'a': /* absolute with timestamp */ - printf("(%ld.%06ld) ", tv.tv_sec, tv.tv_usec); + printf("(%010ld.%06ld) ", tv.tv_sec, tv.tv_usec); break; case 'A': /* absolute with date */ @@ -560,7 +805,7 @@ int main(int argc, char **argv) diff.tv_sec--, diff.tv_usec += 1000000; if (diff.tv_sec < 0) diff.tv_sec = diff.tv_usec = 0; - printf("(%ld.%06ld) ", diff.tv_sec, diff.tv_usec); + printf("(%03ld.%06ld) ", diff.tv_sec, diff.tv_usec); if (timestamp == 'd') last_tv = tv; /* update for delta calculation */ @@ -573,9 +818,18 @@ int main(int argc, char **argv) printf(" %s", (color && (color<3))?col_on[idx%MAXCOL]:""); printf("%*s", max_devname_len, devname[idx]); + + if (extra_msg_info) { + + if (msg.msg_flags & MSG_DONTROUTE) + printf (" TX %s", extra_m_info[frame.flags & 3]); + else + printf (" RX %s", extra_m_info[frame.flags & 3]); + } + printf("%s ", (color==1)?col_off:""); - fprint_long_canframe(stdout, &frame, NULL, view); + fprint_long_canframe(stdout, &frame, NULL, view, maxdlen); printf("%s", (color>1)?col_off:""); printf("\n");