From 1ae2f4a304ff324493612ad590aba155ec99d4ea Mon Sep 17 00:00:00 2001 From: Peter van der Perk Date: Mon, 15 Jun 2020 12:29:01 +0200 Subject: [PATCH] Ported candump & cansend apps from can-utils (https://github.com/linux-can/can-utils) --- canutils/candump/Kconfig | 16 + canutils/candump/Make.defs | 24 + canutils/candump/Makefile | 34 ++ canutils/candump/candump.c | 809 ++++++++++++++++++++++++++++++++ canutils/cansend/Kconfig | 16 + canutils/cansend/Make.defs | 24 + canutils/cansend/Makefile | 34 ++ canutils/cansend/cansend.c | 169 +++++++ canutils/libcanutils/Kconfig | 16 + canutils/libcanutils/Make.defs | 23 + canutils/libcanutils/Makefile | 28 ++ canutils/libcanutils/lib.c | 609 ++++++++++++++++++++++++ canutils/libcanutils/lib.h | 222 +++++++++ canutils/libcanutils/terminal.h | 95 ++++ 14 files changed, 2119 insertions(+) create mode 100644 canutils/candump/Kconfig create mode 100644 canutils/candump/Make.defs create mode 100644 canutils/candump/Makefile create mode 100644 canutils/candump/candump.c create mode 100644 canutils/cansend/Kconfig create mode 100644 canutils/cansend/Make.defs create mode 100644 canutils/cansend/Makefile create mode 100644 canutils/cansend/cansend.c create mode 100644 canutils/libcanutils/Kconfig create mode 100644 canutils/libcanutils/Make.defs create mode 100644 canutils/libcanutils/Makefile create mode 100644 canutils/libcanutils/lib.c create mode 100644 canutils/libcanutils/lib.h create mode 100644 canutils/libcanutils/terminal.h diff --git a/canutils/candump/Kconfig b/canutils/candump/Kconfig new file mode 100644 index 000000000..0ba3eb056 --- /dev/null +++ b/canutils/candump/Kconfig @@ -0,0 +1,16 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config CANUTILS_CANDUMP + tristate "SocketCAN candump tool" + default n + depends on NET_CAN + select CANUTILS_LIBCANUTILS + ---help--- + Enable the SocketCAN candump tool ported from + https://github.com/linux-can/can-utils + +if CANUTILS_CANDUMP +endif diff --git a/canutils/candump/Make.defs b/canutils/candump/Make.defs new file mode 100644 index 000000000..bf15862fe --- /dev/null +++ b/canutils/candump/Make.defs @@ -0,0 +1,24 @@ +############################################################################ +# apps/canutils/candump/Make.defs +# Adds selected applications to apps/ build +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################/ + +ifneq ($(CONFIG_CANUTILS_CANDUMP),) +CONFIGURED_APPS += $(APPDIR)/canutils/candump +endif diff --git a/canutils/candump/Makefile b/canutils/candump/Makefile new file mode 100644 index 000000000..97b0d09a5 --- /dev/null +++ b/canutils/candump/Makefile @@ -0,0 +1,34 @@ +############################################################################ +# apps/canutils/candump/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################/ + +-include $(TOPDIR)/Make.defs + +# SocketCAN userspace utilities and tools candump tool +# https://github.com/linux-can/can-utils/blob/master/candump.c + +PROGNAME = candump +PRIORITY = SCHED_PRIORITY_DEFAULT +STACKSIZE = 3072 +MODULE = $(CONFIG_CANUTILS_CANDUMP) + +CFLAGS += ${shell $(INCDIR) "$(CC)" $(APPDIR)/canutils/libcanutils} +MAINSRC = candump.c + +include $(APPDIR)/Application.mk diff --git a/canutils/candump/candump.c b/canutils/candump/candump.c new file mode 100644 index 000000000..c0f6bd323 --- /dev/null +++ b/canutils/candump/candump.c @@ -0,0 +1,809 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * candump.c + * + * Copyright (c) 2002-2009 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "terminal.h" +#include "lib.h" + +/* for hardware timestamps - since Linux 2.6.30 */ +#ifndef SO_TIMESTAMPING +#define SO_TIMESTAMPING 37 +#endif + +/* from #include - since Linux 2.6.30 */ +#define SOF_TIMESTAMPING_SOFTWARE (1<<4) +#define SOF_TIMESTAMPING_RX_SOFTWARE (1<<3) +#define SOF_TIMESTAMPING_RAW_HARDWARE (1<<6) + +#define MAXSOCK 16 /* max. number of CAN interfaces given on the cmdline */ +#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 +#define YELLOW ATTBOLD FGYELLOW +#define BLUE ATTBOLD FGBLUE +#define MAGENTA ATTBOLD FGMAGENTA +#define CYAN ATTBOLD FGCYAN + +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; + +static volatile int running = 1; + +void print_usage(char *prg) +{ + fprintf(stderr, "%s - dump CAN bus traffic.\n", prg); + fprintf(stderr, "\nUsage: %s [options] +\n", prg); + fprintf(stderr, " (use CTRL-C to terminate %s)\n\n", prg); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -t (timestamp: (a)bsolute/(d)elta/(z)ero/(A)bsolute w date)\n"); + fprintf(stderr, " -H (read hardware timestamps instead of system timestamps)\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 (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, " -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 reception of CAN frames)\n"); + fprintf(stderr, " -r (set socket receive buffer to )\n"); + fprintf(stderr, " -D (Don't exit if a \"detected\" can device goes down.\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, "\nFilters:\n"); + fprintf(stderr, " Comma separated filters can be specified for each given CAN interface:\n"); + fprintf(stderr, " :\n (matches when & mask == can_id & mask)\n"); + fprintf(stderr, " ~\n (matches when & mask != can_id & mask)\n"); + fprintf(stderr, " #\n (set error frame filter, see include/linux/can/error.h)\n"); + fprintf(stderr, " [j|J]\n (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 the can_id is 8 digits long the CAN_EFF_FLAG is set for 29 bit EFF format.\n"); + fprintf(stderr, "Without any given filter all data frames are received ('0:0' default filter).\n"); + fprintf(stderr, "\nUse interface name '%s' to receive from all CAN interfaces.\n", ANYDEV); + fprintf(stderr, "\nExamples:\n"); + fprintf(stderr, "%s -c -c -ta can0,123:7FF,400:700,#000000FF can2,400~7F0 can3 can8\n\n", prg); + fprintf(stderr, "%s -l any,0~0,#FFFFFFFF\n (log only error frames but no(!) data frames)\n", prg); + fprintf(stderr, "%s -l any,0:0,#FFFFFFFF\n (log error frames and also all data frames)\n", prg); + fprintf(stderr, "%s vcan2,12345678:DFFFFFFF\n (match only for extended CAN ID 12345678)\n", prg); + fprintf(stderr, "%s vcan2,123:7FF\n (matches CAN ID 123 - including EFF and RTR frames)\n", prg); + fprintf(stderr, "%s vcan2,123:C00007FF\n (matches CAN ID 123 - only SFF and non-RTR frames)\n", prg); + fprintf(stderr, "\n"); +} + +void sigterm(int signo) +{ + running = 0; +} + +int idx2dindex(int ifidx, int socket) { + + int i; + struct ifreq ifr; + + for (i=0; i < MAXIFNAMES; i++) { + if (dindex[i] == ifidx) + return i; + } + + /* create new interface index cache entry */ + + /* remove index cache zombies first */ + for (i=0; i < MAXIFNAMES; i++) { + if (dindex[i]) { + ifr.ifr_ifindex = dindex[i]; + if (ioctl(socket, SIOCGIFNAME, &ifr) < 0) + dindex[i] = 0; + } + } + + for (i=0; i < MAXIFNAMES; i++) + if (!dindex[i]) /* free entry */ + break; + + if (i == MAXIFNAMES) { + fprintf(stderr, "Interface index cache only supports %d interfaces.\n", + MAXIFNAMES); + exit(1); + } + + dindex[i] = ifidx; + + ifr.ifr_ifindex = ifidx; + if (ioctl(socket, SIOCGIFNAME, &ifr) < 0) + perror("SIOCGIFNAME"); + + if (max_devname_len < (int)strlen(ifr.ifr_name)) + max_devname_len = strlen(ifr.ifr_name); + + strcpy(devname[i], ifr.ifr_name); + +#ifdef DEBUG + printf("new index %d (%s)\n", i, devname[i]); +#endif + + return i; +} + +int main(int argc, char **argv) +{ + fd_set rdfs; + int s[MAXSOCK]; + unsigned char timestamp = 0; + unsigned char hwtimestamp = 0; + unsigned char down_causes_exit = 1; + 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; + struct sockaddr_can addr; + char ctrlmsg[CMSG_SPACE(sizeof(struct timeval) + 3*sizeof(struct timespec) + sizeof(__u32))]; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + struct can_filter *rfilter; + can_err_mask_t err_mask; + 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; + +#if 0 /* NuttX doesn't support these signals */ + signal(SIGTERM, sigterm); + signal(SIGHUP, sigterm); +#endif + signal(SIGINT, sigterm); + + last_tv.tv_sec = 0; + last_tv.tv_usec = 0; + + while ((opt = getopt(argc, argv, "t:HciaSs:lDdxLn:r:heT:?")) != -1) { + switch (opt) { + case 't': + timestamp = optarg[0]; + if ((timestamp != 'a') && (timestamp != 'A') && + (timestamp != 'd') && (timestamp != 'z')) { + fprintf(stderr, "%s: unknown timestamp mode '%c' - ignored\n", + basename(argv[0]), optarg[0]); + timestamp = 0; + } + break; + + case 'H': + hwtimestamp = 1; + break; + + case 'c': + color++; + break; + + case 'i': + view |= CANLIB_VIEW_BINARY; + break; + + case 'a': + 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 'l': + log = 1; + break; + + case 'D': + down_causes_exit = 0; + 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); + break; + } + } + + if (optind == argc) { + print_usage(basename(argv[0])); + 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) { + fprintf(stderr, "More than %d CAN devices given on commandline!\n", MAXSOCK); + return 1; + } + + for (i=0; i < currmax; i++) { + + ptr = argv[optind+i]; + nptr = strchr(ptr, ','); + +#ifdef DEBUG + printf("open %d '%s'.\n", i, ptr); +#endif + + s[i] = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (s[i] < 0) { + perror("socket"); + 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) { + fprintf(stderr, "name of CAN device '%s' is too long!\n", ptr); + return 1; + } + + if (nbytes > max_devname_len) + max_devname_len = nbytes; /* for nice printing */ + + addr.can_family = AF_CAN; + + memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name)); + strncpy(ifr.ifr_name, ptr, nbytes); + +#ifdef DEBUG + printf("using interface name '%s'.\n", ifr.ifr_name); +#endif + + if (strcmp(ANYDEV, ifr.ifr_name)) { + if (ioctl(s[i], SIOCGIFINDEX, &ifr) < 0) { + perror("SIOCGIFINDEX"); + exit(1); + } + addr.can_ifindex = ifr.ifr_ifindex; + } else + addr.can_ifindex = 0; /* any can interface */ + + if (nptr) { + + /* 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, "%x:%x", + &rfilter[numfilter].can_id, + &rfilter[numfilter].can_mask) == 2) { + rfilter[numfilter].can_mask &= ~CAN_ERR_FLAG; + if (*(ptr+8) == ':') + rfilter[numfilter].can_id |= CAN_EFF_FLAG; + numfilter++; + } else if (sscanf(ptr, "%x~%x", + &rfilter[numfilter].can_id, + &rfilter[numfilter].can_mask) == 2) { + rfilter[numfilter].can_id |= CAN_INV_FILTER; + rfilter[numfilter].can_mask &= ~CAN_ERR_FLAG; + if (*(ptr+8) == '~') + rfilter[numfilter].can_id |= CAN_EFF_FLAG; + numfilter++; + } 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; + } + } + + if (err_mask) + 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)); + + 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 (hwtimestamp) { + const int timestamping_flags = (SOF_TIMESTAMPING_SOFTWARE | \ + SOF_TIMESTAMPING_RX_SOFTWARE | \ + SOF_TIMESTAMPING_RAW_HARDWARE); + + if (setsockopt(s[i], SOL_SOCKET, SO_TIMESTAMPING, + ×tamping_flags, sizeof(timestamping_flags)) < 0) { + perror("setsockopt SO_TIMESTAMPING is not supported by your Linux kernel"); + return 1; + } + } else { + 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; + } + } + } + + 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; + } + } + + if (log) { + time_t currtime; + struct tm now; + char fname[83]; /* suggested by -Wformat-overflow= */ + + if (time(&currtime) == (time_t)-1) { + perror("time"); + return 1; + } + + localtime_r(&currtime, &now); + + sprintf(fname, "candump-%04d-%02d-%02d_%02d%02d%02d.log", + now.tm_year + 1900, + now.tm_mon + 1, + now.tm_mday, + now.tm_hour, + now.tm_min, + now.tm_sec); + + if (silent != SILENT_ON) + fprintf(stderr, "Warning: Console output active while logging!\n"); + + fprintf(stderr, "Enabling Logfile '%s'\n", fname); + + logfile = fopen(fname, "w"); + if (!logfile) { + perror("logfile"); + return 1; + } + } + + /* 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) { + memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv)); + } else if (cmsg->cmsg_type == SO_TIMESTAMPING) { + + struct timespec *stamp = (struct timespec *)CMSG_DATA(cmsg); + + /* + * stamp[0] is the software timestamp + * stamp[1] is deprecated + * stamp[2] is the raw hardware timestamp + * See chapter 2.1.2 Receive timestamps in + * linux/Documentation/networking/timestamping.txt + */ + tv.tv_sec = stamp[2].tv_sec; + tv.tv_usec = stamp[2].tv_nsec/1000; + } else if (cmsg->cmsg_type == SO_RXQ_OVFL) + memcpy(&dropcnt[i], CMSG_DATA(cmsg), sizeof(__u32)); + } + + /* 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":"", devname[idx], dropcnt[i]); + + if (log) + fprintf(logfile, "DROPCOUNT: dropped %d CAN frame%s on '%s' socket (total drops %d)\n", + frames, (frames > 1)?"s":"", devname[idx], dropcnt[i]); + + last_dropcnt[i] = dropcnt[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 */ + 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) && (silent == SILENT_OFF)){ + char buf[CL_CFSZ]; /* max length */ + + /* print CAN frame in log file style to stdout */ + 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 != SILENT_OFF){ + if (silent == SILENT_ANI) { + printf("%c\b", anichar[silentani%=MAXANI]); + silentani++; + } + goto out_fflush; /* no other output to stdout */ + } + + printf(" %s", (color>2)?col_on[idx%MAXCOL]:""); + + switch (timestamp) { + + case 'a': /* absolute with timestamp */ + printf("(%010ld.%06ld) ", tv.tv_sec, tv.tv_usec); + break; + + case 'A': /* absolute with date */ + { + struct tm tm; + char timestring[25]; + + tm = *localtime(&tv.tv_sec); + strftime(timestring, 24, "%Y-%m-%d %H:%M:%S", &tm); + printf("(%s.%06ld) ", timestring, tv.tv_usec); + } + break; + + case 'd': /* delta */ + case 'z': /* starting with zero */ + { + struct timeval diff; + + if (last_tv.tv_sec == 0) /* first init */ + last_tv = tv; + diff.tv_sec = tv.tv_sec - last_tv.tv_sec; + diff.tv_usec = tv.tv_usec - last_tv.tv_usec; + if (diff.tv_usec < 0) + diff.tv_sec--, diff.tv_usec += 1000000; + if (diff.tv_sec < 0) + diff.tv_sec = diff.tv_usec = 0; + printf("(%03ld.%06ld) ", diff.tv_sec, diff.tv_usec); + + if (timestamp == 'd') + last_tv = tv; /* update for delta calculation */ + } + break; + + default: /* no timestamp output */ + break; + } + + 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, maxdlen); + + printf("%s", (color>1)?col_off:""); + printf("\n"); + } + + out_fflush: + fflush(stdout); + } + } + + for (i=0; i + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "lib.h" + +void print_usage_send(char *prg) +{ + fprintf(stderr, "%s - send CAN-frames via CAN_RAW sockets.\n", prg); + fprintf(stderr, "\nUsage: %s .\n", prg); + fprintf(stderr, "\n:\n"); + fprintf(stderr, " #{data} for 'classic' CAN 2.0 data frames\n"); + fprintf(stderr, " #R{len} for 'classic' CAN 2.0 data frames\n"); + fprintf(stderr, " ##{data} for CAN FD frames\n\n"); + fprintf(stderr, ":\n" + " 3 (SFF) or 8 (EFF) hex chars\n"); + fprintf(stderr, "{data}:\n" + " 0..8 (0..64 CAN FD) ASCII hex-values (optionally separated by '.')\n"); + fprintf(stderr, "{len}:\n" + " an optional 0..8 value as RTR frames can contain a valid dlc field\n"); + fprintf(stderr, ":\n" + " a single ASCII Hex value (0 .. F) which defines canfd_frame.flags\n\n"); + fprintf(stderr, "Examples:\n"); + fprintf(stderr, " 5A1#11.2233.44556677.88 / 123#DEADBEEF / 5AA# / 123##1 / 213##311223344 /\n" + " 1F334455#1122334455667788 / 123#R / 00000123#R3\n\n"); +} + + + +int main(int argc, char **argv) +{ + int s; /* can raw socket */ + int required_mtu; + int mtu; + int enable_canfd = 1; + struct sockaddr_can addr; + struct canfd_frame frame; + struct ifreq ifr; + + /* check command line options */ + if (argc != 3) { + print_usage_send(argv[0]); + return 1; + } + + /* parse CAN frame */ + required_mtu = parse_canframe(argv[2], &frame); + if (!required_mtu){ + fprintf(stderr, "\nWrong CAN-frame format!\n\n"); + print_usage_send(argv[0]); + return 1; + } + + /* open socket */ + if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) { + perror("socket"); + return 1; + } + + strncpy(ifr.ifr_name, argv[1], IFNAMSIZ - 1); + ifr.ifr_name[IFNAMSIZ - 1] = '\0'; + ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name); + if (!ifr.ifr_ifindex) { + perror("if_nametoindex"); + return 1; + } + + memset(&addr, 0, sizeof(addr)); + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + + if (required_mtu > (int)CAN_MTU) { + + /* check if the frame fits into the CAN netdevice */ + if (ioctl(s, SIOCGIFMTU, &ifr) < 0) { + perror("SIOCGIFMTU"); + return 1; + } + mtu = ifr.ifr_mtu; + + if (mtu != CANFD_MTU) { + printf("CAN interface is not CAN FD capable - sorry.\n"); + return 1; + } + + /* interface is ok - try to switch the socket into CAN FD mode */ + if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, + &enable_canfd, sizeof(enable_canfd))){ + printf("error when enabling CAN FD support\n"); + return 1; + } + + /* ensure discrete CAN FD length values 0..8, 12, 16, 20, 24, 32, 64 */ + frame.len = can_dlc2len(can_len2dlc(frame.len)); + } + + /* disable default receive filter on this RAW socket */ + /* This is obsolete as we do not read from the socket at all, but for */ + /* this reason we can remove the receive list in the Kernel to save a */ + /* little (really a very little!) CPU usage. */ + setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); + + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bind"); + return 1; + } + + /* send frame */ + if (write(s, &frame, required_mtu) != required_mtu) { + perror("write"); + return 1; + } + + close(s); + + return 0; +} diff --git a/canutils/libcanutils/Kconfig b/canutils/libcanutils/Kconfig new file mode 100644 index 000000000..922fb1f30 --- /dev/null +++ b/canutils/libcanutils/Kconfig @@ -0,0 +1,16 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config CANUTILS_LIBCANUTILS + bool "CAN-utils support library" + default n + depends on NET_CAN + ---help--- + Enable the CAN-utils support library ported from + https://github.com/linux-can/can-utils + +if CANUTILS_LIBCANUTILS + +endif diff --git a/canutils/libcanutils/Make.defs b/canutils/libcanutils/Make.defs new file mode 100644 index 000000000..7f6c7bca0 --- /dev/null +++ b/canutils/libcanutils/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/canutils/libcanutils/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################/ + +ifeq ($(CONFIG_CANUTILS_LIBCANUTILS),y) +CONFIGURED_APPS += $(APPDIR)/canutils/libcanutils +endif diff --git a/canutils/libcanutils/Makefile b/canutils/libcanutils/Makefile new file mode 100644 index 000000000..01e7022e6 --- /dev/null +++ b/canutils/libcanutils/Makefile @@ -0,0 +1,28 @@ +############################################################################ +# apps/canutils/libcanutils/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################/ + +include $(TOPDIR)/Make.defs + +# SocketCAN userspace utilities and tools library +# https://github.com/linux-can/can-utils + +CSRCS = lib.c + +include $(APPDIR)/Application.mk diff --git a/canutils/libcanutils/lib.c b/canutils/libcanutils/lib.c new file mode 100644 index 000000000..e8a645fcd --- /dev/null +++ b/canutils/libcanutils/lib.c @@ -0,0 +1,609 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * lib.c - library for command line tools + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to + * + */ + +#include +#include +#include + +#include /* for sa_family_t */ +#include +#include +#include + +#include "lib.h" + +#define CANID_DELIM '#' +#define DATA_SEPERATOR '.' + +const char hex_asc_upper[] = "0123456789ABCDEF"; + +#define hex_asc_upper_lo(x) hex_asc_upper[((x) & 0x0F)] +#define hex_asc_upper_hi(x) hex_asc_upper[((x) & 0xF0) >> 4] + +static inline void put_hex_byte(char *buf, __u8 byte) +{ + buf[0] = hex_asc_upper_hi(byte); + buf[1] = hex_asc_upper_lo(byte); +} + +static inline void _put_id(char *buf, int end_offset, canid_t id) +{ + /* build 3 (SFF) or 8 (EFF) digit CAN identifier */ + while (end_offset >= 0) { + buf[end_offset--] = hex_asc_upper_lo(id); + id >>= 4; + } +} + +#define put_sff_id(buf, id) _put_id(buf, 2, id) +#define put_eff_id(buf, id) _put_id(buf, 7, id) + +/* CAN DLC to real data length conversion helpers */ + +static const unsigned char dlc2len[] = {0, 1, 2, 3, 4, 5, 6, 7, + 8, 12, 16, 20, 24, 32, 48, 64}; + +/* get data length from can_dlc with sanitized can_dlc */ +unsigned char can_dlc2len(unsigned char can_dlc) +{ + return dlc2len[can_dlc & 0x0F]; +} + +static const unsigned char len2dlc[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, /* 0 - 8 */ + 9, 9, 9, 9, /* 9 - 12 */ + 10, 10, 10, 10, /* 13 - 16 */ + 11, 11, 11, 11, /* 17 - 20 */ + 12, 12, 12, 12, /* 21 - 24 */ + 13, 13, 13, 13, 13, 13, 13, 13, /* 25 - 32 */ + 14, 14, 14, 14, 14, 14, 14, 14, /* 33 - 40 */ + 14, 14, 14, 14, 14, 14, 14, 14, /* 41 - 48 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 49 - 56 */ + 15, 15, 15, 15, 15, 15, 15, 15}; /* 57 - 64 */ + +/* map the sanitized data length to an appropriate data length code */ +unsigned char can_len2dlc(unsigned char len) +{ + if (len > 64) + return 0xF; + + return len2dlc[len]; +} + +unsigned char asc2nibble(char c) { + + if ((c >= '0') && (c <= '9')) + return c - '0'; + + if ((c >= 'A') && (c <= 'F')) + return c - 'A' + 10; + + if ((c >= 'a') && (c <= 'f')) + return c - 'a' + 10; + + return 16; /* error */ +} + +int hexstring2data(char *arg, unsigned char *data, int maxdlen) { + + int len = strlen(arg); + int i; + unsigned char tmp; + + if (!len || len%2 || len > maxdlen*2) + return 1; + + memset(data, 0, maxdlen); + + for (i=0; i < len/2; i++) { + + tmp = asc2nibble(*(arg+(2*i))); + if (tmp > 0x0F) + return 1; + + data[i] = (tmp << 4); + + tmp = asc2nibble(*(arg+(2*i)+1)); + if (tmp > 0x0F) + return 1; + + data[i] |= tmp; + } + + return 0; +} + +int parse_canframe(char *cs, struct canfd_frame *cf) { + /* documentation see lib.h */ + + int i, idx, dlen, len; + int maxdlen = CAN_MAX_DLEN; + int ret = CAN_MTU; + unsigned char tmp; + + len = strlen(cs); + //printf("'%s' len %d\n", cs, len); + + memset(cf, 0, sizeof(*cf)); /* init CAN FD frame, e.g. LEN = 0 */ + + if (len < 4) + return 0; + + if (cs[3] == CANID_DELIM) { /* 3 digits */ + + idx = 4; + for (i=0; i<3; i++){ + if ((tmp = asc2nibble(cs[i])) > 0x0F) + return 0; + cf->can_id |= (tmp << (2-i)*4); + } + + } else if (cs[8] == CANID_DELIM) { /* 8 digits */ + + idx = 9; + for (i=0; i<8; i++){ + if ((tmp = asc2nibble(cs[i])) > 0x0F) + return 0; + cf->can_id |= (tmp << (7-i)*4); + } + if (!(cf->can_id & CAN_ERR_FLAG)) /* 8 digits but no errorframe? */ + cf->can_id |= CAN_EFF_FLAG; /* then it is an extended frame */ + + } else + return 0; + + if((cs[idx] == 'R') || (cs[idx] == 'r')){ /* RTR frame */ + cf->can_id |= CAN_RTR_FLAG; + + /* check for optional DLC value for CAN 2.0B frames */ + if(cs[++idx] && (tmp = asc2nibble(cs[idx])) <= CAN_MAX_DLC) + cf->len = tmp; + + return ret; + } + + if (cs[idx] == CANID_DELIM) { /* CAN FD frame escape char '##' */ + + maxdlen = CANFD_MAX_DLEN; + ret = CANFD_MTU; + + /* CAN FD frame ##* */ + if ((tmp = asc2nibble(cs[idx+1])) > 0x0F) + return 0; + + cf->flags = tmp; + idx += 2; + } + + for (i=0, dlen=0; i < maxdlen; i++){ + + if(cs[idx] == DATA_SEPERATOR) /* skip (optional) separator */ + idx++; + + if(idx >= len) /* end of string => end of data */ + break; + + if ((tmp = asc2nibble(cs[idx++])) > 0x0F) + return 0; + cf->data[i] = (tmp << 4); + if ((tmp = asc2nibble(cs[idx++])) > 0x0F) + return 0; + cf->data[i] |= tmp; + dlen++; + } + cf->len = dlen; + + return ret; +} + +void fprint_canframe(FILE *stream , struct canfd_frame *cf, char *eol, int sep, int maxdlen) { + /* documentation see lib.h */ + + char buf[CL_CFSZ]; /* max length */ + + sprint_canframe(buf, cf, sep, maxdlen); + fprintf(stream, "%s", buf); + if (eol) + fprintf(stream, "%s", eol); +} + +void sprint_canframe(char *buf , struct canfd_frame *cf, int sep, int maxdlen) { + /* documentation see lib.h */ + + int i,offset; + int len = (cf->len > maxdlen) ? maxdlen : cf->len; + + if (cf->can_id & CAN_ERR_FLAG) { + put_eff_id(buf, cf->can_id & (CAN_ERR_MASK|CAN_ERR_FLAG)); + buf[8] = '#'; + offset = 9; + } else if (cf->can_id & CAN_EFF_FLAG) { + put_eff_id(buf, cf->can_id & CAN_EFF_MASK); + buf[8] = '#'; + offset = 9; + } else { + put_sff_id(buf, cf->can_id & CAN_SFF_MASK); + buf[3] = '#'; + offset = 4; + } + + /* standard CAN frames may have RTR enabled. There are no ERR frames with RTR */ + if (maxdlen == CAN_MAX_DLEN && cf->can_id & CAN_RTR_FLAG) { + buf[offset++] = 'R'; + /* print a given CAN 2.0B DLC if it's not zero */ + if (cf->len && cf->len <= CAN_MAX_DLC) + buf[offset++] = hex_asc_upper_lo(cf->len); + + buf[offset] = 0; + return; + } + + if (maxdlen == CANFD_MAX_DLEN) { + /* add CAN FD specific escape char and flags */ + buf[offset++] = '#'; + buf[offset++] = hex_asc_upper_lo(cf->flags); + if (sep && len) + buf[offset++] = '.'; + } + + for (i = 0; i < len; i++) { + put_hex_byte(buf + offset, cf->data[i]); + offset += 2; + if (sep && (i+1 < len)) + buf[offset++] = '.'; + } + + buf[offset] = 0; +} + +void fprint_long_canframe(FILE *stream , struct canfd_frame *cf, char *eol, int view, int maxdlen) { + /* documentation see lib.h */ + + char buf[CL_LONGCFSZ]; + + sprint_long_canframe(buf, cf, view, maxdlen); + fprintf(stream, "%s", buf); + if ((view & CANLIB_VIEW_ERROR) && (cf->can_id & CAN_ERR_FLAG)) { + snprintf_can_error_frame(buf, sizeof(buf), cf, "\n\t"); + fprintf(stream, "\n\t%s", buf); + } + if (eol) + fprintf(stream, "%s", eol); +} + +void sprint_long_canframe(char *buf , struct canfd_frame *cf, int view, int maxdlen) { + /* documentation see lib.h */ + + int i, j, dlen, offset; + int len = (cf->len > maxdlen)? maxdlen : cf->len; + + /* initialize space for CAN-ID and length information */ + memset(buf, ' ', 15); + + if (cf->can_id & CAN_ERR_FLAG) { + put_eff_id(buf, cf->can_id & (CAN_ERR_MASK|CAN_ERR_FLAG)); + offset = 10; + } else if (cf->can_id & CAN_EFF_FLAG) { + put_eff_id(buf, cf->can_id & CAN_EFF_MASK); + offset = 10; + } else { + if (view & CANLIB_VIEW_INDENT_SFF) { + put_sff_id(buf + 5, cf->can_id & CAN_SFF_MASK); + offset = 10; + } else { + put_sff_id(buf, cf->can_id & CAN_SFF_MASK); + offset = 5; + } + } + + /* The len value is sanitized by maxdlen (see above) */ + if (maxdlen == CAN_MAX_DLEN) { + buf[offset + 1] = '['; + buf[offset + 2] = len + '0'; + buf[offset + 3] = ']'; + + /* standard CAN frames may have RTR enabled */ + if (cf->can_id & CAN_RTR_FLAG) { + sprintf(buf+offset+5, " remote request"); + return; + } + } else { + buf[offset] = '['; + buf[offset + 1] = (len/10) + '0'; + buf[offset + 2] = (len%10) + '0'; + buf[offset + 3] = ']'; + } + offset += 5; + + if (view & CANLIB_VIEW_BINARY) { + dlen = 9; /* _10101010 */ + if (view & CANLIB_VIEW_SWAP) { + for (i = len - 1; i >= 0; i--) { + buf[offset++] = (i == len-1)?' ':SWAP_DELIMITER; + for (j = 7; j >= 0; j--) + buf[offset++] = (1<data[i])?'1':'0'; + } + } else { + for (i = 0; i < len; i++) { + buf[offset++] = ' '; + for (j = 7; j >= 0; j--) + buf[offset++] = (1<data[i])?'1':'0'; + } + } + } else { + dlen = 3; /* _AA */ + if (view & CANLIB_VIEW_SWAP) { + for (i = len - 1; i >= 0; i--) { + if (i == len-1) + buf[offset++] = ' '; + else + buf[offset++] = SWAP_DELIMITER; + + put_hex_byte(buf + offset, cf->data[i]); + offset += 2; + } + } else { + for (i = 0; i < len; i++) { + buf[offset++] = ' '; + put_hex_byte(buf + offset, cf->data[i]); + offset += 2; + } + } + } + + buf[offset] = 0; /* terminate string */ + + /* + * The ASCII & ERRORFRAME output is put at a fixed len behind the data. + * For now we support ASCII output only for payload length up to 8 bytes. + * Does it make sense to write 64 ASCII byte behind 64 ASCII HEX data on the console? + */ + if (len > CAN_MAX_DLEN) + return; + + if (cf->can_id & CAN_ERR_FLAG) + sprintf(buf+offset, "%*s", dlen*(8-len)+13, "ERRORFRAME"); + else if (view & CANLIB_VIEW_ASCII) { + j = dlen*(8-len)+4; + if (view & CANLIB_VIEW_SWAP) { + sprintf(buf+offset, "%*s", j, "`"); + offset += j; + for (i = len - 1; i >= 0; i--) + if ((cf->data[i] > 0x1F) && (cf->data[i] < 0x7F)) + buf[offset++] = cf->data[i]; + else + buf[offset++] = '.'; + + sprintf(buf+offset, "`"); + } else { + sprintf(buf+offset, "%*s", j, "'"); + offset += j; + for (i = 0; i < len; i++) + if ((cf->data[i] > 0x1F) && (cf->data[i] < 0x7F)) + buf[offset++] = cf->data[i]; + else + buf[offset++] = '.'; + + sprintf(buf+offset, "'"); + } + } +} + +static const char *error_classes[] = { + "tx-timeout", + "lost-arbitration", + "controller-problem", + "protocol-violation", + "transceiver-status", + "no-acknowledgement-on-tx", + "bus-off", + "bus-error", + "restarted-after-bus-off", +}; + +static const char *controller_problems[] = { + "rx-overflow", + "tx-overflow", + "rx-error-warning", + "tx-error-warning", + "rx-error-passive", + "tx-error-passive", + "back-to-error-active", +}; + +static const char *protocol_violation_types[] = { + "single-bit-error", + "frame-format-error", + "bit-stuffing-error", + "tx-dominant-bit-error", + "tx-recessive-bit-error", + "bus-overload", + "active-error", + "error-on-tx", +}; + +static const char *protocol_violation_locations[] = { + "unspecified", + "unspecified", + "id.28-to-id.21", + "start-of-frame", + "bit-srtr", + "bit-ide", + "id.20-to-id.18", + "id.17-to-id.13", + "crc-sequence", + "reserved-bit-0", + "data-field", + "data-length-code", + "bit-rtr", + "reserved-bit-1", + "id.4-to-id.0", + "id.12-to-id.5", + "unspecified", + "active-error-flag", + "intermission", + "tolerate-dominant-bits", + "unspecified", + "unspecified", + "passive-error-flag", + "error-delimiter", + "crc-delimiter", + "acknowledge-slot", + "end-of-frame", + "acknowledge-delimiter", + "overload-flag", + "unspecified", + "unspecified", + "unspecified", +}; + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +static int snprintf_error_data(char *buf, size_t len, uint8_t err, + const char **arr, int arr_len) +{ + int i, n = 0, count = 0; + + if (!err || len <= 0) + return 0; + + for (i = 0; i < arr_len; i++) { + if (err & (1 << i)) { + if (count) + n += snprintf(buf + n, len - n, ","); + n += snprintf(buf + n, len - n, "%s", arr[i]); + count++; + } + } + + return n; +} + +static int snprintf_error_lostarb(char *buf, size_t len, const struct canfd_frame *cf) +{ + if (len <= 0) + return 0; + return snprintf(buf, len, "{at bit %d}", cf->data[0]); +} + +static int snprintf_error_ctrl(char *buf, size_t len, const struct canfd_frame *cf) +{ + int n = 0; + + if (len <= 0) + return 0; + + n += snprintf(buf + n, len - n, "{"); + n += snprintf_error_data(buf + n, len - n, cf->data[1], + controller_problems, + ARRAY_SIZE(controller_problems)); + n += snprintf(buf + n, len - n, "}"); + + return n; +} + +static int snprintf_error_prot(char *buf, size_t len, const struct canfd_frame *cf) +{ + int n = 0; + + if (len <= 0) + return 0; + + n += snprintf(buf + n, len - n, "{{"); + n += snprintf_error_data(buf + n, len - n, cf->data[2], + protocol_violation_types, + ARRAY_SIZE(protocol_violation_types)); + n += snprintf(buf + n, len - n, "}{"); + if (cf->data[3] > 0 && + cf->data[3] < ARRAY_SIZE(protocol_violation_locations)) + n += snprintf(buf + n, len - n, "%s", + protocol_violation_locations[cf->data[3]]); + n += snprintf(buf + n, len - n, "}}"); + + return n; +} + +void snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *cf, + const char* sep) +{ + canid_t class, mask; + int i, n = 0, classes = 0; + char *defsep = ","; + + if (!(cf->can_id & CAN_ERR_FLAG)) + return; + + class = cf->can_id & CAN_EFF_MASK; + if (class > (1 << ARRAY_SIZE(error_classes))) { + fprintf(stderr, "Error class %#x is invalid\n", class); + return; + } + + if (!sep) + sep = defsep; + + for (i = 0; i < (int)ARRAY_SIZE(error_classes); i++) { + mask = 1 << i; + if (class & mask) { + if (classes) + n += snprintf(buf + n, len - n, "%s", sep); + n += snprintf(buf + n, len - n, "%s", error_classes[i]); + if (mask == CAN_ERR_LOSTARB) + n += snprintf_error_lostarb(buf + n, len - n, + cf); + if (mask == CAN_ERR_CRTL) + n += snprintf_error_ctrl(buf + n, len - n, cf); + if (mask == CAN_ERR_PROT) + n += snprintf_error_prot(buf + n, len - n, cf); + classes++; + } + } + + if (cf->data[6] || cf->data[7]) { + n += snprintf(buf + n, len - n, "%s", sep); + n += snprintf(buf + n, len - n, "error-counter-tx-rx{{%d}{%d}}", + cf->data[6], cf->data[7]); + } +} diff --git a/canutils/libcanutils/lib.h b/canutils/libcanutils/lib.h new file mode 100644 index 000000000..24fd41e34 --- /dev/null +++ b/canutils/libcanutils/lib.h @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * lib.h - library include for command line tools + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to + * + */ + +#ifndef CAN_UTILS_LIB_H +#define CAN_UTILS_LIB_H + +#include + +/* Compatibility for NuttX */ +typedef uint8_t __u8; +typedef uint32_t __u32; + +/* buffer sizes for CAN frame string representations */ + +#define CL_ID (sizeof("12345678##1")) +#define CL_DATA sizeof(".AA") +#define CL_BINDATA sizeof(".10101010") + + /* CAN FD ASCII hex short representation with DATA_SEPERATORs */ +#define CL_CFSZ (2*CL_ID + 64*CL_DATA) + +/* CAN FD ASCII hex long representation with binary output */ +#define CL_LONGCFSZ (2*CL_ID + sizeof(" [255] ") + (64*CL_BINDATA)) + +/* CAN DLC to real data length conversion helpers especially for CAN FD */ + +/* get data length from can_dlc with sanitized can_dlc */ +unsigned char can_dlc2len(unsigned char can_dlc); + +/* map the sanitized data length to an appropriate data length code */ +unsigned char can_len2dlc(unsigned char len); + +unsigned char asc2nibble(char c); +/* + * Returns the decimal value of a given ASCII hex character. + * + * While 0..9, a..f, A..F are valid ASCII hex characters. + * On invalid characters the value 16 is returned for error handling. + */ + +int hexstring2data(char *arg, unsigned char *data, int maxdlen); +/* + * Converts a given ASCII hex string to a (binary) byte string. + * + * A valid ASCII hex string consists of an even number of up to 16 chars. + * Leading zeros '00' in the ASCII hex string are interpreted. + * + * Examples: + * + * "1234" => data[0] = 0x12, data[1] = 0x34 + * "001234" => data[0] = 0x00, data[1] = 0x12, data[2] = 0x34 + * + * Return values: + * 0 = success + * 1 = error (in length or the given characters are no ASCII hex characters) + * + * Remark: The not written data[] elements are initialized with zero. + * + */ + +int parse_canframe(char *cs, struct canfd_frame *cf); +/* + * Transfers a valid ASCII string describing a CAN frame into struct canfd_frame. + * + * CAN 2.0 frames + * - string layout #{R{len}|data} + * - {data} has 0 to 8 hex-values that can (optionally) be separated by '.' + * - {len} can take values from 0 to 8 and can be omitted if zero + * - return value on successful parsing: CAN_MTU + * + * CAN FD frames + * - string layout ##{data} + * - a single ASCII Hex value (0 .. F) which defines canfd_frame.flags + * - {data} has 0 to 64 hex-values that can (optionally) be separated by '.' + * - return value on successful parsing: CANFD_MTU + * + * Return value on detected problems: 0 + * + * can have 3 (standard frame format) or 8 (extended frame format) + * hexadecimal chars + * + * + * Examples: + * + * 123# -> standard CAN-Id = 0x123, len = 0 + * 12345678# -> extended CAN-Id = 0x12345678, len = 0 + * 123#R -> standard CAN-Id = 0x123, len = 0, RTR-frame + * 123#R0 -> standard CAN-Id = 0x123, len = 0, RTR-frame + * 123#R7 -> standard CAN-Id = 0x123, len = 7, RTR-frame + * 7A1#r -> standard CAN-Id = 0x7A1, len = 0, RTR-frame + * + * 123#00 -> standard CAN-Id = 0x123, len = 1, data[0] = 0x00 + * 123#1122334455667788 -> standard CAN-Id = 0x123, len = 8 + * 123#11.22.33.44.55.66.77.88 -> standard CAN-Id = 0x123, len = 8 + * 123#11.2233.44556677.88 -> standard CAN-Id = 0x123, len = 8 + * 32345678#112233 -> error frame with CAN_ERR_FLAG (0x2000000) set + * + * 123##0112233 -> CAN FD frame standard CAN-Id = 0x123, flags = 0, len = 3 + * 123##1112233 -> CAN FD frame, flags = CANFD_BRS, len = 3 + * 123##2112233 -> CAN FD frame, flags = CANFD_ESI, len = 3 + * 123##3 -> CAN FD frame, flags = (CANFD_ESI | CANFD_BRS), len = 0 + * ^^ + * CAN FD extension to handle the canfd_frame.flags content + * + * Simple facts on this compact ASCII CAN frame representation: + * + * - 3 digits: standard frame format + * - 8 digits: extendend frame format OR error frame + * - 8 digits with CAN_ERR_FLAG (0x2000000) set: error frame + * - an error frame is never a RTR frame + * - CAN FD frames do not have a RTR bit + */ + +void fprint_canframe(FILE *stream , struct canfd_frame *cf, char *eol, int sep, int maxdlen); +void sprint_canframe(char *buf , struct canfd_frame *cf, int sep, int maxdlen); +/* + * Creates a CAN frame hexadecimal output in compact format. + * The CAN data[] is separated by '.' when sep != 0. + * + * The type of the CAN frame (CAN 2.0 / CAN FD) is specified by maxdlen: + * maxdlen = 8 -> CAN2.0 frame + * maxdlen = 64 -> CAN FD frame + * + * 12345678#112233 -> extended CAN-Id = 0x12345678, len = 3, data, sep = 0 + * 12345678#R -> extended CAN-Id = 0x12345678, RTR, len = 0 + * 12345678#R5 -> extended CAN-Id = 0x12345678, RTR, len = 5 + * 123#11.22.33.44.55.66.77.88 -> standard CAN-Id = 0x123, dlc = 8, sep = 1 + * 32345678#112233 -> error frame with CAN_ERR_FLAG (0x2000000) set + * 123##0112233 -> CAN FD frame standard CAN-Id = 0x123, flags = 0, len = 3 + * 123##2112233 -> CAN FD frame, flags = CANFD_ESI, len = 3 + * + * Examples: + * + * fprint_canframe(stdout, &frame, "\n", 0); // with eol to STDOUT + * fprint_canframe(stderr, &frame, NULL, 0); // no eol to STDERR + * + */ + +#define CANLIB_VIEW_ASCII 0x1 +#define CANLIB_VIEW_BINARY 0x2 +#define CANLIB_VIEW_SWAP 0x4 +#define CANLIB_VIEW_ERROR 0x8 +#define CANLIB_VIEW_INDENT_SFF 0x10 + +#define SWAP_DELIMITER '`' + +void fprint_long_canframe(FILE *stream , struct canfd_frame *cf, char *eol, int view, int maxdlen); +void sprint_long_canframe(char *buf , struct canfd_frame *cf, int view, int maxdlen); +/* + * Creates a CAN frame hexadecimal output in user readable format. + * + * The type of the CAN frame (CAN 2.0 / CAN FD) is specified by maxdlen: + * maxdlen = 8 -> CAN2.0 frame + * maxdlen = 64 -> CAN FD frame + * + * 12345678 [3] 11 22 33 -> extended CAN-Id = 0x12345678, dlc = 3, data + * 12345678 [0] remote request -> extended CAN-Id = 0x12345678, RTR + * 14B0DC51 [8] 4A 94 E8 2A EC 58 55 62 'J..*.XUb' -> (with ASCII output) + * 20001111 [7] C6 23 7B 32 69 98 3C ERRORFRAME -> (CAN_ERR_FLAG set) + * 12345678 [03] 11 22 33 -> CAN FD with extended CAN-Id = 0x12345678, dlc = 3 + * + * 123 [3] 11 22 33 -> CANLIB_VIEW_INDENT_SFF == 0 + * 123 [3] 11 22 33 -> CANLIB_VIEW_INDENT_SFF == set + * + * Examples: + * + * // CAN FD frame with eol to STDOUT + * fprint_long_canframe(stdout, &frame, "\n", 0, CANFD_MAX_DLEN); + * + * // CAN 2.0 frame without eol to STDERR + * fprint_long_canframe(stderr, &frame, NULL, 0, CAN_MAX_DLEN); + * + */ + +void snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *cf, + const char *sep); +/* + * Creates a CAN error frame output in user readable format. + */ + +#endif diff --git a/canutils/libcanutils/terminal.h b/canutils/libcanutils/terminal.h new file mode 100644 index 000000000..198b48ec0 --- /dev/null +++ b/canutils/libcanutils/terminal.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to + * + */ + +#ifndef TERMINAL_H +#define TERMINAL_H + +/* reset to default */ + +#define ATTRESET "\33[0m" + +/* attributes */ + +#define ATTBOLD "\33[1m" +#define ATTUNDERLINE "\33[4m" +#define ATTBLINK "\33[5m" +#define ATTINVERSE "\33[7m" +#define ATTINVISIBLE "\33[8m" + +/* foreground colors */ + +#define FGBLACK "\33[30m" +#define FGRED "\33[31m" +#define FGGREEN "\33[32m" +#define FGYELLOW "\33[33m" +#define FGBLUE "\33[34m" +#define FGMAGENTA "\33[35m" +#define FGCYAN "\33[36m" +#define FGWHITE "\33[37m" + +/* background colors */ + +#define BGBLACK "\33[40m" +#define BGRED "\33[41m" +#define BGGREEN "\33[42m" +#define BGYELLOW "\33[43m" +#define BGBLUE "\33[44m" +#define BGMAGENTA "\33[45m" +#define BGCYAN "\33[46m" +#define BGWHITE "\33[47m" + +/* cursor */ + +#define CSR_HOME "\33[H" +#define CSR_UP "\33[A" +#define CSR_DOWN "\33[B" +#define CSR_RIGHT "\33[C" +#define CSR_LEFT "\33[D" + +#define CSR_HIDE "\33[?25l" +#define CSR_SHOW "\33[?25h" + +/* clear screen */ + +#define CLR_SCREEN "\33[2J" + +#endif /* TERMINAL_H */