# Copyright 2018 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import logging import time from autotest_lib.client.common_lib import error from autotest_lib.server.cros.faft.cr50_test import Cr50Test class firmware_Cr50OpenWhileAPOff(Cr50Test): """Verify the console can be opened while the AP is off. Make sure it runs ok when cr50 saw the AP turn off and when it resets while the AP is off. This test would work the same with any cr50 ccd command that uses vendor commands. 'ccd open' is just one. """ version = 1 SLEEP_DELAY = 20 SHORT_DELAY = 2 CCD_PASSWORD_RATE_LIMIT = 3 PASSWORD = 'Password' PLT_RST = 1 << 6 def initialize(self, host, cmdline_args, full_args): """Initialize the test""" self.changed_dut_state = False super(firmware_Cr50OpenWhileAPOff, self).initialize(host, cmdline_args, full_args) if not hasattr(self, 'cr50'): raise error.TestNAError('Test can only be run on devices with ' 'access to the Cr50 console') # TODO(mruthven): replace with dependency on servo v4 with servo micro # and type c cable. if 'servo_v4_with_servo_micro' != self.servo.get_servo_version(): raise error.TestNAError('Run using servo v4 with servo micro') if not self.cr50.has_command('ccdstate'): raise error.TestNAError('Cannot test on Cr50 with old CCD version') dts_mode_works = self.cr50.servo_v4_supports_dts_mode() if not dts_mode_works: raise error.TestNAError('Plug in servo v4 type c cable into ccd ' 'port') # Asserting warm_reset will hold the AP in reset if the system uses # SYS_RST instead of PLT_RST. If the system uses PLT_RST, we have to # hold the EC in reset to guarantee the device won't turn on during # open. # warm_reset doesn't interfere with rdd, so it's best to use that when # possible. self.reset_signal = ('cold_reset' if self.cr50.get_board_properties() & self.PLT_RST else 'warm_reset') logging.info('Using %r for reset', self.reset_signal) self.fast_open(enable_testlab=True) # make sure password is cleared. self.cr50.send_command('ccd reset') self.cr50.get_ccd_info() # You can only open cr50 from the console if a password is set. Set # a password, so we can use it to open cr50 while the AP is off. self.set_ccd_password(self.PASSWORD) self.changed_dut_state = True self.assert_reset = True if not self.reset_device_get_deep_sleep_count(True): # Some devices can't tell the AP is off when the EC is off. Try # deep sleep with just the AP off. self.assert_reset = False # If deep sleep doesn't work at all, we can't run the test. if not self.reset_device_get_deep_sleep_count(True): raise error.TestNAError('Skipping test on device without deep ' 'sleep support') # We can't hold the ec in reset and enter deep sleep. Set the # capability so physical presence isn't required for open. logging.info("deep sleep doesn't work with EC in reset. skipping " "physical presence checks.") # set OpenNoLongPP so open won't require pressing the power button. self.cr50.set_cap('OpenNoLongPP', 'Always') else: logging.info('Physical presence can be used during open') def cleanup(self): """Make sure the device is on at the end of the test""" # If we got far enough to start changing the DUT power state, attempt to # turn the DUT back on and reenable the cr50 console. try: if self.changed_dut_state: self.restore_dut() finally: super(firmware_Cr50OpenWhileAPOff, self).cleanup() def restore_dut(self): """Turn on the device and reset cr50 Do a deep sleep reset to fix the cr50 console. Then turn the device on. Raises: TestFail if the cr50 console doesn't work """ logging.info('attempt cr50 console recovery') # The console may be hung. Run through reset manually, so we dont need # the console. self.turn_device('off') # Toggle dts mode to enter and exit deep sleep self.toggle_dts_mode() # Turn the device back on self.turn_device('on') # Verify the cr50 console responds to commands. try: logging.info(self.cr50.send_command_get_output('ccdstate', ['ccdstate.*>'])) except error.TestFail, e: if 'Timeout waiting for response' in e.message: raise error.TestFail('Could not restore Cr50 console') raise def turn_device(self, state): """Turn the device off or on. If we are testing ccd open fully, it will also assert device reset so power button presses wont turn on the AP """ # Make sure to release the device from reset before trying anything self.servo.set(self.reset_signal, 'off') time.sleep(self.SHORT_DELAY) # Turn off the AP if state == 'off': self.servo.set_nocheck('power_state', 'off') time.sleep(self.SHORT_DELAY) # Hold the EC in reset or release it from reset based on state if self.assert_reset: # The reset control is the inverse of device state, so convert the # state self.servo.set(reset_signal, 'on' if state == 'off' else # 'off') self.servo.set(self.reset_signal, 'on' if state == 'off' else 'off') time.sleep(self.SHORT_DELAY) # Turn on the AP if state == 'on': self.servo.power_short_press() def reset_device_get_deep_sleep_count(self, deep_sleep): """Reset the device. Use dts mode to enable deep sleep if requested. Args: deep_sleep: True if Cr50 should enter deep sleep Returns: The number of times Cr50 entered deep sleep during reset """ self.turn_device('off') # Do a deep sleep reset to restore the cr50 console. ds_count = self.deep_sleep_reset_get_count() if deep_sleep else 0 self.turn_device('on') return ds_count def toggle_dts_mode(self): """Toggle DTS mode to enable and disable deep sleep""" # We cant use cr50 ccd_disable/enable, because those uses the cr50 # console. Call servo_v4_dts_mode directly. self.servo.set_nocheck('servo_v4_dts_mode', 'off') time.sleep(self.SLEEP_DELAY) self.servo.set_nocheck('servo_v4_dts_mode', 'on') def deep_sleep_reset_get_count(self): """Toggle ccd to get to do a deep sleep reset Returns: The number of times cr50 entered deep sleep """ start_count = self.cr50.get_deep_sleep_count() # CCD is what's keeping Cr50 awake. Toggle DTS mode to turn off ccd # so cr50 will enter deep sleep self.toggle_dts_mode() # Return the number of times cr50 entered deep sleep. return self.cr50.get_deep_sleep_count() - start_count def try_ccd_open(self, cr50_reset): """Try 'ccd open' and make sure the console doesn't hang""" self.cr50.set_ccd_level('lock', self.PASSWORD) try: self.turn_device('off') if cr50_reset: if not self.deep_sleep_reset_get_count(): raise error.TestFail('Did not detect a cr50 reset') # Verify ccd open self.cr50.set_ccd_level('open', self.PASSWORD) finally: self.restore_dut() def run_once(self): """Turn off the AP and try ccd open.""" self.try_ccd_open(False) self.try_ccd_open(True)