autotest: refactor firmware installation for cros_host

The actual work of installing firmware is done by servo, so move related
code to servo.py from cros_host.py

BUG=chromium:877180
TEST=Ran below python code in chroot
    # cd autotest/files/site_utiles/deployment
    import common
    from prepare import dut
    d = dut.create_host('my-dut-hostname', 'coral', 'santa',
            'localhost', 9999)
    d.firmware_install('coral-firmware/R64-10068.71.0')

Change-Id: I744b0cb24e8b97b2cb75eea3549998d37c0c78d6
Reviewed-on: https://chromium-review.googlesource.com/1469224
Commit-Ready: Congbin Guo <guocb@chromium.org>
Tested-by: Congbin Guo <guocb@chromium.org>
Reviewed-by: Wai-Hong Tam <waihong@google.com>
diff --git a/server/cros/servo/servo.py b/server/cros/servo/servo.py
index fe564bc..da9f484 100644
--- a/server/cros/servo/servo.py
+++ b/server/cros/servo/servo.py
@@ -13,6 +13,7 @@
 import xmlrpclib
 
 from autotest_lib.client.common_lib import error
+from autotest_lib.server import utils as server_utils
 from autotest_lib.server.cros.servo import firmware_programmer
 
 # Time to wait when probing for a usb device, it takes on avg 17 seconds
@@ -20,6 +21,25 @@
 _USB_PROBE_TIMEOUT = 40
 
 
+def _extract_image_from_tarball(tarball, dest_dir, image_candidates):
+    """Try extracting the image_candidates from the tarball.
+
+    @param tarball: The path of the tarball.
+    @param dest_path: The path of the destination.
+    @param image_candidates: A tuple of the paths of image candidates.
+
+    @return: The first path from the image candidates, which succeeds, or None
+             if all the image candidates fail.
+    """
+    for image in image_candidates:
+        status = server_utils.system(
+                ('tar xf %s -C %s %s' % (tarball, dest_dir, image)),
+                timeout=60, ignore_status=True)
+        if status == 0:
+            return image
+    return None
+
+
 class _PowerStateController(object):
 
     """Class to provide board-specific power operations.
@@ -786,7 +806,7 @@
         else:
             raise error.TestError(
                     'No firmware programmer for servo version: %s' %
-                         servo_version)
+                    servo_version)
 
 
     def program_bios(self, image, rw_only=False):
@@ -818,9 +838,59 @@
         if not self.is_localhost():
             image = self._scp_image(image)
         if rw_only:
-           self._programmer_rw.program_ec(image)
+            self._programmer_rw.program_ec(image)
         else:
-           self._programmer.program_ec(image)
+            self._programmer.program_ec(image)
+
+
+    def _reprogram(self, tarball_path, firmware_name, image_candidates,
+                   rw_only):
+        """Helper function to reprogram firmware for EC or BIOS.
+
+        @param tarball_path: The path of the downloaded build tarball.
+        @param: firmware_name: either 'EC' or 'BIOS'.
+        @param image_candidates: A tuple of the paths of image candidates.
+        @param rw_only: True to only install firmware to its RW portions. Keep
+                the RO portions unchanged.
+
+        @raise: TestError if cannot extract firmware from the tarball.
+        """
+        dest_dir = os.path.dirname(tarball_path)
+        image = _extract_image_from_tarball(tarball_path, dest_dir,
+                                            image_candidates)
+        if not image:
+            if firmware_name == 'EC':
+                logging.info('Not a Chrome EC, ignore re-programming it')
+                return
+            else:
+                raise error.TestError('Failed to extract the %s image from '
+                                      'tarball' % firmware_name)
+
+        logging.info('Will re-program %s %snow', firmware_name,
+                     'RW ' if rw_only else '')
+
+        if firmware_name == 'EC':
+            self.program_ec(os.path.join(dest_dir, image), rw_only)
+        else:
+            self.program_bios(os.path.join(dest_dir, image), rw_only)
+
+
+    def program_firmware(self, model, tarball_path, rw_only=False):
+        """Program firmware (EC, if applied, and BIOS) of the DUT.
+
+        @param model: The DUT model name.
+        @param tarball_path: The path of the downloaded build tarball.
+        @param rw_only: True to only install firmware to its RW portions. Keep
+                the RO portions unchanged.
+        """
+        ap_image_candidates = ('image.bin', 'image-%s.bin' % model)
+        ec_image_candidates = ('ec.bin', '%s/ec.bin' % model)
+
+        self._reprogram(tarball_path, 'EC', ec_image_candidates, rw_only)
+        self._reprogram(tarball_path, 'BIOS', ap_image_candidates, rw_only)
+
+        self.get_power_state_controller().reset()
+        time.sleep(Servo.BOOT_DELAY)
 
 
     def _switch_usbkey_power(self, power_state, detection_delay=False):