1# Copyright 2018 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.cr50_test import Cr50Test
10
11
12class firmware_Cr50OpenWhileAPOff(Cr50Test):
13    """Verify the console can be opened while the AP is off.
14
15    Make sure it runs ok when cr50 saw the AP turn off and when it resets while
16    the AP is off.
17
18    This test would work the same with any cr50 ccd command that uses vendor
19    commands. 'ccd open' is just one.
20    """
21    version = 1
22
23    SLEEP_DELAY = 20
24    SHORT_DELAY = 2
25    CCD_PASSWORD_RATE_LIMIT = 3
26    PASSWORD = 'Password'
27    PLT_RST = 1 << 6
28
29    def initialize(self, host, cmdline_args, full_args):
30        """Initialize the test"""
31        self.changed_dut_state = False
32        super(firmware_Cr50OpenWhileAPOff, self).initialize(host, cmdline_args,
33                full_args)
34
35        if not hasattr(self, 'cr50'):
36            raise error.TestNAError('Test can only be run on devices with '
37                                    'access to the Cr50 console')
38
39        # TODO(mruthven): replace with dependency on servo v4 with servo micro
40        # and type c cable.
41        if 'servo_v4_with_servo_micro' != self.servo.get_servo_version():
42            raise error.TestNAError('Run using servo v4 with servo micro')
43
44        if not self.cr50.has_command('ccdstate'):
45            raise error.TestNAError('Cannot test on Cr50 with old CCD version')
46
47        dts_mode_works = self.cr50.servo_v4_supports_dts_mode()
48        if not dts_mode_works:
49            raise error.TestNAError('Plug in servo v4 type c cable into ccd '
50                    'port')
51
52        # Asserting warm_reset will hold the AP in reset if the system uses
53        # SYS_RST instead of PLT_RST. If the system uses PLT_RST, we have to
54        # hold the EC in reset to guarantee the device won't turn on during
55        # open.
56        # warm_reset doesn't interfere with rdd, so it's best to use that when
57        # possible.
58        self.reset_signal = ('cold_reset' if self.cr50.get_board_properties() &
59                self.PLT_RST else 'warm_reset')
60        logging.info('Using %r for reset', self.reset_signal)
61
62        self.fast_open(enable_testlab=True)
63        # make sure password is cleared.
64        self.cr50.send_command('ccd reset')
65        self.cr50.get_ccd_info()
66        # You can only open cr50 from the console if a password is set. Set
67        # a password, so we can use it to open cr50 while the AP is off.
68        self.set_ccd_password(self.PASSWORD)
69
70        self.changed_dut_state = True
71        self.assert_reset = True
72        if not self.reset_device_get_deep_sleep_count(True):
73            # Some devices can't tell the AP is off when the EC is off. Try
74            # deep sleep with just the AP off.
75            self.assert_reset = False
76            # If deep sleep doesn't work at all, we can't run the test.
77            if not self.reset_device_get_deep_sleep_count(True):
78                raise error.TestNAError('Skipping test on device without deep '
79                        'sleep support')
80            # We can't hold the ec in reset and enter deep sleep. Set the
81            # capability so physical presence isn't required for open.
82            logging.info("deep sleep doesn't work with EC in reset. skipping "
83                         "physical presence checks.")
84            # set OpenNoLongPP so open won't require pressing the power button.
85            self.cr50.set_cap('OpenNoLongPP', 'Always')
86        else:
87            logging.info('Physical presence can be used during open')
88
89
90    def cleanup(self):
91        """Make sure the device is on at the end of the test"""
92        # If we got far enough to start changing the DUT power state, attempt to
93        # turn the DUT back on and reenable the cr50 console.
94        try:
95            if self.changed_dut_state:
96                self.restore_dut()
97        finally:
98            super(firmware_Cr50OpenWhileAPOff, self).cleanup()
99
100
101    def restore_dut(self):
102        """Turn on the device and reset cr50
103
104        Do a deep sleep reset to fix the cr50 console. Then turn the device on.
105
106        Raises:
107            TestFail if the cr50 console doesn't work
108        """
109        logging.info('attempt cr50 console recovery')
110
111        # The console may be hung. Run through reset manually, so we dont need
112        # the console.
113        self.turn_device('off')
114        # Toggle dts mode to enter and exit deep sleep
115        self.toggle_dts_mode()
116        # Turn the device back on
117        self.turn_device('on')
118
119        # Verify the cr50 console responds to commands.
120        try:
121            logging.info(self.cr50.send_command_get_output('ccdstate',
122                    ['ccdstate.*>']))
123        except error.TestFail, e:
124            if 'Timeout waiting for response' in e.message:
125                raise error.TestFail('Could not restore Cr50 console')
126            raise
127
128
129    def turn_device(self, state):
130        """Turn the device off or on.
131
132        If we are testing ccd open fully, it will also assert device reset so
133        power button presses wont turn on the AP
134        """
135        # Make sure to release the device from reset before trying anything
136        self.servo.set(self.reset_signal, 'off')
137
138        time.sleep(self.SHORT_DELAY)
139
140        # Turn off the AP
141        if state == 'off':
142            self.servo.set_nocheck('power_state', 'off')
143            time.sleep(self.SHORT_DELAY)
144
145        # Hold the EC in reset or release it from reset based on state
146        if self.assert_reset:
147            # The reset control is the inverse of device state, so convert the
148            # state self.servo.set(reset_signal, 'on' if state == 'off' else
149            # 'off')
150            self.servo.set(self.reset_signal, 'on' if state == 'off' else 'off')
151            time.sleep(self.SHORT_DELAY)
152
153        # Turn on the AP
154        if state == 'on':
155            self.servo.power_short_press()
156
157
158    def reset_device_get_deep_sleep_count(self, deep_sleep):
159        """Reset the device. Use dts mode to enable deep sleep if requested.
160
161        Args:
162            deep_sleep: True if Cr50 should enter deep sleep
163
164        Returns:
165            The number of times Cr50 entered deep sleep during reset
166        """
167        self.turn_device('off')
168        # Do a deep sleep reset to restore the cr50 console.
169        ds_count = self.deep_sleep_reset_get_count() if deep_sleep else 0
170        self.turn_device('on')
171        return ds_count
172
173
174    def toggle_dts_mode(self):
175        """Toggle DTS mode to enable and disable deep sleep"""
176        # We cant use cr50 ccd_disable/enable, because those uses the cr50
177        # console. Call servo_v4_dts_mode directly.
178        self.servo.set_nocheck('servo_v4_dts_mode', 'off')
179        time.sleep(self.SLEEP_DELAY)
180        self.servo.set_nocheck('servo_v4_dts_mode', 'on')
181
182
183    def deep_sleep_reset_get_count(self):
184        """Toggle ccd to get to do a deep sleep reset
185
186        Returns:
187            The number of times cr50 entered deep sleep
188        """
189        start_count = self.cr50.get_deep_sleep_count()
190        # CCD is what's keeping Cr50 awake. Toggle DTS mode to turn off ccd
191        # so cr50 will enter deep sleep
192        self.toggle_dts_mode()
193        # Return the number of times cr50 entered deep sleep.
194        return self.cr50.get_deep_sleep_count() - start_count
195
196
197    def try_ccd_open(self, cr50_reset):
198        """Try 'ccd open' and make sure the console doesn't hang"""
199        self.cr50.set_ccd_level('lock', self.PASSWORD)
200        try:
201            self.turn_device('off')
202            if cr50_reset:
203                if not self.deep_sleep_reset_get_count():
204                    raise error.TestFail('Did not detect a cr50 reset')
205            # Verify ccd open
206            self.cr50.set_ccd_level('open', self.PASSWORD)
207        finally:
208            self.restore_dut()
209
210
211    def run_once(self):
212        """Turn off the AP and try ccd open."""
213        self.try_ccd_open(False)
214        self.try_ccd_open(True)
215