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_device 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 23 version = 1 24 PD_ROLE_DELAY = 1 25 PD_CONNECT_DELAY = 10 26 # Should be an even number; back to the original state at the end 27 POWER_SWAP_ITERATIONS = 10 28 29 def _set_pdtester_power_role_to_src(self): 30 """Force PDTester to act as a source 31 32 @returns True if PDTester power role is source, false otherwise 33 """ 34 PDTESTER_SRC_VOLTAGE = 20 35 self.pdtester.charge(PDTESTER_SRC_VOLTAGE) 36 # Wait for change to take place 37 time.sleep(self.PD_CONNECT_DELAY) 38 # Current PDTester power role should be source 39 return self.pdtester_port.is_src() 40 41 def _send_power_swap_get_reply(self, port): 42 """Send power swap request, get PD control msg reply 43 44 The PD console debug mode is enabled prior to sending 45 a pd power role swap request message. This allows the 46 control message reply to be extracted. The debug mode 47 is disabled prior to exiting. 48 49 @param port: pd device object 50 51 @returns: PD control header message 52 """ 53 # Enable PD console debug mode to show control messages 54 port.utils.enable_pd_console_debug() 55 cmd = 'pd %d swap power' % port.port 56 m = port.utils.send_pd_command_get_output(cmd, ['RECV\s([\w]+)']) 57 ctrl_msg = int(m[0][1], 16) & port.utils.PD_CONTROL_MSG_MASK 58 port.utils.disable_pd_console_debug() 59 return ctrl_msg 60 61 def _attempt_power_swap(self, direction): 62 """Perform a power role swap request 63 64 Initiate a power role swap request on either the DUT or 65 PDTester depending on the direction parameter. The power 66 role swap is then verified to have taken place. 67 68 @param direction: rx or tx from the DUT perspective 69 70 @returns True if power swap is successful 71 """ 72 # Get DUT current power role 73 dut_pr = self.dut_port.get_pd_state() 74 if direction == 'rx': 75 port = self.pdtester_port 76 else: 77 port = self.dut_port 78 # Send power swap request 79 self._send_power_swap_get_reply(port) 80 for _ in range(self.PD_CONNECT_DELAY): 81 time.sleep(1) 82 # Get PDTester power role 83 pdtester_pr = self.pdtester_port.get_pd_state() 84 if self.dut_port.is_src(dut_pr) and self.pdtester_port.is_src( 85 pdtester_pr): 86 return True 87 elif self.dut_port.is_snk(dut_pr) and self.pdtester_port.is_snk( 88 pdtester_pr): 89 return True 90 91 return False 92 93 def _test_power_swap_reject(self): 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 # Get current DUT power role 103 dut_power_role = self.dut_port.get_pd_state() 104 # Send swap command from PDTester and get reply 105 ctrl_msg = self._send_power_swap_get_reply(self.pdtester_port) 106 if ctrl_msg != self.dut_port.utils.PD_CONTROL_MSG_DICT['Reject']: 107 raise error.TestFail('Power Swap Req not rejected, returned %r' % 108 ctrl_msg) 109 # Get DUT current state 110 pd_state = self.dut_port.get_pd_state() 111 if pd_state != dut_power_role: 112 raise error.TestFail('PD not connected! pd_state = %r' % 113 pd_state) 114 115 def _test_one_way_power_swap_in_suspend(self): 116 """Verify SRC-to-SNK power role swap on DUT PD port in S0ix/S3 117 118 Set the DUT power role to source and then suspend the DUT. 119 Verify SRC-to-SNK power role request from the PD tester works, 120 while SNK-to-SRC power role request fails. Note that this is 121 ChromeOS policy decision, not part of the PD spec. 122 123 When DUT doesn't provide power in suspend, set DUT power role 124 to sink, supend DUT and check if SNK-to-SRC power role request fails. 125 126 """ 127 # Ensure DUT PD port is sourcing power. 128 time.sleep(self.PD_CONNECT_DELAY) 129 if self.faft_config.dut_can_source_power_in_suspend: 130 if not self.dut_port.is_src(): 131 if self._attempt_power_swap('rx') == False or not self.dut_port.is_src(): 132 raise error.TestFail('Fail to set DUT power role to source.') 133 134 self.set_ap_off_power_mode('suspend') 135 136 new_state = self.dut_port.get_pd_state() 137 if not self.dut_port.is_src(new_state): 138 raise error.TestFail('DUT power role changed to %s ' 139 'during S0-to-S3 transition!' % new_state) 140 141 # If the DUT PD port supports DRP in S0, it should supports SRC-to-SNK 142 # power role swap in suspend mode. The other way around (SNK-to-SRC) in 143 # suspend mode should fail. 144 logging.info('Request a SRC-to-SNK power role swap to DUT PD port ' 145 'in suspend mode. Expect to succeed.') 146 if self._attempt_power_swap('rx') == False: 147 raise error.TestFail('SRC-to-SNK power role swap failed.') 148 149 # If we define AC insertion as a wake source for this board, the 150 # SRC-to-SNK power role swap would wake up the DUT. In this case, 151 # we need to suspend the DUT again to proceed the test. 152 if self.wait_power_state(self.POWER_STATE_S0, 153 self.DEFAULT_PWR_RETRIES): 154 self.set_ap_off_power_mode('suspend') 155 else: 156 # If DUT can't source power in suspend we are going to test only if 157 # SNK-to-SRC transition fails, so make sure that port acts as sink 158 logging.info('DUT is not sourcing power in suspend - ' 159 'skip verification if SRC-to-SNK power swap works') 160 if not self.dut_port.is_snk(): 161 if self._attempt_power_swap('rx') == False or not self.dut_port.is_snk(): 162 raise error.TestFail('Fail to set DUT power role to sink.') 163 164 self.set_ap_off_power_mode('suspend') 165 166 new_state = self.dut_port.get_pd_state() 167 if not self.dut_port.is_snk(new_state): 168 raise error.TestFail('DUT power role changed to %s ' 169 'during S0-to-S3 transition!' % new_state) 170 171 logging.info('Request a SNK-to-SRC power role swap to DUT PD port ' 172 'in suspend mode. Expect to fail.') 173 self._test_power_swap_reject() 174 175 self.restore_ap_on_power_mode() 176 177 def initialize(self, host, cmdline_args, flip_cc=False, dts_mode=False, 178 init_power_mode=None): 179 super(firmware_PDPowerSwap, self).initialize(host, cmdline_args) 180 self.setup_pdtester(flip_cc, dts_mode, min_batt_level=10) 181 # Only run in normal mode 182 self.switcher.setup_mode('normal') 183 if init_power_mode: 184 # Set the DUT to suspend (S3) or shutdown (G3/S5) mode 185 self.set_ap_off_power_mode(init_power_mode) 186 # Turn off console prints, except for USBPD. 187 self.usbpd.enable_console_channel('usbpd') 188 189 def cleanup(self): 190 if hasattr(self, 'pd_port'): 191 # Restore DUT dual role operation 192 self.dut_port.drp_set('on') 193 if hasattr(self, 'pdtester_port'): 194 # Set connection back to default arrangement 195 self.pdtester_port.drp_set('off') 196 197 if hasattr(self, 'pd_port') and hasattr(self, 'pdtester_port'): 198 # Fake-disconnect to restore the original power role 199 self.pdtester_port.utils.send_pd_command('fakedisconnect 100 1000') 200 201 self.usbpd.send_command('chan 0xffffffff') 202 self.restore_ap_on_power_mode() 203 super(firmware_PDPowerSwap, self).cleanup() 204 205 def run_once(self): 206 """Execute Power Role swap test. 207 208 1. Verify that pd console is accessible 209 2. Verify that DUT has a valid PD contract and connected to PDTester 210 3. Determine if DUT is in dualrole mode 211 4. If dualrole mode is not supported, verify DUT rejects power swap 212 request. If dualrole mode is supported, test power swap (tx/rx) in 213 S0 and one-way power swap (SRC-to-SNK on DUT PD port) in S3/S0ix. 214 Then force DUT to be sink or source only and verify rejection of 215 power swap request. 216 217 """ 218 # Create list of available UART consoles 219 consoles = [self.usbpd, self.pdtester] 220 port_partner = pd_device.PDPortPartner(consoles) 221 222 # Identify a valid test port pair 223 port_pair = port_partner.identify_pd_devices() 224 if not port_pair: 225 raise error.TestFail('No PD connection found!') 226 227 for port in port_pair: 228 if port.is_pdtester: 229 self.pdtester_port = port 230 else: 231 self.dut_port = port 232 233 dut_connect_state = self.dut_port.get_pd_state() 234 logging.info('Initial DUT connect state = %s', dut_connect_state) 235 236 # Get DUT dualrole status 237 if self.dut_port.is_drp() == False: 238 # DUT does not support dualrole mode, power swap 239 # requests to the DUT should be rejected. 240 logging.info('Power Swap support not advertised by DUT') 241 self._test_power_swap_reject() 242 logging.info('Power Swap request rejected by DUT as expected') 243 else: 244 if self.get_power_state() != 'S0': 245 raise error.TestFail('If the DUT is not is S0, the DUT port ' 246 'shouldn\'t claim dualrole is enabled.') 247 # Start with PDTester as source 248 if self._set_pdtester_power_role_to_src() == False: 249 raise error.TestFail('PDTester not set to source') 250 # DUT is dualrole in dual role mode. Test power role swap 251 # operation intiated both by the DUT and PDTester. 252 success = 0 253 for attempt in range(self.POWER_SWAP_ITERATIONS): 254 if attempt & 1: 255 direction = 'rx' 256 else: 257 direction = 'tx' 258 if self._attempt_power_swap(direction): 259 success += 1 260 new_state = self.dut_port.get_pd_state() 261 logging.info('New DUT power role = %s', new_state) 262 263 if success != self.POWER_SWAP_ITERATIONS: 264 raise error.TestFail('Failed %r power swap attempts' % 265 (self.POWER_SWAP_ITERATIONS - success)) 266 267 self._test_one_way_power_swap_in_suspend() 268 269 # Force DUT to only support current power role 270 if self.dut_port.is_src(new_state): 271 dual_mode = 'src' 272 else: 273 dual_mode = 'snk' 274 275 # Save current dual role setting 276 current_dual_role = self.dut_port.drp_get() 277 278 try: 279 logging.info('Setting dualrole mode to %s', dual_mode) 280 self.dut_port.drp_set(dual_mode) 281 time.sleep(self.PD_ROLE_DELAY) 282 # Expect behavior now is that DUT will reject power swap 283 self._test_power_swap_reject() 284 logging.info('Power Swap request rejected by DUT as expected') 285 finally: 286 # Restore dual role setting 287 self.dut_port.drp_set(current_dual_role) 288