From be5defaa860696536de348fb7b8fdb26540821ec Mon Sep 17 00:00:00 2001
From: Joerg Wunsch <j@uriah.heep.sax.de>
Date: Tue, 25 Nov 2014 21:33:22 +0000
Subject: [PATCH] patch #8380: adds 500k 1M 2M baud to ser_posix.c *
 ser_posix.c: Add a hack to allow for arbitrary baud rates on Linux

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/trunk/avrdude@1351 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 ChangeLog   |  6 ++++
 NEWS        |  4 +++
 ser_posix.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 92 insertions(+), 6 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 42a6795d..b229f2e1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2014-11-25  Joerg Wunsch <j.gnu@uriah.heep.sax.de>
+
+	patch #8380: adds 500k 1M 2M baud to ser_posix.c
+	* ser_posix.c: Add a hack to allow for arbitrary baud rates on
+	Linux
+
 2014-11-25  Joerg Wunsch <j.gnu@uriah.heep.sax.de>
 
 	patch #8437: [PATCH] Serial-over-ethernet for Win32
diff --git a/NEWS b/NEWS
index d2be83fd..32453c2b 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,9 @@ Current:
     - The "-P net:" syntax (forwarding of serial data over TCP) is now
       also implemented for Win32 systems.
 
+    - Allow for arbitrary serial baudrates under Linux (OSX and *BSD
+      could already handle it).
+
 
   * New devices supported:
     - AT90PWM216 (bug #42310: New part description for AT90PWM216)
@@ -57,6 +60,7 @@ Current:
     - bug #40870: config nitpick: ATtiny25/45/85 have 1 calibration byte not 2
     - bug #42908: no external reset at JTAGICE3
     - patch #8437: [PATCH] Serial-over-ethernet for Win32
+    - patch #8380: adds 500k 1M 2M baud to ser_posix.c
 
   * Internals:
     - Removing exit calls from config parser
diff --git a/ser_posix.c b/ser_posix.c
index beeb9bdc..20230f9b 100644
--- a/ser_posix.c
+++ b/ser_posix.c
@@ -37,6 +37,9 @@
 #include <sys/socket.h>
 #include <netdb.h>
 #include <netinet/in.h>
+#ifdef __linux__
+#include <linux/serial.h>
+#endif
 
 #include <fcntl.h>
 #include <termios.h>
@@ -52,8 +55,13 @@ struct baud_mapping {
   speed_t speed;
 };
 
-/* There are a lot more baud rates we could handle, but what's the point? */
+static struct termios original_termios;
+static int saved_original_termios;
 
+#if !defined __linux__
+/* For linux this mapping is no longer needed.
+ * (OSX and *BSD do not need this mapping either because for them,
+ * Bxxx is the same as xxx.) */
 static struct baud_mapping baud_lookup_table [] = {
   { 1200,   B1200 },
   { 2400,   B2400 },
@@ -73,8 +81,6 @@ static struct baud_mapping baud_lookup_table [] = {
   { 0,      0 }                 /* Terminator. */
 };
 
-static struct termios original_termios;
-static int saved_original_termios;
 
 static speed_t serial_baud_lookup(long baud)
 {
@@ -95,12 +101,19 @@ static speed_t serial_baud_lookup(long baud)
 
   return baud;
 }
+#endif
 
 static int ser_setspeed(union filedescriptor *fd, long baud)
 {
   int rc;
   struct termios termios;
+#if defined __linux__
+  /* for linux no conversion is needed*/
+  speed_t speed = baud;
+#else
+  /* converting the baud rate to the bit set needed by posix way*/
   speed_t speed = serial_baud_lookup (baud);
+#endif
   
   if (!isatty(fd->ifd))
     return -ENOTTY;
@@ -128,16 +141,79 @@ static int ser_setspeed(union filedescriptor *fd, long baud)
   termios.c_cflag = (CS8 | CREAD | CLOCAL);
   termios.c_cc[VMIN]  = 1;
   termios.c_cc[VTIME] = 0;
+#ifdef __linux__
+  /* Support for custom baud rate for linux is implemented by setting
+   * a dummy baud rate of 38400 and manupulating the custom divider of
+   * the serial interface*/
+  struct serial_struct  ss;
+  int ioret = ioctl(fd->ifd, TIOCGSERIAL, &ss);
+  if (ioret < 0){
+    avrdude_message(MSG_INFO,
+		    "%s: Cannot get serial port settings. ioctl returned %d\n",
+		    progname, ioret);
+    return -errno;
+  }
+  ss.flags = (ss.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST;
+  ss.custom_divisor = (ss.baud_base + (speed / 2)) / speed;
+  unsigned int closestSpeed = ss.baud_base / ss.custom_divisor;
 
-  cfsetospeed(&termios, speed);
-  cfsetispeed(&termios, speed);
-
+  if (closestSpeed < speed * 98 / 100 || closestSpeed > speed * 102 / 100) {
+    avrdude_message(MSG_INFO,
+		    "%s: Cannot set serial port speed to %d. Closest possible is %d\n",
+		    progname, speed, closestSpeed);
+    return -errno;
+  }
+  ioret= ioctl(fd->ifd, TIOCSSERIAL, &ss);
+  if (ioret < 0){
+    avrdude_message(MSG_INFO,
+		    "%s: Cannot set serial port speed to %d. ioctl returned %d\n",
+		    progname, speed, ioret);
+    return -errno;
+  }
+  if (cfsetispeed(&termios, B38400) < 0){
+    avrdude_message(MSG_INFO,
+		    "%s: cfsetispeed: failed to set dummy baud\n",
+		    progname);
+    return -errno;
+  }
+  if (cfsetospeed(&termios, B38400) < 0){
+    avrdude_message(MSG_INFO,
+		    "%s: cfsetospeed: failed to set dummy baud\n",
+		    progname);
+    return -errno;
+  }
+#else  /* !linux */
+  if (cfsetospeed(&termios, speed) < 0){
+    avrdude_message(MSG_INFO,
+		    "%s: cfsetospeed: failed to set speed: %d\n",
+		    progname, speed);
+    return -errno;
+  }
+  if (cfsetispeed(&termios, speed) < 0){
+    avrdude_message(MSG_INFO,
+		    "%s: cfsetispeed: failed to set speed: %d\n",
+		    progname, speed);
+    return -errno;
+  }
+#endif	/* linux */
   rc = tcsetattr(fd->ifd, TCSANOW, &termios);
   if (rc < 0) {
     avrdude_message(MSG_INFO, "%s: ser_setspeed(): tcsetattr() failed\n",
             progname);
     return -errno;
   }
+#ifdef __linux__
+  /* a bit more linux specific stuff to set custom baud rates*/
+  if (ioctl(fd->ifd, TIOCGSERIAL, &ss) < 0){
+    avrdude_message(MSG_INFO, "%s: ioctl: failed to get port settins\n", progname);
+    return -errno;
+  }
+  ss.flags &= ~ASYNC_SPD_MASK;
+  if (ioctl(fd->ifd, TIOCSSERIAL, &ss) < 0){
+    avrdude_message(MSG_INFO, "%s: ioctl: failed to set port settins\n", progname);
+    return -errno;
+  }
+#endif
 
   /*
    * Everything is now set up for a local line without modem control