firmware_ECRestoreFW: corrupts EC w/ a zero-binary

This patch makes firmware_ECRestoreFW to flash a zero-filled
file on EC to brick it, and checks if the firmware restoration
after it works.

BUG=b:137386185
TEST=ran the test on Octopus
$ test_that -b octopus 192.168.5.177 --autotest_dir ${AUTOTEST_DIR}
--args="local_tarball=${LOCAL_TARBALL}" firmware_ECRestoreFW

Change-Id: Ie4d22d44e7f83fced49dac85aaf9b9a59157dd54
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/3059771
Commit-Queue: Namyoon Woo <namyoon@chromium.org>
Tested-by: Namyoon Woo <namyoon@chromium.org>
Reviewed-by: Wai-Hong Tam <waihong@google.com>
Reviewed-by: Otabek Kasimov <otabek@google.com>
diff --git a/server/cros/servo/servo.py b/server/cros/servo/servo.py
index cc40a16..8b3d340 100644
--- a/server/cros/servo/servo.py
+++ b/server/cros/servo/servo.py
@@ -1469,12 +1469,13 @@
             self._programmer.program_ec(image)
 
 
-    def extract_ec_image(self, board, model, tarball_path):
+    def extract_ec_image(self, board, model, tarball_path, fake_image=False):
         """Helper function to extract EC image from downloaded tarball.
 
         @param board: The DUT board name.
         @param model: The DUT model name.
         @param tarball_path: The path of the downloaded build tarball.
+        @param fake_image: True to return a fake zero-filled image instead.
 
         @return: Path to extracted EC image.
         """
@@ -1518,6 +1519,17 @@
             _extract_image_from_tarball(tarball_path, dest_dir, mon_candidates,
                                         self.EXTRACT_TIMEOUT_SECS)
 
+            if fake_image:
+                # Create a small (25% of original size) zero-filled binary to
+                # replace the real ec_image
+                file_size = os.path.getsize(ec_image) / 4
+                ec_image = os.path.join(os.path.dirname(ec_image),
+                                        "zero_ec.bin")
+                dump_cmd = 'dd if=/dev/zero of=%s bs=4096 count=%d' % (
+                        os.path.join(dest_dir, ec_image), file_size / 4096)
+                if server_utils.system(dump_cmd, ignore_status=True) != 0:
+                    return None
+
             return os.path.join(dest_dir, ec_image)
         else:
             raise error.TestError('Failed to extract EC image from %s' %
diff --git a/server/hosts/cros_host.py b/server/hosts/cros_host.py
index 8182170..f74d011 100644
--- a/server/hosts/cros_host.py
+++ b/server/hosts/cros_host.py
@@ -829,7 +829,8 @@
                          verify_version=False,
                          try_scp=False,
                          install_ec=True,
-                         install_bios=True):
+                         install_bios=True,
+                         corrupt_ec=False):
         """Install firmware to the DUT.
 
         Use stateful update if the DUT is already running the same build.
@@ -856,6 +857,7 @@
                         the firmware and programming from the DUT.
         @param install_ec: True to install EC FW, and False to skip it.
         @param install_bios: True to install BIOS, and False to skip it.
+        @param corrupt_ec: True to flash EC with a false image (for test purpose).
 
         TODO(dshi): After bug 381718 is fixed, update here with corresponding
                     exceptions that could be raised.
@@ -904,7 +906,8 @@
         if install_ec:
             # Extract EC image from tarball
             logging.info('Extracting EC image.')
-            ec_image = self.servo.extract_ec_image(board, model, local_tarball)
+            ec_image = self.servo.extract_ec_image(board, model, local_tarball,
+                                                   corrupt_ec)
             logging.info('Extracted: %s', ec_image)
 
         bios_image = None
diff --git a/server/site_tests/firmware_ECRestoreFW/control b/server/site_tests/firmware_ECRestoreFW/control
index 5284bd5..6aa538b 100644
--- a/server/site_tests/firmware_ECRestoreFW/control
+++ b/server/site_tests/firmware_ECRestoreFW/control
@@ -17,6 +17,9 @@
 
 DOC = """
 This test flashes a wrong EC RW firmware and checks FW gets restored.
+
+If you want to use a local tarball file to test,
+add --args="local_tarball=<path>" to test_that command line.
 """
 
 if 'args_dict' not in locals():
diff --git a/server/site_tests/firmware_ECRestoreFW/firmware_ECRestoreFW.py b/server/site_tests/firmware_ECRestoreFW/firmware_ECRestoreFW.py
index 29df033..7790e8c 100644
--- a/server/site_tests/firmware_ECRestoreFW/firmware_ECRestoreFW.py
+++ b/server/site_tests/firmware_ECRestoreFW/firmware_ECRestoreFW.py
@@ -5,6 +5,7 @@
 
 """The autotest performing FW update, both EC and AP in CCD mode."""
 import logging
+import re
 
 from autotest_lib.client.common_lib import error
 from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
@@ -24,6 +25,33 @@
         if not self.check_ec_capability():
             raise error.TestNAError('Nothing needs to be tested on this device')
 
+        self.local_tarball = None
+        self.build = None
+        # find if "local_tarball" was given in the command line arguments.
+        for arg in cmdline_args:
+            match = re.search(r'^local_tarball=(.+)', arg)
+            if match:
+                self.local_tarball = match.group(1)
+                logging.info('Use local tarball %s', self.local_tarball)
+                break
+        else:
+            # Get the latest firmware release from the server.
+            # Even this test uses a fake EC image, it needs to download
+            # the release to get some subsidiary binary (like npcx_monitor.bin).
+            platform = self.faft_config.platform
+
+            # Get the parent (a.k.a. reference board or baseboard), and hand it
+            # to get_latest_release_version so that it can use it in search as
+            # secondary candidate. For example, bob doesn't have its own release
+            # directory, but its parent, gru does.
+            parent = getattr(self.faft_config, 'parent', None)
+
+            self.build = host.get_latest_release_version(platform, parent)
+
+            if not self.build:
+                raise error.TestError(
+                        'Cannot locate the latest release for %s' % platform)
+            logging.info('Will use the build %s', self.build)
         self.backup_firmware()
 
     def cleanup(self):
@@ -48,10 +76,12 @@
         """
 
         try:
-            host.firmware_install(build="",
+            host.firmware_install(build=self.build,
                                   dest=self.resultsdir,
+                                  local_tarball=self.local_tarball,
                                   install_ec=True,
-                                  install_bios=False)
+                                  install_bios=False,
+                                  corrupt_ec=True)
         except error.TestError as e:
             # It failed before the test attempts to install firmware.
             # It could be either devserver timeout or servo device error.