xref: /aosp_15_r20/external/autotest/server/site_tests/firmware_PDPowerSwap/firmware_PDPowerSwap.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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