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