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 5 import logging 6 import time 7 import sys 8 9 from multiprocessing import Process 10 from autotest_lib.client.bin import utils 11 from autotest_lib.client.common_lib import error 12 from autotest_lib.client.cros.faft.utils import shell_wrapper 13 14 class ConnectionError(Exception): 15 """Raised on an error of connecting DUT.""" 16 pass 17 18 19 class _BaseFwBypasser(object): 20 """Base class that controls bypass logic for firmware screens.""" 21 22 def __init__(self, faft_framework): 23 self.servo = faft_framework.servo 24 self.faft_config = faft_framework.faft_config 25 self.client_host = faft_framework._client 26 27 28 def bypass_dev_mode(self): 29 """Bypass the dev mode firmware logic to boot internal image.""" 30 raise NotImplementedError 31 32 33 def bypass_dev_boot_usb(self): 34 """Bypass the dev mode firmware logic to boot USB.""" 35 raise NotImplementedError 36 37 38 def bypass_rec_mode(self): 39 """Bypass the rec mode firmware logic to boot USB.""" 40 raise NotImplementedError 41 42 43 def trigger_dev_to_rec(self): 44 """Trigger to the rec mode from the dev screen.""" 45 raise NotImplementedError 46 47 48 def trigger_rec_to_dev(self): 49 """Trigger to the dev mode from the rec screen.""" 50 raise NotImplementedError 51 52 53 def trigger_dev_to_normal(self): 54 """Trigger to the normal mode from the dev screen.""" 55 raise NotImplementedError 56 57 58 class _CtrlDBypasser(_BaseFwBypasser): 59 """Controls bypass logic via Ctrl-D combo.""" 60 61 def bypass_dev_mode(self): 62 """Bypass the dev mode firmware logic to boot internal image.""" 63 time.sleep(self.faft_config.firmware_screen) 64 self.servo.ctrl_d() 65 66 67 def bypass_dev_boot_usb(self): 68 """Bypass the dev mode firmware logic to boot USB.""" 69 time.sleep(self.faft_config.firmware_screen) 70 self.servo.ctrl_u() 71 72 73 def bypass_rec_mode(self): 74 """Bypass the rec mode firmware logic to boot USB.""" 75 self.servo.switch_usbkey('host') 76 time.sleep(self.faft_config.usb_plug) 77 self.servo.switch_usbkey('dut') 78 logging.info('Enabled dut_sees_usb') 79 if not self.client_host.ping_wait_up( 80 timeout=self.faft_config.delay_reboot_to_ping): 81 logging.info('ping timed out, try REC_ON') 82 psc = self.servo.get_power_state_controller() 83 psc.power_on(psc.REC_ON) 84 85 86 def trigger_dev_to_rec(self): 87 """Trigger to the rec mode from the dev screen.""" 88 time.sleep(self.faft_config.firmware_screen) 89 90 # Pressing Enter for too long triggers a second key press. 91 # Let's press it without delay 92 self.servo.enter_key(press_secs=0) 93 94 # For Alex/ZGB, there is a dev warning screen in text mode. 95 # Skip it by pressing Ctrl-D. 96 if self.faft_config.need_dev_transition: 97 time.sleep(self.faft_config.legacy_text_screen) 98 self.servo.ctrl_d() 99 100 101 def trigger_rec_to_dev(self): 102 """Trigger to the dev mode from the rec screen.""" 103 time.sleep(self.faft_config.firmware_screen) 104 self.servo.ctrl_d() 105 time.sleep(self.faft_config.confirm_screen) 106 if self.faft_config.rec_button_dev_switch: 107 logging.info('RECOVERY button pressed to switch to dev mode') 108 self.servo.toggle_recovery_switch() 109 else: 110 logging.info('ENTER pressed to switch to dev mode') 111 self.servo.enter_key() 112 113 114 def trigger_dev_to_normal(self): 115 """Trigger to the normal mode from the dev screen.""" 116 time.sleep(self.faft_config.firmware_screen) 117 self.servo.enter_key() 118 time.sleep(self.faft_config.confirm_screen) 119 self.servo.enter_key() 120 121 122 class _JetstreamBypasser(_BaseFwBypasser): 123 """Controls bypass logic of Jetstream devices.""" 124 125 def bypass_dev_mode(self): 126 """Bypass the dev mode firmware logic to boot internal image.""" 127 # Jetstream does nothing to bypass. 128 pass 129 130 131 def bypass_dev_boot_usb(self): 132 """Bypass the dev mode firmware logic to boot USB.""" 133 self.servo.switch_usbkey('dut') 134 time.sleep(self.faft_config.firmware_screen) 135 self.servo.toggle_development_switch() 136 137 138 def bypass_rec_mode(self): 139 """Bypass the rec mode firmware logic to boot USB.""" 140 self.servo.switch_usbkey('host') 141 time.sleep(self.faft_config.usb_plug) 142 self.servo.switch_usbkey('dut') 143 if not self.client_host.ping_wait_up( 144 timeout=self.faft_config.delay_reboot_to_ping): 145 psc = self.servo.get_power_state_controller() 146 psc.power_on(psc.REC_ON) 147 148 149 def trigger_dev_to_rec(self): 150 """Trigger to the rec mode from the dev screen.""" 151 # Jetstream does not have this triggering logic. 152 raise NotImplementedError 153 154 155 def trigger_rec_to_dev(self): 156 """Trigger to the dev mode from the rec screen.""" 157 self.servo.disable_development_mode() 158 time.sleep(self.faft_config.firmware_screen) 159 self.servo.toggle_development_switch() 160 161 162 def trigger_dev_to_normal(self): 163 """Trigger to the normal mode from the dev screen.""" 164 # Jetstream does not have this triggering logic. 165 raise NotImplementedError 166 167 168 class _TabletDetachableBypasser(_BaseFwBypasser): 169 """Controls bypass logic of tablet/ detachable chromebook devices.""" 170 171 def set_button(self, button, duration, info): 172 """Helper method that sets the button hold time for UI selections""" 173 self.servo.set_nocheck(button, duration) 174 time.sleep(self.faft_config.confirm_screen) 175 logging.info(info) 176 177 178 def bypass_dev_boot_usb(self): 179 """Bypass the dev mode firmware logic to boot USB. 180 181 On tablets/ detachables, recovery entered by pressing pwr, vol up 182 & vol down buttons for 10s. 183 Menu options seen in DEVELOPER WARNING screen: 184 Developer Options 185 Show Debug Info 186 Enable Root Verification 187 Power Off* 188 Language 189 Menu options seen in DEV screen: 190 Boot legacy BIOS 191 Boot USB image 192 Boot developer image* 193 Cancel 194 Power off 195 Language 196 Vol up button selects previous item, vol down button selects 197 next item and pwr button selects current activated item. 198 """ 199 self.trigger_dev_screen() 200 time.sleep(self.faft_config.firmware_screen) 201 self.set_button('volume_up_hold', 100, ('Selecting power as' 202 ' enter key to select Boot USB Image')) 203 self.servo.power_short_press() 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 time.sleep(self.faft_config.usb_plug) 210 self.servo.switch_usbkey('dut') 211 logging.info('Enabled dut_sees_usb') 212 if not self.client_host.ping_wait_up( 213 timeout=self.faft_config.delay_reboot_to_ping): 214 logging.info('ping timed out, try REC_ON') 215 psc = self.servo.get_power_state_controller() 216 psc.power_on(psc.REC_ON) 217 218 219 def bypass_dev_mode(self): 220 """Bypass the dev mode firmware logic to boot internal image 221 222 On tablets/ detachables, recovery entered by pressing pwr, vol up 223 & vol down buttons for 10s. 224 Menu options seen in DEVELOPER WARNING screen: 225 Developer Options 226 Show Debug Info 227 Enable Root Verification 228 Power Off* 229 Language 230 Menu options seen in DEV screen: 231 Boot legacy BIOS 232 Boot USB image 233 Boot developer image* 234 Cancel 235 Power off 236 Language 237 Vol up button selects previous item, vol down button selects 238 next item and pwr button selects current activated item. 239 """ 240 self.trigger_dev_screen() 241 time.sleep(self.faft_config.firmware_screen) 242 logging.info('Selecting power as enter key to select ' 243 'Boot Developer Image') 244 self.servo.power_short_press() 245 246 247 def trigger_dev_screen(self): 248 """Helper method that transitions from DEVELOPER WARNING to DEV screen 249 250 Menu options seen in DEVELOPER WARNING screen: 251 Developer Options 252 Show Debug Info 253 Enable Root Verification 254 Power Off* 255 Language 256 Menu options seen in DEV screen: 257 Boot legacy BIOS 258 Boot USB image 259 Boot developer image* 260 Cancel 261 Power off 262 Language 263 Vol up button selects previous item, vol down button selects 264 next item and pwr button selects current activated item. 265 """ 266 time.sleep(self.faft_config.firmware_screen) 267 self.servo.set_nocheck('volume_up_hold', 100) 268 time.sleep(self.faft_config.confirm_screen) 269 self.servo.set_nocheck('volume_up_hold', 100) 270 time.sleep(self.faft_config.confirm_screen) 271 self.set_button('volume_up_hold', 100, ('Selecting power ' 272 'as enter key to select Developer Options')) 273 self.servo.power_short_press() 274 275 276 def trigger_rec_to_dev(self): 277 """Trigger to the dev mode from the rec screen using vol up button. 278 279 On tablets/ detachables, recovery entered by pressing pwr, vol up 280 & vol down buttons for 10s. TO_DEV screen is entered by pressing 281 vol up & vol down buttons together on the INSERT screen. 282 Menu options seen in TO_DEV screen: 283 Confirm enabling developer mode 284 Cancel* 285 Power off 286 Language 287 Vol up button selects previous item, vol down button selects 288 next item and pwr button selects current activated item. 289 """ 290 time.sleep(self.faft_config.firmware_screen) 291 self.set_button('volume_up_down_hold', 100, ('Enter Recovery Menu.')) 292 time.sleep(self.faft_config.confirm_screen) 293 self.set_button('volume_up_hold', 100, ('Selecting power as ' 294 'enter key to select Confirm Enabling Developer Mode')) 295 self.servo.power_short_press() 296 time.sleep(self.faft_config.firmware_screen) 297 298 299 def trigger_dev_to_normal(self): 300 """Trigger to the normal mode from the dev screen. 301 302 Menu options seen in DEVELOPER WARNING screen: 303 Developer Options 304 Show Debug Info 305 Enable Root Verification 306 Power Off* 307 Language 308 Menu options seen in TO_NORM screen: 309 Confirm Enabling Verified Boot* 310 Cancel 311 Power off 312 Language 313 Vol up button selects previous item, vol down button selects 314 next item and pwr button selects current activated item. 315 """ 316 time.sleep(self.faft_config.firmware_screen) 317 self.set_button('volume_up_hold', 100, ('Selecting ' 318 'Enable Root Verification using pwr ' 319 'button to enter TO_NORM screen')) 320 self.servo.power_short_press() 321 logging.info('Transitioning from DEV to TO_NORM screen.') 322 time.sleep(self.faft_config.firmware_screen) 323 logging.info('Selecting Confirm Enabling Verified ' 324 'Boot using pwr button in ' 325 'TO_NORM screen') 326 self.servo.power_short_press() 327 328 def trigger_dev_to_rec(self): 329 """Trigger to the TO_NORM screen from the dev screen. 330 Menu options seen in DEVELOPER WARNING screen: 331 Developer Options 332 Show Debug Info 333 Enable Root Verification 334 Power Off* 335 Language 336 Menu options seen in TO_NORM screen: 337 Confirm Enabling Verified Boot* 338 Cancel 339 Power off 340 Language 341 Vol up button selects previous item, vol down button selects 342 next item and pwr button selects current activated item. 343 """ 344 time.sleep(self.faft_config.firmware_screen) 345 self.set_button('volume_up_hold', 100, ('Selecting ' 346 'Enable Root Verification using pwr ' 347 'button to enter TO_NORM screen')) 348 self.servo.power_short_press() 349 logging.info('Transitioning from DEV to TO_NORM screen.') 350 time.sleep(self.faft_config.firmware_screen) 351 352 # In firmware_FwScreenPressPower, test will power off the DUT using 353 # Power button in second screen (TO_NORM screen) so scrolling to 354 # Power-off is necessary in this case. Hence scroll to Power-off as 355 # a generic action and wait for next action of either Lid close or 356 # power button press. 357 self.servo.set_nocheck('volume_down_hold', 100) 358 time.sleep(self.faft_config.confirm_screen) 359 self.servo.set_nocheck('volume_down_hold', 100) 360 time.sleep(self.faft_config.confirm_screen) 361 362 def _create_fw_bypasser(faft_framework): 363 """Creates a proper firmware bypasser. 364 365 @param faft_framework: The main FAFT framework object. 366 """ 367 bypasser_type = faft_framework.faft_config.fw_bypasser_type 368 if bypasser_type == 'ctrl_d_bypasser': 369 logging.info('Create a CtrlDBypasser') 370 return _CtrlDBypasser(faft_framework) 371 elif bypasser_type == 'jetstream_bypasser': 372 logging.info('Create a JetstreamBypasser') 373 return _JetstreamBypasser(faft_framework) 374 elif bypasser_type == 'ryu_bypasser': 375 # FIXME Create an RyuBypasser 376 logging.info('Create a CtrlDBypasser') 377 return _CtrlDBypasser(faft_framework) 378 elif bypasser_type == 'tablet_detachable_bypasser': 379 logging.info('Create a TabletDetachableBypasser') 380 return _TabletDetachableBypasser(faft_framework) 381 else: 382 raise NotImplementedError('Not supported fw_bypasser_type: %s', 383 bypasser_type) 384 385 386 class _BaseModeSwitcher(object): 387 """Base class that controls firmware mode switching.""" 388 389 def __init__(self, faft_framework): 390 self.faft_framework = faft_framework 391 self.client_host = faft_framework._client 392 self.faft_client = faft_framework.faft_client 393 self.servo = faft_framework.servo 394 self.faft_config = faft_framework.faft_config 395 self.checkers = faft_framework.checkers 396 self.bypasser = _create_fw_bypasser(faft_framework) 397 self._backup_mode = None 398 399 400 def setup_mode(self, mode): 401 """Setup for the requested mode. 402 403 It makes sure the system in the requested mode. If not, it tries to 404 do so. 405 406 @param mode: A string of mode, one of 'normal', 'dev', or 'rec'. 407 @raise TestFail: If the system not switched to expected mode after 408 reboot_to_mode. 409 410 """ 411 if not self.checkers.mode_checker(mode): 412 logging.info('System not in expected %s mode. Reboot into it.', 413 mode) 414 if self._backup_mode is None: 415 # Only resume to normal/dev mode after test, not recovery. 416 self._backup_mode = 'dev' if mode == 'normal' else 'normal' 417 self.reboot_to_mode(mode) 418 if not self.checkers.mode_checker(mode): 419 raise error.TestFail('System not switched to expected %s' 420 ' mode after setup_mode.' % mode) 421 422 def restore_mode(self): 423 """Restores original dev mode status if it has changed. 424 425 @raise TestFail: If the system not restored to expected mode. 426 """ 427 if (self._backup_mode is not None and 428 not self.checkers.mode_checker(self._backup_mode)): 429 self.reboot_to_mode(self._backup_mode) 430 if not self.checkers.mode_checker(self._backup_mode): 431 raise error.TestFail('System not restored to expected %s' 432 ' mode in cleanup.' % self._backup_mode) 433 434 435 436 def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True, 437 wait_for_dut_up=True): 438 """Reboot and execute the mode switching sequence. 439 440 @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'. 441 @param from_mode: The original mode, optional, one of 'normal, 'dev', 442 or 'rec'. 443 @param sync_before_boot: True to sync to disk before booting. 444 @param wait_for_dut_up: True to wait DUT online again. False to do the 445 reboot and mode switching sequence only and may 446 need more operations to pass the firmware 447 screen. 448 """ 449 logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-', 450 to_mode, from_mode, wait_for_dut_up) 451 if sync_before_boot: 452 self.faft_framework.blocking_sync() 453 if to_mode == 'rec': 454 self._enable_rec_mode_and_reboot(usb_state='dut') 455 if wait_for_dut_up: 456 self.wait_for_client() 457 458 elif to_mode == 'dev': 459 self._enable_dev_mode_and_reboot() 460 if wait_for_dut_up: 461 self.bypass_dev_mode() 462 self.wait_for_client() 463 464 elif to_mode == 'normal': 465 self._enable_normal_mode_and_reboot() 466 if wait_for_dut_up: 467 self.wait_for_client() 468 469 else: 470 raise NotImplementedError( 471 'Not supported mode switching from %s to %s' % 472 (str(from_mode), to_mode)) 473 logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-', 474 to_mode, from_mode, wait_for_dut_up) 475 476 def simple_reboot(self, reboot_type='warm', sync_before_boot=True): 477 """Simple reboot method 478 479 Just reboot the DUT using either cold or warm reset. Does not wait for 480 DUT to come back online. Will wait for test to handle this. 481 482 @param reboot_type: A string of reboot type, 'warm' or 'cold'. 483 If reboot_type != warm/cold, raise exception. 484 @param sync_before_boot: True to sync to disk before booting. 485 If sync_before_boot=False, DUT offline before 486 calling mode_aware_reboot. 487 """ 488 if reboot_type == 'warm': 489 reboot_method = self.servo.get_power_state_controller().warm_reset 490 elif reboot_type == 'cold': 491 reboot_method = self.servo.get_power_state_controller().reset 492 else: 493 raise NotImplementedError('Not supported reboot_type: %s', 494 reboot_type) 495 if sync_before_boot: 496 boot_id = self.faft_framework.get_bootid() 497 self.faft_framework.blocking_sync() 498 logging.info("-[ModeSwitcher]-[ start simple_reboot(%r) ]-", 499 reboot_type) 500 reboot_method() 501 if sync_before_boot: 502 self.wait_for_client_offline(orig_boot_id=boot_id) 503 logging.info("-[ModeSwitcher]-[ end simple_reboot(%r) ]-", 504 reboot_type) 505 506 def mode_aware_reboot(self, reboot_type=None, reboot_method=None, 507 sync_before_boot=True, wait_for_dut_up=True): 508 """Uses a mode-aware way to reboot DUT. 509 510 For example, if DUT is in dev mode, it requires pressing Ctrl-D to 511 bypass the developer screen. 512 513 @param reboot_type: A string of reboot type, one of 'warm', 'cold', or 514 'custom'. Default is a warm reboot. 515 @param reboot_method: A custom method to do the reboot. Only use it if 516 reboot_type='custom'. 517 @param sync_before_boot: True to sync to disk before booting. 518 If sync_before_boot=False, DUT offline before 519 calling mode_aware_reboot. 520 @param wait_for_dut_up: True to wait DUT online again. False to do the 521 reboot only. 522 """ 523 if reboot_type is None or reboot_type == 'warm': 524 reboot_method = self.servo.get_power_state_controller().warm_reset 525 elif reboot_type == 'cold': 526 reboot_method = self.servo.get_power_state_controller().reset 527 elif reboot_type != 'custom': 528 raise NotImplementedError('Not supported reboot_type: %s', 529 reboot_type) 530 531 logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-", 532 reboot_type, reboot_method.__name__) 533 is_dev = is_rec = is_devsw_boot = False 534 if sync_before_boot: 535 is_dev = self.checkers.mode_checker('dev') 536 is_rec = self.checkers.mode_checker('rec') 537 is_devsw_boot = self.checkers.crossystem_checker( 538 {'devsw_boot': '1'}, True) 539 boot_id = self.faft_framework.get_bootid() 540 self.faft_framework.blocking_sync() 541 if is_rec: 542 logging.info("-[mode_aware_reboot]-[ is_rec=%s is_dev_switch=%s ]-", 543 is_rec, is_devsw_boot) 544 else: 545 logging.info("-[mode_aware_reboot]-[ is_dev=%s ]-", is_dev) 546 reboot_method() 547 if sync_before_boot: 548 self.wait_for_client_offline(orig_boot_id=boot_id) 549 # Encapsulating the behavior of skipping dev firmware screen, 550 # hitting ctrl-D 551 # Note that if booting from recovery mode, we can predict the next 552 # boot based on the developer switch position at boot (devsw_boot). 553 # If devsw_boot is True, we will call bypass_dev_mode after reboot. 554 if is_dev or is_devsw_boot: 555 self.bypass_dev_mode() 556 if wait_for_dut_up: 557 self.wait_for_client() 558 logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-", 559 reboot_type, reboot_method.__name__) 560 561 562 def _enable_rec_mode_and_reboot(self, usb_state=None): 563 """Switch to rec mode and reboot. 564 565 This method emulates the behavior of the old physical recovery switch, 566 i.e. switch ON + reboot + switch OFF, and the new keyboard controlled 567 recovery mode, i.e. just press Power + Esc + Refresh. 568 569 @param usb_state: A string, one of 'dut', 'host', or 'off'. 570 """ 571 psc = self.servo.get_power_state_controller() 572 psc.power_off() 573 if usb_state: 574 self.servo.switch_usbkey(usb_state) 575 psc.power_on(psc.REC_ON) 576 577 578 def _disable_rec_mode_and_reboot(self, usb_state=None): 579 """Disable the rec mode and reboot. 580 581 It is achieved by calling power state controller to do a normal 582 power on. 583 """ 584 psc = self.servo.get_power_state_controller() 585 psc.power_off() 586 time.sleep(self.faft_config.ec_boot_to_pwr_button) 587 psc.power_on(psc.REC_OFF) 588 589 590 def _enable_dev_mode_and_reboot(self): 591 """Switch to developer mode and reboot.""" 592 raise NotImplementedError 593 594 595 def _enable_normal_mode_and_reboot(self): 596 """Switch to normal mode and reboot.""" 597 raise NotImplementedError 598 599 600 # Redirects the following methods to FwBypasser 601 def bypass_dev_mode(self): 602 """Bypass the dev mode firmware logic to boot internal image.""" 603 logging.info("-[bypass_dev_mode]-") 604 self.bypasser.bypass_dev_mode() 605 606 607 def bypass_dev_boot_usb(self): 608 """Bypass the dev mode firmware logic to boot USB.""" 609 logging.info("-[bypass_dev_boot_usb]-") 610 self.bypasser.bypass_dev_boot_usb() 611 612 613 def bypass_rec_mode(self): 614 """Bypass the rec mode firmware logic to boot USB.""" 615 logging.info("-[bypass_rec_mode]-") 616 self.bypasser.bypass_rec_mode() 617 618 619 def trigger_dev_to_rec(self): 620 """Trigger to the rec mode from the dev screen.""" 621 self.bypasser.trigger_dev_to_rec() 622 623 624 def trigger_rec_to_dev(self): 625 """Trigger to the dev mode from the rec screen.""" 626 self.bypasser.trigger_rec_to_dev() 627 628 629 def trigger_dev_to_normal(self): 630 """Trigger to the normal mode from the dev screen.""" 631 self.bypasser.trigger_dev_to_normal() 632 633 634 def wait_for_client(self, timeout=180): 635 """Wait for the client to come back online. 636 637 New remote processes will be launched if their used flags are enabled. 638 639 @param timeout: Time in seconds to wait for the client SSH daemon to 640 come up. 641 @raise ConnectionError: Failed to connect DUT. 642 """ 643 logging.info("-[FAFT]-[ start wait_for_client ]---") 644 # Wait for the system to respond to ping before attempting ssh 645 if not self.client_host.ping_wait_up(timeout): 646 logging.warning("-[FAFT]-[ system did not respond to ping ]") 647 if self.client_host.wait_up(timeout): 648 # Check the FAFT client is avaiable. 649 self.faft_client.system.is_available() 650 # Stop update-engine as it may change firmware/kernel. 651 self.faft_framework.faft_client.updater.stop_daemon() 652 else: 653 logging.error('wait_for_client() timed out.') 654 raise ConnectionError('DUT is still down unexpectedly') 655 logging.info("-[FAFT]-[ end wait_for_client ]-----") 656 657 658 def wait_for_client_offline(self, timeout=60, orig_boot_id=None): 659 """Wait for the client to come offline. 660 661 @param timeout: Time in seconds to wait the client to come offline. 662 @param orig_boot_id: A string containing the original boot id. 663 @raise ConnectionError: Failed to wait DUT offline. 664 """ 665 # When running against panther, we see that sometimes 666 # ping_wait_down() does not work correctly. There needs to 667 # be some investigation to the root cause. 668 # If we sleep for 120s before running get_boot_id(), it 669 # does succeed. But if we change this to ping_wait_down() 670 # there are implications on the wait time when running 671 # commands at the fw screens. 672 if not self.client_host.ping_wait_down(timeout): 673 if orig_boot_id and self.client_host.get_boot_id() != orig_boot_id: 674 logging.warn('Reboot done very quickly.') 675 return 676 raise ConnectionError('DUT is still up unexpectedly') 677 678 679 class _PhysicalButtonSwitcher(_BaseModeSwitcher): 680 """Class that switches firmware mode via physical button.""" 681 682 def _enable_dev_mode_and_reboot(self): 683 """Switch to developer mode and reboot.""" 684 self.servo.enable_development_mode() 685 self.faft_client.system.run_shell_command( 686 'chromeos-firmwareupdate --mode todev && reboot') 687 688 689 def _enable_normal_mode_and_reboot(self): 690 """Switch to normal mode and reboot.""" 691 self.servo.disable_development_mode() 692 self.faft_client.system.run_shell_command( 693 'chromeos-firmwareupdate --mode tonormal && reboot') 694 695 696 class _KeyboardDevSwitcher(_BaseModeSwitcher): 697 """Class that switches firmware mode via keyboard combo.""" 698 699 def _enable_dev_mode_and_reboot(self): 700 """Switch to developer mode and reboot.""" 701 logging.info("Enabling keyboard controlled developer mode") 702 # Rebooting EC with rec mode on. Should power on AP. 703 # Plug out USB disk for preventing recovery boot without warning 704 self._enable_rec_mode_and_reboot(usb_state='host') 705 self.wait_for_client_offline() 706 self.bypasser.trigger_rec_to_dev() 707 708 709 def _enable_normal_mode_and_reboot(self): 710 """Switch to normal mode and reboot.""" 711 logging.info("Disabling keyboard controlled developer mode") 712 self._disable_rec_mode_and_reboot() 713 self.wait_for_client_offline() 714 self.bypasser.trigger_dev_to_normal() 715 716 717 class _JetstreamSwitcher(_BaseModeSwitcher): 718 """Class that switches firmware mode in Jetstream devices.""" 719 720 def _enable_dev_mode_and_reboot(self): 721 """Switch to developer mode and reboot.""" 722 logging.info("Enabling Jetstream developer mode") 723 self._enable_rec_mode_and_reboot(usb_state='host') 724 self.wait_for_client_offline() 725 self.bypasser.trigger_rec_to_dev() 726 727 728 def _enable_normal_mode_and_reboot(self): 729 """Switch to normal mode and reboot.""" 730 logging.info("Disabling Jetstream developer mode") 731 self.servo.disable_development_mode() 732 self._enable_rec_mode_and_reboot(usb_state='host') 733 time.sleep(self.faft_config.firmware_screen) 734 self._disable_rec_mode_and_reboot(usb_state='host') 735 736 737 class _TabletDetachableSwitcher(_BaseModeSwitcher): 738 """Class that switches fw mode in tablets/detachables with fw menu UI.""" 739 740 def _enable_dev_mode_and_reboot(self): 741 """Switch to developer mode and reboot. 742 743 On tablets/ detachables, recovery entered by pressing pwr, vol up 744 & vol down buttons for 10s. 745 Menu options seen in RECOVERY screen: 746 Enable Developer Mode 747 Show Debug Info 748 Power off* 749 Language 750 """ 751 logging.info('Enabling tablets/detachable recovery mode') 752 self._enable_rec_mode_and_reboot(usb_state='host') 753 self.wait_for_client_offline() 754 self.bypasser.trigger_rec_to_dev() 755 756 757 def _enable_normal_mode_and_reboot(self): 758 """Switch to normal mode and reboot. 759 760 Menu options seen in DEVELOPER WARNING screen: 761 Developer Options 762 Show Debug Info 763 Enable Root Verification 764 Power Off* 765 Language 766 Menu options seen in TO_NORM screen: 767 Confirm Enabling Verified Boot 768 Cancel 769 Power off* 770 Language 771 Vol up button selects previous item, vol down button selects 772 next item and pwr button selects current activated item. 773 """ 774 self._disable_rec_mode_and_reboot() 775 self.wait_for_client_offline() 776 self.bypasser.trigger_dev_to_normal() 777 778 779 class _RyuSwitcher(_BaseModeSwitcher): 780 """Class that switches firmware mode via physical button.""" 781 782 FASTBOOT_OEM_DELAY = 10 783 RECOVERY_TIMEOUT = 2400 784 RECOVERY_SETUP = 60 785 ANDROID_BOOTUP = 600 786 FWTOOL_STARTUP_DELAY = 30 787 788 def wait_for_client(self, timeout=180): 789 """Wait for the client to come back online. 790 791 New remote processes will be launched if their used flags are enabled. 792 793 @param timeout: Time in seconds to wait for the client SSH daemon to 794 come up. 795 @raise ConnectionError: Failed to connect DUT. 796 """ 797 if not self.faft_client.system.wait_for_client(timeout): 798 raise ConnectionError('DUT is still down unexpectedly') 799 800 # there's a conflict between fwtool and crossystem trying to access 801 # the nvram after the OS boots up. Temporarily put a hard wait of 802 # 30 seconds to try to wait for fwtool to finish up. 803 time.sleep(self.FWTOOL_STARTUP_DELAY) 804 805 806 def wait_for_client_offline(self, timeout=60, orig_boot_id=None): 807 """Wait for the client to come offline. 808 809 @param timeout: Time in seconds to wait the client to come offline. 810 @param orig_boot_id: A string containing the original boot id. 811 @raise ConnectionError: Failed to wait DUT offline. 812 """ 813 # TODO: Add a way to check orig_boot_id 814 if not self.faft_client.system.wait_for_client_offline(timeout): 815 raise ConnectionError('DUT is still up unexpectedly') 816 817 def print_recovery_warning(self): 818 """Print recovery warning""" 819 logging.info("***") 820 logging.info("*** Entering recovery mode. This may take awhile ***") 821 logging.info("***") 822 # wait a minute for DUT to get settled into wipe stage 823 time.sleep(self.RECOVERY_SETUP) 824 825 def is_fastboot_mode(self): 826 """Return True if DUT in fastboot mode, False otherwise""" 827 result = self.faft_client.host.run_shell_command_get_output( 828 'fastboot devices') 829 if not result: 830 return False 831 else: 832 return True 833 834 def wait_for_client_fastboot(self, timeout=30): 835 """Wait for the client to come online in fastboot mode 836 837 @param timeout: Time in seconds to wait the client 838 @raise ConnectionError: Failed to wait DUT offline. 839 """ 840 utils.wait_for_value(self.is_fastboot_mode, True, timeout_sec=timeout) 841 842 def _run_cmd(self, args): 843 """Wrapper for run_shell_command 844 845 For Process creation 846 """ 847 return self.faft_client.host.run_shell_command(args) 848 849 def _enable_dev_mode_and_reboot(self): 850 """Switch to developer mode and reboot.""" 851 logging.info("Entering RyuSwitcher: _enable_dev_mode_and_reboot") 852 try: 853 self.faft_client.system.run_shell_command('reboot bootloader') 854 self.wait_for_client_fastboot() 855 856 process = Process( 857 target=self._run_cmd, 858 args=('fastboot oem unlock',)) 859 process.start() 860 861 # need a slight delay to give the ap time to get into valid state 862 time.sleep(self.FASTBOOT_OEM_DELAY) 863 self.servo.power_key(self.faft_config.hold_pwr_button_poweron) 864 process.join() 865 866 self.print_recovery_warning() 867 self.wait_for_client_fastboot(self.RECOVERY_TIMEOUT) 868 self.faft_client.host.run_shell_command('fastboot continue') 869 self.wait_for_client(self.ANDROID_BOOTUP) 870 871 # need to reset DUT into clean state 872 except shell_wrapper.ShellError: 873 raise error.TestError('Error executing shell command') 874 except ConnectionError: 875 raise error.TestError('Timed out waiting for DUT to exit recovery') 876 except: 877 raise error.TestError('Unexpected Exception: %s' % sys.exc_info()[0]) 878 logging.info("Exiting RyuSwitcher: _enable_dev_mode_and_reboot") 879 880 def _enable_normal_mode_and_reboot(self): 881 """Switch to normal mode and reboot.""" 882 try: 883 self.faft_client.system.run_shell_command('reboot bootloader') 884 self.wait_for_client_fastboot() 885 886 process = Process( 887 target=self._run_cmd, 888 args=('fastboot oem lock',)) 889 process.start() 890 891 # need a slight delay to give the ap time to get into valid state 892 time.sleep(self.FASTBOOT_OEM_DELAY) 893 self.servo.power_key(self.faft_config.hold_pwr_button_poweron) 894 process.join() 895 896 self.print_recovery_warning() 897 self.wait_for_client_fastboot(self.RECOVERY_TIMEOUT) 898 self.faft_client.host.run_shell_command('fastboot continue') 899 self.wait_for_client(self.ANDROID_BOOTUP) 900 901 # need to reset DUT into clean state 902 except shell_wrapper.ShellError: 903 raise error.TestError('Error executing shell command') 904 except ConnectionError: 905 raise error.TestError('Timed out waiting for DUT to exit recovery') 906 except: 907 raise error.TestError('Unexpected Exception: %s' % sys.exc_info()[0]) 908 logging.info("Exiting RyuSwitcher: _enable_normal_mode_and_reboot") 909 910 def create_mode_switcher(faft_framework): 911 """Creates a proper mode switcher. 912 913 @param faft_framework: The main FAFT framework object. 914 """ 915 switcher_type = faft_framework.faft_config.mode_switcher_type 916 if switcher_type == 'physical_button_switcher': 917 logging.info('Create a PhysicalButtonSwitcher') 918 return _PhysicalButtonSwitcher(faft_framework) 919 elif switcher_type == 'keyboard_dev_switcher': 920 logging.info('Create a KeyboardDevSwitcher') 921 return _KeyboardDevSwitcher(faft_framework) 922 elif switcher_type == 'jetstream_switcher': 923 logging.info('Create a JetstreamSwitcher') 924 return _JetstreamSwitcher(faft_framework) 925 elif switcher_type == 'ryu_switcher': 926 logging.info('Create a RyuSwitcher') 927 return _RyuSwitcher(faft_framework) 928 elif switcher_type == 'tablet_detachable_switcher': 929 logging.info('Create a TabletDetachableSwitcher') 930 return _TabletDetachableSwitcher(faft_framework) 931 else: 932 raise NotImplementedError('Not supported mode_switcher_type: %s', 933 switcher_type) 934