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().
Signed-off-by: Stefan Tauner <stefan.tauner(a)student.tuwien.ac.at>
---
programmer.h | 1 +
serial.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
serprog.c | 19 ++++------------
3 files changed, 78 insertions(+), 14 deletions(-)
diff --git a/programmer.h b/programmer.h
index e385314..722fa54 100644
--- a/programmer.h
+++ b/programmer.h
@@ -649,6 +649,7 @@ extern fdtype sp_fd;
/* 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 15fafbb..7c136c9 100644
--- a/serial.c
+++ b/serial.c
@@ -387,3 +387,75 @@ int serialport_read_nonblock(unsigned char *c, unsigned int readcnt, unsigned in
#endif
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) != 0) {
+ msg_perr("Could not get serial port timeout settings!\n");
+ return -1;
+ }
+ if(SetCommTimeouts(sp_fd, &newTimeout) != 0) {
+ msg_perr("Could not set serial port timeout settings!\n");
+ 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("Serial port write error: %s\n", strerror(errno));
+ 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) != 0) {
+ msg_perr("Could not restore serial port timeout settings!\n");
+ return -1;
+ }
+#else
+ fcntl(sp_fd, F_SETFL, flags);
+#endif
+ return ret;
+}
diff --git a/serprog.c b/serprog.c
index cc06798..cf329a3 100644
--- a/serprog.c
+++ b/serprog.c
@@ -117,23 +117,19 @@ static int sp_opensocket(char *ip, unsigned int port)
/* 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 @@ static int sp_synchronize(void)
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 @@ static int sp_synchronize(void)
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 @@ static int sp_synchronize(void)
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;
}
--
Kind regards, Stefan Tauner