• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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