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 19Given a serial number, inspects connected USB devices and issues USB 20reset to the one that matches. Python version written by Than 21McIntosh, based on a perl version from Chris Ferris. Intended for use 22on linux. 23 24""" 25 26import fcntl 27import getopt 28import locale 29import os 30import re 31import shlex 32import subprocess 33import sys 34 35# Serial number of device that we want to reset 36flag_serial = None 37 38# Debugging verbosity level (0 -> no output) 39flag_debug = 0 40 41USBDEVFS_RESET = ord("U") << (4*2) | 20 42 43 44def 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 50def increment_verbosity(): 51 """Increment debug trace level by 1.""" 52 global flag_debug 53 flag_debug += 1 54 55 56def 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 70def 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 77def warning(msg): 78 """Issue a warning to stderr.""" 79 sys.stderr.write("warning: " + msg + "\n") 80 81 82def 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 89def 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 108def 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 136def 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 152def 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 170set_default_lang_locale() 171parse_args() 172perform() 173