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 import servo_keyboard_utils 11from autotest_lib.server.cros.dark_resume_utils import DarkResumeUtils 12from autotest_lib.server.cros.faft.utils.config import Config as FAFTConfig 13from autotest_lib.server.cros.servo import chrome_ec 14 15 16# Possible states base can be forced into. 17BASE_STATE = enum.Enum('ATTACH', 'DETACH', 'RESET') 18 19# Possible states for tablet mode as defined in common/tablet_mode.c via 20# crrev.com/c/1797370. 21TABLET_MODE = enum.Enum('ON', 'OFF', 'RESET') 22 23# List of wake sources expected to cause a full resume. 24FULL_WAKE_SOURCES = [ 25 'PWR_BTN', 'LID_OPEN', 'BASE_ATTACH', 'BASE_DETACH', 'INTERNAL_KB', 26 'USB_KB', 'TABLET_MODE_ON', 'TABLET_MODE_OFF' 27] 28 29# Max time taken by the device to resume. This gives enough time for the device 30# to establish network connection with the autotest server 31SECS_FOR_RESUMING = 15 32 33# Time in future after which RTC goes off when testing other wake sources. 34BACKUP_RTC_SECS = 60 35 36# Time in future after which RTC goes off when testing wake due to RTC alarm. 37RTC_WAKE_SECS = 10 38 39# Max time taken by the device to suspend. This includes the time powerd takes 40# trigger the suspend after receiving the suspend request from autotest script. 41SECS_FOR_SUSPENDING = 20 42 43# Time to allow lid transition to take effect. 44WAIT_TIME_LID_TRANSITION_SECS = 5 45 46# Time to wait for the DUT to see USB keyboard after restting the Atmega USB 47# emulator on Servo. 48USB_PRESENT_DELAY = 1 49 50 51class power_WakeSources(test.test): 52 """ 53 Verify that wakes from input devices can trigger a full 54 resume. Currently tests : 55 1. power button 56 2. lid open 57 3. base attach 58 4. base detach 59 60 Also tests RTC triggers a dark resume. 61 62 """ 63 version = 1 64 65 def _after_resume(self, wake_source): 66 """Cleanup to perform after resuming the device. 67 68 @param wake_source: Wake source that has been tested. 69 """ 70 if wake_source in ['BASE_ATTACH', 'BASE_DETACH']: 71 self._force_base_state(BASE_STATE.RESET) 72 if wake_source in ['TABLET_MODE_ON', 'TABLET_MODE_OFF']: 73 self._force_tablet_mode(TABLET_MODE.RESET) 74 75 def _before_suspend(self, wake_source): 76 """Prep before suspend. 77 78 @param wake_source: Wake source that is going to be tested. 79 80 @return: Boolean, whether _before_suspend action is successful. 81 """ 82 if wake_source == 'BASE_ATTACH': 83 # Force detach before suspend so that attach won't be ignored. 84 self._force_base_state(BASE_STATE.DETACH) 85 return True 86 if wake_source == 'BASE_DETACH': 87 # Force attach before suspend so that detach won't be ignored. 88 self._force_base_state(BASE_STATE.ATTACH) 89 return True 90 if wake_source == 'LID_OPEN': 91 # Set the power policy for lid closed action to suspend. 92 return self._host.run( 93 'set_power_policy --lid_closed_action suspend', 94 ignore_status=True).exit_status == 0 95 if wake_source == 'USB_KB': 96 # Initialize USB keyboard. 97 self._host.servo.set_nocheck('init_usb_keyboard', 'on') 98 return True 99 if wake_source == 'TABLET_MODE_ON': 100 self._force_tablet_mode(TABLET_MODE.OFF) 101 return True 102 if wake_source == 'TABLET_MODE_OFF': 103 self._force_tablet_mode(TABLET_MODE.ON) 104 return True 105 return True 106 107 def _force_tablet_mode(self, mode): 108 """Send EC command to force the tablet mode. 109 110 @param mode: mode to force. One of the |TABLET_MODE| enum. 111 """ 112 ec_cmd = 'tabletmode ' 113 ec_arg = { 114 TABLET_MODE.ON: 'on', 115 TABLET_MODE.OFF: 'off', 116 TABLET_MODE.RESET: 'r' 117 } 118 119 ec_cmd += ec_arg[mode] 120 self._ec.send_command(ec_cmd) 121 122 def _force_base_state(self, base_state): 123 """Send EC command to force the |base_state|. 124 125 @param base_state: State to force base to. One of |BASE_STATE| enum. 126 """ 127 ec_cmd = 'basestate ' 128 ec_arg = { 129 BASE_STATE.ATTACH: 'a', 130 BASE_STATE.DETACH: 'd', 131 BASE_STATE.RESET: 'r' 132 } 133 134 ec_cmd += ec_arg[base_state] 135 self._ec.send_command(ec_cmd) 136 137 def _is_valid_wake_source(self, wake_source): 138 """Check if |wake_source| is valid for DUT. 139 140 @param wake_source: wake source to verify. 141 @return: False if |wake_source| is not valid for DUT, True otherwise 142 """ 143 if wake_source.startswith('BASE'): 144 return self._ec.has_command('basestate') 145 if wake_source.startswith('TABLET_MODE'): 146 return self._ec.has_command('tabletmode') 147 if wake_source == 'LID_OPEN': 148 return self._dr_utils.host_has_lid() 149 if wake_source == 'INTERNAL_KB': 150 return self._faft_config.has_keyboard 151 if wake_source == 'USB_KB': 152 # Initialize USB keyboard. 153 self._host.servo.set_nocheck('init_usb_keyboard', 'on') 154 time.sleep(USB_PRESENT_DELAY) 155 # Check if DUT can see a wake capable Atmel USB keyboard. 156 if servo_keyboard_utils.is_servo_usb_keyboard_present( 157 self._host): 158 if servo_keyboard_utils.is_servo_usb_wake_capable( 159 self._host): 160 return True 161 else: 162 logging.warning( 163 'Atmel USB keyboard does not have wake capability.' 164 ' Please run firmware_FlashServoKeyboardMap Autotest ' 165 'to update the Atmel firmware.') 166 return False 167 else: 168 logging.warning( 169 'DUT cannot see a Atmel USB keyboard. ' 170 ' Please plug in USB C charger into Servo if using V4.') 171 172 return False 173 174 return True 175 176 def _test_full_wake(self, wake_source): 177 """Test if |wake_source| triggers a full resume. 178 179 @param wake_source: wake source to test. One of |FULL_WAKE_SOURCES|. 180 @return: True, if we are able to successfully test the |wake source| 181 triggers a full wake. 182 """ 183 is_success = True 184 logging.info( 185 'Testing wake by %s triggers a ' 186 'full wake when dark resume is enabled.', wake_source) 187 if not self._before_suspend(wake_source): 188 logging.error('Before suspend action failed for %s', wake_source) 189 is_success = False 190 else: 191 count_before = self._dr_utils.count_dark_resumes() 192 self._dr_utils.suspend(BACKUP_RTC_SECS) 193 logging.info('DUT suspended! Waiting to resume...') 194 # Wait at least |SECS_FOR_SUSPENDING| secs for the kernel to 195 # fully suspend. 196 time.sleep(SECS_FOR_SUSPENDING) 197 self._trigger_wake(wake_source) 198 # Wait at least |SECS_FOR_RESUMING| secs for the device to 199 # resume. 200 time.sleep(SECS_FOR_RESUMING) 201 202 if not self._host.is_up_fast(): 203 logging.error('Device did not resume from suspend for %s.' 204 ' Waiting for backup RTC to wake the system.', 205 wake_source) 206 time.sleep(BACKUP_RTC_SECS - 207 SECS_FOR_SUSPENDING - SECS_FOR_RESUMING) 208 is_success = False 209 if not self._host.is_up(): 210 raise error.TestFail( 211 'Device failed to wakeup from backup RTC.') 212 213 count_after = self._dr_utils.count_dark_resumes() 214 if is_success and count_before != count_after: 215 logging.error('%s caused a dark resume.', wake_source) 216 is_success = False 217 elif is_success: 218 logging.info('%s caused a full resume.', wake_source) 219 self._after_resume(wake_source) 220 return is_success 221 222 def _test_rtc(self): 223 """Suspend the device and test if RTC triggers a dark_resume. 224 225 @return boolean, true if RTC alarm caused a dark resume. 226 """ 227 228 logging.info('Testing RTC triggers dark resume when enabled.') 229 230 count_before = self._dr_utils.count_dark_resumes() 231 self._dr_utils.suspend(SECS_FOR_SUSPENDING + RTC_WAKE_SECS) 232 logging.info('DUT suspended! Waiting to resume...') 233 time.sleep(SECS_FOR_SUSPENDING + RTC_WAKE_SECS + 234 SECS_FOR_RESUMING) 235 236 if not self._host.is_up(): 237 logging.error('Device did not resume from suspend for RTC') 238 return False 239 240 count_after = self._dr_utils.count_dark_resumes() 241 if count_before != count_after - 1: 242 logging.error( 243 'RTC did not cause a dark resume.' 244 'count before = %d, count after = %d', count_before, 245 count_after) 246 return False 247 return True 248 249 def _trigger_wake(self, wake_source): 250 """Trigger wake using the given |wake_source|. 251 252 @param wake_source : wake_source that is being tested. 253 One of |FULL_WAKE_SOURCES|. 254 """ 255 if wake_source == 'PWR_BTN': 256 self._host.servo.power_short_press() 257 elif wake_source == 'LID_OPEN': 258 self._host.servo.lid_close() 259 time.sleep(WAIT_TIME_LID_TRANSITION_SECS) 260 self._host.servo.lid_open() 261 elif wake_source == 'BASE_ATTACH': 262 self._force_base_state(BASE_STATE.ATTACH) 263 elif wake_source == 'BASE_DETACH': 264 self._force_base_state(BASE_STATE.DETACH) 265 elif wake_source == 'TABLET_MODE_ON': 266 self._force_tablet_mode(TABLET_MODE.ON) 267 elif wake_source == 'TABLET_MODE_OFF': 268 self._force_tablet_mode(TABLET_MODE.OFF) 269 elif wake_source == 'INTERNAL_KB': 270 self._host.servo.ctrl_key() 271 elif wake_source == 'USB_KB': 272 self._host.servo.set_nocheck('usb_keyboard_enter_key', '10') 273 274 def cleanup(self): 275 """cleanup.""" 276 self._dr_utils.stop_resuspend_on_dark_resume(False) 277 self._dr_utils.teardown() 278 279 def initialize(self, host): 280 """Initialize wake sources tests. 281 282 @param host: Host on which the test will be run. 283 """ 284 self._host = host 285 self._dr_utils = DarkResumeUtils(host) 286 self._dr_utils.stop_resuspend_on_dark_resume() 287 self._ec = chrome_ec.ChromeEC(self._host.servo) 288 self._faft_config = FAFTConfig(self._host.get_platform()) 289 290 def run_once(self): 291 """Body of the test.""" 292 293 test_ws = set( 294 ws for ws in FULL_WAKE_SOURCES if self._is_valid_wake_source(ws)) 295 passed_ws = set(ws for ws in test_ws if self._test_full_wake(ws)) 296 failed_ws = test_ws.difference(passed_ws) 297 skipped_ws = set(FULL_WAKE_SOURCES).difference(test_ws) 298 299 if self._test_rtc(): 300 passed_ws.add('RTC') 301 else: 302 failed_ws.add('RTC') 303 test_keyval = {} 304 305 for ws in passed_ws: 306 test_keyval.update({ws: 'PASS'}) 307 for ws in failed_ws: 308 test_keyval.update({ws: 'FAIL'}) 309 for ws in skipped_ws: 310 test_keyval.update({ws: 'SKIPPED'}) 311 self.write_test_keyval(test_keyval) 312 313 if len(passed_ws): 314 logging.info('[%s] woke the device as expected.', 315 ''.join(str(elem) + ', ' for elem in passed_ws)) 316 if skipped_ws: 317 logging.info( 318 '[%s] are not wake sources on this platform. ' 319 'Please test manually if not the case.', 320 ''.join(str(elem) + ', ' for elem in skipped_ws)) 321 322 if len(failed_ws): 323 raise error.TestFail( 324 '[%s] wake sources did not behave as expected.' % 325 (''.join(str(elem) + ', ' for elem in failed_ws))) 326