| # 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: True if |wake_source| is a valid wake source for this specific |
| DUT |
| """ |
| 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) |
| 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): |
| 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))) |