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