1# Copyright 2016 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 5import logging 6import time 7 8from autotest_lib.client.common_lib import error 9from autotest_lib.server.cros.faft.firmware_test import FirmwareTest 10from autotest_lib.server.cros.servo import pd_console 11 12 13class firmware_PDPowerSwap(FirmwareTest): 14 """ 15 Servo based USB PD power role swap test. 16 17 Pass critera is all power role swaps are successful if the DUT 18 is dualrole capable. If not dualrole, then pass criteria is 19 the DUT sending a reject message in response to swap request. 20 21 """ 22 version = 1 23 24 PD_ROLE_DELAY = 0.5 25 PD_CONNECT_DELAY = 4 26 # Should be an even number; back to the original state at the end 27 POWER_SWAP_ITERATIONS = 10 28 # Source power role 29 SRC ='SRC_READY' 30 # Sink power role 31 SNK = 'SNK_READY' 32 33 def _set_pdtester_power_role_to_src(self): 34 """Force PDTester to act as a source 35 36 @returns True if PDTester power role is source, false otherwise 37 """ 38 PDTESTER_SRC_VOLTAGE = 20 39 self.pdtester.charge(PDTESTER_SRC_VOLTAGE) 40 # Wait for change to take place 41 time.sleep(self.PD_CONNECT_DELAY) 42 pdtester_state = self.pdtester_pd_utils.get_pd_state(self.pdtester_port) 43 # Current PDTester power role should be source 44 return bool(pdtester_state == self.SRC) 45 46 def _send_power_swap_get_reply(self, console, port): 47 """Send power swap request, get PD control msg reply 48 49 The PD console debug mode is enabled prior to sending 50 a pd power role swap request message. This allows the 51 control message reply to be extracted. The debug mode 52 is disabled prior to exiting. 53 54 @param console: pd console object for uart access 55 56 @returns: PD control header message 57 """ 58 # Enable PD console debug mode to show control messages 59 console.enable_pd_console_debug() 60 cmd = 'pd %d swap power' % port 61 m = console.send_pd_command_get_output(cmd, ['RECV\s([\w]+)']) 62 ctrl_msg = int(m[0][1], 16) & console.PD_CONTROL_MSG_MASK 63 console.disable_pd_console_debug() 64 return ctrl_msg 65 66 def _attempt_power_swap(self, pd_port, direction): 67 """Perform a power role swap request 68 69 Initiate a power role swap request on either the DUT or 70 PDTester depending on the direction parameter. The power 71 role swap is then verified to have taken place. 72 73 @param pd_port: DUT pd port value 0/1 74 @param direction: rx or tx from the DUT perspective 75 76 @returns True if power swap is successful 77 """ 78 # Get DUT current power role 79 dut_pr = self.dut_pd_utils.get_pd_state(pd_port) 80 if direction == 'rx': 81 console = self.pdtester_pd_utils 82 port = self.pdtester_port 83 else: 84 console = self.dut_pd_utils 85 port = pd_port 86 # Send power swap request 87 self._send_power_swap_get_reply(console, port) 88 time.sleep(self.PD_CONNECT_DELAY) 89 # Get PDTester power role 90 pdtester_pr = self.pdtester_pd_utils.get_pd_state(self.pdtester_port) 91 return bool(dut_pr == pdtester_pr) 92 93 def _test_power_swap_reject(self, pd_port): 94 """Verify that a power swap request is rejected 95 96 This tests the case where the DUT isn't in dualrole mode. 97 A power swap request is sent by PDTester, and then 98 the control message checked to ensure the request was rejected. 99 In addition, the connection state is verified to not have 100 changed. 101 102 @param pd_port: port for DUT pd connection 103 """ 104 # Get current DUT power role 105 dut_power_role = self.dut_pd_utils.get_pd_state(pd_port) 106 # Send swap command from PDTester and get reply 107 ctrl_msg = self._send_power_swap_get_reply(self.pdtester_pd_utils, 108 self.pdtester_port) 109 if ctrl_msg != self.dut_pd_utils.PD_CONTROL_MSG_DICT['Reject']: 110 raise error.TestFail('Power Swap Req not rejected, returned %r' % 111 ctrl_msg) 112 # Get DUT current state 113 pd_state = self.dut_pd_utils.get_pd_state(pd_port) 114 if pd_state != dut_power_role: 115 raise error.TestFail('PD not connected! pd_state = %r' % 116 pd_state) 117 118 def initialize(self, host, cmdline_args, flip_cc=False): 119 super(firmware_PDPowerSwap, self).initialize(host, cmdline_args) 120 self.setup_pdtester(flip_cc) 121 # Only run in normal mode 122 self.switcher.setup_mode('normal') 123 # Turn off console prints, except for USBPD. 124 self.usbpd.enable_console_channel('usbpd') 125 126 def cleanup(self): 127 if hasattr(self, 'pd_port'): 128 # Restore DUT dual role operation 129 self.dut_pd_utils.set_pd_dualrole(self.pd_port, 'on') 130 if hasattr(self, 'pdtester_port'): 131 # Set connection back to default arrangement 132 self.pdtester_pd_utils.set_pd_dualrole(self.pdtester_port, 'off') 133 134 if hasattr(self, 'pd_port') and hasattr(self, 'pdtester_port'): 135 # Fake-disconnect to restore the original power role 136 self.pdtester_pd_utils.send_pd_command('fakedisconnect 100 1000') 137 138 self.usbpd.send_command('chan 0xffffffff') 139 super(firmware_PDPowerSwap, self).cleanup() 140 141 def run_once(self): 142 """Execute Power Role swap test. 143 144 1. Verify that pd console is accessible 145 2. Verify that DUT has a valid PD contract and connected to PDTester 146 3. Determine if DUT is in dualrole mode 147 4. If not dualrole mode, verify DUT rejects power swap request 148 Else test power swap (tx/rx), then Force DUT to be sink or 149 source only and verify rejecttion of power swap request. 150 151 """ 152 # TODO(b/35573842): Refactor to use PDPortPartner to probe the port 153 self.pdtester_port = 1 if 'servo_v4' in self.pdtester.servo_type else 0 154 155 # create objects for pd utilities 156 self.dut_pd_utils = pd_console.PDConsoleUtils(self.usbpd) 157 self.pdtester_pd_utils = pd_console.PDConsoleUtils(self.pdtester) 158 self.connect_utils = pd_console.PDConnectionUtils( 159 self.dut_pd_utils, self.pdtester_pd_utils) 160 161 # Make sure PD support exists in the UART console 162 if self.dut_pd_utils.verify_pd_console() == False: 163 raise error.TestFail("pd command not present on console!") 164 165 # Type C connection (PD contract) should exist at this point 166 # For this test, the DUT must be connected to a PDTester. 167 self.pd_port = self.connect_utils.find_dut_to_pdtester_connection() 168 if self.pd_port is None: 169 raise error.TestFail("DUT to PDTester PD connection not found") 170 dut_connect_state = self.dut_pd_utils.get_pd_state(self.pd_port) 171 logging.info('Initial DUT connect state = %s', dut_connect_state) 172 173 # Get DUT dualrole status 174 if self.dut_pd_utils.is_pd_dual_role_enabled(self.pd_port) == False: 175 # DUT does not support dualrole mode, power swap 176 # requests to the DUT should be rejected. 177 logging.info('Power Swap support not advertised by DUT') 178 self._test_power_swap_reject(self.pd_port) 179 logging.info('Power Swap request rejected by DUT as expected') 180 else: 181 # Start with PDTester as source 182 if self._set_pdtester_power_role_to_src() == False: 183 raise error.TestFail('PDTester not set to source') 184 # DUT is dualrole in dual role mode. Test power role swap 185 # operation intiated both by the DUT and PDTester. 186 success = 0 187 for attempt in xrange(self.POWER_SWAP_ITERATIONS): 188 if attempt & 1: 189 direction = 'rx' 190 else: 191 direction = 'tx' 192 if self._attempt_power_swap(self.pd_port, direction): 193 success += 1 194 new_state = self.dut_pd_utils.get_pd_state(self.pd_port) 195 logging.info('New DUT power role = %s', new_state) 196 197 if success != self.POWER_SWAP_ITERATIONS: 198 raise error.TestFail('Failed %r power swap attempts' % 199 (self.POWER_SWAP_ITERATIONS - success)) 200 201 # Force DUT to only support current power role 202 if new_state == self.SRC: 203 dual_mode = 'src' 204 else: 205 dual_mode = 'snk' 206 logging.info('Setting dualrole mode to %s', dual_mode) 207 self.dut_pd_utils.set_pd_dualrole(self.pd_port, dual_mode) 208 time.sleep(self.PD_ROLE_DELAY) 209 # Expect behavior now is that DUT will reject power swap 210 self._test_power_swap_reject(self.pd_port) 211 logging.info('Power Swap request rejected by DUT as expected') 212 213