WG: mcp251xfd: rx frame truncation (1/2)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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, &param) == -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);
}

[Index of Archives]     [Automotive Discussions]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]     [CAN Bus]

  Powered by Linux