Abstract out operations needed for Servo recovery.

Create a new class that provides a board-independent interface to
basic "power off" and "power on" operations used during Servo
recovery.  The new class is meant to allow autotest repair to work
successfully regardless of the OS software is working on the DUT,
and without requiring callers to know board details.

This change is preparatory only; it doesn't affect the list of
supported boards.  Before and after this change, Servo repair is
restricted to just 'x86-alex' and 'lumpy'.  Additional changes will
follow to add support for additional boards.

BUG=chromium-os:35804
TEST=platform_InstallTestImage on a locally attached Alex
TEST=manually trigger repair for an Alex in a local autotest installation

Change-Id: I05a92726d7c3c21955e7a42cf16142c9edac066d
Reviewed-on: https://gerrit.chromium.org/gerrit/45159
Reviewed-by: Tom Wai-Hong Tam <waihong@chromium.org>
Commit-Queue: Richard Barnette <jrbarnette@chromium.org>
Tested-by: Richard Barnette <jrbarnette@chromium.org>
diff --git a/server/cros/servo/power_state_controller.py b/server/cros/servo/power_state_controller.py
new file mode 100644
index 0000000..80e2988
--- /dev/null
+++ b/server/cros/servo/power_state_controller.py
@@ -0,0 +1,89 @@
+# 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 logging
+import time
+
+
+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.
+
+    TODO(jrbarnette):  This class is intended to be and abstract
+    superclass enabling support for multiple board types.
+    Currently, this class effectively hard-codes support for
+    x86-alex and lumpy only.
+
+    """
+
+    # _REC_MODE_DETECTION_DELAY:  Time in seconds to leave the
+    #   recovery button pressed after power on, in order to allow
+    #   the BIOS to detect the button state.
+    _REC_MODE_DETECTION_DELAY = 0.5
+
+    # Time required for the EC to be working after cold reset.
+    # Five seconds is at least twice as big as necessary for Alex,
+    # and is presumably good enough for all future systems.
+    _EC_RESET_DELAY = 5.0
+
+    DEV_ON = 'on'
+    DEV_OFF = 'off'
+
+    REC_ON = 'on'
+    REC_OFF = 'off'
+
+
+    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 power_off(self):
+        """Force the DUT to power off.
+
+        The device 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.
+
+        """
+        self._servo.cold_reset()
+
+    def power_on(self, dev_mode=DEV_OFF, rec_mode=REC_ON):
+        """Force the DUT to power on.
+
+        At power on, recovery mode and dev mode are set as specified
+        by the corresponding arguments.
+
+        Prior to calling this function, the DUT must be powered off,
+        e.g. with a call to `power_off()`.
+
+        @param dev_mode Setting of dev mode to be applied at power on.
+        @param rec_mode Setting of recovery mode to be applied at
+                        power on.
+
+        """
+        try:
+            self._servo.set_nocheck('dev_mode', dev_mode)
+            self._servo.set_nocheck('rec_mode', rec_mode)
+            self._servo.power_short_press()
+            if rec_mode == self.REC_ON:
+                time.sleep(self._REC_MODE_DETECTION_DELAY)
+                self._servo.set('rec_mode', self.REC_OFF)
+        except:
+            # In case anything went wrong we want to make sure to do a clean
+            # reset.
+            self._servo.set_nocheck('rec_mode', self.REC_OFF)
+            self._servo.warm_reset()
+            raise
diff --git a/server/cros/servo/servo.py b/server/cros/servo/servo.py
index c73cfbb..0113cee 100644
--- a/server/cros/servo/servo.py
+++ b/server/cros/servo/servo.py
@@ -11,9 +11,12 @@
 
 from autotest_lib.client.common_lib import error
 from autotest_lib.server import utils
+from autotest_lib.server.cros.servo import power_state_controller
 from autotest_lib.server.cros.servo import programmer
 
+
 class Servo(object):
+
     """Manages control of a Servo board.
 
     Servo is a board developed by hardware group to aide in the debug and
@@ -22,6 +25,7 @@
     class manages setting up and communicating with a servo demon (servod)
     process. It provides both high-level functions for common servo tasks and
     low-level functions for directly setting and reading gpios.
+
     """
 
     # Power button press delays in seconds.
@@ -47,16 +51,23 @@
     # Delays to deal with DUT state transitions.
     SLEEP_DELAY = 6
     BOOT_DELAY = 10
-    RECOVERY_BOOT_DELAY = 10
+
+    # 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
+
+    # Minimum time in seconds to hold the "cold_reset" or
+    # "warm_reset" signals asserted.
+    _DUT_RESET_DELAY = 0.5
 
     # Time required for the EC to be working after cold reset.
     # Five seconds is at least twice as big as necessary for Alex,
     # and is presumably good enough for all future systems.
     _EC_RESET_DELAY = 5.0
 
-    # Servo-specific delays.
-    MAX_SERVO_STARTUP_DELAY = 10
-    SERVO_SEND_SIGNAL_DELAY = 0.5
+    # Default minimum time interval between 'press' and 'release'
+    # keyboard events.
     SERVO_KEY_PRESS_DELAY = 0.1
 
     # Time between an usb disk plugged-in and detected in the system.
@@ -111,6 +122,7 @@
         self._server = None
         self._connect_servod(servo_host, servo_port)
         self._is_localhost = (servo_host == 'localhost')
+        self._power_state = power_state_controller.PowerStateController(self)
 
         # a string, showing what interface (host or dut) the USB device is
         # connected to.
@@ -352,7 +364,7 @@
         # After the reset, give the EC the time it needs to
         # re-initialize.
         self.set('cold_reset', 'on')
-        time.sleep(Servo.SERVO_SEND_SIGNAL_DELAY)
+        time.sleep(self._DUT_RESET_DELAY)
         self.set('cold_reset', 'off')
         time.sleep(self._EC_RESET_DELAY)
 
@@ -363,7 +375,7 @@
         Has the side effect of restarting the device.
         """
         self.set('warm_reset', 'on')
-        time.sleep(Servo.SERVO_SEND_SIGNAL_DELAY)
+        time.sleep(self._DUT_RESET_DELAY)
         self.set('warm_reset', 'off')
 
 
@@ -480,10 +492,12 @@
                                        therefore the DUT will reboot
                                        automatically after installation.
         """
-        # Turn the device off. This should happen before USB key detection, to
-        # prevent a recovery destined DUT from sensing the USB key due to the
-        # autodetection procedure.
-        self.initialize_dut(cold_reset=True)
+        # We're about to start plugging/unplugging the USB key.  We
+        # don't know the state of the DUT, or what it might choose
+        # to do to the device after hotplug.  To avoid surprises,
+        # force the DUT to be off.
+        self._server.hwinit()
+        self._power_state.power_off()
 
         # Set up Servo's usb mux.
         self.set('prtctl4_pwren', 'on')
@@ -518,20 +532,10 @@
                                        automatically after installation.
         """
         self.image_to_servo_usb(image_path, make_image_noninteractive)
-
-        # Boot in recovery mode.
-        try:
-            self.enable_recovery_mode()
-            self.power_short_press()
-            time.sleep(Servo.RECOVERY_BOOT_DELAY)
-            self.switch_usbkey('dut')
-            self.disable_recovery_mode()
-        except:
-            # In case anything went wrong we want to make sure to do a clean
-            # reset.
-            self.disable_recovery_mode()
-            self.warm_reset()
-            raise
+        self._power_state.power_on(dev_mode=self._power_state.DEV_OFF,
+                                   rec_mode=self._power_state.REC_ON)
+        time.sleep(self._RECOVERY_INSERT_DELAY)
+        self.switch_usbkey('dut')
 
 
     def _connect_servod(self, servo_host, servo_port):