| # 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 |
| |
| |
| class _BaseFwBypasser(object): |
| """Base class that controls bypass logic for firmware screens.""" |
| |
| def __init__(self, servo, faft_config): |
| self.servo = servo |
| self.faft_config = faft_config |
| |
| |
| 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 _CtrlDBypasser(_BaseFwBypasser): |
| """Controls bypass logic via Ctrl-D combo.""" |
| |
| def bypass_dev_mode(self): |
| """Bypass the dev mode firmware logic to boot internal image.""" |
| time.sleep(self.faft_config.firmware_screen) |
| self.servo.ctrl_d() |
| |
| |
| 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') |
| |
| |
| def trigger_dev_to_rec(self): |
| """Trigger to the rec mode from the dev screen.""" |
| time.sleep(self.faft_config.firmware_screen) |
| |
| # Pressing Enter for too long triggers a second key press. |
| # Let's press it without delay |
| self.servo.enter_key(press_secs=0) |
| |
| # For Alex/ZGB, there is a dev warning screen in text mode. |
| # Skip it by pressing Ctrl-D. |
| if self.faft_config.need_dev_transition: |
| time.sleep(self.faft_config.legacy_text_screen) |
| self.servo.ctrl_d() |
| |
| |
| 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() |
| 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.""" |
| 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.""" |
| # TODO: Confirm if it is a proper way to trigger dev boot USB. |
| # We can't verify it this time due to a bug that always boots into |
| # USB on dev mode. |
| self.servo.enable_development_mode() |
| 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') |
| |
| |
| 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 |
| |
| |
| def _create_fw_bypasser(servo, faft_config): |
| """Creates a proper firmware bypasser. |
| |
| @param servo: A servo object controlling the servo device. |
| @param faft_config: A FAFT config object, which describes the type of |
| firmware bypasser. |
| """ |
| bypasser_type = faft_config.fw_bypasser_type |
| if bypasser_type == 'ctrl_d_bypasser': |
| logging.info('Create a CtrlDBypasser') |
| return _CtrlDBypasser(servo, faft_config) |
| if bypasser_type == 'jetstream_bypasser': |
| logging.info('Create a JetstreamBypasser') |
| return _JetstreamBypasser(servo, faft_config) |
| else: |
| raise NotImplementedError('Not supported fw_bypasser_type: %s', |
| bypasser_type) |
| |
| |
| class _BaseModeSwitcher(object): |
| """Base class that controls firmware mode switching.""" |
| |
| def __init__(self, faft_framework): |
| self.faft_framework = faft_framework |
| 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 = _create_fw_bypasser(self.servo, self.faft_config) |
| self._backup_mode = None |
| |
| |
| 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'. |
| """ |
| 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) |
| |
| |
| def restore_mode(self): |
| """Restores original dev mode status if it has changed.""" |
| if self._backup_mode is not None: |
| self.reboot_to_mode(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. |
| |
| @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.bypasser.bypass_rec_mode() |
| self.faft_framework.wait_for_client() |
| |
| elif to_mode == 'dev': |
| self._enable_dev_mode_and_reboot() |
| if wait_for_dut_up: |
| self.bypasser.bypass_dev_mode() |
| self.faft_framework.wait_for_client() |
| |
| elif to_mode == 'normal': |
| self._enable_normal_mode_and_reboot() |
| if wait_for_dut_up: |
| self.faft_framework.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 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. |
| @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_normal = is_dev = False |
| if sync_before_boot: |
| if wait_for_dut_up: |
| is_normal = self.checkers.mode_checker('normal') |
| is_dev = self.checkers.mode_checker('dev') |
| boot_id = self.faft_framework.get_bootid() |
| self.faft_framework.blocking_sync() |
| reboot_method() |
| if sync_before_boot: |
| self.faft_framework.wait_for_client_offline(orig_boot_id=boot_id) |
| if wait_for_dut_up: |
| # For encapsulating the behavior of skipping firmware screen, |
| # e.g. requiring unplug and plug USB, the variants are not |
| # hard coded in tests. We keep this logic in this |
| # mode_aware_reboot method. |
| if not is_dev: |
| # In the normal/recovery boot flow, replugging USB does not |
| # affect the boot flow. But when something goes wrong, like |
| # firmware corrupted, it automatically leads to a recovery USB |
| # boot. |
| self.servo.switch_usbkey('host') |
| if not is_normal: |
| self.bypasser.bypass_dev_mode() |
| if not is_dev: |
| self.bypasser.bypass_rec_mode() |
| self.faft_framework.wait_for_kernel_up() |
| 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 _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() |
| 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.""" |
| self.bypasser.bypass_dev_mode() |
| |
| |
| def bypass_dev_boot_usb(self): |
| """Bypass the dev mode firmware logic to boot USB.""" |
| self.bypasser.bypass_dev_boot_usb() |
| |
| |
| def bypass_rec_mode(self): |
| """Bypass the rec mode firmware logic to boot USB.""" |
| 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() |
| |
| |
| class _PhysicalButtonSwitcher(_BaseModeSwitcher): |
| """Class that switches firmware mode via physical button.""" |
| |
| def _enable_dev_mode_and_reboot(self): |
| """Switch to developer mode and reboot.""" |
| self.servo.enable_development_mode() |
| self.faft_client.system.run_shell_command( |
| 'chromeos-firmwareupdate --mode todev && reboot') |
| |
| |
| def _enable_normal_mode_and_reboot(self): |
| """Switch to normal mode and reboot.""" |
| self.servo.disable_development_mode() |
| self.faft_client.system.run_shell_command( |
| 'chromeos-firmwareupdate --mode tonormal && reboot') |
| |
| |
| class _KeyboardDevSwitcher(_BaseModeSwitcher): |
| """Class that switches firmware mode via keyboard combo.""" |
| |
| 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.faft_framework.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.faft_framework.wait_for_client_offline() |
| self.bypasser.trigger_dev_to_normal() |
| |
| |
| class _JetstreamSwitcher(_BaseModeSwitcher): |
| """Class that switches firmware mode in Jetstream devices.""" |
| |
| 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.faft_framework.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') |
| |
| |
| 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 |
| if switcher_type == 'physical_button_switcher': |
| logging.info('Create a PhysicalButtonSwitcher') |
| return _PhysicalButtonSwitcher(faft_framework) |
| elif switcher_type == 'keyboard_dev_switcher': |
| logging.info('Create a KeyboardDevSwitcher') |
| return _KeyboardDevSwitcher(faft_framework) |
| elif switcher_type == 'jetstream_switcher': |
| logging.info('Create a JetstreamSwitcher') |
| return _JetstreamSwitcher(faft_framework) |
| else: |
| raise NotImplementedError('Not supported mode_switcher_type: %s', |
| switcher_type) |