| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import time |
| |
| from autotest_lib.client.common_lib import error |
| |
| class ConnectionError(Exception): |
| """Raised on an error of connecting DUT.""" |
| pass |
| |
| |
| class _BaseFwBypasser(object): |
| """Base class that controls bypass logic for firmware screens.""" |
| |
| # Duration of holding Volume down button to quickly bypass the developer |
| # warning screen in tablets/detachables. |
| HOLD_VOL_DOWN_BUTTON_BYPASS = 3 |
| |
| def __init__(self, faft_framework): |
| self.servo = faft_framework.servo |
| self.faft_config = faft_framework.faft_config |
| self.client_host = faft_framework._client |
| |
| |
| def bypass_dev_mode(self): |
| """Bypass the dev mode firmware logic to boot internal image.""" |
| raise NotImplementedError |
| |
| |
| def bypass_dev_boot_usb(self): |
| """Bypass the dev mode firmware logic to boot USB.""" |
| raise NotImplementedError |
| |
| |
| def bypass_rec_mode(self): |
| """Bypass the rec mode firmware logic to boot USB.""" |
| raise NotImplementedError |
| |
| |
| def trigger_dev_to_rec(self): |
| """Trigger to the rec mode from the dev screen.""" |
| raise NotImplementedError |
| |
| |
| def trigger_rec_to_dev(self): |
| """Trigger to the dev mode from the rec screen.""" |
| raise NotImplementedError |
| |
| |
| def trigger_dev_to_normal(self): |
| """Trigger to the normal mode from the dev screen.""" |
| raise NotImplementedError |
| |
| |
| class _KeyboardBypasser(_BaseFwBypasser): |
| """Controls bypass logic via keyboard shortcuts for menu UI.""" |
| |
| def bypass_dev_mode(self): |
| """Bypass the dev mode firmware logic to boot internal image. |
| |
| Press Ctrl-D repeatedly. To obtain a low firmware boot time, pressing |
| Ctrl+D for every half second until firmware_screen delay has been |
| reached. |
| """ |
| logging.info("Pressing Ctrl-D.") |
| # At maximum, device waits for twice of firmware_screen delay to |
| # bypass the Dev screen. |
| timeout = time.time() + (self.faft_config.firmware_screen * 2) |
| while time.time() < timeout: |
| self.servo.ctrl_d() |
| time.sleep(0.5) |
| if self.client_host.ping_wait_up(timeout=0.1): |
| break |
| |
| |
| def bypass_dev_boot_usb(self): |
| """Bypass the dev mode firmware logic to boot USB.""" |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.ctrl_u() |
| |
| |
| def bypass_rec_mode(self): |
| """Bypass the rec mode firmware logic to boot USB.""" |
| self.servo.switch_usbkey('host') |
| time.sleep(self.faft_config.usb_plug) |
| self.servo.switch_usbkey('dut') |
| logging.info('Enabled dut_sees_usb') |
| if not self.client_host.ping_wait_up( |
| timeout=self.faft_config.delay_reboot_to_ping): |
| logging.info('ping timed out, try REC_ON') |
| psc = self.servo.get_power_state_controller() |
| psc.power_on(psc.REC_ON) |
| |
| |
| def trigger_dev_to_rec(self): |
| """Trigger to the to-norm screen from the dev screen.""" |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.ctrl_s() |
| |
| |
| def trigger_rec_to_dev(self): |
| """Trigger to the dev mode from the rec screen.""" |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.ctrl_d() |
| time.sleep(self.faft_config.confirm_screen) |
| if self.faft_config.rec_button_dev_switch: |
| logging.info('RECOVERY button pressed to switch to dev mode') |
| self.servo.toggle_recovery_switch() |
| elif self.faft_config.power_button_dev_switch: |
| logging.info('POWER button pressed to switch to dev mode') |
| self.servo.power_normal_press() |
| else: |
| logging.info('ENTER pressed to switch to dev mode') |
| self.servo.enter_key() |
| |
| |
| def trigger_dev_to_normal(self): |
| """Trigger to the normal mode from the dev screen.""" |
| # Navigate to to-norm screen |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.ctrl_s() |
| # Select "Confirm" |
| time.sleep(self.faft_config.confirm_screen) |
| self.servo.enter_key() |
| |
| |
| class _LegacyKeyboardBypasser(_KeyboardBypasser): |
| """Controls bypass logic via keyboard shortcuts for legacy clamshell UI.""" |
| |
| def trigger_dev_to_rec(self): |
| """Trigger to the to-norm screen from the dev screen.""" |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.enter_key() |
| |
| def trigger_dev_to_normal(self): |
| """Trigger to the normal mode from the dev screen.""" |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.enter_key() |
| time.sleep(self.faft_config.confirm_screen) |
| self.servo.enter_key() |
| |
| |
| class _JetstreamBypasser(_BaseFwBypasser): |
| """Controls bypass logic of Jetstream devices.""" |
| |
| def bypass_dev_mode(self): |
| """Bypass the dev mode firmware logic to boot internal image.""" |
| # Jetstream does nothing to bypass. |
| pass |
| |
| |
| def bypass_dev_boot_usb(self): |
| """Bypass the dev mode firmware logic to boot USB.""" |
| self.servo.switch_usbkey('dut') |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.toggle_development_switch() |
| |
| |
| def bypass_rec_mode(self): |
| """Bypass the rec mode firmware logic to boot USB.""" |
| self.servo.switch_usbkey('host') |
| time.sleep(self.faft_config.usb_plug) |
| self.servo.switch_usbkey('dut') |
| if not self.client_host.ping_wait_up( |
| timeout=self.faft_config.delay_reboot_to_ping): |
| psc = self.servo.get_power_state_controller() |
| psc.power_on(psc.REC_ON) |
| |
| |
| def trigger_dev_to_rec(self): |
| """Trigger to the rec mode from the dev screen.""" |
| # Jetstream does not have this triggering logic. |
| raise NotImplementedError |
| |
| |
| def trigger_rec_to_dev(self): |
| """Trigger to the dev mode from the rec screen.""" |
| self.servo.disable_development_mode() |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.toggle_development_switch() |
| |
| |
| def trigger_dev_to_normal(self): |
| """Trigger to the normal mode from the dev screen.""" |
| # Jetstream does not have this triggering logic. |
| raise NotImplementedError |
| |
| |
| class _TabletDetachableBypasser(_BaseFwBypasser): |
| """Controls bypass logic of tablet/ detachable chromebook devices.""" |
| |
| def set_button(self, button, duration, info): |
| """Helper method that sets the button hold time for UI selections""" |
| self.servo.set_nocheck(button, duration) |
| time.sleep(self.faft_config.confirm_screen) |
| logging.info(info) |
| |
| |
| def bypass_dev_boot_usb(self): |
| """Bypass the dev mode firmware logic to boot USB. |
| |
| On tablets/ detachables, recovery entered by pressing pwr, vol up |
| & vol down buttons for 10s. |
| Menu options seen in DEVELOPER WARNING screen: |
| Developer Options |
| Show Debug Info |
| Enable Root Verification |
| Power Off* |
| Language |
| Menu options seen in DEV screen: |
| Boot legacy BIOS |
| Boot USB image |
| Boot developer image* |
| Cancel |
| Power off |
| Language |
| |
| Vol up button selects previous item, vol down button selects |
| next item and pwr button selects current activated item. |
| |
| Note: if dev_default_boot=usb, the default selection will start on USB, |
| and this will move up one to legacy boot instead. |
| """ |
| self.trigger_dev_screen() |
| time.sleep(self.faft_config.firmware_screen) |
| self.set_button('volume_up_hold', 100, ('Selecting power as' |
| ' enter key to select Boot USB Image')) |
| self.servo.power_short_press() |
| |
| def bypass_dev_default_boot(self): |
| """Open the Developer Options menu, and accept the default boot device |
| |
| Menu options seen in DEVELOPER WARNING screen: |
| Developer Options |
| Show Debug Info |
| Enable Root Verification |
| Power Off* |
| Language |
| Menu options seen in DEV screen: |
| Boot legacy BIOS* (default if dev_default_boot=legacy) |
| Boot USB image* (default if dev_default_boot=usb) |
| Boot developer image* (default if dev_default_boot=disk) |
| Cancel |
| Power off |
| Language |
| |
| Vol up button selects previous item, vol down button selects |
| next item and pwr button selects current activated item. |
| """ |
| self.trigger_dev_screen() |
| time.sleep(self.faft_config.firmware_screen) |
| logging.info('Selecting power as enter key to accept the default' |
| ' boot option.') |
| self.servo.power_short_press() |
| |
| def bypass_rec_mode(self): |
| """Bypass the rec mode firmware logic to boot USB.""" |
| self.servo.switch_usbkey('host') |
| time.sleep(self.faft_config.usb_plug) |
| self.servo.switch_usbkey('dut') |
| logging.info('Enabled dut_sees_usb') |
| if not self.client_host.ping_wait_up( |
| timeout=self.faft_config.delay_reboot_to_ping): |
| logging.info('ping timed out, try REC_ON') |
| psc = self.servo.get_power_state_controller() |
| psc.power_on(psc.REC_ON) |
| |
| |
| def bypass_dev_mode(self): |
| """Bypass the developer warning screen immediately to boot into |
| internal disk. |
| |
| On tablets/detachables, press & holding the Volume down button for |
| 3-seconds will quickly bypass the developer warning screen. |
| """ |
| # Unit for the "volume_down_hold" console command is msec. |
| duration = (self.HOLD_VOL_DOWN_BUTTON_BYPASS + 0.1) * 1000 |
| logging.info("Press and hold volume down button for %.1f seconds to " |
| "immediately bypass the Developer warning screen.", |
| self.HOLD_VOL_DOWN_BUTTON_BYPASS + 0.1) |
| # At maximum, device waits for twice of firmware_screen delay to |
| # bypass the Dev screen. |
| timeout = time.time() + (self.faft_config.firmware_screen * 2) |
| # To obtain a low firmware boot time, volume_down button pressed for |
| # every 3.1 seconds until firmware_screen delay has been reached. |
| while time.time() < timeout: |
| self.servo.set_nocheck('volume_down_hold', duration) |
| # After pressing 'volume_down_hold' button, wait for 0.1 seconds |
| # before start pressing the button for next iteration. |
| time.sleep(0.1) |
| if self.client_host.ping_wait_up(timeout=0.1): |
| break |
| |
| |
| def trigger_dev_screen(self): |
| """Helper method that transitions from DEVELOPER WARNING to DEV screen |
| |
| Menu options seen in DEVELOPER WARNING screen: |
| Developer Options |
| Show Debug Info |
| Enable Root Verification |
| Power Off* |
| Language |
| Menu options seen in DEV screen: |
| Boot legacy BIOS |
| Boot USB image |
| Boot developer image* |
| Cancel |
| Power off |
| Language |
| Vol up button selects previous item, vol down button selects |
| next item and pwr button selects current activated item. |
| """ |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.set_nocheck('volume_up_hold', 100) |
| time.sleep(self.faft_config.confirm_screen) |
| self.servo.set_nocheck('volume_up_hold', 100) |
| time.sleep(self.faft_config.confirm_screen) |
| self.set_button('volume_up_hold', 100, ('Selecting power ' |
| 'as enter key to select Developer Options')) |
| self.servo.power_short_press() |
| |
| |
| def trigger_rec_to_dev(self): |
| """Trigger to the dev mode from the rec screen using vol up button. |
| |
| On tablets/ detachables, recovery entered by pressing pwr, vol up |
| & vol down buttons for 10s. TO_DEV screen is entered by pressing |
| vol up & vol down buttons together on the INSERT screen. |
| Menu options seen in TO_DEV screen: |
| Confirm enabling developer mode |
| Cancel* |
| Power off |
| Language |
| Vol up button selects previous item, vol down button selects |
| next item and pwr button selects current activated item. |
| """ |
| time.sleep(self.faft_config.firmware_screen) |
| self.set_button('volume_up_down_hold', 100, ('Enter Recovery Menu.')) |
| time.sleep(self.faft_config.confirm_screen) |
| self.set_button('volume_up_hold', 100, ('Selecting power as ' |
| 'enter key to select Confirm Enabling Developer Mode')) |
| self.servo.power_short_press() |
| time.sleep(self.faft_config.firmware_screen) |
| |
| |
| def trigger_dev_to_normal(self): |
| """Trigger to the normal mode from the dev screen. |
| |
| Menu options seen in DEVELOPER WARNING screen: |
| Developer Options |
| Show Debug Info |
| Enable Root Verification |
| Power Off* |
| Language |
| Menu options seen in TO_NORM screen: |
| Confirm Enabling Verified Boot* |
| Cancel |
| Power off |
| Language |
| Vol up button selects previous item, vol down button selects |
| next item and pwr button selects current activated item. |
| """ |
| time.sleep(self.faft_config.firmware_screen) |
| self.set_button('volume_up_hold', 100, ('Selecting ' |
| 'Enable Root Verification using pwr ' |
| 'button to enter TO_NORM screen')) |
| self.servo.power_short_press() |
| logging.info('Transitioning from DEV to TO_NORM screen.') |
| time.sleep(self.faft_config.firmware_screen) |
| logging.info('Selecting Confirm Enabling Verified ' |
| 'Boot using pwr button in ' |
| 'TO_NORM screen') |
| self.servo.power_short_press() |
| |
| def trigger_dev_to_rec(self): |
| """Trigger to the TO_NORM screen from the dev screen. |
| Menu options seen in DEVELOPER WARNING screen: |
| Developer Options |
| Show Debug Info |
| Enable Root Verification |
| Power Off* |
| Language |
| Menu options seen in TO_NORM screen: |
| Confirm Enabling Verified Boot* |
| Cancel |
| Power off |
| Language |
| Vol up button selects previous item, vol down button selects |
| next item and pwr button selects current activated item. |
| """ |
| time.sleep(self.faft_config.firmware_screen) |
| self.set_button('volume_up_hold', 100, ('Selecting ' |
| 'Enable Root Verification using pwr ' |
| 'button to enter TO_NORM screen')) |
| self.servo.power_short_press() |
| logging.info('Transitioning from DEV to TO_NORM screen.') |
| time.sleep(self.faft_config.firmware_screen) |
| |
| # In firmware_FwScreenPressPower, test will power off the DUT using |
| # Power button in second screen (TO_NORM screen) so scrolling to |
| # Power-off is necessary in this case. Hence scroll to Power-off as |
| # a generic action and wait for next action of either Lid close or |
| # power button press. |
| self.servo.set_nocheck('volume_down_hold', 100) |
| time.sleep(self.faft_config.confirm_screen) |
| self.servo.set_nocheck('volume_down_hold', 100) |
| time.sleep(self.faft_config.confirm_screen) |
| |
| |
| class _BaseModeSwitcher(object): |
| """Base class that controls firmware mode switching.""" |
| |
| HOLD_VOL_DOWN_BUTTON_BYPASS = _BaseFwBypasser.HOLD_VOL_DOWN_BUTTON_BYPASS |
| |
| FW_BYPASSER_CLASS = _BaseFwBypasser |
| |
| def __init__(self, faft_framework): |
| self.faft_framework = faft_framework |
| self.client_host = faft_framework._client |
| self.faft_client = faft_framework.faft_client |
| self.servo = faft_framework.servo |
| self.faft_config = faft_framework.faft_config |
| self.checkers = faft_framework.checkers |
| self.bypasser = self._create_fw_bypasser() |
| self._backup_mode = None |
| |
| def _create_fw_bypasser(self): |
| """Creates a proper firmware bypasser. |
| |
| @rtype: _BaseFwBypasser |
| """ |
| return self.FW_BYPASSER_CLASS(self.faft_framework) |
| |
| def setup_mode(self, mode): |
| """Setup for the requested mode. |
| |
| It makes sure the system in the requested mode. If not, it tries to |
| do so. |
| |
| @param mode: A string of mode, one of 'normal', 'dev', or 'rec'. |
| @raise TestFail: If the system not switched to expected mode after |
| reboot_to_mode. |
| |
| """ |
| if not self.checkers.mode_checker(mode): |
| logging.info('System not in expected %s mode. Reboot into it.', |
| mode) |
| if self._backup_mode is None: |
| # Only resume to normal/dev mode after test, not recovery. |
| self._backup_mode = 'dev' if mode == 'normal' else 'normal' |
| self.reboot_to_mode(mode) |
| if not self.checkers.mode_checker(mode): |
| raise error.TestFail('System not switched to expected %s' |
| ' mode after setup_mode.' % mode) |
| |
| def restore_mode(self): |
| """Restores original dev mode status if it has changed. |
| |
| @raise TestFail: If the system not restored to expected mode. |
| """ |
| if (self._backup_mode is not None and |
| not self.checkers.mode_checker(self._backup_mode)): |
| self.reboot_to_mode(self._backup_mode) |
| if not self.checkers.mode_checker(self._backup_mode): |
| raise error.TestFail('System not restored to expected %s' |
| ' mode in cleanup.' % self._backup_mode) |
| |
| |
| |
| def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True, |
| wait_for_dut_up=True): |
| """Reboot and execute the mode switching sequence. |
| |
| This method simulates what a user would do to switch between different |
| modes of ChromeOS. Note that the modes are end-states where the OS is |
| booted up to the Welcome screen, so it takes care of navigating through |
| intermediate steps such as various boot confirmation screens. |
| |
| From the user perspective, these are the states (note that there's also |
| a rec_force_mrc mode which is like rec mode but forces MRC retraining): |
| |
| normal <-----> dev <------ rec |
| ^ ^ |
| | | |
| +-------------------------+ |
| |
| This is the implementation, note that "from_mode" is only used for |
| logging purposes. |
| |
| Normal <-----> Dev: |
| _enable_dev_mode_and_reboot() |
| |
| Rec,normal -----> Dev: |
| _disable_rec_mode_and_reboot() |
| |
| Any -----> normal: |
| _enable_normal_mode_and_reboot() |
| |
| Normal <-----> rec: |
| enable_rec_mode_and_reboot(usb_state='dut') |
| |
| Normal <-----> rec_force_mrc: |
| _enable_rec_mode_force_mrc_and_reboot(usb_state='dut') |
| |
| Note that one shouldn't transition to dev again without going through the |
| normal mode. This is because trying to disable os_verification when it's |
| already off is not supported by reboot_to_mode. |
| |
| @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'. |
| @param from_mode: The original mode, optional, one of 'normal, 'dev', |
| or 'rec'. |
| @param sync_before_boot: True to sync to disk before booting. |
| @param wait_for_dut_up: True to wait DUT online again. False to do the |
| reboot and mode switching sequence only and may |
| need more operations to pass the firmware |
| screen. |
| """ |
| logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-', |
| to_mode, from_mode, wait_for_dut_up) |
| |
| if sync_before_boot: |
| self.faft_framework.blocking_sync() |
| if to_mode == 'rec': |
| self.enable_rec_mode_and_reboot(usb_state='dut') |
| if wait_for_dut_up: |
| self.wait_for_client() |
| |
| elif to_mode == 'rec_force_mrc': |
| self._enable_rec_mode_force_mrc_and_reboot(usb_state='dut') |
| if wait_for_dut_up: |
| self.wait_for_client() |
| |
| elif to_mode == 'dev': |
| if sync_before_boot: |
| lines = self.faft_client.system.run_shell_command_get_output( |
| 'crossystem') |
| logging.debug('-[ModeSwitcher]- crossystem output:\n%s', |
| '\n'.join(lines)) |
| devsw_cur = self.faft_client.system.get_crossystem_value( |
| 'devsw_cur') |
| else: |
| devsw_cur = 'N/A' |
| self._enable_dev_mode_and_reboot() |
| if wait_for_dut_up: |
| self.bypass_dev_mode() |
| try: |
| self.wait_for_client() |
| except ConnectionError as e: |
| raise ConnectionError('{} devsw_cur: {}'.format(e, |
| devsw_cur)) |
| |
| elif to_mode == 'normal': |
| self._enable_normal_mode_and_reboot() |
| if wait_for_dut_up: |
| self.wait_for_client() |
| |
| else: |
| raise NotImplementedError( |
| 'Not supported mode switching from %s to %s' % |
| (str(from_mode), to_mode)) |
| logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-', |
| to_mode, from_mode, wait_for_dut_up) |
| |
| def simple_reboot(self, reboot_type='warm', sync_before_boot=True): |
| """Simple reboot method |
| |
| Just reboot the DUT using either cold or warm reset. Does not wait for |
| DUT to come back online. Will wait for test to handle this. |
| |
| @param reboot_type: A string of reboot type, 'warm' or 'cold'. |
| If reboot_type != warm/cold, raise exception. |
| @param sync_before_boot: True to sync to disk before booting. |
| If sync_before_boot=False, DUT offline before |
| calling mode_aware_reboot. |
| """ |
| if reboot_type == 'warm': |
| reboot_method = self.servo.get_power_state_controller().warm_reset |
| elif reboot_type == 'cold': |
| reboot_method = self.servo.get_power_state_controller().reset |
| else: |
| raise NotImplementedError('Not supported reboot_type: %s', |
| reboot_type) |
| if sync_before_boot: |
| boot_id = self.faft_framework.get_bootid() |
| self.faft_framework.blocking_sync() |
| logging.info("-[ModeSwitcher]-[ start simple_reboot(%r) ]-", |
| reboot_type) |
| reboot_method() |
| if sync_before_boot: |
| self.wait_for_client_offline(orig_boot_id=boot_id) |
| logging.info("-[ModeSwitcher]-[ end simple_reboot(%r) ]-", |
| reboot_type) |
| |
| def mode_aware_reboot(self, reboot_type=None, reboot_method=None, |
| sync_before_boot=True, wait_for_dut_up=True): |
| """Uses a mode-aware way to reboot DUT. |
| |
| For example, if DUT is in dev mode, it requires pressing Ctrl-D to |
| bypass the developer screen. |
| |
| @param reboot_type: A string of reboot type, one of 'warm', 'cold', or |
| 'custom'. Default is a warm reboot. |
| @param reboot_method: A custom method to do the reboot. Only use it if |
| reboot_type='custom'. |
| @param sync_before_boot: True to sync to disk before booting. |
| If sync_before_boot=False, DUT offline before |
| calling mode_aware_reboot. |
| @param wait_for_dut_up: True to wait DUT online again. False to do the |
| reboot only. |
| """ |
| if reboot_type is None or reboot_type == 'warm': |
| reboot_method = self.servo.get_power_state_controller().warm_reset |
| elif reboot_type == 'cold': |
| reboot_method = self.servo.get_power_state_controller().reset |
| elif reboot_type != 'custom': |
| raise NotImplementedError('Not supported reboot_type: %s', |
| reboot_type) |
| |
| logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-", |
| reboot_type, reboot_method.__name__) |
| is_dev = is_rec = is_devsw_boot = False |
| if sync_before_boot: |
| is_dev = self.checkers.mode_checker('dev') |
| is_rec = self.checkers.mode_checker('rec') |
| is_devsw_boot = self.checkers.crossystem_checker( |
| {'devsw_boot': '1'}, True) |
| boot_id = self.faft_framework.get_bootid() |
| self.faft_framework.blocking_sync() |
| if is_rec: |
| logging.info("-[mode_aware_reboot]-[ is_rec=%s is_dev_switch=%s ]-", |
| is_rec, is_devsw_boot) |
| else: |
| logging.info("-[mode_aware_reboot]-[ is_dev=%s ]-", is_dev) |
| reboot_method() |
| if sync_before_boot: |
| self.wait_for_client_offline(orig_boot_id=boot_id) |
| # Encapsulating the behavior of skipping dev firmware screen, |
| # hitting ctrl-D |
| # Note that if booting from recovery mode, we can predict the next |
| # boot based on the developer switch position at boot (devsw_boot). |
| # If devsw_boot is True, we will call bypass_dev_mode after reboot. |
| if is_dev or is_devsw_boot: |
| self.bypass_dev_mode() |
| if wait_for_dut_up: |
| self.wait_for_client() |
| logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-", |
| reboot_type, reboot_method.__name__) |
| |
| |
| def enable_rec_mode_and_reboot(self, usb_state=None): |
| """Switch to rec mode and reboot. |
| |
| This method emulates the behavior of the old physical recovery switch, |
| i.e. switch ON + reboot + switch OFF, and the new keyboard controlled |
| recovery mode, i.e. just press Power + Esc + Refresh. |
| |
| @param usb_state: A string, one of 'dut', 'host', or 'off'. |
| """ |
| psc = self.servo.get_power_state_controller() |
| psc.power_off() |
| if usb_state: |
| self.servo.switch_usbkey(usb_state) |
| psc.power_on(psc.REC_ON) |
| |
| |
| def _enable_rec_mode_force_mrc_and_reboot(self, usb_state=None): |
| """Switch to rec mode, enable force mrc cache retraining, and reboot. |
| |
| This method emulates the behavior of the old physical recovery switch, |
| i.e. switch ON + reboot + switch OFF, and the new keyboard controlled |
| recovery mode, i.e. just press Power + Esc + Refresh. |
| |
| @param usb_state: A string, one of 'dut', 'host', or 'off'. |
| """ |
| psc = self.servo.get_power_state_controller() |
| psc.power_off() |
| if usb_state: |
| self.servo.switch_usbkey(usb_state) |
| psc.power_on(psc.REC_ON_FORCE_MRC) |
| |
| def _disable_rec_mode_and_reboot(self, usb_state=None): |
| """Disable the rec mode and reboot. |
| |
| It is achieved by calling power state controller to do a normal |
| power on. |
| """ |
| psc = self.servo.get_power_state_controller() |
| psc.power_off() |
| time.sleep(self.faft_config.ec_boot_to_pwr_button) |
| psc.power_on(psc.REC_OFF) |
| |
| |
| def _enable_dev_mode_and_reboot(self): |
| """Switch to developer mode and reboot.""" |
| raise NotImplementedError |
| |
| |
| def _enable_normal_mode_and_reboot(self): |
| """Switch to normal mode and reboot.""" |
| raise NotImplementedError |
| |
| |
| # Redirects the following methods to FwBypasser |
| def bypass_dev_mode(self): |
| """Bypass the dev mode firmware logic to boot internal image.""" |
| logging.info("-[bypass_dev_mode]-") |
| self.bypasser.bypass_dev_mode() |
| |
| |
| def bypass_dev_boot_usb(self): |
| """Bypass the dev mode firmware logic to boot USB.""" |
| logging.info("-[bypass_dev_boot_usb]-") |
| self.bypasser.bypass_dev_boot_usb() |
| |
| |
| def bypass_rec_mode(self): |
| """Bypass the rec mode firmware logic to boot USB.""" |
| logging.info("-[bypass_rec_mode]-") |
| self.bypasser.bypass_rec_mode() |
| |
| |
| def trigger_dev_to_rec(self): |
| """Trigger to the rec mode from the dev screen.""" |
| self.bypasser.trigger_dev_to_rec() |
| |
| |
| def trigger_rec_to_dev(self): |
| """Trigger to the dev mode from the rec screen.""" |
| self.bypasser.trigger_rec_to_dev() |
| |
| |
| def trigger_dev_to_normal(self): |
| """Trigger to the normal mode from the dev screen.""" |
| self.bypasser.trigger_dev_to_normal() |
| |
| |
| def wait_for_client(self, timeout=180): |
| """Wait for the client to come back online. |
| |
| New remote processes will be launched if their used flags are enabled. |
| |
| @param timeout: Time in seconds to wait for the client SSH daemon to |
| come up. |
| @raise ConnectionError: Failed to connect DUT. |
| """ |
| logging.info("-[FAFT]-[ start wait_for_client ]---") |
| # Wait for the system to respond to ping before attempting ssh |
| if not self.client_host.ping_wait_up(timeout): |
| logging.warning("-[FAFT]-[ system did not respond to ping ]") |
| if self.client_host.wait_up(timeout): |
| # Check the FAFT client is avaiable. |
| self.faft_client.system.is_available() |
| # Stop update-engine as it may change firmware/kernel. |
| self.faft_framework.faft_client.updater.stop_daemon() |
| else: |
| logging.error('wait_for_client() timed out.') |
| power_state = self.faft_framework.get_power_state() |
| if power_state: |
| raise ConnectionError('DUT is still down unexpectedly.' |
| ' Power state: %s' % power_state) |
| else: |
| raise ConnectionError('DUT is still down unexpectedly') |
| logging.info("-[FAFT]-[ end wait_for_client ]-----") |
| |
| |
| def wait_for_client_offline(self, timeout=60, orig_boot_id=None): |
| """Wait for the client to come offline. |
| |
| @param timeout: Time in seconds to wait the client to come offline. |
| @param orig_boot_id: A string containing the original boot id. |
| @raise ConnectionError: Failed to wait DUT offline. |
| """ |
| # When running against panther, we see that sometimes |
| # ping_wait_down() does not work correctly. There needs to |
| # be some investigation to the root cause. |
| # If we sleep for 120s before running get_boot_id(), it |
| # does succeed. But if we change this to ping_wait_down() |
| # there are implications on the wait time when running |
| # commands at the fw screens. |
| if not self.client_host.ping_wait_down(timeout): |
| if orig_boot_id and self.client_host.get_boot_id() != orig_boot_id: |
| logging.warn('Reboot done very quickly.') |
| return |
| raise ConnectionError('DUT is still up unexpectedly') |
| |
| |
| class _MenuSwitcher(_BaseModeSwitcher): |
| """Mode switcher via keyboard shortcuts for menu UI.""" |
| |
| FW_BYPASSER_CLASS = _KeyboardBypasser |
| |
| def _enable_dev_mode_and_reboot(self): |
| """Switch to developer mode and reboot.""" |
| logging.info("Enabling keyboard controlled developer mode") |
| # Rebooting EC with rec mode on. Should power on AP. |
| # Plug out USB disk for preventing recovery boot without warning |
| self.enable_rec_mode_and_reboot(usb_state='host') |
| self.wait_for_client_offline() |
| self.bypasser.trigger_rec_to_dev() |
| |
| def _enable_normal_mode_and_reboot(self): |
| """Switch to normal mode and reboot.""" |
| logging.info("Disabling keyboard controlled developer mode") |
| self._disable_rec_mode_and_reboot() |
| self.wait_for_client_offline() |
| self.bypasser.trigger_dev_to_normal() |
| |
| |
| class _KeyboardDevSwitcher(_MenuSwitcher): |
| """Mode switcher via keyboard shortcuts for legacy clamshell UI.""" |
| |
| FW_BYPASSER_CLASS = _LegacyKeyboardBypasser |
| |
| |
| class _JetstreamSwitcher(_BaseModeSwitcher): |
| """Mode switcher for Jetstream devices.""" |
| |
| FW_BYPASSER_CLASS = _JetstreamBypasser |
| |
| def _enable_dev_mode_and_reboot(self): |
| """Switch to developer mode and reboot.""" |
| logging.info("Enabling Jetstream developer mode") |
| self.enable_rec_mode_and_reboot(usb_state='host') |
| self.wait_for_client_offline() |
| self.bypasser.trigger_rec_to_dev() |
| |
| def _enable_normal_mode_and_reboot(self): |
| """Switch to normal mode and reboot.""" |
| logging.info("Disabling Jetstream developer mode") |
| self.servo.disable_development_mode() |
| self.enable_rec_mode_and_reboot(usb_state='host') |
| time.sleep(self.faft_config.firmware_screen) |
| self._disable_rec_mode_and_reboot(usb_state='host') |
| |
| |
| class _TabletDetachableSwitcher(_BaseModeSwitcher): |
| """Mode switcher for legacy menu UI.""" |
| |
| FW_BYPASSER_CLASS = _TabletDetachableBypasser |
| |
| def _enable_dev_mode_and_reboot(self): |
| """Switch to developer mode and reboot. |
| |
| On tablets/ detachables, recovery entered by pressing pwr, vol up |
| & vol down buttons for 10s. |
| Menu options seen in RECOVERY screen: |
| Enable Developer Mode |
| Show Debug Info |
| Power off* |
| Language |
| """ |
| logging.info('Enabling tablets/detachable recovery mode') |
| self.enable_rec_mode_and_reboot(usb_state='host') |
| self.wait_for_client_offline() |
| self.bypasser.trigger_rec_to_dev() |
| |
| def _enable_normal_mode_and_reboot(self): |
| """Switch to normal mode and reboot. |
| |
| Menu options seen in DEVELOPER WARNING screen: |
| Developer Options |
| Show Debug Info |
| Enable Root Verification |
| Power Off* |
| Language |
| Menu options seen in TO_NORM screen: |
| Confirm Enabling Verified Boot |
| Cancel |
| Power off* |
| Language |
| Vol up button selects previous item, vol down button selects |
| next item and pwr button selects current activated item. |
| """ |
| self._disable_rec_mode_and_reboot() |
| self.wait_for_client_offline() |
| self.bypasser.trigger_dev_to_normal() |
| |
| def bypass_dev_default_boot(self): |
| """Accept the default boot device from the Developer Options screen.""" |
| self.bypasser.bypass_dev_default_boot() |
| |
| |
| _SWITCHER_CLASSES = { |
| 'menu_switcher': _MenuSwitcher, |
| 'keyboard_dev_switcher': _KeyboardDevSwitcher, |
| 'jetstream_switcher': _JetstreamSwitcher, |
| 'tablet_detachable_switcher': _TabletDetachableSwitcher, |
| } |
| |
| |
| def create_mode_switcher(faft_framework): |
| """Creates a proper mode switcher. |
| |
| @param faft_framework: The main FAFT framework object. |
| """ |
| switcher_type = faft_framework.faft_config.mode_switcher_type |
| switcher_class = _SWITCHER_CLASSES.get(switcher_type, None) |
| if switcher_class is None: |
| raise NotImplementedError('Not supported mode_switcher_type: %s', |
| switcher_type) |
| else: |
| return switcher_class(faft_framework) |