
/* *********************************************************
/* ttyutil.c - routines to aid writing serial device drivers
/*
/* taken from The Master, Ken Carson
/* re-keyed by Russ Sasnett	6/15/87
/*
/* 6/18/87 RMS - added support for baud rate and flags on ttyutil_open()
/* 	         added ttyutil_makebaud()
/*		 added ttyutil_waitinput()
/*
/* ttyutil: contains routines to aid writing serial device drivers.
/* These print their own error messages on stderr.
/* To have complete info printed on stdout about each read or write,
/* declare 'extern int ttyutil_debug;' and set ttyutil_debug to 1.
/*
/* Externally visible functions are:
/*
/* fd = ttyutil_open( dev );
/* int	fd;		/* returned file descriptor */
/* char *dev;		/* "/dev/ttya" or other such name */
/*
/* cc = ttyutil_read( fd, buf, num, timeout );
/* int	cc;		/* actual read count, -1 for error */
/* int	fd;		/* device file descriptor */
/* char *buf;		/* place to put the input */
/* int	num;		/* how many bytes to read */
/* int	timeout;	/* millisecs to wait before quitting, 0 for indefinite */
/*	NOTE: catches SIGALRM if using timeout
/*
/* cc = ttyutil_write( fd, buf, num, pause );
/* int	cc;		/* actual write count, -1 for error */
/* int	fd;		/* device file descriptor */
/* char *buf;		/* stuff to write */
/* int	num;		/* how many bytes to write */
/* int	timeout;	/* millisecs to pause between writing each char */
/*	NOTE: catches SIGALRM if using pause
/*
/* cc = ttyutil_clear( fd );
/* int	cc;		/* number of characters discarded, -1 for error */
/* int	fd;		/* device file descriptor */
/*
*/

#include <sys/file.h>
#include <strings.h>
#include <sys/ioctl.h>
#include <sgtty.h>
#include <ctype.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>

int			ttyutil_debug = 0;	/* print lots of errmsgs */

typedef unsigned char 	byte;
char *strsave();

#define	MAXFDS	16	/* maximum # of open files */

static fd_set		readfds;	/* read mask for select() */
static int		nfds = 0;	/* # open files */
static int		fds[MAXFDS];	/* array of files */
static char		*fns[MAXFDS];	/* array of port names */
static int		ready[MAXFDS];	/* array of ready-to-read fd's */
static int		nbytes[MAXFDS];	/* array of #-of-bytes-to-read */
static int		initialized = 0;	/* arrays ready? */

/* -------------------------------------------------------------------- */
/* Open tty, get file descriptor, flush the port clear, set baud rates, */
/* set up the port for RAW communication.				*/
/*   fd = ttyutil_open( "/dev/tty02", 9600, 0 );			*/
/* -------------------------------------------------------------------- */

int
ttyutil_open( dev, baudrate, flags )

	char	*dev;		/* pathname */
	int	baudrate;	/* integer representation (9600 = 9600 baud) */
	int	flags;		/* flags to turn on (EVENP,ODDP,ANYP,etc.) */	
{	
struct sgttyb	tty_params;
int		fd;
int		speed;
int		i;

	if( nfds >= MAXFDS )
	{
		fprintf(stderr,"ttyutil_open: too many files open.\n");
		return(-1);
	}

	if( !initialized )
	{
		initialized = 1;
		for( i=0; i<MAXFDS; i++ )
		{
			fds[i] = -1;		/* no files open yet */
			fns[i] = NULL;		/* no names yet */
		}
	}

	if( ttyutil_debug )
		printf("ttyutil_open: file %s, %d baud\n",dev,baudrate);

	fd = open( dev, O_RDWR, 0 );
	if( fd < 0 )
	{
		fprintf(stderr,"ttyutil_open: open failed\n");
		return(-1);
	}
	if( ioctl(fd, TIOCGETP, &tty_params) == -1 )
	{
		fprintf(stderr,"ttyutil_open: ioctl TIOCGETP failed\n");
		return(-1);
	}
	if( (speed = ttyutil_makebaud( baudrate )) < 0 )
	{
		fprintf(stderr,"ttyutil_open: invalid baudrate %d\n",baudrate);
		return(-1);
	}
	if( !flags ) 
		flags = RAW;		/* default: no processing (no parity) */
	else
		flags |= CBREAK;	/* semi-cooked */

	tty_params.sg_ospeed = speed;	/* the system setting */
	tty_params.sg_ispeed = speed;
	tty_params.sg_flags = flags;

	if( ioctl(fd, TIOCSETP, &tty_params) == -1 )
	{
		perror("ttyutil_open: ioctl TIOCSETP");
		return(-1);
	}
	
	/* this doesn't seem to work - ken */
	if( ioctl(fd, TIOCFLUSH, &tty_params) == -1 )	
	{
		perror("ttyutil_open: ioctl TIOCFLUSH");
		return(-1);
	}

	/* get exclusive access on the port - RMS */
	if( ioctl(fd, TIOCEXCL, &tty_params) == -1 )
	{
		perror("ttyutil_open: ioctl TIOCEXCL");
		return(-1);
	}
	
	if( fcntl(fd,F_SETFL,fcntl(fd,F_GETFL) | O_NDELAY) == -1 )
	{
		perror("ttyutil_open: setting O_NDELAY flag failed");
		return(-1);
	}

	if( (i=fdindex(fd)) != -1 )	/* duplicate fd !! */
	{
		fprintf(stderr,
		"ttyutil_open: WARNING -- duplicate file descriptor.\n");
		fprintf(stderr,
		"              Ports %s and %s in intenal table.\n",
		fns[i],dev);
		fprintf(stderr,
		"              Table corrrupted; use ttyutil_close()\n");
	}

	fds[nfds] = fd;
	fns[nfds] = strsave(dev);
	FD_SET(fd,&readfds);	/* add to mask for select() events */
	++nfds;
	return(fd);
}
/* end ttyutil_open */

/* -------------------------------------------------------------------- */
/* Close a tty opened by ttyutil_open()					*/
/*   success = ttyutil_close(fd);					*/
/* -------------------------------------------------------------------- */

int
ttyutil_close( fd )

	int	fd;	/* a file desc returned by ttyutil_open() */
{
int	i;

	if( (i=fdindex(fd)) == -1 )
	{
		fprintf(stderr,
		"ttyutil_close: file not opened by ttyutil_open()\n");
		return(-1);
	}

	fds[i] = -1;
	if( fns[i] )
		free( (char *) fns[i] );
	fns[i] = NULL;
	FD_CLR(fd,&readfds);	/* remove from mask for select() events */
	--nfds;	
	return (0);
}

/* -------------------------------------------------------------------- */
/* cc = ttyutil_testinput( fd );					*/
/*    returns number of chars in input buffer				*/
/* -------------------------------------------------------------------- */

int
ttyutil_testinput( fd )

{
int	i;

	if( ioctl(fd, FIONREAD, &i) == -1 )
	{
		perror("ttyutil_testinput: error with FIONREAD");
		return(-1);
	}
	return(i);
}

/* -------------------------------------------------------------------- */
/* Read commands from tty						*/
/*   cc = ttyutil_read( fd, buf, num, timeout );			*/
/* -------------------------------------------------------------------- */

static int	Timeout = 0;	/* flag indicating a timeout */

int
ttyutil_read( fd, buf, readnum, timeoutval )

	int	fd;
	char	*buf;
	int	readnum;
	int	timeoutval;
{
int	num = 0;
int	i;

	Timeout = 0;

	if( timeoutval > 0 )
		set_timeout( timeoutval, &Timeout );

	for( i=0; i<readnum && !Timeout; )	/* wait till enough char available */
		if( ioctl(fd, FIONREAD, &i) == -1 )
			perror("ttyutil_read: error with FIONREAD");

	if( !Timeout )
		num = read( fd, buf, readnum );
	if( timeoutval > 0 )
		set_timeout( 0, &Timeout );

	if( ttyutil_debug )
	{
		if( Timeout==1 )	/* only care about THIS timeout */
			printf("ttyutil_read: TIMEOUT\n");
		else if( num != -1 )
		{
			printf("ttyutil_read: read");
			for( i=0; i<num; i++ )
				printf(" [%x]",(byte)buf[i]);
			printf("\n");
		}
	}

	if( Timeout==1 )		/* only care about THIS timeout */
		return(-2);

	if( num == -1 )
		perror("ttyutil_read: read err, probable type");

	return(num);
}
/* end ttyutil_read */

/* -------------------------------------------------------------------- */
/* Write buf to tty							*/
/*   cc = ttyutil_write( fd, buf, num, pause );				*/
/* -------------------------------------------------------------------- */

int
ttyutil_write( fd, buf, writenum, pauselength )

	int	fd;		/* device file descriptor */
	char	*buf;		/* stuff to write */
	int	writenum;	/* how many bytes to write */
	int	pauselength;	/* millisecs to pause between writing each char */

{
int	i;
int	num;

	if( ttyutil_debug )
	{
		printf("ttyutil_write: writing");
		for( i=0; i<writenum; i++ )
			printf(" [%x]",(byte)buf[i]);
		printf("\n");
	}

	for( i=0; i<writenum; i++ )
	{
		num = write( fd, &buf[i], 1 );
		if( num != 1 )
		{
			perror("ttyutil_write: write");
			return(-1);
		}
		if( pauselength > 0 )
			mpause(pauselength);
	}

	return(writenum);
}
/* end ttyutil_write */

/* -------------------------------------------------------------------- */
/* Clear input buffer							*/
/*   cc = ttyutil_clear(fd);						*/
/* -------------------------------------------------------------------- */

int
ttyutil_clear( fd )

	int	fd;		/* device file descriptor */
{
int	i;
int	num;
char	c;

/*	ioctl( fd, TIOCFLUSH, 0 );	doesn't work anyway */

	if( ioctl( fd, FIONREAD, &num ) == -1 )
	{
		perror("ttyutil_clear: error with FIONREAD");
		return (-1);
	}

	for( i=num; i>0; i-- )
		if( read(fd,&c,1) == -1 )
		{
			perror("ttyutil_clear: read error");
			return(-1);
		}

	return (num);
}
/* end ttyutil_clear */

/* ------------------------------------------------------------------- */
/* ttyutil_waitinput()						       */
/* Return the file descriptors of all of the files ready for reading   */
/*  if msecs is 0, just polls and returns; else waits up to msecs time */
/* ------------------------------------------------------------------- */

int
ttyutil_waitinput( rdyfiles, rdybytes, nrdy, msecs )

	int	**rdyfiles;	/* RETURNed array ptr to ready[] */
	int	**rdybytes;	/* RETURNed array of #-bytes-to-read */
	int	*nrdy;		/* RETURNed number of ready files */
	int	msecs;		/* # of millisecs before timeout */
{
struct timeval 	timeout;
int 		i, num, n, toread;
fd_set		temp;

	timeout.tv_sec = 0;
	timeout.tv_usec = msecs * 1000;

	/* copy the descriptor set so we don't trash it */
	bcopy( (char *) &readfds, (char *) &temp, sizeof(fd_set) );

	num = select( nfds, &temp, NULL,NULL, &timeout );
	if( num == -1 )
	{
		perror("ttyutil_waitinput: select error");
		return(-1);
	}

	*nrdy = num;
	for( i=0; i<nfds; i++ )
	{
		if( fds[i] != -1 && FD_ISSET(fds[i],&temp) )
		{
			ready[n] = fds[i];
			if( ioctl( fds[i], FIONREAD, &toread ) == -1 )
			{
				perror(
				"ttyutil_waitinput: error with FIONREAD");
				return (-1);
			}
			else 
				nbytes[n] = toread;
		}
		else
		{
			ready[n] = -1;
			nbytes[n] = 0;	
		}
	}

	*rdybytes = nbytes;
	*rdyfiles = ready;
	return(0);
}

/* -------------------------------------------------------------------- */
/* Convert integer baud rate to system value				*/
/*   baud = ttyutil_makebaud(baudrate);					*/
/* -------------------------------------------------------------------- */

int
ttyutil_makebaud( baudrate )

	int	baudrate;
{

	switch( baudrate ) {
		case 0:
			return (B0);
			break;
		case 50:
			return (B50);
			break;
		case 75:
			return (B75);
			break;
		case 110:
			return (B110);
			break;
		case 134:
			return (B134);
			break;
		case 150:
			return (B150);
			break;
		case 200:
			return (B200);
			break;
		case 300:
			return (B300);
			break;
		case 600:
			return (B600);
			break;
		case 1200:
			return (B1200);
			break;
		case 1800:
			return (B1800);
			break;
		case 2400:
			return (B2400);
			break;
		case 4800:
			return (B4800);
			break;
		case 9600:
			return (B9600);
			break;
/*		case 19200:
			return (B19200);
			break;
		case 38400:
			return (B38400);
			break;
*/		default:
			return (-1);
			break;		
		}
}

static
char *
strsave(s)

	char	*s;
{
char	*p;
char	*calloc();
int 	len;

	if( s == NULL )
		return (NULL);

	len = strlen(s);
	if( len > 0 )
	{
		if( (p = calloc(1,len+1)) != NULL )
			strcpy(p,s);
		return (p);
	}
	else
		return (NULL);
}

static int
fdindex( fd )

	int	fd;	/* the file descriptor */
{
int	i;

	for( i=0; i<MAXFDS; i++ )
		if( fd == fds[i] )
			return(i);
	return(-1);			/* not found */
}
