Home | History | Annotate | Download | only in power_WakeSources
      1 # Copyright 2018 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import time
      7 
      8 from autotest_lib.client.common_lib import enum, error
      9 from autotest_lib.server import test
     10 from autotest_lib.server.cros.dark_resume_utils import DarkResumeUtils
     11 from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
     12 from autotest_lib.server.cros.servo import chrome_ec
     13 
     14 
     15 # Possible states base can be forced into.
     16 BASE_STATE = enum.Enum('ATTACH', 'DETACH', 'RESET')
     17 
     18 
     19  # List of wake sources expected to cause a full resume.
     20 FULL_WAKE_SOURCES = ['PWR_BTN', 'LID_OPEN', 'BASE_ATTACH',
     21                      'BASE_DETACH', 'INTERNAL_KB']
     22 
     23 # Max time taken by the system to resume.
     24 RESUME_DURATION_SECS = 5
     25 
     26 # Time in future after which RTC goes off.
     27 RTC_WAKE_SECS = 30
     28 
     29 # Max time taken by the system to suspend.
     30 SUSPEND_DURATION_SECS = 5
     31 
     32 # Time to allow lid transition to take effect.
     33 WAIT_TIME_LID_TRANSITION_SECS = 5
     34 
     35 
     36 class power_WakeSources(test.test):
     37     """
     38     Verify that wakes from input devices can trigger a full
     39     resume. Currently tests :
     40         1. power button
     41         2. lid open
     42         3. base attach
     43         4. base detach
     44 
     45     Also tests RTC triggers a dark resume.
     46 
     47     """
     48     version = 1
     49 
     50     def _after_resume(self, wake_source):
     51         """Cleanup to perform after resuming the device.
     52 
     53         @param wake_source: Wake source that has been tested.
     54         """
     55         if wake_source in ['BASE_ATTACH', 'BASE_DETACH']:
     56             self._force_base_state(BASE_STATE.RESET)
     57 
     58     def _before_suspend(self, wake_source):
     59         """Prep before suspend.
     60 
     61         @param wake_source: Wake source that is going to be tested.
     62 
     63         @return: Boolean, whether _before_suspend action is successful.
     64         """
     65         if wake_source == 'BASE_ATTACH':
     66             # Force detach before suspend so that attach won't be ignored.
     67             return self._force_base_state(BASE_STATE.DETACH)
     68         if wake_source == 'BASE_DETACH':
     69             # Force attach before suspend so that detach won't be ignored.
     70             return self._force_base_state(BASE_STATE.ATTACH)
     71         if wake_source == 'LID_OPEN':
     72             # Set the power policy for lid closed action to suspend.
     73             return self._host.run(
     74                 'set_power_policy --lid_closed_action suspend',
     75                 ignore_status=True).exit_status == 0
     76         return True
     77 
     78     def _force_base_state(self, base_state):
     79         """Send EC command to force the |base_state|.
     80 
     81         @param base_state: State to force base to. One of |BASE_STATE| enum.
     82 
     83         @return: False if the command does not exist in the current EC build.
     84 
     85         @raise error.TestFail : If base state change fails.
     86         """
     87         ec_cmd = 'basestate '
     88         ec_arg = {
     89             BASE_STATE.ATTACH: 'a',
     90             BASE_STATE.DETACH: 'd',
     91             BASE_STATE.RESET: 'r'
     92         }
     93 
     94         ec_cmd += ec_arg[base_state]
     95 
     96         try:
     97             self._ec.send_command(ec_cmd)
     98         except error.TestFail as e:
     99             if 'No control named' in str(e):
    100                 # Since the command is added recently, this might not exist on
    101                 # every board.
    102                 logging.warning('basestate command does not exist on the EC. '
    103                                 'Please verify the base state manually.')
    104                 return False
    105             else:
    106                 raise e
    107         return True
    108 
    109     def _is_valid_wake_source(self, wake_source):
    110         """Check if |wake_source| is valid for DUT.
    111 
    112         @param wake_source: wake source to verify.
    113         @return: False if |wake_source| is not valid for DUT, True otherwise
    114         """
    115         if wake_source.startswith('BASE'):
    116             if self._host.run('which hammerd', ignore_status=True).\
    117                 exit_status == 0:
    118                 # Smoke test to see if EC has support to reset base.
    119                 return self._force_base_state(BASE_STATE.RESET)
    120             else:
    121                 return False
    122         if wake_source == 'LID_OPEN':
    123             return self._dr_utils.host_has_lid()
    124         if wake_source == 'INTERNAL_KB':
    125             return self._faft_config.has_keyboard
    126         return True
    127 
    128     def _test_full_wake(self, wake_source):
    129         """Test if |wake_source| triggers a full resume.
    130 
    131         @param wake_source: wake source to test. One of |FULL_WAKE_SOURCES|.
    132         @return: True, if we are able to successfully test the |wake source|
    133             triggers a full wake.
    134         """
    135         is_success = True
    136         logging.info('Testing wake by %s triggers a '
    137                      'full wake when dark resume is enabled.', wake_source)
    138         if not self._before_suspend(wake_source):
    139             logging.error('Before suspend action failed for %s', wake_source)
    140             is_success = False
    141         else:
    142             count_before = self._dr_utils.count_dark_resumes()
    143             with self._dr_utils.suspend() as _:
    144                 logging.info('DUT suspended! Waiting to resume...')
    145                 # Wait at least |SUSPEND_DURATION_SECS| secs for the kernel to
    146                 # fully suspend.
    147                 time.sleep(SUSPEND_DURATION_SECS)
    148                 self._trigger_wake(wake_source)
    149                 # Wait at least |RESUME_DURATION_SECS| secs for the device to
    150                 # resume.
    151                 time.sleep(RESUME_DURATION_SECS)
    152 
    153                 if not self._host.is_up():
    154                     logging.error('Device did not resume from suspend for %s',
    155                                   wake_source)
    156                     is_success = False
    157 
    158             count_after = self._dr_utils.count_dark_resumes()
    159             if count_before != count_after:
    160                 logging.error('%s caused a dark resume.', wake_source)
    161                 is_success = False
    162         self._after_resume(wake_source)
    163         return is_success
    164 
    165     def _test_rtc(self):
    166         """Suspend the device and test if RTC triggers a dark_resume.
    167 
    168         @return boolean, true if RTC alarm caused a dark resume.
    169         """
    170 
    171         logging.info('Testing RTC triggers dark resume when enabled.')
    172 
    173         count_before = self._dr_utils.count_dark_resumes()
    174         with self._dr_utils.suspend(RTC_WAKE_SECS) as _:
    175             logging.info('DUT suspended! Waiting to resume...')
    176             time.sleep(SUSPEND_DURATION_SECS + RTC_WAKE_SECS +
    177                        RESUME_DURATION_SECS)
    178 
    179             if not self._host.is_up():
    180                 logging.error('Device did not resume from suspend for RTC')
    181                 return False
    182 
    183         count_after = self._dr_utils.count_dark_resumes()
    184         if count_before != count_after - 1:
    185             logging.error(' RTC did not cause a dark resume.'
    186                           'count before = %d, count after = %d',
    187                           count_before, count_after)
    188             return False
    189         return True
    190 
    191     def _trigger_wake(self, wake_source):
    192         """Trigger wake using the given |wake_source|.
    193 
    194         @param wake_source : wake_source that is being tested.
    195             One of |FULL_WAKE_SOURCES|.
    196         """
    197         if wake_source == 'PWR_BTN':
    198             self._host.servo.power_short_press()
    199         elif wake_source == 'LID_OPEN':
    200             self._host.servo.lid_close()
    201             time.sleep(WAIT_TIME_LID_TRANSITION_SECS)
    202             self._host.servo.lid_open()
    203         elif wake_source == 'BASE_ATTACH':
    204             self._force_base_state(BASE_STATE.ATTACH)
    205         elif wake_source == 'BASE_DETACH':
    206             self._force_base_state(BASE_STATE.DETACH)
    207         elif wake_source == 'INTERNAL_KB':
    208             self._host.servo.ctrl_key()
    209 
    210     def cleanup(self):
    211         """cleanup."""
    212         self._dr_utils.stop_resuspend_on_dark_resume(False)
    213         self._dr_utils.teardown()
    214 
    215     def initialize(self, host):
    216         """Initialize wake sources tests.
    217 
    218         @param host: Host on which the test will be run.
    219         """
    220         self._host = host
    221         self._dr_utils = DarkResumeUtils(host)
    222         self._dr_utils.stop_resuspend_on_dark_resume()
    223         self._ec = chrome_ec.ChromeEC(self._host.servo)
    224         self._faft_config = FAFTConfig(self._host.get_platform())
    225 
    226     def run_once(self):
    227         """Body of the test."""
    228 
    229         test_ws = set(ws for ws in FULL_WAKE_SOURCES if \
    230             self._is_valid_wake_source(ws))
    231         passed_ws = set(ws for ws in test_ws if self._test_full_wake(ws))
    232         failed_ws = test_ws.difference(passed_ws)
    233         skipped_ws = set(FULL_WAKE_SOURCES).difference(test_ws)
    234 
    235         if self._test_rtc():
    236             passed_ws.add('RTC')
    237         else:
    238             failed_ws.add('RTC')
    239         if len(passed_ws):
    240             logging.info('[%s] woke the device as expected.',
    241                          ''.join(str(elem) + ', ' for elem in passed_ws))
    242         if skipped_ws:
    243             logging.info('[%s] are not wake sources on this platform. '
    244                          'Please test manually if not the case.',
    245                          ''.join(str(elem) + ', ' for elem in skipped_ws))
    246 
    247         if len(failed_ws):
    248             raise error.TestFail(
    249                 '[%s] wake sources did not behave as expected.'
    250                 % (''.join(str(elem) + ', ' for elem in failed_ws)))
    251