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