blob: e2a55305cfb761f5c91eb709a276ba5e4bbe92f9 [file] [log] [blame] [edit]
# Copyright 2017 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 difflib
import logging
import time
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import cr50_utils, tpm_utils
from autotest_lib.server import autotest
from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
class firmware_Cr50DeepSleepStress(FirmwareTest):
"""Verify cr50 deep sleep after running power_SuspendStress.
Cr50 should enter deep sleep every suspend. Verify that by checking the
idle deep sleep count.
@param suspend_count: The number of times to reboot or suspend the device.
@param reset_type: a str with the cycle type: 'mem' or 'reboot'
"""
version = 1
SLEEP_DELAY = 20
MIN_RESUME = 15
MIN_SUSPEND = 15
MEM = 'mem'
# Initialize the FWMP with a non-zero value. Use 100, because it's an
# unused flag and it wont do anything like lock out dev mode or ccd.
FWMP_FLAGS = '0x100'
def initialize(self, host, cmdline_args, suspend_count, reset_type):
"""Make sure the test is running with access to the cr50 console"""
self.host = host
super(firmware_Cr50DeepSleepStress, self).initialize(host, cmdline_args)
if not hasattr(self, 'cr50'):
raise error.TestNAError('Test can only be run on devices with '
'access to the Cr50 console')
if self.servo.main_device_is_ccd():
raise error.TestNAError('deep sleep tests can only be run with a '
'servo flex')
# Reset the device
self.servo.get_power_state_controller().reset()
# Save the original version, so we can make sure cr50 doesn't rollback.
self.original_cr50_version = self.cr50.get_active_version_info()
def cleanup(self):
"""Clear the fwmp."""
try:
self.clear_fwmp()
finally:
super(firmware_Cr50DeepSleepStress, self).cleanup()
def create_fwmp(self):
"""Create the FWMP."""
self.clear_fwmp()
# Clear the TPM owner, so we can set the fwmp.
tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True)
logging.info('Setting FWMP flags to %s', self.FWMP_FLAGS)
autotest.Autotest(self.host).run_test('firmware_SetFWMP',
flags=self.FWMP_FLAGS, fwmp_cleared=True,
check_client_result=True)
if self.fwmp_is_cleared():
raise error.TestError('Unable to create the FWMP')
def check_fwmp(self):
"""Returns an error message if the fwmp doesn't exist."""
if self.fwmp_is_cleared():
return 'FWMP was lost during test'
logging.info('No issues detected with the FWMP')
def check_cr50_version(self, expected_ver):
"""Return an error message if the version changed running the test."""
version = self.cr50.get_active_version_info()
logging.info('running %s', version)
if version != expected_ver:
return 'version changed from %s to %s' % (expected_ver, version)
def run_reboots(self, suspend_count):
"""Reboot the device the requested number of times
@param suspend_count: the number of times to reboot the device.
"""
cr50_dev_mode = self.cr50.in_dev_mode()
# Disable CCD so Cr50 can enter deep sleep
self.cr50.ccd_disable()
self.cr50.clear_deep_sleep_count()
rv = self.check_cr50_deep_sleep(0)
if rv:
raise error.TestError('Issue setting up test %s' % rv)
errors = []
for i in range(suspend_count):
# Power off the device
self.servo.get_power_state_controller().power_off()
time.sleep(self.MIN_SUSPEND)
# Power on the device
self.servo.power_short_press()
time.sleep(self.MIN_RESUME)
rv = self.check_cr50_deep_sleep(i + 1)
if rv:
errors.append(rv)
# Make sure the device didn't boot into a different mode.
if self.cr50.in_dev_mode() != cr50_dev_mode:
errors.append('Switched out of %s mode' %
('dev' if cr50_dev_mode else 'normal'))
if errors:
msg = 'Reboot %d failed (%s)' % (i, ' and '.join(errors))
raise error.TestFail(msg)
def _dut_is_responsive(self):
"""Returns True if the DUT eventually responds"""
return self.host.ping_wait_up(180)
def wait_for_client_after_changing_ccd(self, enable):
"""Change CCD and wait for client.
@param enable: True to enable ccd. False to disable it.
@returns an error message
"""
if enable:
self.cr50.ccd_enable()
else:
self.cr50.ccd_disable()
# power suspend stress needs to ssh into the DUT. If ethernet goes
# down, raise a test error, so we can tell the difference between
# dts ethernet issues and the dut going down during the suspend stress.
if self._dut_is_responsive():
return
msg = 'DUT is not pingable after %sabling ccd' % ('en' if enable else
'dis')
logging.info(msg)
# TODO(b/135147658): Raise an error once CCD disable is fixed.
logging.info('Resetting DUT')
self.servo.get_power_state_controller().reset()
if not self._dut_is_responsive():
return msg
def run_suspend_resume(self, suspend_count):
"""Suspend the device the requested number of times
@param suspend_count: the number of times to suspend the device.
"""
# Disable CCD so Cr50 can enter deep sleep
rv = self.wait_for_client_after_changing_ccd(False)
if rv:
raise error.TestFail('Network connection issue %s', rv)
self.cr50.clear_deep_sleep_count()
rv = self.check_cr50_deep_sleep(0)
if rv:
raise error.TestError('Issue setting up test %s' % rv)
client_at = autotest.Autotest(self.host)
# Duration is set to 0, because it is required but unused when
# iterations is given.
client_at.run_test('power_SuspendStress', tag='idle',
duration=0,
min_suspend=self.MIN_SUSPEND,
min_resume=self.MIN_RESUME,
check_connection=False,
suspend_iterations=suspend_count,
suspend_state=self.MEM)
def check_cr50_deep_sleep(self, suspend_count):
"""Verify cr50 has entered deep sleep the correct number of times.
Also print ccdstate and sleepmask output to get some basic information
about the cr50 state.
- sleepmask will show what may be preventing cr50 from entering sleep.
- ccdstate will show what cr50 thinks the AP state is. If the AP is 'on'
cr50 won't enter deep sleep.
All of these functions log the state, so no need to log the return
values.
@param suspend_count: The number of suspends.
@returns a message describing errors found in the state
"""
exp_count = suspend_count if self._enters_deep_sleep else 0
act_count = self.cr50.get_deep_sleep_count()
logging.info('suspend %d: deep sleep count %d', act_count, exp_count)
self.cr50.get_sleepmask()
self.cr50.get_ccdstate()
hibernate = self.cr50.was_reset('RESET_FLAG_HIBERNATE')
errors = []
if exp_count and not hibernate:
errors.append('reset during suspend')
if exp_count != act_count:
errors.append('count mismatch DUT %d cr50 %d' % (exp_count,
act_count))
return ', '.join(errors) if errors else None
def check_flog_output(self, original_flog):
"""Check for new flog messages.
@param original_flog: the original flog output.
@returns an error message with the flog difference, if there are new
entries.
"""
new_flog = cr50_utils.DumpFlog(self.host).strip()
logging.info('New FLOG output:\n%s', new_flog)
diff = difflib.unified_diff(original_flog.splitlines(),
new_flog.splitlines())
line_diff = '\n'.join(diff)
if line_diff:
logging.info('FLOG output:\n%s', line_diff)
return 'New Flog messages (%s)' % ','.join(diff)
else:
logging.info('No new FLOG output')
def run_once(self, host, suspend_count, reset_type):
"""Verify deep sleep after suspending for the given number of cycles
The test either suspends to s3 or reboots the device depending on
reset_type. There are two valid reset types: mem and reboot. The test
will make sure that the device is off or in s3 long enough to ensure
Cr50 should be able to enter deep sleep. At the end of the test, it
checks that Cr50 entered deep sleep the same number of times it
suspended.
@param host: the host object representing the DUT.
@param suspend_count: The number of cycles to suspend or reboot the
device.
@param reset_type: a str with the cycle type: 'mem' or 'reboot'
"""
if reset_type not in ['reboot', 'mem']:
raise error.TestNAError('Invalid reset_type. Use "mem" or "reboot"')
if self.MIN_SUSPEND + self.MIN_RESUME < self.SLEEP_DELAY:
logging.info('Minimum suspend-resume cycle is %ds. This is '
'shorter than the Cr50 idle timeout. Cr50 may not '
'enter deep sleep every cycle',
self.MIN_SUSPEND + self.MIN_RESUME)
if not suspend_count:
raise error.TestFail('Need to provide non-zero suspend_count')
original_flog = cr50_utils.DumpFlog(self.host).strip()
logging.debug('Initial FLOG output:\n%s', original_flog)
# x86 devices should suspend once per reset. ARM will only suspend
# if the device enters s5.
if reset_type == 'reboot':
self._enters_deep_sleep = True
else:
is_arm = self.check_ec_capability(['arm'], suppress_warning=True)
self._enters_deep_sleep = not is_arm
self.create_fwmp()
main_error = None
try:
if reset_type == 'reboot':
self.run_reboots(suspend_count)
elif reset_type == 'mem':
self.run_suspend_resume(suspend_count)
except Exception, e:
main_error = e
errors = []
# Collect logs for debugging
# Autotest has some stages in between run_once and cleanup that may
# be run if the test succeeds. Do this here to make sure this is
# always run immediately after the suspend/resume cycles.
self.cr50.dump_nvmem()
# Reenable CCD. Reestablish network connection.
rv = self.wait_for_client_after_changing_ccd(True)
if rv:
errors.append(rv)
rv = self.check_flog_output(original_flog)
if rv:
errors.append(rv)
rv = self.check_fwmp()
if rv:
errors.append(rv)
rv = self.check_cr50_deep_sleep(suspend_count)
if rv:
errors.append(rv)
rv = self.check_cr50_version(self.original_cr50_version)
if rv:
errors.append(rv)
secondary_error = 'Suspend issues: %s' % ', '.join(errors)
if main_error:
logging.info(secondary_error)
raise main_error
if errors:
raise error.TestFail(secondary_error)