| # 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 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_PDPowerSwap(FirmwareTest): |
| """ |
| Servo based USB PD power role swap test. |
| |
| Pass critera is all power role swaps are successful if the DUT |
| is dualrole capable. If not dualrole, then pass criteria is |
| the DUT sending a reject message in response to swap request. |
| |
| """ |
| |
| version = 1 |
| PD_ROLE_DELAY = 1 |
| PD_CONNECT_DELAY = 10 |
| # Should be an even number; back to the original state at the end |
| POWER_SWAP_ITERATIONS = 10 |
| |
| def _set_pdtester_power_role_to_src(self): |
| """Force PDTester to act as a source |
| |
| @returns True if PDTester power role is source, false otherwise |
| """ |
| PDTESTER_SRC_VOLTAGE = 20 |
| self.pdtester.charge(PDTESTER_SRC_VOLTAGE) |
| # Wait for change to take place |
| time.sleep(self.PD_CONNECT_DELAY) |
| # Current PDTester power role should be source |
| return self.pdtester_port.is_src() |
| |
| def _send_power_swap_get_reply(self, port): |
| """Send power swap request, get PD control msg reply |
| |
| The PD console debug mode is enabled prior to sending |
| a pd power role swap request message. This allows the |
| control message reply to be extracted. The debug mode |
| is disabled prior to exiting. |
| |
| @param port: pd device object |
| |
| @returns: PD control header message |
| """ |
| # Enable PD console debug mode to show control messages |
| port.utils.enable_pd_console_debug() |
| cmd = 'pd %d swap power' % port.port |
| m = port.utils.send_pd_command_get_output(cmd, ['RECV\s([\w]+)']) |
| ctrl_msg = int(m[0][1], 16) & port.utils.PD_CONTROL_MSG_MASK |
| port.utils.disable_pd_console_debug() |
| return ctrl_msg |
| |
| def _attempt_power_swap(self, direction): |
| """Perform a power role swap request |
| |
| Initiate a power role swap request on either the DUT or |
| PDTester depending on the direction parameter. The power |
| role swap is then verified to have taken place. |
| |
| @param direction: rx or tx from the DUT perspective |
| |
| @returns True if power swap is successful |
| """ |
| # Get DUT current power role |
| dut_pr = self.dut_port.get_pd_state() |
| if direction == 'rx': |
| port = self.pdtester_port |
| else: |
| port = self.dut_port |
| # Send power swap request |
| self._send_power_swap_get_reply(port) |
| time.sleep(self.PD_CONNECT_DELAY) |
| # Get PDTester power role |
| pdtester_pr = self.pdtester_port.get_pd_state() |
| if self.dut_port.is_src(dut_pr) and self.pdtester_port.is_src(pdtester_pr): |
| return True |
| elif self.dut_port.is_snk(dut_pr) and self.pdtester_port.is_snk(pdtester_pr): |
| return True |
| else: |
| return False |
| |
| def _test_power_swap_reject(self): |
| """Verify that a power swap request is rejected |
| |
| This tests the case where the DUT isn't in dualrole mode. |
| A power swap request is sent by PDTester, and then |
| the control message checked to ensure the request was rejected. |
| In addition, the connection state is verified to not have |
| changed. |
| """ |
| # Get current DUT power role |
| dut_power_role = self.dut_port.get_pd_state() |
| # Send swap command from PDTester and get reply |
| ctrl_msg = self._send_power_swap_get_reply(self.pdtester_port) |
| if ctrl_msg != self.dut_port.utils.PD_CONTROL_MSG_DICT['Reject']: |
| raise error.TestFail('Power Swap Req not rejected, returned %r' % |
| ctrl_msg) |
| # Get DUT current state |
| pd_state = self.dut_port.get_pd_state() |
| if pd_state != dut_power_role: |
| raise error.TestFail('PD not connected! pd_state = %r' % |
| pd_state) |
| |
| def _test_one_way_power_swap_in_suspend(self): |
| """Verify SRC-to-SNK power role swap on DUT PD port in S0ix/S3 |
| |
| Set the DUT power role to source and then suspend the DUT. |
| Verify SRC-to-SNK power role request from the PD tester works, |
| while SNK-to-SRC power role request fails. Note that this is |
| Chrome OS policy decision, not part of the PD spec. |
| |
| When DUT doesn't provide power in suspend, set DUT power role |
| to sink, supend DUT and check if SNK-to-SRC power role request fails. |
| |
| """ |
| # Ensure DUT PD port is sourcing power. |
| time.sleep(self.PD_CONNECT_DELAY) |
| if self.faft_config.dut_can_source_power_in_suspend: |
| if not self.dut_port.is_src(): |
| if self._attempt_power_swap('rx') == False or not self.dut_port.is_src(): |
| raise error.TestFail('Fail to set DUT power role to source.') |
| |
| self.set_ap_off_power_mode('suspend') |
| |
| new_state = self.dut_port.get_pd_state() |
| if not self.dut_port.is_src(new_state): |
| raise error.TestFail('DUT power role changed to %s ' |
| 'during S0-to-S3 transition!' % new_state) |
| |
| # If the DUT PD port supports DRP in S0, it should supports SRC-to-SNK |
| # power role swap in suspend mode. The other way around (SNK-to-SRC) in |
| # suspend mode should fail. |
| logging.info('Request a SRC-to-SNK power role swap to DUT PD port ' |
| 'in suspend mode. Expect to succeed.') |
| if self._attempt_power_swap('rx') == False: |
| raise error.TestFail('SRC-to-SNK power role swap failed.') |
| |
| # If we define AC insertion as a wake source for this board, the |
| # SRC-to-SNK power role swap would wake up the DUT. In this case, |
| # we need to suspend the DUT again to proceed the test. |
| if self.wait_power_state(self.POWER_STATE_S0, |
| self.DEFAULT_PWR_RETRIES): |
| self.set_ap_off_power_mode('suspend') |
| else: |
| # If DUT can't source power in suspend we are going to test only if |
| # SNK-to-SRC transition fails, so make sure that port acts as sink |
| logging.info('DUT is not sourcing power in suspend - ' |
| 'skip verification if SRC-to-SNK power swap works') |
| if not self.dut_port.is_snk(): |
| if self._attempt_power_swap('rx') == False or not self.dut_port.is_snk(): |
| raise error.TestFail('Fail to set DUT power role to sink.') |
| |
| self.set_ap_off_power_mode('suspend') |
| |
| new_state = self.dut_port.get_pd_state() |
| if not self.dut_port.is_snk(new_state): |
| raise error.TestFail('DUT power role changed to %s ' |
| 'during S0-to-S3 transition!' % new_state) |
| |
| logging.info('Request a SNK-to-SRC power role swap to DUT PD port ' |
| 'in suspend mode. Expect to fail.') |
| self._test_power_swap_reject() |
| |
| self.restore_ap_on_power_mode() |
| |
| def initialize(self, host, cmdline_args, flip_cc=False, dts_mode=False, |
| init_power_mode=None): |
| super(firmware_PDPowerSwap, self).initialize(host, cmdline_args) |
| self.setup_pdtester(flip_cc, dts_mode, min_batt_level=10) |
| # Only run in normal mode |
| self.switcher.setup_mode('normal') |
| if init_power_mode: |
| # Set the DUT to suspend (S3) or shutdown (G3/S5) mode |
| self.set_ap_off_power_mode(init_power_mode) |
| # Turn off console prints, except for USBPD. |
| self.usbpd.enable_console_channel('usbpd') |
| |
| def cleanup(self): |
| if hasattr(self, 'pd_port'): |
| # Restore DUT dual role operation |
| self.dut_port.drp_set('on') |
| if hasattr(self, 'pdtester_port'): |
| # Set connection back to default arrangement |
| self.pdtester_port.drp_set('off') |
| |
| if hasattr(self, 'pd_port') and hasattr(self, 'pdtester_port'): |
| # Fake-disconnect to restore the original power role |
| self.pdtester_port.utils.send_pd_command('fakedisconnect 100 1000') |
| |
| self.usbpd.send_command('chan 0xffffffff') |
| self.restore_ap_on_power_mode() |
| super(firmware_PDPowerSwap, self).cleanup() |
| |
| def run_once(self): |
| """Execute Power Role swap test. |
| |
| 1. Verify that pd console is accessible |
| 2. Verify that DUT has a valid PD contract and connected to PDTester |
| 3. Determine if DUT is in dualrole mode |
| 4. If dualrole mode is not supported, verify DUT rejects power swap |
| request. If dualrole mode is supported, test power swap (tx/rx) in |
| S0 and one-way power swap (SRC-to-SNK on DUT PD port) in S3/S0ix. |
| Then force DUT to be sink or source only and verify rejection of |
| power swap request. |
| |
| """ |
| # Create list of available UART consoles |
| consoles = [self.usbpd, self.pdtester] |
| port_partner = pd_device.PDPortPartner(consoles) |
| |
| # Identify a valid test port pair |
| port_pair = port_partner.identify_pd_devices() |
| if not port_pair: |
| raise error.TestFail('No PD connection found!') |
| |
| for port in port_pair: |
| if port.is_pdtester: |
| self.pdtester_port = port |
| else: |
| self.dut_port = port |
| |
| dut_connect_state = self.dut_port.get_pd_state() |
| logging.info('Initial DUT connect state = %s', dut_connect_state) |
| |
| # Get DUT dualrole status |
| if self.dut_port.is_drp() == False: |
| # DUT does not support dualrole mode, power swap |
| # requests to the DUT should be rejected. |
| logging.info('Power Swap support not advertised by DUT') |
| self._test_power_swap_reject() |
| logging.info('Power Swap request rejected by DUT as expected') |
| else: |
| if self.get_power_state() != 'S0': |
| raise error.TestFail('If the DUT is not is S0, the DUT port ' |
| 'shouldn\'t claim dualrole is enabled.') |
| # Start with PDTester as source |
| if self._set_pdtester_power_role_to_src() == False: |
| raise error.TestFail('PDTester not set to source') |
| # DUT is dualrole in dual role mode. Test power role swap |
| # operation intiated both by the DUT and PDTester. |
| success = 0 |
| for attempt in xrange(self.POWER_SWAP_ITERATIONS): |
| if attempt & 1: |
| direction = 'rx' |
| else: |
| direction = 'tx' |
| if self._attempt_power_swap(direction): |
| success += 1 |
| new_state = self.dut_port.get_pd_state() |
| logging.info('New DUT power role = %s', new_state) |
| |
| if success != self.POWER_SWAP_ITERATIONS: |
| raise error.TestFail('Failed %r power swap attempts' % |
| (self.POWER_SWAP_ITERATIONS - success)) |
| |
| self._test_one_way_power_swap_in_suspend() |
| |
| # Force DUT to only support current power role |
| if self.dut_port.is_src(new_state): |
| dual_mode = 'src' |
| else: |
| dual_mode = 'snk' |
| |
| # Save current dual role setting |
| current_dual_role = self.dut_port.drp_get() |
| |
| try: |
| logging.info('Setting dualrole mode to %s', dual_mode) |
| self.dut_port.drp_set(dual_mode) |
| time.sleep(self.PD_ROLE_DELAY) |
| # Expect behavior now is that DUT will reject power swap |
| self._test_power_swap_reject() |
| logging.info('Power Swap request rejected by DUT as expected') |
| finally: |
| # Restore dual role setting |
| self.dut_port.drp_set(current_dual_role) |