/* Ruby/SerialPort $Id: serialport.c,v 1.15 2003/02/13 19:55:48 moumar Exp $
 * Guillaume Pierronnet <moumar@netcourrier.com>
 * Alan Stern <stern@rowland.harvard.edu>
 *
 * This code is hereby licensed for public consumption under either the
 * GNU GPL v2 or greater.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * For documentation on serial programming, see the excellent:
 * "Serial Programming Guide for POSIX Operating Systems"
 * written Michael R. Sweet.
 * http://www.easysw.com/~mike/serial/
 */

#define VERSION 	"0.6"

#include <ruby.h>    /* ruby inclusion */
#include <rubyio.h>  /* ruby io inclusion */

struct modem_params {
  int data_rate;
  int data_bits;
  int stop_bits;
  int parity;
  };

struct line_signals {
  int rts;
  int dtr;
  int cts;
  int dsr;
  int dcd;
  int ri;
  };

VALUE cSerialPort; /* serial port class */

static VALUE sBaud, sDataBits, sStopBits, sParity; /* strings */
static VALUE sRts, sDtr, sCts, sDsr, sDcd, sRi;


#if defined(mswin) || defined(bccwin)


#include <stdio.h>   /* Standard input/output definitions */
#include <io.h>      /* Low-level I/O definitions */
#include <fcntl.h>   /* File control definitions */
#include <windows.h> /* Windows standard function definitions */

#define NONE            0
#define HARD            1
#define SOFT            2

#define SPACE           SPACEPARITY
#define MARK            MARKPARITY
#define EVEN            EVENPARITY
#define ODD             ODDPARITY

static char sGetCommState[] = "GetCommState";
static char sSetCommState[] = "SetCommState";
static char sGetCommTimeouts[] = "GetCommTimeouts";
static char sSetCommTimeouts[] = "SetCommTimeouts";


static HANDLE sp_get_handle(obj)
  VALUE obj;
{
  OpenFile *fptr;

  GetOpenFile(obj, fptr);
  return (HANDLE) _get_osfhandle(fileno(fptr->f));
}

static VALUE sp_create(class, _port)
  VALUE class, _port;
{
  OpenFile *fp;
  int fd;
  HANDLE fh;
  int num_port;
  char *port;
  char *ports[] = {
  "COM1", "COM2", "COM3", "COM4",
  "COM5", "COM6", "COM7", "COM8"
  };
  DCB dcb;

  NEWOBJ(sp, struct RFile);
  rb_secure(4);
  OBJSETUP(sp, class, T_FILE);
  MakeOpenFile(sp, fp);

  switch(TYPE(_port)) {
    case T_FIXNUM:
      num_port = FIX2INT(_port);
      if (num_port < 0 || num_port > sizeof(ports) / sizeof(ports[0]))
	rb_raise(rb_eArgError, "illegal port number");
      port = ports[num_port];
      break;

    case T_STRING:
      Check_SafeStr(_port);
      port = RSTRING(_port)->ptr;
      break;

    default:
      rb_raise(rb_eTypeError, "wrong argument type");
      break;
  }

  fd = open(port, O_BINARY | O_RDWR);
  if (fd == -1)
    rb_sys_fail(port);
  fh = (HANDLE) _get_osfhandle(fd);
  if (SetupComm(fh, 1024, 1024) == 0) {
    close(fd);
    rb_raise(rb_eArgError, "not a serial port");
  }

  dcb.DCBlength = sizeof(dcb);
  if (GetCommState(fh, &dcb) == 0) {
    close(fd);
    rb_sys_fail(sGetCommState);
  }
  dcb.fBinary = TRUE;
  dcb.fParity = FALSE;
  dcb.fOutxDsrFlow = FALSE;
  dcb.fDtrControl = DTR_CONTROL_ENABLE;
  dcb.fDsrSensitivity = FALSE;
  dcb.fTXContinueOnXoff = FALSE;
  dcb.fErrorChar = FALSE;
  dcb.fNull = FALSE;
  dcb.fAbortOnError = FALSE;
  dcb.XonChar = 17;
  dcb.XoffChar = 19;
  if (SetCommState(fh, &dcb) == 0) {
    close(fd);
    rb_sys_fail(sSetCommState);
  }

  fp->f = rb_fdopen(fd, "rb+");
  fp->mode = FMODE_READWRITE | FMODE_BINMODE | FMODE_SYNC;
  return (VALUE) sp;
}

static VALUE sp_set_modem_params(argc, argv, self)
  int argc;
  VALUE *argv, self;
{
  HANDLE fh;
  DCB dcb;
  VALUE _data_rate, _data_bits, _parity, _stop_bits;
  int use_hash = 0;
  int data_rate, data_bits, parity;

  if (argc == 0)
    return self;
  if (argc == 1 && T_HASH == TYPE(argv[0])) {
    use_hash = 1;
    _data_rate = rb_hash_aref(argv[0], sBaud);
    _data_bits = rb_hash_aref(argv[0], sDataBits);
    _stop_bits = rb_hash_aref(argv[0], sStopBits);
    _parity = rb_hash_aref(argv[0], sParity);
  }

  fh = sp_get_handle(self);
  dcb.DCBlength = sizeof(dcb);
  if (GetCommState(fh, &dcb) == 0)
    rb_sys_fail(sGetCommState);

  if (!use_hash)
    _data_rate = argv[0];
  if (NIL_P(_data_rate))
    goto SkipDataRate;
  Check_Type(_data_rate, T_FIXNUM);

  data_rate = FIX2INT(_data_rate);
  switch (data_rate) {
    case 110:
    case 300:
    case 600:
    case 1200:
    case 2400:
    case 4800:
    case 9600:
    case 14400:
    case 19200:
    case 38400:
    case 56000:
    case 57600:
    case 115200:
    case 128000:
    case 256000:
      dcb.BaudRate = data_rate;
      break;

    default:
      rb_raise(rb_eArgError, "unknown baud rate");
      break;
  }
SkipDataRate:

  if (!use_hash)
    _data_bits = (argc >= 2 ? argv[1] : INT2FIX(8));
  if (NIL_P(_data_bits))
    goto SkipDataBits;
  Check_Type(_data_bits, T_FIXNUM);

  data_bits = FIX2INT(_data_bits);
  if (4 <= data_bits && data_bits <= 8)
    dcb.ByteSize = data_bits;
  else
    rb_raise(rb_eArgError, "unknown character size");
SkipDataBits:

  if (!use_hash)
    _stop_bits = (argc >= 3 ? argv[2] : INT2FIX(1));
  if (NIL_P(_stop_bits))
    goto SkipStopBits;
  Check_Type(_stop_bits, T_FIXNUM);

  switch (FIX2INT(_stop_bits)) {
    case 1:
      dcb.StopBits = ONESTOPBIT;
      break;
    case 2:
      dcb.StopBits = TWOSTOPBITS;
      break;
    default:
      rb_raise(rb_eArgError, "unknown number of stop bits");
    break;
  }
SkipStopBits:

  if (!use_hash)
    _parity = (argc >= 4 ? argv[3] : (dcb.ByteSize == 8 ?
      INT2FIX(NOPARITY) : INT2FIX(EVENPARITY)));
  if (NIL_P(_parity))
    goto SkipParity;
  Check_Type(_parity, T_FIXNUM);

  parity = FIX2INT(_parity);
  switch (parity) {
    case EVENPARITY:
    case ODDPARITY:
    case MARKPARITY:
    case SPACEPARITY:
    case NOPARITY:
      dcb.Parity = parity;
      break;

    default:
      rb_raise(rb_eArgError, "unknown parity");
      break;
  }
SkipParity:

  if (SetCommState(fh, &dcb) == 0)
    rb_sys_fail(sSetCommState);
  return self;
}

static void get_modem_params(self, mp)
  VALUE self;
  struct modem_params *mp;
{
  HANDLE fh;
  DCB dcb;

  fh = sp_get_handle(self);
  dcb.DCBlength = sizeof(dcb);
  if (GetCommState(fh, &dcb) == 0)
    rb_sys_fail(sGetCommState);

  mp->data_rate = dcb.BaudRate;
  mp->data_bits = dcb.ByteSize;
  mp->stop_bits = (dcb.StopBits == ONESTOPBIT ? 1 : 2);
  mp->parity = dcb.Parity;
}

static VALUE sp_set_flow_control(self, val)
  VALUE self, val;
{
  HANDLE fh;
  int flowc;
  DCB dcb;

  Check_Type(val, T_FIXNUM);

  fh = sp_get_handle(self);
  dcb.DCBlength = sizeof(dcb);
  if (GetCommState(fh, &dcb) == 0)
    rb_sys_fail(sGetCommState);

  flowc = FIX2INT(val);
  if (flowc & HARD) {
    dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
    dcb.fOutxCtsFlow = TRUE;
  } else {
    dcb.fRtsControl = RTS_CONTROL_ENABLE;
    dcb.fOutxCtsFlow = FALSE;
  }
  if (flowc & SOFT)
    dcb.fOutX = dcb.fInX = TRUE;
  else
    dcb.fOutX = dcb.fInX = FALSE;

  if (SetCommState(fh, &dcb) == 0)
    rb_sys_fail(sSetCommState);
  return self;
}

static VALUE sp_get_flow_control(self)
  VALUE self;
{
  HANDLE fh;
  int ret;
  DCB dcb;

  fh = sp_get_handle(self);
  dcb.DCBlength = sizeof(dcb);
  if (GetCommState(fh, &dcb) == 0)
    rb_sys_fail(sGetCommState);

  ret = 0;
  if (dcb.fOutxCtsFlow)
    ret += HARD;
  if (dcb.fOutX)
    ret += SOFT;

  return INT2FIX(ret);
}

static VALUE sp_set_read_timeout(self, val)
  VALUE self, val;
{
  int timeout;
  HANDLE fh;
  COMMTIMEOUTS ctout;

  Check_Type(val, T_FIXNUM);
  timeout = FIX2INT(val);

  fh = sp_get_handle(self);
  if (GetCommTimeouts(fh, &ctout) == 0)
    rb_sys_fail(sGetCommTimeouts);

  if (timeout < 0) {
    ctout.ReadIntervalTimeout = MAXDWORD;
    ctout.ReadTotalTimeoutMultiplier = 0;
    ctout.ReadTotalTimeoutConstant = 0;
  } else if (timeout == 0) {
    ctout.ReadIntervalTimeout = MAXDWORD;
    ctout.ReadTotalTimeoutMultiplier = MAXDWORD;
    ctout.ReadTotalTimeoutConstant = MAXDWORD - 1;
  } else {
    ctout.ReadIntervalTimeout = timeout;
    ctout.ReadTotalTimeoutMultiplier = 0;
    ctout.ReadTotalTimeoutConstant = timeout;
  }

  if (SetCommTimeouts(fh, &ctout) == 0)
    rb_sys_fail(sSetCommTimeouts);
  return self;
}

static VALUE sp_get_read_timeout(self)
  VALUE self;
{
  HANDLE fh;
  COMMTIMEOUTS ctout;

  fh = sp_get_handle(self);
  if (GetCommTimeouts(fh, &ctout) == 0)
    rb_sys_fail(sGetCommTimeouts);
  switch (ctout.ReadTotalTimeoutConstant) {
    case 0:
      return INT2FIX(-1);
    case MAXDWORD:
      return INT2FIX(0);
  }
  return INT2FIX(ctout.ReadTotalTimeoutConstant);
}

static VALUE sp_set_write_timeout(self, val)
  VALUE self, val;
{
  int timeout;
  HANDLE fh;
  COMMTIMEOUTS ctout;

  Check_Type(val, T_FIXNUM);
  timeout = FIX2INT(val);

  fh = sp_get_handle(self);
  if (GetCommTimeouts(fh, &ctout) == 0)
    rb_sys_fail(sGetCommTimeouts);

  if (timeout <= 0) {
    ctout.WriteTotalTimeoutMultiplier = 0;
    ctout.WriteTotalTimeoutConstant = 0;
  } else {
    ctout.WriteTotalTimeoutMultiplier = timeout;
    ctout.WriteTotalTimeoutConstant = 0;
  }

  if (SetCommTimeouts(fh, &ctout) == 0)
    rb_sys_fail(sSetCommTimeouts);
  return self;
}

static VALUE sp_get_write_timeout(self)
  VALUE self;
{
  HANDLE fh;
  COMMTIMEOUTS ctout;

  fh = sp_get_handle(self);
  if (GetCommTimeouts(fh, &ctout) == 0)
    rb_sys_fail(sGetCommTimeouts);
  return INT2FIX(ctout.WriteTotalTimeoutMultiplier);
}

static void delay_ms(time)
  int time;
{
  HANDLE ev;

  ev = CreateEvent(NULL, FALSE, FALSE, NULL);
  if (!ev)
    rb_sys_fail("CreateEvent");
  if (WaitForSingleObject(ev, time) == WAIT_FAILED)
    rb_sys_fail("WaitForSingleObject");
  CloseHandle(ev);
}

static VALUE sp_break(self, time)
  VALUE self, time;
{
  HANDLE fh;

  Check_Type(time, T_FIXNUM);

  fh = sp_get_handle(self);
  if (SetCommBreak(fh) == 0)
    rb_sys_fail("SetCommBreak");
  delay_ms(FIX2INT(time) * 100);
  ClearCommBreak(fh);
  return Qnil;
}

static void get_line_signals(obj, ls)
  VALUE obj;
  struct line_signals *ls;
{
  HANDLE fh;
  int status;

  fh = sp_get_handle(obj);
  if (GetCommModemStatus(fh, &status) == 0)
    rb_sys_fail("GetCommModemStatus");

  ls->cts = (status & MS_CTS_ON ? 1 : 0);
  ls->dsr = (status & MS_DSR_ON ? 1 : 0);
  ls->dcd = (status & MS_RLSD_ON ? 1 : 0);
  ls->ri = (status & MS_RING_ON ? 1 : 0);
}

static VALUE set_signal(obj, val, sigoff, sigon)
  VALUE obj,val;
  int sigoff, sigon;
{
  HANDLE fh;
  int set, sig;

  Check_Type(val, T_FIXNUM);
  fh = sp_get_handle(obj);

  set = FIX2INT(val);
  if (set == 0)
    sig = sigoff;
  else if (set == 1)
    sig = sigon;
  else
    rb_raise(rb_eArgError, "invalid value");

  if (EscapeCommFunction(fh, sig) == 0)
    rb_sys_fail("EscapeCommFunction");
  return obj;
}

static VALUE sp_set_rts(self, val)
  VALUE self, val;
{
  return set_signal(self, val, CLRRTS, SETRTS);
}

static VALUE sp_set_dtr(self, val)
  VALUE self, val;
{
  return set_signal(self, val, CLRDTR, SETDTR);
}

static VALUE sp_get_rts(self)
  VALUE self;
{
  rb_notimplement();
  return self;
}

static VALUE sp_get_dtr(self)
  VALUE self;
{
  rb_notimplement();
  return self;
}


#else /* defined(mswin) || defined(bccwin) */


#include <stdio.h>   /* Standard input/output definitions */
#include <unistd.h>  /* UNIX standard function definitions */
#include <fcntl.h>   /* File control definitions */
#include <errno.h>   /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <sys/ioctl.h>

#ifdef CRTSCTS
#define HAVE_FLOWCONTROL_HARD 1
#else
#undef HAVE_FLOWCONTROL_HARD
#endif

#define NONE            0
#define HARD            1
#define SOFT            2

#define SPACE           0
#define MARK            0
#define EVEN            1
#define ODD             2

static char sTcgetattr[] = "tcgetattr";
static char sTcsetattr[] = "tcsetattr";
static char sIoctl[] = "ioctl";


static int sp_get_fd(obj)
  VALUE obj;
{
  OpenFile *fptr;

  GetOpenFile(obj, fptr);
  return (fileno(fptr->f));
}

static VALUE sp_create(class, _port)
  VALUE class, _port;
{
  OpenFile *fp;
  int fd;
  int num_port;
  char *port;
  char *ports[] = {
#if defined(linux) || defined(cygwin)
  "/dev/ttyS0", "/dev/ttyS1", "/dev/ttyS2", "/dev/ttyS3",
  "/dev/ttyS4", "/dev/ttyS5", "/dev/ttyS6", "/dev/ttyS7"
#elif defined(freebsd) || defined(netbsd) || defined(openbsd)
  "/dev/cuaa0", "/dev/cuaa1", "/dev/cuaa2", "/dev/cuaa3",
  "/dev/cuaa4", "/dev/cuaa5", "/dev/cuaa6", "/dev/cuaa7"
#elif defined(solaris)
  "/dev/ttya", "/dev/ttyb", "/dev/ttyc", "/dev/ttyd",
  "/dev/ttye", "/dev/ttyf", "/dev/ttyg", "/dev/ttyh"
#elif defined(aix)
  "/dev/tty0", "/dev/tty1", "/dev/tty2", "/dev/tty3",
  "/dev/tty4", "/dev/tty5", "/dev/tty6", "/dev/tty7"
#elif defined(irix)
  "/dev/ttyf1", "/dev/ttyf2", "/dev/ttyf3", "/dev/ttyf4",
  "/dev/ttyf5", "/dev/ttyf6", "/dev/ttyf7", "/dev/ttyf8"
#endif
  };
  struct termios params;

  NEWOBJ(sp, struct RFile);
  rb_secure(4);
  OBJSETUP(sp, class, T_FILE);
  MakeOpenFile(sp, fp);

  switch(TYPE(_port)) {
    case T_FIXNUM:
      num_port = FIX2INT(_port);
      if (num_port < 0 || num_port > sizeof(ports) / sizeof(ports[0]))
	rb_raise(rb_eArgError, "illegal port number");
      port = ports[num_port];
      break;

    case T_STRING:
      Check_SafeStr(_port);
      port = RSTRING(_port)->ptr;
      break;

    default:
      rb_raise(rb_eTypeError, "wrong argument type");
      break;
  }

  fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
  if (fd == -1)
    rb_sys_fail(port);
  if (!isatty(fd)) {
    close(fd);
    rb_raise(rb_eArgError, "not a serial port");
  }

  /* enable blocking read */
  fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);

  if (tcgetattr(fd, &params) == -1) {
    close(fd);
    rb_sys_fail(sTcgetattr);
  }
  params.c_oflag = 0;
  params.c_lflag = 0;
  params.c_iflag &= (IXON | IXOFF | IXANY);
  params.c_cflag |= CLOCAL | CREAD;
  params.c_cflag &= ~HUPCL;
  if (tcsetattr(fd, TCSANOW, &params) == -1) {
    close(fd);
    rb_sys_fail(sTcsetattr);
  }

  fp->f = rb_fdopen(fd, "r+");
  fp->mode = FMODE_READWRITE | FMODE_SYNC;
  return (VALUE) sp;
}

static VALUE sp_set_modem_params(argc, argv, self)
  int argc;
  VALUE *argv, self;
{
  int fd;
  struct termios params;
  VALUE _data_rate, _data_bits, _parity, _stop_bits;
  int use_hash = 0;
  int data_rate, data_bits;

  if (argc == 0)
    return self;
  if (argc == 1 && T_HASH == TYPE(argv[0])) {
    use_hash = 1;
    _data_rate = rb_hash_aref(argv[0], sBaud);
    _data_bits = rb_hash_aref(argv[0], sDataBits);
    _stop_bits = rb_hash_aref(argv[0], sStopBits);
    _parity = rb_hash_aref(argv[0], sParity);
  }

  fd = sp_get_fd(self);
  if (tcgetattr(fd, &params) == -1)
    rb_sys_fail(sTcgetattr);

  if (!use_hash)
    _data_rate = argv[0];
  if (NIL_P(_data_rate))
    goto SkipDataRate;
  Check_Type(_data_rate, T_FIXNUM);

  switch(FIX2INT(_data_rate)) {
    case 50:    data_rate = B50; break;
    case 75:    data_rate = B75; break;
    case 110:   data_rate = B110; break;
    case 134:   data_rate = B134; break;
    case 150:   data_rate = B150; break;
    case 200:   data_rate = B200; break;
    case 300:   data_rate = B300; break;
    case 600:   data_rate = B600; break;
    case 1200:  data_rate = B1200; break;
    case 1800:  data_rate = B1800; break;
    case 2400:  data_rate = B2400; break;
    case 4800:  data_rate = B4800; break;
    case 9600:  data_rate = B9600; break;
    case 19200: data_rate = B19200; break;
    case 38400: data_rate = B38400; break;
#ifdef B57600
    case 57600: data_rate = B57600; break;
#endif
#ifdef B76800
    case 76800: data_rate = B76800; break;
#endif
#ifdef B115200
    case 115200: data_rate = B115200; break;
#endif
#ifdef B230400
    case 230400: data_rate = B230400; break;
#endif

    default:
      rb_raise(rb_eArgError, "unknown baud rate");
      break;
  }
  cfsetispeed(&params, data_rate);
  cfsetospeed(&params, data_rate);
SkipDataRate:

  if (!use_hash)
    _data_bits = (argc >= 2 ? argv[1] : INT2FIX(8));
  if (NIL_P(_data_bits))
    goto SkipDataBits;
  Check_Type(_data_bits, T_FIXNUM);

  switch(FIX2INT(_data_bits)) {
    case 5:
      data_bits = CS5;
      break;
    case 6:
      data_bits = CS6;
      break;
    case 7:
      data_bits = CS7;
      break;
    case 8:
      data_bits = CS8;
      break;
    default:
      rb_raise(rb_eArgError, "unknown character size");
      break;
  }
  params.c_cflag &= ~CSIZE;
  params.c_cflag |= data_bits;
SkipDataBits:

  if (!use_hash)
    _stop_bits = (argc >= 3 ? argv[2] : INT2FIX(1));
  if (NIL_P(_stop_bits))
    goto SkipStopBits;
  Check_Type(_stop_bits, T_FIXNUM);

  switch(FIX2INT(_stop_bits)) {
    case 1:
      params.c_cflag &= ~CSTOPB;
      break;
    case 2:
      params.c_cflag |= CSTOPB;
      break;
    default:
      rb_raise(rb_eArgError, "unknown number of stop bits");
    break;
  }
SkipStopBits:

  if (!use_hash)
    _parity = (argc >= 4 ? argv[3] : ((params.c_cflag & CSIZE) == CS8 ?
      INT2FIX(NONE) : INT2FIX(EVEN)));
  if (NIL_P(_parity))
    goto SkipParity;
  Check_Type(_parity, T_FIXNUM);

  switch(FIX2INT(_parity)) {
    case EVEN:
      params.c_cflag |= PARENB;
      params.c_cflag &= ~PARODD;
      break;

    case ODD:
      params.c_cflag |= PARENB;
      params.c_cflag |= PARODD;
      break;

    case NONE:
      params.c_cflag &= ~PARENB;
      break;

    default:
      rb_raise(rb_eArgError, "unknown parity");
      break;
  }
SkipParity:

  if (tcsetattr(fd, TCSANOW, &params) == -1)
    rb_sys_fail(sTcsetattr);
  return self;
}

static void get_modem_params(self, mp)
  VALUE self;
  struct modem_params *mp;
{
  int fd;
  struct termios params;

  fd = sp_get_fd(self);
  if (tcgetattr(fd, &params) == -1)
    rb_sys_fail(sTcgetattr);

  switch (cfgetospeed(&params)) {
    case B50:    mp->data_rate = 50; break;
    case B75:    mp->data_rate = 75; break;
    case B110:   mp->data_rate = 110; break;
    case B134:   mp->data_rate = 134; break;
    case B150:   mp->data_rate = 150; break;
    case B200:   mp->data_rate = 200; break;
    case B300:   mp->data_rate = 300; break;
    case B600:   mp->data_rate = 600; break;
    case B1200:  mp->data_rate = 1200; break;
    case B1800:  mp->data_rate = 1800; break;
    case B2400:  mp->data_rate = 2400; break;
    case B4800:  mp->data_rate = 4800; break;
    case B9600:  mp->data_rate = 9600; break;
    case B19200: mp->data_rate = 19200; break;
    case B38400: mp->data_rate = 38400; break;
#ifdef B57600
    case B57600: mp->data_rate = 57600; break;
#endif
#ifdef B76800
    case B76800: mp->data_rate = 76800; break;
#endif
#ifdef B115200
    case B115200: mp->data_rate = 115200; break;
#endif
#ifdef B230400
    case B230400: mp->data_rate = 230400; break;
#endif
  }

  switch(params.c_cflag & CSIZE) {
    case CS5:
      mp->data_bits = 5;
      break;
    case CS6:
      mp->data_bits = 6;
      break;
    case CS7:
      mp->data_bits = 7;
      break;
    case CS8:
      mp->data_bits = 8;
      break;
    default:
      mp->data_bits = 0;
      break;
  }

  mp->stop_bits = (params.c_cflag & CSTOPB ? 2 : 1);

  if (!(params.c_cflag & PARENB))
    mp->parity = NONE;
  else if (params.c_cflag & PARODD)
    mp->parity = ODD;
  else
    mp->parity = EVEN;
}

static VALUE sp_set_flow_control(self, val)
  VALUE self, val;
{
  int fd;
  int flowc;
  struct termios params;

  Check_Type(val, T_FIXNUM);

  fd = sp_get_fd(self);
  if (tcgetattr(fd, &params) == -1)
    rb_sys_fail(sTcgetattr);

  flowc = FIX2INT(val);
  if (flowc & HARD)
#ifdef HAVE_FLOWCONTROL_HARD
    params.c_cflag |= CRTSCTS;
  else
    params.c_cflag &= ~CRTSCTS;
#else
    rb_raise(rb_eIOError, "Hardware flow control not supported");
#endif
  if (flowc & SOFT)
    params.c_iflag |= (IXON | IXOFF | IXANY);
  else
    params.c_iflag &= ~(IXON | IXOFF | IXANY);

  if (tcsetattr(fd, TCSANOW, &params) == -1)
    rb_sys_fail(sTcsetattr);
  return self;
}

static VALUE sp_get_flow_control(self)
  VALUE self;
{
  int ret;
  int fd;
  struct termios params;

  fd = sp_get_fd(self);
  if (tcgetattr(fd, &params) == -1)
    rb_sys_fail(sTcgetattr);

  ret = 0;
#ifdef HAVE_FLOWCONTROL_HARD
  if (params.c_cflag & CRTSCTS)
    ret += HARD;
#endif
  if (params.c_iflag & (IXON | IXOFF | IXANY))
    ret += SOFT;

  return INT2FIX(ret);
}

static VALUE sp_set_read_timeout(self, val)
  VALUE self, val;
{
  int timeout;
  int fd;
  struct termios params;

  Check_Type(val, T_FIXNUM);
  timeout = FIX2INT(val);

  fd = sp_get_fd(self);
  if (tcgetattr(fd, &params) == -1)
    rb_sys_fail(sTcgetattr);

  if (timeout < 0) {
    params.c_cc[VTIME] = 0;
    params.c_cc[VMIN] = 0;
  } else if (timeout == 0) {
    params.c_cc[VTIME] = 0;
    params.c_cc[VMIN] = 1;
  } else {
    params.c_cc[VTIME] = (timeout + 50) / 100;
    params.c_cc[VMIN] = 0;
  }

  if (tcsetattr(fd, TCSANOW, &params) == -1)
    rb_sys_fail(sTcsetattr);
  return self;
}

static VALUE sp_get_read_timeout(self)
  VALUE self;
{
  int fd;
  struct termios params;

  fd = sp_get_fd(self);
  if (tcgetattr(fd, &params) == -1)
    rb_sys_fail(sTcgetattr);
  if (params.c_cc[VTIME] == 0 && params.c_cc[VMIN] == 0)
    return INT2FIX(-1);
  return INT2FIX(params.c_cc[VTIME] * 100);
}

static VALUE sp_set_write_timeout(self, val)
  VALUE self, val;
{
  rb_notimplement();
  return self;
}

static VALUE sp_get_write_timeout(self)
  VALUE self;
{
  rb_notimplement();
  return self;
}

static VALUE sp_break(self, time)
  VALUE self, time;
{
  int fd;

  Check_Type(time, T_FIXNUM);

  fd = sp_get_fd(self);
  if (tcsendbreak(fd, FIX2INT(time) / 3) == -1)
    rb_sys_fail("tcsendbreak");
  return Qnil;
}

static void get_line_signals(obj, ls)
  VALUE obj;
  struct line_signals *ls;
{
  int fd, status;

  fd = sp_get_fd(obj);
  if (ioctl(fd, TIOCMGET, &status) == -1)
    rb_sys_fail(sIoctl);

  ls->rts = (status & TIOCM_RTS ? 1 : 0);
  ls->dtr = (status & TIOCM_DTR ? 1 : 0);
  ls->cts = (status & TIOCM_CTS ? 1 : 0);
  ls->dsr = (status & TIOCM_DSR ? 1 : 0);
  ls->dcd = (status & TIOCM_CD ? 1 : 0);
  ls->ri = (status & TIOCM_RI ? 1 : 0);
}

static VALUE set_signal(obj, val, sig)
  VALUE obj,val;
  int sig;
{
  int status;
  int fd;
  int set;

  Check_Type(val, T_FIXNUM);
  fd = sp_get_fd(obj);
  if (ioctl(fd, TIOCMGET, &status) == -1)
    rb_sys_fail(sIoctl);

  set = FIX2INT(val);
  if (set == 0)
    status &= ~sig;
  else if (set == 1)
    status |= sig;
  else
    rb_raise(rb_eArgError, "invalid value");

  if (ioctl(fd, TIOCMSET, &status) == -1)
    rb_sys_fail(sIoctl);
  return obj;
}

static VALUE sp_set_rts(self, val)
  VALUE self, val;
{
    return set_signal(self, val, TIOCM_RTS);
}

static VALUE sp_set_dtr(self, val)
  VALUE self, val;
{
  return set_signal(self, val, TIOCM_DTR);
}

static VALUE sp_get_rts(self)
  VALUE self;
{
  struct line_signals ls;

  get_line_signals(self, &ls);
  return INT2FIX(ls.rts);
}

static VALUE sp_get_dtr(self)
  VALUE self;
{
  struct line_signals ls;

  get_line_signals(self, &ls);
  return INT2FIX(ls.dtr);
}


#endif /* defined(mswin) || defined(bccwin) */


static VALUE sp_set_data_rate(self, data_rate)
  VALUE self, data_rate;
{
  VALUE argv[4];

  argv[0] = data_rate;
  argv[1] = argv[2] = argv[3] = Qnil;
  return sp_set_modem_params(4, argv, self);
}

static VALUE sp_set_data_bits(self, data_bits)
  VALUE self, data_bits;
{
  VALUE argv[4];

  argv[1] = data_bits;
  argv[0] = argv[2] = argv[3] = Qnil;
  return sp_set_modem_params(4, argv, self);
}

static VALUE sp_set_stop_bits(self, stop_bits)
  VALUE self, stop_bits;
{
  VALUE argv[4];

  argv[2] = stop_bits;
  argv[0] = argv[1] = argv[3] = Qnil;
  return sp_set_modem_params(4, argv, self);
}

static VALUE sp_set_parity(self, parity)
  VALUE self, parity;
{
  VALUE argv[4];

  argv[3] = parity;
  argv[0] = argv[1] = argv[2] = Qnil;
  return sp_set_modem_params(4, argv, self);
}

static VALUE sp_get_data_rate(self)
  VALUE self;
{
  struct modem_params mp;

  get_modem_params(self, &mp);
  return INT2FIX(mp.data_rate);
}

static VALUE sp_get_data_bits(self)
  VALUE self;
{
  struct modem_params mp;

  get_modem_params(self, &mp);
  return INT2FIX(mp.data_bits);
}

static VALUE sp_get_stop_bits(self)
  VALUE self;
{
  struct modem_params mp;

  get_modem_params(self, &mp);
  return INT2FIX(mp.stop_bits);
}

static VALUE sp_get_parity(self)
  VALUE self;
{
  struct modem_params mp;

  get_modem_params(self, &mp);
  return INT2FIX(mp.parity);
}

static VALUE sp_get_modem_params(self)
  VALUE self;
{
  struct modem_params mp;
  VALUE hash;

  get_modem_params(self, &mp);
  hash = rb_hash_new();
  rb_hash_aset(hash, sBaud, INT2FIX(mp.data_rate));
  rb_hash_aset(hash, sDataBits, INT2FIX(mp.data_bits));
  rb_hash_aset(hash, sStopBits, INT2FIX(mp.stop_bits));
  rb_hash_aset(hash, sParity, INT2FIX(mp.parity));
  return hash;
}

static VALUE sp_get_cts(self)
  VALUE self;
{
  struct line_signals ls;

  get_line_signals(self, &ls);
  return INT2FIX(ls.cts);
}

static VALUE sp_get_dsr(self)
  VALUE self;
{
  struct line_signals ls;

  get_line_signals(self, &ls);
  return INT2FIX(ls.dsr);
}

static VALUE sp_get_dcd(self)
  VALUE self;
{
  struct line_signals ls;

  get_line_signals(self, &ls);
  return INT2FIX(ls.dcd);
}

static VALUE sp_get_ri(self)
  VALUE self;
{
  struct line_signals ls;

  get_line_signals(self, &ls);
  return INT2FIX(ls.ri);
}

static VALUE
sp_signals(self)
  VALUE self;
{
  struct line_signals ls;
  VALUE hash;

  get_line_signals(self, &ls);
  hash = rb_hash_new();
#if !(defined(mswin) || defined(bccwin))
  rb_hash_aset(hash, sRts, INT2FIX(ls.rts));
  rb_hash_aset(hash, sDtr, INT2FIX(ls.dtr));
#endif
  rb_hash_aset(hash, sCts, INT2FIX(ls.cts));
  rb_hash_aset(hash, sDsr, INT2FIX(ls.dsr));
  rb_hash_aset(hash, sDcd, INT2FIX(ls.dcd));
  rb_hash_aset(hash, sRi, INT2FIX(ls.ri));
  return hash;
}

void Init_serialport() {
  sBaud = rb_str_new2("baud");
  sDataBits = rb_str_new2("data_bits");
  sStopBits = rb_str_new2("stop_bits");
  sParity = rb_str_new2("parity");
  sRts = rb_str_new2("rts");
  sDtr = rb_str_new2("dtr");
  sCts = rb_str_new2("cts");
  sDsr = rb_str_new2("dsr");
  sDcd = rb_str_new2("dcd");
  sRi = rb_str_new2("ri");

  rb_gc_register_address(&sBaud);
  rb_gc_register_address(&sDataBits);
  rb_gc_register_address(&sStopBits);
  rb_gc_register_address(&sParity);
  rb_gc_register_address(&sRts);
  rb_gc_register_address(&sDtr);
  rb_gc_register_address(&sCts);
  rb_gc_register_address(&sDsr);
  rb_gc_register_address(&sDcd);
  rb_gc_register_address(&sRi);

  cSerialPort = rb_define_class("SerialPort", rb_cIO);
  rb_define_singleton_method(cSerialPort, "create", sp_create, 1);

  rb_define_method(cSerialPort, "get_modem_params", sp_get_modem_params, 0);
  rb_define_method(cSerialPort, "set_modem_params", sp_set_modem_params, -1);
  rb_define_method(cSerialPort, "modem_params", sp_get_modem_params, 0);
  rb_define_method(cSerialPort, "modem_params=", sp_set_modem_params, -1);
  rb_define_method(cSerialPort, "baud", sp_get_data_rate, 0);
  rb_define_method(cSerialPort, "baud=", sp_set_data_rate, 1);
  rb_define_method(cSerialPort, "data_bits", sp_get_data_bits, 0);
  rb_define_method(cSerialPort, "data_bits=", sp_set_data_bits, 1);
  rb_define_method(cSerialPort, "stop_bits", sp_get_stop_bits, 0);
  rb_define_method(cSerialPort, "stop_bits=", sp_set_stop_bits, 1);
  rb_define_method(cSerialPort, "parity", sp_get_parity, 0);
  rb_define_method(cSerialPort, "parity=", sp_set_parity, 1);

  rb_define_method(cSerialPort, "flow_control=", sp_set_flow_control, 1);
  rb_define_method(cSerialPort, "flow_control", sp_get_flow_control, 0);

  rb_define_method(cSerialPort, "read_timeout", sp_get_read_timeout, 0);
  rb_define_method(cSerialPort, "read_timeout=", sp_set_read_timeout, 1);
  rb_define_method(cSerialPort, "write_timeout", sp_get_write_timeout, 0);
  rb_define_method(cSerialPort, "write_timeout=", sp_set_write_timeout, 1);

  rb_define_method(cSerialPort, "break", sp_break, 1);

  rb_define_method(cSerialPort, "signals", sp_signals, 0);
  rb_define_method(cSerialPort, "get_signals", sp_signals, 0);
  rb_define_method(cSerialPort, "rts", sp_get_rts, 0);
  rb_define_method(cSerialPort, "rts=", sp_set_rts, 1);
  rb_define_method(cSerialPort, "dtr", sp_get_dtr, 0);
  rb_define_method(cSerialPort, "dtr=", sp_set_dtr, 1);
  rb_define_method(cSerialPort, "cts", sp_get_cts, 0);
  rb_define_method(cSerialPort, "dsr", sp_get_dsr, 0);
  rb_define_method(cSerialPort, "dcd", sp_get_dcd, 0);
  rb_define_method(cSerialPort, "ri", sp_get_ri, 0);

  rb_define_const(cSerialPort, "NONE", INT2FIX(NONE));
  rb_define_const(cSerialPort, "HARD", INT2FIX(HARD));
  rb_define_const(cSerialPort, "SOFT", INT2FIX(SOFT));

  rb_define_const(cSerialPort, "SPACE", INT2FIX(SPACE));
  rb_define_const(cSerialPort, "MARK", INT2FIX(MARK));
  rb_define_const(cSerialPort, "EVEN", INT2FIX(EVEN));
  rb_define_const(cSerialPort, "ODD", INT2FIX(ODD));

  rb_define_const(cSerialPort, "VERSION", rb_str_new2(VERSION));

  /* The following definitions are more easily carried out in Ruby */
  rb_eval_string(
	"class SerialPort\n"

	  "private_class_method(:create)\n"

	  "def SerialPort::new(port, *params)\n"
	    "sp = create(port)\n"
	    "begin\n"
	      "sp.set_modem_params(*params)\n"
	    "rescue\n"
	      "sp.close\n"
	      "raise\n"
	    "end\n"
	    "return sp\n"
	  "end\n"

	  "def SerialPort::open(port, *params)\n"
	    "sp = create(port)\n"
	    "begin\n"
	      "sp.set_modem_params(*params)\n"
	      "if (block_given?)\n"
		"yield sp\n"
		"sp.close\n"
		"return nil\n"
	      "end\n"
	    "rescue\n"
	      "sp.close\n"
	      "raise\n"
	    "end\n"
	    "return sp\n"
	  "end\n"

	"end\n"
  );
}
