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 PLANKTON_PORT = 0 27 POWER_SWAP_ITERATIONS = 5 28 # Source power role 29 SRC ='SRC_READY' 30 # Sink power role 31 SNK = 'SNK_READY' 32 33 def _set_plankton_power_role_to_src(self): 34 """Force Plankton to act as a source 35 36 @returns True if Plankton power role is source, false otherwise 37 """ 38 PLANKTON_SRC_VOLTAGE = 20 39 self.plankton.charge(PLANKTON_SRC_VOLTAGE) 40 # Wait for change to take place 41 time.sleep(self.PD_CONNECT_DELAY) 42 plankton_state = self.plankton_pd_utils.get_pd_state(self.PLANKTON_PORT) 43 # Current Plankton power role should be source 44 return bool(plankton_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 Plankton 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.plankton_pd_utils 82 port = self.PLANKTON_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 Plankton power role 90 plankton_pr = self.plankton_pd_utils.get_pd_state(self.PLANKTON_PORT) 91 return bool(dut_pr == plankton_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 Plankton, 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 Plankton and get reply 107 ctrl_msg = self._send_power_swap_get_reply(self.plankton_pd_utils, 108 self.PLANKTON_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): 119 super(firmware_PDPowerSwap, self).initialize(host, cmdline_args) 120 # Only run in normal mode 121 self.switcher.setup_mode('normal') 122 # Turn off console prints, except for USBPD. 123 self.usbpd.send_command('chan 0x08000000') 124 125 def cleanup(self): 126 self.usbpd.send_command('chan 0xffffffff') 127 super(firmware_PDPowerSwap, self).cleanup() 128 129 def run_once(self): 130 """Execute Power Role swap test. 131 132 1. Verify that pd console is accessible 133 2. Verify that DUT has a valid PD contract and connected to Plankton 134 3. Determine if DUT is in dualrole mode 135 4. If not dualrole mode, verify DUT rejects power swap request 136 Else test power swap (tx/rx), then Force DUT to be sink or 137 source only and verify rejecttion of power swap request. 138 139 """ 140 # create objects for pd utilities 141 self.dut_pd_utils = pd_console.PDConsoleUtils(self.usbpd) 142 self.plankton_pd_utils = pd_console.PDConsoleUtils(self.plankton) 143 self.connect_utils = pd_console.PDConnectionUtils(self.dut_pd_utils, 144 self.plankton_pd_utils) 145 146 # Make sure PD support exists in the UART console 147 if self.dut_pd_utils.verify_pd_console() == False: 148 raise error.TestFail("pd command not present on console!") 149 150 # Type C connection (PD contract) should exist at this point 151 # For this test, the DUT must be connected to a Plankton. 152 pd_port = self.connect_utils.find_dut_to_plankton_connection() 153 if pd_port is None: 154 raise error.TestFail("DUT to Plankton PD connection not found") 155 dut_connect_state = self.dut_pd_utils.get_pd_state(pd_port) 156 logging.info('Initial DUT connect state = %s', dut_connect_state) 157 158 # Get DUT dualrole status 159 if self.dut_pd_utils.is_pd_dual_role_enabled() == False: 160 # DUT does not support dualrole mode, power swap 161 # requests to the DUT should be rejected. 162 logging.info('Power Swap support not advertised by DUT') 163 self._test_power_swap_reject(pd_port) 164 logging.info('Power Swap request rejected by DUT as expected') 165 else: 166 # Start with Plankton as source 167 if self._set_plankton_power_role_to_src() == False: 168 raise error.TestFail('Plankton not set to source') 169 # DUT is dualrole in dual role mode. Test power role swap 170 # operation intiated both by the DUT and Plankton. 171 success = 0 172 for attempt in xrange(self.POWER_SWAP_ITERATIONS): 173 if attempt & 1: 174 direction = 'rx' 175 else: 176 direction = 'tx' 177 if self._attempt_power_swap(pd_port, direction): 178 success += 1 179 new_state = self.dut_pd_utils.get_pd_state(pd_port) 180 logging.info('New DUT power role = %s', new_state) 181 182 if success != self.POWER_SWAP_ITERATIONS: 183 raise error.TestFail('Failed %r power swap attempts' % 184 (self.POWER_SWAP_ITERATIONS - success)) 185 186 # Force DUT to only support current power role 187 if new_state == self.SRC: 188 dual_mode = 'src' 189 else: 190 dual_mode = 'snk' 191 logging.info('Setting dualrole mode to %s', dual_mode) 192 self.dut_pd_utils.set_pd_dualrole(dual_mode) 193 time.sleep(self.PD_ROLE_DELAY) 194 # Expect behavior now is that DUT will reject power swap 195 self._test_power_swap_reject(pd_port) 196 logging.info('Power Swap request rejected by DUT as expected') 197 # Restore DUT dual role operation 198 self.dut_pd_utils.set_pd_dualrole('on') 199 # Set connection back to default arrangement 200 self.plankton_pd_utils.set_pd_dualrole('off') 201 self.plankton_pd_utils.send_pd_command('fake disconnect 100 1000') 202 203