blob: 57f5746f4cdf0c21694d8b6e0285e08e3ba2fc4f [file] [log] [blame]
# Copyright (c) 2013 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 time
from autotest_lib.server.cros.servo import chrome_ec
def _inherit_docstring(cls):
"""Decorator to propagate a docstring to a subclass's method.
@param cls Class with the method whose docstring is to be
inherited. The class must contain a method with
the same name as the name of the function to be
decorated.
"""
def _copy_docstring(methfunc):
"""Actually copy the parent docstring to the child.
@param methfunc Function that will inherit the docstring.
"""
methfunc.__doc__ = getattr(cls, methfunc.__name__).__doc__
return methfunc
return _copy_docstring
# Constants acceptable to be passed for the `rec_mode` parameter
# to power_on().
#
# REC_ON: Boot the DUT in recovery mode, i.e. boot from USB or
# SD card.
# REC_OFF: Boot in normal mode, i.e. boot from internal storage.
REC_ON = 'on'
REC_OFF = 'off'
class _PowerStateController(object):
"""Class to provide board-specific power operations.
This class is responsible for "power on" and "power off"
operations that can operate without making assumptions in
advance about board state. It offers an interface that
abstracts out the different sequences required for different
board types.
"""
# Delay in seconds needed between asserting and de-asserting cold
# or warm reset. Subclasses will normally override this constant
# with a board-specific value.
_RESET_HOLD_TIME = 0.5
# _EC_RESET_DELAY: Time required before the EC will be working
# after cold reset. Five seconds is at least twice as long as
# necessary for Alex, and is presumably good enough for all other
# systems.
_EC_RESET_DELAY = 5.0
def __init__(self, servo):
"""Initialize the power state control.
@param servo Servo object providing the underlying `set` and `get`
methods for the target controls.
"""
self._servo = servo
def cold_reset(self):
"""Apply cold reset to the DUT.
This asserts, then de-asserts the 'cold_reset' signal.
The exact affect on the hardware varies depending on
the board type.
"""
self._servo.set_get_all(['cold_reset:on',
'sleep:%.4f' % self._RESET_HOLD_TIME,
'cold_reset:off'])
# After the reset, give the EC the time it needs to
# re-initialize.
time.sleep(self._EC_RESET_DELAY)
def warm_reset(self):
"""Apply warm reset to the DUT.
This asserts, then de-asserts the 'warm_reset' signal.
Generally, this causes the board to restart.
"""
self._servo.set_get_all(['warm_reset:on',
'sleep:%.4f' % self._RESET_HOLD_TIME,
'warm_reset:off'])
def recovery_supported(self):
"""Return whether the power on/off methods are supported.
@return True means the power_on() and power_off() methods will
not raise a NotImplementedError. False means they will.
"""
return False
def power_off(self):
"""Force the DUT to power off.
The DUT is guaranteed to be off at the end of this call,
regardless of its previous state, provided that there is
working EC and boot firmware. There is no requirement for
working OS software.
"""
raise NotImplementedError()
def power_on(self, rec_mode=REC_OFF):
"""Force the DUT to power on.
Prior to calling this function, the DUT must be powered off,
e.g. with a call to `power_off()`.
At power on, recovery mode is set as specified by the
corresponding argument. When booting with recovery mode on, it
is the caller's responsibility to unplug/plug in a bootable
external storage device.
If the DUT requires a delay after powering on but before
processing inputs such as USB stick insertion, the delay is
handled by this method; the caller is not responsible for such
delays.
@param rec_mode Setting of recovery mode to be applied at
power on. default: REC_OFF aka 'off'
"""
raise NotImplementedError()
class _PantherController(_PowerStateController):
"""Power-state controller for Pantherand compatible boards.
For Panther, the 'cold_reset' signal is not connected
"""
# TODO(@moch): rename and modify appropriately
_TIME_TO_SWITCH_OFF = 10.0
_TIME_TO_BOOT = 10.0
_TIME_TO_HOLD_POWER_BUTTON = 1.2
@_inherit_docstring(_PowerStateController)
def cold_reset(self):
self._servo.set('pwr_button', 'press')
time.sleep(self._TIME_TO_HOLD_POWER_BUTTON)
self._servo.set('pwr_button', 'release')
time.sleep(self._TIME_TO_SWITCH_OFF)
self._servo.set('pwr_button', 'press')
time.sleep(self._TIME_TO_HOLD_POWER_BUTTON)
self._servo.set('pwr_button', 'release')
time.sleep(self._TIME_TO_BOOT)
class _AlexController(_PowerStateController):
"""Power-state controller for Alex and compatible boards.
For Alex, Lumpy, et al., the 'cold_reset' signal forces the unit
off, and leaves it off. Recovery mode and developer mode are
controlled by signals from the Servo board.
"""
# Time in seconds to allow the firmware to initialize itself and
# present the "INSERT" screen in recovery mode before actually
# inserting a USB stick to boot from.
_RECOVERY_INSERT_DELAY = 10.0
@_inherit_docstring(_PowerStateController)
def recovery_supported(self):
return True
@_inherit_docstring(_PowerStateController)
def power_off(self):
self.cold_reset()
@_inherit_docstring(_PowerStateController)
def power_on(self, rec_mode=REC_OFF):
self._servo.set_nocheck('rec_mode', rec_mode)
self._servo.power_short_press()
if rec_mode == REC_ON:
time.sleep(self._RECOVERY_INSERT_DELAY)
self._servo.set('rec_mode', REC_OFF)
class _LumpyController(_AlexController):
"""Power-state controller for Lumpy."""
@_inherit_docstring(_AlexController)
def power_off(self):
# The debug header for MP Lumpy's are missing a resistor to support
# cold_reset. Therefore to do a power off we simply long hold the
# power button. This should cover the most common types of failures
# in the lab. However if the unit is already off this will cause it to
# power on instead. In this case, re-run repair a second time to
# recover the device.
self._servo.power_long_press()
class _StumpyController(_AlexController):
"""Power-state controller for Stumpy."""
@_inherit_docstring(_AlexController)
def power_off(self):
# In test images in the lab, the 'autoreboot' upstart job will
# commonly configure the unit so that it reboots after cold
# reset. Since we mustn't rely on the OS, we can't know for
# sure whether the unit will be on or off after cold reset.
#
# Fortunately, the autoreboot setting only applies through one
# reset. So, after one reset, the unit may be on or off, but
# autoreboot is disabled. We can be sure to be off after a
# second reset as long as it happens before the unit has a
# chance to run the autoreboot job.
self.cold_reset()
self.cold_reset()
class _ParrotController(_PowerStateController):
"""Power-state controller for Parrot.
On Parrot, uncontrolled assertion of `cold_reset` sometimes
leaves the DUT unresponsive. The `cold_reset()` method
implemented in this class is the only known, reliable way to
assert the `cold_reset` signal on Parrot.
The `rec_mode` signal on Parrot is finicky. These are the
rules:
1. You can't read or write the signal unless the DUT is on.
2. The setting of the signal is only sampled during a cold
reset. The sampled setting applies to every boot until the
next cold reset.
3. After cold reset, the signal is turned off.
N.B. Rule 3 is subtle. Although `rec_mode` is off after reset,
because of rule 2, the DUT will continue to boot with the prior
recovery mode setting until the next cold reset.
"""
_RESET_HOLD_TIME = 0.0
_EC_RESET_DELAY = 0.5
# _PWR_BUTTON_READY_TIME: This represents the time after cold
# reset until the EC will be able to see a power button press.
# Used in power_off().
_PWR_BUTTON_READY_TIME = 4
# _REC_MODE_READY_TIME: This represents the time after power on
# until the EC will be able to see changes to rec_mode. Used
# in power_on().
_REC_MODE_READY_TIME = 0.75
@_inherit_docstring(_PowerStateController)
def recovery_supported(self):
return True
@_inherit_docstring(_PowerStateController)
def cold_reset(self):
# The sequence here leaves the DUT powered on, similar to
# Chrome EC devices.
self._servo.set_nocheck('pwr_button', 'press')
super(_ParrotController, self).cold_reset()
self._servo.set_nocheck('pwr_button', 'release')
@_inherit_docstring(_PowerStateController)
def power_off(self):
self.cold_reset()
time.sleep(self._PWR_BUTTON_READY_TIME)
self._servo.power_short_press()
@_inherit_docstring(_PowerStateController)
def power_on(self, rec_mode=REC_OFF):
self._servo.power_short_press()
time.sleep(self._REC_MODE_READY_TIME)
self._servo.set_nocheck('rec_mode', rec_mode)
self.cold_reset()
class _ChromeECController(_PowerStateController):
"""Power-state controller for systems with a Chrome EC.
For these systems, after releasing 'cold_reset' the DUT is left
powered on. Recovery mode is triggered by simulating keyboard
recovery by issuing commands to the EC.
"""
_RESET_HOLD_TIME = 0.1
_EC_RESET_DELAY = 0.0
_EC_CONSOLE_DELAY = 1.2
@_inherit_docstring(_PowerStateController)
def __init__(self, servo):
super(_ChromeECController, self).__init__(servo)
self._ec = chrome_ec.ChromeEC(servo)
@_inherit_docstring(_PowerStateController)
def recovery_supported(self):
return True
@_inherit_docstring(_PowerStateController)
def power_off(self):
self.cold_reset()
time.sleep(self._EC_CONSOLE_DELAY)
self._servo.power_long_press()
@_inherit_docstring(_PowerStateController)
def power_on(self, rec_mode=REC_OFF):
if rec_mode == REC_ON:
# Reset the EC to force it back into RO code; this clears
# the EC_IN_RW signal, so the system CPU will trust the
# upcoming recovery mode request.
self.cold_reset()
# Restart the EC, but leave the system CPU off...
self._ec.reboot('ap-off')
time.sleep(self._EC_CONSOLE_DELAY)
# ... and tell the EC to tell the CPU we're in recovery mode.
self._ec.set_hostevent(chrome_ec.HOSTEVENT_KEYBOARD_RECOVERY)
self._servo.power_short_press()
class _DaisyController(_ChromeECController):
"""Power-state controller for Snow/Daisy systems."""
_EC_CONSOLE_DELAY = 0.4
class _LinkController(_ChromeECController):
"""Power-state controller for Link.
Link has a Chrome EC, but the hardware supports the rec_mode
signal.
"""
# Time in seconds to allow the BIOS and EC to detect the
# 'rec_mode' signal after cold reset.
_RECOVERY_DETECTION_DELAY = 2.5
@_inherit_docstring(_ChromeECController)
def power_off(self):
self._ec.send_command('x86shutdown')
@_inherit_docstring(_ChromeECController)
def power_on(self, rec_mode=REC_OFF):
if rec_mode == REC_ON:
self._servo.set('rec_mode', REC_ON)
self.cold_reset()
time.sleep(self._RECOVERY_DETECTION_DELAY)
self._servo.set('rec_mode', REC_OFF)
else:
self._servo.power_short_press()
_CONTROLLER_BOARD_MAP = {
'daisy': _DaisyController,
'spring': _DaisyController,
'link': _LinkController,
'lumpy': _LumpyController,
'parrot': _ParrotController,
'stumpy': _StumpyController,
'x86-alex': _AlexController,
'panther':_PantherController
}
def create_controller(servo, board):
"""Create a power state controller instance.
The controller class will be selected based on the provided
board type, and instantiated with the provided servo instance.
@param servo Servo object that will be used to manipulate DUT
power states.
@param board Board name of the DUT to be controlled.
@returns An instance of a power state controller appropriate to
the given board type, or `None` if the board is
unsupported.
"""
return _CONTROLLER_BOARD_MAP.get(board, _PowerStateController)(servo)