From 15478a676a231b30ec019f991e8869af2ea0902f Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Wed, 1 Feb 2017 13:26:33 -0800 Subject: [PATCH 1/5] Add timeout (-T) option to serio and serio-test.sh This will set a timeout for read operations on the serial port. The intention is to prevent serio from hanging in certain situations. Signed-off-by: Tim Bird --- serio | 13 +++++++---- serio-test.sh | 65 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/serio b/serio index 84d80c2..f6c8471 100755 --- a/serio +++ b/serio @@ -317,7 +317,6 @@ class RemoteExecute: # should do this in a thread so we can output while # the data is coming back - # FIXME - use timeout here result = self.readuntil(None, None, self.DELIM) if remote_shell: @@ -335,10 +334,10 @@ class RemoteExecute: class SerialTerminal(RemoteExecute): - def __init__(self, basic, checksum, port, baudrate, io_time): + def __init__(self, basic, checksum, port, baudrate, io_time, timeout): self.basic = basic self.checksum = checksum - self.serial = serial.Serial(port=port, baudrate=baudrate) + self.serial = serial.Serial(port=port, baudrate=baudrate, timeout=timeout) already_open = self.serial.isOpen() if not already_open: self.serial.open() @@ -411,6 +410,7 @@ def main(): checksum = CHECKSUM_NONE destination = None io_time = None + timeout = None minicom = None paranoid = None port = '/dev/ttyUSB0' @@ -419,7 +419,7 @@ def main(): try: opts, args = GetOpt(sys.argv[1:], - 'b:Bc:d:ghMNm:pPs:t:vy:', + 'b:Bc:d:ghMNm:pPs:t:T:vy:', ['baudrate=', 'basic', 'cmd=', @@ -433,6 +433,7 @@ def main(): 'no-remote-shell', 'source=', 'time=', + 'timeout=', 'verbose', 'port=', ] @@ -471,6 +472,8 @@ def main(): source = arg elif opt in ('-t', '--time'): io_time = float(arg) + elif opt in ('-T', '--timeout'): + timeout = float(arg) elif opt in ('-v', '--verbose'): verbose += 1 elif opt in ('-y', '--port'): @@ -499,7 +502,7 @@ def main(): port = minicom.port() baudrate = minicom.baudrate() - sterm = SerialTerminal(basic, checksum, port, baudrate, io_time) + sterm = SerialTerminal(basic, checksum, port, baudrate, io_time, timeout) if action == 'put': try: diff --git a/serio-test.sh b/serio-test.sh index f7c8278..da83e5b 100755 --- a/serio-test.sh +++ b/serio-test.sh @@ -17,14 +17,15 @@ echo " usage: ${script_name} [-v] [--debug] [-h] [-nc] -B, --basic *Use basic (minimal) remote system commands - --debug Print each shell line as it is executed - -F, --numfile Number of random data test files + --debug Print each shell line as it is executed + -F, --numfiles Number of random data test files -h, -help, --help Show help -nc Don't clean up test files -s, --sleep Time to sleep between --get and --put tests [0.0] -t, --time *Delay in _safe_write() + -T, --timeout *Timeout for serial port I/O operations -y, --port *Serial port to use [/dev/ttyUSB0] - --remote-dir Directory on remote system to put files into [.] + -R, --remote-dir Directory on remote system to put files into [.] -v Be verbose * Sets a serio command line option @@ -44,10 +45,11 @@ unset SERIAL_DEV unset PARANOID unset SLEEP unset TIME +unset TIMEOUT do_cleanup=1 help=0 -numfile=1 +numfiles=2 remote_dir="." verbose=0 while [ $# -gt 0 ] ; do @@ -64,9 +66,9 @@ while [ $# -gt 0 ] ; do set -x ;; - -F | --numfile ) + -F | --numfiles ) shift - numfile=$1 + numfiles=$1 shift ;; @@ -98,7 +100,14 @@ while [ $# -gt 0 ] ; do verbose=1 ;; - -T | --remote-dir ) + -T | --timeout ) + shift + TIMEOUT="-T $1" + shift + verbose=1 + ;; + + -R | --remote-dir ) shift remote_dir="$1" shift @@ -148,13 +157,13 @@ fi tmp_dir=$( mktemp --tmpdir -d serio-test.XXXXXXXXXX ) -FILE_LIST="file_1" -for k in $( seq 2 $(( ${numfile} + 1 )) ) ; do +FILE_LIST="" +for k in $(seq ${numfiles}) ; do FILE_LIST="${FILE_LIST} file_${k}" done -SERIO_ARGS="$SERIAL_DEV $PARANOID $BASIC $TIME" +SERIO_ARGS="$SERIAL_DEV $PARANOID $BASIC $TIME $TIMEOUT" # shell builtin 'time' does not recognize -f TIME="/usr/bin/time" @@ -177,7 +186,7 @@ vecho "SERIO = ${SERIO}" echo "Creating files..." # make a super-small, super-safe file for file1 -echo "this is the first test file for serio" >file1 +echo "this is the first test file for serio" >${tmp_dir}/file_short if [ $verbose -gt 0 ] ; then ddout="/dev/stdout" @@ -194,6 +203,9 @@ for f in ${FILE_LIST} ; do i=$(( $i + 1 )) done +FILE_LIST="file_short ${FILE_LIST}" + + ######################### # test put and get # run some put and get tests, timing them @@ -276,18 +288,37 @@ vecho " Execute 'ls -l $remote_dir'" $SERIO $SERIO_ARGS -c "ls -l $remote_dir" vecho " Execute 'echo hello there'" -res1=$($SERIO $SERIO_ARGS -c "echo hello there") -exp1=$'hello there' +res=$($SERIO $SERIO_ARGS -c "echo hello there") +rcode=$? +exp=$'hello there' -echo "expected : [$exp1]" -echo "got result: [$res1]" +echo "expected : [$exp], rcode=[0]" +echo "got result: [$res], rcode=[$rcode]" desc="$test_num - run 'echo hello there' on remote" -if [ "$res1" = "$exp1" ] ; then +if [ "$res" = "$exp" -a "$rcode" = "0" ] ; then + echo "ok $desc" +else + echo "not ok $desc" +fi +test_num=$(( $test_num + 1 )) + +vecho " Execute 'echo foo ; false'" +res=$($SERIO $SERIO_ARGS -c "echo foo ; false") +rcode=$? +exp=$'foo' +expcode=1 + +echo "expected : [$exp], rcode=[$expcode]" +echo "got result: [$res], rcode=[$rcode]" + +desc="$test_num - run 'echo foo ; false' on remote" +if [ "$res" = "$exp" -a "$rcode" = "$expcode" ] ; then echo "ok $desc" else echo "not ok $desc" fi +test_num=$(( $test_num + 1 )) ######################### # test cleanup @@ -295,7 +326,7 @@ fi function cleanup { # remove test files - $SERIO $SERIAL_DEV -c "rm ${remote_dir}/file_[1-9]*" + $SERIO $SERIAL_DEV -c "rm ${remote_dir}/file_[1-9]* ${remote_dir}/file_short" rm -r ${tmp_dir} } From c8f7779dcdeb0f664d630d7075c4c7adc7920a78 Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Wed, 1 Feb 2017 14:04:08 -0800 Subject: [PATCH 2/5] Fix bug in readuntil with count argument The data needs to be accumulated in this routine, when used with the 'count' option. Without this fix the routine only returns one byte of data (the last byte read). Signed-off-by: Tim Bird --- serio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serio b/serio index f6c8471..8302956 100755 --- a/serio +++ b/serio @@ -112,7 +112,7 @@ class RemoteExecute: if count is not None: for i in range(0, count): - data = self.fp.read(1) + data += self.fp.read(1) self.progress(len(data), count) elif lines is not None: From f1e92ef65cd56f03cff5e13ee252acd01517111e Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Wed, 1 Feb 2017 14:07:35 -0800 Subject: [PATCH 3/5] Be consistent in using self.fp as the serial port file descriptor Don't use self.serial (even though it's available), but use self.fp, as that's consistent with the rest of the code. --- serio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serio b/serio index 8302956..0cd7123 100755 --- a/serio +++ b/serio @@ -124,7 +124,7 @@ class RemoteExecute: elif delim is not None: while not data.endswith(delim): - data += self.serial.read(1) + data += self.fp.read(1) self.progress((len(data)-len(delim)), (len(data)-len(delim))) data = data[0:-len(delim)] From f57c90a5f7a3d352c6886140d6fb2668269c1f2e Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Wed, 1 Feb 2017 14:11:39 -0800 Subject: [PATCH 4/5] Read prompt after each transmitted line, in putfile The prevents data overruns when doing putfile on my setup, with io_time=0. This is faster (and likely more robust) than using io_time as a delay, but still prevents overruns on the target, when doing a putfile. Performance to put a 10000-byte file is: -P (io_time 0.1) => 41 seconds -t 0.05 (io_time 0.05) => 21 seconds waiting for prompt, but using io_time=0 => 7 seconds With io_time=0 and no wait for the prompt, serio hangs with the target stuck at the secondary shell prompt on my setup (beaglebone black, connected via Sony debug board). The target misses some of the data, and since it is in the middle of an echo of quoted material, the second quote is missed. Signed-off-by: Tim Bird --- serio | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/serio b/serio index 0cd7123..cc93dd5 100755 --- a/serio +++ b/serio @@ -134,6 +134,16 @@ class RemoteExecute: print return data + # read data from the serial port, for a fixed amount of time + # intended for short reads (e.g. to drain a small amount of + # data from the port, or to get the prompt) + def readuntil_time(self, duration): + saved_timeout = self.fp.timeout + self.fp.timeout = duration + data = self.fp.read(10000) + self.fp.timeout = saved_timeout + return data + def prime_before_first_execute(self): # required before first call of execute(), after this # program begins @@ -153,6 +163,11 @@ class RemoteExecute: if not cmd_echo.endswith(cmd + "\r\n"): cmd_echo = self.fp.readline() + def get_prompt(self): + self.fp.write("# gp\n") + data = self.readuntil_time(1) + return data.strip() + def execute(self, cmd): # This program MUST send a newline to the remote system once # before calling a sequence of execute() so that the data @@ -256,6 +271,10 @@ class RemoteExecute: # a newline, so this does not eliminate all traffic from the # remote system, it just reduces it. self._safe_write('stty -echo\n') + # drain the response from the serial buffer + junk = self.readuntil_time(1) + + prompt = self.get_prompt() # Create/zero the file self._safe_write('echo -ne > %s\n' % filename) @@ -285,6 +304,9 @@ class RemoteExecute: # Show upload status self.progress(i, data_size) + # wait for shell prompt before sending next line + recv_data = self.readuntil(delim=prompt) + if self.showprogress: print From 92d4fb2d5e71287afb2bfe5dc8124af4754b4705 Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Wed, 1 Feb 2017 14:24:57 -0800 Subject: [PATCH 5/5] Add time_print routine for debugging serio operations This prints a time-stamped line for most of the interactions with the target. This is handy to see what gets sent to the target, and when it is sent. It's very verbose, and can only be turned on by changing the definition of "debug" in the source. To turn it on, change the line to "debug = 1". Signed-off-by: Tim Bird --- serio | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/serio b/serio index cc93dd5..db8bc6b 100755 --- a/serio +++ b/serio @@ -30,6 +30,7 @@ import sys, os.path, math, time import hashlib import StringIO import uu +import datetime from getopt import GetoptError, getopt as GetOpt # verbose @@ -37,6 +38,12 @@ from getopt import GetoptError, getopt as GetOpt # 2 progress verbose = 0 +debug = 0 +def time_print(msg): + if debug: + time_str = datetime.datetime.now().strftime("%H:%M:%S.%f") + print "[%s] %s" % (time_str, msg) + # zero always will mean no checksum CHECKSUM_NONE = 0 CHECKSUM_MD5SUM = 1 @@ -110,25 +117,22 @@ class RemoteExecute: data = "" if count is not None: - for i in range(0, count): data += self.fp.read(1) self.progress(len(data), count) elif lines is not None: - for i in range(0, lines): data += self.fp.readline() self.progress(len(data), len(data)) elif delim is not None: - while not data.endswith(delim): data += self.fp.read(1) self.progress((len(data)-len(delim)), (len(data)-len(delim))) data = data[0:-len(delim)] - + self.progress(len(data), len(data)) if self.showprogress > 0: print @@ -148,13 +152,15 @@ class RemoteExecute: # required before first call of execute(), after this # program begins - cmd = "#" + cmd = "# pbfe" + time_print("pbfe: write:" + cmd + "\n") self.fp.write(cmd + "\n") # remote system shell echos command, slurp it up cmd_echo = self.fp.readline() + time_print("pbfe: cmd_echo=[%s]" % cmd_echo) - # The probably will not be a prompt, becuase the remote + # The probably will not be a prompt, because the remote # shell probably sent it before serio started execution. # Check for the command with carriage return, newline. # If the echoed command does not include this then @@ -162,6 +168,7 @@ class RemoteExecute: # if not cmd_echo.endswith(cmd + "\r\n"): cmd_echo = self.fp.readline() + time_print("pbfe: cmd_echo=[%s]" % cmd_echo) def get_prompt(self): self.fp.write("# gp\n") @@ -201,6 +208,7 @@ class RemoteExecute: if self.basic: self.execute("cat '%s' && echo '%s'" % (filename, self.DELIM)) + data = self.readuntil(delim=self.DELIM) # Catting data over a terminal turns all \n's to \r\n's @@ -275,6 +283,7 @@ class RemoteExecute: junk = self.readuntil_time(1) prompt = self.get_prompt() + time_print("putfile: prompt='%s'" % prompt) # Create/zero the file self._safe_write('echo -ne > %s\n' % filename) @@ -306,6 +315,7 @@ class RemoteExecute: # wait for shell prompt before sending next line recv_data = self.readuntil(delim=prompt) + time_print("putfile: received data=[%s]" % recv_data) if self.showprogress: print @@ -316,6 +326,7 @@ class RemoteExecute: return i def _safe_write(self, data): + time_print("_safe_write:"+data) self.fp.write(data) if data.endswith('\n'): # give the remote system time for disk/flash I/O