Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / support / serial.c
index abe409e05b617269da96c9b7f453dbe179d31ca9..5e3d732c005170bbcd4052841fad7dc3b27b342c 100644 (file)
@@ -5,13 +5,25 @@
  * 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>
-#include <errno.h>
+
+/* This is for ASYNC_*, serial_struct on linux */
+#if defined(HAVE_LINUX_SERIAL_H)
+#include <linux/serial.h>
+#endif
 
 #include <glib.h>
 
 #endif
 
 /* sys/time.h (for timeval) is required when using osx 10.3 (but not 10.4) */
-#ifdef __APPLE__
+/* 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 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
 
 /* This is a copy of System.IO.Ports.Handshake */
@@ -60,22 +81,40 @@ typedef enum {
        Rts = 16  /* Request to send */
 } MonoSerialSignal;
 
+/*
+ * Silence the compiler, we do not need these prototypes to be public, since these are only
+ * used by P/Invoke
+ */
+
+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 (chardevfile)
+open_serial (char *devfile)
 {
        int fd;
        fd = open (devfile, O_RDWR | O_NOCTTY | O_NONBLOCK);
 
-       if (fd == -1)
-               return -1;
-
        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
@@ -92,27 +131,19 @@ int
 write_serial (int fd, guchar *buffer, int offset, int count, int timeout)
 {
        struct pollfd pinfo;
+       guint32 n;
 
        pinfo.fd = fd;
        pinfo.events = POLLOUT;
        pinfo.revents = POLLOUT;
 
-       
-       struct timeval tmval;
-       fd_set writefs;
-       guint32 n;
-
        n = count;
 
-       FD_SET(fd, &writefs);
-       tmval.tv_sec = timeout / 1000;
-       tmval.tv_usec = (timeout - tmval.tv_sec) * 1000;        
-       
        while (n > 0)
        {
-               size_t t;
+               ssize_t t;
                        
-               if (timeout > 0) {
+               if (timeout != 0) {
                        int c;
                        
                        while ((c = poll (&pinfo, 1, timeout)) == -1 && errno == EINTR)
@@ -135,15 +166,20 @@ write_serial (int fd, guchar *buffer, int offset, int count, int timeout)
        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)
 {
+#if defined(__HAIKU__)
+       /* FIXME: Haiku doesn't support TIOCOUTQ nor FIONREAD on fds */
+       return -1;
+#define TIOCOUTQ 0
+#endif
        gint32 retval;
 
        if (ioctl (fd, input ? FIONREAD : TIOCOUTQ, &retval) == -1) {
@@ -154,19 +190,28 @@ get_bytes_in_buffer (int fd, gboolean input)
 }
 
 gboolean
-set_attributes (int fd, int baud_rate, MonoParity parity, int dataBits, MonoStopBits stopBits, MonoHandshake handshake)
+is_baud_rate_legal (int baud_rate)
 {
-       struct termios newtio;
-
-       tcgetattr (fd, &newtio);
-       newtio.c_cflag |=  (CLOCAL | CREAD);
-       newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG | IEXTEN );
-       newtio.c_oflag &= ~(OPOST);
-       newtio.c_iflag = IGNBRK;
+       gboolean ignore = FALSE;
+       return setup_baud_rate (baud_rate, &ignore) != -1;
+}
 
-       /* setup baudrate */
+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;
@@ -219,11 +264,42 @@ set_attributes (int fd, int baud_rate, MonoParity parity, int dataBits, MonoStop
            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:
-           baud_rate = B9600;
+               *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;
@@ -277,6 +353,7 @@ set_attributes (int fd, int baud_rate, MonoParity parity, int dataBits, MonoStop
            
        case Even: /* Even */
            newtio.c_cflag &= ~(PARODD);
+           newtio.c_cflag |= (PARENB);
            break;
            
        case Mark: /* Mark */
@@ -311,16 +388,55 @@ set_attributes (int fd, int baud_rate, MonoParity parity, int dataBits, MonoStop
            newtio.c_iflag |= IXOFF | IXON;
                break;
        }
-       
-       if (cfsetospeed (&newtio, baud_rate) < 0 || cfsetispeed (&newtio, baud_rate) < 0 ||
-           tcsetattr (fd, TCSANOW, &newtio) < 0)
-       {
+
+       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
        }
-       else
-       {
+
        return TRUE;
-       }
 }
 
 
@@ -404,10 +520,10 @@ set_signal (int fd, MonoSerialSignal signal, gboolean value)
        return 1;
 }
 
-void
+int
 breakprop (int fd)
 {
-       tcsendbreak (fd, 0);
+       return tcsendbreak (fd, 0);
 }
 
 gboolean