// TTY_IO.cpp,v 4.34 2002/03/28 15:01:21 schmidt Exp

#include "ace/TTY_IO.h"

ACE_RCSID(ace, TTY_IO, "TTY_IO.cpp,v 4.34 2002/03/28 15:01:21 schmidt Exp")

ACE_TTY_IO::Serial_Params::Serial_Params (void)
{
  ACE_OS::memset (this, 0, sizeof *this);
}

// Interface for reading/writing serial device parameters

int
ACE_TTY_IO::control (Control_Mode cmd,
                     Serial_Params *arg) const
{
#if defined (ACE_HAS_TERM_IOCTLS)
#if defined(TCGETS)
  struct termios devpar;
#elif defined(TCGETA)
  struct termio devpar;
#else
  errno = ENOSYS;
  return -1;
#endif
  u_long c_iflag;
  u_long c_oflag;
  u_long c_cflag;
  u_long c_lflag;
  // u_long c_line;
  u_char ivmin_cc4;
  u_char ivtime_cc5;

  c_iflag=0;
  c_oflag=0;
  c_cflag=0;
  c_lflag=0;
  // c_line=0;

  // Get default device parameters.

#if defined (TCGETS)
    if (this->ACE_IO_SAP::control (TCGETS, (void *) &devpar) == -1)
#elif defined (TCGETA)
    if (this->ACE_IO_SAP::control (TCGETA, (void *) &devpar) == -1)
#else
    errno = ENOSYS;
#endif /* TCGETS */
      return -1;

  u_int newbaudrate = 0;

  switch (cmd)
    {
    case SETPARAMS:
      switch (arg->baudrate)
        {
#if defined (B0)
	case 0:       newbaudrate = B0;       break;
#endif /* B0 */
#if defined (B50)
	case 50:      newbaudrate = B50;      break;
#endif /* B50 */
#if defined (B75)
	case 75:      newbaudrate = B75;      break;
#endif /* B75 */
#if defined (B110)
	case 110:     newbaudrate = B110;     break;
#endif /* B110 */
#if defined (B134)
	case 134:     newbaudrate = B134;     break;
#endif /* B134 */
#if defined (B150)
	case 150:     newbaudrate = B150;     break;
#endif /* B150 */
#if defined (B200)
	case 200:     newbaudrate = B200;     break;
#endif /* B200 */
#if defined (B300)
        case 300:     newbaudrate = B300;     break;
#endif /* B300 */
#if defined (B600)
        case 600:     newbaudrate = B600;     break;
#endif /* B600 */
#if defined (B1200)
        case 1200:    newbaudrate = B1200;    break;
#endif /* B1200 */
#if defined (B1800)
	case 1800:    newbaudrate = B1800;    break;
#endif /* B1800 */
#if defined (B2400)
        case 2400:    newbaudrate = B2400;    break;
#endif /* B2400 */
#if defined (B4800)
        case 4800:    newbaudrate = B4800;    break;
#endif /* B4800 */
#if defined (B9600)
        case 9600:    newbaudrate = B9600;    break;
#endif /* B9600 */
#if defined (B19200)
        case 19200:   newbaudrate = B19200;   break;
#endif /* B19200 */
#if defined (B38400)
        case 38400:   newbaudrate = B38400;   break;
#endif /* B38400 */
#if defined (B56000)
	case 56000:   newbaudrate = B56000;   break;
#endif /* B56000 */
#if defined (B57600)
        case 57600:   newbaudrate = B57600;   break;
#endif /* B57600 */
#if defined (B76800)
	case 76800:   newbaudrate = B76800;   break;
#endif /* B76800 */
#if defined (B115200)
        case 115200:  newbaudrate = B115200;  break;
#endif /* B115200 */
#if defined (B128000)
	case 128000:  newbaudrate = B128000;  break;
#endif /* B128000 */
#if defined (B153600)
	case 153600:  newbaudrate = B153600;  break;
#endif /* B153600 */
#if defined (B230400)
	case 230400:  newbaudrate = B230400;  break;
#endif /* B230400 */
#if defined (B307200)
	case 307200:  newbaudrate = B307200;  break;
#endif /* B307200 */
#if defined (B256000)
	case 256000:  newbaudrate = B256000;  break;
#endif /* B256000 */
#if defined (B460800)
	case 460800:  newbaudrate = B460800;  break;
#endif /* B460800 */
#if defined (B500000)
	case 500000:  newbaudrate = B500000;  break;
#endif /* B500000 */
#if defined (B576000)
	case 576000:  newbaudrate = B576000;  break;
#endif /* B576000 */
#if defined (B921600)
	case 921600:  newbaudrate = B921600;  break;
#endif /* B921600 */
#if defined (B1000000)
	case 1000000: newbaudrate = B1000000; break;
#endif /* B1000000 */
#if defined (B1152000)
	case 1152000: newbaudrate = B1152000; break;
#endif /* B1152000 */
#if defined (B1500000)
	case 1500000: newbaudrate = B1500000; break;
#endif /* B1500000 */
#if defined (B2000000)
	case 2000000: newbaudrate = B2000000; break;
#endif /* B2000000 */
#if defined (B2500000)
	case 2500000: newbaudrate = B2500000; break;
#endif /* B2500000 */
#if defined (B3000000)
	case 3000000: newbaudrate = B3000000; break;
#endif /* B3000000 */
#if defined (B3500000)
	case 3500000: newbaudrate = B3500000; break;
#endif /* B3500000 */
#if defined (B4000000)
	case 4000000: newbaudrate = B4000000; break;
#endif /* B4000000 */
        default:
          return -1;
        }

#if defined(ACE_USES_NEW_TERMIOS_STRUCT)
      // @@ Can you really have different input and output baud
      // rates?!
      devpar.c_ispeed = newbaudrate;
      devpar.c_ospeed = newbaudrate;
#else
      c_cflag |= newbaudrate;
#endif /* ACE_USES_NEW_TERMIOS_STRUCT */

      switch (arg->databits)
        {
        case   5:
          c_cflag |= CS5;
          break;
        case   6:
          c_cflag |= CS6;
          break;
        case   7:
          c_cflag |= CS7;
          break;
        case   8:
          c_cflag |= CS8;
          break;
        default:
          return -1;
        }

      switch (arg->stopbits)
        {
        case   1:
          break;
        case   2:
          c_cflag |= CSTOPB;
          break;
        default:
          return -1;
        }
      if (arg->parityenb)
        {
          c_cflag |= PARENB;
          if (ACE_OS::strcasecmp (arg->paritymode, "odd") == 0)
            c_cflag |= PARODD;
        }

#if defined (CRTSCTS)
          // 6/22/00 MLS add rtsenb to if statement
      if ((arg->ctsenb)||(arg->rtsenb)) /* enable CTS/RTS protocoll */
        c_cflag |= CRTSCTS;
#endif /* CRTSCTS */
#if defined (NEW_RTSCTS)
      // 8/30/00 MLS add rtsenb to if statement to support new termios
      if ((arg->ctsenb)||(arg->rtsenb)) /* enable CTS/RTS protocoll */
        c_cflag |= NEW_RTSCTS;
#endif /* CRTSCTS */
#if defined (CREAD)
      if (arg->rcvenb) /* enable receiver */
        c_cflag |= CREAD;
#endif /* CREAD */


#if defined (HUPCL)
      // Cause DTR to be drop after port close MS 7/24/2000;
      c_cflag |= HUPCL;
#endif /* HUPCL */

#if defined (CLOCAL)
      // if device is not a modem set to local device MS  7/24/2000
      if(!arg->modem)
        c_cflag |= CLOCAL;
#endif /* CLOCAL */

      c_oflag = 0;
      c_iflag = IGNPAR | INPCK;
      if (arg->databits < 8)
        c_iflag |= ISTRIP;
#if defined (IGNBRK)
      // if device is not a modem set to ignore break points MS  7/24/2000
      if(!arg->modem)
        c_iflag |= IGNBRK;
#endif /* IGNBRK */
          // 6/22/00 MLS add enable xon/xoff
#if defined (IXON)
      if (arg->xinenb) /* enable XON/XOFF output*/
        c_iflag |= IXON;
#endif /* IXON */
#if defined (IXOFF)
      if (arg->xoutenb) /* enable XON/XOFF input*/
        c_iflag |= IXOFF;
#endif /* IXOFF */
      c_lflag = 0;

      ivmin_cc4 = (u_char) 0;
      ivtime_cc5= (u_char) (arg->readtimeoutmsec / 100);
      devpar.c_iflag = c_iflag;
      devpar.c_oflag = c_oflag;
      devpar.c_cflag = c_cflag;
      devpar.c_lflag = c_lflag;
      devpar.c_cc[ACE_VMIN] = ivmin_cc4;
      devpar.c_cc[ACE_VTIME] = ivtime_cc5;

#if defined(TIOCMGET)
      // ensure DTR is enabled
      int status;
      this->ACE_IO_SAP::control(TIOCMGET, &status);
      status |= TIOCM_DTR;
      this->ACE_IO_SAP::control(TIOCMSET,&status);
#endif /* definded (TIOCMGET) */

#if defined(TCSETS)
      return this->ACE_IO_SAP::control (TCSETS,
                                        (void *) &devpar);
#elif defined(TCSETA)
      return this->ACE_IO_SAP::control (TCSETA,
                                        (void *) &devpar);
#else
      errno = ENOSYS;
      return -1;
#endif
    case GETPARAMS:
      return -1; // Not yet implemented.
    default:
      return -1; // Wrong cmd.
    }
#elif defined (ACE_WIN32)
  switch (cmd)
    {
    case SETPARAMS:
      DCB dcb;
      dcb.DCBlength = sizeof dcb;
      if (!::GetCommState (this->get_handle (), &dcb)) 
        {
          ACE_OS::set_errno_to_last_error ();
          return -1; 
        }
/*SadreevAA
      switch (arg->baudrate)
        {
        case   300: dcb.BaudRate = CBR_300; break;
        case   600: dcb.BaudRate = CBR_600; break;
        case  1200: dcb.BaudRate = CBR_1200; break;
        case  2400: dcb.BaudRate = CBR_2400; break;
        case  4800: dcb.BaudRate = CBR_4800; break;
        case  9600: dcb.BaudRate = CBR_9600; break;
        case  19200: dcb.BaudRate = CBR_19200; break;
        case  38400: dcb.BaudRate = CBR_38400; break;
//          case  56000: dcb.BaudRate = CBR_56000; break;
        case  57600: dcb.BaudRate = CBR_57600; break;
        case  115200: dcb.BaudRate = CBR_115200; break;
//          case  128000: dcb.BaudRate = CBR_128000; break;
//          case  256000: dcb.BaudRate = CBR_256000; break;
        default:  return -1;
        }
*/  
      dcb.BaudRate = arg->baudrate;
      switch (arg->databits)
        {
        case 4:
        case 5:
        case 6:
        case 7:
        case 8:
          dcb.ByteSize = u_char (arg->databits);
          break;
        default:
          return -1;
        }

      switch (arg->stopbits)
        {
        case 1:
          dcb.StopBits = ONESTOPBIT;
          break;
        case 2:
          dcb.StopBits = TWOSTOPBITS;
          break;
        default:
          return -1;
        }


      // 6/22/00 MLS enabled extra paths for win32 parity checking.
      if (arg->parityenb)
        {
          dcb.fParity = TRUE;
          if (ACE_OS::strcasecmp (arg->paritymode, "odd") == 0)
            dcb.Parity = ODDPARITY;
          else if (ACE_OS::strcasecmp (arg->paritymode, "even") == 0)
            dcb.Parity = EVENPARITY;
          else if (ACE_OS::strcasecmp (arg->paritymode, "mark") == 0)
            dcb.Parity = MARKPARITY;
          else if (ACE_OS::strcasecmp (arg->paritymode, "space") == 0)
            dcb.Parity = SPACEPARITY;
          else
            dcb.Parity = NOPARITY;
        }
      else
        {
          dcb.fParity = FALSE;
          dcb.Parity = NOPARITY;
        }

      if (arg->ctsenb) // enable CTS protocol.
        dcb.fOutxCtsFlow = TRUE;
      else
        dcb.fOutxCtsFlow = FALSE;

            // SadreevAA
      if (arg->dsrenb) // enable DSR protocol.
        dcb.fOutxDsrFlow = TRUE;
      else
        dcb.fOutxDsrFlow = FALSE;

      // 6/22/00 MLS add  great flexibility for win32
      //                     pulled rts out of ctsenb
      switch (arg->rtsenb) // enable RTS protocol.
        {
        case 1:
          dcb.fRtsControl = RTS_CONTROL_ENABLE;
          break;
        case 2:
          dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
          break;
        case 3:
          dcb.fRtsControl = RTS_CONTROL_TOGGLE;
          break;
        default:
          dcb.fRtsControl = RTS_CONTROL_DISABLE;
        }

      // 6/22/00 MLS add enable xon/xoff
      if (arg->xinenb) // enable XON/XOFF for reception
        dcb.fInX = TRUE; // Fixed by SadreevAA
      else
        dcb.fInX = FALSE; // Fixed by SadreevAA

      if (arg->xoutenb) // enable XON/XOFF for transmission
        dcb.fOutX = TRUE;
      else
        dcb.fOutX = FALSE;

      // always set limits unless set to -1 to use default
      // 6/22/00 MLS add xon/xoff limits
      if (arg->xonlim != -1)
        dcb.XonLim  = arg->xonlim;
      if (arg->xofflim != -1)
        dcb.XoffLim  = arg->xofflim;

      dcb.fDtrControl = DTR_CONTROL_ENABLE;
      dcb.fAbortOnError = FALSE;
      dcb.fErrorChar = FALSE; 
      dcb.fNull = FALSE; 
      dcb.fBinary = TRUE;
      if (!::SetCommState (this->get_handle (), &dcb))
        {
          ACE_OS::set_errno_to_last_error ();
          return -1; 
        }

      // 2/13/97 BWF added drop out timer
      // modified time out to operate correctly with when delay
      // is requested or no delay is requestes
      COMMTIMEOUTS timeouts;
      if (!::GetCommTimeouts (this->get_handle(), &timeouts))
        {
          ACE_OS::set_errno_to_last_error ();
          return -1; 
        }

      if (arg->readtimeoutmsec == 0)
        {
          // return immediately if no data in the input buffer
          timeouts.ReadIntervalTimeout = MAXDWORD; 
          timeouts.ReadTotalTimeoutMultiplier = 0;
          timeouts.ReadTotalTimeoutConstant   = 0;
        }
      else
        {
          // Wait for specified  time-out for char to arrive
          // before returning.
          timeouts.ReadIntervalTimeout        = MAXDWORD;
          timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;

          // ensure specified timeout is below MAXDWORD

          // We don't test arg->readtimeoutmsec against MAXDWORD
          // directly to avoid a warning in the case DWORD is
          // unsigned.  Ensure specified timeout is below MAXDWORD use
          // MAXDWORD as indicator for infinite timeout.
          DWORD dw = arg->readtimeoutmsec;
          if (dw < MAXDWORD)
            {
              // Wait for specified time-out for char to arrive before
              // returning.
              timeouts.ReadIntervalTimeout = MAXDWORD;
              timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
              timeouts.ReadTotalTimeoutConstant = dw;
            }
          else
            {
              // settings for infinite timeout
              timeouts.ReadIntervalTimeout = 0;
              timeouts.ReadTotalTimeoutMultiplier = 0;
              timeouts.ReadTotalTimeoutConstant = 0;
            }
          }
  
       if (!::SetCommTimeouts (this->get_handle (), &timeouts))
         {
           ACE_OS::set_errno_to_last_error ();
           return -1; 
         }
 
       return 0;

    case GETPARAMS:
      ACE_NOTSUP_RETURN (-1); // Not yet implemented.
    default:
      return -1; // Wrong cmd.

    } // arg switch
#else
  ACE_UNUSED_ARG (cmd);
  ACE_UNUSED_ARG (arg);
  ACE_NOTSUP_RETURN (-1);
#endif /* ACE_HAS_TERM_IOCTLS */
}

#if defined (ACE_NEEDS_DEV_IO_CONVERSION)
ACE_TTY_IO::operator ACE_DEV_IO &()
{
  return (ACE_DEV_IO &) *this;
}
#endif /* ACE_NEEDS_DEV_IO_CONVERSION */
