1 #!/usr/bin/python 2 # 3 # Copyright (C) 2016 The Android Open Source Project 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 """Reset a USB device (presumbly android phone) by serial number. 18 19 Given a serial number, inspects connected USB devices and issues USB 20 reset to the one that matches. Python version written by Than 21 McIntosh, based on a perl version from Chris Ferris. Intended for use 22 on linux. 23 24 """ 25 26 import fcntl 27 import getopt 28 import locale 29 import os 30 import re 31 import shlex 32 import subprocess 33 import sys 34 35 # Serial number of device that we want to reset 36 flag_serial = None 37 38 # Debugging verbosity level (0 -> no output) 39 flag_debug = 0 40 41 USBDEVFS_RESET = ord("U") << (4*2) | 20 42 43 44 def verbose(level, msg): 45 """Print debug trace output of verbosity level is >= value in 'level'.""" 46 if level <= flag_debug: 47 sys.stderr.write(msg + "\n") 48 49 50 def increment_verbosity(): 51 """Increment debug trace level by 1.""" 52 global flag_debug 53 flag_debug += 1 54 55 56 def issue_ioctl_to_device(device): 57 """Issue USB reset ioctl to device.""" 58 59 try: 60 fd = open(device, "wb") 61 except IOError as e: 62 error("unable to open device %s: " 63 "%s" % (device, e.strerror)) 64 verbose(1, "issuing USBDEVFS_RESET ioctl() to %s" % device) 65 fcntl.ioctl(fd, USBDEVFS_RESET, 0) 66 fd.close() 67 68 69 # perform default locale setup if needed 70 def set_default_lang_locale(): 71 if "LANG" not in os.environ: 72 warning("no env setting for LANG -- using default values") 73 os.environ["LANG"] = "en_US.UTF-8" 74 os.environ["LANGUAGE"] = "en_US:" 75 76 77 def warning(msg): 78 """Issue a warning to stderr.""" 79 sys.stderr.write("warning: " + msg + "\n") 80 81 82 def error(msg): 83 """Issue an error to stderr, then exit.""" 84 sys.stderr.write("error: " + msg + "\n") 85 exit(1) 86 87 88 # invoke command, returning array of lines read from it 89 def docmdlines(cmd, nf=None): 90 """Run a command via subprocess, returning output as an array of lines.""" 91 verbose(2, "+ docmdlines executing: %s" % cmd) 92 args = shlex.split(cmd) 93 mypipe = subprocess.Popen(args, stdout=subprocess.PIPE) 94 encoding = locale.getdefaultlocale()[1] 95 pout, perr = mypipe.communicate() 96 if mypipe.returncode != 0: 97 if perr: 98 decoded_err = perr.decode(encoding) 99 warning(decoded_err) 100 if nf: 101 return None 102 error("command failed (rc=%d): cmd was %s" % (mypipe.returncode, args)) 103 decoded = pout.decode(encoding) 104 lines = decoded.strip().split("\n") 105 return lines 106 107 108 def perform(): 109 """Main driver routine.""" 110 lines = docmdlines("usb-devices") 111 dmatch = re.compile(r"^\s*T:\s*Bus\s*=\s*(\d+)\s+.*\s+Dev#=\s*(\d+).*$") 112 smatch = re.compile(r"^\s*S:\s*SerialNumber=(.*)$") 113 device = None 114 found = False 115 for line in lines: 116 m = dmatch.match(line) 117 if m: 118 p1 = int(m.group(1)) 119 p2 = int(m.group(2)) 120 device = "/dev/bus/usb/%03d/%03d" % (p1, p2) 121 verbose(1, "setting device: %s" % device) 122 continue 123 m = smatch.match(line) 124 if m: 125 ser = m.group(1) 126 if ser == flag_serial: 127 verbose(0, "matched serial %s to device " 128 "%s, invoking reset" % (ser, device)) 129 issue_ioctl_to_device(device) 130 found = True 131 break 132 if not found: 133 error("unable to locate device with serial number %s" % flag_serial) 134 135 136 def usage(msgarg): 137 """Print usage and exit.""" 138 if msgarg: 139 sys.stderr.write("error: %s\n" % msgarg) 140 print """\ 141 usage: %s [options] XXYYZZ 142 143 where XXYYZZ is the serial number of a connected Android device. 144 145 options: 146 -d increase debug msg verbosity level 147 148 """ % os.path.basename(sys.argv[0]) 149 sys.exit(1) 150 151 152 def parse_args(): 153 """Command line argument parsing.""" 154 global flag_serial 155 156 try: 157 optlist, args = getopt.getopt(sys.argv[1:], "d") 158 except getopt.GetoptError as err: 159 # unrecognized option 160 usage(str(err)) 161 if not args or len(args) != 1: 162 usage("supply a single device serial number as argument") 163 flag_serial = args[0] 164 165 for opt, _ in optlist: 166 if opt == "-d": 167 increment_verbosity() 168 169 170 set_default_lang_locale() 171 parse_args() 172 perform() 173