Introduce serialport_write_nonblock()

It seems useful to have a generic and platform-independent method to
read and write to a serial port without blocking. This is the write part.

This allows to get rid of the explicit temporary disabling of blocking I/O in
serprog's sp_synchronize().

Corresponding to flashrom svn r1662.

Signed-off-by: Stefan Tauner <stefan.tauner@alumni.tuwien.ac.at>
Acked-by: Stefan Tauner <stefan.tauner@alumni.tuwien.ac.at>
diff --git a/programmer.h b/programmer.h
index c36b452..a22bdd2 100644
--- a/programmer.h
+++ b/programmer.h
@@ -660,6 +660,7 @@
 /* expose serialport_shutdown as it's currently used by buspirate */
 int serialport_shutdown(void *data);
 int serialport_write(unsigned char *buf, unsigned int writecnt);
+int serialport_write_nonblock(unsigned char *buf, unsigned int writecnt, unsigned int timeout, unsigned int *really_wrote);
 int serialport_read(unsigned char *buf, unsigned int readcnt);
 int serialport_read_nonblock(unsigned char *c, unsigned int readcnt, unsigned int timeout, unsigned int *really_read);
 
diff --git a/serial.c b/serial.c
index d1e2cea..8e27aa0 100644
--- a/serial.c
+++ b/serial.c
@@ -431,3 +431,75 @@
 	}
 	return ret;
 }
+
+/* Tries up to timeout ms to write writecnt characters from the array starting at buf. Returns
+ * 0 on success, positive values on temporary errors (e.g. timeouts) and negative ones on permanent errors.
+ * If really_wrote is not NULL, this function sets its contents to the number of bytes written successfully. */
+int serialport_write_nonblock(unsigned char *buf, unsigned int writecnt, unsigned int timeout, unsigned int *really_wrote)
+{
+	int ret = 1;
+	/* disable blocked i/o and declare platform-specific variables */
+#ifdef _WIN32
+	DWORD rv;
+	COMMTIMEOUTS oldTimeout;
+	COMMTIMEOUTS newTimeout = {
+		.ReadIntervalTimeout = MAXDWORD,
+		.ReadTotalTimeoutMultiplier = 0,
+		.ReadTotalTimeoutConstant = 0,
+		.WriteTotalTimeoutMultiplier = 0,
+		.WriteTotalTimeoutConstant = 0
+	};
+	if(!GetCommTimeouts(sp_fd, &oldTimeout)) {
+		msg_perr_strerror("Could not get serial port timeout settings: ");
+		return -1;
+	}
+	if(!SetCommTimeouts(sp_fd, &newTimeout)) {
+		msg_perr_strerror("Could not set serial port timeout settings: ");
+		return -1;
+	}
+#else
+	ssize_t rv;
+	const int flags = fcntl(sp_fd, F_GETFL);
+	fcntl(sp_fd, F_SETFL, flags | O_NONBLOCK);
+#endif
+
+	int i;
+	int wr_bytes = 0;
+	for (i = 0; i < timeout; i++) {
+		msg_pspew("writecnt %d wr_bytes %d\n", writecnt, wr_bytes);
+#ifdef _WIN32
+		WriteFile(sp_fd, buf + wr_bytes, writecnt - wr_bytes, &rv, NULL);
+		msg_pspew("wrote %lu bytes\n", rv);
+#else
+		rv = write(sp_fd, buf + wr_bytes, writecnt - wr_bytes);
+		msg_pspew("wrote %zd bytes\n", rv);
+#endif
+		if ((rv == -1) && (errno != EAGAIN)) {
+			msg_perr_strerror("Serial port write error: ");
+			ret = -1;
+			break;
+		}
+		if (rv > 0) {
+			wr_bytes += rv;
+			if (wr_bytes == writecnt) {
+				msg_pspew("write successful\n");
+				ret = 0;
+				break;
+			}
+		}
+		internal_delay(1000);	/* 1ms units */
+	}
+	if (really_wrote != NULL)
+		*really_wrote = wr_bytes;
+
+	/* restore original blocking behavior */
+#ifdef _WIN32
+	if (!SetCommTimeouts(sp_fd, &oldTimeout)) {
+		msg_perr_strerror("Could not restore serial port timeout settings: ");
+#else
+	if (fcntl(sp_fd, F_SETFL, flags) != 0) {
+#endif
+		return -1;
+	}
+	return ret;
+}
diff --git a/serprog.c b/serprog.c
index 15d1d1b..ae01d29 100644
--- a/serprog.c
+++ b/serprog.c
@@ -117,23 +117,19 @@
 
 /* Synchronize: a bit tricky algorithm that tries to (and in my tests has *
  * always succeeded in) bring the serial protocol to known waiting-for-   *
- * command state - uses nonblocking read - rest of the driver uses	  *
+ * command state - uses nonblocking I/O - rest of the driver uses         *
  * blocking read - TODO: add an alarm() timer for the rest of the app on  *
  * serial operations, though not such a big issue as the first thing to   *
  * do is synchronize (eg. check that device is alive).			  */
 static int sp_synchronize(void)
 {
 	int i;
-	int flags = fcntl(sp_fd, F_GETFL);
 	unsigned char buf[8];
-	flags |= O_NONBLOCK;
-	fcntl(sp_fd, F_SETFL, flags);
 	/* First sends 8 NOPs, then flushes the return data - should cause *
 	 * the device serial parser to get to a sane state, unless if it   *
 	 * is waiting for a real long write-n.                             */
 	memset(buf, S_CMD_NOP, 8);
-	if (write(sp_fd, buf, 8) != 8) {
-		msg_perr("flush write: %s\n", strerror(errno));
+	if (serialport_write_nonblock(buf, 8, 1, NULL) != 0) {
 		goto err_out;
 	}
 	/* A second should be enough to get all the answers to the buffer */
@@ -147,8 +143,7 @@
 	for (i = 0; i < 8; i++) {
 		int n;
 		unsigned char c = S_CMD_SYNCNOP;
-		if (write(sp_fd, &c, 1) != 1) {
-			msg_perr("sync write: %s\n", strerror(errno));
+		if (serialport_write_nonblock(&c, 1, 1, NULL) != 0) {
 			goto err_out;
 		}
 		msg_pdbg(".");
@@ -165,9 +160,8 @@
 			if (ret > 0 || c != S_ACK)
 				continue;
 			c = S_CMD_SYNCNOP;
-			if (write(sp_fd, &c, 1) != 1) {
-				msg_perr("sync write: %s\n", strerror(errno));
-				return 1;
+			if (serialport_write_nonblock(&c, 1, 1, NULL) != 0) {
+				goto err_out;
 			}
 			ret = serialport_read_nonblock(&c, 1, 500, NULL);
 			if (ret < 0)
@@ -179,9 +173,6 @@
 				goto err_out;
 			if (c != S_ACK)
 				break;	/* fail */
-			/* Ok, synchronized; back to blocking reads and return. */
-			flags &= ~O_NONBLOCK;
-			fcntl(sp_fd, F_SETFL, flags);
 			msg_pdbg("\n");
 			return 0;
 		}