1# Copyright 2015 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 error 9 10DEBOUNCE_STATE = 'debouncing' 11 12class ConnectionError(Exception): 13 """Raised on an error of connecting DUT.""" 14 pass 15 16 17class _BaseFwBypasser(object): 18 """Base class that controls bypass logic for firmware screens.""" 19 20 # Duration of holding Volume down button to quickly bypass the developer 21 # warning screen in tablets/detachables. 22 HOLD_VOL_DOWN_BUTTON_BYPASS = 3 23 24 def __init__(self, faft_framework): 25 self.faft_framework = faft_framework 26 self.servo = faft_framework.servo 27 self.faft_config = faft_framework.faft_config 28 self.client_host = faft_framework._client 29 self.ec = getattr(faft_framework, 'ec', None) 30 31 32 def bypass_dev_mode(self): 33 """Bypass the dev mode firmware logic to boot internal image.""" 34 raise NotImplementedError 35 36 37 def bypass_dev_boot_usb(self): 38 """Bypass the dev mode firmware logic to boot USB.""" 39 raise NotImplementedError 40 41 42 def bypass_rec_mode(self): 43 """Bypass the rec mode firmware logic to boot USB.""" 44 raise NotImplementedError 45 46 47 def trigger_dev_to_rec(self): 48 """Trigger to the rec mode from the dev screen.""" 49 raise NotImplementedError 50 51 52 def trigger_rec_to_dev(self): 53 """Trigger to the dev mode from the rec screen.""" 54 raise NotImplementedError 55 56 57 def trigger_dev_to_normal(self): 58 """Trigger to the normal mode from the dev screen.""" 59 raise NotImplementedError 60 61 62 # This is used as a workaround of a bug in RO - DUT does not supply 63 # Vbus when in SRC_ACCESSORY state (we set servo to snk before booting 64 # to recovery due to the assumption of no PD in RO). It causes that DUT can 65 # not see USB Stick in recovery mode(RO) despite being DFP(b/159938441). 66 # The bug in RO has been fixed in 251212fb. 67 # Some boards already have it in RO, so the issue does not appear 68 def check_vbus_and_pd_state(self): 69 """Perform PD power and data swap, if DUT is SRC and doesn't supply 70 Vbus""" 71 if self.ec and self.faft_config.ec_ro_vbus_bug: 72 time.sleep(self.faft_framework.PD_RESYNC_DELAY) 73 servo_pr_role = self.servo.get_servo_v4_role() 74 if servo_pr_role == 'snk': 75 mv = self.servo.get_vbus_voltage() 76 # Despite the faft_config, make sure the issue occurs - 77 # servo is snk and vbus is not supplied. 78 if mv is not None and mv < self.servo.VBUS_THRESHOLD: 79 # Make servo SRC to supply Vbus correctly 80 self.servo.set_servo_v4_role('src') 81 time.sleep(self.faft_framework.PD_RESYNC_DELAY) 82 # After reboot, EC can be UFP so check that 83 if not self.ec.is_dfp(): 84 # EC is UFP, perform PD Data Swap 85 self.ec.send_command("pd 0 swap data") 86 time.sleep(self.faft_framework.PD_RESYNC_DELAY) 87 # Make sure EC is DFP now 88 if not self.ec.is_dfp(): 89 # EC is still UFP 90 raise error.TestError('DUT is not DFP in recovery mode.') 91 92 93class _KeyboardBypasser(_BaseFwBypasser): 94 """Controls bypass logic via keyboard shortcuts for menu UI.""" 95 96 def bypass_dev_mode(self): 97 """Bypass the dev mode firmware logic to boot internal image. 98 99 Press Ctrl-D repeatedly. To obtain a low firmware boot time, pressing 100 Ctrl+D for every half second until firmware_screen delay has been 101 reached. 102 """ 103 logging.info("Pressing Ctrl-D.") 104 # At maximum, device waits for twice of firmware_screen delay to 105 # bypass the Dev screen. 106 timeout = time.time() + (self.faft_config.firmware_screen * 2) 107 while time.time() < timeout: 108 self.servo.ctrl_d() 109 time.sleep(0.5) 110 if self.client_host.ping_wait_up(timeout=0.1): 111 break 112 113 114 def bypass_dev_boot_usb(self): 115 """Bypass the dev mode firmware logic to boot USB.""" 116 self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+u') 117 self.servo.ctrl_u() 118 119 120 def bypass_dev_default_boot(self): 121 """Bypass the dev mode firmware logic to boot from default target.""" 122 self.faft_framework.wait_for('firmware_screen', 'Pressing enter') 123 self.servo.enter_key() 124 125 126 def bypass_rec_mode(self): 127 """Bypass the rec mode firmware logic to boot USB.""" 128 self.servo.switch_usbkey('host') 129 self.faft_framework.wait_for('usb_plug', 'Switching usb key to DUT') 130 self.check_vbus_and_pd_state() 131 self.servo.switch_usbkey('dut') 132 logging.info('Enabled dut_sees_usb') 133 if not self.client_host.ping_wait_up( 134 timeout=self.faft_config.delay_reboot_to_ping): 135 logging.info('ping timed out, try REC_ON') 136 psc = self.servo.get_power_state_controller() 137 psc.power_on(psc.REC_ON) 138 # Check Vbus after reboot again 139 self.check_vbus_and_pd_state() 140 141 142 def trigger_dev_to_rec(self): 143 """Trigger to the to-norm screen from the dev screen.""" 144 self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+s') 145 self.servo.ctrl_s() 146 147 148 def trigger_rec_to_dev(self): 149 """Trigger to the dev mode from the rec screen.""" 150 self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+d') 151 self.servo.ctrl_d() 152 self.faft_framework.wait_for('confirm_screen', 'Pressing button to switch to dev mode') 153 if self.faft_config.rec_button_dev_switch: 154 logging.info('RECOVERY button pressed to switch to dev mode') 155 self.servo.toggle_recovery_switch() 156 elif self.faft_config.power_button_dev_switch: 157 logging.info('POWER button pressed to switch to dev mode') 158 self.servo.power_normal_press() 159 else: 160 logging.info('ENTER pressed to switch to dev mode') 161 self.servo.enter_key() 162 163 164 def trigger_dev_to_normal(self): 165 """Trigger to the normal mode from the dev screen.""" 166 # Navigate to to-norm screen 167 self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+s') 168 self.servo.ctrl_s() 169 # Select "Confirm" 170 self.faft_framework.wait_for('confirm_screen', 'Pressing enter') 171 self.servo.enter_key() 172 173 174class _LegacyKeyboardBypasser(_KeyboardBypasser): 175 """Controls bypass logic via keyboard shortcuts for legacy clamshell UI.""" 176 177 def trigger_dev_to_rec(self): 178 """Trigger to the to-norm screen from the dev screen.""" 179 self.faft_framework.wait_for('firmware_screen', 'Pressing enter') 180 self.servo.enter_key() 181 182 def trigger_dev_to_normal(self): 183 """Trigger to the normal mode from the dev screen.""" 184 self.faft_framework.wait_for('firmware_screen', 'Pressing enter') 185 self.servo.enter_key() 186 self.faft_framework.wait_for('confirm_screen', 'Pressing enter') 187 self.servo.enter_key() 188 189 190class _JetstreamBypasser(_BaseFwBypasser): 191 """Controls bypass logic of Jetstream devices.""" 192 193 def bypass_dev_mode(self): 194 """Bypass the dev mode firmware logic to boot internal image.""" 195 # Jetstream does nothing to bypass. 196 pass 197 198 199 def bypass_dev_boot_usb(self): 200 """Bypass the dev mode firmware logic to boot USB.""" 201 self.servo.switch_usbkey('dut') 202 self.faft_framework.wait_for('firmware_screen', 'Toggling development switch') 203 self.servo.toggle_development_switch() 204 205 206 def bypass_rec_mode(self): 207 """Bypass the rec mode firmware logic to boot USB.""" 208 self.servo.switch_usbkey('host') 209 self.faft_framework.wait_for('usb_plug', 'Switching usb key to DUT') 210 self.check_vbus_and_pd_state() 211 self.servo.switch_usbkey('dut') 212 if not self.client_host.ping_wait_up( 213 timeout=self.faft_config.delay_reboot_to_ping): 214 psc = self.servo.get_power_state_controller() 215 psc.power_on(psc.REC_ON) 216 # Check Vbus after reboot again 217 self.check_vbus_and_pd_state() 218 219 220 def trigger_dev_to_rec(self): 221 """Trigger to the rec mode from the dev screen.""" 222 # Jetstream does not have this triggering logic. 223 raise NotImplementedError 224 225 226 def trigger_rec_to_dev(self): 227 """Trigger to the dev mode from the rec screen.""" 228 self.servo.disable_development_mode() 229 self.faft_framework.wait_for('firmware_screen', 'Toggling development switch') 230 self.servo.toggle_development_switch() 231 232 233 def trigger_dev_to_normal(self): 234 """Trigger to the normal mode from the dev screen.""" 235 # Jetstream does not have this triggering logic. 236 raise NotImplementedError 237 238 239class _TabletDetachableBypasser(_BaseFwBypasser): 240 """Controls bypass logic of tablet/ detachable chromebook devices.""" 241 242 def set_button(self, button, duration, info): 243 """Helper method that sets the button hold time for UI selections""" 244 self.servo.set_nocheck(button, duration) 245 self.faft_framework.wait_for('confirm_screen') 246 logging.info(info) 247 248 249 def bypass_dev_boot_usb(self): 250 """Bypass the dev mode firmware logic to boot USB. 251 252 On tablets/ detachables, recovery entered by pressing pwr, vol up 253 & vol down buttons for 10s. 254 Menu options seen in DEVELOPER WARNING screen: 255 Developer Options 256 Show Debug Info 257 Enable Root Verification 258 Power Off* 259 Language 260 Menu options seen in DEV screen: 261 Boot legacy BIOS 262 Boot USB image 263 Boot developer image* 264 Cancel 265 Power off 266 Language 267 268 Vol up button selects previous item, vol down button selects 269 next item and pwr button selects current activated item. 270 271 Note: if dev_default_boot=usb, the default selection will start on USB, 272 and this will move up one to legacy boot instead. 273 """ 274 self.trigger_dev_screen() 275 self.faft_framework.wait_for('firmware_screen', 'Pressing volume up') 276 self.set_button('volume_up_hold', 100, ('Selecting power as' 277 ' enter key to select Boot USB Image')) 278 self.servo.power_short_press() 279 280 def bypass_dev_default_boot(self): 281 """Open the Developer Options menu, and accept the default boot device 282 283 Menu options seen in DEVELOPER WARNING screen: 284 Developer Options 285 Show Debug Info 286 Enable Root Verification 287 Power Off* 288 Language 289 Menu options seen in DEV screen: 290 Boot legacy BIOS* (default if dev_default_boot=legacy) 291 Boot USB image* (default if dev_default_boot=usb) 292 Boot developer image* (default if dev_default_boot=disk) 293 Cancel 294 Power off 295 Language 296 297 Vol up button selects previous item, vol down button selects 298 next item and pwr button selects current activated item. 299 """ 300 self.trigger_dev_screen() 301 self.faft_framework.wait_for('firmware_screen', 'Pressing power button') 302 logging.info('Selecting power as enter key to accept the default' 303 ' boot option.') 304 self.servo.power_short_press() 305 306 def bypass_rec_mode(self): 307 """Bypass the rec mode firmware logic to boot USB.""" 308 self.servo.switch_usbkey('host') 309 self.faft_framework.wait_for('usb_plug', 'Switching usb key to DUT') 310 self.check_vbus_and_pd_state() 311 self.servo.switch_usbkey('dut') 312 logging.info('Enabled dut_sees_usb') 313 if not self.client_host.ping_wait_up( 314 timeout=self.faft_config.delay_reboot_to_ping): 315 logging.info('ping timed out, try REC_ON') 316 psc = self.servo.get_power_state_controller() 317 psc.power_on(psc.REC_ON) 318 # Check Vbus after reboot again 319 self.check_vbus_and_pd_state() 320 321 322 def bypass_dev_mode(self): 323 """Bypass the developer warning screen immediately to boot into 324 internal disk. 325 326 On tablets/detachables, press & holding the Volume down button for 327 3-seconds will quickly bypass the developer warning screen. 328 """ 329 # Unit for the "volume_down_hold" console command is msec. 330 duration = (self.HOLD_VOL_DOWN_BUTTON_BYPASS + 0.1) * 1000 331 logging.info("Press and hold volume down button for %.1f seconds to " 332 "immediately bypass the Developer warning screen.", 333 self.HOLD_VOL_DOWN_BUTTON_BYPASS + 0.1) 334 # At maximum, device waits for twice of firmware_screen delay to 335 # bypass the Dev screen. 336 timeout = time.time() + (self.faft_config.firmware_screen * 2) 337 # To obtain a low firmware boot time, volume_down button pressed for 338 # every 3.1 seconds until firmware_screen delay has been reached. 339 while time.time() < timeout: 340 self.servo.set_nocheck('volume_down_hold', duration) 341 # After pressing 'volume_down_hold' button, wait for 0.1 seconds 342 # before start pressing the button for next iteration. 343 time.sleep(0.1) 344 if self.client_host.ping_wait_up(timeout=0.1): 345 break 346 347 348 def trigger_dev_screen(self): 349 """Helper method that transitions from DEVELOPER WARNING to DEV screen 350 351 Menu options seen in DEVELOPER WARNING screen: 352 Developer Options 353 Show Debug Info 354 Enable Root Verification 355 Power Off* 356 Language 357 Menu options seen in DEV screen: 358 Boot legacy BIOS 359 Boot USB image 360 Boot developer image* 361 Cancel 362 Power off 363 Language 364 Vol up button selects previous item, vol down button selects 365 next item and pwr button selects current activated item. 366 """ 367 self.faft_framework.wait_for('firmware_screen', 'Pressing volume up') 368 self.servo.set_nocheck('volume_up_hold', 100) 369 self.faft_framework.wait_for('confirm_screen', 'Pressing volume up') 370 self.servo.set_nocheck('volume_up_hold', 100) 371 self.faft_framework.wait_for('confirm_screen', 'Pressing volume up') 372 self.set_button('volume_up_hold', 100, ('Selecting power ' 373 'as enter key to select Developer Options')) 374 self.servo.power_short_press() 375 376 377 def trigger_rec_to_dev(self): 378 """Trigger to the dev mode from the rec screen using vol up button. 379 380 On tablets/ detachables, recovery entered by pressing pwr, vol up 381 & vol down buttons for 10s. TO_DEV screen is entered by pressing 382 vol up & vol down buttons together on the INSERT screen. 383 Menu options seen in TO_DEV screen: 384 Confirm enabling developer mode 385 Cancel* 386 Power off 387 Language 388 Vol up button selects previous item, vol down button selects 389 next item and pwr button selects current activated item. 390 """ 391 self.faft_framework.wait_for('firmware_screen', 'Pressing volume up + volume down') 392 self.set_button('volume_up_down_hold', 100, ('Enter Recovery Menu.')) 393 self.faft_framework.wait_for('confirm_screen', 'Pressing volume up') 394 self.set_button('volume_up_hold', 100, ('Selecting power as ' 395 'enter key to select Confirm Enabling Developer Mode')) 396 self.servo.power_short_press() 397 self.faft_framework.wait_for('firmware_screen') 398 399 400 def trigger_dev_to_normal(self): 401 """Trigger to the normal mode from the dev screen. 402 403 Menu options seen in DEVELOPER WARNING screen: 404 Developer Options 405 Show Debug Info 406 Enable Root Verification 407 Power Off* 408 Language 409 Menu options seen in TO_NORM screen: 410 Confirm Enabling Verified Boot* 411 Cancel 412 Power off 413 Language 414 Vol up button selects previous item, vol down button selects 415 next item and pwr button selects current activated item. 416 """ 417 self.faft_framework.wait_for('firmware_screen', 'Pressing volume up') 418 self.set_button('volume_up_hold', 100, ('Selecting ' 419 'Enable Root Verification using pwr ' 420 'button to enter TO_NORM screen')) 421 self.servo.power_short_press() 422 logging.info('Transitioning from DEV to TO_NORM screen.') 423 self.faft_framework.wait_for('firmware_screen', 'Pressing power button') 424 logging.info('Selecting Confirm Enabling Verified ' 425 'Boot using pwr button in ' 426 'TO_NORM screen') 427 self.servo.power_short_press() 428 429 def trigger_dev_to_rec(self): 430 """Trigger to the TO_NORM screen from the dev screen. 431 Menu options seen in DEVELOPER WARNING screen: 432 Developer Options 433 Show Debug Info 434 Enable Root Verification 435 Power Off* 436 Language 437 Menu options seen in TO_NORM screen: 438 Confirm Enabling Verified Boot* 439 Cancel 440 Power off 441 Language 442 Vol up button selects previous item, vol down button selects 443 next item and pwr button selects current activated item. 444 """ 445 self.faft_framework.wait_for('firmware_screen', 'Pressing volume up') 446 self.set_button('volume_up_hold', 100, ('Selecting ' 447 'Enable Root Verification using pwr ' 448 'button to enter TO_NORM screen')) 449 self.servo.power_short_press() 450 logging.info('Transitioning from DEV to TO_NORM screen.') 451 self.faft_framework.wait_for('firmware_screen', 'Pressing volume down') 452 453 # In firmware_FwScreenPressPower, test will power off the DUT using 454 # Power button in second screen (TO_NORM screen) so scrolling to 455 # Power-off is necessary in this case. Hence scroll to Power-off as 456 # a generic action and wait for next action of either Lid close or 457 # power button press. 458 self.servo.set_nocheck('volume_down_hold', 100) 459 self.faft_framework.wait_for('confirm_screen', 'Pressing volume down') 460 self.servo.set_nocheck('volume_down_hold', 100) 461 self.faft_framework.wait_for('confirm_screen') 462 463 464class _BaseModeSwitcher(object): 465 """Base class that controls firmware mode switching.""" 466 467 HOLD_VOL_DOWN_BUTTON_BYPASS = _BaseFwBypasser.HOLD_VOL_DOWN_BUTTON_BYPASS 468 469 FW_BYPASSER_CLASS = _BaseFwBypasser 470 471 def __init__(self, faft_framework): 472 self.faft_framework = faft_framework 473 self.client_host = faft_framework._client 474 self.faft_client = faft_framework.faft_client 475 self.servo = faft_framework.servo 476 self.faft_config = faft_framework.faft_config 477 self.checkers = faft_framework.checkers 478 self.bypasser = self._create_fw_bypasser() 479 self._backup_mode = None 480 481 def _create_fw_bypasser(self): 482 """Creates a proper firmware bypasser. 483 484 @rtype: _BaseFwBypasser 485 """ 486 return self.FW_BYPASSER_CLASS(self.faft_framework) 487 488 def setup_mode(self, mode): 489 """Setup for the requested mode. 490 491 It makes sure the system in the requested mode. If not, it tries to 492 do so. 493 494 @param mode: A string of mode, one of 'normal', 'dev', or 'rec'. 495 @raise TestFail: If the system not switched to expected mode after 496 reboot_to_mode. 497 498 """ 499 if not self.checkers.mode_checker(mode): 500 logging.info('System not in expected %s mode. Reboot into it.', 501 mode) 502 if self._backup_mode is None: 503 # Only resume to normal/dev mode after test, not recovery. 504 self._backup_mode = 'dev' if mode == 'normal' else 'normal' 505 self.reboot_to_mode(mode) 506 if not self.checkers.mode_checker(mode): 507 raise error.TestFail('System not switched to expected %s' 508 ' mode after setup_mode.' % mode) 509 510 def restore_mode(self): 511 """Restores original dev mode status if it has changed. 512 513 @raise TestFail: If the system not restored to expected mode. 514 """ 515 if (self._backup_mode is not None and 516 not self.checkers.mode_checker(self._backup_mode)): 517 self.reboot_to_mode(self._backup_mode) 518 if not self.checkers.mode_checker(self._backup_mode): 519 raise error.TestFail('System not restored to expected %s' 520 ' mode in cleanup.' % self._backup_mode) 521 522 523 524 def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True, 525 wait_for_dut_up=True): 526 """Reboot and execute the mode switching sequence. 527 528 This method simulates what a user would do to switch between different 529 modes of ChromeOS. Note that the modes are end-states where the OS is 530 booted up to the Welcome screen, so it takes care of navigating through 531 intermediate steps such as various boot confirmation screens. 532 533 From the user perspective, these are the states (note that there's also 534 a rec_force_mrc mode which is like rec mode but forces MRC retraining): 535 536 normal <-----> dev <------ rec 537 ^ ^ 538 | | 539 +-------------------------+ 540 541 This is the implementation, note that "from_mode" is only used for 542 logging purposes. 543 544 Normal <-----> Dev: 545 _enable_dev_mode_and_reboot() 546 547 Rec,normal -----> Dev: 548 disable_rec_mode_and_reboot() 549 550 Any -----> normal: 551 _enable_normal_mode_and_reboot() 552 553 Normal <-----> rec: 554 enable_rec_mode_and_reboot(usb_state='dut') 555 556 Normal <-----> rec_force_mrc: 557 _enable_rec_mode_force_mrc_and_reboot(usb_state='dut') 558 559 Note that one shouldn't transition to dev again without going through the 560 normal mode. This is because trying to disable os_verification when it's 561 already off is not supported by reboot_to_mode. 562 563 @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'. 564 @param from_mode: The original mode, optional, one of 'normal, 'dev', 565 or 'rec'. 566 @param sync_before_boot: True to sync to disk before booting. 567 @param wait_for_dut_up: True to wait DUT online again. False to do the 568 reboot and mode switching sequence only and may 569 need more operations to pass the firmware 570 screen. 571 """ 572 logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-', 573 to_mode, from_mode, wait_for_dut_up) 574 575 if from_mode: 576 note = 'reboot_to_mode: to=%s, from=%s' % (from_mode, to_mode) 577 else: 578 note = 'reboot_to_mode: to=%s' % to_mode 579 if sync_before_boot: 580 lines = self.faft_client.system.run_shell_command_get_output( 581 'crossystem') 582 logging.debug('-[ModeSwitcher]- crossystem output:\n%s', 583 '\n'.join(lines)) 584 devsw_cur = self.faft_client.system.get_crossystem_value( 585 'devsw_cur') 586 note += ', devsw_cur=%s' % devsw_cur 587 self.faft_framework.blocking_sync(freeze_for_reset=True) 588 note += '.' 589 590 if to_mode == 'rec': 591 self.enable_rec_mode_and_reboot(usb_state='dut') 592 593 elif to_mode == 'rec_force_mrc': 594 self._enable_rec_mode_force_mrc_and_reboot(usb_state='dut') 595 596 elif to_mode == 'dev': 597 self._enable_dev_mode_and_reboot() 598 if wait_for_dut_up: 599 self.bypass_dev_mode() 600 601 elif to_mode == 'normal': 602 self._enable_normal_mode_and_reboot() 603 604 else: 605 raise NotImplementedError( 606 'Not supported mode switching from %s to %s' % 607 (str(from_mode), to_mode)) 608 609 if wait_for_dut_up: 610 self.wait_for_client(retry_power_on=True, note=note) 611 612 logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-', 613 to_mode, from_mode, wait_for_dut_up) 614 615 def simple_reboot(self, reboot_type='warm', sync_before_boot=True): 616 """Simple reboot method 617 618 Just reboot the DUT using either cold or warm reset. Does not wait for 619 DUT to come back online. Will wait for test to handle this. 620 621 @param reboot_type: A string of reboot type, 'warm' or 'cold'. 622 If reboot_type != warm/cold, raise exception. 623 @param sync_before_boot: True to sync to disk before booting. 624 If sync_before_boot=False, DUT offline before 625 calling mode_aware_reboot. 626 """ 627 if reboot_type == 'warm': 628 reboot_method = self.servo.get_power_state_controller().warm_reset 629 elif reboot_type == 'cold': 630 reboot_method = self.servo.get_power_state_controller().reset 631 else: 632 raise NotImplementedError('Not supported reboot_type: %s', 633 reboot_type) 634 if sync_before_boot: 635 boot_id = self.faft_framework.get_bootid() 636 self.faft_framework.blocking_sync(freeze_for_reset=True) 637 logging.info("-[ModeSwitcher]-[ start simple_reboot(%r) ]-", 638 reboot_type) 639 reboot_method() 640 if sync_before_boot: 641 self.wait_for_client_offline(orig_boot_id=boot_id) 642 logging.info("-[ModeSwitcher]-[ end simple_reboot(%r) ]-", 643 reboot_type) 644 645 def mode_aware_reboot(self, reboot_type=None, reboot_method=None, 646 sync_before_boot=True, wait_for_dut_up=True): 647 """Uses a mode-aware way to reboot DUT. 648 649 For example, if DUT is in dev mode, it requires pressing Ctrl-D to 650 bypass the developer screen. 651 652 @param reboot_type: A string of reboot type, one of 'warm', 'cold', or 653 'custom'. Default is a warm reboot. 654 @param reboot_method: A custom method to do the reboot. Only use it if 655 reboot_type='custom'. 656 @param sync_before_boot: True to sync to disk before booting. 657 If sync_before_boot=False, DUT offline before 658 calling mode_aware_reboot. 659 @param wait_for_dut_up: True to wait DUT online again. False to do the 660 reboot only. 661 """ 662 if reboot_type is None or reboot_type == 'warm': 663 reboot_method = self.servo.get_power_state_controller().warm_reset 664 elif reboot_type == 'cold': 665 reboot_method = self.servo.get_power_state_controller().reset 666 elif reboot_type != 'custom': 667 raise NotImplementedError('Not supported reboot_type: %s', 668 reboot_type) 669 670 logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-", 671 reboot_type, reboot_method.__name__) 672 is_dev = is_rec = is_devsw_boot = False 673 if sync_before_boot: 674 is_dev = self.checkers.mode_checker('dev') 675 is_rec = self.checkers.mode_checker('rec') 676 is_devsw_boot = self.checkers.crossystem_checker( 677 {'devsw_boot': '1'}, True) 678 boot_id = self.faft_framework.get_bootid() 679 680 self.faft_framework.blocking_sync(reboot_type != 'custom') 681 if is_rec: 682 logging.info("-[mode_aware_reboot]-[ is_rec=%s is_dev_switch=%s ]-", 683 is_rec, is_devsw_boot) 684 else: 685 logging.info("-[mode_aware_reboot]-[ is_dev=%s ]-", is_dev) 686 reboot_method() 687 if sync_before_boot: 688 self.wait_for_client_offline(orig_boot_id=boot_id) 689 # Encapsulating the behavior of skipping dev firmware screen, 690 # hitting ctrl-D 691 # Note that if booting from recovery mode, we can predict the next 692 # boot based on the developer switch position at boot (devsw_boot). 693 # If devsw_boot is True, we will call bypass_dev_mode after reboot. 694 if is_dev or is_devsw_boot: 695 self.bypass_dev_mode() 696 if wait_for_dut_up: 697 self.wait_for_client() 698 logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-", 699 reboot_type, reboot_method.__name__) 700 701 702 def enable_rec_mode_and_reboot(self, usb_state=None): 703 """Switch to rec mode and reboot. 704 705 This method emulates the behavior of the old physical recovery switch, 706 i.e. switch ON + reboot + switch OFF, and the new keyboard controlled 707 recovery mode, i.e. just press Power + Esc + Refresh. 708 709 @param usb_state: A string, one of 'dut', 'host', or 'off'. 710 """ 711 psc = self.servo.get_power_state_controller() 712 # Switch the USB key when AP is on, because there is a 713 # bug (b/172909077) - using "image_usbkey_direction:usb_state", when 714 # AP if off may cause not recognizing the file system, 715 # so system won't boot in recovery from USB. 716 # When the issue is fixed, it can be done when AP is off. 717 if usb_state: 718 self.servo.switch_usbkey(usb_state) 719 psc.power_off() 720 psc.power_on(psc.REC_ON) 721 # Check VBUS and pd state only if we are going to boot 722 # to ChromeOS in the recovery mode 723 if usb_state == 'dut': 724 self.bypasser.check_vbus_and_pd_state() 725 726 727 def _enable_rec_mode_force_mrc_and_reboot(self, usb_state=None): 728 """Switch to rec mode, enable force mrc cache retraining, and reboot. 729 730 This method emulates the behavior of the old physical recovery switch, 731 i.e. switch ON + reboot + switch OFF, and the new keyboard controlled 732 recovery mode, i.e. just press Power + Esc + Refresh. 733 734 @param usb_state: A string, one of 'dut', 'host', or 'off'. 735 """ 736 psc = self.servo.get_power_state_controller() 737 # Switch the USB key when AP is on, because there is a 738 # bug (b/172909077) - using "image_usbkey_direction:usb_state", when 739 # AP if off may cause not recognizing the file system, 740 # so system won't boot in recovery from USB. 741 # When the issue is fixed, it can be done when AP is off. 742 if usb_state: 743 self.servo.switch_usbkey(usb_state) 744 psc.power_off() 745 psc.power_on(psc.REC_ON_FORCE_MRC) 746 747 def disable_rec_mode_and_reboot(self, usb_state=None): 748 """Disable the rec mode and reboot. 749 750 It is achieved by calling power state controller to do a normal 751 power on. 752 """ 753 psc = self.servo.get_power_state_controller() 754 psc.power_off() 755 self.faft_framework.wait_for('ec_boot_to_pwr_button', 'Powering on') 756 psc.power_on(psc.REC_OFF) 757 758 759 def _enable_dev_mode_and_reboot(self): 760 """Switch to developer mode and reboot.""" 761 raise NotImplementedError 762 763 764 def _enable_normal_mode_and_reboot(self): 765 """Switch to normal mode and reboot.""" 766 raise NotImplementedError 767 768 769 # Redirects the following methods to FwBypasser 770 def bypass_dev_mode(self): 771 """Bypass the dev mode firmware logic to boot internal image.""" 772 logging.info("-[bypass_dev_mode]-") 773 self.bypasser.bypass_dev_mode() 774 775 776 def bypass_dev_boot_usb(self): 777 """Bypass the dev mode firmware logic to boot USB.""" 778 logging.info("-[bypass_dev_boot_usb]-") 779 self.bypasser.bypass_dev_boot_usb() 780 781 782 def bypass_dev_default_boot(self): 783 """Bypass the dev mode firmware logic to boot from default target.""" 784 logging.info("-[bypass_dev_default_boot]-") 785 self.bypasser.bypass_dev_default_boot() 786 787 788 def bypass_rec_mode(self): 789 """Bypass the rec mode firmware logic to boot USB.""" 790 logging.info("-[bypass_rec_mode]-") 791 self.bypasser.bypass_rec_mode() 792 793 794 def trigger_dev_to_rec(self): 795 """Trigger to the rec mode from the dev screen.""" 796 self.bypasser.trigger_dev_to_rec() 797 798 799 def trigger_rec_to_dev(self): 800 """Trigger to the dev mode from the rec screen.""" 801 self.bypasser.trigger_rec_to_dev() 802 803 804 def trigger_dev_to_normal(self): 805 """Trigger to the normal mode from the dev screen.""" 806 self.bypasser.trigger_dev_to_normal() 807 808 809 def wait_for_client(self, timeout=180, retry_power_on=False, 810 debounce_power_state=True, note=''): 811 """Wait for the client to come back online. 812 813 New remote processes will be launched if their used flags are enabled. 814 815 @param timeout: Time in seconds to wait for the client SSH daemon to 816 come up. 817 @param retry_power_on: Try to power on the DUT if it isn't in S0. 818 @param debounce_power_state: Wait until power_state is the same two 819 times in a row to determine the actual 820 power_state. 821 @param note: Extra note to add to the end of the error text 822 @raise ConnectionError: Failed to connect DUT. 823 """ 824 logging.info("-[FAFT]-[ start wait_for_client(%ds) ]---", 825 timeout if retry_power_on else 0) 826 # Wait for the system to be powered on before trying the network 827 # Skip "None" result because that indicates lack of EC or problem 828 # querying the power state. 829 current_timer = 0 830 self.faft_framework.wait_for('delay_powerinfo_stable', 831 'checking power state') 832 power_state = self.faft_framework.get_power_state() 833 834 # The device may transition between states. Wait until the power state 835 # is stable for two seconds before determining the state. 836 if debounce_power_state: 837 last_state = power_state 838 power_state = DEBOUNCE_STATE 839 840 while (timeout > current_timer and 841 power_state not in (self.faft_framework.POWER_STATE_S0, None)): 842 time.sleep(2) 843 current_timer += 2 844 power_state = self.faft_framework.get_power_state() 845 846 # If the state changed, debounce it. 847 if debounce_power_state and power_state != last_state: 848 last_state = power_state 849 power_state = DEBOUNCE_STATE 850 851 logging.info('power state: %s', power_state) 852 853 # Only power-on the device if it has been consistently out of 854 # S0. 855 if (retry_power_on and 856 power_state not in (self.faft_framework.POWER_STATE_S0, 857 None, DEBOUNCE_STATE)): 858 logging.info("-[FAFT]-[ retry powering on the DUT ]---") 859 psc = self.servo.get_power_state_controller() 860 psc.retry_power_on() 861 862 # Use the last state if the device didn't reach a stable state in 863 # timeout seconds. 864 if power_state == DEBOUNCE_STATE: 865 power_state = last_state 866 if power_state not in (self.faft_framework.POWER_STATE_S0, None): 867 msg = 'DUT unexpectedly down, power state is %s.' % power_state 868 if note: 869 msg += ' %s' % note 870 raise ConnectionError(msg) 871 872 # Wait for the system to respond to ping before attempting ssh 873 if not self.client_host.ping_wait_up(timeout): 874 logging.warning("-[FAFT]-[ system did not respond to ping ]") 875 if self.client_host.wait_up(timeout): 876 # Check the FAFT client is avaiable. 877 self.faft_client.system.is_available() 878 # Stop update-engine as it may change firmware/kernel. 879 self.faft_framework.faft_client.updater.stop_daemon() 880 else: 881 logging.error('wait_for_client() timed out.') 882 power_state = self.faft_framework.get_power_state() 883 msg = 'DUT is still down unexpectedly.' 884 if power_state: 885 msg += ' Power state: %s.' % power_state 886 if note: 887 msg += ' %s' % note 888 raise ConnectionError(msg) 889 logging.info("-[FAFT]-[ end wait_for_client ]-----") 890 891 892 def wait_for_client_offline(self, timeout=60, orig_boot_id=None): 893 """Wait for the client to come offline. 894 895 @param timeout: Time in seconds to wait the client to come offline. 896 @param orig_boot_id: A string containing the original boot id. 897 @raise ConnectionError: Failed to wait DUT offline. 898 """ 899 # When running against panther, we see that sometimes 900 # ping_wait_down() does not work correctly. There needs to 901 # be some investigation to the root cause. 902 # If we sleep for 120s before running get_boot_id(), it 903 # does succeed. But if we change this to ping_wait_down() 904 # there are implications on the wait time when running 905 # commands at the fw screens. 906 if not self.client_host.ping_wait_down(timeout): 907 if orig_boot_id and self.client_host.get_boot_id() != orig_boot_id: 908 logging.warn('Reboot done very quickly.') 909 return 910 raise ConnectionError('DUT is still up unexpectedly') 911 912 913class _MenuSwitcher(_BaseModeSwitcher): 914 """Mode switcher via keyboard shortcuts for menu UI.""" 915 916 FW_BYPASSER_CLASS = _KeyboardBypasser 917 918 def _enable_dev_mode_and_reboot(self): 919 """Switch to developer mode and reboot.""" 920 logging.info("Enabling keyboard controlled developer mode") 921 # Rebooting EC with rec mode on. Should power on AP. 922 # Plug out USB disk for preventing recovery boot without warning 923 self.enable_rec_mode_and_reboot(usb_state='host') 924 self.wait_for_client_offline() 925 self.bypasser.trigger_rec_to_dev() 926 927 def _enable_normal_mode_and_reboot(self): 928 """Switch to normal mode and reboot.""" 929 logging.info("Disabling keyboard controlled developer mode") 930 self.disable_rec_mode_and_reboot() 931 self.wait_for_client_offline() 932 self.bypasser.trigger_dev_to_normal() 933 934 935class _KeyboardDevSwitcher(_MenuSwitcher): 936 """Mode switcher via keyboard shortcuts for legacy clamshell UI.""" 937 938 FW_BYPASSER_CLASS = _LegacyKeyboardBypasser 939 940 941class _JetstreamSwitcher(_BaseModeSwitcher): 942 """Mode switcher for Jetstream devices.""" 943 944 FW_BYPASSER_CLASS = _JetstreamBypasser 945 946 def _enable_dev_mode_and_reboot(self): 947 """Switch to developer mode and reboot.""" 948 logging.info("Enabling Jetstream developer mode") 949 self.enable_rec_mode_and_reboot(usb_state='host') 950 self.wait_for_client_offline() 951 self.bypasser.trigger_rec_to_dev() 952 953 def _enable_normal_mode_and_reboot(self): 954 """Switch to normal mode and reboot.""" 955 logging.info("Disabling Jetstream developer mode") 956 self.servo.disable_development_mode() 957 self.enable_rec_mode_and_reboot(usb_state='host') 958 self.faft_framework.wait_for('firmware_screen', 'Disabling rec and rebooting') 959 self.disable_rec_mode_and_reboot(usb_state='host') 960 961 962class _TabletDetachableSwitcher(_BaseModeSwitcher): 963 """Mode switcher for legacy menu UI.""" 964 965 FW_BYPASSER_CLASS = _TabletDetachableBypasser 966 967 def _enable_dev_mode_and_reboot(self): 968 """Switch to developer mode and reboot. 969 970 On tablets/ detachables, recovery entered by pressing pwr, vol up 971 & vol down buttons for 10s. 972 Menu options seen in RECOVERY screen: 973 Enable Developer Mode 974 Show Debug Info 975 Power off* 976 Language 977 """ 978 logging.info('Enabling tablets/detachable recovery mode') 979 self.enable_rec_mode_and_reboot(usb_state='host') 980 self.wait_for_client_offline() 981 self.bypasser.trigger_rec_to_dev() 982 983 def _enable_normal_mode_and_reboot(self): 984 """Switch to normal mode and reboot. 985 986 Menu options seen in DEVELOPER WARNING screen: 987 Developer Options 988 Show Debug Info 989 Enable Root Verification 990 Power Off* 991 Language 992 Menu options seen in TO_NORM screen: 993 Confirm Enabling Verified Boot 994 Cancel 995 Power off* 996 Language 997 Vol up button selects previous item, vol down button selects 998 next item and pwr button selects current activated item. 999 """ 1000 self.disable_rec_mode_and_reboot() 1001 self.wait_for_client_offline() 1002 self.bypasser.trigger_dev_to_normal() 1003 1004 1005_SWITCHER_CLASSES = { 1006 'menu_switcher': _MenuSwitcher, 1007 'keyboard_dev_switcher': _KeyboardDevSwitcher, 1008 'jetstream_switcher': _JetstreamSwitcher, 1009 'tablet_detachable_switcher': _TabletDetachableSwitcher, 1010} 1011 1012 1013def create_mode_switcher(faft_framework): 1014 """Creates a proper mode switcher. 1015 1016 @param faft_framework: The main FAFT framework object. 1017 """ 1018 switcher_type = faft_framework.faft_config.mode_switcher_type 1019 switcher_class = _SWITCHER_CLASSES.get(switcher_type, None) 1020 if switcher_class is None: 1021 raise NotImplementedError('Not supported mode_switcher_type: %s', 1022 switcher_type) 1023 else: 1024 return switcher_class(faft_framework) 1025