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 5import logging 6import time 7 8from autotest_lib.client.common_lib import enum, error 9from autotest_lib.server import test 10from autotest_lib.server.cros.dark_resume_utils import DarkResumeUtils 11from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig 12from autotest_lib.server.cros.servo import chrome_ec 13 14 15# Possible states base can be forced into. 16BASE_STATE = enum.Enum('ATTACH', 'DETACH', 'RESET') 17 18 19 # List of wake sources expected to cause a full resume. 20FULL_WAKE_SOURCES = ['PWR_BTN', 'LID_OPEN', 'BASE_ATTACH', 21 'BASE_DETACH', 'INTERNAL_KB'] 22 23# Max time taken by the system to resume. 24RESUME_DURATION_SECS = 5 25 26# Time in future after which RTC goes off. 27RTC_WAKE_SECS = 30 28 29# Max time taken by the system to suspend. 30SUSPEND_DURATION_SECS = 5 31 32# Time to allow lid transition to take effect. 33WAIT_TIME_LID_TRANSITION_SECS = 5 34 35 36class 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