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