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