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