• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2019 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"""A test to verify the muxable USB port on servo works with a stick."""
6
7import logging
8import time
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.server import test
12
13class servo_USBMuxVerification(test.test):
14    """Test to expose mux to both servo and dut host as well as power off."""
15    version = 1
16
17    # The file names to find the stick's vid, pid, serial.
18    STICK_ID_FILE_NAMES = ('idVendor', 'idProduct', 'serial')
19
20    def _validate_state(self):
21      """Validate the current state of the mux and the visibility of the stick.
22
23      Given the mux direction, and the power state, validates that the stick
24      is showing up on the host it is expected to show up, and not on the
25      host(s) it is not.
26
27      Raises:
28        error.TestFail: if the USB stick is visible when it should not be or if
29        the USB stick is not visible when it should be.
30      """
31      # A small sleep count to ensure everyone is in the right state and the
32      # caches values/sysfs files are all updated.
33      time.sleep(2)
34      host_map = {'dut': self.dut_host,
35                  'servo': self.servo_host}
36      direction = self.servo.get('image_usbkey_direction')
37      pwr = self.servo.get('image_usbkey_pwr')
38      expectation = {}
39      expectation['dut'] = expectation['servo'] = True
40      if pwr == 'off':
41        expectation['dut'] = expectation['servo'] = False
42      else:
43        if direction == 'dut_sees_usbkey':
44          expectation['servo'] = False
45        elif direction == 'servo_sees_usbkey':
46          expectation['dut'] = False
47        else:
48          raise error.TestNA('Unknown servo usbkey direction %s' % direction)
49      for host in expectation:
50        if expectation[host] and not self.is_usb_stick_visible(host_map[host]):
51          raise error.TestFail ('Usb stick not visible on %s side even though '
52                                'it should be.' % host)
53        if not expectation[host] and self.is_usb_stick_visible(host_map[host]):
54          raise error.TestFail ('Usb stick visible on %s side even though it '
55                                'should not be.' % host)
56
57    def is_usb_stick_visible(self, host):
58      """Check whether the stick is visible on |host|.
59
60      On |host| check whether a usb device exists that has the
61      (vid, pid, serial) that was originally read out from the stick.
62
63      Args:
64        host: dut or servo host to inspect
65
66      Returns:
67        True if the stick is visible on |host|, False otherwise
68
69      Raises:
70        error.TestError: if more than one usb device have the vid, pid, serial
71        that was originally identified by the init sequence
72      """
73      usb_dirs = []
74      for value, fname in zip(self.stick_id, self.STICK_ID_FILE_NAMES):
75        fs = host.run('grep -lr %s $(find /sys/bus/usb/devices/*/ -maxdepth 1 '
76                      '-name %s)' % (value, fname),
77                      ignore_status=True).stdout.strip().split()
78        # Remove the file name to have the usb sysfs dir
79        dirnames = ['/'.join(f.split('/')[:-1]) for f in fs]
80        usb_dirs.append(set(dirnames))
81      common_usb_dev = usb_dirs.pop()
82      while usb_dirs:
83        # This will only leave the usb device dirs that share the same
84        # vid, pid, serial. Ideally, that's 1 - the stick, or 0, if the stick
85        # is not visible
86        common_usb_dev = common_usb_dev & usb_dirs.pop()
87      if len(common_usb_dev) > 1:
88        raise error.TestError('More than one usb device detected on host %s '
89                              'with vid:0x%s pid:0x%s serial:%s.'
90                              % ((host.hostname,) + self.stick_id))
91      return len(common_usb_dev) == 1
92
93    def get_usb_stick_id(self):
94      """Helper to retrieve usb stick's vid, pid, and serial.
95
96      Returns:
97        (vid, pid, serial) tuple of the stick
98
99      Raises:
100        error.TestFail: if the usb stick cannot be found or if reading
101        any of the idVendor, idProduct, serial files fails.
102      """
103      # Getting the stick id by pointing it to the servo host.
104      self.servo.set('image_usbkey_direction', 'servo_sees_usbkey')
105      # Get the usb sysfs directory of the stick
106      symbolic_dev_name = self.servo.get('image_usbkey_dev')
107      if not symbolic_dev_name:
108        raise error.TestFail('No usb stick dev file found.')
109      # This will get just the sdx portion of /dev/sdx
110      path_cmd = 'realpath /sys/block/%s' % symbolic_dev_name.split('/')[-1]
111      # This sed command essentially anchors the path on the usb device's
112      # interface's pattern i.e. the bus-port.port...:x.x pattern. Then
113      # it removes anything beyond it and it itself, leaving the usb
114      # device file's sysfs directory that houses the ID files.
115      sed_cmd =  r"sed -nr 's|/[0-9]+\-[1-9.]+\:[0-9.]+/.*$|/|p'"
116      cmd = r'%s | %s' % (path_cmd, sed_cmd)
117      logging.debug('Using cmd: %s over ssh to see the stick\'s usb sysfs '
118                    'device file directory.', cmd)
119      dev_dir = self.servo_host.run(cmd).stdout.strip()
120      # Get vid, pid, serial
121      if not dev_dir:
122        raise error.TestFail('Failed to find the usb stick usb sysfs device '
123                             'directory on the servo host.')
124      id_elems = []
125      for id_file in self.STICK_ID_FILE_NAMES:
126        cmd = 'sudo cat %s%s' % (dev_dir, id_file)
127        try:
128          elem = self.servo_host.run(cmd).stdout.strip()
129          if not elem:
130            raise error.TestFail('Device id file %s not found.' % id_file)
131        except (error.AutoservRunError, error.TestFail) as e:
132          raise error.TestFail('Failed to get %s file for dev %s' % (id_file,
133                                                                     dev_dir))
134        id_elems.append(elem)
135      return tuple(id_elems)
136
137    def initialize(self, host):
138      """Initialize test by extracting usb stick information."""
139      self.dut_host = host
140      self.servo_host = host._servo_host
141      self.servo = host.servo
142      self._original_pwr = self.servo.get('image_usbkey_pwr')
143      self._original_direction = self.servo.get('image_usbkey_direction')
144      self.servo.set('image_usbkey_pwr', 'on')
145      logging.info('Turning the stick to the servo host.')
146      # Stick id is a (vid, pid, serial) tuple for the stick.
147      self.stick_id = self.get_usb_stick_id()
148
149    def run_once(self, host):
150      """Run through the test cases.
151
152      - Facing the servo host
153        - power off
154      - Facing the DUT
155        - power off
156      """
157      self.servo.set('image_usbkey_direction', 'servo_sees_usbkey')
158      # Sleep for 2s to let the device properly enumerate.
159      self._validate_state()
160      logging.info('Turning the power to the stick off.')
161      self.servo.set('image_usbkey_pwr', 'off')
162      self._validate_state()
163      logging.info('Turning the power to the stick on.')
164      self.servo.set('image_usbkey_pwr', 'on')
165      logging.info('Turning the stick to the dut host.')
166      self.servo.set('image_usbkey_direction', 'dut_sees_usbkey')
167      # Sleep for 2s to let the device properly enumerate.
168      self._validate_state()
169      logging.info('Turning the power to the stick off.')
170      self.servo.set('image_usbkey_pwr', 'off')
171      self._validate_state()
172
173    def cleanup(self, host):
174      """Restore usb mux to its original state."""
175      self.servo.set('image_usbkey_pwr', self._original_pwr)
176      self.servo.set('image_usbkey_direction', self._original_direction)
177