blob: 9315e29948dcdac727dc1ac7fc4a4f6d7b548e02 [file] [log] [blame]
# 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 enum, error
from autotest_lib.server import test
from autotest_lib.server.cros.dark_resume_utils import DarkResumeUtils
from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
from autotest_lib.server.cros.servo import chrome_ec
# Possible states base can be forced into.
BASE_STATE = enum.Enum('ATTACH', 'DETACH', 'RESET')
# List of wake sources expected to cause a full resume.
FULL_WAKE_SOURCES = ['PWR_BTN', 'LID_OPEN', 'BASE_ATTACH',
'BASE_DETACH', 'INTERNAL_KB']
# Max time taken by the system to resume.
RESUME_DURATION_SECS = 5
# Time in future after which RTC goes off.
RTC_WAKE_SECS = 30
# Max time taken by the system to suspend.
SUSPEND_DURATION_SECS = 5
# Time to allow lid transition to take effect.
WAIT_TIME_LID_TRANSITION_SECS = 5
class power_WakeSources(test.test):
"""
Verify that wakes from input devices can trigger a full
resume. Currently tests :
1. power button
2. lid open
3. base attach
4. base detach
Also tests RTC triggers a dark resume.
"""
version = 1
def _after_resume(self, wake_source):
"""Cleanup to perform after resuming the device.
@param wake_source: Wake source that has been tested.
"""
if wake_source in ['BASE_ATTACH', 'BASE_DETACH']:
self._force_base_state(BASE_STATE.RESET)
def _before_suspend(self, wake_source):
"""Prep before suspend.
@param wake_source: Wake source that is going to be tested.
@return: Boolean, whether _before_suspend action is successful.
"""
if wake_source == 'BASE_ATTACH':
# Force detach before suspend so that attach won't be ignored.
return self._force_base_state(BASE_STATE.DETACH)
if wake_source == 'BASE_DETACH':
# Force attach before suspend so that detach won't be ignored.
return self._force_base_state(BASE_STATE.ATTACH)
if wake_source == 'LID_OPEN':
# Set the power policy for lid closed action to suspend.
return self._host.run(
'set_power_policy --lid_closed_action suspend',
ignore_status=True).exit_status == 0
return True
def _force_base_state(self, base_state):
"""Send EC command to force the |base_state|.
@param base_state: State to force base to. One of |BASE_STATE| enum.
@return: False if the command does not exist in the current EC build.
@raise error.TestFail : If base state change fails.
"""
ec_cmd = 'basestate '
ec_arg = {
BASE_STATE.ATTACH: 'a',
BASE_STATE.DETACH: 'd',
BASE_STATE.RESET: 'r'
}
ec_cmd += ec_arg[base_state]
try:
self._ec.send_command(ec_cmd)
except error.TestFail as e:
if 'No control named' in str(e):
# Since the command is added recently, this might not exist on
# every board.
logging.warning('basestate command does not exist on the EC. '
'Please verify the base state manually.')
return False
else:
raise e
return True
def _is_valid_wake_source(self, wake_source):
"""Check if |wake_source| is valid for DUT.
@param wake_source: wake source to verify.
@return: False if |wake_source| is not valid for DUT, True otherwise
"""
if wake_source.startswith('BASE'):
if self._host.run('which hammerd', ignore_status=True).\
exit_status == 0:
# Smoke test to see if EC has support to reset base.
return self._force_base_state(BASE_STATE.RESET)
else:
return False
if wake_source == 'LID_OPEN':
return self._dr_utils.host_has_lid()
if wake_source == 'INTERNAL_KB':
return self._faft_config.has_keyboard
return True
def _test_full_wake(self, wake_source):
"""Test if |wake_source| triggers a full resume.
@param wake_source: wake source to test. One of |FULL_WAKE_SOURCES|.
@return: True, if we are able to successfully test the |wake source|
triggers a full wake.
"""
is_success = True
logging.info('Testing wake by %s triggers a '
'full wake when dark resume is enabled.', wake_source)
if not self._before_suspend(wake_source):
logging.error('Before suspend action failed for %s', wake_source)
is_success = False
else:
count_before = self._dr_utils.count_dark_resumes()
with self._dr_utils.suspend() as _:
logging.info('DUT suspended! Waiting to resume...')
# Wait at least |SUSPEND_DURATION_SECS| secs for the kernel to
# fully suspend.
time.sleep(SUSPEND_DURATION_SECS)
self._trigger_wake(wake_source)
# Wait at least |RESUME_DURATION_SECS| secs for the device to
# resume.
time.sleep(RESUME_DURATION_SECS)
if not self._host.is_up():
logging.error('Device did not resume from suspend for %s',
wake_source)
is_success = False
count_after = self._dr_utils.count_dark_resumes()
if count_before != count_after:
logging.error('%s caused a dark resume.', wake_source)
is_success = False
self._after_resume(wake_source)
return is_success
def _test_rtc(self):
"""Suspend the device and test if RTC triggers a dark_resume.
@return boolean, true if RTC alarm caused a dark resume.
"""
logging.info('Testing RTC triggers dark resume when enabled.')
count_before = self._dr_utils.count_dark_resumes()
with self._dr_utils.suspend(RTC_WAKE_SECS) as _:
logging.info('DUT suspended! Waiting to resume...')
time.sleep(SUSPEND_DURATION_SECS + RTC_WAKE_SECS +
RESUME_DURATION_SECS)
if not self._host.is_up():
logging.error('Device did not resume from suspend for RTC')
return False
count_after = self._dr_utils.count_dark_resumes()
if count_before != count_after - 1:
logging.error(' RTC did not cause a dark resume.'
'count before = %d, count after = %d',
count_before, count_after)
return False
return True
def _trigger_wake(self, wake_source):
"""Trigger wake using the given |wake_source|.
@param wake_source : wake_source that is being tested.
One of |FULL_WAKE_SOURCES|.
"""
if wake_source == 'PWR_BTN':
self._host.servo.power_short_press()
elif wake_source == 'LID_OPEN':
self._host.servo.lid_close()
time.sleep(WAIT_TIME_LID_TRANSITION_SECS)
self._host.servo.lid_open()
elif wake_source == 'BASE_ATTACH':
self._force_base_state(BASE_STATE.ATTACH)
elif wake_source == 'BASE_DETACH':
self._force_base_state(BASE_STATE.DETACH)
elif wake_source == 'INTERNAL_KB':
self._host.servo.ctrl_key()
def cleanup(self):
"""cleanup."""
self._dr_utils.stop_resuspend_on_dark_resume(False)
self._dr_utils.teardown()
def initialize(self, host):
"""Initialize wake sources tests.
@param host: Host on which the test will be run.
"""
self._host = host
self._dr_utils = DarkResumeUtils(host)
self._dr_utils.stop_resuspend_on_dark_resume()
self._ec = chrome_ec.ChromeEC(self._host.servo)
self._faft_config = FAFTConfig(self._host.get_platform())
def run_once(self):
"""Body of the test."""
test_ws = set(ws for ws in FULL_WAKE_SOURCES if \
self._is_valid_wake_source(ws))
passed_ws = set(ws for ws in test_ws if self._test_full_wake(ws))
failed_ws = test_ws.difference(passed_ws)
skipped_ws = set(FULL_WAKE_SOURCES).difference(test_ws)
if self._test_rtc():
passed_ws.add('RTC')
else:
failed_ws.add('RTC')
if len(passed_ws):
logging.info('[%s] woke the device as expected.',
''.join(str(elem) + ', ' for elem in passed_ws))
if skipped_ws:
logging.info('[%s] are not wake sources on this platform. '
'Please test manually if not the case.',
''.join(str(elem) + ', ' for elem in skipped_ws))
if len(failed_ws):
raise error.TestFail(
'[%s] wake sources did not behave as expected.'
% (''.join(str(elem) + ', ' for elem in failed_ws)))