Add an autotest reporgramming DUT firmware through servo.

This autotest allows to program firmware on an 'unresponsive' Chrome
OS DUT, using the servo device. As submitted it supports only Link,
more platforms will be integrated shortly.

A new module (server/cros/programmer.py) is being added to provide the
firmware programmer interface and implementation and
server/cros/servo.py is being extended to export the new module as a
servo object service and adds an interface to execute an arbitrary
shell command on the host controlling the servo device.

Chrome OS DUTs use two distinct firmware types, EC and and AP (a.k.a
bootprom) firmware. Bad firmware can easily brick a DUT, this is why
it is necessary to be able to program firmware on 'dead' DUTs. Servo
devices provide the means of doing it, the new module requires the DUT
to be connected to a servo device. Servo device state is preserved
across the programming process.

The new module is still incomplete, it fully supports Link devices but
not Lucas (ARM platforms) yet.

BUG=chrome-os-partner:15610
TEST=try reporgramming a Link connected to both local and remote servo:

  . observe current FW versions on the DUT:

   $  echo $(ssh 192.168.1.4 ectool version)
   RO version: link_v1.1.618-52440ef RW version: link_v1.1.618-52440ef Firmware copy: RO Build info: link_v1.1.618-52440ef 2012-12-05 10:42:46 @build51-m2
   $ echo  $(ssh 192.168.1.4  crossystem fwid)
   Google_Link.3354.0.0

  . local servo
   run_remote_tests.sh --board=link --servo --remote=192.168.1.4 --allow_offline_remote fwupdate --args \
    'fwurl=gs://chromeos-releases/canary-channel/link/3419.0.0/ChromeOS-firmware-R25-3419.0.0-link.tar.bz2 \
     board=link'

  . observe the test succeed, check versions
   $ echo $(ssh 192.168.1.4 ectool version)
   RO version: link_v1.1.626-e927d15 RW version: link_v1.1.626-e927d15 Firmware copy: RO Build info: link_v1.1.626-e927d15 2012-12-17 10:42:49 @build51-m2
   $ echo  $(ssh 192.168.1.4  crossystem fwid)
   Google_Link.3419.0.0

  . connect servo to a Beaglebone (at 192.168.1.103), reboot the board and verify that servod is running

  . run with remote servo
   run_remote_tests.sh --board=link --remote=192.168.1.4 --allow_offline_remote fwupdate --args \
    'fwurl=gs://chromeos-releases/canary-channel/link/3354.0.0/ChromeOS-firmware-R25-3354.0.0-link.tar.bz2 \
     board=link \
     servo_host=192.168.1.103 \
     servo_port=9999'

  . observe the test succeed, check versions
    $ echo $(ssh 192.168.1.4 ectool version)
    RO version: link_v1.1.618-52440ef RW version: link_v1.1.618-52440ef Firmware copy: RO Build info: link_v1.1.618-52440ef 2012-12-05 10:42:46 @build51-m2
    $ echo  $(ssh 192.168.1.4  crossystem fwid)
    Google_Link.3354.0.0

    BUG=chrome-os-partner:15610
    TEST=manual
      The new methods were used in an upcoming autotest module. It allowed
      to reprogram EC and AP firmware on a Link controlled by both local
      and remote servos.

Change-Id: I7ce48ff4cc5135f9ac10c3b4bc94bd9826e2f103
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/39408
diff --git a/server/cros/servo.py b/server/cros/servo.py
index 9ab7b9c..5bbd882 100644
--- a/server/cros/servo.py
+++ b/server/cros/servo.py
@@ -5,9 +5,13 @@
 # Expects to be run in an environment with sudo and no interactive password
 # prompt, such as within the Chromium OS development chroot.
 
+import os
+
 import logging, re, time, xmlrpclib
+
 from autotest_lib.client.common_lib import error
 from autotest_lib.server import utils
+from autotest_lib.server.cros import programmer
 
 class Servo(object):
     """Manages control of a Servo board.
@@ -141,6 +145,19 @@
         self._is_localhost = (servo_host == 'localhost')
         self._target_host = target_host
 
+        # Commands on the servo host must be run by the superuser. Our account
+        # on Beaglebone is root, but locally we might be running as a
+        # different user. If so - `sudo ' will have to be added to the
+        # commands.
+        if self._is_localhost:
+            self._sudo_required = utils.system_output('id -u') != '0'
+            self._ssh_prefix = ''
+        else:
+            common_options = '-o PasswordAuthentication=no'
+            self._sudo_required = False
+            self._ssh_prefix = 'ssh %s root@%s ' % (common_options, servo_host)
+            self._scp_cmd_template = 'scp %s ' % common_options
+            self._scp_cmd_template += '%s ' + 'root@' + servo_host + ':%s'
 
     def get_target_hostname(self):
         """Retrieves target (DUT) hostname."""
@@ -608,3 +625,46 @@
         except:
             logging.error('Connection to servod failed')
             raise
+
+
+    def _scp_image(self, image_path):
+        """Copy image to the servo host.
+
+        When programming a firmware image on the DUT, the image must be
+        located on the host to which the servo device is connected. Sometimes
+        servo is controlled by a remote host, in this case the image needs to
+        be transferred to the remote host.
+
+        @param image_path: a string, name of the firmware image file to be
+               transferred.
+        @return: a string, full path name of the copied file on the remote.
+        """
+
+        dest_path = os.path.join('/tmp', os.path.basename(image_path))
+        scp_cmd = self._scp_cmd_template % (image_path, dest_path)
+        utils.system(scp_cmd)
+        return dest_path
+
+
+    def system_on_servo(self, command):
+        """Execute the passed in command on the servod host."""
+        if self._sudo_required:
+            command = 'sudo -n %s' % command
+        if self._ssh_prefix:
+            command = "%s '%s'" % (self._ssh_prefix, command)
+        logging.info('Will execute on servo host: %s' % command)
+        utils.system(command)
+
+
+    def program_ec(self, board, image):
+        """Program EC on a given board using given image."""
+        if not self.is_localhost():
+            image = self._scp_image(image)
+        programmer.program_ec(board, self, image)
+
+
+    def program_bootprom(self, board, image):
+        """Program bootprom on a given board using given image."""
+        if not self.is_localhost():
+            image = self._scp_image(image)
+        programmer.program_bootprom(board, self, image)