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 autotest_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.power import servo_charger 14from autotest_lib.server.cros.servo import chrome_ec 15 16 17# Possible states base can be forced into. 18BASE_STATE = autotest_enum.AutotestEnum('ATTACH', 'DETACH', 'RESET') 19 20# Possible states for tablet mode as defined in common/tablet_mode.c via 21# crrev.com/c/1797370. 22TABLET_MODE = autotest_enum.AutotestEnum('ON', 'OFF', 'RESET') 23 24# List of wake sources expected to cause a full resume. 25FULL_WAKE_SOURCES = [ 26 'PWR_BTN', 'LID_OPEN', 'BASE_ATTACH', 'BASE_DETACH', 'INTERNAL_KB', 27 'USB_KB', 'TABLET_MODE_ON', 'TABLET_MODE_OFF' 28] 29 30# List of wake sources expected to cause a dark resume. 31DARK_RESUME_SOURCES = ['RTC', 'AC_CONNECTED', 'AC_DISCONNECTED'] 32 33# Max time taken by the device to resume. This gives enough time for the device 34# to establish network connection with the autotest server 35SECS_FOR_RESUMING = 15 36 37# Time in future after which RTC goes off when testing wake due to RTC alarm. 38RTC_WAKE_SECS = 20 39 40# Max time taken by the device to suspend. This includes the time powerd takes 41# trigger the suspend after receiving the suspend request from autotest script. 42SECS_FOR_SUSPENDING = 20 43 44# Time to allow lid transition to take effect. 45WAIT_TIME_LID_TRANSITION_SECS = 5 46 47# Time to wait for the DUT to see USB keyboard after restting the Atmega USB 48# emulator on Servo. 49USB_PRESENT_DELAY = 1 50 51 52class power_WakeSources(test.test): 53 """ 54 Verify that wakes from input devices can trigger a full 55 resume. Currently tests : 56 1. power button 57 2. lid open 58 3. base attach 59 4. base detach 60 61 Also tests that dark resume wake sources work as expected, such as: 62 1. RTC 63 2. AC_CONNECTED 64 3. AC_DISCONNECTED 65 66 """ 67 version = 1 68 69 def _after_resume(self, wake_source): 70 """Cleanup to perform after resuming the device. 71 72 @param wake_source: Wake source that has been tested. 73 """ 74 if wake_source in ['BASE_ATTACH', 'BASE_DETACH']: 75 self._force_base_state(BASE_STATE.RESET) 76 elif wake_source in ['TABLET_MODE_ON', 'TABLET_MODE_OFF']: 77 self._force_tablet_mode(TABLET_MODE.RESET) 78 elif wake_source in ['AC_CONNECTED', 'AC_DISCONNECTED']: 79 self._chg_manager.start_charging() 80 81 def _before_suspend(self, wake_source): 82 """Prep before suspend. 83 84 @param wake_source: Wake source that is going to be tested. 85 86 @return: Boolean, whether _before_suspend action is successful. 87 """ 88 if wake_source == 'BASE_ATTACH': 89 # Force detach before suspend so that attach won't be ignored. 90 self._force_base_state(BASE_STATE.DETACH) 91 elif wake_source == 'BASE_DETACH': 92 # Force attach before suspend so that detach won't be ignored. 93 self._force_base_state(BASE_STATE.ATTACH) 94 elif wake_source == 'LID_OPEN': 95 # Set the power policy for lid closed action to suspend. 96 return self._host.run( 97 'set_power_policy --lid_closed_action suspend', 98 ignore_status=True).exit_status == 0 99 elif wake_source == 'USB_KB': 100 # Initialize USB keyboard. 101 self._host.servo.set_nocheck('init_usb_keyboard', 'on') 102 elif wake_source == 'TABLET_MODE_ON': 103 self._force_tablet_mode(TABLET_MODE.OFF) 104 elif wake_source == 'TABLET_MODE_OFF': 105 self._force_tablet_mode(TABLET_MODE.ON) 106 elif wake_source == 'AC_CONNECTED': 107 self._chg_manager.stop_charging() 108 elif wake_source == 'AC_DISCONNECTED': 109 self._chg_manager.start_charging() 110 return True 111 112 def _force_tablet_mode(self, mode): 113 """Send EC command to force the tablet mode. 114 115 @param mode: mode to force. One of the |TABLET_MODE| enum. 116 """ 117 ec_cmd = 'tabletmode ' 118 ec_arg = { 119 TABLET_MODE.ON: 'on', 120 TABLET_MODE.OFF: 'off', 121 TABLET_MODE.RESET: 'r' 122 } 123 124 ec_cmd += ec_arg[mode] 125 self._ec.send_command(ec_cmd) 126 127 def _force_base_state(self, base_state): 128 """Send EC command to force the |base_state|. 129 130 @param base_state: State to force base to. One of |BASE_STATE| enum. 131 """ 132 ec_cmd = 'basestate ' 133 ec_arg = { 134 BASE_STATE.ATTACH: 'a', 135 BASE_STATE.DETACH: 'd', 136 BASE_STATE.RESET: 'r' 137 } 138 139 ec_cmd += ec_arg[base_state] 140 self._ec.send_command(ec_cmd) 141 142 def _is_valid_wake_source(self, wake_source): 143 """Check if |wake_source| is valid for DUT. 144 145 @param wake_source: wake source to verify. 146 @return: False if |wake_source| is not valid for DUT, True otherwise 147 """ 148 if wake_source in ['BASE_ATTACH', 'BASE_DETACH']: 149 return self._ec.has_command('basestate') 150 if wake_source in ['TABLET_MODE_ON', 'TABLET_MODE_OFF']: 151 return self._ec.has_command('tabletmode') 152 if wake_source == 'LID_OPEN': 153 return self._dr_utils.host_has_lid() 154 if wake_source == 'INTERNAL_KB': 155 return self._faft_config.has_keyboard 156 if wake_source == 'USB_KB': 157 # Initialize USB keyboard. 158 self._host.servo.set_nocheck('init_usb_keyboard', 'on') 159 time.sleep(USB_PRESENT_DELAY) 160 # Check if DUT can see a wake capable Atmel USB keyboard. 161 if servo_keyboard_utils.is_servo_usb_keyboard_present( 162 self._host): 163 if servo_keyboard_utils.is_servo_usb_wake_capable( 164 self._host): 165 return True 166 else: 167 logging.warning( 168 'Atmel USB keyboard does not have wake capability.' 169 ' Please run firmware_FlashServoKeyboardMap Autotest ' 170 'to update the Atmel firmware.') 171 return False 172 else: 173 logging.warning( 174 'DUT cannot see a Atmel USB keyboard. ' 175 ' Please plug in USB C charger into Servo if using V4.') 176 177 return False 178 if wake_source in ['AC_CONNECTED', 'AC_DISCONNECTED']: 179 if not self._chg_manager: 180 logging.warning( 181 'Unable to test AC connect/disconnect with this ' 182 'servo setup') 183 return False 184 # Check both the S0ix and S3 wake masks. 185 try: 186 s0ix_wake_mask = int(self._host.run( 187 'ectool hostevent get %d' % 188 chrome_ec.EC_HOST_EVENT_LAZY_WAKE_MASK_S0IX).stdout, 189 base=16) 190 except error.AutoservRunError as e: 191 s0ix_wake_mask = 0 192 logging.info( 193 '"ectool hostevent get" failed for s0ix wake mask with' 194 ' exception: %s', str(e)) 195 196 try: 197 s3_wake_mask = int(self._host.run( 198 'ectool hostevent get %d' % 199 chrome_ec.EC_HOST_EVENT_LAZY_WAKE_MASK_S3).stdout, 200 base=16) 201 except error.AutoservRunError as e: 202 s3_wake_mask = 0 203 logging.info( 204 '"ectool hostevent get" failed for s3 wake mask with' 205 ' exception: %s', str(e)) 206 207 wake_mask = s0ix_wake_mask | s3_wake_mask 208 supported = False 209 if wake_source == 'AC_CONNECTED': 210 supported = wake_mask & chrome_ec.HOSTEVENT_AC_CONNECTED 211 elif wake_source == 'AC_DISCONNECTED': 212 supported = wake_mask & chrome_ec.HOSTEVENT_AC_DISCONNECTED 213 214 if not supported: 215 logging.info( 216 '%s not supported. Platforms launched in 2020 or before' 217 ' may not require it. S0ix wake mask: 0x%x S3 wake' 218 ' mask: 0x%x', wake_source, s0ix_wake_mask, 219 s3_wake_mask) 220 return False 221 222 return True 223 224 def _test_wake(self, wake_source, full_wake): 225 """Test if |wake_source| triggers a full resume. 226 227 @param wake_source: wake source to test. One of |FULL_WAKE_SOURCES|. 228 @return: True, if we are able to successfully test the |wake source| 229 triggers a full wake. 230 """ 231 is_success = True 232 logging.info( 233 'Testing wake by %s triggers a ' 234 'full wake when dark resume is enabled.', wake_source) 235 if not self._before_suspend(wake_source): 236 logging.error('Before suspend action failed for %s', wake_source) 237 # Still run the _after_resume callback since we can do things like 238 # stop charging. 239 self._after_resume(wake_source) 240 return False 241 242 count_before = self._dr_utils.count_dark_resumes() 243 self._dr_utils.suspend(SECS_FOR_SUSPENDING + RTC_WAKE_SECS) 244 logging.info('DUT suspended! Waiting to resume...') 245 # Wait at least |SECS_FOR_SUSPENDING| secs for the kernel to 246 # fully suspend. 247 time.sleep(SECS_FOR_SUSPENDING) 248 self._trigger_wake(wake_source) 249 # Wait at least |SECS_FOR_RESUMING| secs for the device to 250 # resume. 251 time.sleep(SECS_FOR_RESUMING) 252 253 if not self._host.is_up_fast(): 254 logging.error( 255 'Device did not resume from suspend for %s.' 256 ' Waking system with power button then RTC.', wake_source) 257 self._trigger_wake('PWR_BTN') 258 self._after_resume(wake_source) 259 if not self._host.is_up(): 260 raise error.TestFail( 261 'Device failed to wakeup from backup wake sources' 262 ' (power button and RTC).') 263 264 return False 265 266 count_after = self._dr_utils.count_dark_resumes() 267 if full_wake: 268 if count_before != count_after: 269 logging.error('%s incorrectly caused a dark resume.', 270 wake_source) 271 is_success = False 272 elif is_success: 273 logging.info('%s caused a full resume.', wake_source) 274 else: 275 if count_before == count_after: 276 logging.error('%s incorrectly caused a full resume.', 277 wake_source) 278 is_success = False 279 elif is_success: 280 logging.info('%s caused a dark resume.', wake_source) 281 282 self._after_resume(wake_source) 283 return is_success 284 285 def _trigger_wake(self, wake_source): 286 """Trigger wake using the given |wake_source|. 287 288 @param wake_source : wake_source that is being tested. 289 One of |FULL_WAKE_SOURCES|. 290 """ 291 if wake_source == 'PWR_BTN': 292 self._host.servo.power_short_press() 293 elif wake_source == 'LID_OPEN': 294 self._host.servo.lid_close() 295 time.sleep(WAIT_TIME_LID_TRANSITION_SECS) 296 self._host.servo.lid_open() 297 elif wake_source == 'BASE_ATTACH': 298 self._force_base_state(BASE_STATE.ATTACH) 299 elif wake_source == 'BASE_DETACH': 300 self._force_base_state(BASE_STATE.DETACH) 301 elif wake_source == 'TABLET_MODE_ON': 302 self._force_tablet_mode(TABLET_MODE.ON) 303 elif wake_source == 'TABLET_MODE_OFF': 304 self._force_tablet_mode(TABLET_MODE.OFF) 305 elif wake_source == 'INTERNAL_KB': 306 self._host.servo.ctrl_key() 307 elif wake_source == 'USB_KB': 308 self._host.servo.set_nocheck('usb_keyboard_enter_key', '10') 309 elif wake_source == 'RTC': 310 # The RTC will wake on its own. We just need to wait 311 time.sleep(RTC_WAKE_SECS) 312 elif wake_source == 'AC_CONNECTED': 313 self._chg_manager.start_charging() 314 elif wake_source == 'AC_DISCONNECTED': 315 self._chg_manager.stop_charging() 316 317 def cleanup(self): 318 """cleanup.""" 319 self._dr_utils.stop_resuspend_on_dark_resume(False) 320 self._dr_utils.teardown() 321 322 def initialize(self, host): 323 """Initialize wake sources tests. 324 325 @param host: Host on which the test will be run. 326 """ 327 self._host = host 328 self._dr_utils = DarkResumeUtils(host) 329 self._dr_utils.stop_resuspend_on_dark_resume() 330 self._ec = chrome_ec.ChromeEC(self._host.servo) 331 self._faft_config = FAFTConfig(self._host.get_platform()) 332 self._kstr = host.get_kernel_version() 333 # TODO(b/168939843) : Look at implementing AC plug/unplug w/ non-PD RPMs 334 # in the lab. 335 try: 336 self._chg_manager = servo_charger.ServoV4ChargeManager( 337 host, host.servo) 338 except error.TestNAError: 339 logging.warning('Servo does not support AC switching.') 340 self._chg_manager = None 341 342 def run_once(self): 343 """Body of the test.""" 344 345 test_ws = set( 346 ws for ws in FULL_WAKE_SOURCES if self._is_valid_wake_source(ws)) 347 passed_ws = set(ws for ws in test_ws if self._test_wake(ws, True)) 348 failed_ws = test_ws.difference(passed_ws) 349 skipped_ws = set(FULL_WAKE_SOURCES).difference(test_ws) 350 351 test_dark_ws = set(ws for ws in DARK_RESUME_SOURCES 352 if self._is_valid_wake_source(ws)) 353 skipped_ws.update(set(DARK_RESUME_SOURCES).difference(test_dark_ws)) 354 for ws in test_dark_ws: 355 if self._test_wake(ws, False): 356 passed_ws.add(ws) 357 else: 358 failed_ws.add(ws) 359 360 test_keyval = {} 361 362 for ws in passed_ws: 363 test_keyval.update({ws: 'PASS'}) 364 for ws in failed_ws: 365 test_keyval.update({ws: 'FAIL'}) 366 for ws in skipped_ws: 367 test_keyval.update({ws: 'SKIPPED'}) 368 self.write_test_keyval(test_keyval) 369 370 if passed_ws: 371 logging.info('[%s] woke the device as expected.', 372 ''.join(str(elem) + ', ' for elem in passed_ws)) 373 374 if skipped_ws: 375 logging.info( 376 '[%s] are not wake sources on this platform. ' 377 'Please test manually if not the case.', 378 ''.join(str(elem) + ', ' for elem in skipped_ws)) 379 380 if failed_ws: 381 raise error.TestFail( 382 '[%s] wake sources did not behave as expected.' % 383 (''.join(str(elem) + ', ' for elem in failed_ws))) 384