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