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 time.sleep(self.PD_CONNECT_DELAY) 81 # Get PDTester power role 82 pdtester_pr = self.pdtester_port.get_pd_state() 83 if self.dut_port.is_src(dut_pr) and self.pdtester_port.is_src(pdtester_pr): 84 return True 85 elif self.dut_port.is_snk(dut_pr) and self.pdtester_port.is_snk(pdtester_pr): 86 return True 87 else: 88 return False 89 90 def _test_power_swap_reject(self): 91 """Verify that a power swap request is rejected 92 93 This tests the case where the DUT isn't in dualrole mode. 94 A power swap request is sent by PDTester, and then 95 the control message checked to ensure the request was rejected. 96 In addition, the connection state is verified to not have 97 changed. 98 """ 99 # Get current DUT power role 100 dut_power_role = self.dut_port.get_pd_state() 101 # Send swap command from PDTester and get reply 102 ctrl_msg = self._send_power_swap_get_reply(self.pdtester_port) 103 if ctrl_msg != self.dut_port.utils.PD_CONTROL_MSG_DICT['Reject']: 104 raise error.TestFail('Power Swap Req not rejected, returned %r' % 105 ctrl_msg) 106 # Get DUT current state 107 pd_state = self.dut_port.get_pd_state() 108 if pd_state != dut_power_role: 109 raise error.TestFail('PD not connected! pd_state = %r' % 110 pd_state) 111 112 def _test_one_way_power_swap_in_suspend(self): 113 """Verify SRC-to-SNK power role swap on DUT PD port in S0ix/S3 114 115 Set the DUT power role to source and then suspend the DUT. 116 Verify SRC-to-SNK power role request from the PD tester works, 117 while SNK-to-SRC power role request fails. Note that this is 118 Chrome OS policy decision, not part of the PD spec. 119 120 When DUT doesn't provide power in suspend, set DUT power role 121 to sink, supend DUT and check if SNK-to-SRC power role request fails. 122 123 """ 124 # Ensure DUT PD port is sourcing power. 125 time.sleep(self.PD_CONNECT_DELAY) 126 if self.faft_config.dut_can_source_power_in_suspend: 127 if not self.dut_port.is_src(): 128 if self._attempt_power_swap('rx') == False or not self.dut_port.is_src(): 129 raise error.TestFail('Fail to set DUT power role to source.') 130 131 self.set_ap_off_power_mode('suspend') 132 133 new_state = self.dut_port.get_pd_state() 134 if not self.dut_port.is_src(new_state): 135 raise error.TestFail('DUT power role changed to %s ' 136 'during S0-to-S3 transition!' % new_state) 137 138 # If the DUT PD port supports DRP in S0, it should supports SRC-to-SNK 139 # power role swap in suspend mode. The other way around (SNK-to-SRC) in 140 # suspend mode should fail. 141 logging.info('Request a SRC-to-SNK power role swap to DUT PD port ' 142 'in suspend mode. Expect to succeed.') 143 if self._attempt_power_swap('rx') == False: 144 raise error.TestFail('SRC-to-SNK power role swap failed.') 145 146 # If we define AC insertion as a wake source for this board, the 147 # SRC-to-SNK power role swap would wake up the DUT. In this case, 148 # we need to suspend the DUT again to proceed the test. 149 if self.wait_power_state(self.POWER_STATE_S0, 150 self.DEFAULT_PWR_RETRIES): 151 self.set_ap_off_power_mode('suspend') 152 else: 153 # If DUT can't source power in suspend we are going to test only if 154 # SNK-to-SRC transition fails, so make sure that port acts as sink 155 logging.info('DUT is not sourcing power in suspend - ' 156 'skip verification if SRC-to-SNK power swap works') 157 if not self.dut_port.is_snk(): 158 if self._attempt_power_swap('rx') == False or not self.dut_port.is_snk(): 159 raise error.TestFail('Fail to set DUT power role to sink.') 160 161 self.set_ap_off_power_mode('suspend') 162 163 new_state = self.dut_port.get_pd_state() 164 if not self.dut_port.is_snk(new_state): 165 raise error.TestFail('DUT power role changed to %s ' 166 'during S0-to-S3 transition!' % new_state) 167 168 logging.info('Request a SNK-to-SRC power role swap to DUT PD port ' 169 'in suspend mode. Expect to fail.') 170 self._test_power_swap_reject() 171 172 self.restore_ap_on_power_mode() 173 174 def initialize(self, host, cmdline_args, flip_cc=False, dts_mode=False, 175 init_power_mode=None): 176 super(firmware_PDPowerSwap, self).initialize(host, cmdline_args) 177 self.setup_pdtester(flip_cc, dts_mode, min_batt_level=10) 178 # Only run in normal mode 179 self.switcher.setup_mode('normal') 180 if init_power_mode: 181 # Set the DUT to suspend (S3) or shutdown (G3/S5) mode 182 self.set_ap_off_power_mode(init_power_mode) 183 # Turn off console prints, except for USBPD. 184 self.usbpd.enable_console_channel('usbpd') 185 186 def cleanup(self): 187 if hasattr(self, 'pd_port'): 188 # Restore DUT dual role operation 189 self.dut_port.drp_set('on') 190 if hasattr(self, 'pdtester_port'): 191 # Set connection back to default arrangement 192 self.pdtester_port.drp_set('off') 193 194 if hasattr(self, 'pd_port') and hasattr(self, 'pdtester_port'): 195 # Fake-disconnect to restore the original power role 196 self.pdtester_port.utils.send_pd_command('fakedisconnect 100 1000') 197 198 self.usbpd.send_command('chan 0xffffffff') 199 self.restore_ap_on_power_mode() 200 super(firmware_PDPowerSwap, self).cleanup() 201 202 def run_once(self): 203 """Execute Power Role swap test. 204 205 1. Verify that pd console is accessible 206 2. Verify that DUT has a valid PD contract and connected to PDTester 207 3. Determine if DUT is in dualrole mode 208 4. If dualrole mode is not supported, verify DUT rejects power swap 209 request. If dualrole mode is supported, test power swap (tx/rx) in 210 S0 and one-way power swap (SRC-to-SNK on DUT PD port) in S3/S0ix. 211 Then force DUT to be sink or source only and verify rejection of 212 power swap request. 213 214 """ 215 # Create list of available UART consoles 216 consoles = [self.usbpd, self.pdtester] 217 port_partner = pd_device.PDPortPartner(consoles) 218 219 # Identify a valid test port pair 220 port_pair = port_partner.identify_pd_devices() 221 if not port_pair: 222 raise error.TestFail('No PD connection found!') 223 224 for port in port_pair: 225 if port.is_pdtester: 226 self.pdtester_port = port 227 else: 228 self.dut_port = port 229 230 dut_connect_state = self.dut_port.get_pd_state() 231 logging.info('Initial DUT connect state = %s', dut_connect_state) 232 233 # Get DUT dualrole status 234 if self.dut_port.is_drp() == False: 235 # DUT does not support dualrole mode, power swap 236 # requests to the DUT should be rejected. 237 logging.info('Power Swap support not advertised by DUT') 238 self._test_power_swap_reject() 239 logging.info('Power Swap request rejected by DUT as expected') 240 else: 241 if self.get_power_state() != 'S0': 242 raise error.TestFail('If the DUT is not is S0, the DUT port ' 243 'shouldn\'t claim dualrole is enabled.') 244 # Start with PDTester as source 245 if self._set_pdtester_power_role_to_src() == False: 246 raise error.TestFail('PDTester not set to source') 247 # DUT is dualrole in dual role mode. Test power role swap 248 # operation intiated both by the DUT and PDTester. 249 success = 0 250 for attempt in xrange(self.POWER_SWAP_ITERATIONS): 251 if attempt & 1: 252 direction = 'rx' 253 else: 254 direction = 'tx' 255 if self._attempt_power_swap(direction): 256 success += 1 257 new_state = self.dut_port.get_pd_state() 258 logging.info('New DUT power role = %s', new_state) 259 260 if success != self.POWER_SWAP_ITERATIONS: 261 raise error.TestFail('Failed %r power swap attempts' % 262 (self.POWER_SWAP_ITERATIONS - success)) 263 264 self._test_one_way_power_swap_in_suspend() 265 266 # Force DUT to only support current power role 267 if self.dut_port.is_src(new_state): 268 dual_mode = 'src' 269 else: 270 dual_mode = 'snk' 271 272 # Save current dual role setting 273 current_dual_role = self.dut_port.drp_get() 274 275 try: 276 logging.info('Setting dualrole mode to %s', dual_mode) 277 self.dut_port.drp_set(dual_mode) 278 time.sleep(self.PD_ROLE_DELAY) 279 # Expect behavior now is that DUT will reject power swap 280 self._test_power_swap_reject() 281 logging.info('Power Swap request rejected by DUT as expected') 282 finally: 283 # Restore dual role setting 284 self.dut_port.drp_set(current_dual_role) 285