diff options
Diffstat (limited to 'third_party/catapult/devil/devil/utils/lsusb.py')
-rw-r--r-- | third_party/catapult/devil/devil/utils/lsusb.py | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/third_party/catapult/devil/devil/utils/lsusb.py b/third_party/catapult/devil/devil/utils/lsusb.py new file mode 100644 index 0000000000..6cbf2567b9 --- /dev/null +++ b/third_party/catapult/devil/devil/utils/lsusb.py @@ -0,0 +1,174 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import re + +from devil.utils import cmd_helper + +logger = logging.getLogger(__name__) + +_COULDNT_OPEN_ERROR_RE = re.compile(r'Couldn\'t open device.*') +_INDENTATION_RE = re.compile(r'^( *)') +_LSUSB_BUS_DEVICE_RE = re.compile(r'^Bus (\d{3}) Device (\d{3}): (.*)') +_LSUSB_ENTRY_RE = re.compile(r'^ *([^ ]+) +([^ ]+) *([^ ].*)?$') +_LSUSB_GROUP_RE = re.compile(r'^ *([^ ]+.*):$') + + +def _lsusbv_on_device(bus_id, dev_id): + """Calls lsusb -v on device.""" + _, raw_output = cmd_helper.GetCmdStatusAndOutputWithTimeout( + ['lsusb', '-v', '-s', '%s:%s' % (bus_id, dev_id)], timeout=10) + + device = {'bus': bus_id, 'device': dev_id} + depth_stack = [device] + + # This builds a nested dict -- a tree, basically -- that corresponds + # to the lsusb output. It looks first for a line containing + # + # "Bus <bus number> Device <device number>: ..." + # + # and uses that to create the root node. It then parses all remaining + # lines as a tree, with the indentation level determining the + # depth of the new node. + # + # This expects two kinds of lines: + # - "groups", which take the form + # "<Group name>:" + # and typically have children, and + # - "entries", which take the form + # "<entry name> <entry value> <possible entry description>" + # and typically do not have children (but can). + # + # This maintains a stack containing all current ancestor nodes in + # order to add new nodes to the proper place in the tree. + # The stack is added to when a new node is parsed. Nodes are removed + # from the stack when they are either at the same indentation level as + # or a deeper indentation level than the current line. + # + # e.g. the following lsusb output: + # + # Bus 123 Device 456: School bus + # Device Descriptor: + # bDeviceClass 5 Actual School Bus + # Configuration Descriptor: + # bLength 20 Rows + # + # would produce the following dict: + # + # { + # 'bus': 123, + # 'device': 456, + # 'desc': 'School bus', + # 'Device Descriptor': { + # 'bDeviceClass': { + # '_value': '5', + # '_desc': 'Actual School Bus', + # }, + # 'Configuration Descriptor': { + # 'bLength': { + # '_value': '20', + # '_desc': 'Rows', + # }, + # }, + # } + # } + for line in raw_output.splitlines(): + # Ignore blank lines. + if not line: + continue + # Filter out error mesage about opening device. + if _COULDNT_OPEN_ERROR_RE.match(line): + continue + # Find start of device information. + m = _LSUSB_BUS_DEVICE_RE.match(line) + if m: + if m.group(1) != bus_id: + logger.warning( + 'Expected bus_id value: %r, seen %r', bus_id, m.group(1)) + if m.group(2) != dev_id: + logger.warning( + 'Expected dev_id value: %r, seen %r', dev_id, m.group(2)) + device['desc'] = m.group(3) + continue + + # Skip any lines that aren't indented, as they're not part of the + # device descriptor. + indent_match = _INDENTATION_RE.match(line) + if not indent_match: + continue + + # Determine the indentation depth. + depth = 1 + len(indent_match.group(1)) / 2 + if depth > len(depth_stack): + logger.error( + 'lsusb parsing error: unexpected indentation: "%s"', line) + continue + + # Pop everything off the depth stack that isn't a parent of + # this element. + while depth < len(depth_stack): + depth_stack.pop() + + cur = depth_stack[-1] + + m = _LSUSB_GROUP_RE.match(line) + if m: + new_group = {} + cur[m.group(1)] = new_group + depth_stack.append(new_group) + continue + + m = _LSUSB_ENTRY_RE.match(line) + if m: + new_entry = { + '_value': m.group(2), + '_desc': m.group(3), + } + cur[m.group(1)] = new_entry + depth_stack.append(new_entry) + continue + + logger.error('lsusb parsing error: unrecognized line: "%s"', line) + + return device + +def lsusb(): + """Call lsusb and return the parsed output.""" + _, lsusb_list_output = cmd_helper.GetCmdStatusAndOutputWithTimeout( + ['lsusb'], timeout=10) + devices = [] + for line in lsusb_list_output.splitlines(): + m = _LSUSB_BUS_DEVICE_RE.match(line) + if m: + bus_num = m.group(1) + dev_num = m.group(2) + try: + devices.append(_lsusbv_on_device(bus_num, dev_num)) + except cmd_helper.TimeoutError: + # Will be blacklisted if it is in expected device file, but times out. + logger.info('lsusb -v %s:%s timed out.', bus_num, dev_num) + return devices + +def raw_lsusb(): + return cmd_helper.GetCmdOutput(['lsusb']) + +def get_lsusb_serial(device): + try: + return device['Device Descriptor']['iSerial']['_desc'] + except KeyError: + return None + +def _is_android_device(device): + try: + # Hubs are not android devices. + if device['Device Descriptor']['bDeviceClass']['_value'] == '9': + return False + except KeyError: + pass + return get_lsusb_serial(device) is not None + +def get_android_devices(): + android_devices = (d for d in lsusb() if _is_android_device(d)) + return [get_lsusb_serial(d) for d in android_devices] |