Send again as the original post was to long -----Ursprüngliche Nachricht----- Von: Stefan Althöfer Gesendet: Mittwoch, 18. Januar 2023 12:27 An: linux-can@xxxxxxxxxxxxxxx Cc: Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx> Betreff: mcp251xfd: rx frame truncation While still testing the double-RX erratum patch I'm experiencing another problem. I'm using my setup with two mcp2518fd and my own 'sctest' program. Both are sending messages to each other. I start sending a number of transmit ahead frames and then send out one more messages when a frame arrives at the peer. So overrun should not occur. In contrast to the tests I did over the weekend, I increased the transmit ahead to 3, sending now up to 64 bytes and the frame size is now random. I now frequently see these kind of messages appear: Jan 18 05:34:00 raspberrypi kernel: [405471.116322] mcp251xfd spi0.0 can0: Transmit Event FIFO buffer not empty. (seq=0x00001e36, tef_tail=0x00001e3a, tef_head=0x00001e3b, tx_head=0x00001e3b). Jan 18 09:36:27 raspberrypi kernel: [420017.452369] mcp251xfd spi6.0 can1: Transmit Event FIFO buffer full. (seq=0x00001062, tef_tail=0x00001066, tef_head=0x00001067, tx_head=0x00001067). Why do they occur? But now the real problem. After some time one of the socketcans is receiving only a truncated message: 1: idx 2 exp:2A5#00 02 F4 7C 7B F2 53 BD 53 84 B3 4B B7 5F AE 55 2D 88 13 25 7C D4 82 EF 85 35 9C 33 1B 7C 5B 55 91 4F D1 0D 41 24 CA 95 A9 7E E0 60 DD 8E B5 0A 16 C8 2F 92 9C B1 82 22 E6 1E 55 01 9A B0 56 2B 1: rx:2A5#00 02 F4 7C 7B F2 53 BD 53 84 B3 4B B7 5F AE 55 2D 88 13 25 Same reception in the candump1 log (candump -H -td -D canX,0:0,#FFFFFFFF). However the message was Transmitted correctly, as the external logger has received it. 47:36.412.753 +0.172 0677fd 64 00 02 f4 7c 7b f2 53 bd 53 84 b3 4b b7 5f ae 55 2d 88 13 25 7c d4 82 ef 85 35 9c 33 1b 7c 5b 55 91 4f d1 0d 41 24 ca 95 a9 7e e0 60 dd 8e b5 0a 16 c8 2f 92 9c b1 82 22 e6 1e 55 01 9a b0 56 2b I attached the regdump at the time when the test loop finished. At least all data bytes seem to be in the RAM. I attached the 'sctest' source. It should build in the same environment as 'sctestself'. I've seen this truncation occurring several times. I think the problem did not occur when transmit ahead was set to 2, but I re-run the test again right now to verify. Ask if you want more information! ----- Stefan Althöfer (stefan.althoefer@xxxxxxxxxxx)
/* -*-C-*- * sctest.c - Raw socketcan test program (2 node rx/tx test) * *----------------------------------------------------------------------- * * Copyright (c) 2023 Janz Tec AG * All Rights Reserved * * Permission is hereby granted to licensees of Janz Tec AG * products to use or abstract this computer program for the sole * purpose of implementing a product based on Janz Tec AG * products. No other rights to reproduce, use, or disseminate * this computer program, whether in part or in whole, are granted. * * Janz Tec AG makes no representation or warranties * with respect to the performance of this computer program, and * specifically disclaims any responsibility for any damages, special * or consequential, connected with the use of this program. * * Janz Tec AG products are not authorized for use as * critical components in life support devices or systems without the * express written approval of Janz Tec AG. * *----------------------------------------------------------------------- * * $Id: sctest.c 4020 2023-01-17 22:09:24Z as $ * *----------------------------------------------------------------------- */ /***** includes *************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <net/if.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <fcntl.h> #include <sys/time.h> #include <sys/times.h> #include <unistd.h> #include <errno.h> #include <sched.h> #include <sys/mman.h> #include <signal.h> #include <netinet/in.h> #include <netdb.h> #include <linux/can.h> #include <linux/can/raw.h> #include <linux/can/error.h> #include "svnglbrev.h" #include "jlib.h" #include "libsocketcan.h" /***** defines **************************************************************/ #define CT_RAW_TIMEOUT 1 #define CT_RAW_BAD_RECEIVE 2 #define CT_RAW_NO_RESPONSE 3 #define CT_RAW_BUS_ERRORS 4 #define CT_RAW_READ_ERROR 5 #define CT_RAW_BAD_ORDER 6 #define CT_RAW_DUPLICATES 7 #define CT_RAW_TERMINATED 99 #define MAXFPATH 512 #define MAXPKTNUM 1000 #define DFLTPKTNUM 80 #define MAXLOOPS 10000 #define DFLTLOOPS 25 #define DFLTTXAHEAD 4 #define DFLTTIMEOUT 20 #define DFLTID 0x2a5 #define FLAGS_NORANDOMDATA 0x001 #define FLAGS_RANDOMIZE 0x002 #define FLAGS_REFILLDATA 0x004 #define FLAGS_ALLOWOOOD 0x010 /* Out-Of-Order-Delivery */ #define FLAGS_LESSDOTS 0x100 #define FLAGS_LEASTDOTS 0x200 #define FLAGS_STOPONERROR 0x400 #define VERB_STATS 0x001 #define VERB_IFPROP 0x002 #define VERB_TIMERR 0x100 #define VERB_CMPERR 0x200 #define VERB_LOGMSG 0x400 #define VERB_LOSSERR 0x800 #define vprintf(level, format, ...) do { \ if(level & opt_verbose) \ fprintf(stdout, format, ##__VA_ARGS__); \ } while(0) /***** typedefs *************************************************************/ struct cantest_raw { int status; /* 0=ok, 1=fail */ double dT; /* in seconds */ double speed; /* in kbit/s */ int loss; /* messages lost */ int berrcnt; /* bus error count */ } cantest_raw; typedef unsigned int usTimer_t; /***** imports **************************************************************/ /***** locals ***************************************************************/ /***** globals **************************************************************/ int opt_verbose; char *opt_candev[2]; int opt_rt; int opt_pkts; int opt_loops; unsigned int opt_ldelay; int opt_timeout; int opt_allowerr; int opt_txahead; int opt_flags; int opt_term; int opt_id; int opt_dlen; int opt_eff; int opt_brs; int opt_txdevnum; char *opt_logger; char *opt_send; int sockfd[2]; int can_mtu[2]; /* MTU of each channel */ int cmp_mtu; /* least MTU of both channel */ struct canfd_frame txdata[2][MAXPKTNUM+100]; /* message out buf */ struct canfd_frame rxdata[2][MAXPKTNUM+100]; /* message in buf */ int rxidxcnt[2][MAXPKTNUM+100]; /* count each rx index */ volatile int hup; volatile int sigint; #define DBG_LOG_MAX 4196 #define DBG_LOG_TX 0 #define DBG_LOG_RX 1 int dbg_log_pos; struct dbg_log_buff { int ch; int dir; int txcnt; int rxcnt; struct canfd_frame frame; } dbg_log_buff[DBG_LOG_MAX]; /***** forward declarations *************************************************/ static int cleanup(int retval); /* * logger: <host>:<portno> (implemented) * [<type>@]<host>:<portno> (not yet implemented) */ int readLogger( char *logger, int msgcnt ) { struct hostent *him; struct sockaddr_in sin; int fd; char inbuff[1024]; char cmdbuff[64]; int n; struct timeval timeout; char host[64]; int port; host[0] = '\0'; port = 0; if( sscanf(logger, "%63[^:]:%d", host, &port) != 2 ){ fprintf(stderr, "readLogger(): wrong logger parameter: found %s:%d\n", host, port); return -1; } if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "readLogger(): socket() failed\n"); return -1; } bzero((char*)&sin,sizeof(sin)); if ((him = gethostbyname(host)) == NULL) { fprintf(stderr, "readLogger(): Unknown host %s\n", host); return -2; } bcopy(him->h_addr, (caddr_t)&sin.sin_addr, him->h_length); sin.sin_family = him->h_addrtype; sin.sin_port = htons(port); if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { fprintf(stderr, "readLogger(): cannot connect()\n"); close(fd); return -3; } timeout.tv_sec = 0; timeout.tv_usec = 100*1000; if( setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout,sizeof(timeout)) < 0 ) { fprintf(stderr, "readLogger(): cannot set RCVTIMEOUT\n"); return -2; } /* Request data from peer */ sprintf(cmdbuff,"ics:%ds\r\n", msgcnt); if( write(fd, cmdbuff, strlen(cmdbuff)) < 0 ){ return -4; } /* FIXME: the data on the socket is sent with CRNL line ending. We dont convert to NL-only ending, and just print the CRs along with the NLs. */ while( 1 ){ n = read(fd, inbuff, 1023); if( n <= 0 ){ printf("done (%d)\n", n); break; } inbuff[n] = '\0'; printf("%s", inbuff); } close(fd); return 0; } void dbg_log( int ch, int dir, int txcnt, int rxcnt, struct canfd_frame *frame ) { dbg_log_buff[dbg_log_pos].ch = ch; dbg_log_buff[dbg_log_pos].dir = dir; dbg_log_buff[dbg_log_pos].txcnt = txcnt; dbg_log_buff[dbg_log_pos].rxcnt = rxcnt; memcpy(&(dbg_log_buff[dbg_log_pos].frame), frame, sizeof(struct canfd_frame)); dbg_log_pos++; if( dbg_log_pos >= DBG_LOG_MAX ){ dbg_log_pos = 0; } } void dbg_log_print(void) { int i; int logcnt; /* print in reverse order */ dbg_log_pos--; if( dbg_log_pos < 0 ){ dbg_log_pos = DBG_LOG_MAX-1; } logcnt = opt_pkts * 5; if( logcnt > 4000 ){ logcnt = 4000; } for(i=0; i<logcnt; i++){ printf("%d: %s (%.3d/%.3d) ", dbg_log_buff[dbg_log_pos].ch, dbg_log_buff[dbg_log_pos].dir ? "RX": "TX" , dbg_log_buff[dbg_log_pos].txcnt, dbg_log_buff[dbg_log_pos].rxcnt); fprint_canframe_mtu(stdout , &(dbg_log_buff[dbg_log_pos].frame), "\n", 1, sizeof(struct canfd_frame)); dbg_log_pos--; if( dbg_log_pos < 0 ){ dbg_log_pos = DBG_LOG_MAX-1; } } } usTimer_t usTimer( usTimer_t reference ) { struct timeval tv; struct timezone tz; int x; gettimeofday(&tv, &tz); x = (tv.tv_sec%100)*1000000 + tv.tv_usec; /* printf("sec=%u, us=%u, x=%u --", (tv.tv_sec%10), tv.tv_usec, x); */ if( reference != 0 ){ if( x >= reference ){ return (x-reference); } else { return (100*1000000-reference+x); } } return x; } /*error - wrapper for perror */ void error( char *msg ) { perror(msg); exit(126); } void set_hup( int signo ) { hup = 1; if( signo == SIGINT ){ sigint = 1; } } int testloop( int sockfd[2], int pktnum ) { int txnum; int n; int loopcnt = 0; int txcnt[2]; int rxcnt[2]; int goodcnt[2]; int idx1 = 0; int devidx; usTimer_t t0; struct timeval timeout; int berrcnt = 0; int oocnt = 0; int dupcnt = 0; fd_set rdfs; int ret; int first_cmp_err; int thisrx; int exprx; int read_error = 0; /* Unlink UDP, CAN is reliable. Hence we do not need to send execss packets to compensate loss */ txnum = pktnum; /* set our socket waiting timeout */ timeout.tv_sec = 0; timeout.tv_usec = opt_timeout*1000; for( devidx=0; devidx<2; devidx++ ){ if( setsockopt(sockfd[0], SOL_SOCKET, SO_RCVTIMEO, &timeout,sizeof(timeout)) < 0 ) { error("\nError in setsockopt "); exit(cleanup(1)); } goodcnt[devidx] = 0; txcnt[devidx] = 0; rxcnt[devidx] = 0; } /* Pre-fill (0xff) both receive buffers */ memset(rxdata, 0xff, sizeof(rxdata)); memset(rxidxcnt, 0, sizeof(rxidxcnt)); /* sample time */ t0 = usTimer(0); /* send ahead packets (only on devidx=0 when opt_txdevnum=1) */ for( devidx=0; devidx<opt_txdevnum; devidx++ ){ for( txcnt[devidx]=0; (txcnt[devidx]<txnum) && (txcnt[devidx]<opt_txahead); txcnt[devidx]++ ) { n = write(sockfd[devidx], (void *)&(txdata[devidx][txcnt[devidx]]), can_mtu[devidx]); if( n < can_mtu[devidx] ){ fprintf(stderr, "\nERROR in write (%d, errno=%d) ", n, errno); exit(cleanup(1)); } if( opt_verbose & VERB_LOGMSG ){ dbg_log(devidx, DBG_LOG_TX, txcnt[devidx], rxcnt[devidx], &(txdata[devidx][txcnt[devidx]])); } } } while( 1 ){ /* Receive messages: Only on devidx=1 when opt_txdevnum=1 */ FD_ZERO(&rdfs); for(devidx=(opt_txdevnum==2)?0:1; devidx<2; devidx++){ FD_SET(sockfd[devidx], &rdfs); } timeout.tv_sec = 0; timeout.tv_usec = opt_timeout*1000; if( (ret = select(sockfd[1]+1, &rdfs, NULL, NULL, &timeout)) <= 0 ){ //fprintf(stderr, "\nERROR select timed out "); goto done; } for(devidx=(opt_txdevnum==2)?0:1; devidx<2; devidx++){ if( ! FD_ISSET(sockfd[devidx], &rdfs)) { continue; } n = read(sockfd[devidx], (void *)&(rxdata[devidx][rxcnt[devidx]]), can_mtu[devidx]); if( opt_verbose & VERB_LOGMSG ){ dbg_log(devidx, DBG_LOG_RX, txcnt[devidx], rxcnt[devidx], &(rxdata[devidx][rxcnt[devidx]])); } #if 0 /* Simulate msg error: create a duplicate */ if( (devidx == 1) && (rxcnt[devidx] == 3) ){ memcpy(&(rxdata[devidx][3]), &(rxdata[devidx][3-2]), sizeof(struct canfd_frame)); } #endif thisrx = (rxdata[devidx][rxcnt[devidx]].data[0]<<8)&0xff00; thisrx |= (rxdata[devidx][rxcnt[devidx]].data[1]<<0)&0x00ff; if( thisrx < txnum ){ rxidxcnt[devidx][thisrx]++; } #if 0 /* Simulate data error: toggle byte in sequence count */ if( (devidx == 1) && (rxcnt[devidx] == 3) ){ printf("SIM RXERROR\n"); rxdata[devidx][rxcnt[devidx]].data[0] ^= 0xff; } /* Simulate data error: toggle byte in payload */ if( (devidx == 1) && (rxcnt[devidx] == 19) ){ printf("SIM RXERROR\n"); rxdata[devidx][rxcnt[devidx]].data[2] ^= 0xff; } #endif if( (n == can_mtu[devidx]) || (n == CAN_MTU) ){ if( n == CAN_MTU ){ /* Note: We dont care for the flags field when we compare incomming data, but we dont want random data here */ rxdata[devidx][rxcnt[devidx]].flags = 0; } if( rxdata[devidx][rxcnt[devidx]].can_id & CAN_ERR_FLAG ){ if( rxdata[devidx][rxcnt[devidx]].can_id & CAN_ERR_BUSOFF ){ printf("Eoff"); } else if ( rxdata[devidx][rxcnt[devidx]].can_id & CAN_ERR_ACK ){ printf("Eack"); } else if ( rxdata[devidx][rxcnt[devidx]].can_id & CAN_ERR_BUSERROR ){ printf("E"); berrcnt++; } else { printf("E???"); } fflush(stdout); } else { rxcnt[devidx]++; if( (rxcnt[1] == txnum) && /* When opt_txdevnum==1, then the second part is always true */ ((opt_txdevnum==1) || (rxcnt[0] == txnum)) ) { /* done: sample time and stop testing */ cantest_raw.dT = (double)usTimer(t0); cantest_raw.dT /= 1e6; goto done; } } } else { if( (errno == EAGAIN) || (errno == EWOULDBLOCK) ){ /* timed out while waiting. This can occur when: - there is no message return at all (e.g. no server) - more than txahead messages are lost in sequence. Both conditions are considered hard errors. */ cantest_raw.dT = (double)usTimer(t0); cantest_raw.dT /= 1e6; vprintf(VERB_TIMERR, "\nERROR: read EAGAIN after %fs " "(ret=%d txcnt=%d, rxcnt=%d) ", cantest_raw.dT, n, txcnt[devidx], rxcnt[devidx]); goto done; } else { fprintf(stderr, "\nERROR: read: ret=%d, errno=%d, rxcnt=%d ", n, errno, rxcnt[devidx]); read_error = 1; /* Do not break here, so we can see if more message will be received (likely aborted by timeout) */ } } /* Maintain txahead. The tx-ahead of one channel depends on the rxcnt of the other channel. */ while( ((txcnt[devidx^1] - rxcnt[devidx]) < opt_txahead) && (txcnt[devidx^1] < txnum) ){ n = write(sockfd[devidx^1], (void *)&(txdata[devidx^1][txcnt[devidx^1]]), can_mtu[devidx^1]); if (n < 0){ fprintf(stderr, "\nERROR in write (%d, errno=%d) ", n, errno); } if( opt_verbose & VERB_LOGMSG ){ dbg_log(devidx^1, DBG_LOG_TX, txcnt[devidx^1], rxcnt[devidx^1], &(txdata[devidx^1][txcnt[devidx^1]])); } txcnt[devidx^1]++; #if 0 /* Simulate tx error: drop one TX messages */ if( (devidx == 0) && (txcnt[devidx] == 3) ){ /* drop a message for testing */ printf("SIM TXERROR\n"); txcnt[devidx^1]++; } #endif } loopcnt++; } if( hup ){ break; } } done: if( hup ){ cantest_raw.status = CT_RAW_TERMINATED; return cantest_raw.status; } #if 0 /* Simulate msg error: swap RX messages */ struct canfd_frame etmp; int mm1, mm2; mm1=2; mm2=34; memcpy(&etmp, &(rxdata[0][mm1]), sizeof(struct canfd_frame)); memcpy(&(rxdata[0][mm1]), &(rxdata[0][mm2]), sizeof(struct canfd_frame)); memcpy(&(rxdata[0][mm2]), &etmp, sizeof(struct canfd_frame)); mm1=11; mm2=17; memcpy(&etmp, &(rxdata[0][mm1]), sizeof(struct canfd_frame)); memcpy(&(rxdata[0][mm1]), &(rxdata[0][mm2]), sizeof(struct canfd_frame)); memcpy(&(rxdata[0][mm2]), &etmp, sizeof(struct canfd_frame)); #endif first_cmp_err = 1; for(devidx=(opt_txdevnum==2)?0:1; devidx<2; devidx++){ /* compare RX of devidx with TX of devidx^1 (the other channel */ for( idx1=0; idx1<rxcnt[devidx]; idx1++ ){ thisrx = (rxdata[devidx][idx1].data[0]<<8)&0xff00; thisrx |= (rxdata[devidx][idx1].data[1]<<0)&0x00ff; if( thisrx < txnum ){ if( thisrx != idx1 ){ if( (rxcnt[1] == pktnum) && ((opt_txdevnum == 1) || (rxcnt[0] == pktnum)) ){ /* Only indicate out-of-order error when we have the correct packet number. If not, it is not out-of-order, it was just message loss */ oocnt = 1; printf("O"); if( opt_verbose & VERB_CMPERR ){ printf("[idx=%.4x,rx=%.4x]", idx1, thisrx); } } } if( cmp_canframe(&txdata[devidx^1][thisrx], &rxdata[devidx][idx1], cmp_mtu) == 0 ){ goodcnt[devidx]++; } else { if( opt_verbose & VERB_CMPERR ){ if( first_cmp_err ){ fprintf(stdout, "\n"); first_cmp_err = 0; } fprintf(stdout, "%d: idx%5d exp:", devidx, idx1); fprint_canframe_mtu(stdout , &txdata[devidx^1][idx1], "\n", 1, can_mtu[devidx^1]); fprintf(stdout, "%d: rx:", devidx); fprint_canframe_mtu(stdout , &rxdata[devidx][idx1], "\n", 1, can_mtu[devidx]); } } } else { /* A receive error in bytes 0 and 1, the sequence counter Dont' inc goodcnt, so it will be an error. */ if( opt_verbose & VERB_CMPERR ){ if( first_cmp_err ){ fprintf(stdout, "\n"); first_cmp_err = 0; } fprintf(stdout, "%d: idx%5d: out of range rx'ed sequence # 0x%x\n", devidx, idx1, thisrx); } else { printf("I"); } } } for( idx1=0; idx1<rxcnt[devidx]; idx1++ ){ /* Check for duplicates */ if( rxidxcnt[devidx][idx1] > 1 ){ dupcnt++; if( opt_verbose & VERB_CMPERR ){ fprintf(stdout, "%d: idx%5d: duplicate (x%d)\n", devidx, idx1, rxidxcnt[devidx][idx1]); } else { printf("D"); } } } } cantest_raw.speed = ((rxcnt[0]+rxcnt[1])*8)/(cantest_raw.dT*1e3); cantest_raw.loss = 0; cantest_raw.berrcnt = berrcnt; if( (rxcnt[1] < pktnum) || ((opt_txdevnum == 2) && (rxcnt[0] < pktnum)) ){ /* Did not receive the requested number of packets */ printf("L"); if( opt_verbose & VERB_LOSSERR ){ printf("[%d", pktnum); for(devidx=0; devidx<2; devidx++){ printf("/%d:%d", rxcnt[devidx], goodcnt[devidx]); if( rxcnt[devidx] > 0 && rxcnt[devidx] < pktnum){ /* Print missing packets, Assume that they are in strict sequence */ /* FIXME: This code is ugly und not complely correct */ printf("(missing:"); exprx = 0; for(idx1=0; (idx1<pktnum) && (exprx<pktnum); ){ thisrx = (rxdata[devidx][idx1].data[0]<<8)&0xff00; thisrx |= (rxdata[devidx][idx1].data[1]<<0)&0x00ff; if( thisrx != exprx ){ printf("%.4x,", exprx); exprx = thisrx; continue; } else { idx1++; exprx++; } } printf(")"); } } printf("]"); } if( opt_txdevnum == 2 ){ cantest_raw.loss += (pktnum-rxcnt[0]); } cantest_raw.loss += (pktnum-rxcnt[1]); if( (rxcnt[1] == 0) || ((opt_txdevnum == 2) && (rxcnt[1] == 0)) ){ cantest_raw.status = CT_RAW_NO_RESPONSE; } else { cantest_raw.status = CT_RAW_TIMEOUT; } } else { /* requested number of packets arrived */ if( (rxcnt[0]+rxcnt[1]) == (goodcnt[0]+goodcnt[1]) ){ if( dupcnt ){ cantest_raw.status = CT_RAW_DUPLICATES; } else if( berrcnt ){ if( opt_allowerr ){ cantest_raw.status = 0; } else { cantest_raw.status = CT_RAW_BUS_ERRORS; } } else { if( oocnt ){ cantest_raw.status = CT_RAW_BAD_ORDER; } else { cantest_raw.status = 0; } } } else { cantest_raw.status = CT_RAW_BAD_RECEIVE; } } if( opt_verbose & VERB_STATS ){ printf("status=%d ", cantest_raw.status); printf("dT=%.6f ", cantest_raw.dT); printf("speed=%.1f ", cantest_raw.speed); printf("loss=%d ", cantest_raw.loss); printf("\n"); } if( read_error ){ cantest_raw.status = CT_RAW_READ_ERROR; } return cantest_raw.status; } /* Free all resources we used and return with <retVal> */ static int cleanup( int retval ) { if( sockfd[0] ){ close(sockfd[0]); } if( sockfd[1] ){ close(sockfd[1]); } if( opt_term ){ /* Turn off, dont care for erros */ set_can_term(opt_candev[0], 0); set_can_term(opt_candev[1], 0); } if( sigint ){ return 255; } return retval; } /* parse for opt_flags, return -1 if error */ int parse_flags( const char *flags ) { const char *p; int opt_flags; opt_flags = 0; if( sscanf(flags, "%i", &opt_flags) != 1 ){ p = flags; while( *p != '\0' ){ if( strstr(p, "norandomdata") == p ){ opt_flags |= FLAGS_NORANDOMDATA; } else if( strstr(p, "randomize") == p ){ opt_flags |= FLAGS_RANDOMIZE; } else if( strstr(p, "refilldata") == p ){ opt_flags |= FLAGS_REFILLDATA; } else if( strstr(p, "allowoood") == p ){ opt_flags |= FLAGS_ALLOWOOOD; } else if( strstr(p, "lessdots") == p ){ opt_flags |= FLAGS_LESSDOTS; } else if( strstr(p, "leastdots") == p ){ opt_flags |= FLAGS_LEASTDOTS; } else if( strstr(p, "stoponerror") == p ){ opt_flags |= FLAGS_STOPONERROR; } else { return -1; } /* skip after next comma, or break if the is none */ if( (p=strstr(p, ",")) ){ p++; } else { break; } } } return opt_flags; } /* parse for opt_verbose, return -1 if error */ int parse_verbose( const char *verbose ) { const char *p; int opt_verbose; opt_verbose = 0; if( sscanf(verbose, "%i", &opt_verbose) != 1 ){ p = verbose; while( *p != '\0' ){ if( strstr(p, "stats") == p ){ opt_verbose |= VERB_STATS; } else if( strstr(p, "ifprop") == p ){ opt_verbose |= VERB_IFPROP; } else if( strstr(p, "timerr") == p ){ opt_verbose |= VERB_TIMERR; } else if( strstr(p, "cmperr") == p ){ opt_verbose |= VERB_CMPERR; } else if( strstr(p, "logmsg") == p ){ opt_verbose |= VERB_LOGMSG; } else if( strstr(p, "losserr") == p ){ opt_verbose |= VERB_LOSSERR; } else { return -1; } /* skip after next comma, or break if the is none */ if( (p=strstr(p, ",")) ){ p++; } else { break; } } } return opt_verbose; } #define xstr(a) str(a) #define str(a) #a /* * Show help * * RETURNS: N/A */ void usage(void) { fprintf( stdout, /* 1 2 3 4 5 6 7 8 */ /* 345678901234567890123456789012345678901234567890123456789012345678901234567890 */ "sctest - SocketCAN two node transmission test program\n" "USAGE: sctest [options] [canx-netif] [cany-netif]\n" "VERSION: $Id: sctest.c 4020 2023-01-17 22:09:24Z as $ " "$GlbRev: " __SVNGLBREV__ " $\n" "BUILT: " __DATE__ " " __TIME__ "\n" "\n" "The program sends out some tx-ahead packets on both can interfaces.\n" "It waits for messages to be recieved on the opposite channel. On every receipt,\n" "the next message is transmitted.\n" "When all messages are transmitted and received, data is checked for equalness.\n" "\n" "The following indicators are printed: Eoff when bus-off is detected, Eack\n" "when acknowledge error is detected, E if a bus-error (e.g. CRC) is detected.\n" "E??? is printed for error that is not dechipered.\n" "\n" "Sctest changes some socketcan options, hence the interfaces are put into\n" "down state duing program start.\n" "\n" "OPTIONS:\n" " -l <loops> : Number of test loops (default " xstr(DFLTLOOPS) ", max " xstr(MAXLOOPS) ")\n" " -n <msgs> : Messages sent in one loop (default " xstr(DFLTPKTNUM) ", max " xstr(MAXPKTNUM) ")\n" " -i <id> : CAN ID to use for canx-netif transmit. cany-netif will\n" " use ID+1 (default " xstr(DFLTID) ")\n" " -e : Transmit extended format frames\n" " -d <dlen> : Data bytes for one message (default = MTU)\n" " -b : Enable bitrate switch\n" " -t <num> : number of tx-ahead messages (default " xstr(DFLTTXAHEAD) ")\n" " -T <msecs> : timeout for one message (default " xstr(DFLTTIMEOUT) " ms)\n" " -u : Unidirectional operation. Messages are only send on canx-netif\n" " and received by cany-netif\n" " -e : Send transmit extended frames\n" " -F <flags> : Modify operation by comma separated list of modifiers\n" " or by bitmask value:\n" " norandomdata (" xstr(FLAGS_NORANDOMDATA) "): non-random data payload.\n" " randomize (" xstr(FLAGS_RANDOMIZE) "): randomize parameters.\n" " dlen = 2 .. dlen.\n" " refilldata (" xstr(FLAGS_REFILLDATA) "): data payload is randomized for\n" " every program loop. Increases runtime, but may help to\n" " identify a sent messages more easily\n" " allowoood (" xstr(FLAGS_ALLOWOOOD) "): allow out of order\n" " receiption. If this happens, then O is printed to stdout.\n" " lessdots (" xstr(FLAGS_LESSDOTS) "): print dot only every 10th loop dot.\n" " leastdots (" xstr(FLAGS_LEASTDOTS) "): print dot only every 100th loop.\n" " stoponerror (" xstr(FLAGS_STOPONERROR) "): Dont cleanup and exit\n" " -E : ignore bus errors (e.g. in EMC test)\n" " -z <msecs> : delay after each loop (defaut 0ms)\n" " -r <prio> : enable realtime task with prio <prio>\n" " -R : enable termination resistor on canx-netif. Duplicate option to\n" " enable termination on both netifs.\n" /* 345678901234567890123456789012345678901234567890123456789012345678901234567890 */ " -X <logdev>: In case of error try to capture logged data from external\n" " logger. Currently onyl available with ICANOS icanbuff tool.\n" " Use parameter hostname:portno to select which device to use.\n" " -Y <msg> : Just send a CAN frame on canx-netif and cany-netif for detection\n" " purposes. The argument is a hex digit which is used as data\n" " payload.\n" " -v <level> : Select verbose output by comma separated list of options\n" " or by bitmask value:\n" " stats (" xstr(VERB_STATS) "): per loop statistics\n" " ifprop (" xstr(VERB_IFPROP) "): interface properties\n" " timerr (" xstr(VERB_TIMERR) "): show timeout error details\n" " cmperr (" xstr(VERB_CMPERR) "): show compare error details\n" " logmsg (" xstr(VERB_LOGMSG) "): log tx/rx-ed frames and\n" " print on error\n" "\n" ); } int main(int argc, char** argv) { int rc; time_t t; int devidx; int opt; int i,n; int loop; int txcnt; struct sched_param param; struct sockaddr_can addr; struct ifreq ifr; struct can_ctrlmode cm; can_err_mask_t err_mask; int enable_canfd; hup = 0; sigint = 0; dbg_log_pos = 0; opt_verbose = 0; opt_rt = 0; opt_pkts = DFLTPKTNUM; opt_loops = DFLTLOOPS; opt_ldelay = 0; opt_timeout = DFLTTIMEOUT; opt_allowerr = 0; opt_txahead = DFLTTXAHEAD; opt_flags = 0; opt_term = 0; opt_dlen = -1; opt_eff = 0; opt_brs = 0; opt_txdevnum = 2; opt_logger = 0; opt_send = 0; opt_id = DFLTID; /* * Parse command line arguments */ while ((opt = getopt(argc, argv, "bd:eEF:i:hl:n:r:Rt:T:uv:X:Y:z:")) != -1) { switch (opt) { case 'b': opt_brs = 1; break; case 'd': opt_dlen = strtol(optarg, 0, 0); break; case 'e': opt_eff = 1; break; case 'E': opt_allowerr = 1; break; case 'F': opt_flags = parse_flags(optarg); if( opt_flags == -1 ){ fprintf(stderr, "unrecognised -F flag\n"); exit(1); } break; case 'i': opt_id = strtol(optarg, 0, 0); break; case 'l': opt_loops = strtol(optarg, 0, 0); break; case 'n': opt_pkts = strtol(optarg, 0, 0); break; case 'r': opt_rt = strtol(optarg, 0, 0); break; case 'R': opt_term++; break; case 't': opt_txahead = strtol(optarg, 0, 0); break; case 'T': opt_timeout = strtol(optarg, 0, 0); break; case 'u': opt_txdevnum = 1; break; case 'v': opt_verbose = parse_verbose(optarg); if( opt_verbose == -1 ){ fprintf(stderr, "unrecognised -v flag\n"); exit(1); } break; case 'X': opt_logger = optarg; break; case 'Y': opt_send = optarg; break; case 'z': opt_ldelay = strtoul(optarg, 0, 0); break; case 'h': default: usage(); exit(1); } } if (optind != (argc - 2)) { fprintf(stderr, "missing network interface option\n"); usage(); return 1; } opt_candev[0] = argv[optind]; opt_candev[1] = argv[optind+1]; signal(SIGINT, set_hup); signal(SIGHUP, set_hup); signal(SIGTERM, set_hup); if( opt_loops >= MAXLOOPS ){ fprintf(stderr, "error: -l must be less than %d\n", MAXLOOPS); exit(cleanup(1)); } if( opt_pkts >= MAXPKTNUM ){ fprintf(stderr, "error: -n must be less than %d\n", MAXPKTNUM); exit(cleanup(1)); } printf("CAN test %s %s ", opt_candev[0], opt_candev[1]); if( opt_term ){ if( ! set_can_term(opt_candev[0], 1) ){ fprintf(stderr, "can not enable termination %s\n", opt_candev[0]); exit(cleanup(80)); } if( opt_term > 1 ){ if( ! set_can_term(opt_candev[1], 1) ){ fprintf(stderr, "can not enable termination %s\n", opt_candev[1]); exit(cleanup(80)); } } } for(devidx=0; devidx<2; devidx++){ /* * Configure CAN opt_candev global options. Here only bus error * reporting. We don't care if it is not supported. */ if (can_do_stop(opt_candev[devidx]) < 0) { fprintf(stderr, "%s: failed to stop\n", opt_candev[devidx]); exit(cleanup(1)); } cm.mask = CAN_CTRLMODE_BERR_REPORTING; cm.flags = cm.mask; if (can_set_ctrlmode(opt_candev[devidx], &cm) < 0) { fprintf(stderr, "%s: failed to set controlmode\n", opt_candev[devidx]); exit(cleanup(1)); } if (can_do_start(opt_candev[devidx]) < 0) { fprintf(stderr, "%s: failed to start\n", opt_candev[devidx]); exit(cleanup(1)); } /* * Open CAN network devices */ sockfd[devidx] = socket(PF_CAN, SOCK_RAW, CAN_RAW); if (-1 == sockfd[devidx]) { perror("socket"); exit(cleanup(1)); } // Get the index of the network opt_candev strncpy(ifr.ifr_name, opt_candev[devidx], IFNAMSIZ); if (ioctl(sockfd[devidx], SIOCGIFINDEX, &ifr) == -1) { perror("ioctl"); exit(cleanup(1)); } // Bind the socket to the network opt_candev addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; rc = bind(sockfd[devidx], (struct sockaddr *)&addr, sizeof(addr)); if (-1 == rc) { perror("bind"); exit(cleanup(1)); } err_mask = CAN_ERR_MASK; /* all */ err_mask &= ~CAN_ERR_LOSTARB; if( setsockopt(sockfd[devidx], SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask)) < 0 ) { error("\nError in setsockopt "); exit(cleanup(1)); } /* Check if we have FD capable interface */ if( ioctl(sockfd[devidx], SIOCGIFMTU, &ifr) < 0 ) { error("\nError in SIOCGIFMTU "); exit(cleanup(1)); } can_mtu[devidx] = ifr.ifr_mtu; /* Try to switch the socket into CAN FD mode. This was needed for kcan with "fd off" to be able to receive frames sent for a kcan with "fd on". */ enable_canfd = 1; setsockopt(sockfd[devidx], SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd, sizeof(enable_canfd)); } if( (can_mtu[0] == CANFD_MTU) && (can_mtu[1] == CANFD_MTU) ){ /* Only if both support FD */ cmp_mtu = CANFD_MTU; if( opt_dlen == -1 ){ opt_dlen = 64; } if( opt_dlen > 64 ){ fprintf(stderr, "\nError: dlen does not fit into MTU\n"); exit(cleanup(1)); } } else { cmp_mtu = CAN_MTU; if( opt_dlen == -1 ){ opt_dlen = 8; } if( opt_dlen > 8 ){ fprintf(stderr, "\nError: dlen does not fit into MTU (no FD support)\n"); exit(cleanup(1)); } if( opt_brs ){ fprintf(stderr, "\nError: non-FD does not support bit rate switch\n"); exit(cleanup(1)); } } vprintf(VERB_IFPROP, " MTU=%d/%d ", can_mtu[0], can_mtu[1]); if( opt_send ){ struct canfd_frame txframe; int txdata; int txfail=0; if( sscanf(opt_send, "%x", &txdata) == 1 ){ txframe.can_id = 0; txframe.flags = 0; txframe.len = 8; for(i=0; i<8; i++){ txframe.data[i] = txdata; } printf("tx0 "); if( write(sockfd[0], (void *)&txframe, can_mtu[0]) < can_mtu[0] ){ fprintf(stderr, "opt_send: send error canx-netif\n"); txfail = 1; } txframe.can_id = 1; printf("tx1 "); if( write(sockfd[1], (void *)&txframe, can_mtu[1]) < can_mtu[1] ){ fprintf(stderr, "opt_send: send error cany-netif\n"); txfail = 1; } /* wait untils msgs are sended */ usleep(10*1000); } else { fprintf(stderr, "opt_send: wrong parameter\n"); txfail = 1; } if( txfail ){ printf("fail\n"); } else { printf("ok\n"); } exit(cleanup(txfail)); } if( opt_rt ){ /* Declare ourself as a real time task */ param.sched_priority = opt_rt; if(sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) { perror("\nERROR: sched_setscheduler failed"); exit(cleanup(2)); } /* Lock memory */ if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { perror("\nERROR: mlockall failed"); exit(cleanup(2)); } } random_dlc_init(opt_dlen); /* Create our transmission packets. bytes 0..1 : sequence number rest : random data */ time(&t); srand((unsigned int)t); for(txcnt=0; txcnt<MAXPKTNUM; txcnt++){ for(devidx=0; devidx<2; devidx++){ txdata[devidx][txcnt].can_id = (opt_id+devidx); if( opt_eff ){ txdata[devidx][txcnt].can_id |= CAN_EFF_FLAG; } txdata[devidx][txcnt].flags = 0; if( opt_brs ){ txdata[devidx][txcnt].flags |= CANFD_BRS; } txdata[devidx][txcnt].len = opt_dlen; if( opt_flags & FLAGS_RANDOMIZE ){ txdata[devidx][txcnt].len = random_dlc(); } txdata[devidx][txcnt].data[0] = (txcnt>> 8)&0xff; txdata[devidx][txcnt].data[1] = (txcnt>> 0)&0xff; for(i=2; i<opt_dlen; i++){ if( opt_flags & FLAGS_NORANDOMDATA ){ txdata[devidx][txcnt].data[i] = txcnt & 0xff; } else { txdata[devidx][txcnt].data[i] = rand() & 0xff; } } } } fflush(stdout); /* Run requested loops of test */ for(loop=0; loop<opt_loops; loop++){ rc = testloop(sockfd, opt_pkts); if( rc < 0 ){ printf("fail\n"); exit(cleanup(1)); } if( opt_flags & FLAGS_LEASTDOTS ){ if( (loop % 100) == 0 ){ printf("."); } } else if( opt_flags & FLAGS_LESSDOTS ){ if( (loop % 10) == 0 ){ printf("."); } } else { printf("."); } fflush(stdout); if( cantest_raw.status == CT_RAW_TERMINATED ){ if( ! sigint ){ printf(" (terminated) ok\n"); fflush(stdout); } exit(cleanup(0)); } if( cantest_raw.status != 0 ){ if( (cantest_raw.status == CT_RAW_BAD_ORDER) && (opt_flags & FLAGS_ALLOWOOOD) ) { /* allowed */ } else { if( (cantest_raw.status == CT_RAW_BAD_RECEIVE) || (cantest_raw.status == CT_RAW_BAD_ORDER) || (cantest_raw.status == CT_RAW_DUPLICATES) ) { printf("c (%d) fail\n", loop); } else if( cantest_raw.status == CT_RAW_TIMEOUT ){ printf("t (%d) fail\n", loop); } else if( cantest_raw.status == CT_RAW_NO_RESPONSE){ printf("T (%d) fail\n", loop); } else if( cantest_raw.status == CT_RAW_BUS_ERRORS){ printf("b (%d) fail\n", loop); } else if( cantest_raw.status == CT_RAW_READ_ERROR){ printf("r (%d) fail\n", loop); } else { printf("?%d (%d) fail\n", cantest_raw.status, loop); } if( opt_verbose & VERB_LOGMSG ){ dbg_log_print(); } if( opt_logger ){ readLogger(opt_logger, opt_pkts*2+20); } if( opt_flags & FLAGS_STOPONERROR ){ printf("Stopped. Ctrl-C to exit.\n"); fflush(stdout); while( 1 ){ sleep(1); } } exit(cleanup(1)); } } if( opt_ldelay ){ usleep(opt_ldelay*1000); } if( opt_flags & FLAGS_REFILLDATA ){ for(devidx=0; devidx<2; devidx++){ for(txcnt=0; txcnt<opt_pkts; txcnt++){ n = txdata[devidx][txcnt].len; for(i=2; i<n; i++){ txdata[devidx][txcnt].data[i] = rand() & 0xff; } } } } } printf(" ok\n"); return cleanup(0); }