/*
 * Copyright 1988, 1989, 1990, 1991 Massachusetts Institute of Technology
 */

/* *********************************************************
 * ttyutil.c - routines to aid writing serial device drivers
*/
#include"structs.h"

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

typedef unsigned char 	byte;
char *strsave();

#define	MAXFDS	16	/* maximum # of open files */
#ifndef B19200
#define B19200 EXTA
#endif /* B19200 */
#ifndef B38400
#define B38400 EXTB
#endif /* B38400 */
#ifdef inter386
#ifndef FIONREAD
#define FIONREAD (('f'<<8)|3) 
#endif /* FIONREAD */
#endif /* inter386 */

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, temp_fd;
  int		speed;
  int		i;
  int           ctlfd;
  int openflags = 0;
  
  if( nfds >= MAXFDS )
    {
      bsdsyslog(LOG_NOTICE,"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);*/
#ifdef O_NOCTTY
  openflags |= O_NOCTTY;
#endif /* O_NOCTTY */

  fd = open( dev, O_RDWR, openflags );
  if( fd < 0 )
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_open: open failed\n");
      return(-1);
    }

  /* since we have no controlling terminal, and we just opened
     a terminal file, we have 'inherited' that file as our
     controlling terminal; so, reset our proc to think it no
     longer has one; if the device toggles the carrier detect
     line (as the Panasonic apparently does), we'll get a SIGHUP
     that we don't really want. of course, we can't do this
     when we're running in 'DEBUG' mode.
  */

  if(!DEBUG)
  {
	ctlfd = open("/dev/tty",O_RDWR);
	if (ctlfd >= 0) {
#ifndef SYSV
	      ioctl(ctlfd,TIOCNOTTY,0);
#else
	     /* cc = 0;*/
	      /*ioctl(i, TIOCSPGRP, &cc);*/  /* Where is the definition???? */
#endif /* SYSV */
	      close(ctlfd);
        }
  }
  
  if( ioctl(fd, TIOCGETP, &tty_params) == -1 )
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_open: ioctl TIOCGETP failed\n");
      close(fd);
      return(-1);
    }
  if( (speed = ttyutil_makebaud( baudrate )) < 0 )
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_open: invalid baudrate %d\n",baudrate);
      close(fd);
      return(-1);
    }
#ifndef SYSV
  if( !flags ) 
#endif /* SYSV */
    flags = RAW;	/* default: no processing (no parity) */
#ifndef SYSV
  else
    flags |= CBREAK;	/* semi-cooked */
#endif /* SYSV */

  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 )
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_open: ioctl TIOCSETP failed");
      close(fd);
      return(-1);
    }
  
  /* this doesn't seem to work - ken */
  /* that's because he had the arguments wrong, but I have
     no idea if it works now - danapple 1/13/89 */
#ifndef SYSV
  i = 0;  /* 0 says flush both i/o queues */
  if( ioctl(fd, TIOCFLUSH, &i) == -1 )	
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_open: ioctl TIOCFLUSH");
/*      close(fd);
      return(-1);*/
    }
  if( fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) | FNDELAY) == -1 )
    {
      bsdsyslog(LOG_NOTICE,
		"ttyutil_open: setting FNDELAY flag failed");
/*      close(fd);
      return(-1);*/
    }
#else
  if( ioctl(fd, TCFLSH, 2) == -1 )   /* 2 says flush both i/o queues */	
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_open: ioctl TCFLSH failed");
/*      close(fd);
      return(-1);*/
    }
  if( fcntl(fd,F_SETFL,fcntl(fd,F_GETFL) | O_NDELAY) == -1 )
    {
      bsdsyslog(LOG_NOTICE,
		"ttyutil_open: setting O_NDELAY flag failed");
/*      close(fd);
      return(-1);*/
    }
#endif /* SYSV */
#ifdef TIOCEXCL
  if( ioctl(fd, TIOCEXCL, &tty_params) == -1 )
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_open: ioctl TIOCEXCL failed\n");
/*      close(fd);
      return(-1);*/
    }
#endif /* TIOCEXCL */

/* RMS; I took TIOCSPGRP out because some terminal drivers will
   pass on the HUPs if you set their process group */

#ifdef notdef
#ifdef TIOCSPGRP
  i = getpid();
  if (ioctl(fd, TIOCSPGRP, &i) == -1) {
    bsdsyslog(LOG_NOTICE, "ttyutil_open: ioctl TIOCSPGRP failed");
#ifdef OBSOLETE
    /* This really isn't fatal, but it should be noted. */
    close(fd);
    return(-1);
#endif /* OBSOLETE */
  }
#endif /* TIOCSPGRP */
#endif /* notdef */

/* try to prevent terminal from sending us HUP signals */
#ifdef TIOCLBIS
#ifdef LNOHANG
  i = LNOHANG;
  if( ioctl(fd, TIOCLBIS, &i) == -1 )
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_open: ioctl TIOCLBIS failed\n");
/*      close(fd);
      return(-1);*/
    }
#endif /* LNOHANG */
#endif /* TIOCLBIS */

/* try to set Data Terminal Ready bit in the terminal driver */
#ifdef TIOCSDTR
  if( ioctl(fd, TIOCSDTR, 0) == -1 )
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_open: ioctl TIOCSDTR failed\n");
/*      close(fd);
      return(-1);*/
    }
#endif /* TIOCLBIS */

  
  if( (i=fdindex(fd)) != -1 )	/* duplicate fd !! */
    {
      bsdsyslog(LOG_NOTICE,
		"ttyutil_open: WARNING -- duplicate file descriptor.\n");
      bsdsyslog(LOG_NOTICE,
		"Ports %s and %s in internal table.\n",
		fns[i],dev);
      bsdsyslog(LOG_NOTICE,
		"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 )
	{
		bsdsyslog(LOG_NOTICE,
		"ttyutil_close: file not opened by ttyutil_open()\n");
		return(-1);
	}
        close(fds[i]);
	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;
#ifndef inter386
  if( ioctl(fd, FIONREAD, &i) == -1 )
    {
      bsdsyslog(LOG_NOTICE,"ttyutil_testinput: error with FIONREAD: %m");
      return(-1);
    }
#else /* inter386 */
  if ((i = ioctl(fd, FIONREAD, NULL)) == -1)
    {
      bsdsyslog(LOG_NOTICE, "ttyutil_testinput: error with FIONREAD: %m");
      return(-1);
    }
#endif /* inter386 */
  return(i);
}

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


int
ttyutil_read( fd, buf, readnum, timeoutval )
	int	fd;
	char	*buf;
	int	readnum;
	int	timeoutval;
{
  int num_read = 0, num_to_read = 0, cc;
  struct timeval end_read, timer_val, *point_time;
  struct fd_set read_set, perm_set;
#ifdef TRAP_BUG
    bsdsyslog(LOG_INFO, "entered ttyutil_read");
    bsdsyslog(LOG_INFO, "fd = %d", fd);
    bsdsyslog(LOG_INFO, "buf = %d", buf);
#endif /* TRAP_BUG */
  FD_ZERO(&perm_set);
  FD_SET(fd, &perm_set);
#ifdef TRAP_BUG
    bsdsyslog(LOG_INFO, "setup perm_set");
#endif /* TRAP_BUG */
  if (timeoutval) {
    end_read.tv_sec = timeoutval/1000;
    end_read.tv_usec = (timeoutval%1000) * 1000; /* msec to usec */
    timer_val = end_read;
    point_time = &timer_val;
#ifdef TRAP_BUG
    bsdsyslog(LOG_INFO, "setup timer_val");
#endif /* TRAP_BUG */

  }
  else point_time = (struct timeval *)NULL;
  while (num_read < readnum) {
#ifdef TRAP_BUG
    bsdsyslog(LOG_INFO, "beginning of outer loop");
#endif /* TRAP_BUG */
    while (1) {
#ifdef TRAP_BUG
      bsdsyslog(LOG_INFO, "beginning of inner loop");
#endif /* TRAP_BUG */
      read_set = perm_set;
    /* If select(2) changes timer_val, then ttyutil_read has a total timeout
    as passed in, otherwise, the timeout is per incoming character packet */
      cc = select(fd+1, &read_set, 0, 0, point_time);
#ifdef TRAP_BUG
      bsdsyslog(LOG_INFO, "out of select");
#endif /* TRAP_BUG */

      if (cc > 0) break;
      if (cc == -1) {
	if (errno == EINTR) continue;
	else return(-1);
      }
      if (cc == 0) return(-1);
    }
    num_to_read = ttyutil_testinput(fd);
#ifdef TRAP_BUG
      bsdsyslog(LOG_INFO, "returned from ttyutil_testinput: %d", num_to_read);
#endif /* TRAP_BUG */
    if (num_read + num_to_read > readnum) 
      num_to_read = readnum - num_read;
#ifdef TRAP_BUG
    bsdsyslog(LOG_INFO, "computed num_to_read: %d", num_to_read);
    bsdsyslog(LOG_INFO, "num_read = %d", num_read);
    bsdsyslog(LOG_INFO, "fd = %d", fd);
    bsdsyslog(LOG_INFO, "buf = %d", buf);
    bsdsyslog(LOG_INFO, "buf+num_read = %d", buf+num_read);
#endif /* TRAP_BUG */
    cc = read(fd, buf + num_read, num_to_read);
#ifdef TRAP_BUG
      bsdsyslog(LOG_INFO, "read data");
#endif /* TRAP_BUG */
    if (cc == -1) return(-1);
    num_read+=cc;
  }
  return(num_read);
}
/* 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 (pauselength) {
    for( i=0; i<writenum; i++ )	{
      num = write( fd, &buf[i], 1 );
      if( num != 1 )
	{
	  return(-1);
	}
      mpause(pauselength);
    }
  }
  else {
    num = write( fd, buf, writenum);
    if (num != writenum) {
      return(-1);
    }
  }
  return(writenum);
}
/* end ttyutil_write */

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

int
ttyutil_clear( fd )
	int	fd;		/* device file descriptor */
{
  int	num;
  char	c;
  int   i;
  int   sum = 0;

  /* now flushes only the input queue */

#ifndef SYSV
  num = FREAD; /* from <sys/file.h> */
  ioctl( fd, TIOCFLUSH, &num );	
#else /* SYSV */
  ioctl( fd, TCFLSH, 0);  /* OLD BUG: this used to say '&num' */
#endif /* SYSV */

  /* this is just in case the ioctl() fails ... */

  while ((num=ttyutil_testinput(fd)) > 0) {
    for(i=0;i<num;i++)
      if( read(fd,&c,1) == -1 )
      {
	bsdsyslog(LOG_NOTICE,"ttyutil_clear: read error: %m");
	return(-1);
      }
    sum += num;
  }
  return (sum);
}
/* end ttyutil_clear */

#ifdef OBSOLETE

/* ------------------------------------------------------------------- */
/* 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 = 0, 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);
}
#endif /* OBSOLETE */
/* -------------------------------------------------------------------- */
/* 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;		
		}
/*NOTREACHED*/
}

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);
}

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 */
}
