/************************************************************************ * 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 /* FILE*, fopen(), .. */ #include /* strncmp(), .. */ #include /* read(), write(), .. */ #include /* tcgetattr(), .. */ #include /* F_GETFL, .. */ #include /* ioctl(), .. */ #include /* size_t, .. */ #include /* struct timeval */ #include /* 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__*/