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