| # Copyright 2016 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import random |
| import time |
| |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.server.cros.faft.firmware_test import FirmwareTest |
| from autotest_lib.server.cros.servo import pd_device |
| |
| class firmware_PDTrySrc(FirmwareTest): |
| """ |
| Servo based USB PD Try.SRC protocol test. |
| |
| When a PD device supports Try.SRC mode and it's enabled, it will attempt |
| to always connect as a SRC device. This test is therefore only applicable |
| if both devices support dualrole and at least one device supports Try.SRC. |
| |
| Pass criteria is that when Try.SRC is enabled the device connects > 95% of |
| the time in SRC mode. When it is disabled, there must be at least 25% |
| variation in connecting as SRC and SNK. |
| """ |
| |
| version = 1 |
| CONNECT_ITERATIONS = 20 |
| PD_DISCONNECT_TIME = 1 |
| PD_STABLE_DELAY = 3 |
| SNK = 0 |
| SRC = 1 |
| TRYSRC_OFF_THRESHOLD = 15.0 |
| TRYSRC_ON_THRESHOLD = 96.0 |
| |
| def _execute_connect_sequence(self, usbpd_dev, pdtester_dev, trysrc): |
| """Execute mulitple connections and track power role |
| |
| This method will disconnect/connect a TypeC PD port and |
| collect the power role statistics of each connection. The time |
| delay for reconnect adds a random delay so that test to increase |
| randomness for dualrole swaps. |
| |
| @param usbpd_dev: PD device object of DUT |
| @param pdtester_dev: PD device object of PDTester |
| @param trysrc: True to enable TrySrc before disconnect/connect, |
| False to disable TrySrc, None to do nothing. |
| |
| @returns list with number of SNK and SRC connections |
| """ |
| stats = [0, 0] |
| random.seed() |
| # Try N disconnect/connects |
| for attempt in xrange(self.CONNECT_ITERATIONS): |
| try: |
| # Disconnect time from 1 to 1.5 seconds |
| disc_time = self.PD_DISCONNECT_TIME + random.random() / 2 |
| logging.info('Disconnect time = %.2f seconds', disc_time) |
| # Set the TrySrc value on DUT |
| if trysrc is not None: |
| usbpd_dev.try_src(trysrc) |
| # Force disconnect/connect and get the connected state |
| state = pdtester_dev.get_connected_state_after_cc_reconnect( |
| disc_time) |
| # Update connection stats according to the returned state |
| if pdtester_dev.is_snk(state): |
| stats[self.SNK] += 1 |
| logging.info('Power Role = SNK') |
| elif pdtester_dev.is_src(state): |
| stats[self.SRC] += 1 |
| logging.info('Power Role = SRC') |
| # Wait a bit before the next iteration, in case any PR_Swap |
| time.sleep(self.PD_STABLE_DELAY) |
| except NotImplementedError: |
| raise error.TestFail('TrySRC disconnect requires PDTester') |
| logging.info('SNK = %d: SRC = %d: Total = %d', |
| stats[0], stats[1], self.CONNECT_ITERATIONS) |
| return stats |
| |
| def initialize(self, host, cmdline_args, flip_cc=False): |
| super(firmware_PDTrySrc, self).initialize(host, cmdline_args) |
| self.setup_pdtester(flip_cc, min_batt_level=10) |
| # Only run in normal mode |
| self.switcher.setup_mode('normal') |
| # Turn off console prints, except for USBPD. |
| self.usbpd.enable_console_channel('usbpd') |
| |
| def cleanup(self): |
| self.usbpd.send_command('chan 0xffffffff') |
| super(firmware_PDTrySrc, self).cleanup() |
| |
| def run_once(self): |
| """Execute Try.SRC PD protocol test |
| |
| 1. Verify that DUT <-> PDTester device pair exists |
| 2. Verify that DUT supports dualrole |
| 3. Verify that DUT supports Try.SRC mode |
| 4. Enable Try.SRC mode, execute disc/connect sequences |
| 5. Disable Try.SRC mode, execute disc/connect sequences |
| 6. Compute DUT SRC/SNK connect ratios for both modes |
| 7. Compare SRC connect ratio to threholds to determine pass/fail |
| """ |
| |
| # Create list of available UART consoles |
| consoles = [self.usbpd, self.pdtester] |
| # Backup the original dualrole settings |
| original_drp = [None, None] |
| port_partner = pd_device.PDPortPartner(consoles) |
| # Identify PDTester <-> DUT PD device pair |
| port_pair = port_partner.identify_pd_devices() |
| if not port_pair: |
| raise error.TestFail('No DUT to PDTester connection found!') |
| |
| # TODO Device pair must have PDTester so that the disconnect/connect |
| # sequence does not affect the SRC/SNK connection. PDTester provides |
| # a 'fakedisconnect' feature which more closely resembles unplugging |
| # and replugging a Type C cable. |
| for side in xrange(len(port_pair)): |
| original_drp[side] = port_pair[side].drp_get() |
| if port_pair[side].is_pdtester: |
| # Identify PDTester and DUT device |
| p_idx = side |
| d_idx = side ^ 1 |
| |
| try: |
| # Both devices must support dualrole mode for this test. |
| for port in port_pair: |
| try: |
| if not port.drp_set('on'): |
| raise error.TestFail('Could not enable DRP') |
| except NotImplementedError: |
| raise error.TestFail('Both devices must support DRP') |
| |
| # Check to see if DUT supports Try.SRC mode |
| try_src_supported = port_pair[d_idx].try_src(True) |
| |
| if not try_src_supported: |
| logging.warn('DUT does not support Try.SRC feature. ' |
| 'Skip running Try.SRC-enabled test case.') |
| else: |
| # Run disconnect/connect sequence with Try.SRC enabled |
| stats_on = self._execute_connect_sequence( |
| usbpd_dev=port_pair[d_idx], |
| pdtester_dev=port_pair[p_idx], |
| trysrc=True) |
| |
| # Run disconnect/connect sequence with Try.SRC disabled |
| stats_off = self._execute_connect_sequence( |
| usbpd_dev=port_pair[d_idx], |
| pdtester_dev=port_pair[p_idx], |
| trysrc=(False if try_src_supported else None)) |
| |
| # Compute SNK/(SNK+SRC) ratio (percentage) for Try.SRC off case |
| total_off = float(stats_off[self.SNK] + stats_off[self.SRC]) |
| trysrc_off = float(stats_off[self.SNK]) / total_off * 100.0 |
| logging.info('SNK ratio with Try.SRC disabled = %.1f%%', trysrc_off) |
| |
| # When Try.SRC is off, ideally the SNK/SRC ratio will be close to |
| # 50%. However, in practice there is a wide range related to the |
| # dualrole swap timers in firmware. |
| if (trysrc_off < self.TRYSRC_OFF_THRESHOLD or |
| trysrc_off > 100 - self.TRYSRC_OFF_THRESHOLD): |
| raise error.TestFail('SRC %% = %.1f: Must be > %.1f & < %.1f' % |
| (trysrc_off, self.TRYSRC_OFF_THRESHOLD, |
| 100 - self.TRYSRC_OFF_THRESHOLD)) |
| |
| if try_src_supported: |
| # Compute SNK/(SNK+SRC) ratio (percentage) for Try.SRC on case |
| total_on = float(stats_on[self.SNK] + stats_on[self.SRC]) |
| trysrc_on = float(stats_on[self.SNK]) / total_on * 100.0 |
| logging.info('SNK ratio with Try.SRC enabled = %.1f%%', |
| trysrc_on) |
| |
| # When Try.SRC is on, the SRC/SNK, the DUT should connect in SRC |
| # mode nearly 100% of the time. |
| if trysrc_on < self.TRYSRC_ON_THRESHOLD: |
| raise error.TestFail('SRC %% = %.1f: Must be > %.1f' % |
| (trysrc_on, self.TRYSRC_ON_THRESHOLD)) |
| finally: |
| # Reenable Try.SRC mode |
| port_pair[d_idx].try_src(True) |
| # Restore the original dualrole settings |
| for side in xrange(len(port_pair)): |
| port_pair[side].drp_set(original_drp[side]) |