| # 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 import servo_keyboard_utils |
| from autotest_lib.server.cros.dark_resume_utils import DarkResumeUtils |
| from autotest_lib.server.cros.faft.utils.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') |
| |
| # Possible states for tablet mode as defined in common/tablet_mode.c via |
| # crrev.com/c/1797370. |
| TABLET_MODE = enum.Enum('ON', 'OFF', 'RESET') |
| |
| # List of wake sources expected to cause a full resume. |
| FULL_WAKE_SOURCES = [ |
| 'PWR_BTN', 'LID_OPEN', 'BASE_ATTACH', 'BASE_DETACH', 'INTERNAL_KB', |
| 'USB_KB', 'TABLET_MODE_ON', 'TABLET_MODE_OFF' |
| ] |
| |
| # Max time taken by the device to resume. This gives enough time for the device |
| # to establish network connection with the autotest server |
| SECS_FOR_RESUMING = 15 |
| |
| # Time in future after which RTC goes off when testing other wake sources. |
| BACKUP_RTC_SECS = 60 |
| |
| # Time in future after which RTC goes off when testing wake due to RTC alarm. |
| RTC_WAKE_SECS = 10 |
| |
| # Max time taken by the device to suspend. This includes the time powerd takes |
| # trigger the suspend after receiving the suspend request from autotest script. |
| SECS_FOR_SUSPENDING = 20 |
| |
| # Time to allow lid transition to take effect. |
| WAIT_TIME_LID_TRANSITION_SECS = 5 |
| |
| # Time to wait for the DUT to see USB keyboard after restting the Atmega USB |
| # emulator on Servo. |
| USB_PRESENT_DELAY = 1 |
| |
| |
| 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) |
| if wake_source in ['TABLET_MODE_ON', 'TABLET_MODE_OFF']: |
| self._force_tablet_mode(TABLET_MODE.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. |
| self._force_base_state(BASE_STATE.DETACH) |
| return True |
| if wake_source == 'BASE_DETACH': |
| # Force attach before suspend so that detach won't be ignored. |
| self._force_base_state(BASE_STATE.ATTACH) |
| return True |
| 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 |
| if wake_source == 'USB_KB': |
| # Initialize USB keyboard. |
| self._host.servo.set_nocheck('init_usb_keyboard', 'on') |
| return True |
| if wake_source == 'TABLET_MODE_ON': |
| self._force_tablet_mode(TABLET_MODE.OFF) |
| return True |
| if wake_source == 'TABLET_MODE_OFF': |
| self._force_tablet_mode(TABLET_MODE.ON) |
| return True |
| return True |
| |
| def _force_tablet_mode(self, mode): |
| """Send EC command to force the tablet mode. |
| |
| @param mode: mode to force. One of the |TABLET_MODE| enum. |
| """ |
| ec_cmd = 'tabletmode ' |
| ec_arg = { |
| TABLET_MODE.ON: 'on', |
| TABLET_MODE.OFF: 'off', |
| TABLET_MODE.RESET: 'r' |
| } |
| |
| ec_cmd += ec_arg[mode] |
| self._ec.send_command(ec_cmd) |
| |
| 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. |
| """ |
| ec_cmd = 'basestate ' |
| ec_arg = { |
| BASE_STATE.ATTACH: 'a', |
| BASE_STATE.DETACH: 'd', |
| BASE_STATE.RESET: 'r' |
| } |
| |
| ec_cmd += ec_arg[base_state] |
| self._ec.send_command(ec_cmd) |
| |
| 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'): |
| return self._ec.has_command('basestate') |
| if wake_source.startswith('TABLET_MODE'): |
| return self._ec.has_command('tabletmode') |
| if wake_source == 'LID_OPEN': |
| return self._dr_utils.host_has_lid() |
| if wake_source == 'INTERNAL_KB': |
| return self._faft_config.has_keyboard |
| if wake_source == 'USB_KB': |
| # Initialize USB keyboard. |
| self._host.servo.set_nocheck('init_usb_keyboard', 'on') |
| time.sleep(USB_PRESENT_DELAY) |
| # Check if DUT can see a wake capable Atmel USB keyboard. |
| if servo_keyboard_utils.is_servo_usb_keyboard_present( |
| self._host): |
| if servo_keyboard_utils.is_servo_usb_wake_capable( |
| self._host): |
| return True |
| else: |
| logging.warning( |
| 'Atmel USB keyboard does not have wake capability.' |
| ' Please run firmware_FlashServoKeyboardMap Autotest ' |
| 'to update the Atmel firmware.') |
| return False |
| else: |
| logging.warning( |
| 'DUT cannot see a Atmel USB keyboard. ' |
| ' Please plug in USB C charger into Servo if using V4.') |
| |
| return False |
| |
| 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() |
| self._dr_utils.suspend(BACKUP_RTC_SECS) |
| logging.info('DUT suspended! Waiting to resume...') |
| # Wait at least |SECS_FOR_SUSPENDING| secs for the kernel to |
| # fully suspend. |
| time.sleep(SECS_FOR_SUSPENDING) |
| self._trigger_wake(wake_source) |
| # Wait at least |SECS_FOR_RESUMING| secs for the device to |
| # resume. |
| time.sleep(SECS_FOR_RESUMING) |
| |
| if not self._host.is_up_fast(): |
| logging.error('Device did not resume from suspend for %s.' |
| ' Waiting for backup RTC to wake the system.', |
| wake_source) |
| time.sleep(BACKUP_RTC_SECS - |
| SECS_FOR_SUSPENDING - SECS_FOR_RESUMING) |
| is_success = False |
| if not self._host.is_up(): |
| raise error.TestFail( |
| 'Device failed to wakeup from backup RTC.') |
| |
| count_after = self._dr_utils.count_dark_resumes() |
| if is_success and count_before != count_after: |
| logging.error('%s caused a dark resume.', wake_source) |
| is_success = False |
| elif is_success: |
| logging.info('%s caused a full resume.', wake_source) |
| 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() |
| self._dr_utils.suspend(SECS_FOR_SUSPENDING + RTC_WAKE_SECS) |
| logging.info('DUT suspended! Waiting to resume...') |
| time.sleep(SECS_FOR_SUSPENDING + RTC_WAKE_SECS + |
| SECS_FOR_RESUMING) |
| |
| 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 == 'TABLET_MODE_ON': |
| self._force_tablet_mode(TABLET_MODE.ON) |
| elif wake_source == 'TABLET_MODE_OFF': |
| self._force_tablet_mode(TABLET_MODE.OFF) |
| elif wake_source == 'INTERNAL_KB': |
| self._host.servo.ctrl_key() |
| elif wake_source == 'USB_KB': |
| self._host.servo.set_nocheck('usb_keyboard_enter_key', '10') |
| |
| 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') |
| test_keyval = {} |
| |
| for ws in passed_ws: |
| test_keyval.update({ws: 'PASS'}) |
| for ws in failed_ws: |
| test_keyval.update({ws: 'FAIL'}) |
| for ws in skipped_ws: |
| test_keyval.update({ws: 'SKIPPED'}) |
| self.write_test_keyval(test_keyval) |
| |
| 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))) |