xref: /aosp_15_r20/external/autotest/client/common_lib/cros/power_cycle_usb_util.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright 2017 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Power cycle a usb port on DUT(device under test)."""
7
8from __future__ import absolute_import
9from __future__ import division
10from __future__ import print_function
11
12import logging
13import os
14from six.moves import zip
15import time
16
17from autotest_lib.client.common_lib.cros.cfm.usb import usb_port_manager
18
19
20TOKEN_NEW_BUS = '/:  '
21TOKEN_ROOT_DEVICE = '\n    |__ '
22
23# On board guado, there are three gpios that control usb port power:
24# Front left usb port:  218, port number: 2
25# Front right usb port: 219, port number: 3
26# Rear dual usb ports:  209, port number: 5,6
27#
28# On board fizz, there are 5 usb ports and usb port power is controlled by EC
29# with user space command: ectool goioset USBx_ENABLE 0/1 (x from 1 to 5).
30PORT_NUM_DICT = {
31    'guado': {
32        # USB 2.0.
33        'bus1': {
34            2: 'front_left',
35            3: 'front_right',
36            5: 'back_dual',
37            6: 'back_dual'
38        },
39        # USB 3.0.
40        'bus2': {
41            1: 'front_left',
42            2: 'front_right',
43            3: 'back_dual',
44            4: 'back_dual'
45        }
46    },
47    'fizz': {
48        # USB 2.0.
49        'bus1': {
50            2: 'rear_right',
51            3: 'front_right',
52            4: 'front_left',
53            5: 'rear_left',
54            6: 'rear_middle'
55        },
56        # USB 3.0.
57        'bus2': {
58            2: 'rear_right',
59            3: 'front_right',
60            4: 'front_left',
61            5: 'rear_left',
62            6: 'rear_middle'
63        }
64    }
65}
66PORT_GPIO_DICT = {
67    'guado': {
68        'bus1': {
69            'front_left': 218,
70            'front_right': 219,
71            'back_dual': 209
72        },
73        'bus2': {
74            'front_left': 218,
75            'front_right': 219,
76            'back_dual': 209
77        }
78    },
79    'fizz': {
80        'bus1': {
81            'rear_left': 1,
82            'rear_middle': 2,
83            'rear_right': 3,
84            'front_right': 4,
85            'front_left': 5
86        },
87        'bus2': {
88            'rear_left': 1,
89            'rear_middle': 2,
90            'rear_right': 3,
91            'front_right': 4,
92            'front_left': 5
93        }
94    }
95}
96
97
98def power_cycle_usb_vidpid(dut, board, vid, pid, pause=1):
99    """
100    Power cycle a usb port on DUT via peripharel's VID and PID.
101
102    When only the VID and PID of the peripharel is known, a search is needed
103    to decide which port it connects to by its VID and PID and look up the gpio
104    index according to the board and port number in the dictionary. Then the
105    USB port is power cycled using the gpio number.
106
107    @param dut: The handle of the device under test.
108    @param board: Board name ('guado', etc.)
109    @param vid: Vendor ID of the peripharel device.
110    @param pid: Product ID of the peripharel device.
111    @param pause: Time interval between power off and power on, unit is second.
112
113    @raise KeyError if the target device wasn't found by given VID and PID.
114
115    """
116    bus_idx, port_idx = get_port_number_from_vidpid(dut, vid, pid)
117    if port_idx is None:
118        raise KeyError('Couldn\'t find target device, {}:{}.'.format(vid, pid))
119    logging.info('found device bus {} port {}'.format(bus_idx, port_idx))
120
121    usb_manager = usb_port_manager.UsbPortManager(dut)
122    port_id = [usb_port_manager.PortId(bus=bus_idx, port_number=port_idx)]
123    usb_manager.set_port_power(port_id, 0)
124    time.sleep(pause)
125    usb_manager.set_port_power(port_id, 1)
126
127
128def get_port_number_from_vidpid(dut, vid, pid):
129    """
130    Get bus number and port number a device is connected to on DUT.
131
132    Get the bus number and port number of the usb port the target perpipharel
133    device is connected to.
134
135    @param dut: The handle of the device under test.
136    @param vid: Vendor ID of the peripharel device.
137    @param pid: Product ID of the peripharel device.
138
139    @returns the target bus number and port number, if device not found, returns
140          (None, None).
141
142    """
143    cmd = 'lsusb -d {}:{}'.format(vid, pid)
144    lsusb_output = dut.run(cmd, ignore_status=True).stdout
145    logging.info('lsusb output {}'.format(lsusb_output))
146    target_bus_idx, target_dev_idx = get_bus_dev_id(lsusb_output, vid, pid)
147    if target_bus_idx is None:
148        return None, None
149    cmd = 'lsusb -t'
150    lsusb_output = dut.run(cmd, ignore_status=True).stdout
151    target_port_number = get_port_number(
152        lsusb_output, target_bus_idx, target_dev_idx)
153    return target_bus_idx, target_port_number
154
155
156def get_bus_dev_id(lsusb_output, vid, pid):
157    """
158    Get bus number and device index a device is connected to on DUT.
159
160    Get the bus number and port number of the usb port the target perpipharel
161    device is connected to based on the output of command 'lsusb -d VID:PID'.
162
163    @param lsusb_output: output of command 'lsusb -d VID:PID' running on DUT.
164    @param vid: Vendor ID of the peripharel device.
165    @param pid: Product ID of the peripharel device.
166
167    @returns the target bus number and device index, if device not found,
168          returns (None, None).
169
170    """
171    if lsusb_output == '':
172        return None, None
173    lsusb_device_info = lsusb_output.strip().split('\n')
174    if len(lsusb_device_info) > 1:
175        logging.info('find more than one device with VID:PID: %s:%s', vid, pid)
176        return None, None
177    # An example of the info line is 'Bus 001 Device 006:  ID 266e:0110 ...'
178    fields = lsusb_device_info[0].split(' ')
179    assert len(fields) >= 6, 'Wrong info format: {}'.format(lsusb_device_info)
180    target_bus_idx = int(fields[1])
181    target_device_idx = int(fields[3][:-1])
182    logging.info('found target device %s:%s, bus: %d, dev: %d',
183                 vid, pid, target_bus_idx, target_device_idx)
184    return target_bus_idx, target_device_idx
185
186def get_port_number(lsusb_tree_output, bus, dev):
187    """
188    Get port number that certain device is connected to on DUT.
189
190    Get the port number of the usb port that the target peripharel device is
191    connected to based on the output of command 'lsusb -t', its bus number and
192    device index.
193    An example of lsusb_tree_output could be:
194    /:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
195        |__ Port 2: Dev 2, If 0, Class=Hub, Driver=hub/4p, 5000M
196    /:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/11p, 480M
197        |__ Port 2: Dev 52, If 0, Class=Hub, Driver=hub/4p, 480M
198            |__ Port 1: Dev 55, If 0, Class=Human Interface Device,
199                        Driver=usbhid, 12M
200            |__ Port 3: Dev 54, If 0, Class=Vendor Specific Class,
201                        Driver=udl, 480M
202        |__ Port 3: Dev 3, If 0, Class=Hub, Driver=hub/4p, 480M
203        |__ Port 4: Dev 4, If 0, Class=Wireless, Driver=btusb, 12M
204        |__ Port 4: Dev 4, If 1, Class=Wireless, Driver=btusb, 12M
205
206    @param lsusb_tree_output: The output of command 'lsusb -t' on DUT.
207    @param bus: The bus number the peripharel device is connected to.
208    @param dev: The device index of the peripharel device on DUT.
209
210    @returns the target port number, if device not found, returns None.
211
212    """
213    lsusb_device_buses = lsusb_tree_output.strip().split(TOKEN_NEW_BUS)
214    target_bus_token = 'Bus {:02d}.'.format(bus)
215    for bus_info in lsusb_device_buses:
216        if bus_info.find(target_bus_token) != 0:
217            continue
218        target_dev_token = 'Dev {}'.format(dev)
219        device_info = bus_info.strip(target_bus_token).split(TOKEN_ROOT_DEVICE)
220        for device in device_info:
221            if target_dev_token not in device:
222                continue
223            target_port_number = int(device.split(':')[0].split(' ')[1])
224            return target_port_number
225    return None
226
227
228def get_all_port_number_from_vidpid(dut, vid, pid):
229    """
230    Get the list of bus number and port number devices are connected to DUT.
231
232    Get the the list of bus number and port number of the usb ports the target
233           perpipharel devices are connected to.
234
235    @param dut: The handle of the device under test.
236    @param vid: Vendor ID of the peripharel device.
237    @param pid: Product ID of the peripharel device.
238
239    @returns the list of target bus number and port number, if device not found,
240            returns empty list.
241
242    """
243    port_number = []
244    cmd = 'lsusb -d {}:{}'.format(vid, pid)
245    lsusb_output = dut.run(cmd, ignore_status=True).stdout
246    (target_bus_idx, target_dev_idx) = get_all_bus_dev_id(lsusb_output, vid, pid)
247    if target_bus_idx is None:
248        return None, None
249    cmd = 'lsusb -t'
250    lsusb_output = dut.run(cmd, ignore_status=True).stdout
251    for bus, dev in zip(target_bus_idx, target_dev_idx):
252        port_number.append(get_port_number(
253            lsusb_output, bus, dev))
254    return (target_bus_idx, port_number)
255
256
257def get_all_bus_dev_id(lsusb_output, vid, pid):
258    """
259    Get the list of bus number and device index devices are connected to DUT.
260
261    Get the bus number and port number of the usb ports the target perpipharel
262            devices are connected to based on the output of command 'lsusb -d VID:PID'.
263
264    @param lsusb_output: output of command 'lsusb -d VID:PID' running on DUT.
265    @param vid: Vendor ID of the peripharel device.
266    @param pid: Product ID of the peripharel device.
267
268    @returns the list of target bus number and device index, if device not found,
269           returns empty list.
270
271    """
272    bus_idx = []
273    device_idx =[]
274    if lsusb_output == '':
275        return None, None
276    lsusb_device_info = lsusb_output.strip().split('\n')
277    for lsusb_device in lsusb_device_info:
278        fields = lsusb_device.split(' ')
279        assert len(fields) >= 6, 'Wrong info format: {}'.format(lsusb_device_info)
280        target_bus_idx = int(fields[1])
281        target_device_idx = int(fields[3][:-1])
282        bus_idx.append(target_bus_idx)
283        device_idx.append( target_device_idx)
284    return (bus_idx, device_idx)
285
286
287def get_target_all_gpio(dut, board, vid, pid):
288    """
289    Get GPIO for all devices with vid, pid connected to on DUT.
290
291    Get gpio of usb port the target perpipharel  devices are
292    connected to based on the output of command 'lsusb -d VID:PID'.
293
294    @param dut: The handle of the device under test.
295    @param board: Board name ('guado', etc.)
296    @param vid: Vendor ID of the peripharel device.
297    @param pid: Product ID of the peripharel device.
298
299    @returns the list of gpio, if no device found return []
300
301    """
302    gpio_list = []
303    (bus_idx, port_idx) = get_all_port_number_from_vidpid(dut, vid, pid)
304    if port_idx is None:
305        raise KeyError('Couldn\'t find target device, {}:{}.'.format(vid, pid))
306
307    for bus, port in zip(bus_idx, port_idx):
308        logging.info('found device bus {} port {}'.format(bus, port))
309        token_bus = 'bus{}'.format(bus)
310        target_gpio_pos = (PORT_NUM_DICT.get(board, {})
311                       .get(token_bus, {}).get(port, ''))
312        target_gpio = (PORT_GPIO_DICT.get(board, {})
313                   .get(token_bus, {}).get(target_gpio_pos, None))
314        logging.info('Target gpio num {}'.format(target_gpio))
315        gpio_list.append(target_gpio)
316    return gpio_list
317