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.common_lib import base_utils 14from autotest_lib.client.cros.audio import cras_dbus_utils 15from autotest_lib.client.cros.audio import cras_utils 16 17 18class USBFacadeNativeError(Exception): 19 """Error in USBFacadeNative.""" 20 pass 21 22 23class 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 122class USBDeviceDriversManagerError(Exception): 123 """Error in USBDeviceDriversManager.""" 124 pass 125 126 127class 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 158class 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