Home | History | Annotate | Download | only in multimedia
      1 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """An interface to access the local USB facade."""
      6 
      7 import glob
      8 import logging
      9 import os
     10 import time
     11 
     12 from autotest_lib.client.bin import utils
     13 from autotest_lib.client.common_lib import base_utils
     14 from autotest_lib.client.cros.audio import cras_dbus_utils
     15 from autotest_lib.client.cros.audio import cras_utils
     16 
     17 
     18 class USBFacadeNativeError(Exception):
     19     """Error in USBFacadeNative."""
     20     pass
     21 
     22 
     23 class USBFacadeNative(object):
     24     """Facade to access the USB-related functionality.
     25 
     26     Property:
     27       _drivers_manager: A USBDeviceDriversManager object used to manage the
     28                         status of drivers associated with the USB audio gadget
     29                         on the host side.
     30 
     31     """
     32     _DEFAULT_DEVICE_PRODUCT_NAME = 'Linux USB Audio Gadget'
     33     _TIMEOUT_FINDING_USB_DEVICE_SECS = 10
     34     _TIMEOUT_CRAS_NODES_CHANGE_SECS = 30
     35 
     36     def __init__(self):
     37         """Initializes the USB facade.
     38 
     39         The _drivers_manager is set with a USBDeviceDriversManager, which is
     40         used to control the visibility and availability of a USB device on a
     41         host Cros device.
     42 
     43         """
     44         self._drivers_manager = USBDeviceDriversManager()
     45 
     46 
     47     def _reenumerate_usb_devices(self):
     48         """Resets host controller to re-enumerate usb devices."""
     49         self._drivers_manager.reset_host_controller()
     50 
     51 
     52     def plug(self):
     53         """Sets and plugs the USB device into the host.
     54 
     55         The USB device is initially set to one with the default product name,
     56         which is assumed to be the name of the USB audio gadget on Chameleon.
     57         This method blocks until Cras enumerate USB nodes within a timeout
     58         specified in _wait_for_nodes_changed.
     59 
     60         """
     61         # Only supports controlling one USB device of default name.
     62         device_name = self._DEFAULT_DEVICE_PRODUCT_NAME
     63 
     64         def find_usb_device():
     65             """Find USB device with name device_name.
     66 
     67             @returns: True if succeed to find the device, False otherwise.
     68 
     69             """
     70             try:
     71                 self._drivers_manager.find_usb_device(device_name)
     72                 return True
     73             except USBDeviceDriversManagerError:
     74                 logging.debug('Can not find %s yet' % device_name)
     75                 return False
     76 
     77         if self._drivers_manager.has_found_device(device_name):
     78             if self._drivers_manager.drivers_are_bound():
     79                 return
     80             self._drivers_manager.bind_usb_drivers()
     81             self._wait_for_nodes_changed()
     82         else:
     83             # If driver manager has not found device yet, re-enumerate USB
     84             # devices. The correct USB driver will be binded automatically.
     85             self._reenumerate_usb_devices()
     86             self._wait_for_nodes_changed()
     87             # Wait some time for paths and fields in sysfs to be created.
     88             utils.poll_for_condition(
     89                     condition=find_usb_device,
     90                     desc='Find USB device',
     91                     timeout=self._TIMEOUT_FINDING_USB_DEVICE_SECS)
     92 
     93 
     94     def unplug(self):
     95         """Unplugs the USB device from the host."""
     96         self._drivers_manager.unbind_usb_drivers()
     97 
     98 
     99     def _wait_for_nodes_changed(self):
    100         """Waits for Cras to enumerate USB nodes.
    101 
    102         USB nodes will be plugged, but not necessarily selected.
    103 
    104         """
    105         def find_usb_node():
    106             """Checks if USB input and output nodes are plugged.
    107 
    108             @returns: True if USB input and output nodes are plugged. False
    109                       otherwise.
    110             """
    111             out_nodes, in_nodes = cras_utils.get_plugged_node_types()
    112             logging.info('Cras nodes: output: %s, input: %s',
    113                          out_nodes, in_nodes)
    114             return 'USB' in out_nodes and 'USB' in in_nodes
    115 
    116         utils.poll_for_condition(
    117                 condition=find_usb_node,
    118                 desc='Find USB node',
    119                 timeout=self._TIMEOUT_CRAS_NODES_CHANGE_SECS)
    120 
    121 
    122 class USBDeviceDriversManagerError(Exception):
    123     """Error in USBDeviceDriversManager."""
    124     pass
    125 
    126 
    127 class HostControllerDriver(object):
    128     """Abstract a host controller driver.
    129 
    130     This class stores id and path like:
    131     path: /sys/bus/pci/drivers/echi_hcd
    132     id: 0000:00:1a.0
    133     Then, it can bind/unbind driver by writing
    134     0000:00:1a.0 to /sys/bus/pci/drivers/echi_hcd/bind
    135     and /sys/bus/pci/drivers/echi_hcd/unbind.
    136 
    137     """
    138     def __init__(self, hcd_id, hcd_path):
    139         """Inits an HostControllerDriver object.
    140 
    141         @param hcd_id: The HCD id, e.g. 0000:00:1a.0
    142         @param hcd_path: The path to HCD, e.g. /sys/bus/pci/drivers/echi_hcd.
    143 
    144         """
    145         logging.debug('hcd id: %s, hcd path: %s', hcd_id, hcd_path)
    146         self._hcd_id = hcd_id
    147         self._hcd_path = hcd_path
    148 
    149 
    150     def reset(self):
    151         """Resets HCD by unbinding and binding driver."""
    152         base_utils.open_write_close(
    153             os.path.join(self._hcd_path, 'unbind'), self._hcd_id)
    154         base_utils.open_write_close(
    155             os.path.join(self._hcd_path, 'bind'), self._hcd_id)
    156 
    157 
    158 class USBDeviceDriversManager(object):
    159     """The class to control the USB drivers associated with a USB device.
    160 
    161     By binding/unbinding certain USB driver, we can emulate the plug/unplug
    162     action on that bus. However, this method only applies when the USB driver
    163     has already been binded once.
    164     To solve above problem, we can unbind then bind USB host controller driver
    165     (HCD), then, HCD will re-enumerate all the USB devices. This method has
    166     a side effect that all the USB devices will be disconnected for several
    167     seconds, so we should only do it if needed.
    168     Note that there might be multiple HCDs, e.g. 0000:00:1a.0 for bus1 and
    169     0000:00:1b.0 for bus2.
    170 
    171     Properties:
    172         _device_product_name: The product name given to the USB device.
    173         _device_bus_id: The bus ID of the USB device in the host.
    174         _hcd_ids: The host controller driver IDs.
    175         _hcds: A list of HostControllerDrivers.
    176 
    177     """
    178     # The file to write to bind USB drivers of specified device
    179     _USB_BIND_FILE_PATH = '/sys/bus/usb/drivers/usb/bind'
    180     # The file to write to unbind USB drivers of specified device
    181     _USB_UNBIND_FILE_PATH = '/sys/bus/usb/drivers/usb/unbind'
    182     # The file path that exists when drivers are bound for current device
    183     _USB_BOUND_DRIVERS_FILE_PATH = '/sys/bus/usb/drivers/usb/%s/driver'
    184     # The pattern to glob usb drivers
    185     _USB_DRIVER_GLOB_PATTERN = '/sys/bus/usb/drivers/usb/usb?/'
    186     # The path to search for HCD on PCI or platform bus.
    187     # The HCD id should be filled in the end.
    188     _HCD_GLOB_PATTERNS = [
    189             '/sys/bus/pci/drivers/*/%s',
    190             '/sys/bus/platform/drivers/*/%s']
    191 
    192     # Skips auto HCD for issue crbug.com/537513.
    193     # Skips s5p-echi for issue crbug.com/546651.
    194     # This essentially means we can not control HCD on these boards.
    195     _SKIP_HCD_BLACKLIST = ['daisy', 'peach_pit', 'peach_pi']
    196 
    197     def __init__(self):
    198         """Initializes the manager.
    199 
    200         _device_product_name and _device_bus_id are initially set to None.
    201 
    202         """
    203         self._device_product_name = None
    204         self._device_bus_id = None
    205         self._hcd_ids = None
    206         self._hcds = None
    207         self._find_hcd_ids()
    208         self._create_hcds()
    209 
    210 
    211     def _skip_hcd(self):
    212         """Skips HCD controlling on some boards."""
    213         board = utils.get_board()
    214         if board in self._SKIP_HCD_BLACKLIST:
    215             logging.info('Skip HCD controlling on board %s', board)
    216             return True
    217         return False
    218 
    219 
    220     def _find_hcd_ids(self):
    221         """Finds host controller driver ids for USB.
    222 
    223         We can find the HCD id for USB from driver's realpath.
    224         E.g. On ARM device:
    225         /sys/bus/usb/drivers/usb/usb1 links to
    226         /sys/devices/soc0/70090000.usb/xhci-hcd.0.auto/usb1
    227         => HCD id is xhci-hcd.0.auto
    228 
    229         E.g. On X86 device:
    230         /sys/bus/usb/drivers/usb/usb1 links to
    231         /sys/devices/pci0000:00/0000:00:14.0/usb1
    232         => HCD id is 0000:00:14.0
    233 
    234         There might be multiple HCD ids like 0000:00:1a.0 for usb1,
    235         and 0000:00:1d.0 for usb2.
    236 
    237         @raises: USBDeviceDriversManagerError if HCD id can not be found.
    238 
    239         """
    240         def _get_dir_name(path):
    241             return os.path.basename(os.path.dirname(path))
    242 
    243         if self._skip_hcd():
    244             self._hcd_ids = set()
    245             return
    246 
    247         hcd_ids = set()
    248 
    249         for search_root_path in glob.glob(self._USB_DRIVER_GLOB_PATTERN):
    250             hcd_id = _get_dir_name(os.path.realpath(search_root_path))
    251             hcd_ids.add(hcd_id)
    252 
    253         if not hcd_ids:
    254             raise USBDeviceDriversManagerError('Can not find HCD id')
    255 
    256         self._hcd_ids = hcd_ids
    257         logging.debug('Found HCD ids: %s', self._hcd_ids)
    258 
    259 
    260     def _create_hcds(self):
    261         """Finds HCD paths from HCD id and create HostControllerDrivers.
    262 
    263         HCD is under /sys/bus/pci/drivers/ for x86 boards, and under
    264         /sys/bus/platform/drivers/ for ARM boards.
    265 
    266         For each HCD id, finds HCD by checking HCD id under it, e.g.
    267         /sys/bus/pci/drivers/ehci_hcd has 0000:00:1a.0 under it.
    268         Then, create a HostControllerDriver and store it in self._hcds.
    269 
    270         @raises: USBDeviceDriversManagerError if there are multiple
    271                  HCD path found for a given HCD id.
    272 
    273         @raises: USBDeviceDriversManagerError if no HostControllerDriver is found.
    274 
    275         """
    276         self._hcds = []
    277 
    278         for hcd_id in self._hcd_ids:
    279             for glob_pattern in self._HCD_GLOB_PATTERNS:
    280                 glob_pattern = glob_pattern % hcd_id
    281                 hcd_id_paths = glob.glob(glob_pattern)
    282                 if not hcd_id_paths:
    283                     continue
    284                 if len(hcd_id_paths) > 1:
    285                     raise USBDeviceDriversManagerError(
    286                             'More than 1 HCD id path found: %s' % hcd_id_paths)
    287                 hcd_id_path = hcd_id_paths[0]
    288 
    289                 # Gets /sys/bus/pci/drivers/echi_hcd from
    290                 # /sys/bus/pci/drivers/echi_hcd/0000:00:1a.0
    291                 hcd_path = os.path.dirname(hcd_id_path)
    292                 self._hcds.append(
    293                         HostControllerDriver(hcd_id=hcd_id, hcd_path=hcd_path))
    294 
    295 
    296     def reset_host_controller(self):
    297         """Resets host controller by unbinding then binding HCD.
    298 
    299         @raises: USBDeviceDriversManagerError if there is no HCD to control.
    300 
    301         """
    302         if not self._hcds and not self._skip_hcd():
    303             raise USBDeviceDriversManagerError('HCD is not found yet')
    304         for hcd in self._hcds:
    305             hcd.reset()
    306 
    307 
    308     def _find_usb_device_bus_id(self, product_name):
    309         """Finds the bus ID of the USB device with the given product name.
    310 
    311         @param product_name: The product name of the USB device as it appears
    312                              to the host.
    313 
    314         @returns: The bus ID of the USB device if it is detected by the host
    315                   successfully; or None if there is no such device with the
    316                   given product name.
    317 
    318         """
    319         def product_matched(path):
    320             """Checks if the product field matches expected product name.
    321 
    322             @returns: True if the product name matches, False otherwise.
    323 
    324             """
    325             read_product_name = base_utils.read_one_line(path)
    326             logging.debug('Read product at %s = %s', path, read_product_name)
    327             return read_product_name == product_name
    328 
    329         # Find product field at these possible paths:
    330         # '/sys/bus/usb/drivers/usb/usbX/X-Y/product' => bus id is X-Y.
    331         # '/sys/bus/usb/drivers/usb/usbX/X-Y/X-Y.Z/product' => bus id is X-Y.Z.
    332 
    333         for search_root_path in glob.glob(self._USB_DRIVER_GLOB_PATTERN):
    334             logging.debug('search_root_path: %s', search_root_path)
    335             for root, dirs, _ in os.walk(search_root_path):
    336                 logging.debug('root: %s', root)
    337                 for bus_id in dirs:
    338                     logging.debug('bus_id: %s', bus_id)
    339                     product_path = os.path.join(root, bus_id, 'product')
    340                     logging.debug('product_path: %s', product_path)
    341                     if not os.path.exists(product_path):
    342                         continue
    343                     if not product_matched(product_path):
    344                         continue
    345                     logging.debug(
    346                             'Bus ID of %s found: %s', product_name, bus_id)
    347                     return bus_id
    348 
    349         logging.error('Bus ID of %s not found', product_name)
    350         return None
    351 
    352 
    353     def has_found_device(self, product_name):
    354         """Checks if the device has been found.
    355 
    356         @param product_name: The product name of the USB device as it appears
    357                              to the host.
    358 
    359         @returns: True if device has been found, False otherwise.
    360 
    361         """
    362         return self._device_product_name == product_name
    363 
    364 
    365     def find_usb_device(self, product_name):
    366         """Sets _device_product_name and _device_bus_id if it can be found.
    367 
    368         @param product_name: The product name of the USB device as it appears
    369                              to the host.
    370 
    371         @raises: USBDeviceDriversManagerError if device bus ID cannot be found
    372                  for the device with the given product name.
    373 
    374         """
    375         device_bus_id = self._find_usb_device_bus_id(product_name)
    376         if device_bus_id is None:
    377             error_message = 'Cannot find device with product name: %s'
    378             raise USBDeviceDriversManagerError(error_message % product_name)
    379         else:
    380             self._device_product_name = product_name
    381             self._device_bus_id = device_bus_id
    382 
    383 
    384     def drivers_are_bound(self):
    385         """Checks whether the drivers with the of current device are bound.
    386 
    387         If the drivers are already bound, calling bind_usb_drivers will be
    388         redundant and also result in an error.
    389 
    390         @return: True if the path to the drivers exist, meaning the drivers
    391                  are already bound. False otherwise.
    392 
    393         """
    394         if self._device_bus_id is None:
    395             raise USBDeviceDriversManagerError('USB Bus ID is not set yet.')
    396         driver_path = self._USB_BOUND_DRIVERS_FILE_PATH % self._device_bus_id
    397         return os.path.exists(driver_path)
    398 
    399 
    400     def bind_usb_drivers(self):
    401         """Binds the USB driver(s) of the current device to the host.
    402 
    403         This is applied to all the drivers associated with and listed under
    404         the USB device with the current _device_product_name and _device_bus_id.
    405 
    406         @raises: USBDeviceDriversManagerError if device bus ID for this instance
    407                  has not been set yet.
    408 
    409         """
    410         if self._device_bus_id is None:
    411             raise USBDeviceDriversManagerError('USB Bus ID is not set yet.')
    412         if self.drivers_are_bound():
    413             return
    414         base_utils.open_write_close(self._USB_BIND_FILE_PATH,
    415                 self._device_bus_id)
    416 
    417 
    418     def unbind_usb_drivers(self):
    419         """Unbinds the USB driver(s) of the current device from the host.
    420 
    421         This is applied to all the drivers associated with and listed under
    422         the USB device with the current _device_product_name and _device_bus_id.
    423 
    424         @raises: USBDeviceDriversManagerError if device bus ID for this instance
    425                  has not been set yet.
    426 
    427         """
    428         if self._device_bus_id is None:
    429             raise USBDeviceDriversManagerError('USB Bus ID is not set yet.')
    430         if not self.drivers_are_bound():
    431             return
    432         base_utils.open_write_close(self._USB_UNBIND_FILE_PATH,
    433                                     self._device_bus_id)
    434