• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7import glob
8import logging
9import os
10import time
11
12from autotest_lib.client.bin import utils
13from autotest_lib.client.cros.audio import cras_dbus_utils
14from autotest_lib.client.cros.audio import cras_utils
15
16
17class USBFacadeNativeError(Exception):
18    """Error in USBFacadeNative."""
19    pass
20
21
22class 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
121class USBDeviceDriversManagerError(Exception):
122    """Error in USBDeviceDriversManager."""
123    pass
124
125
126class 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
157class 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