# 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 DEBOUNCE_STATE = 'debouncing' 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.faft_framework = faft_framework self.servo = faft_framework.servo self.faft_config = faft_framework.faft_config self.client_host = faft_framework._client self.ec = getattr(faft_framework, 'ec', None) 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 # This is used as a workaround of a bug in RO - DUT does not supply # Vbus when in SRC_ACCESSORY state (we set servo to snk before booting # to recovery due to the assumption of no PD in RO). It causes that DUT can # not see USB Stick in recovery mode(RO) despite being DFP(b/159938441). # The bug in RO has been fixed in 251212fb. # Some boards already have it in RO, so the issue does not appear def check_vbus_and_pd_state(self): """Perform PD power and data swap, if DUT is SRC and doesn't supply Vbus""" if self.ec and self.faft_config.ec_ro_vbus_bug: time.sleep(self.faft_framework.PD_RESYNC_DELAY) servo_pr_role = self.servo.get_servo_v4_role() if servo_pr_role == 'snk': mv = self.servo.get_vbus_voltage() # Despite the faft_config, make sure the issue occurs - # servo is snk and vbus is not supplied. if mv is not None and mv < self.servo.VBUS_THRESHOLD: # Make servo SRC to supply Vbus correctly self.servo.set_servo_v4_role('src') time.sleep(self.faft_framework.PD_RESYNC_DELAY) # After reboot, EC can be UFP so check that if not self.ec.is_dfp(): # EC is UFP, perform PD Data Swap self.ec.send_command("pd 0 swap data") time.sleep(self.faft_framework.PD_RESYNC_DELAY) # Make sure EC is DFP now if not self.ec.is_dfp(): # EC is still UFP raise error.TestError('DUT is not DFP in recovery mode.') 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.""" self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+u') self.servo.ctrl_u() def bypass_dev_default_boot(self): """Bypass the dev mode firmware logic to boot from default target.""" self.faft_framework.wait_for('firmware_screen', 'Pressing enter') self.servo.enter_key() def bypass_rec_mode(self): """Bypass the rec mode firmware logic to boot USB.""" self.servo.switch_usbkey('host') self.faft_framework.wait_for('usb_plug', 'Switching usb key to DUT') self.check_vbus_and_pd_state() 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) # Check Vbus after reboot again self.check_vbus_and_pd_state() def trigger_dev_to_rec(self): """Trigger to the to-norm screen from the dev screen.""" self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+s') self.servo.ctrl_s() def trigger_rec_to_dev(self): """Trigger to the dev mode from the rec screen.""" self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+d') self.servo.ctrl_d() self.faft_framework.wait_for('confirm_screen', 'Pressing button to switch to dev mode') 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 self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+s') self.servo.ctrl_s() # Select "Confirm" self.faft_framework.wait_for('confirm_screen', 'Pressing enter') 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.""" self.faft_framework.wait_for('firmware_screen', 'Pressing enter') self.servo.enter_key() def trigger_dev_to_normal(self): """Trigger to the normal mode from the dev screen.""" self.faft_framework.wait_for('firmware_screen', 'Pressing enter') self.servo.enter_key() self.faft_framework.wait_for('confirm_screen', 'Pressing enter') 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') self.faft_framework.wait_for('firmware_screen', 'Toggling development switch') self.servo.toggle_development_switch() def bypass_rec_mode(self): """Bypass the rec mode firmware logic to boot USB.""" self.servo.switch_usbkey('host') self.faft_framework.wait_for('usb_plug', 'Switching usb key to DUT') self.check_vbus_and_pd_state() 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) # Check Vbus after reboot again self.check_vbus_and_pd_state() 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() self.faft_framework.wait_for('firmware_screen', 'Toggling development switch') 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) self.faft_framework.wait_for('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() self.faft_framework.wait_for('firmware_screen', 'Pressing volume up') 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() self.faft_framework.wait_for('firmware_screen', 'Pressing power button') 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') self.faft_framework.wait_for('usb_plug', 'Switching usb key to DUT') self.check_vbus_and_pd_state() 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) # Check Vbus after reboot again self.check_vbus_and_pd_state() 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. """ self.faft_framework.wait_for('firmware_screen', 'Pressing volume up') self.servo.set_nocheck('volume_up_hold', 100) self.faft_framework.wait_for('confirm_screen', 'Pressing volume up') self.servo.set_nocheck('volume_up_hold', 100) self.faft_framework.wait_for('confirm_screen', 'Pressing volume up') 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. """ self.faft_framework.wait_for('firmware_screen', 'Pressing volume up + volume down') self.set_button('volume_up_down_hold', 100, ('Enter Recovery Menu.')) self.faft_framework.wait_for('confirm_screen', 'Pressing volume up') self.set_button('volume_up_hold', 100, ('Selecting power as ' 'enter key to select Confirm Enabling Developer Mode')) self.servo.power_short_press() self.faft_framework.wait_for('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. """ self.faft_framework.wait_for('firmware_screen', 'Pressing volume up') 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.') self.faft_framework.wait_for('firmware_screen', 'Pressing power button') 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. """ self.faft_framework.wait_for('firmware_screen', 'Pressing volume up') 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.') self.faft_framework.wait_for('firmware_screen', 'Pressing volume down') # 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) self.faft_framework.wait_for('confirm_screen', 'Pressing volume down') self.servo.set_nocheck('volume_down_hold', 100) self.faft_framework.wait_for('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 from_mode: note = 'reboot_to_mode: to=%s, from=%s' % (from_mode, to_mode) else: note = 'reboot_to_mode: to=%s' % to_mode 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') note += ', devsw_cur=%s' % devsw_cur self.faft_framework.blocking_sync(freeze_for_reset=True) note += '.' if to_mode == 'rec': self.enable_rec_mode_and_reboot(usb_state='dut') elif to_mode == 'rec_force_mrc': self._enable_rec_mode_force_mrc_and_reboot(usb_state='dut') elif to_mode == 'dev': self._enable_dev_mode_and_reboot() if wait_for_dut_up: self.bypass_dev_mode() elif to_mode == 'normal': self._enable_normal_mode_and_reboot() else: raise NotImplementedError( 'Not supported mode switching from %s to %s' % (str(from_mode), to_mode)) if wait_for_dut_up: self.wait_for_client(retry_power_on=True, note=note) 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(freeze_for_reset=True) 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(reboot_type != 'custom') 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() # Switch the USB key when AP is on, because there is a # bug (b/172909077) - using "image_usbkey_direction:usb_state", when # AP if off may cause not recognizing the file system, # so system won't boot in recovery from USB. # When the issue is fixed, it can be done when AP is off. if usb_state: self.servo.switch_usbkey(usb_state) psc.power_off() psc.power_on(psc.REC_ON) # Check VBUS and pd state only if we are going to boot # to ChromeOS in the recovery mode if usb_state == 'dut': self.bypasser.check_vbus_and_pd_state() 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() # Switch the USB key when AP is on, because there is a # bug (b/172909077) - using "image_usbkey_direction:usb_state", when # AP if off may cause not recognizing the file system, # so system won't boot in recovery from USB. # When the issue is fixed, it can be done when AP is off. if usb_state: self.servo.switch_usbkey(usb_state) psc.power_off() 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() self.faft_framework.wait_for('ec_boot_to_pwr_button', 'Powering on') 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_dev_default_boot(self): """Bypass the dev mode firmware logic to boot from default target.""" logging.info("-[bypass_dev_default_boot]-") self.bypasser.bypass_dev_default_boot() 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, retry_power_on=False, debounce_power_state=True, note=''): """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. @param retry_power_on: Try to power on the DUT if it isn't in S0. @param debounce_power_state: Wait until power_state is the same two times in a row to determine the actual power_state. @param note: Extra note to add to the end of the error text @raise ConnectionError: Failed to connect DUT. """ logging.info("-[FAFT]-[ start wait_for_client(%ds) ]---", timeout if retry_power_on else 0) # Wait for the system to be powered on before trying the network # Skip "None" result because that indicates lack of EC or problem # querying the power state. current_timer = 0 self.faft_framework.wait_for('delay_powerinfo_stable', 'checking power state') power_state = self.faft_framework.get_power_state() # The device may transition between states. Wait until the power state # is stable for two seconds before determining the state. if debounce_power_state: last_state = power_state power_state = DEBOUNCE_STATE while (timeout > current_timer and power_state not in (self.faft_framework.POWER_STATE_S0, None)): time.sleep(2) current_timer += 2 power_state = self.faft_framework.get_power_state() # If the state changed, debounce it. if debounce_power_state and power_state != last_state: last_state = power_state power_state = DEBOUNCE_STATE logging.info('power state: %s', power_state) # Only power-on the device if it has been consistently out of # S0. if (retry_power_on and power_state not in (self.faft_framework.POWER_STATE_S0, None, DEBOUNCE_STATE)): logging.info("-[FAFT]-[ retry powering on the DUT ]---") psc = self.servo.get_power_state_controller() psc.retry_power_on() # Use the last state if the device didn't reach a stable state in # timeout seconds. if power_state == DEBOUNCE_STATE: power_state = last_state if power_state not in (self.faft_framework.POWER_STATE_S0, None): msg = 'DUT unexpectedly down, power state is %s.' % power_state if note: msg += ' %s' % note raise ConnectionError(msg) # 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() msg = 'DUT is still down unexpectedly.' if power_state: msg += ' Power state: %s.' % power_state if note: msg += ' %s' % note raise ConnectionError(msg) 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') self.faft_framework.wait_for('firmware_screen', 'Disabling rec and rebooting') 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() _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)