You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1025 lines
41 KiB
1025 lines
41 KiB
# 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)
|