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