Script to perform USB reset of Android device.

Given a serial number, applies correct ioctl()
to reset the USB device with that serial number.
Useful to avoid having to unplug/replug after
flashall + wipe of device.

Change-Id: I7402e1e53cabc19c423ef912a5dc7ade8221b08b
diff --git a/scripts/usb-reset-by-serial.py b/scripts/usb-reset-by-serial.py
new file mode 100755
index 0000000..beb7e45
--- /dev/null
+++ b/scripts/usb-reset-by-serial.py
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Reset a USB device (presumbly android phone) by serial number.
+
+Given a serial number, inspects connected USB devices and issues USB
+reset to the one that matches. Python version written by Than
+McIntosh, based on a perl version from Chris Ferris. Intended for use
+on linux.
+
+"""
+
+import fcntl
+import getopt
+import locale
+import os
+import re
+import shlex
+import subprocess
+import sys
+
+# Serial number of device that we want to reset
+flag_serial = None
+
+# Debugging verbosity level (0 -> no output)
+flag_debug = 0
+
+USBDEVFS_RESET = ord("U") << (4*2) | 20
+
+
+def verbose(level, msg):
+  """Print debug trace output of verbosity level is >= value in 'level'."""
+  if level <= flag_debug:
+    sys.stderr.write(msg + "\n")
+
+
+def increment_verbosity():
+  """Increment debug trace level by 1."""
+  global flag_debug
+  flag_debug += 1
+
+
+def issue_ioctl_to_device(device):
+  """Issue USB reset ioctl to device."""
+
+  try:
+    fd = open(device, "wb")
+  except IOError as e:
+    error("unable to open device %s: "
+          "%s" % (device, e.strerror))
+  verbose(1, "issuing USBDEVFS_RESET ioctl() to %s" % device)
+  fcntl.ioctl(fd, USBDEVFS_RESET, 0)
+  fd.close()
+
+
+# perform default locale setup if needed
+def set_default_lang_locale():
+  if "LANG" not in os.environ:
+    warning("no env setting for LANG -- using default values")
+    os.environ["LANG"] = "en_US.UTF-8"
+    os.environ["LANGUAGE"] = "en_US:"
+
+
+def warning(msg):
+  """Issue a warning to stderr."""
+  sys.stderr.write("warning: " + msg + "\n")
+
+
+def error(msg):
+  """Issue an error to stderr, then exit."""
+  sys.stderr.write("error: " + msg + "\n")
+  exit(1)
+
+
+# invoke command, returning array of lines read from it
+def docmdlines(cmd, nf=None):
+  """Run a command via subprocess, returning output as an array of lines."""
+  verbose(2, "+ docmdlines executing: %s" % cmd)
+  args = shlex.split(cmd)
+  mypipe = subprocess.Popen(args, stdout=subprocess.PIPE)
+  encoding = locale.getdefaultlocale()[1]
+  pout, perr = mypipe.communicate()
+  if mypipe.returncode != 0:
+    if perr:
+      decoded_err = perr.decode(encoding)
+      warning(decoded_err)
+    if nf:
+      return None
+    error("command failed (rc=%d): cmd was %s" % (mypipe.returncode, args))
+  decoded = pout.decode(encoding)
+  lines = decoded.strip().split("\n")
+  return lines
+
+
+def perform():
+  """Main driver routine."""
+  lines = docmdlines("usb-devices")
+  dmatch = re.compile(r"^\s*T:\s*Bus\s*=\s*(\d+)\s+.*\s+Dev#=\s*(\d+).*$")
+  smatch = re.compile(r"^\s*S:\s*SerialNumber=(.*)$")
+  device = None
+  found = False
+  for line in lines:
+    m = dmatch.match(line)
+    if m:
+      p1 = int(m.group(1))
+      p2 = int(m.group(2))
+      device = "/dev/bus/usb/%03d/%03d" % (p1, p2)
+      verbose(1, "setting device: %s" % device)
+      continue
+    m = smatch.match(line)
+    if m:
+      ser = m.group(1)
+      if ser == flag_serial:
+        verbose(0, "matched serial %s to device "
+                "%s, invoking reset" % (ser, device))
+        issue_ioctl_to_device(device)
+        found = True
+        break
+  if not found:
+    error("unable to locate device with serial number %s" % flag_serial)
+
+
+def usage(msgarg):
+  """Print usage and exit."""
+  if msgarg:
+    sys.stderr.write("error: %s\n" % msgarg)
+  print """\
+    usage:  %s [options] XXYYZZ
+
+    where XXYYZZ is the serial number of a connected Android device.
+
+    options:
+    -d    increase debug msg verbosity level
+
+    """ % os.path.basename(sys.argv[0])
+  sys.exit(1)
+
+
+def parse_args():
+  """Command line argument parsing."""
+  global flag_serial
+
+  try:
+    optlist, args = getopt.getopt(sys.argv[1:], "d")
+  except getopt.GetoptError as err:
+    # unrecognized option
+    usage(str(err))
+  if not args or len(args) != 1:
+    usage("supply a single device serial number as argument")
+  flag_serial = args[0]
+
+  for opt, _ in optlist:
+    if opt == "-d":
+      increment_verbosity()
+
+
+set_default_lang_locale()
+parse_args()
+perform()