ic35link/src/comio.c

485 lines
14 KiB
C

/************************************************************************
* Copyright (C) 2000 Thomas Schulz *
* */
static char rcsid[] =
"$Id: comio.c,v 1.13 2001/03/03 16:30:45 tsch Rel $"; /*
* *
* IC35 serial communication
* *
*************************************************************************
* *
* conditional compile on NO_LOGSIM: if #defined, the simulated *
* communication is NOT supported, default WITH com-simulation. *
* conditional compile on __STRICT_ANSI__: if #defined, substitute *
* functions, which are not available with the ANSI C standard. *
* (compilation with 'gcc -ansi ..' does #define __STRICT_ANSI__) *
* *
* simulated communication #ifndef NO_LOGSIM *
* com_siminit initialize communications with simulation file *
* com_simexit local: leave simulated communication *
* com_simul local: report if simulation active *
* com_simrecv local: simulate receive from simulation file *
* real communication
* com_setsigs local: set RS232 output signals *
* com_settimeout set receive timeout, return previous *
* com_init initialize serial communication device *
* com_waitnice lower process priority when using com_sendw() *
* com_sendw send datablock to comm.device with waiting *
* com_send send datablock to comm.device *
* com_recv receive datablock from comm.device *
* com_exit close serial communication device *
* *
************************************************************************/
#include <stdio.h> /* FILE*, fopen(), .. */
#include <string.h> /* strncmp(), .. */
#include <unistd.h> /* read(), write(), .. */
#include <termios.h> /* tcgetattr(), .. */
#include <fcntl.h> /* F_GETFL, .. */
#include <sys/ioctl.h> /* ioctl(), .. */
#include <sys/types.h> /* size_t, .. */
#include <sys/time.h> /* struct timeval */
#include <signal.h> /* sigaction(), .. */
#include "util.h" /* LPRINTF(), .. */
#include "comio.h"
NOTUSED(rcsid)
/* ==================================== */
/* simulated communication */
/* ==================================== */
/*
* uses post-processed log of real communication with IC35:
* WR nn xx xx xx ...
* RD nn xx xx xx ...
* and "receives" data using the "RD nn" lines.
*/
#ifndef NO_LOGSIM
static FILE * simfp = NULL;
static int
_com_siminit( char * s_fname ) /* init simulated comm */
{
if ( s_fname && *s_fname ) {
simfp = fopen( s_fname, "r" );
if ( simfp == NULL )
return ERR;
}
return OK;
}
void
com_siminit( char * s_fname )
{
if ( _com_siminit( s_fname ) != OK )
fatal( "cannot open simulation file: %s", s_fname );
}
static void
com_simexit( void ) /* leave simulated comm */
{
if ( simfp ) {
fclose( simfp );
simfp = NULL;
}
}
static bool
com_simul( void ) /* report if simul active */
{
return (bool)( simfp != NULL );
}
static int
com_simrecv( uchar * buff, size_t blen ) /* receive from simul.file */
{
static int simrlen = 0;
static int simridx = 0;
bool do_check;
uchar * bptr;
int chr, n, rbyte;
char xdir[8];
if ( buff == NULL || blen == 0 ) /* sanity */
return 0;
memset( buff, 0, blen); /* clear buffer sets dummy bytes */
bptr = buff; do_check = FALSE;
while ( bptr < buff + blen ) {
/* forward to or check next "RD nn" line */
if ( simridx >= simrlen ) {
for ( ; ; ) {
while ( (chr = fgetc( simfp )) != '\n' )
if ( chr < 0 )
return ERR;
if ( fscanf( simfp, "%s %d", xdir, &simrlen ) == 2
&& strncmp( xdir, "RD", 2 ) == 0 )
break;
if ( do_check )
return bptr - buff;
}
do_check = TRUE;
simridx = 0;
}
/* read bytes from "RD nn" line */
for ( ; simridx < simrlen; ++simridx ) {
if ( bptr >= buff + blen )
return blen;
ungetc( chr = fgetc( simfp ), simfp ); /* avoid fscanf() eat \n */
if ( chr == '\n'
|| fscanf( simfp, "%x", &rbyte ) != 1 )
break;
*bptr++ = (uchar)rbyte;
}
/* dummy non-logged recv bytes */
n = min( blen - (bptr - buff), simrlen - simridx);
simridx += n;
bptr += n;
}
return bptr - buff;
}
#endif /*NO_LOGSIM*/
/* ==================================== */
/* real communication */
/* ==================================== */
static int com_fd = -1;
static int com_tmo = 500; /* timeout 500 ms */
/* local: set RS232 output signals
* -------------------------------
*/
static void
com_setsigs( int sigs )
{
int flags;
if ( com_fd >= 0
&& ioctl( com_fd, TIOCMGET, &flags ) == 0 ) {
flags &= ~(TIOCM_DTR|TIOCM_RTS);
flags |= (TIOCM_DTR|TIOCM_RTS) & sigs;
ioctl( com_fd, TIOCMSET, &flags );
LPRINTF(( L_NOISE, "com_setsigs(%08X) DTR %s RTS %s",
sigs, sigs & TIOCM_DTR ? "ON " : "off",
sigs & TIOCM_RTS ? "ON " : "off" ));
}
}
/* local: log state of RS232 signals
* ---------------------------------
*/
static void
_com_sigchg( void )
{
static int oflags = -1;
static struct {
char * name;
int sig;
} sigtab[] = {
{ "CTS", TIOCM_CTS },
{ "DCD", TIOCM_CAR },
{ "DSR", TIOCM_DSR },
{ "RI", TIOCM_RNG },
{ NULL, 0 }
}, *psig;
int flags;
char sigtext[48];
if ( com_fd >= 0
&& ioctl( com_fd, TIOCMGET, &flags ) == 0
&& ( ((oflags ^ flags) & (TIOCM_CTS|TIOCM_CAR|TIOCM_DSR|TIOCM_RNG)) != 0
|| oflags == -1 ) ) {
strcpy( sigtext, "" );
for ( psig = sigtab; psig->name; ++psig )
sprintf( sigtext+strlen(sigtext), " %s %s%s",
psig->name,
flags & psig->sig ? "ON" : "off",
((oflags ^ flags) & psig->sig) && oflags != -1 ? "*" : "" );
LPRINTF(( L_NOISE, "com signals:%s", sigtext ));
oflags = flags;
}
}
/* set,get receive timeout
* -----------------------
* return previous timeout. zero or negative timeout argument 'msec'
* will not be set and can be used to enquire current timeout.
*/
int
com_settimeout( int msec )
{
int old_tmo = com_tmo;
old_tmo = com_tmo;
if ( msec > 0 )
com_tmo = msec;
if ( old_tmo != com_tmo )
LPRINTF(( L_NOISE, "com_settimeout %d -> %d", old_tmo, com_tmo ));
return old_tmo;
}
/* initialize comm.device
* ----------------------
* open in O_NONBLOCK mode to avoid hanging on inactice DCD signal,
* finally set back to blocking mode.
* set line parameters to 115200,N,8,2 and raw mode, 2 stopbits are
* needed to avoid IC35 receive errors with MMCard operations.
* raise DTR and RTS signals.
*/
#ifdef __STRICT_ANSI__
#define cfmakeraw(tiop) \
(tiop)->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); \
(tiop)->c_oflag &= ~(OPOST); \
(tiop)->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); \
(tiop)->c_cflag &= ~(CSIZE|PARENB); \
(tiop)->c_cflag |= CS8;
#endif
int
com_init( char * devname )
{
int i, flags;
struct termios tio;
#ifndef NO_LOGSIM
if ( com_simul() ) {
com_fd = -1;
return OK;
}
#endif
/* open serial communication device */
if ( (com_fd = open( devname, O_RDWR|O_NONBLOCK|O_NOCTTY )) == -1 ) {
return ERR;
}
if ( !isatty( com_fd ) ) {
close( com_fd );
com_fd = -1;
return ERR;
}
/* set linepars 11500,N,8,2 and raw mode */
/* HUPCL to clear DTR,RTS on last close */
tcgetattr( com_fd, &tio );
tio.c_oflag = 0;
tio.c_iflag = IGNBRK | IGNPAR;
tio.c_cflag = CREAD | CLOCAL | CS8 | HUPCL | CSTOPB;
cfsetispeed( &tio, B115200 );
cfsetospeed( &tio, B115200 );
tio.c_lflag = NOFLSH;
cfmakeraw( &tio );
for ( i = 0; i <= NCCS; ++i ) tio.c_cc[i] = 0;
tio.c_cc[VMIN] = 1;
tio.c_cc[VTIME] = 0;
tcsetattr( com_fd, TCSANOW, &tio );
/* set line signals DTR on, RTS off */
com_setsigs( TIOCM_DTR|TIOCM_RTS );
/* back to blocking mode for timeout */
if ( (flags = fcntl( com_fd, F_GETFL, 0 )) != -1 ) {
flags &= ~O_NONBLOCK;
fcntl( com_fd, F_SETFL, flags );
}
_com_sigchg();
return OK;
}
/* send datablock with waiting
* ---------------------------
* IC35 gets receive receive errors if datablocks of more than 16 bytes
* are sent at full speed of 115200 baud, using 2 stopbits increases
* this size limit to 29 bytes.
* the problem occurred only with IC35 MMCard operations, import and
* synchronize PIM-data don't suffer this limitation.
* as a workaround do delay some time after sent every couple of bytes.
* the delay is done with busy waiting using gettimeofday(), because
* interrupt controlled delays are either too long (at least 10 msec
* with an "itimer" at Linux-2.x timer interrupt frequency of 100 Hz)
* or require root privilege (nanosleep() or fast RTC interrupts).
* other methods like usleep(), nanosleep(), select() yielded delays
* of ca. 20..30 msec and were therefore also not chosen.
* (see getitimer(2), sigaction(2), nanosleep(2), usleep(3), select(2)
* for reference).
* to avoid too much CPU hogging during busywait delays, the process
* scheduling priority may be lowered using nice(2) when comsendw()
* is called with a block of more than NICE_MINBLEN bytes.
* com_waitnice() is used to enable the priority lowering: each call
* of com_waitnice() causes one nice() systemcall.
* the maximum throughput measured with write file to IC35 MMCard was
* ca. 1740 b/s with 2kB buffersize (with "itimer" it was ca. 1350 b/s,
* with usleep() delay it was only ca. 600 b/s).
* measurements on a Pentium-I/133MHz showed that
* for blocks of 1 8 16 24 28 bytes
* the delay of 165 1570 3180 4795 5620 usec was needed at least.
* measurements on a Pentium-III/500MHz (by Harald Becker) showed that
* for blocks of 1 16 bytes
* the delay of 187 3212 usec was needed at least.
*/
#define NICE_MINBLEN 32 /* min. block length for nice() */
#define WAIT_BLKSIZE 16 /* number of bytes per write() */
#define WAIT_USDELAY 3250 /* usec to wait before write() */
static int _waitnice_inc; /* increment to use for nice(2) */
static void /* busy wait some microseconds */
_wait_usec( long usec ) /* contributed by Harald Becker */
{
long elapsed; /* elapsed delay in usec */
struct timeval tbeg, tnow; /* start time and current time */
gettimeofday( &tbeg, NULL ); /* get start time of delay loop */
do { /* loop .. */
gettimeofday( &tnow, NULL ); /* get current time */
if ( (elapsed = tnow.tv_sec - tbeg.tv_sec) ) /* seconds diff */
elapsed *= 1000000; /* to usec if nonzero */
elapsed += tnow.tv_usec - tbeg.tv_usec; /* add microsecs diff */
} while( elapsed < usec ); /* .. until specified usec over */
}
void /* export interface to store increment */
com_waitnice( int inc ) /* 'inc' for lowering process priority */
{
_waitnice_inc = inc;
}
int /* send data to communication device */
com_sendw( uchar * data, size_t dlen ) /* with busywait delays */
{
uchar * dptr;
int slen;
#ifndef NO_LOGSIM
if ( com_simul() )
slen = dlen;
else
#endif
{
if ( com_fd < 0 )
return ERR;
LPRINTF(( L_DEBUG, "com_sendw(%p,%u) ..", data, dlen ));
if ( dlen >= NICE_MINBLEN && _waitnice_inc != 0 ) {
nice( _waitnice_inc ); /* lower own process priority */
_waitnice_inc = 0; /* no more nice() before next */
} /* com_waitnice() tells again */
dptr = data;
while ( (slen = data+dlen - dptr) > 0 ) {
if ( slen > WAIT_BLKSIZE ) slen = WAIT_BLKSIZE;
_wait_usec( WAIT_USDELAY );
if ( write( com_fd, dptr, slen ) != slen )
break;
dptr += slen;
}
slen = dptr - data;
}
LDUMP(( L_NOISE, data, dlen,
"com_sendw(%p,%u) = %d", data, dlen, slen ));
return slen;
}
/* send datablock to comm.device
* -----------------------------
*/
int
com_send( uchar * data, size_t dlen )
{
int slen;
#ifndef NO_LOGSIM
if ( com_simul() )
slen = dlen;
else
#endif
{
if ( com_fd < 0 )
return ERR;
LPRINTF(( L_DEBUG, "com_send(%p,%u) ..", data, dlen ));
slen = write( com_fd, data, dlen );
}
LDUMP(( L_NOISE, data, dlen,
"com_send(%p,%u) = %d", data, dlen, slen ));
return slen;
}
/* receive datablock from comm.device
* ----------------------------------
* use select() to wait for data, accumulate data with read()
* until buffer 'buff' gets full or receive timeout occurs.
*/
int
com_recv( uchar * buff, size_t blen )
{
fd_set rfds;
struct timeval tmo;
uchar *rptr, *rend;
int rlen;
#ifndef NO_LOGSIM
if ( com_simul() )
rlen = com_simrecv( buff, blen );
else
#endif
{
if ( com_fd < 0 )
return ERR;
rend = (rptr = buff) + blen;
do {
FD_ZERO( &rfds );
FD_SET( com_fd, &rfds );
tmo.tv_sec = com_tmo / 1000;
tmo.tv_usec = (com_tmo % 1000) * 1000;
if ( select( com_fd+1, &rfds, NULL, NULL, &tmo ) > 0
&& FD_ISSET( com_fd, &rfds ) ) {
_com_sigchg();
rlen = read( com_fd, rptr, rend - rptr );
LPRINTF(( L_DEBUG, "com_recv: read(com,rptr,%d) = %d",
rend-rptr, rlen ));
if ( rlen > 0 )
rptr += rlen;
} else {
rlen = 0;
}
} while ( rlen > 0 && rptr < rend );
rlen = rptr - buff;
}
if ( rlen > 0 )
LDUMP(( L_NOISE, buff, rlen,
"com_recv(%p,%u) = %d", buff, blen, rlen ));
else
LPRINTF(( L_NOISE, "com_recv(%p,%u) = %d", buff, blen, rlen ));
return rlen;
}
/* close comm.device
* -----------------
* leave simulated communication if active.
* clear RS232 signals DTR and RTS and close comm.device.
*/
void
com_exit( void )
{
#ifndef NO_LOGSIM
com_simexit();
#endif
if ( com_fd < 0 )
return;
com_setsigs( 0 ); /* DTR off, RTS off */
_com_sigchg();
close( com_fd );
com_fd = -1;
}
/* substitute for usleep()
* -----------------------
* the usleep() function is unavailable on compilation with "-ansi",
* which #defines __STRICT_ANSI__
*/
#ifdef __STRICT_ANSI__
void
usleep( unsigned long usec )
{
struct timeval tmo;
tmo.tv_sec = usec / 1000000;
tmo.tv_usec = usec % 1000000;
select( 0, NULL, NULL, NULL, &tmo );
}
#endif /*__STRICT_ANSI__*/