Merge pull request #2734 from nealef/master
[mono.git] / support / serial.c
index 8ae46b8a6bfcfd110ee0e63d4bfd512854d1668e..ec02e48b5e8bd37a30601aa90c5332e9bcfecba1 100644 (file)
  * Author: Chris Toshok <toshok@ximian.com>
  */
 
+#include "map.h"
+#include "mph.h"
+
 #include <termios.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <string.h>
+#include <errno.h>
+#if defined(HAVE_POLL_H)
+#include <poll.h>
+#elif defined(HAVE_SYS_POLL_H)
 #include <sys/poll.h>
+#endif
+#include <sys/ioctl.h>
+
+/* This is for ASYNC_*, serial_struct on linux */
+#if defined(HAVE_LINUX_SERIAL_H)
+#include <linux/serial.h>
+#endif
 
 #include <glib.h>
 
-int
-open_serial (char* devfile)
-{
-       int fd;
-       struct termios newtio;
+/* This is for FIONREAD on solaris */
+#if defined(sun)
+#include <sys/filio.h>
+#endif
 
-       fd = open (devfile, O_RDWR);
+/* sys/time.h (for timeval) is required when using osx 10.3 (but not 10.4) */
+/* IOKit is a private framework in iOS, so exclude there */
+#if defined(__APPLE__) && !defined(HOST_IOS) && !defined(HOST_WATCHOS) && !defined(HOST_APPLETVOS)
+#define HAVE_IOKIT 1
+#endif
 
-       if (fd == -1)
-               return -1;
+#if defined(HAVE_IOKIT)
+#include <sys/time.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/serial/IOSerialKeys.h>
+#include <IOKit/serial/ioss.h>
+#include <IOKit/IOBSD.h>
+#endif
 
-       newtio.c_cflag = CLOCAL | CREAD;
-       newtio.c_iflag = 0;
-       newtio.c_oflag = 0;
-       newtio.c_lflag = 0;
+/* This is a copy of System.IO.Ports.Handshake */
+typedef enum {
+       NoneHandshake = 0,
+       XOnXOff = 1,
+       RequestToSend = 2,
+       RequestToSendXOnXOff = 3
+} MonoHandshake;
+
+/* This is a copy of System.IO.Ports.Parity */
+typedef enum {
+       NoneParity = 0,
+       Odd = 1,
+       Even = 2,
+       Mark = 3,
+       Space = 4
+} MonoParity;
+
+/* This is a copy of System.IO.Ports.StopBits */
+typedef enum {
+       NoneStopBits = 0,
+       One = 1,
+       Two = 2,
+       OnePointFive = 3
+} MonoStopBits;
+
+/* This is a copy of System.IO.Ports.SerialSignal */
+typedef enum {
+       NoneSignal,
+       Cd = 1, /* Carrier detect */
+       Cts = 2, /* Clear to send */
+       Dsr = 4, /* Data set ready */
+       Dtr = 8, /* Data terminal ready */
+       Rts = 16  /* Request to send */
+} MonoSerialSignal;
 
-       tcflush(fd, TCIOFLUSH);
-       tcsetattr(fd,TCSANOW,&newtio);
+/*
+ * Silence the compiler, we do not need these prototypes to be public, since these are only
+ * used by P/Invoke
+ */
 
-       fcntl (fd, F_SETFL, O_NONBLOCK);
+int              open_serial (char *devfile);
+int              close_serial (int unix_fd);
+guint32          read_serial (int fd, guchar *buffer, int offset, int count);
+int              write_serial (int fd, guchar *buffer, int offset, int count, int timeout);
+int              discard_buffer (int fd, gboolean input);
+gint32           get_bytes_in_buffer (int fd, gboolean input);
+gboolean         is_baud_rate_legal (int baud_rate);
+int              setup_baud_rate (int baud_rate, gboolean *custom_baud_rate);
+gboolean         set_attributes (int fd, int baud_rate, MonoParity parity, int dataBits, MonoStopBits stopBits, MonoHandshake handshake);
+MonoSerialSignal get_signals (int fd, gint32 *error);
+gint32           set_signal (int fd, MonoSerialSignal signal, gboolean value);
+int              breakprop (int fd);
+gboolean         poll_serial (int fd, gint32 *error, int timeout);
+void            *list_serial_devices (void);
+
+int
+open_serial (char *devfile)
+{
+       int fd;
+       fd = open (devfile, O_RDWR | O_NOCTTY | O_NONBLOCK);
 
        return fd;
 }
 
-void
+int
 close_serial (int unix_fd)
 {
-       close (unix_fd);
+       // Linus writes: do not retry close after EINTR
+       return close (unix_fd);
 }
 
 guint32
-read_serial (int fd, guchar *buffer, int offset, int count, int timeout)
+read_serial (int fd, guchar *buffer, int offset, int count)
 {
        guint32 n;
-       struct pollfd ufd;
-
-       ufd.fd = fd;
-       ufd.events = POLLHUP | POLLIN | POLLERR;
-
-       poll (&ufd, 1, timeout);
-
-       if ((ufd.revents & POLLIN) != POLLIN) {
-               return -1;
-       }
  
        n = read (fd, buffer + offset, count);
 
        return (guint32) n;
 }
 
-void
+int
 write_serial (int fd, guchar *buffer, int offset, int count, int timeout)
 {
+       struct pollfd pinfo;
        guint32 n;
 
-       struct pollfd ufd;
-
-       ufd.fd = fd;
-       ufd.events = POLLHUP | POLLOUT | POLLERR;
-
-       poll (&ufd, 1, timeout);
-
-       if ((ufd.revents & POLLOUT) != POLLOUT) {
-               return;
+       pinfo.fd = fd;
+       pinfo.events = POLLOUT;
+       pinfo.revents = POLLOUT;
+
+       n = count;
+
+       while (n > 0)
+       {
+               ssize_t t;
+                       
+               if (timeout != 0) {
+                       int c;
+                       
+                       while ((c = poll (&pinfo, 1, timeout)) == -1 && errno == EINTR)
+                               ;
+                       if (c == -1)
+                               return -1;
+               }               
+
+               do {
+                       t = write (fd, buffer + offset, n);
+               } while (t == -1 && errno == EINTR);
+
+               if (t < 0)
+                       return -1;
+               
+               offset += t;
+               n -= t; 
        }
  
-       n = write (fd, buffer + offset, count);
+       return 0;
 }
 
-void
+int
 discard_buffer (int fd, gboolean input)
 {
-       tcflush(fd, input ? TCIFLUSH : TCOFLUSH);
+       return tcflush(fd, input ? TCIFLUSH : TCOFLUSH);
+}
+
+gint32
+get_bytes_in_buffer (int fd, gboolean input)
+{
+       gint32 retval;
+
+       if (ioctl (fd, input ? FIONREAD : TIOCOUTQ, &retval) == -1) {
+               return -1;
+       }
+
+       return retval;
 }
 
 gboolean
-set_attributes (int fd, int baud_rate, int parity, int dataBits, int stopBits, int handshake)
+is_baud_rate_legal (int baud_rate)
 {
-       struct termios newtio;
+       gboolean ignore = FALSE;
+       return setup_baud_rate (baud_rate, &ignore) != -1;
+}
 
-       tcgetattr (fd, &newtio);
-
-       switch (baud_rate) {
-       case 230400: baud_rate = B230400; break;
-       case 115200: baud_rate = B115200; break;
-       case 57600: baud_rate = B57600; break;
-       case 38400: baud_rate = B38400; break;
-       case 19200: baud_rate = B19200; break;
-       case 9600: baud_rate = B9600; break;
-       case 4800: baud_rate = B4800; break;
-       case 2400: baud_rate = B2400; break;
-       case 1800: baud_rate = B1800; break;
-       case 1200: baud_rate = B1200; break;
-       case 600: baud_rate = B600; break;
-       case 300: baud_rate = B300; break;
-       case 200: baud_rate = B200; break;
-       case 150: baud_rate = B150; break;
-       case 134: baud_rate = B134; break;
-       case 110: baud_rate = B110; break;
-       case 75: baud_rate = B75; break;
-       case 50:
-       case 0:
-       default:
+int
+setup_baud_rate (int baud_rate, gboolean *custom_baud_rate)
+{
+       switch (baud_rate)
+       {
+/*Some values are not defined on OSX and *BSD */
+#if defined(B921600)
+       case 921600:
+           baud_rate = B921600;
+           break;
+#endif
+#if defined(B460800)
+       case 460800:
+           baud_rate = B460800;
+           break;
+#endif
+       case 230400: 
+           baud_rate = B230400;
+           break;
+       case 115200: 
+           baud_rate = B115200;
+           break;
+       case 57600:
+           baud_rate = B57600;
+           break;
+       case 38400: 
+           baud_rate = B38400;
+           break;
+       case 19200: 
+           baud_rate = B19200;
+           break;
+       case 9600: 
                baud_rate = B9600;
                break;
-       }
-
-       switch (parity) {
-       case 0: /* Even */
-               newtio.c_iflag &= ~IGNPAR;
-               newtio.c_cflag |= PARENB;
+       case 4800: 
+           baud_rate = B4800;
                break;
-       case 1: /* Mark */
-               /* XXX unhandled */
+       case 2400: 
+           baud_rate = B2400;
                break;
-       case 2: /* None */
-               newtio.c_iflag |= IGNPAR;
-               newtio.c_cflag &= ~(PARENB | PARODD);
+       case 1800: 
+           baud_rate = B1800;
                break;
-       case 3: /* Odd */
-               newtio.c_iflag &= ~IGNPAR;
-               newtio.c_cflag |= PARENB | PARODD;
+       case 1200: 
+           baud_rate = B1200;
                break;
-       case 4: /* Space */
-               /* XXX unhandled */
+       case 600: 
+           baud_rate = B600;
+           break;
+       case 300: 
+           baud_rate = B300;
+           break;
+       case 200: 
+           baud_rate = B200;
+           break;
+       case 150: 
+           baud_rate = B150;
+           break;
+       case 134: 
+           baud_rate = B134;
+           break;
+       case 110: 
+           baud_rate = B110;
+           break;
+       case 75: 
+           baud_rate = B75;
+           break;
+       case 50:
+#ifdef B50
+           baud_rate = B50;
+#else
+           baud_rate = -1;
+           break;
+#endif
+       case 0:
+#ifdef B0
+           baud_rate = B0;
+#else
+           baud_rate = -1;
+#endif
+           break;
+       default:
+               *custom_baud_rate = TRUE;
                break;
        }
+       return baud_rate;
+}
+
+gboolean
+set_attributes (int fd, int baud_rate, MonoParity parity, int dataBits, MonoStopBits stopBits, MonoHandshake handshake)
+{
+       struct termios newtio;
+       gboolean custom_baud_rate = FALSE;
+
+       if (tcgetattr (fd, &newtio) == -1)
+               return FALSE;
 
+       newtio.c_cflag |=  (CLOCAL | CREAD);
+       newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG | IEXTEN );
+       newtio.c_oflag &= ~(OPOST);
+       newtio.c_iflag = IGNBRK;
+
+       /* setup baudrate */
+       baud_rate = setup_baud_rate (baud_rate, &custom_baud_rate);
+
+       /* char lenght */
        newtio.c_cflag &= ~CSIZE;
-       switch (dataBits) {
-       case 5: newtio.c_cflag |= CS5; break;
-       case 6: newtio.c_cflag |= CS6; break;
-       case 7: newtio.c_cflag |= CS7; break;
+       switch (dataBits)
+       {
+       case 5: 
+           newtio.c_cflag |= CS5;
+           break;
+       case 6: 
+           newtio.c_cflag |= CS6;
+           break;
+       case 7: 
+           newtio.c_cflag |= CS7;
+           break;
        case 8:
        default:
                newtio.c_cflag |= CS8;
                break;
        }
 
-       newtio.c_cflag &= ~CSTOPB;
-       switch (stopBits) {
-       case 0: /* One */
-               /* do nothing, the default is one stop bit */
+       /* stopbits */
+       switch (stopBits)
+       {
+       case NoneStopBits:
+               /* Unhandled */
                break;
-       case 1: /* OnePointFive */
-               /* XXX unhandled */
+       case One: /* One */
+               /* do nothing, the default is one stop bit */
+           newtio.c_cflag &= ~CSTOPB;
                break;
-       case 2: /* Two */
+       case Two: /* Two */
                newtio.c_cflag |= CSTOPB;
                break;
+       case OnePointFive: /* OnePointFive */
+               /* XXX unhandled */
+               break;
+       }
+
+       /* parity */
+       newtio.c_iflag &= ~(INPCK | ISTRIP );
+
+       switch (parity)
+       {
+       case NoneParity: /* None */
+           newtio.c_cflag &= ~(PARENB | PARODD);
+           break;
+           
+       case Odd: /* Odd */
+           newtio.c_cflag |= PARENB | PARODD;
+           break;
+           
+       case Even: /* Even */
+           newtio.c_cflag &= ~(PARODD);
+           newtio.c_cflag |= (PARENB);
+           break;
+           
+       case Mark: /* Mark */
+           /* XXX unhandled */
+           break;
+       case Space: /* Space */
+           /* XXX unhandled */
+           break;
        }
 
-       newtio.c_iflag &= ~IXOFF;
-       newtio.c_oflag &= ~IXON;
+       newtio.c_iflag &= ~(IXOFF | IXON);
 #ifdef CRTSCTS
        newtio.c_cflag &= ~CRTSCTS;
 #endif /* def CRTSCTS */
-       switch (handshake) {
-       case 0: /* None */
+
+       switch (handshake)
+       {
+       case NoneHandshake: /* None */
                /* do nothing */
                break;
-       case 1: /* RequestToSend (RTS) */
+       case RequestToSend: /* RequestToSend (RTS) */
 #ifdef CRTSCTS
                newtio.c_cflag |= CRTSCTS;
 #endif /* def CRTSCTS */
                break;
-       case 2: /* RequestToSendXOnXOff (RTS + XON/XOFF) */
+       case RequestToSendXOnXOff: /* RequestToSendXOnXOff (RTS + XON/XOFF) */
 #ifdef CRTSCTS
                newtio.c_cflag |= CRTSCTS;
 #endif /* def CRTSCTS */
                /* fall through */
-       case 3: /* XOnXOff */
-               newtio.c_iflag |= IXOFF;
-               //              newtio.c_oflag |= IXON;
+       case XOnXOff: /* XOnXOff */
+           newtio.c_iflag |= IXOFF | IXON;
                break;
        }
-       
-       cfsetospeed (&newtio, baud_rate);
-       cfsetispeed (&newtio, baud_rate);
 
-       tcsetattr(fd,TCSADRAIN,&newtio);
+       if (custom_baud_rate == FALSE) {
+               if (cfsetospeed (&newtio, baud_rate) < 0 || cfsetispeed (&newtio, baud_rate) < 0)
+                       return FALSE;
+       } else {
+#if __linux__ || defined(HAVE_IOKIT)
+
+               /* On Linux to set a custom baud rate, we must set the
+                * "standard" baud_rate to 38400.   On Apple we set it purely
+                * so that tcsetattr has something to use (and report back later), but
+                * the Apple specific API is still opaque to these APIs, see:
+                * https://developer.apple.com/library/mac/samplecode/SerialPortSample/Listings/SerialPortSample_SerialPortSample_c.html#//apple_ref/doc/uid/DTS10000454-SerialPortSample_SerialPortSample_c-DontLinkElementID_4
+                */
+               if (cfsetospeed (&newtio, B38400) < 0 || cfsetispeed (&newtio, B38400) < 0)
+                       return FALSE;
+#endif
+       }
+
+       if (tcsetattr (fd, TCSANOW, &newtio) < 0)
+               return FALSE;
+
+       if (custom_baud_rate == TRUE){
+#if defined(HAVE_LINUX_SERIAL_H)
+               struct serial_struct ser;
+
+               if (ioctl (fd, TIOCGSERIAL, &ser) < 0)
+               {
+                       return FALSE;
+               }
+
+               ser.custom_divisor = ser.baud_base / baud_rate;
+               ser.flags &= ~ASYNC_SPD_MASK;
+               ser.flags |= ASYNC_SPD_CUST;
+
+               if (ioctl (fd, TIOCSSERIAL, &ser) < 0)
+               {
+                       return FALSE;
+               }
+#elif defined(HAVE_IOKIT)
+               speed_t speed = baud_rate;
+               if (ioctl(fd, IOSSIOSPEED, &speed) == -1)
+                       return FALSE;
+#else
+               /* Don't know how to set custom baud rate on this platform. */
+               return FALSE;
+#endif
+       }
 
        return TRUE;
 }
 
+
+static gint32
+get_signal_code (MonoSerialSignal signal)
+{
+       switch (signal) {
+               case Cd:
+                       return TIOCM_CAR;
+               case Cts:
+                       return TIOCM_CTS;
+               case Dsr:
+                       return TIOCM_DSR;
+               case Dtr:
+                       return TIOCM_DTR;
+               case Rts:
+                       return TIOCM_RTS;
+               default:
+                       return 0;
+       }
+
+       /* Not reached */
+       return 0;
+}
+
+static MonoSerialSignal
+get_mono_signal_codes (int signals)
+{
+       MonoSerialSignal retval = NoneSignal;
+
+       if ((signals & TIOCM_CAR) != 0)
+               retval |= Cd;
+       if ((signals & TIOCM_CTS) != 0)
+               retval |= Cts;
+       if ((signals & TIOCM_DSR) != 0)
+               retval |= Dsr;
+       if ((signals & TIOCM_DTR) != 0)
+               retval |= Dtr;
+       if ((signals & TIOCM_RTS) != 0)
+               retval |= Rts;
+
+       return retval;
+}
+
+MonoSerialSignal
+get_signals (int fd, gint32 *error)
+{
+       int signals;
+
+       *error = 0;
+       
+       if (ioctl (fd, TIOCMGET, &signals) == -1) {
+               *error = -1;
+               return NoneSignal;
+       }
+       
+       return get_mono_signal_codes (signals);
+}
+
+gint32
+set_signal (int fd, MonoSerialSignal signal, gboolean value)
+{
+       int signals, expected, activated;
+
+       expected = get_signal_code (signal);
+       if (ioctl (fd, TIOCMGET, &signals) == -1)
+               return -1;
+       
+       activated = (signals & expected) != 0;
+       if (activated == value) /* Already set */
+               return 1;
+       
+       if (value)
+               signals |= expected;
+       else
+               signals &= ~expected;
+       
+       if (ioctl (fd, TIOCMSET, &signals) == -1)
+               return -1;
+       
+       return 1;
+}
+
+int
+breakprop (int fd)
+{
+       return tcsendbreak (fd, 0);
+}
+
+gboolean
+poll_serial (int fd, gint32 *error, int timeout)
+{
+       struct pollfd pinfo;
+       
+       *error = 0;
+       
+       pinfo.fd = fd;
+       pinfo.events = POLLIN;
+       pinfo.revents = 0;
+
+       while (poll (&pinfo, 1, timeout) == -1 && errno == EINTR) {
+               /* EINTR is an OK condition, we should not throw in the upper layer an IOException */
+               if (errno != EINTR){
+                       *error = -1;
+                       return FALSE;
+               }
+       }
+
+       return (pinfo.revents & POLLIN) != 0 ? 1 : 0;
+}
+
 /*
  * mono internals should not be used here.
  * this serial stuff needs to be implemented with icalls.