blob: beb7e45aab7f9ef3b643d1baeb3b3a3f94db75f4 [file] [log] [blame]
Than McIntosh6ae32412016-03-31 15:23:55 -04001#!/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()