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     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         time.sleep(self.faft_config.firmware_screen)
    268         self.servo.set_nocheck('volume_up_hold', 100)
    269         time.sleep(self.faft_config.confirm_screen)
    270         self.servo.set_nocheck('volume_up_hold', 100)
    271         time.sleep(self.faft_config.confirm_screen)
    272         self.set_button('volume_up_hold', 100, ('Selecting power '
    273                         'as enter key to select Developer Options'))
    274         self.servo.power_short_press()
    275 
    276 
    277     def trigger_rec_to_dev(self):
    278         """Trigger to the dev mode from the rec screen using vol up button.
    279 
    280         On tablets/ detachables, recovery entered by pressing pwr, vol up
    281         & vol down buttons for 10s.
    282            Menu options seen in RECOVERY screen:
    283                  Enable Developer Mode
    284                  Show Debug Info
    285                  Power off*
    286                  Language
    287            Menu options seen in TO_DEV screen:
    288                  Confirm enabling developer mode
    289                  Cancel
    290                  Power off*
    291                  Language
    292         Vol up button selects previous item, vol down button selects
    293         next item and pwr button selects current activated item.
    294         """
    295         time.sleep(self.faft_config.firmware_screen)
    296         self.servo.set_nocheck('volume_up_hold', 100)
    297         time.sleep(self.faft_config.confirm_screen)
    298         self.set_button('volume_up_hold', 100, ('Selecting power as '
    299                         'enter key to select Enable Developer Mode'))
    300         self.servo.power_short_press()
    301         logging.info('Transitioning from REC to TO_DEV screen.')
    302         time.sleep(self.faft_config.confirm_screen)
    303         self.servo.set_nocheck('volume_up_hold', 100)
    304         time.sleep(self.faft_config.confirm_screen)
    305         self.set_button('volume_up_hold', 100, ('Selecting power as '
    306                         'enter key to select Confirm enabling '
    307                         'developer mode'))
    308         self.servo.power_short_press()
    309         time.sleep(self.faft_config.firmware_screen)
    310 
    311 
    312     def trigger_dev_to_normal(self):
    313         """Trigger to the normal mode from the dev screen.
    314 
    315            Menu options seen in DEVELOPER WARNING screen:
    316                  Developer Options
    317                  Show Debug Info
    318                  Enable Root Verification
    319                  Power Off*
    320                  Language
    321            Menu options seen in TO_NORM screen:
    322                  Confirm Enabling Verified Boot
    323                  Cancel
    324                  Power off*
    325                  Language
    326         Vol up button selects previous item, vol down button selects
    327         next item and pwr button selects current activated item.
    328         """
    329         time.sleep(self.faft_config.firmware_screen)
    330         self.set_button('volume_up_hold', 100, ('Selecting '
    331                         'Enable Root Verification using pwr '
    332                         'button to enter TO_NORM screen'))
    333         self.servo.power_short_press()
    334         time.sleep(self.faft_config.firmware_screen)
    335         self.servo.set_nocheck('volume_up_hold', 100)
    336         time.sleep(self.faft_config.confirm_screen)
    337         self.set_button('volume_up_hold', 100, ('Selecting Confirm '
    338                         'Enabling Verified Boot using pwr '
    339                         'button in TO_NORM screen'))
    340         self.servo.power_short_press()
    341 
    342 
    343 def _create_fw_bypasser(faft_framework):
    344     """Creates a proper firmware bypasser.
    345 
    346     @param faft_framework: The main FAFT framework object.
    347     """
    348     bypasser_type = faft_framework.faft_config.fw_bypasser_type
    349     if bypasser_type == 'ctrl_d_bypasser':
    350         logging.info('Create a CtrlDBypasser')
    351         return _CtrlDBypasser(faft_framework)
    352     elif bypasser_type == 'jetstream_bypasser':
    353         logging.info('Create a JetstreamBypasser')
    354         return _JetstreamBypasser(faft_framework)
    355     elif bypasser_type == 'ryu_bypasser':
    356         # FIXME Create an RyuBypasser
    357         logging.info('Create a CtrlDBypasser')
    358         return _CtrlDBypasser(faft_framework)
    359     elif bypasser_type == 'tablet_detachable_bypasser':
    360         logging.info('Create a TabletDetachableBypasser')
    361         return _TabletDetachableBypasser(faft_framework)
    362     else:
    363         raise NotImplementedError('Not supported fw_bypasser_type: %s',
    364                                   bypasser_type)
    365 
    366 
    367 class _BaseModeSwitcher(object):
    368     """Base class that controls firmware mode switching."""
    369 
    370     def __init__(self, faft_framework):
    371         self.faft_framework = faft_framework
    372         self.client_host = faft_framework._client
    373         self.faft_client = faft_framework.faft_client
    374         self.servo = faft_framework.servo
    375         self.faft_config = faft_framework.faft_config
    376         self.checkers = faft_framework.checkers
    377         self.bypasser = _create_fw_bypasser(faft_framework)
    378         self._backup_mode = None
    379 
    380 
    381     def setup_mode(self, mode):
    382         """Setup for the requested mode.
    383 
    384         It makes sure the system in the requested mode. If not, it tries to
    385         do so.
    386 
    387         @param mode: A string of mode, one of 'normal', 'dev', or 'rec'.
    388         """
    389         if not self.checkers.mode_checker(mode):
    390             logging.info('System not in expected %s mode. Reboot into it.',
    391                          mode)
    392             if self._backup_mode is None:
    393                 # Only resume to normal/dev mode after test, not recovery.
    394                 self._backup_mode = 'dev' if mode == 'normal' else 'normal'
    395             self.reboot_to_mode(mode)
    396 
    397 
    398     def restore_mode(self):
    399         """Restores original dev mode status if it has changed."""
    400         if self._backup_mode is not None:
    401             self.reboot_to_mode(self._backup_mode)
    402 
    403 
    404     def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True,
    405                        wait_for_dut_up=True):
    406         """Reboot and execute the mode switching sequence.
    407 
    408         @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'.
    409         @param from_mode: The original mode, optional, one of 'normal, 'dev',
    410                           or 'rec'.
    411         @param sync_before_boot: True to sync to disk before booting.
    412         @param wait_for_dut_up: True to wait DUT online again. False to do the
    413                                 reboot and mode switching sequence only and may
    414                                 need more operations to pass the firmware
    415                                 screen.
    416         """
    417         logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-',
    418                      to_mode, from_mode, wait_for_dut_up)
    419         if sync_before_boot:
    420             self.faft_framework.blocking_sync()
    421         if to_mode == 'rec':
    422             self._enable_rec_mode_and_reboot(usb_state='dut')
    423             if wait_for_dut_up:
    424                 self.wait_for_client()
    425 
    426         elif to_mode == 'dev':
    427             self._enable_dev_mode_and_reboot()
    428             if wait_for_dut_up:
    429                 self.bypass_dev_mode()
    430                 self.wait_for_client()
    431 
    432         elif to_mode == 'normal':
    433             self._enable_normal_mode_and_reboot()
    434             if wait_for_dut_up:
    435                 self.wait_for_client()
    436 
    437         else:
    438             raise NotImplementedError(
    439                     'Not supported mode switching from %s to %s' %
    440                      (str(from_mode), to_mode))
    441         logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-',
    442                      to_mode, from_mode, wait_for_dut_up)
    443 
    444     def simple_reboot(self, reboot_type='warm', sync_before_boot=True):
    445         """Simple reboot method
    446 
    447         Just reboot the DUT using either cold or warm reset.  Does not wait for
    448         DUT to come back online.  Will wait for test to handle this.
    449 
    450         @param reboot_type: A string of reboot type, 'warm' or 'cold'.
    451                             If reboot_type != warm/cold, raise exception.
    452         @param sync_before_boot: True to sync to disk before booting.
    453                                  If sync_before_boot=False, DUT offline before
    454                                  calling mode_aware_reboot.
    455         """
    456         if reboot_type == 'warm':
    457             reboot_method = self.servo.get_power_state_controller().warm_reset
    458         elif reboot_type == 'cold':
    459             reboot_method = self.servo.get_power_state_controller().reset
    460         else:
    461             raise NotImplementedError('Not supported reboot_type: %s',
    462                                       reboot_type)
    463         if sync_before_boot:
    464             boot_id = self.faft_framework.get_bootid()
    465             self.faft_framework.blocking_sync()
    466         logging.info("-[ModeSwitcher]-[ start simple_reboot(%r) ]-",
    467                      reboot_type)
    468         reboot_method()
    469         if sync_before_boot:
    470             self.wait_for_client_offline(orig_boot_id=boot_id)
    471         logging.info("-[ModeSwitcher]-[ end simple_reboot(%r) ]-",
    472                      reboot_type)
    473 
    474     def mode_aware_reboot(self, reboot_type=None, reboot_method=None,
    475                           sync_before_boot=True, wait_for_dut_up=True):
    476         """Uses a mode-aware way to reboot DUT.
    477 
    478         For example, if DUT is in dev mode, it requires pressing Ctrl-D to
    479         bypass the developer screen.
    480 
    481         @param reboot_type: A string of reboot type, one of 'warm', 'cold', or
    482                             'custom'. Default is a warm reboot.
    483         @param reboot_method: A custom method to do the reboot. Only use it if
    484                               reboot_type='custom'.
    485         @param sync_before_boot: True to sync to disk before booting.
    486                                  If sync_before_boot=False, DUT offline before
    487                                  calling mode_aware_reboot.
    488         @param wait_for_dut_up: True to wait DUT online again. False to do the
    489                                 reboot only.
    490         """
    491         if reboot_type is None or reboot_type == 'warm':
    492             reboot_method = self.servo.get_power_state_controller().warm_reset
    493         elif reboot_type == 'cold':
    494             reboot_method = self.servo.get_power_state_controller().reset
    495         elif reboot_type != 'custom':
    496             raise NotImplementedError('Not supported reboot_type: %s',
    497                                       reboot_type)
    498 
    499         logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-",
    500                      reboot_type, reboot_method.__name__)
    501         is_dev = False
    502         if sync_before_boot:
    503             is_dev = self.checkers.mode_checker('dev')
    504             boot_id = self.faft_framework.get_bootid()
    505             self.faft_framework.blocking_sync()
    506         logging.info("-[mode_aware_reboot]-[ is_dev=%s ]-", is_dev);
    507         reboot_method()
    508         if sync_before_boot:
    509             self.wait_for_client_offline(orig_boot_id=boot_id)
    510         # Encapsulating the behavior of skipping dev firmware screen,
    511         # hitting ctrl-D
    512         # Note that if booting from recovery mode, will not
    513         # call bypass_dev_mode because can't determine prior to
    514         # reboot if we're going to boot up in dev or normal mode.
    515         if is_dev:
    516             self.bypass_dev_mode()
    517         if wait_for_dut_up:
    518             self.wait_for_client()
    519         logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-",
    520                      reboot_type, reboot_method.__name__)
    521 
    522 
    523     def _enable_rec_mode_and_reboot(self, usb_state=None):
    524         """Switch to rec mode and reboot.
    525 
    526         This method emulates the behavior of the old physical recovery switch,
    527         i.e. switch ON + reboot + switch OFF, and the new keyboard controlled
    528         recovery mode, i.e. just press Power + Esc + Refresh.
    529 
    530         @param usb_state: A string, one of 'dut', 'host', or 'off'.
    531         """
    532         psc = self.servo.get_power_state_controller()
    533         psc.power_off()
    534         if usb_state:
    535             self.servo.switch_usbkey(usb_state)
    536         psc.power_on(psc.REC_ON)
    537 
    538 
    539     def _disable_rec_mode_and_reboot(self, usb_state=None):
    540         """Disable the rec mode and reboot.
    541 
    542         It is achieved by calling power state controller to do a normal
    543         power on.
    544         """
    545         psc = self.servo.get_power_state_controller()
    546         psc.power_off()
    547         time.sleep(self.faft_config.ec_boot_to_pwr_button)
    548         psc.power_on(psc.REC_OFF)
    549 
    550 
    551     def _enable_dev_mode_and_reboot(self):
    552         """Switch to developer mode and reboot."""
    553         raise NotImplementedError
    554 
    555 
    556     def _enable_normal_mode_and_reboot(self):
    557         """Switch to normal mode and reboot."""
    558         raise NotImplementedError
    559 
    560 
    561     # Redirects the following methods to FwBypasser
    562     def bypass_dev_mode(self):
    563         """Bypass the dev mode firmware logic to boot internal image."""
    564         logging.info("-[bypass_dev_mode]-")
    565         self.bypasser.bypass_dev_mode()
    566 
    567 
    568     def bypass_dev_boot_usb(self):
    569         """Bypass the dev mode firmware logic to boot USB."""
    570         logging.info("-[bypass_dev_boot_usb]-")
    571         self.bypasser.bypass_dev_boot_usb()
    572 
    573 
    574     def bypass_rec_mode(self):
    575         """Bypass the rec mode firmware logic to boot USB."""
    576         logging.info("-[bypass_rec_mode]-")
    577         self.bypasser.bypass_rec_mode()
    578 
    579 
    580     def trigger_dev_to_rec(self):
    581         """Trigger to the rec mode from the dev screen."""
    582         self.bypasser.trigger_dev_to_rec()
    583 
    584 
    585     def trigger_rec_to_dev(self):
    586         """Trigger to the dev mode from the rec screen."""
    587         self.bypasser.trigger_rec_to_dev()
    588 
    589 
    590     def trigger_dev_to_normal(self):
    591         """Trigger to the normal mode from the dev screen."""
    592         self.bypasser.trigger_dev_to_normal()
    593 
    594 
    595     def wait_for_client(self, timeout=180):
    596         """Wait for the client to come back online.
    597 
    598         New remote processes will be launched if their used flags are enabled.
    599 
    600         @param timeout: Time in seconds to wait for the client SSH daemon to
    601                         come up.
    602         @raise ConnectionError: Failed to connect DUT.
    603         """
    604         logging.info("-[FAFT]-[ start wait_for_client ]---")
    605         # Wait for the system to respond to ping before attempting ssh
    606         if not self.client_host.ping_wait_up(timeout):
    607             logging.warning("-[FAFT]-[ system did not respond to ping ]")
    608         if self.client_host.wait_up(timeout):
    609             # Check the FAFT client is avaiable.
    610             self.faft_client.system.is_available()
    611             # Stop update-engine as it may change firmware/kernel.
    612             self.faft_framework._stop_service('update-engine')
    613         else:
    614             logging.error('wait_for_client() timed out.')
    615             raise ConnectionError()
    616         logging.info("-[FAFT]-[ end wait_for_client ]-----")
    617 
    618 
    619     def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
    620         """Wait for the client to come offline.
    621 
    622         @param timeout: Time in seconds to wait the client to come offline.
    623         @param orig_boot_id: A string containing the original boot id.
    624         @raise ConnectionError: Failed to wait DUT offline.
    625         """
    626         # When running against panther, we see that sometimes
    627         # ping_wait_down() does not work correctly. There needs to
    628         # be some investigation to the root cause.
    629         # If we sleep for 120s before running get_boot_id(), it
    630         # does succeed. But if we change this to ping_wait_down()
    631         # there are implications on the wait time when running
    632         # commands at the fw screens.
    633         if not self.client_host.ping_wait_down(timeout):
    634             if orig_boot_id and self.client_host.get_boot_id() != orig_boot_id:
    635                 logging.warn('Reboot done very quickly.')
    636                 return
    637             raise ConnectionError()
    638 
    639 
    640 class _PhysicalButtonSwitcher(_BaseModeSwitcher):
    641     """Class that switches firmware mode via physical button."""
    642 
    643     def _enable_dev_mode_and_reboot(self):
    644         """Switch to developer mode and reboot."""
    645         self.servo.enable_development_mode()
    646         self.faft_client.system.run_shell_command(
    647                 'chromeos-firmwareupdate --mode todev && reboot')
    648 
    649 
    650     def _enable_normal_mode_and_reboot(self):
    651         """Switch to normal mode and reboot."""
    652         self.servo.disable_development_mode()
    653         self.faft_client.system.run_shell_command(
    654                 'chromeos-firmwareupdate --mode tonormal && reboot')
    655 
    656 
    657 class _KeyboardDevSwitcher(_BaseModeSwitcher):
    658     """Class that switches firmware mode via keyboard combo."""
    659 
    660     def _enable_dev_mode_and_reboot(self):
    661         """Switch to developer mode and reboot."""
    662         logging.info("Enabling keyboard controlled developer mode")
    663         # Rebooting EC with rec mode on. Should power on AP.
    664         # Plug out USB disk for preventing recovery boot without warning
    665         self._enable_rec_mode_and_reboot(usb_state='host')
    666         self.wait_for_client_offline()
    667         self.bypasser.trigger_rec_to_dev()
    668 
    669 
    670     def _enable_normal_mode_and_reboot(self):
    671         """Switch to normal mode and reboot."""
    672         logging.info("Disabling keyboard controlled developer mode")
    673         self._disable_rec_mode_and_reboot()
    674         self.wait_for_client_offline()
    675         self.bypasser.trigger_dev_to_normal()
    676 
    677 
    678 class _JetstreamSwitcher(_BaseModeSwitcher):
    679     """Class that switches firmware mode in Jetstream devices."""
    680 
    681     def _enable_dev_mode_and_reboot(self):
    682         """Switch to developer mode and reboot."""
    683         logging.info("Enabling Jetstream developer mode")
    684         self._enable_rec_mode_and_reboot(usb_state='host')
    685         self.wait_for_client_offline()
    686         self.bypasser.trigger_rec_to_dev()
    687 
    688 
    689     def _enable_normal_mode_and_reboot(self):
    690         """Switch to normal mode and reboot."""
    691         logging.info("Disabling Jetstream developer mode")
    692         self.servo.disable_development_mode()
    693         self._enable_rec_mode_and_reboot(usb_state='host')
    694         time.sleep(self.faft_config.firmware_screen)
    695         self._disable_rec_mode_and_reboot(usb_state='host')
    696 
    697 
    698 class _TabletDetachableSwitcher(_BaseModeSwitcher):
    699     """Class that switches fw mode in tablets/detachables with fw menu UI."""
    700 
    701     def _enable_dev_mode_and_reboot(self):
    702         """Switch to developer mode and reboot.
    703 
    704         On tablets/ detachables, recovery entered by pressing pwr, vol up
    705         & vol down buttons for 10s.
    706            Menu options seen in RECOVERY screen:
    707                  Enable Developer Mode
    708                  Show Debug Info
    709                  Power off*
    710                  Language
    711         """
    712         logging.info('Enabling tablets/detachable recovery mode')
    713         self._enable_rec_mode_and_reboot(usb_state='host')
    714         self.wait_for_client_offline()
    715         self.bypasser.trigger_rec_to_dev()
    716 
    717 
    718     def _enable_normal_mode_and_reboot(self):
    719         """Switch to normal mode and reboot.
    720 
    721            Menu options seen in DEVELOPER WARNING screen:
    722                  Developer Options
    723                  Show Debug Info
    724                  Enable Root Verification
    725                  Power Off*
    726                  Language
    727            Menu options seen in TO_NORM screen:
    728                  Confirm Enabling Verified Boot
    729                  Cancel
    730                  Power off*
    731                  Language
    732         Vol up button selects previous item, vol down button selects
    733         next item and pwr button selects current activated item.
    734         """
    735         self._disable_rec_mode_and_reboot()
    736         self.wait_for_client_offline()
    737         self.bypasser.trigger_dev_to_normal()
    738 
    739 
    740 class _RyuSwitcher(_BaseModeSwitcher):
    741     """Class that switches firmware mode via physical button."""
    742 
    743     FASTBOOT_OEM_DELAY = 10
    744     RECOVERY_TIMEOUT = 2400
    745     RECOVERY_SETUP = 60
    746     ANDROID_BOOTUP = 600
    747     FWTOOL_STARTUP_DELAY = 30
    748 
    749     def wait_for_client(self, timeout=180):
    750         """Wait for the client to come back online.
    751 
    752         New remote processes will be launched if their used flags are enabled.
    753 
    754         @param timeout: Time in seconds to wait for the client SSH daemon to
    755                         come up.
    756         @raise ConnectionError: Failed to connect DUT.
    757         """
    758         if not self.faft_client.system.wait_for_client(timeout):
    759             raise ConnectionError()
    760 
    761         # there's a conflict between fwtool and crossystem trying to access
    762         # the nvram after the OS boots up.  Temporarily put a hard wait of
    763         # 30 seconds to try to wait for fwtool to finish up.
    764         time.sleep(self.FWTOOL_STARTUP_DELAY)
    765 
    766 
    767     def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
    768         """Wait for the client to come offline.
    769 
    770         @param timeout: Time in seconds to wait the client to come offline.
    771         @param orig_boot_id: A string containing the original boot id.
    772         @raise ConnectionError: Failed to wait DUT offline.
    773         """
    774         # TODO: Add a way to check orig_boot_id
    775         if not self.faft_client.system.wait_for_client_offline(timeout):
    776             raise ConnectionError()
    777 
    778     def print_recovery_warning(self):
    779         """Print recovery warning"""
    780         logging.info("***")
    781         logging.info("*** Entering recovery mode.  This may take awhile ***")
    782         logging.info("***")
    783         # wait a minute for DUT to get settled into wipe stage
    784         time.sleep(self.RECOVERY_SETUP)
    785 
    786     def is_fastboot_mode(self):
    787         """Return True if DUT in fastboot mode, False otherwise"""
    788         result = self.faft_client.host.run_shell_command_get_output(
    789             'fastboot devices')
    790         if not result:
    791             return False
    792         else:
    793             return True
    794 
    795     def wait_for_client_fastboot(self, timeout=30):
    796         """Wait for the client to come online in fastboot mode
    797 
    798         @param timeout: Time in seconds to wait the client
    799         @raise ConnectionError: Failed to wait DUT offline.
    800         """
    801         utils.wait_for_value(self.is_fastboot_mode, True, timeout_sec=timeout)
    802 
    803     def _run_cmd(self, args):
    804         """Wrapper for run_shell_command
    805 
    806         For Process creation
    807         """
    808         return self.faft_client.host.run_shell_command(args)
    809 
    810     def _enable_dev_mode_and_reboot(self):
    811         """Switch to developer mode and reboot."""
    812         logging.info("Entering RyuSwitcher: _enable_dev_mode_and_reboot")
    813         try:
    814             self.faft_client.system.run_shell_command('reboot bootloader')
    815             self.wait_for_client_fastboot()
    816 
    817             process = Process(
    818                 target=self._run_cmd,
    819                 args=('fastboot oem unlock',))
    820             process.start()
    821 
    822             # need a slight delay to give the ap time to get into valid state
    823             time.sleep(self.FASTBOOT_OEM_DELAY)
    824             self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
    825             process.join()
    826 
    827             self.print_recovery_warning()
    828             self.wait_for_client_fastboot(self.RECOVERY_TIMEOUT)
    829             self.faft_client.host.run_shell_command('fastboot continue')
    830             self.wait_for_client(self.ANDROID_BOOTUP)
    831 
    832         # need to reset DUT into clean state
    833         except shell_wrapper.ShellError:
    834             raise error.TestError('Error executing shell command')
    835         except ConnectionError:
    836             raise error.TestError('Timed out waiting for DUT to exit recovery')
    837         except:
    838             raise error.TestError('Unexpected Exception: %s' % sys.exc_info()[0])
    839         logging.info("Exiting RyuSwitcher: _enable_dev_mode_and_reboot")
    840 
    841     def _enable_normal_mode_and_reboot(self):
    842         """Switch to normal mode and reboot."""
    843         try:
    844             self.faft_client.system.run_shell_command('reboot bootloader')
    845             self.wait_for_client_fastboot()
    846 
    847             process = Process(
    848                 target=self._run_cmd,
    849                 args=('fastboot oem lock',))
    850             process.start()
    851 
    852             # need a slight delay to give the ap time to get into valid state
    853             time.sleep(self.FASTBOOT_OEM_DELAY)
    854             self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
    855             process.join()
    856 
    857             self.print_recovery_warning()
    858             self.wait_for_client_fastboot(self.RECOVERY_TIMEOUT)
    859             self.faft_client.host.run_shell_command('fastboot continue')
    860             self.wait_for_client(self.ANDROID_BOOTUP)
    861 
    862         # need to reset DUT into clean state
    863         except shell_wrapper.ShellError:
    864             raise error.TestError('Error executing shell command')
    865         except ConnectionError:
    866             raise error.TestError('Timed out waiting for DUT to exit recovery')
    867         except:
    868             raise error.TestError('Unexpected Exception: %s' % sys.exc_info()[0])
    869         logging.info("Exiting RyuSwitcher: _enable_normal_mode_and_reboot")
    870 
    871 def create_mode_switcher(faft_framework):
    872     """Creates a proper mode switcher.
    873 
    874     @param faft_framework: The main FAFT framework object.
    875     """
    876     switcher_type = faft_framework.faft_config.mode_switcher_type
    877     if switcher_type == 'physical_button_switcher':
    878         logging.info('Create a PhysicalButtonSwitcher')
    879         return _PhysicalButtonSwitcher(faft_framework)
    880     elif switcher_type == 'keyboard_dev_switcher':
    881         logging.info('Create a KeyboardDevSwitcher')
    882         return _KeyboardDevSwitcher(faft_framework)
    883     elif switcher_type == 'jetstream_switcher':
    884         logging.info('Create a JetstreamSwitcher')
    885         return _JetstreamSwitcher(faft_framework)
    886     elif switcher_type == 'ryu_switcher':
    887         logging.info('Create a RyuSwitcher')
    888         return _RyuSwitcher(faft_framework)
    889     elif switcher_type == 'tablet_detachable_switcher':
    890         logging.info('Create a TabletDetachableSwitcher')
    891         return _TabletDetachableSwitcher(faft_framework)
    892     else:
    893         raise NotImplementedError('Not supported mode_switcher_type: %s',
    894                                   switcher_type)
    895