Patchwork [5/7] Combine serialport_write and serialport_read into serialport_rw.

login
register
about
Submitter Stefan Tauner
Date 2013-07-10 19:21:09
Message ID <1373484071-549-6-git-send-email-stefan.tauner@student.tuwien.ac.at>
Download mbox | patch
Permalink /patch/3982/
State New
Headers show

Comments

Stefan Tauner - 2013-07-10 19:21:09
Because they share almost all code, combine them into serialport_rw
and export the old functions as wrappers. This makes reads "empty reads"-
aware and programmers able to detect such problems even on "blocking"
reads. Also, this fixes error detection on Windows.

Signed-off-by: Stefan Tauner <stefan.tauner@student.tuwien.ac.at>
---
 serial.c | 83 ++++++++++++++++++++++++++++++----------------------------------
 1 file changed, 39 insertions(+), 44 deletions(-)
Carl-Daniel Hailfinger - 2013-07-18 23:57:30
Am 10.07.2013 21:21 schrieb Stefan Tauner:
> Because they share almost all code, combine them into serialport_rw
> and export the old functions as wrappers. This makes reads "empty reads"-
> aware and programmers able to detect such problems even on "blocking"
> reads. Also, this fixes error detection on Windows.
>
> Signed-off-by: Stefan Tauner <stefan.tauner@student.tuwien.ac.at>
> ---
>  serial.c | 83 ++++++++++++++++++++++++++++++----------------------------------
>  1 file changed, 39 insertions(+), 44 deletions(-)
>
> diff --git a/serial.c b/serial.c
> index 1b394cd..5f74fd1 100644
> --- a/serial.c
> +++ b/serial.c
> @@ -328,66 +330,59 @@ int serialport_shutdown(void *data)
>  	return 0;
>  }
>  
> -int serialport_write(unsigned char *buf, unsigned int writecnt)
> +#define EMPTY_TRIES 10

A total of 10 ms timeout is too fast for the Bus Pirate and some slower
serprog implementations AFAICS. I'd prefer a 1 s timeout before
signaling an error. That should be sufficient, and callers are supposed
to check for errors, so the 1 s timeout won't happen multiple times.


> +/* Writes (if \c outdir is true) or reads \c todo bytes from/into \c buf.
> + * Tries up to \c EMPTY_TRIES times with a timeout of \c ms ms between each try. */
> +static int serialport_rw(unsigned char *buf, unsigned int todo, bool outdir, unsigned int ms)
>  {
> -#ifdef _WIN32
> -	DWORD tmp = 0;
> -#else
> -	ssize_t tmp = 0;
> -#endif
> -	unsigned int empty_writes = 250; /* results in a ca. 125ms timeout */
> +	const char * const op = outdir ? "write" : "read";
> +	bool err = false;
> +	unsigned int empty_tries = EMPTY_TRIES;
>  
> -	while (writecnt > 0) {
> +	while (todo > 0) {
>  #ifdef _WIN32
> -		WriteFile(sp_fd, buf, writecnt, &tmp, NULL);
> +		DWORD cur = 0;
> +		if (outdir)
> +			err = !WriteFile(sp_fd, buf, todo, &cur, NULL);
> +		else
> +			err = !ReadFile(sp_fd, buf, todo, &cur, NULL);

Should we check for cur == 0 here? Can that happen?


>  #else
> -		tmp = write(sp_fd, buf, writecnt);
> -#endif
> -		if (tmp == -1) {
> -			msg_perr("Serial port write error!\n");
> +		ssize_t cur = 0;
> +		if (outdir)
> +			err = (cur = write(sp_fd, buf, todo)) < 0;

OK.


> +		else
> +			err = (cur = read(sp_fd, buf, todo)) < 0;

Should be <= 0 instead of < 0 AFAICS. See the POSIX man page of read():
"When attempting to read from an empty pipe or FIFO: If no process has
the pipe open for writing, read() shall return 0 to indicate end-of-file."

 
> +#endif
> +		if (err) {
> +			msg_perr("Serial port %s error!\n", op);
>  			return 1;
>  		}
> -		if (!tmp) {
> -			msg_pdbg2("Empty write\n");
> -			empty_writes--;
> -			programmer_delay(500);
> -			if (empty_writes == 0) {
> -				msg_perr("Serial port is unresponsive!\n");
> +		if (cur == 0) {
> +			msg_pdbg2("Empty %s.\n", op);
> +			empty_tries--;
> +			programmer_delay(ms * 1000);
> +			if (empty_tries == 0) {
> +				msg_perr("Serial port is unresponsive or disappeared!\n");
>  				return 1;
>  			}
> +			continue;
>  		}
> -		writecnt -= tmp;
> -		buf += tmp;
> +		todo -= cur;
> +		buf += cur;
> +		empty_tries = EMPTY_TRIES;
>  	}
>  
>  	return 0;
>  }
>  
> -int serialport_read(unsigned char *buf, unsigned int readcnt)
> +int serialport_write(unsigned char *buf, unsigned int writecnt)
>  {
> -#ifdef _WIN32
> -	DWORD tmp = 0;
> -#else
> -	ssize_t tmp = 0;
> -#endif
> -
> -	while (readcnt > 0) {
> -#ifdef _WIN32
> -		ReadFile(sp_fd, buf, readcnt, &tmp, NULL);
> -#else
> -		tmp = read(sp_fd, buf, readcnt);
> -#endif
> -		if (tmp == -1) {
> -			msg_perr("Serial port read error!\n");
> -			return 1;
> -		}
> -		if (!tmp)
> -			msg_pdbg2("Empty read\n");
> -		readcnt -= tmp;
> -		buf += tmp;
> -	}
> +	return serialport_rw(buf, writecnt, true, 1);
> +}
>  
> -	return 0;
> +int serialport_read(unsigned char *buf, unsigned int readcnt)
> +{
> +	return serialport_rw(buf, readcnt, false, 10);
>  }
>  
>  /* Tries up to timeout ms to read readcnt characters and places them into the array starting at c. Returns


Regards,
Carl-Daniel
Stefan Tauner - 2013-07-20 00:55:01
On Fri, 19 Jul 2013 01:57:30 +0200
Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006@gmx.net> wrote:

> Am 10.07.2013 21:21 schrieb Stefan Tauner:
> > Because they share almost all code, combine them into serialport_rw
> > and export the old functions as wrappers. This makes reads "empty reads"-
> > aware and programmers able to detect such problems even on "blocking"
> > reads. Also, this fixes error detection on Windows.
> >
> > Signed-off-by: Stefan Tauner <stefan.tauner@student.tuwien.ac.at>
> > ---
> >  serial.c | 83 ++++++++++++++++++++++++++++++----------------------------------
> >  1 file changed, 39 insertions(+), 44 deletions(-)
> >
> > diff --git a/serial.c b/serial.c
> > index 1b394cd..5f74fd1 100644
> > --- a/serial.c
> > +++ b/serial.c
> > @@ -328,66 +330,59 @@ int serialport_shutdown(void *data)
> >  	return 0;
> >  }
> >  
> > -int serialport_write(unsigned char *buf, unsigned int writecnt)
> > +#define EMPTY_TRIES 10
> 
> A total of 10 ms timeout is too fast for the Bus Pirate and some slower
> serprog implementations AFAICS. I'd prefer a 1 s timeout before
> signaling an error. That should be sufficient, and callers are supposed
> to check for errors, so the 1 s timeout won't happen multiple times.

I was under the impression that reads and writes are blocking, hence
that these timeout values do not determine the upper bound of the
execution time of one operation. I came to that conclusion because it is
the behavior of my hardware (serprog via cdc-acm) and software (native
linux and wine). I also tested it on native windows.

On non-windows we open the device with O_NDELAY which seems to have been
different to O_NONBLOCK in the distant past but is now semantically
equivalent on most platforms. However, only O_NONBLOCK seems to be in
POSIX and I would like to switch to that.

That being said, apparently we are relying on not too well specified
behavior. AFAIK the tty devices are "character special" files/devices.
The standard specifies the following:

"When opening a block special or character special file that supports
non-blocking opens:
 - If O_NONBLOCK is set, the open() function shall return without
   blocking for the device to be ready or available. *Subsequent
   behavior of the device is device-specific*." (emphasis mine, and I
   am very certain from the other context that this means if reads and
   writes are blocking or not)

So... before I think and write about what we could do, I would like to
hear what you have to say about it.

> > +/* Writes (if \c outdir is true) or reads \c todo bytes from/into \c buf.
> > + * Tries up to \c EMPTY_TRIES times with a timeout of \c ms ms between each try. */
> > +static int serialport_rw(unsigned char *buf, unsigned int todo, bool outdir, unsigned int ms)
> >  {
> > -#ifdef _WIN32
> > -	DWORD tmp = 0;
> > -#else
> > -	ssize_t tmp = 0;
> > -#endif
> > -	unsigned int empty_writes = 250; /* results in a ca. 125ms timeout */
> > +	const char * const op = outdir ? "write" : "read";
> > +	bool err = false;
> > +	unsigned int empty_tries = EMPTY_TRIES;
> >  
> > -	while (writecnt > 0) {
> > +	while (todo > 0) {
> >  #ifdef _WIN32
> > -		WriteFile(sp_fd, buf, writecnt, &tmp, NULL);
> > +		DWORD cur = 0;
> > +		if (outdir)
> > +			err = !WriteFile(sp_fd, buf, todo, &cur, NULL);
> > +		else
> > +			err = !ReadFile(sp_fd, buf, todo, &cur, NULL);
> 
> Should we check for cur == 0 here? Can that happen?

we do after the ifdefs (marked below) for all platforms.

> >  #else
> > -		tmp = write(sp_fd, buf, writecnt);
> > -#endif
> > -		if (tmp == -1) {
> > -			msg_perr("Serial port write error!\n");
> > +		ssize_t cur = 0;
> > +		if (outdir)
> > +			err = (cur = write(sp_fd, buf, todo)) < 0;
> 
> OK.
> 
> 
> > +		else
> > +			err = (cur = read(sp_fd, buf, todo)) < 0;
> 
> Should be <= 0 instead of < 0 AFAICS. See the POSIX man page of read():
> "When attempting to read from an empty pipe or FIFO: If no process has
> the pipe open for writing, read() shall return 0 to indicate end-of-file."

A character special file is not a pipe.
But even then... we would receive empty reads and timeout quickly. No
harm done AFAICS.

> > +#endif
> > +		if (err) {
> > +			msg_perr("Serial port %s error!\n", op);
> >  			return 1;
> >  		}
> > -		if (!tmp) {
> > -			msg_pdbg2("Empty write\n");
> > -			empty_writes--;
> > -			programmer_delay(500);
> > -			if (empty_writes == 0) {
> > -				msg_perr("Serial port is unresponsive!\n");
> > +		if (cur == 0) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > +			msg_pdbg2("Empty %s.\n", op);
> > +			empty_tries--;
> > +			programmer_delay(ms * 1000);
> > +			if (empty_tries == 0) {
> > +				msg_perr("Serial port is unresponsive or disappeared!\n");
> >  				return 1;
> >  			}
> > +			continue;
> >  		}

Patch

diff --git a/serial.c b/serial.c
index 1b394cd..5f74fd1 100644
--- a/serial.c
+++ b/serial.c
@@ -3,6 +3,7 @@ 
  *
  * Copyright (C) 2009 Urja Rannikko <urjaman@gmail.com>
  * Copyright (C) 2009,2010 Carl-Daniel Hailfinger
+ * Copyright (C) 2013 Stefan Tauner
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,6 +29,7 @@ 
 #include <sys/stat.h>
 #include <errno.h>
 #include <inttypes.h>
+#include <stdbool.h>
 #ifdef _WIN32
 #include <conio.h>
 #else
@@ -328,66 +330,59 @@  int serialport_shutdown(void *data)
 	return 0;
 }
 
-int serialport_write(unsigned char *buf, unsigned int writecnt)
+#define EMPTY_TRIES 10
+/* Writes (if \c outdir is true) or reads \c todo bytes from/into \c buf.
+ * Tries up to \c EMPTY_TRIES times with a timeout of \c ms ms between each try. */
+static int serialport_rw(unsigned char *buf, unsigned int todo, bool outdir, unsigned int ms)
 {
-#ifdef _WIN32
-	DWORD tmp = 0;
-#else
-	ssize_t tmp = 0;
-#endif
-	unsigned int empty_writes = 250; /* results in a ca. 125ms timeout */
+	const char * const op = outdir ? "write" : "read";
+	bool err = false;
+	unsigned int empty_tries = EMPTY_TRIES;
 
-	while (writecnt > 0) {
+	while (todo > 0) {
 #ifdef _WIN32
-		WriteFile(sp_fd, buf, writecnt, &tmp, NULL);
+		DWORD cur = 0;
+		if (outdir)
+			err = !WriteFile(sp_fd, buf, todo, &cur, NULL);
+		else
+			err = !ReadFile(sp_fd, buf, todo, &cur, NULL);
 #else
-		tmp = write(sp_fd, buf, writecnt);
-#endif
-		if (tmp == -1) {
-			msg_perr("Serial port write error!\n");
+		ssize_t cur = 0;
+		if (outdir)
+			err = (cur = write(sp_fd, buf, todo)) < 0;
+		else
+			err = (cur = read(sp_fd, buf, todo)) < 0;
+#endif
+		if (err) {
+			msg_perr("Serial port %s error!\n", op);
 			return 1;
 		}
-		if (!tmp) {
-			msg_pdbg2("Empty write\n");
-			empty_writes--;
-			programmer_delay(500);
-			if (empty_writes == 0) {
-				msg_perr("Serial port is unresponsive!\n");
+		if (cur == 0) {
+			msg_pdbg2("Empty %s.\n", op);
+			empty_tries--;
+			programmer_delay(ms * 1000);
+			if (empty_tries == 0) {
+				msg_perr("Serial port is unresponsive or disappeared!\n");
 				return 1;
 			}
+			continue;
 		}
-		writecnt -= tmp;
-		buf += tmp;
+		todo -= cur;
+		buf += cur;
+		empty_tries = EMPTY_TRIES;
 	}
 
 	return 0;
 }
 
-int serialport_read(unsigned char *buf, unsigned int readcnt)
+int serialport_write(unsigned char *buf, unsigned int writecnt)
 {
-#ifdef _WIN32
-	DWORD tmp = 0;
-#else
-	ssize_t tmp = 0;
-#endif
-
-	while (readcnt > 0) {
-#ifdef _WIN32
-		ReadFile(sp_fd, buf, readcnt, &tmp, NULL);
-#else
-		tmp = read(sp_fd, buf, readcnt);
-#endif
-		if (tmp == -1) {
-			msg_perr("Serial port read error!\n");
-			return 1;
-		}
-		if (!tmp)
-			msg_pdbg2("Empty read\n");
-		readcnt -= tmp;
-		buf += tmp;
-	}
+	return serialport_rw(buf, writecnt, true, 1);
+}
 
-	return 0;
+int serialport_read(unsigned char *buf, unsigned int readcnt)
+{
+	return serialport_rw(buf, readcnt, false, 10);
 }
 
 /* Tries up to timeout ms to read readcnt characters and places them into the array starting at c. Returns